OpenSolaris_b135/cmd/avs/sv/svadm.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 <sys/stat.h>
#include <sys/mkdev.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <stdio.h>
#include <locale.h>
#include <unistd.h>
#include <search.h>
#include <libgen.h>
#include <nsctl.h>

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

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

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

#include "../sv/svadm.h"


static int sv_max_devices;


/*
 * support for the special cluster tag "local" to be used with -C in a
 * cluster for local volumes.
 */

#define	SV_LOCAL_TAG	"local"

static int sv_islocal;

/*
 * libcfg access.
 */

static CFGFILE *cfg;		/* libcfg file pointer */
static int cfg_changed;		/* set to 1 if we need to commit changes */

static char *cfg_cluster_tag;	/* local cluster tag */

static char *implicit_tag;	/* implicit cluster tag */


/*
 * Print width for print_sv() output.
 */

#define	STATWIDTH	(SV_MAXPATH / 2)

/*
 * Pathnames.
 */

static const caddr_t sv_rpath = SV_DEVICE;

/*
 * Functions.
 */

static int read_config_file(const caddr_t, sv_name_t []);
static int enable_dev(sv_name_t *);
static int disable_dev(const caddr_t);
static void error(spcs_s_info_t *, caddr_t, ...);
static void create_cfg_hash();
static int find_in_hash(char *path);
static void destroy_hashtable();
static void remove_from_cfgfile(char *path, int setnumber);

static caddr_t program;

static void
sv_cfg_open(CFGLOCK mode)
{
	if (cfg != NULL)
		return;

	cfg = cfg_open(NULL);
	if (cfg == NULL) {
		error(NULL, gettext("unable to access the configuration"));
		/* NOTREACHED */
	}

	if (cfg_cluster_tag && *cfg_cluster_tag) {
		cfg_resource(cfg, cfg_cluster_tag);
	} else {
		cfg_resource(cfg, NULL);
	}
	if (!cfg_lock(cfg, mode)) {
		error(NULL, gettext("unable to lock the configuration"));
		/* NOTREACHED */
	}
}


static void
sv_cfg_close(void)
{
	if (cfg == NULL)
		return;

	if (cfg_changed) {
		(void) cfg_commit(cfg);
		cfg_changed = 0;
	}

	cfg_close(cfg);
	cfg = NULL;
}



static void
usage(void)
{
	(void) fprintf(stderr, gettext("usage:\n"));

	(void) fprintf(stderr, gettext(
	    "\t%s -h                                 help\n"), program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag]                           display status\n"),
	    program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag] -i                        display "
	    "extended status\n"), program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag] -v                        display "
	    "version number\n"), program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag] -e { -f file | volume }   enable\n"), program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag] -d { -f file | volume }   disable\n"), program);

	(void) fprintf(stderr, gettext(
	    "\t%s [-C tag] -r { -f file | volume }   reconfigure\n"), program);

	sv_cfg_close();
}

static void
message(caddr_t prefix, spcs_s_info_t *status, caddr_t string, va_list ap)
{
	(void) fprintf(stderr, "%s: %s: ", program, prefix);
	(void) vfprintf(stderr, string, ap);
	(void) fprintf(stderr, "\n");

	if (status) {
		spcs_s_report(*status, stderr);
		spcs_s_ufree(status);
	}
}


static void
error(spcs_s_info_t *status, caddr_t string, ...)
{
	va_list ap;
	va_start(ap, string);

	message(gettext("error"), status, string, ap);

	va_end(ap);

	sv_cfg_close();
	exit(1);
}


static void
warn(spcs_s_info_t *status, caddr_t string, ...)
{
	va_list ap;
	va_start(ap, string);

	message(gettext("warning"), status, string, ap);

	va_end(ap);
}


static void
sv_get_maxdevs(void)
{
	sv_name_t svn[1];
	sv_list_t svl;
	int fd;

	if (sv_max_devices > 0)
		return;

	fd = open(sv_rpath, O_RDONLY);
	if (fd < 0)
		error(NULL, gettext("unable to open %s: %s"),
			sv_rpath, strerror(errno));

	bzero(&svl, sizeof (svl));
	bzero(&svn[0], sizeof (svn));

	svl.svl_names = &svn[0];
	svl.svl_error = spcs_s_ucreate();

	if (ioctl(fd, SVIOC_LIST, &svl) < 0) {
		(void) close(fd);
		error(&svl.svl_error, gettext("unable to get max devs"));
	}

	spcs_s_ufree(&svl.svl_error);
	sv_max_devices = svl.svl_maxdevs;

	(void) close(fd);
}


static sv_name_t *
sv_alloc_svnames(void)
{
	sv_name_t *svn = NULL;

	sv_get_maxdevs();

	svn = calloc(sv_max_devices, sizeof (*svn));
	if (svn == NULL) {
		error(NULL, "unable to allocate %ld bytes of memory",
		    sv_max_devices * sizeof (*svn));
	}

	return (svn);
}


static void
sv_check_dgislocal(char *dgname)
{
	char *othernode;
	int rc;

	/*
	 * check where this disk service is mastered
	 */

	rc = cfg_dgname_islocal(dgname, &othernode);
	if (rc < 0) {
		error(NULL, gettext("unable to find "
		    "disk service, %s: %s"), dgname, strerror(errno));
	}

	if (rc == 0) {
		error(NULL, gettext("disk service, %s, is "
		    "active on node \"%s\"\nPlease re-issue "
		    "the command on that node"), dgname, othernode);
	}
}


/*
 * Carry out cluster based checks for a specified volume, or just
 * global options.
 */
static void
sv_check_cluster(char *path)
{
	char dgname[CFG_MAX_BUF];
	static int sv_iscluster = -1;	/* set to 1 if running in a cluster */

	/*
	 * Find out if we are running in a cluster
	 */
	if (sv_iscluster == -1) {
		if ((sv_iscluster = cfg_iscluster()) < 0) {
			error(NULL, gettext("unable to ascertain environment"));
		}
	}

	if (!sv_iscluster && cfg_cluster_tag != NULL) {
		error(NULL, gettext("-C is not valid when not in a cluster"));
	}

	if (!sv_iscluster || sv_islocal || path == NULL) {
		return;
	}


	/*
	 * Cluster-only checks on pathname
	 */
	if (cfg_dgname(path, dgname, sizeof (dgname)) == NULL) {
		error(NULL, gettext("unable to determine "
		    "disk group name for %s"), path);
		return;
	}

	if (cfg_cluster_tag != NULL) {
		/*
		 * Do dgislocal check now in case path did not contain
		 * a dgname.
		 *
		 * E.g. adding a /dev/did/ device to a disk service.
		 */

		sv_check_dgislocal(cfg_cluster_tag);
	}

	if (strcmp(dgname, "") == 0)
		return;		/* NULL dgname is valid */

	if (cfg_cluster_tag == NULL) {
		/*
		 * Implicitly set the cluster tag to dgname
		 */

		sv_check_dgislocal(dgname);

		if (implicit_tag) {
			free(implicit_tag);
			implicit_tag = NULL;
		}

		implicit_tag = strdup(dgname);
		if (implicit_tag == NULL) {
			error(NULL,
			    gettext("unable to allocate memory "
			    "for cluster tag"));
		}
	} else {
		/*
		 * Check dgname and cluster tag from -C are the same.
		 */

		if (strcmp(dgname, cfg_cluster_tag) != 0) {
			error(NULL,
			    gettext("-C (%s) does not match disk group "
			    "name (%s) for %s"), cfg_cluster_tag,
			    dgname, path);
		}

		/*
		 * sv_check_dgislocal(cfg_cluster_tag) was called above.
		 */
	}
}


static void
print_version(void)
{
	sv_version_t svv;
	int fd;

	bzero(&svv, sizeof (svv));
	svv.svv_error = spcs_s_ucreate();

	fd = open(sv_rpath, O_RDONLY);
	if (fd < 0) {
		warn(NULL, gettext("unable to open %s: %s"),
			sv_rpath, strerror(errno));
		return;
	}

	if (ioctl(fd, SVIOC_VERSION, &svv) != 0) {
		error(&svv.svv_error,
		    gettext("unable to read the version number"));
		/* NOTREACHED */
	}

	spcs_s_ufree(&svv.svv_error);
#ifdef DEBUG
	(void) printf(gettext("Storage Volume version %d.%d.%d.%d\n"),
	    svv.svv_major_rev, svv.svv_minor_rev,
	    svv.svv_micro_rev, svv.svv_baseline_rev);
#else
	if (svv.svv_micro_rev) {
		(void) printf(gettext("Storage Volume version %d.%d.%d\n"),
		    svv.svv_major_rev, svv.svv_minor_rev, svv.svv_micro_rev);
	} else {
		(void) printf(gettext("Storage Volume version %d.%d\n"),
		    svv.svv_major_rev, svv.svv_minor_rev);
	}
#endif

	(void) close(fd);
}

int
main(int argc, char *argv[])
{
	extern int optind;
	extern char *optarg;
	char *conf_file = NULL;
	int enable, disable, compare, print, version;
	int opt, Cflag, fflag, iflag;
	int rc;

	(void) setlocale(LC_ALL, "");
	(void) textdomain("svadm");

	program = strdup(basename(argv[0]));

	Cflag = fflag = iflag = 0;
	compare = enable = disable = version = 0;

	print = 1;

	while ((opt = getopt(argc, argv, "C:def:hirv")) != EOF) {
		switch (opt) {

		case 'C':
			if (Cflag) {
				warn(NULL,
				    gettext("-C specified multiple times"));
				usage();
				exit(2);
				/* NOTREACHED */
			}

			Cflag++;
			cfg_cluster_tag = optarg;
			break;

		case 'e':
			print = 0;
			enable++;
			break;

		case 'd':
			print = 0;
			disable++;
			break;

		case 'f':
			fflag++;
			conf_file = optarg;
			break;

		case 'i':
			iflag++;
			break;

		case 'r':
			/* Compare running system with sv.cf */
			print = 0;
			compare++;
			break;

		case 'v':
			print = 0;
			version++;
			break;

		case 'h':
			usage();
			exit(0);

		default:
			usage();
			exit(2);
			/* NOTREACHED */
		}
	}


	/*
	 * Usage checks
	 */

	if ((enable + disable + compare) > 1) {
		warn(NULL, gettext("-d, -e and -r are mutually exclusive"));
		usage();
		exit(2);
	}

	if (fflag && (print || version)) {
		warn(NULL, gettext("-f is only valid with -d, -e or -r"));
		usage();
		exit(2);
	}

	if (fflag && optind != argc) {
		usage();
		exit(2);
	}

	if (print || version) {
		/* check for no more args */

		if (optind != argc) {
			usage();
			exit(2);
		}
	} else {
		/* check for inline args */

		if (!fflag && (argc - optind) != 1) {
			usage();
			exit(2);
		}
	}

	if (!print && iflag) {
		usage();
		exit(2);
	}


	/*
	 * Check for the special cluster tag and convert into the
	 * internal representation.
	 */

	if (cfg_cluster_tag != NULL &&
	    strcmp(cfg_cluster_tag, SV_LOCAL_TAG) == 0) {
		cfg_cluster_tag = "-";
		sv_islocal = 1;
	}


	/*
	 * Process commands
	 */

	if (optind != argc) {
		/* deal with inline volume argument */

		rc = 0;
		if (enable)
			rc = enable_one_sv(argv[optind]);
		else if (disable)
			rc = disable_one_sv(argv[optind]);
		else /* if (compare) */
			compare_one_sv(argv[optind]);

		if (rc != 0)
			return (1);

		return (0);
	}

	rc = 0;
	if (enable)
		rc = enable_sv(conf_file);
	else if (disable)
		rc = disable_sv(conf_file);
	else if (compare)
		compare_sv(conf_file);
	else if (print)
		print_sv(iflag);
	else /* if (version) */
		print_version();

	if (rc != 0)
		return (1);

	return (0);
}



/* LINT - not static as fwcadm uses it */
static int
enable_sv(char *conf_file)
{
	int index;
	sv_name_t *svn;
	int cnt;
	int rc, ret;

	svn = sv_alloc_svnames();

	index = read_config_file(conf_file, svn);

	rc = ret = 0;

	for (cnt = 0; cnt < index; cnt++) {

		/*
		 * Check for more data.
		 */
		if (svn[cnt].svn_path[0] == '\0') {
			/*
			 * This was set when reading sv.conf.  After the last
			 * line svn_path was set to \0, so we are finished.
			 * We shouldn't get here, but put this in just in
			 * case.
			 */
			break;
		}
		rc = enable_dev(&svn[cnt]);
		if (rc && !ret)
			ret = rc;
	}

	sv_cfg_close();

	return (ret);
}


/* LINT - not static as fwcadm uses it */
static int
enable_one_sv(caddr_t path)
{
	sv_name_t svn;
	int rc;

	sv_get_maxdevs();

	bzero(&svn, sizeof (svn));
	(void) strncpy(svn.svn_path, path, sizeof (svn.svn_path));
	svn.svn_mode = (NSC_DEVICE | NSC_CACHE);

	/* force NULL termination */
	svn.svn_path[sizeof (svn.svn_path) - 1] = '\0';

	rc = enable_dev(&svn);
	sv_cfg_close();

	return (rc);
}


static int
enable_dev(sv_name_t *svn)
{
	char buf[CFG_MAX_BUF];
	struct stat stb;
	sv_conf_t svc;
	int fd;
	int sev;
	int rc;
	char *lcltag;
	char *altname;

	sv_check_cluster(svn->svn_path);
	sv_cfg_open(CFG_WRLOCK);

	bzero(&svc, sizeof (svc));

	if (stat(svn->svn_path, &stb) != 0) {
		warn(NULL, gettext("unable to access %s: %s"),
			svn->svn_path, strerror(errno));
		return (1);
	}

	if (!S_ISCHR(stb.st_mode)) {
		warn(NULL, gettext("%s is not a character device - ignored"),
		    svn->svn_path);
		return (1);
	}

	svc.svc_major = major(stb.st_rdev);
	svc.svc_minor = minor(stb.st_rdev);
	(void) strncpy(svc.svc_path, svn->svn_path, sizeof (svc.svc_path));

	fd = open(sv_rpath, O_RDONLY);
	if (fd < 0) {
		warn(NULL, gettext("unable to open %s: %s"),
			svn->svn_path, strerror(errno));
		return (1);
	}

	svc.svc_flag = svn->svn_mode;
	svc.svc_error = spcs_s_ucreate();

	/* first, check for duplicates */
	rc = cfg_get_canonical_name(cfg, svn->svn_path, &altname);
	if (rc < 0) {
		spcs_log("sv", NULL, gettext("Unable to parse config file"));
		warn(NULL, gettext("Unable to parse config file"));
		(void) close(fd);
		return (1);
	}
	if (rc) {
		error(NULL, gettext("'%s' has already been configured as "
		    "'%s'.  Re-enter command with the latter name."),
		    svn->svn_path, altname);
	}

	/* secondly, try to insert it into the dsvol config */
	if (implicit_tag && *implicit_tag) {
		lcltag = implicit_tag;
	} else if (cfg_cluster_tag && *cfg_cluster_tag) {
		lcltag = cfg_cluster_tag;
	} else {
		lcltag = "-";
	}
	rc = cfg_add_user(cfg, svn->svn_path, lcltag, "sv");
	if (CFG_USER_ERR == rc) {
		spcs_log("sv", NULL,
		    gettext("%s: unable to put %s into dsvol cfg"),
		    program, svn->svn_path);
		warn(NULL, gettext("unable to put %s into dsvol cfg"),
		    svn->svn_path);
		(void) close(fd);
		return (1);
	}
	cfg_changed = 1;

	if (CFG_USER_OK == rc) {
		/* success */
		(void) close(fd);
		return (0);
	}

	if (ioctl(fd, SVIOC_ENABLE, &svc) < 0) {
		if ((CFG_USER_REPEAT == rc) && (SV_EENABLED == errno)) {
			/* it's ok -- we were just double-checking */
			(void) close(fd);
			return (0);
		}

		spcs_log("sv", &svc.svc_error,
		    gettext("%s: unable to enable %s"),
		    program, svn->svn_path);

		warn(&svc.svc_error, gettext("unable to enable %s"),
			svn->svn_path);

		/* remove it from dsvol, if we're the ones who put it in */
		if (CFG_USER_FIRST == rc) {
			(void) cfg_rem_user(cfg, svn->svn_path, lcltag, "sv");
		}
		(void) close(fd);
		return (1);
	}

	spcs_log("sv", NULL, gettext("%s: enabled %s"),
	    program, svn->svn_path);

	if (implicit_tag != NULL) {
#ifdef DEBUG
		if (cfg_cluster_tag != NULL) {
			error(NULL,
			    gettext("enable_dev: -C %s AND implicit_tag %s!"),
			    cfg_cluster_tag, implicit_tag);
		}
#endif

		(void) snprintf(buf, sizeof (buf), "%s - %s",
		    svc.svc_path, implicit_tag);
	} else {
		(void) strcpy(buf, svc.svc_path);
	}

	rc = 0;
	if (cfg_put_cstring(cfg, "sv", buf, sizeof (buf)) < 0) {
		warn(NULL,
		    gettext("unable to add %s to configuration storage: %s"),
		    svc.svc_path, cfg_error(&sev));
		rc = 1;
	}

	cfg_changed = 1;
	spcs_s_ufree(&svc.svc_error);
	(void) close(fd);

	return (rc);
}


/*
 * This routine parses the config file passed in via conf_file and
 * stores the data in the svn array.  The return value is the number
 * of entries read from conf_file.  If an error occurs the error()
 * routine is called (which exits the program).
 */
static int
read_config_file(const caddr_t conf_file, sv_name_t svn[])
{
	char line[1024], rdev[1024], junk[1024];
	struct stat stb;
	int lineno;
	int cnt, i;
	int index = 0;		/* Current location in svn array	*/
	sv_name_t *cur_svn;	/* Pointer to svn[index]		*/
	FILE *fp;

	if (access(conf_file, R_OK) != 0 ||
	    stat(conf_file, &stb) != 0 ||
	    !S_ISREG(stb.st_mode)) {
		error(NULL, gettext("cannot read config file %s"), conf_file);
	}

	if ((fp = fopen(conf_file, "r")) == NULL) {
		error(NULL, gettext("unable to open config file %s: %s"),
			conf_file, strerror(errno));
	}

	lineno = 0;

	while (fgets(line, sizeof (line), fp) != NULL) {
		lineno++;

		i = strlen(line);

		if (i < 1)
			continue;

		if (line[i-1] == '\n')
			line[i-1] = '\0';
		else if (i == (sizeof (line) - 1)) {
			warn(NULL, gettext(
		"line %d: line too long -- should be less than %d characters"),
				lineno, (sizeof (line) - 1));
			warn(NULL, gettext("line %d: ignored"), lineno);
		}

		/*
		 * check for comment line.
		 */
		if (line[0] == '#')
			continue;

		cnt = sscanf(line, "%s %s", rdev, junk);

		if (cnt != 1 && cnt != 2) {
			if (cnt > 0) {
				warn(NULL, gettext("line %d: invalid format"),
					lineno);
				warn(NULL, gettext("line %d: ignored"), lineno);
			}
			continue;
		}

		rdev[sizeof (rdev) - 1] = '\0';

		cur_svn = &svn[index];  /* For easier reading below */

		if (strlen(rdev) >= sizeof (cur_svn->svn_path)) {
			warn(NULL, gettext(
		"line %d: raw device name (%s) longer than %d characters"),
				lineno, rdev,
				(sizeof (cur_svn->svn_path) - 1));
			warn(NULL, gettext("line %d: ignored"), lineno);
			continue;
		}

		(void) strcpy(cur_svn->svn_path, rdev);
		cur_svn->svn_mode = (NSC_DEVICE | NSC_CACHE);

		index++;
	}

	/* Set the last path to NULL */
	svn[index].svn_path[0] = '\0';

	(void) fclose(fp);

	return (index);
}


/*
 * Disable the device from the kernel configuration.
 *
 * RETURN:
 *   0 on success
 *   non-zero on failure.
 *
 * Failures are reported to the user.
 */
static int
disable_dev(const caddr_t path)
{
	struct stat stb;
	sv_conf_t svc;
	int fd;

	sv_check_cluster(path);

	if (stat(path, &stb) < 0) {
		svc.svc_major = (major_t)-1;
		svc.svc_minor = (minor_t)-1;
	} else {
		svc.svc_major = major(stb.st_rdev);
		svc.svc_minor = minor(stb.st_rdev);
	}

	if ((fd = open(sv_rpath, O_RDONLY)) < 0) {
		warn(NULL, gettext("unable to open %s: %s"),
			sv_rpath, strerror(errno));
		return (-1);
	}

	(void) strcpy(svc.svc_path, path);
	svc.svc_error = spcs_s_ucreate();

	/*
	 * Issue the ioctl to attempt to disable this device.  Note that all
	 * the libdscfg details are handled elsewhere.
	 */
	if (ioctl(fd, SVIOC_DISABLE, &svc) < 0) {
		if (errno != SV_EDISABLED) {
			spcs_log("sv", &svc.svc_error,
					gettext("%s: unable to disable %s"),
					program, path);

			warn(&svc.svc_error,
					gettext("unable to disable %s"), path);
			(void) close(fd);
			return (-1);
		}
	}

	spcs_log("sv", NULL, gettext("%s: disabled %s"), program, path);

	spcs_s_ufree(&svc.svc_error);
	(void) close(fd);

	return (0);
}


static void
print_cluster_tag(const int setnumber)
{
	char buf[CFG_MAX_BUF];
	char key[CFG_MAX_KEY];

	bzero(buf, sizeof (buf));
	(void) snprintf(key, sizeof (key), "sv.set%d.cnode", setnumber);

	(void) cfg_get_cstring(cfg, key, buf, sizeof (buf));

	if (*buf != '\0') {
		if (strcmp(buf, "-") == 0) {
			(void) printf(" [%s]", gettext("local to node"));
		} else {
			(void) printf(" [%s: %s]", gettext("cluster"), buf);
		}
	}
}


/* LINT - not static as fwcadm uses it */
static void
print_sv(int verbose)
{
	sv_name_t *svn, *svn_system;	/* Devices in system */
	sv_list_t svl_system;
	int fd, i;
	int setnumber;

	sv_check_cluster(NULL);
	sv_cfg_open(CFG_RDLOCK);

	svn_system = sv_alloc_svnames();

	if ((fd = open(sv_rpath, O_RDONLY)) < 0) {
		(void) printf(gettext("unable to open %s: %s"),
			sv_rpath, strerror(errno));
		return;
	}

	/* Grab the system list from the driver */
	svl_system.svl_count = sv_max_devices;
	svl_system.svl_names = &svn_system[0];
	svl_system.svl_error = spcs_s_ucreate();

	if (ioctl(fd, SVIOC_LIST, &svl_system) < 0) {
		error(&svl_system.svl_error, gettext("unable to get list"));
	}

	spcs_s_ufree(&svl_system.svl_error);
	(void) close(fd);

	/*
	 * We build a hashmap out of the entries from the config file to make
	 * searching faster. We end up taking a performance hit when the # of
	 * volumes is small, but for larger configurations it's a
	 * HUGE improvement.
	 */

	/* build the hashtable */
	cfg_rewind(cfg, CFG_SEC_CONF);
	create_cfg_hash();

	/*
	 * For each volume found from the kernel, print out
	 * info about it from the kernel.
	 */
	for (i = 0; i < svl_system.svl_count; i++) {
		if (*svn_system[i].svn_path == '\0') {
			break;
		}

		svn = &svn_system[i];
		if (svn->svn_mode == 0) {
#ifdef DEBUG
			(void) printf(gettext("%s [kernel guard]\n"),
			    svn->svn_path);
#endif
			continue;
		}
		/* get sv entry from the hashtable */
		if ((setnumber = find_in_hash(svn->svn_path)) != -1) {
			(void) printf("%-*s", STATWIDTH, svn->svn_path);

			if (verbose) {
				print_cluster_tag(setnumber);
			}

			(void) printf("\n");

		} else {
			/*
			 * We didn't find the entry in the hashtable.  Let
			 * the user know that the persistent storage is
			 * inconsistent with the kernel configuration.
			 */
			if (cfg_cluster_tag == NULL)
				warn(NULL, gettext(
					"%s is configured, but not in the "
					"config storage"), svn->svn_path);
		}
	}

	/* free up the hashtable */
	destroy_hashtable();

	sv_cfg_close();
}


/* LINT - not static as fwcadm uses it */
static int
disable_sv(char *conf_file)
{
	sv_name_t *svn, *svn_system;	/* Devices in system */
	sv_list_t svl_system;
	int fd, i, setnumber;
	int rc, ret;

	svn_system = sv_alloc_svnames();

	rc = ret = 0;

	if (conf_file == NULL) {
		if ((fd = open(sv_rpath, O_RDONLY)) < 0) {
			(void) printf(gettext("unable to open %s: %s"),
				sv_rpath, strerror(errno));
			return (1);
		}

		/* Grab the system list from the driver */
		svl_system.svl_count = sv_max_devices;
		svl_system.svl_names = &svn_system[0];
		svl_system.svl_error = spcs_s_ucreate();

		if (ioctl(fd, SVIOC_LIST, &svl_system) < 0) {
			error(&(svl_system.svl_error),
					gettext("unable to get list"));
		}

		spcs_s_ufree(&(svl_system.svl_error));
		(void) close(fd);
	} else {
		svl_system.svl_count = read_config_file(conf_file, svn_system);
	}


	for (i = 0; i < svl_system.svl_count; i++) {
		if (*svn_system[i].svn_path == '\0')
			break;

		svn = &svn_system[i];

		sv_check_cluster(svn->svn_path);
		sv_cfg_open(CFG_WRLOCK);
		create_cfg_hash();
		rc = 0;
		if ((setnumber = find_in_hash(svn->svn_path)) != -1) {
			if ((rc = disable_dev(svn->svn_path)) != -1) {
				remove_from_cfgfile(svn->svn_path, setnumber);
			} else if (errno == SV_ENODEV) {
				remove_from_cfgfile(svn->svn_path, setnumber);
			}
		} else {
			/* warn the user that we didn't find it in cfg file */
			warn(NULL, gettext(
				"%s was not found in the config storage"),
				svn->svn_path);
			/* try to disable anyway */
			(void) disable_dev(svn->svn_path);
			rc = 1;
		}

		sv_cfg_close();
		destroy_hashtable();

		if (rc && !ret)
			ret = rc;
	}

	return (ret);
}


/* LINT - not static as fwcadm uses it */
static int
disable_one_sv(char *path)
{
	int setnumber;
	int rc;

	sv_get_maxdevs();
	sv_check_cluster(path);
	sv_cfg_open(CFG_WRLOCK);

	create_cfg_hash();
	if ((setnumber = find_in_hash(path)) != -1) {
		/* remove from kernel */
		if ((rc = disable_dev(path)) == 0) {
			/* remove the cfgline */
			remove_from_cfgfile(path, setnumber);
		} else if (errno == SV_ENODEV) {
			remove_from_cfgfile(path, setnumber);
		}
	} else {
		/* warn the user that we didn't find it in cfg file */
		warn(NULL,
		    gettext("%s was not found in the config storage"), path);
		/* still attempt to remove */
		(void) disable_dev(path);
		rc = 1;
	}
	destroy_hashtable();

	sv_cfg_close();
	return (rc);
}


static void
compare_tag(char *path)
{
	char buf[CFG_MAX_BUF], vol[CFG_MAX_BUF], cnode[CFG_MAX_BUF];
	char key[CFG_MAX_KEY];
	int found, setnumber, i;
	char *tag;

	sv_check_cluster(path);
	cfg_resource(cfg, (char *)NULL);	/* reset */
	cfg_rewind(cfg, CFG_SEC_CONF);

#ifdef DEBUG
	if (cfg_cluster_tag != NULL && implicit_tag != NULL) {
		error(NULL, gettext("compare_tag: -C %s AND implicit_tag %s!"),
		    cfg_cluster_tag, implicit_tag);
	}
#endif

	if (cfg_cluster_tag != NULL)
		tag = cfg_cluster_tag;
	else if (implicit_tag != NULL)
		tag = implicit_tag;
	else
		tag = "-";

	found = 0;
	for (i = 0; i < sv_max_devices; i++) {
		setnumber = i + 1;
		(void) snprintf(key, sizeof (key), "sv.set%d", setnumber);
		if (cfg_get_cstring(cfg, key, buf, sizeof (buf)) < 0) {
			break;
		}

		if (sscanf(buf, "%s - %s", vol, cnode) != 2) {
			continue;
		}

		if (strcmp(path, vol) == 0) {
			found = 1;
			break;
		}
	}

	if (!found) {
		warn(NULL, gettext("unable to find %s in the configuration"),
		    path);
		return;
	}

	/* have name match, compare cnode to new tag */

	if (strcmp(tag, cnode) == 0) {
		/* cluster tags match */
		return;
	}

	/* need to change the cluster tag */

	(void) snprintf(key, sizeof (key), "sv.set%d.cnode", setnumber);
	if (cfg_put_cstring(cfg, key, tag, strlen(tag)) < 0) {
		warn(NULL,
		    gettext("unable to change cluster tag for %s"), path);
		return;
	}

	cfg_changed = 1;

	/* change "-" tags to "" for display purposes */

	if (strcmp(tag, "-") == 0)
		tag = "";

	if (strcmp(cnode, "-") == 0)
		(void) strcpy(cnode, "");

	(void) printf(
	    gettext("%s: changed cluster tag for %s from \"%s\" to \"%s\"\n"),
	    program, path, cnode, tag);

	spcs_log("sv", NULL,
	    gettext("%s: changed cluster tag for %s from \"%s\" to \"%s\""),
	    program, path, cnode, tag);
}


/* LINT - not static as fwcadm uses it */
static void
compare_sv(char *conf_file)
{
	sv_name_t *svn_config;		/* Devices in config file */
	sv_name_t *svn_system;		/* Devices in system */
	sv_name_t *enable;		/* Devices that need enabled */
	sv_list_t svl_system;
	int config_cnt;
	int sys_cnt = 0;
	int setnumber, i, j;
	int index = 0;	/* Index in enable[] */
	int found;
	int fd0;

	svn_config = sv_alloc_svnames();
	svn_system = sv_alloc_svnames();
	enable = sv_alloc_svnames();

	bzero(svn_system, sizeof (svn_system));
	bzero(&svl_system, sizeof (svl_system));
	bzero(enable, sizeof (enable));

	/*
	 * Read the configuration file
	 * The return value is the number of entries
	 */
	config_cnt = read_config_file(conf_file, svn_config);

	if ((fd0 = open(sv_rpath, O_RDONLY)) < 0)
		error(NULL, gettext("unable to open %s: %s"),
			sv_rpath, strerror(errno));

	/* Grab the system list from the driver */
	svl_system.svl_count = sv_max_devices;
	svl_system.svl_names = &svn_system[0];
	svl_system.svl_error = spcs_s_ucreate();

	if (ioctl(fd0, SVIOC_LIST, &svl_system) < 0) {
		error(&svl_system.svl_error, gettext("unable to get list"));
	}

	spcs_s_ufree(&svl_system.svl_error);
	(void) close(fd0);

	/*
	 * Count the number of devices in the system.
	 * The last entry in the array has '\0' for a path name.
	 */
	for (j = 0; j < sv_max_devices; j++) {
		if (svn_system[j].svn_path[0] != '\0') {
			sys_cnt++;
		} else {
			break;
		}
	}
	/*
	 * Compare the configuration array with the system array.
	 * Mark any differences and disable conflicting devices.
	 */
	for (i = 0; i < config_cnt; i++) {
		found = 0;
		for (j = 0; j < sys_cnt; j++) {
			if (svn_system[j].svn_path[0] == '\0' ||
			    svn_system[j].svn_mode == 0)
				continue;

			/*  Check to see if path matches */
			if (strcmp(svn_system[j].svn_path,
			    svn_config[i].svn_path) == 0) {
				/*  Found a match  */
				svn_system[j].svn_path[0] = '\0';
				found++;
				break;
			}
		}

		if (!found) {
			/* Minor number not in system  = > enable device */
			enable[index].svn_mode = svn_config[i].svn_mode;
			(void) strcpy(enable[index].svn_path,
			    svn_config[i].svn_path);
			index++;
		}
	}

	/* Disable any devices that weren't in the config file */
	for (j = 0; j < sys_cnt; j++) {
		sv_check_cluster(NULL);
		sv_cfg_open(CFG_WRLOCK);
		create_cfg_hash();
		if (svn_system[j].svn_path[0] != '\0' &&
		    svn_system[j].svn_mode != 0) {
			(void) printf(gettext("%s: disabling sv: %s\n"),
			    program, svn_system[j].svn_path);
			if (disable_dev(svn_system[j].svn_path) == 0) {
				setnumber =
					find_in_hash(svn_system[j].svn_path);
				if (setnumber != -1) {
					/* the volume was found in cfg store */
					remove_from_cfgfile(
					svn_system[j].svn_path, setnumber);
				}
			}
		}
		sv_cfg_close();
		destroy_hashtable();
	}

	while (index) {
		/*
		 * Config file doesn't match system => enable the devices
		 * in enable[]
		 */
		index--;
		(void) printf(gettext("%s: enabling new sv: %s\n"),
		    program, enable[index].svn_path);
		(void) enable_dev(&enable[index]);
	}

	/*
	 * Search for entries where the cluster tag has changed.
	 */
	sv_check_cluster(NULL);
	sv_cfg_open(CFG_WRLOCK);

	for (i = 0; i < sv_max_devices; i++) {
		if (svn_config[i].svn_path[0] == '\0')
			break;

		compare_tag(svn_config[i].svn_path);
	}

	sv_cfg_close();
}


/*
 * We assume that the volume is already enabled and we can only
 * be changing the cluster tag.  Anything else is an error.
 */
/* LINT - not static as fwcadm uses it */
static void
compare_one_sv(char *path)
{
	sv_get_maxdevs();
	sv_check_cluster(NULL);
	sv_cfg_open(CFG_WRLOCK);

	compare_tag(path);

	sv_cfg_close();
}

/*
 * Read all sets from the libdscfg configuration file, and store everything in
 * the hashfile.
 *
 * We assume that the config file has been opened & rewound for us.  We store
 * the volume name as the key, and the setnumber where we found it as the data.
 *
 * The caller can pass in a pointer to the maximum number of volumes, or
 * a pointer to NULL, specifying we want 'all' the volumes.  The table is
 * searched using find_in_hash.
 */
static void
create_cfg_hash()
{
	char key[CFG_MAX_KEY], buf[CFG_MAX_BUF];
	char vol[CFG_MAX_BUF], cnode[CFG_MAX_BUF];
	int setnumber;
	ENTRY item;

	if (hcreate((size_t)sv_max_devices) == 0)
		error(NULL, gettext("unable to create hash table"));

	for (setnumber = 1; /* CSTYLED */; setnumber++) {
		(void) snprintf(key, sizeof (key), "sv.set%d", setnumber);
		if (cfg_get_cstring(cfg, key, buf, sizeof (buf)) < 0)
			break;

		if (sscanf(buf, "%s - %s", vol, cnode) != 2) {
			continue;
		}

		item.key = strdup(vol);
		item.data = (void *)setnumber;
		if (hsearch(item, ENTER) == NULL) {
			error(NULL,
			    gettext("unable to add entry to hash table"));
		}
	}
}

/*
 * Function to search the hash for a specific volume.  If it is found,
 * we return the set number.  If it isn't found, we return -1
 */
static int
find_in_hash(char *path)
{
	ENTRY *found_entry, item;
	int retval = -1;

	item.key = path;

	if ((found_entry = hsearch(item, FIND)) != NULL) {
		retval = (int)found_entry->data;
	}

	return (retval);
}

/*
 * Just a wrapper to destory the hashtable.  At some point in the future we
 * might want to do something more....  For instance, verify that the cfg
 * database and the kernel configuration match (?)  Just an idea.
 */
static void
destroy_hashtable()
{
	hdestroy();
}

/*
 * This function will remove a particular set from the config file.
 *
 * We make a whole host of assumptions:
 *   o the hashfile is up to date;
 *   o The config file has been opened with a WRLOCK for us.
 */
static void
remove_from_cfgfile(char *path, int setnumber)
{
	char key[CFG_MAX_KEY];
	int sev;
	char *lcltag;

	/* attempt to remove the volume from config storage */
	(void) snprintf(key, sizeof (key), "sv.set%d", setnumber);
	if (cfg_put_cstring(cfg, key, NULL, 0) < 0) {
		warn(NULL, gettext("unable to remove %s from "
		    "config storage: %s"), path, cfg_error(&sev));
	} else {
		if (implicit_tag && *implicit_tag) {
			lcltag = implicit_tag;
		} else if (cfg_cluster_tag && *cfg_cluster_tag) {
			lcltag = cfg_cluster_tag;
		} else {
			lcltag = "-";
		}
		if (cfg_rem_user(cfg, path, lcltag, "sv") != CFG_USER_LAST) {
			warn(NULL, gettext("unable to remove %s from dsvol"),
			    path);
		}
		cfg_changed = 1;
	}
}