OpenSolaris_b135/cmd/avs/dsw/iiadm.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <values.h>
#include <locale.h>
#include <langinfo.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <strings.h>
#include <stdarg.h>
#include <ctype.h>
#include <math.h>
#include <sys/param.h>
#include <sys/mnttab.h>
#include <nsctl.h>
#include <netdb.h>
#include <search.h>

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

#include <sys/unistat/spcs_s.h>
#include <sys/unistat/spcs_s_u.h>
#include <sys/unistat/spcs_errors.h>
#include <sys/nsctl/dsw.h>
#include <sys/nsctl/dsw_dev.h>		/* for bit map header format */

#include <sys/nskernd.h>

typedef struct mstcount_s {
	int count;
} mstcount_t;
typedef struct shdvol_s {
	char master[ DSW_NAMELEN ];
} shdvol_t;
typedef struct grptag_s {
	char ctag[ DSW_NAMELEN ];
} grptag_t;
hash_node_t **volhash = NULL;

#define	DSW_TEXT_DOMAIN	"II"

#include <dlfcn.h>
#define	RDC_LIB "/usr/lib/librdc.so.1"
static int (*self_check)(char *);

/*
 * Support for the special cluster tag "local" to be used with -C in a
 * cluster for local volumes.
 */
#define	II_LOCAL_TAG	"local"

#define	II_NOT_CLUSTER	1
#define	II_CLUSTER	2
#define	II_CLUSTER_LCL	3

static char *cfg_cluster_tag = NULL;
static CFGFILE *cfg = NULL;

void sigterm(int sig);

#define	SD_BIT_CLR(bmap, bit)		(bmap &= ~(1 << bit))
#define	SD_BIT_ISSET(bmap, bit)		((bmap & (1 << bit)) != 0)

#define	MAX_LINE_SIZE 256	/* maximum characters per line in config file */
#define	MAX_GROUPS 1024		/* maximum number of groups to support */
#define	MAX_CLUSTERS 1024	/* maximum number of resource groups */

unsigned long	bm_size;		/* size in bytes of bitmap */
unsigned long	bm_actual;		/* original number of bits in bitmap */
int	debug = 0;

int	dsw_fd;

#define	LD_II		0x00000001
#define	LD_DSVOLS	0x00000002
#define	LD_SVOLS	0x00000004
#define	LD_SHADOWS	0x00000008

static int reload_vols = 0;
static int config_locked = 0;
static int last_lock;

/*
 * names for do_copy() flags.
 */

enum	copy_update {Copy = 0, Update};
enum	copy_direction {ToShadow = 0, ToMaster};
enum	copy_wait {WaitForStart = 0, WaitForEnd};

char	*cmdnam;

unsigned char	*allocate_bitmap(char *);
void		usage(char *);
void		enable(char *, char *, char *, char *);
int		disable(char *);
void		bitmap_op(char *, int, int, int, int);
void		print_status(dsw_config_t *, int);
int		abort_copy(char *);
int		reset(char *);
int		overflow(char *);
void		iiversion(void);
int		wait_for_copy(char *);
int		export(char *);
void		list_volumes(void);
void		dsw_error(char *, spcs_s_info_t *);
void		InitEnv();
static void	check_dg_is_local(char *dgname);
static int	check_resource_group(char *volume);
static int	check_diskgroup(char *path, char *result);
static int	check_cluster();
static void	unload_ii_vols();
static void	load_ii_vols(CFGFILE *);
static int	perform_autosv();
static int	is_exported(char *);
static void	conform_name(char **);
static void	do_attach(dsw_config_t *);
static int	ii_lock(CFGFILE *, int);
static void	verify_groupname(char *grp, int testDash);

void	dsw_list_clusters(char *);
void	dsw_enable(int, char **);
void	dsw_disable(int, char **);
void	dsw_copy_to_shadow(int, char **);
void	dsw_update_shadow(int, char **);
void	dsw_copy_to_master(int, char **);
void	dsw_update_master(int, char **);
void	dsw_abort_copy(int, char **);
void	dsw_display_status(int, char **);
void	dsw_display_bitmap(int, char **);
void	dsw_reset(int, char **);
void	dsw_overflow(int, char **);
void	dsw_version(int, char **);
void	dsw_wait(int, char **);
void	dsw_list_volumes(int, char **);
void	dsw_list_group_volumes();
void	dsw_export(int, char **);
void	dsw_import(int, char **);
void	dsw_join(int, char **);
void	dsw_attach(int, char **);
void	dsw_detach(int, char **);
void	dsw_params(int, char **);
void	dsw_olist(int, char **);
void	dsw_ostat(int, char **);
void	dsw_move_2_group(int, char **);
void	dsw_list_groups();
void	check_iishadow(char *);

extern char *optarg;
extern int optind, opterr, optopt;

int	Aflg;
int	Cflg;
int	CLflg;
int	Dflg;
int	Eflg;
int	Iflg;
int	Jflg;
int	Lflg;
int	Oflg;
int	Pflg;
int	Qflg;
int	Rflg;
int	aflg;
int	bflg;
int	cflg;
int	dflg;
int	eflg;
int	fflg;
int	gflg;
int	gLflg;
int	hflg;
int	iflg;
int	lflg;
int	mflg;
int	nflg;
int	pflg;
int	uflg;
int	vflg;
int	wflg;

int	errflg;
#ifdef DEBUG
const char single_opts[] =
	"a:b:c:d:e:f:g:hilmnpu:vw:A:C:D:E:I:J:LO:PQ:R:";
#else
/* no b or f flags */
const char single_opts[] = "a:c:d:e:g:hilmnpu:vw:A:C:D:E:I:J:LO:PQ:R:";
#endif
const char group_opts[] = "ac:de:ilmnpu:wA:C:DELPR";
const char *opt_list = single_opts;

char	buf[CFG_MAX_BUF];
char	key[CFG_MAX_KEY];
char	last_overflow[DSW_NAMELEN];
int	setnumber;
char	*group_name;
char	**group_volumes;
enum copy_direction direction;
char	*param_delay, *param_unit;
char	*overflow_file;

#ifdef lint
int
iiadm_lintmain(int argc, char *argv[])
#else
int
main(int argc, char *argv[])
#endif
{
	int c;
	int actions = 0;
	int ac;
	char *av[1024];

	InitEnv();

	(void) memset(av, 0, sizeof (av));
	cmdnam = argv[0];
	while ((c = getopt(argc, argv, opt_list)) != EOF)
		switch (c) {
		case 'c':
			cflg++;
			actions++;
			if (strcmp(optarg, "m") == 0) {
				av[0] = "copy_to_master";
				direction = ToMaster;
			} else if (strcmp(optarg, "s") == 0) {
				av[0] = "copy_to_shadow";
				direction = ToShadow;
			} else {
				errflg ++;
				usage(gettext(
					"must specify m or s with -c"));
			}
			ac = 2;
			break;
		case 'd':
			dflg++;
			actions++;
			av[0] = "disable";
			av[1] = optarg;
			ac = 2;
			break;
		case 'e':
			eflg++;
			actions++;
			av[0] = "enable";
			if (strcmp(optarg, "ind") == 0)
				av[4] = "independent";
			else if (strcmp(optarg, "dep") == 0)
				av[4] = "dependent";
			else {
				errflg ++;
				usage(gettext(
					"must specify ind or dep with -e"));
			}
			ac = 1;
			break;
		case 'g':
			gflg++;
			opt_list = group_opts;
			group_name = optarg;
			if (group_name && *group_name == '-') {
				gLflg = (strcmp("-L", group_name) == 0);
				if (gLflg)
					actions++;
			}
			verify_groupname(group_name, !gLflg);
			break;
		case 'h':
			hflg++;
			actions++;
			break;
		case 'u':
			uflg++;
			actions++;
			if (strcmp(optarg, "m") == 0) {
				av[0] = "update_master";
				direction = ToMaster;
			} else if (strcmp(optarg, "s") == 0) {
				av[0] = "update_shadow";
				direction = ToShadow;
			} else {
				errflg ++;
				usage(gettext(
					"must specify m or s with -u"));
			}
			ac = 2;
			break;
		case 'i':
			iflg++;
			actions++;
			av[0] = "display_status";
			break;
		case 'l':
			lflg++;
			actions++;
			av[0] = "list_config";
			ac = 1;
			break;
		case 'm':
			mflg++;
			actions++;
			av[0] = "move_to_group";
			ac = 1;
			break;
		case 'n':
			nflg++;
			break;
		case 'p':
			pflg++;
			break;
		case 'b':
			bflg++;
			actions++;
			av[0] = "display_bitmap";
			av[1] = optarg;
			ac = 2;
			break;
		case 'a':
			aflg++;
			actions++;
			av[0] = "abort_copy";
			av[1] = optarg;
			ac = 2;
			break;
		case 'v':
			vflg++;
			actions++;
			av[1] = "version";
			ac = 1;
			break;
		case 'w':
			wflg++;
			actions++;
			av[0] = "wait";
			av[1] = optarg;
			ac = 2;
			break;
		case 'A':
			Aflg++;
			actions++;
			av[0] = "attach";
			av[1] = optarg;
			ac = 2;
			break;
		case 'C':
			Cflg++;
			cfg_cluster_tag = optarg;
			if (cfg_cluster_tag && *cfg_cluster_tag == '-') {
				CLflg = (strcmp("-L", cfg_cluster_tag) == 0);
				if (CLflg)
					actions++;
			}
			break;
		case 'D':
			Dflg++;
			actions++;
			av[0] = "detach";
			av[1] = optarg;
			ac = 2;
			break;
		case 'O':
			Oflg++;
			actions++;
			av[0] = "overflow";
			av[1] = optarg;
			ac = 2;
			break;
		case 'R':
			Rflg++;
			actions++;
			av[0] = "reset";
			av[1] = optarg;
			ac = 2;
			break;
		case 'E':
			Eflg++;
			actions++;
			av[0] = "export";
			av[1] = optarg;
			ac = 2;
			break;
		case 'I':
			Iflg++;
			actions++;
			av[0] = "import";
			av[1] = optarg;
			ac = 2;
			break;
		case 'J':
			Jflg++;
			actions++;
			av[0] = "join";
			av[1] = optarg;
			ac = 2;
			break;
		case 'P':
			Pflg++;
			actions++;
			av[0] = "parameter";
			ac = 1;
			break;
		case 'L':
			Lflg++;
			actions++;
			/* If -g group -L, force error */
			if (group_name) actions++;
			av[0] = "LIST";
			ac = 1;
			break;
		case 'Q':
			Qflg++;
			actions++;
			av[0] = "query";
			av[1] = optarg;
			ac = 2;
			break;
		case '?':
			errflg++;
			break;
		}
	if (hflg) {
		usage(NULL);
		exit(0);
		}

	if (errflg)
		usage(gettext("unrecognized argument"));
	switch (actions) {
		case 0:
			if (argc > 1)
				usage(gettext("must specify an action flag"));

			/* default behavior is to list configuration */
			lflg++; av[0] = "list_config"; ac = 1;
			break;
		case 1:
			break;
		default:
			usage(gettext("too many action flags"));
			break;
	}

	if (gflg && (Iflg || Jflg || Oflg || Qflg))
		usage(gettext("can't use a group with this option"));
	if (!gflg && (mflg))
		usage(gettext("must use a group with this option"));

	/*
	 * Open configuration file.
	 */
	if ((cfg = cfg_open(NULL)) == NULL) {
		perror("unable to access configuration");
		exit(2);
	}

	/*
	 * Set write locking (CFG_WRLOCK) for:
	 *	iiadm -e (enable)
	 * 	iiadm -d (disable)
	 *	iiadm -A (attach overflow)
	 *	iiadm -D (detach overflow)
	 *	iiadm -g grp -m volume (move volume into group)
	 *	iiadm -E (export shadow [needs to update dsvol section])
	 *	iiadm -I (import shadow [ditto])
	 *	iiadm -J (join shadow [ditto])
	 * read locking (CFG_RDLOCK) for all other commands
	 */
	last_lock = (eflg || dflg || mflg || Aflg || Dflg || Eflg || Iflg ||
	    Jflg)? CFG_WRLOCK : CFG_RDLOCK;
	if (!cfg_lock(cfg, last_lock)) {
		perror("unable to lock configuration");
		exit(2);
	}
	config_locked = 1;

	/*
	 * If we are in a cluster, set or derive a valid disk group
	 */
	switch (check_cluster()) {
	case II_CLUSTER:
		/*
		 * If in a Sun Cluster, can't Import an II shadow
		 * Must be done as -C local
		 */
		if (Iflg)
			dsw_error(gettext(
				"-I (import) only allowed as -C local"), NULL);
		/*FALLTHRU*/
	case II_CLUSTER_LCL:
		/*
		 * If a cluster tag was specified or derived, set it
		 */
		if (CLflg) {
			dsw_list_clusters(argv[optind]);
			cfg_close(cfg);
			exit(0);
		} else {
			cfg_resource(cfg, cfg_cluster_tag);
		}
		break;
	case II_NOT_CLUSTER:
		if (cfg_cluster_tag != NULL)
			dsw_error(gettext(
			    "-C is valid only in a Sun Cluster"), NULL);
		break;
	default:
		dsw_error(gettext(
		    "Unexpected return from check_cluster()"), NULL);
	}

	/* preload the ii config */
	load_ii_vols(cfg);
	reload_vols |= LD_II;

	if (eflg) {
		if (argc - optind != 3)
			usage(gettext("must specify 3 volumes with -e"));
		av[1] = argv[optind++];
		av[2] = argv[optind++];
		av[3] = argv[optind++];
		ac = 5;
		dsw_enable(ac, av);
	} else if (dflg) {
		dsw_disable(ac, av);
	} else if (uflg) {
		if (argv[optind] == NULL && group_name == NULL)
			usage(gettext("must specify volume with -u"));
		for (c = 1; argv[optind] != NULL; optind++)
			av[c++] = argv[optind];
		av[c] = NULL;

		if (direction == ToMaster)
			dsw_update_master(ac, av);
		else
			dsw_update_shadow(ac, av);
	} else if (iflg) {
		if (argv[optind]) {
			av[1] = argv[optind];
			ac = 2;
		} else
			ac = 1;
		dsw_display_status(ac, av);
	} else if (bflg) {
		dsw_display_bitmap(ac, av);
	} else if (cflg) {
		if (argv[optind] == NULL && group_name == NULL)
			usage(gettext("must specify volume with -c"));
		for (c = 1; argv[optind] != NULL; optind++)
			av[c++] = argv[optind];
		av[c] = NULL;

		if (direction == ToMaster)
			dsw_copy_to_master(ac, av);
		else
			dsw_copy_to_shadow(ac, av);
	} else if (aflg) {
		dsw_abort_copy(ac, av);
	} else if (Eflg) {
		dsw_export(ac, av);
	} else if (Iflg) {
		if (argc - optind != 1)
			usage(gettext("must specify 2 volumes with -I"));
		av[2] = argv[optind++];
		ac = 3;
		dsw_import(ac, av);
	} else if (Aflg) {
		if (group_name) {
			if (argc - optind != 0)
				usage(gettext("must specify overflow volume " \
				"when using groups with -A"));
			ac = 2;
		} else {
			if (argc - optind != 1)
				usage(gettext("specify 2 volumes with -A"));
			ac = 3;
			av[2] = argv[optind++];
		}
		dsw_attach(ac, av);
	} else if (Dflg) {
		dsw_detach(ac, av);
	} else if (Jflg) {
		if (argc - optind != 1)
			usage(gettext("must specify 2 volumes with -J"));
		av[2] = argv[optind++];
		ac = 3;
		dsw_join(ac, av);
	} else if (Pflg) {
		if (argc - optind == ((group_name) ? 0 : 1)) {
			av[1] = argv[optind++];
			ac = (group_name) ? 0 : 2;
		} else if (argc - optind == ((group_name) ? 2 : 3)) {
			av[1] = argv[optind++];
			av[2] = argv[optind++];
			av[3] = argv[optind++];
			ac = (group_name) ? 2 : 4;
		} else
			usage(gettext(
				"must specify delay, unit and shadow with -P"));
		dsw_params(ac, av);
	} else if (Oflg) {
		dsw_overflow(ac, av);
	} else if (Rflg) {
		dsw_reset(ac, av);
	} else if (vflg) {
		dsw_version(ac, av);
	} else if (wflg) {
		dsw_wait(ac, av);
	} else if (lflg) {
		if ((gflg) && (!group_name))
			dsw_list_group_volumes();
		else
			dsw_list_volumes(ac, av);
	} else if (Lflg) {
		dsw_olist(ac, av);
	} else if (gLflg) {
		dsw_list_groups();
	} else if (Qflg) {
		dsw_ostat(ac, av);
	} else if (mflg) {
		if (argc - optind < 1)
			usage(gettext("must specify one or more volumes"));
		for (c = 1; argv[optind] != NULL; optind++)
			av[c++] = argv[optind];
		av[c] = NULL;
		dsw_move_2_group(ac, av);
	}
	if (cfg)
		cfg_close(cfg);

	exit(0);
	return (0);
}

static int
ii_lock(CFGFILE *cfg, int locktype)
{
	last_lock = locktype;
	return (cfg_lock(cfg, locktype));
}

static int
do_ioctl(int fd, int cmd, void *arg)
{
	int unlocked = 0;
	int rc;
	int save_errno;

	if (config_locked) {
		switch (cmd) {
		case DSWIOC_ENABLE:
		case DSWIOC_RESUME:
		case DSWIOC_SUSPEND:
		case DSWIOC_COPY:
		case DSWIOC_BITMAP:
		case DSWIOC_DISABLE:
		case DSWIOC_SHUTDOWN:
		case DSWIOC_ABORT:
		case DSWIOC_RESET:
		case DSWIOC_OFFLINE:
		case DSWIOC_WAIT:
		case DSWIOC_ACOPY:
		case DSWIOC_EXPORT:
		case DSWIOC_IMPORT:
		case DSWIOC_JOIN:
		case DSWIOC_COPYP:
		case DSWIOC_OATTACH:
		case DSWIOC_ODETACH:
		case DSWIOC_SBITSSET:
		case DSWIOC_CBITSSET:
		case DSWIOC_SEGMENT:
		case DSWIOC_MOVEGRP:
		case DSWIOC_CHANGETAG:
			cfg_unlock(cfg);
			unlocked = 1;
			break;

		case DSWIOC_STAT:
		case DSWIOC_VERSION:
		case DSWIOC_LIST:
		case DSWIOC_OCREAT:
		case DSWIOC_OLIST:
		case DSWIOC_OSTAT:
		case DSWIOC_OSTAT2:
		case DSWIOC_LISTLEN:
		case DSWIOC_OLISTLEN:
		case DSWIOC_CLIST:
		case DSWIOC_GLIST:
			break;

		default:
			(void) fprintf(stderr,
			    "cfg locking needs to be set for %08x\n", cmd);
			exit(1);
			break;
		}
	}
	if (unlocked) {
		/* unload vol hashes */
		if (reload_vols & LD_II)
			unload_ii_vols();
		if (reload_vols & LD_SHADOWS)
			cfg_unload_shadows();
		if (reload_vols & LD_DSVOLS)
			cfg_unload_dsvols();
		if (reload_vols & LD_SVOLS)
			cfg_unload_svols();
	}
	rc = ioctl(fd, cmd, arg);
	save_errno = errno;
	if (config_locked && unlocked) {
		(void) cfg_lock(cfg, last_lock);
	}
	if (unlocked) {
		/* reload vol hashes */
		if (reload_vols & LD_SVOLS)
			(void) cfg_load_svols(cfg);
		if (reload_vols & LD_DSVOLS)
			(void) cfg_load_dsvols(cfg);
		if (reload_vols & LD_SHADOWS)
			(void) cfg_load_shadows(cfg);
		if (reload_vols & LD_II)
			load_ii_vols(cfg);
	}

	errno = save_errno;
	return (rc);
}

static int
get_dsw_config(int setno, dsw_config_t *parms)
{
	char buf[CFG_MAX_BUF];
	char key[CFG_MAX_KEY];

	bzero(parms, sizeof (dsw_config_t));
	(void) snprintf(key, sizeof (key), "ii.set%d.master", setno);
	if (cfg_get_cstring(cfg, key, parms->master_vol, DSW_NAMELEN) < 0)
		return (-1);

	(void) snprintf(key, sizeof (key), "ii.set%d.shadow", setno);
	(void) cfg_get_cstring(cfg, key, parms->shadow_vol, DSW_NAMELEN);

	(void) snprintf(key, sizeof (key), "ii.set%d.bitmap", setno);
	(void) cfg_get_cstring(cfg, key, parms->bitmap_vol, DSW_NAMELEN);

	(void) snprintf(key, sizeof (key), "ii.set%d.mode", setno);
	(void) cfg_get_cstring(cfg, key, buf, sizeof (buf));
	if (strcmp(buf, "I") == 0)
		parms->flag |= DSW_GOLDEN;

	(void) snprintf(key, sizeof (key), "ii.set%d.overflow", setno);
	(void) cfg_get_cstring(cfg, key, last_overflow, DSW_NAMELEN);

	(void) snprintf(key, sizeof (key), "ii.set%d.group", setno);
	(void) cfg_get_cstring(cfg, key, parms->group_name, DSW_NAMELEN);

	(void) snprintf(key, sizeof (key), "ii.set%d.cnode", setno);
	(void) cfg_get_cstring(cfg, key, parms->cluster_tag, DSW_NAMELEN);
	return (0);
}

static int
find_next_cf_line(char *volume, int next)
{
	dsw_config_t cf_line;

	for (setnumber = next; get_dsw_config(setnumber, &cf_line) == 0;
								setnumber++) {
		if (strncmp(volume, cf_line.master_vol, DSW_NAMELEN) == 0 ||
		    strncmp(volume, cf_line.shadow_vol, DSW_NAMELEN) == 0 ||
		    strncmp(volume, cf_line.bitmap_vol, DSW_NAMELEN) == 0)
			return (1);
	}
	return (0);
}
int
find_any_cf_line(char *volume)
{
	return (find_next_cf_line(volume, 1));
}

static int
find_next_shadow_line(char *volume, int next)
{
	dsw_config_t cf_line;

	for (setnumber = next; get_dsw_config(setnumber, &cf_line) == 0;
	    setnumber++) {
		if (strncmp(volume, cf_line.shadow_vol, DSW_NAMELEN) == 0)
			return (1);
	}
	return (0);
}
int
find_shadow_line(char *volume)
{
	return (find_next_shadow_line(volume, 1));
}

/*
 * this function is designed to be called once, subsequent calls won't
 * free memory allocated by earlier invocations.
 */
char *
get_overflow_list()
{
	dsw_aioctl_t *acopy_args;
	int rc, num;

	num = do_ioctl(dsw_fd, DSWIOC_OLISTLEN, NULL);
	if (num < 0)
		dsw_error(gettext("Can't get overflow list length"), NULL);

	acopy_args = malloc(sizeof (dsw_aioctl_t) + num * DSW_NAMELEN);
	if (acopy_args == NULL)
		dsw_error(gettext("Can't get memory for list enquiry"), NULL);

	acopy_args->count = num;
	acopy_args->flags = 0;
	acopy_args->status = spcs_s_ucreate();

	rc = do_ioctl(dsw_fd, DSWIOC_OLIST, acopy_args);
	if (rc == -1)
		dsw_error(gettext("Overflow list access failure"),
			&acopy_args->status);
	else
		acopy_args->shadow_vol[DSW_NAMELEN*acopy_args->count] = NULL;

	return (acopy_args->shadow_vol);
}

/*
 * this function is designed to be called once, subsequent calls won't
 * free memory allocated by earlier invocations.
 */

int
find_group_members(char *group)
{
	int nmembers = 0;
	int vector_len = 0;

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

		if (strcmp(group, buf))
			continue;

		(void) snprintf(key, sizeof (key), "ii.set%d.shadow",
		    setnumber);
		if (cfg_get_cstring(cfg, key, buf,
					sizeof (buf)) < 0)
			break;

		if (nmembers >= vector_len) {
			vector_len += 10;
			group_volumes = realloc(group_volumes, (1+vector_len) *
					sizeof (char *));
		}
		group_volumes[nmembers] = strdup(buf);
		nmembers++;
	}
	if (group_volumes)
		group_volumes[nmembers] = NULL;	/* terminate list */
	return (nmembers);
}

static int
find_next_matching_cf_line(
	char *volume, dsw_config_t *conf, dsw_ioctl_t *io, int next)
{
	dsw_config_t config;

	if (!find_next_cf_line(volume, next)) {
		return (0);
	}

	if (conf == NULL)
		conf = &config;
	(void) get_dsw_config(setnumber, conf);
	if (io) {
		(void) strncpy(io->shadow_vol, conf->shadow_vol, DSW_NAMELEN);
		io->shadow_vol[DSW_NAMELEN] = '\0';
	}
	return (1);
}

int
find_matching_cf_line(char *volume, dsw_config_t *conf, dsw_ioctl_t *io)
{
	return (find_next_matching_cf_line(volume, conf, io, 1));
}

int
find_shadow_config(char *volume, dsw_config_t *conf, dsw_ioctl_t *io)
{
	dsw_config_t *c, cf;

	if (io) {
		bzero(io, sizeof (dsw_ioctl_t));
	}
	c = conf ? conf : &cf;
	setnumber = 1;
	/* perform action for each line of the stored config file */
	for ((void) snprintf(key, sizeof (key), "ii.set%d.shadow", setnumber);
	    cfg_get_cstring(cfg, key, c->shadow_vol, DSW_NAMELEN) >= 0;
	    (void) snprintf(key, sizeof (key), "ii.set%d.shadow",
	    ++setnumber)) {
		if (strncmp(volume, c->shadow_vol, DSW_NAMELEN) == 0) {
			(void) get_dsw_config(setnumber, c);

			if (check_resource_group(c->bitmap_vol)) {
				setnumber = 0;
				continue;
			}

			switch (check_cluster()) {
			case II_CLUSTER:
				if ((cfg_cluster_tag) &&
				    (strcmp(cfg_cluster_tag, c->cluster_tag)))
					continue;
				break;
			case II_CLUSTER_LCL:
				if (strlen(c->cluster_tag))
					continue;
				break;
			}

			if (io) {
				(void) strncpy(io->shadow_vol, c->shadow_vol,
				    DSW_NAMELEN);
				io->shadow_vol[DSW_NAMELEN] = '\0';
			}
			return (1);
		}
	}
	return (0);
}

void
add_cfg_entry(dsw_config_t *parms)
{
	/* insert the well-known fields first */
	(void) snprintf(buf, sizeof (buf), "%s %s %s %s",
	    parms->master_vol, parms->shadow_vol, parms->bitmap_vol,
	    (parms->flag & DSW_GOLDEN) ? "I" : "D");

	if (cfg_put_cstring(cfg, "ii", buf, strlen(buf)) >=  0) {
		/* if we have a group name, add it */
		if (group_name) {
			if (find_any_cf_line(parms->shadow_vol)) {
				(void) sprintf(buf, "ii.set%d.group",
				    setnumber);
				if (cfg_put_cstring(cfg, buf,
					group_name, strlen(group_name)) < 0)
					perror("cfg_put_cstring");
			}
			else
				perror("cfg_location");
		}

		/* commit the record */
		(void) cfg_commit(cfg);
	}
	else
		perror("cfg_put_string");
}

void
remove_iiset(int setno, char *shadow, int shd_exp)
{
	mstcount_t *mdata;
	shdvol_t *sdata;
	char sn[CFG_MAX_BUF];

	if (perform_autosv()) {
		if (volhash) {
			unload_ii_vols();
		}
		load_ii_vols(cfg);
		if (cfg_load_dsvols(cfg) < 0 || cfg_load_svols(cfg) < 0) {
			dsw_error(gettext("Unable to parse config file"), NULL);
		}

		sdata = (shdvol_t *)nsc_lookup(volhash, shadow);
		if (sdata) {
			/*
			 * Handle the normal cases of disabling a set that is
			 * not an imported shadow volume
			 */
			if (strcmp(sdata->master, II_IMPORTED_SHADOW)) {
				/*
				 * Handle multiple-shadows of single master
				 */
				mdata = (mstcount_t *)
					nsc_lookup(volhash, sdata->master);
				if ((mdata) && (mdata->count == 1)) {
				    if (cfg_vol_disable(cfg, sdata->master,
					cfg_cluster_tag, "ii") < 0)
					    dsw_error(gettext(
						"SV disable of master failed"),
						NULL);
				}
			}

			/*
			 * Handle disk group name of different shadow
			 */
			if (shd_exp) {
				/*
				 * If shadow is exported, then do nothing
				 */
				/*EMPTY*/;
			} else if (cfg_cluster_tag &&
				    strcmp(cfg_cluster_tag, "") &&
				    cfg_dgname(shadow, sn, sizeof (sn)) &&
				    strlen(sn) &&
				    strcmp(sn, cfg_cluster_tag)) {
					/* reload disk group volumes */
					cfg_resource(cfg, sn);
					cfg_unload_dsvols();
					cfg_unload_svols();
					(void) cfg_load_dsvols(cfg);
					(void) cfg_load_svols(cfg);
					if (cfg_vol_disable(cfg, shadow, sn,
					    "ii") < 0)
					    dsw_error(gettext(
						"SV disable of shadow failed"),
						NULL);
					cfg_resource(cfg, cfg_cluster_tag);
			} else {
				if (cfg_vol_disable(cfg, shadow,
				    cfg_cluster_tag, "ii") < 0)
					dsw_error(gettext(
					    "SV disable of shadow failed"),
					    NULL);
			}
		}
		cfg_unload_svols();
		cfg_unload_dsvols();
		unload_ii_vols();
		reload_vols &= ~(LD_SVOLS | LD_DSVOLS | LD_II);
	}

	(void) sprintf(key, "ii.set%d", setno);
	if (cfg_put_cstring(cfg, key, NULL, 0) < 0) {
		perror("cfg_put_cstring");
	}
	(void) cfg_commit(cfg);
}

/*
 * determine if we are running in a Sun Cluster Environment
 */
int
check_cluster()
{
	static int is_cluster = -1;
	int rc;
#ifdef DEBUG
	char *cdebug = getenv("II_SET_CLUSTER");
#endif

	/*
	 * If this routine was previously called, just return results
	 */
	if (is_cluster != -1)
		return (is_cluster);

	/*
	 * See if Sun Cluster was installed on this node
	 */
#ifdef DEBUG
	if (cdebug) rc = atoi(cdebug);
	else
#endif
	rc = cfg_iscluster();
	if (rc > 0) {
		/*
		 * Determine if user specified -C local
		 */
		if ((cfg_cluster_tag == NULL) ||
		    (strcmp(cfg_cluster_tag, II_LOCAL_TAG))) {
			/*
			 * We're in a Sun Cluster, and no "-C local"
			 */
			is_cluster = II_CLUSTER;
		} else {
			/*
			 * We're in a Sun Cluster, but "-C local" was specified
			 */
			is_cluster = II_CLUSTER_LCL;
			cfg_cluster_tag = "";
		}
		return (is_cluster);
	} else if (rc == 0) {
		/*
		 * Not in a Sun Cluster
		 */
		is_cluster = II_NOT_CLUSTER;
		return (is_cluster);
	} else {
		dsw_error(gettext("unable to ascertain environment"), NULL);
		/*NOTREACHED*/
	}

	/* gcc */
	return (is_cluster);
}

/*
 * Determine if we need to set a cfg_resource based on this volume
 */
static int
check_resource_group(char *volume)
{
	char diskgroup[CFG_MAX_BUF];

	/*
	 * If we are in a cluster, attempt to derive a new resource group
	 */

#ifdef DEBUG
	if (getenv("II_SET_CLUSTER") || (check_cluster() == II_CLUSTER)) {
#else
	if (check_cluster() == II_CLUSTER) {
#endif
		if (check_diskgroup(volume, diskgroup)) {
			if (cfg_cluster_tag == NULL) {
				cfg_cluster_tag = strdup(diskgroup);
				if (cfg_cluster_tag == NULL)
					dsw_error(gettext(
					"Memory allocation failure"), NULL);
				cfg_resource(cfg, cfg_cluster_tag);
				return (1);
			} else {
			/*
			 * Check dgname and cluster tag from -C are the same.
			 */
			if (strcmp(diskgroup, cfg_cluster_tag) != 0) {
			    char error_buffer[128];
			    (void) snprintf(error_buffer, sizeof (error_buffer),
				gettext(
				    "-C (%s) does not match disk group "
				    "name (%s) for %s"), cfg_cluster_tag,
				    diskgroup, volume);
				spcs_log("ii", NULL, error_buffer);
				dsw_error(error_buffer, NULL);
			    }
			}
		} else if (cfg_cluster_tag == NULL)
			dsw_error(gettext(
				"Point-in-Time Copy volumes, that are not "
				"in a device group which has been "
				"registered with SunCluster, "
				"require usage of \"-C\""), NULL);
	}
	return (0);
}

static void
check_dg_is_local(char *dgname)
{
	char error_buffer[128];
	char *othernode;
	int rc;

	/*
	 * check where this disk service is mastered
	 */
	rc = cfg_dgname_islocal(dgname, &othernode);
	if (rc < 0) {
		(void) snprintf(error_buffer, sizeof (error_buffer),
		    gettext("Unable to find disk service:%s"), dgname);
		dsw_error(error_buffer, NULL);
	} else if (rc == 0) {
		(void) snprintf(error_buffer, sizeof (error_buffer),
		    gettext("disk service, %s, is active on node \"%s\"\n"
		    "Please re-issue the command on that node"), dgname,
		    othernode);
		dsw_error(error_buffer, NULL);
	}
}

/*
 * Carry out cluster based checks for a specified volume, or just
 * global options.
 */
static int
check_diskgroup(char *path, char *result)
{
	char dgname[CFG_MAX_BUF];
	char error_buffer[128];

#ifdef DEBUG
	char *override = getenv("II_CLUSTER_TAG");
	if (override) {
		(void) strcpy(result, override);
		return (1);
	}
#endif
	/*
	 * Check on path name, a returned NULL dgname is valid
	 */
	if (cfg_dgname(path, dgname, sizeof (dgname)) == NULL) {
		(void) snprintf(error_buffer, sizeof (error_buffer), gettext(
		    "unable to determine disk group name for %s"), path);
		dsw_error(error_buffer, NULL);
	}
	if (strcmp(dgname, "") == 0)
		return (0);

	/*
	 * See if disk group is local to this node
	 */
	check_dg_is_local(dgname);

	/*
	 * Copy dgname into result
	 */
	(void) strcpy(result, dgname);
	return (1);
}

/*
 * sigterm (): traps specified signal , usually termination one
 */
void
sigterm(int sig)
{
	spcs_log("ii", NULL, gettext("%s received signal %d"), cmdnam, sig);
	exit(1);
}

/*
 * sigchild; reap child and collect status.
 */

volatile pid_t	dead_child;
int	dead_stat;

/*ARGSUSED*/
void
sigchild(int sig)
{
	dead_child = wait(&dead_stat);
}

/*
 * InitEnv(): initializes environment
 */
void
InitEnv()
{
	(void) setlocale(LC_ALL, "");
	(void) textdomain(DSW_TEXT_DOMAIN);

#ifndef DEBUG
	(void) sigset(SIGHUP, sigterm);
	(void) sigset(SIGINT, sigterm);
	(void) sigset(SIGQUIT, sigterm);
	(void) sigset(SIGILL, sigterm);
	(void) sigset(SIGEMT, sigterm);
	(void) sigset(SIGABRT, sigterm);
	(void) sigset(SIGFPE, sigterm);
	(void) sigset(SIGBUS, sigterm);
	(void) sigset(SIGSEGV, sigterm);
	(void) sigset(SIGTERM, sigterm);
	(void) sigset(SIGPWR, sigterm);
	(void) sigset(SIGSTOP, sigterm);
	(void) sigset(SIGTSTP, sigterm);
#endif

	dsw_fd = open(DSWDEV, O_RDONLY);
	if (dsw_fd < 0) {
		perror(DSWDEV);
		exit(1);
	}

	(void) setsid();
}

/*
 * print an error message, followed by decoded errno then exit.
 */
void
dsw_error(char *str, spcs_s_info_t *status)
{
	char *sp;

	(void) fprintf(stderr, "%s: %s:\n", cmdnam, str);
	if (status == NULL) {
		/* deflect ESRCH */
		if (ESRCH == errno) {
			sp = "Set/volume not found";
		} else {
			sp = strerror(errno);
		}
		(void) fprintf(stderr, "%s\n", sp ? sp : "");
	} else {
		spcs_s_report(*status, stderr);
		spcs_s_ufree(status);
	}
	if (cfg)
		cfg_close(cfg);
	exit(2);
}


#undef size

void
free_bitmap(unsigned char *bitmap)
{
	free(bitmap);
}


int
get_bitmap(master_volume, shd_bitmap, copy_bitmap, size)
	char		*master_volume;
	unsigned char	*shd_bitmap;
	unsigned char	*copy_bitmap;
	unsigned long	size;
{
	dsw_bitmap_t parms;

	(void) strncpy(parms.shadow_vol, master_volume, DSW_NAMELEN);
	parms.shadow_vol[DSW_NAMELEN-1] = '\0';
	parms.shd_bitmap = shd_bitmap;
	parms.shd_size = size;
	parms.copy_bitmap = copy_bitmap;
	parms.copy_size = size;

	return (do_ioctl(dsw_fd, DSWIOC_BITMAP, &parms));
}

unsigned char *
allocate_bitmap(char *shadow_volume)
{
	unsigned char	*shd_bitmap;
	unsigned char	*copy_bitmap;
	unsigned char	*p;
	unsigned char	*q;
	int		i;
	dsw_stat_t	args;
	int		stat_flags;

	(void) strncpy(args.shadow_vol, shadow_volume, DSW_NAMELEN);
	args.shadow_vol[DSW_NAMELEN-1] = '\0';

	args.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_STAT, &args) == -1)
		dsw_error(gettext("Stat failed"), &args.status);

	stat_flags = args.stat;
	if (stat_flags & DSW_BMPOFFLINE)
		return (NULL);

	bm_size = args.size;
	bm_size = (bm_size + DSW_SIZE-1) / DSW_SIZE;
	bm_actual = bm_size;
	bm_size = (bm_size + DSW_BITS-1) / DSW_BITS;
	spcs_s_ufree(&args.status);

	p = shd_bitmap = (unsigned char *) malloc(bm_size);
	if (!shd_bitmap) {
		perror(gettext("malloc bitmap"));
		return (NULL);
	}

	q = copy_bitmap = (unsigned char *) malloc(bm_size);
	if (!copy_bitmap) {
		perror(gettext("malloc bitmap"));
		free(shd_bitmap);
		return (NULL);
	}

	(void) memset(shd_bitmap, 0, bm_size);
	(void) memset(copy_bitmap, 0, bm_size);

	if (get_bitmap(shadow_volume, shd_bitmap, copy_bitmap, bm_size) < 0) {
		free(copy_bitmap);
		free(shd_bitmap);
		return (NULL);
	}

	/*
	 * "or" the copy and shadow bitmaps together to return a composite
	 * bitmap that contains the total set of differences between the
	 * volumes.
	 */
	for (i = bm_size; i-- > 0; /*CSTYLED*/)
		*p++ |= *q++;

	free(copy_bitmap);

	return (shd_bitmap);
}

/*
 * print usage message and exit.
 */
void
usage(char *why)
{
	if (why) {
		(void) fprintf(stderr, "%s: %s\n", cmdnam, why);

		(void) fprintf(stderr, "%s\n",
		    gettext("\nBrief summary:"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-e {ind|dep} master_vol shadow_vol "
		    "bitmap_vol"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-[cu {s|m}] volume_set"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-i all"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-[adDEilPRw] volume_set"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-g group_name [options]"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-C cluster_tag [options]"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-[hilLv]"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-[IJ] volume_set bitmap"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-A overflow_vol volume_set"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-[OQ] overflow_vol"));
		(void) fprintf(stderr, "%s\n",
		    gettext("\t-P {delay} {units} volume_set"));

		/* assume we came here due to a user mistake */
		exit(1);
		/* NOTREACHED */
	} else {

		(void) fprintf(stdout, "%s\n",
		    gettext("Point-in-Time Copy Administrator CLI options"));
		(void) fprintf(stdout, "%s\n",
		    gettext("Usage summary:"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-e ind m s b\tenable independent master shadow "
		    "bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-e dep m s b\tenable dependent master shadow "
		    "bitmap"));
		if (check_cluster() == II_CLUSTER)
		    (void) fprintf(stdout, "%s\n",
			gettext("\t-ne ind m s b\tenable exportable master "
			"shadow bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-d v\t\tdisable volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-u s v\t\tupdate shadow volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-u m v\t\tupdate master volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-c s v\t\tcopy to shadow volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-c m v\t\tcopy to master volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-a v\t\tabort copy volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-w v\t\twait volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-i v\t\tdisplay volume status"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-i all\t\tdisplay all volume status"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-l\t\tlist all volumes"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-R v\t\treset volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-A o v\t\tattach overflow to volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-D v\t\tdetach overflow from volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-L\t\tlist all overflow volumes"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-O o\t\tinitialize overflow"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-Q o\t\tquery status of overflow"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-E v\t\texport shadow volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-I v b\t\timport volume bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-J v b\t\tjoin volume bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-P d u v\tset copy delay/units for volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-P v\t\tget copy delay/units for volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-C tag\t\tcluster resource tag"));
#ifdef DEBUG
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-b v\t\tdisplay bitmap volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-f f\t\tuse private configuration file"));
#endif
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-v\t\tprint software versions"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-n\t\tperform action without question"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-p [-c|-u] {m|s}"
			"enable PID locking on copy or update"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-p -w v\t\tdisable PID locking"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-h\t\tiiadm usage summary"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\nUsage summary for options that support "
		    "grouping (-g g):"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -e ind m s b group enable independent "
		    "master shadow bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -e dep m s b group enable dependent "
		    "master shadow bitmap"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -d\t\tdisable group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -u s\tupdate shadow for all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -u m\tupdate master for all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -c s\tcopy to shadow for all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -c m\tcopy to master for all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -a\t\tabort copy for all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -w\t\twait for all volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -i\t\tdisplay status of all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -l\t\tlist all volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g -L\t\tlist all groups"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -m v v\tmove one or more volumes into "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g \"\" -m v\tremove volume from group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -R\t\treset all volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -A o\tattach overflow to all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -D\t\tdetach overflow from all volumes in "
		    "group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -E\t\texport shadow volume for all "
		    "volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -P d u\tset copy delay/units for all "
		    "volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\t-g g -P\t\tget copy delay/units for all "
		    "volumes in group"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\nLegend summary:"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tind\t\tindependent volume set"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tdep\t\tdependent volume set"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tall\t\tall configured volumes"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tm\t\tmaster volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\ts\t\tshadow volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tv\t\tshadow volume (reference name)"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\to\t\toverflow volume"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tb\t\tbitmap volume"));
#ifdef DEBUG
		(void) fprintf(stdout, "%s\n",
		    gettext("\tf\t\tconfiguration file name"));
#endif
		(void) fprintf(stdout, "%s\n",
		    gettext("\td\t\tdelay tick interval"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tu\t\tunit size"));
		(void) fprintf(stdout, "%s\n",
		    gettext("\tg\t\tgroup name"));

		/* assume we came here because user request help text */
		exit(0);
		/* NOTREACHED */
	}

}

static  char    yeschr[MAX_LINE_SIZE + 2];
static  char    nochr[MAX_LINE_SIZE + 2];

static int
yes(void)
{
	int	i, b;
	char	ans[MAX_LINE_SIZE + 1];

	for (i = 0; /*CSTYLED*/; i++) {
		b = getchar();
		if (b == '\n' || b == '\0' || b == EOF) {
			if (i < MAX_LINE_SIZE)
				ans[i] = 0;
			break;
		}
		if (i < MAX_LINE_SIZE)
			ans[i] = b;
	}
	if (i >= MAX_LINE_SIZE) {
		i = MAX_LINE_SIZE;
		ans[MAX_LINE_SIZE] = 0;
	}
	if ((i == 0) || (strncmp(yeschr, ans, i))) {
		if (strncmp(nochr, ans, i) == 0)
			return (0);
		else if (strncmp(yeschr, ans, i) == 0)
			return (1);
		else {
			(void) fprintf(stderr, "%s %s/%s\n",
			    gettext("You have to respond with"),
			    yeschr, nochr);
			return (2);
		}
	}
	return (1);
}

static int
convert_int(char *str)
{
	int result, rc;
	char *buf;

	buf = (char *)calloc(strlen(str) + 256, sizeof (char));
	rc = sscanf(str, "%d%s", &result, buf);

	if (rc != 1) {
		(void) sprintf(buf, gettext("'%s' is not a valid number"), str);
		/* dsw_error calls exit which frees 'buf' */
		errno = EINVAL;
		dsw_error(buf, NULL);
	}
	free(buf);

	return (result);
}

void
check_action(char *will_happen)
{
	int answer;

	if (nflg || !isatty(fileno(stdin)))
		return;
	(void) strncpy(yeschr, nl_langinfo(YESSTR), MAX_LINE_SIZE + 1);
	(void) strncpy(nochr, nl_langinfo(NOSTR), MAX_LINE_SIZE + 1);

	/*CONSTCOND*/
	while (1) {
		(void) printf("%s %s/%s ", will_happen, yeschr, nochr);
		answer = yes();
		if (answer == 1 || answer == 0)
			break;
	}
	if (answer == 1)
		return;
	exit(1);
}

enum	child_event {Status, CopyStart};

/*
 * Wait for child process to get to some state, where some state may be:
 *
 *	Status		Set up the shadow enough so that it responds
 *			to status requests.
 *	CopyStart	Start copy/update operations.
 */

int
child_wait(pid_t child, enum child_event event, char *volume)
{
	dsw_stat_t	args;
	int rc;

	(void) strncpy(args.shadow_vol, volume, DSW_NAMELEN);
	args.shadow_vol[DSW_NAMELEN-1] = '\0';

	for (; dead_child != child; (void) sleep(1)) {

		/* poll shadow group with a status ioctl() */
		args.status = spcs_s_ucreate();
		errno = 0;
		rc = do_ioctl(dsw_fd, DSWIOC_STAT, &args);

		spcs_s_ufree(&args.status);

		if (event == Status) {
			/* keep polling while we fail with DSW_ENOTFOUND */
			if (rc != -1 || errno != DSW_ENOTFOUND)
				return (0);
		} else {
			/* event == CopyStart */
			if (rc == -1) {
				return (1);	/* something wrong */
			}
			if (args.stat & DSW_COPYINGP)
				return (0);	/* copying underway */
		}
	}
	/* child died */
	if (WIFEXITED(dead_stat))
		return (WEXITSTATUS(dead_stat));
	else
		return (1);
}

int
mounted(char *t)
{
	int	rdsk;
	int	i;
	FILE	*mntfp;
	struct mnttab mntref;
	struct mnttab mntent;
	char	target[DSW_NAMELEN];
	char	*s;

	rdsk = i = 0;
	for (s = target; i < DSW_NAMELEN && (*s = *t++); i++) {
		if (*s == 'r' && rdsk == 0)
			rdsk = 1;
		else
			s++;
	}
	*s = '\0';

	mntref.mnt_special = target;
	mntref.mnt_mountp = NULL;
	mntref.mnt_fstype = NULL;
	mntref.mnt_mntopts = NULL;
	mntref.mnt_time = NULL;

	if ((mntfp = fopen("/etc/mnttab", "r")) == NULL) {
		dsw_error(gettext("Can not check volume against mount table"),
					NULL);
	}
	if (getmntany(mntfp, &mntent, &mntref) != -1) {
		/* found something before EOF */
		(void) fclose(mntfp);
		return (1);
	}
	(void) fclose(mntfp);
	return (0);
}

void
enable(char *master_volume, char *shadow_volume,
	char *bitmap_volume, char *copy_type)
{
	dsw_config_t parms;
	dsw_ioctl_t temp;
	char	*p;
	int	rc;
	pid_t	child;
	spcs_s_info_t *sp_info;
	struct stat mstat, sstat, bstat;
	char	mst_dg[DSW_NAMELEN] = {0};
	char	shd_dg[DSW_NAMELEN] = {0};
	char	bmp_dg[DSW_NAMELEN] = {0};
	int	mvol_enabled;
	char	*altname;
	grptag_t *gdata;

	bzero(&parms, sizeof (dsw_config_t));

	if (strcmp(copy_type, "independent") == 0 ||
			strcmp(copy_type, gettext("independent")) == 0)
		parms.flag = DSW_GOLDEN;
	else if (strcmp(copy_type, "dependent") == 0 ||
			strcmp(copy_type, gettext("dependent")) == 0)
		parms.flag = 0;
	else
		dsw_error(gettext("don't understand shadow type"), NULL);

	/* validate volume names */
	if (perform_autosv()) {
		if (cfg_load_svols(cfg) < 0 || cfg_load_dsvols(cfg) < 0 ||
		    cfg_load_shadows(cfg) < 0) {
			dsw_error(gettext("Unable to parse config file"), NULL);
		}
		load_ii_vols(cfg);
		reload_vols = LD_SVOLS | LD_DSVOLS | LD_SHADOWS | LD_II;

		/* see if it's been used before under a different name */
		conform_name(&master_volume);
		conform_name(&shadow_volume);
		rc = cfg_get_canonical_name(cfg, bitmap_volume, &altname);
		if (rc < 0) {
			dsw_error(gettext("Unable to parse config file"), NULL);
		}
		if (rc) {
			errno = EBUSY;
			dsw_error(gettext("Bitmap in use"), NULL);
		}
	}

	/*
	 * If not local, determine disk group names for volumes in II set
	 */
	switch (check_cluster()) {
	case II_CLUSTER:
		/*
		 * Check if none or all volumes are in a disk group
		 */
		rc = 0;
		if (check_diskgroup(master_volume, mst_dg)) rc++;
		if (check_diskgroup(shadow_volume, shd_dg)) rc++;
		if (check_diskgroup(bitmap_volume, bmp_dg)) rc++;
		if ((rc != 0) && (rc != 3))
			dsw_error(gettext(
				"Not all Point-in-Time Copy volumes are "
				"in a disk group"), NULL);

		/*
		 * If volumes are not in a disk group, but are in a
		 * cluster, then "-C <tag>", must be set
		 */
		if (rc == 0 && cfg_cluster_tag == NULL)
			dsw_error(gettext(
				"Point-in-Time Copy volumes, that are not "
				"in a device group which has been "
				"registered with SunCluster, "
				"require usage of \"-C\""), NULL);

		/*
		 * the same disk group
		 * If -n, plus mst_dg==bmp_dg, then allow E/I/J in SunCluster
		 */
		if ((strcmp(mst_dg, bmp_dg)) ||
		    (strcmp(mst_dg, shd_dg) && (!nflg)))
			    dsw_error(gettext(
				"Volumes are not in same disk group"), NULL);

		/*
		 * Can never enable the same shadow twice, regardless of
		 * exportable shadow device group movement
		 */
		if (find_shadow_line(shadow_volume))
			dsw_error(gettext(
				"Shadow volume is already configured"), NULL);

		/*
		 * Groups cannot span multiple clusters
		 */
		if (group_name && perform_autosv()) {
			gdata = (grptag_t *)nsc_lookup(volhash, group_name);
			if (gdata &&
			    strncmp(gdata->ctag, mst_dg, DSW_NAMELEN) != 0) {
				errno = EINVAL;
				dsw_error(gettext("Group contains sets not "
				    "in the same cluster resource"), NULL);
			}
		}

		/*
		 * Check cluster tag and bitmap disk group
		 * set latter if different
		 */
		if (check_resource_group(bitmap_volume)) {
			/*
			 * Unload and reload in the event cluster tag has
			 * changed
			 */
			if (perform_autosv()) {
				unload_ii_vols();
				cfg_unload_shadows();
				cfg_unload_dsvols();
				cfg_unload_svols();
				if (cfg_load_svols(cfg) < 0 ||
				    cfg_load_dsvols(cfg) < 0 ||
				    cfg_load_shadows(cfg) < 0) {
					dsw_error(gettext(
					    "Unable to parse config "
					    "file"), NULL);
				}
				load_ii_vols(cfg);
			}
		}
		/*
		 * Copy cluster name into config
		 */
		(void) strncpy(parms.cluster_tag, cfg_cluster_tag, DSW_NAMELEN);
		break;

	case II_CLUSTER_LCL:
		/* ensure that the -C local won't interfere with the set */
		if (group_name && perform_autosv()) {
			gdata = (grptag_t *)nsc_lookup(volhash, group_name);
			if (gdata) {
				if (strlen(gdata->ctag) != 0) {
					errno = EINVAL;
					dsw_error(gettext("Unable to put set "
					    "into -C local and specified "
					    "group"), NULL);
				}
			}
		}
		break;
	}

	/*
	 * If we've got a group name, add it into the config
	 */
	if (group_name) {
		(void) strncpy(parms.group_name, group_name, DSW_NAMELEN);
	}

	/*
	 * Determine accessability of volumes
	 */
	if (stat(master_volume, &mstat) != 0)
		dsw_error(gettext(
			"Unable to access master volume"), NULL);
	if (!S_ISCHR(mstat.st_mode))
		dsw_error(gettext(
			"Master volume is not a character device"), NULL);
	/* check the shadow_vol hasn't be used as SNDR secondary vol */
	check_iishadow(shadow_volume);
	if (stat(shadow_volume, &sstat) != 0)
		dsw_error(gettext(
			"Unable to access shadow volume"), NULL);
	if (!S_ISCHR(sstat.st_mode))
		dsw_error(gettext(
			"Shadow volume is not a character device"), NULL);
	if (mounted(shadow_volume)) {
		errno = EBUSY;
		dsw_error(gettext(
			"Shadow volume is mounted, unmount it first"), NULL);
	}
	if (mstat.st_rdev == sstat.st_rdev) {
		errno = EINVAL;
		dsw_error(gettext(
		    "Master and shadow are the same device"), NULL);
	}
	if (stat(bitmap_volume, &bstat) != 0) {
		dsw_error(gettext("Unable to access bitmap"), NULL);
	}
	if (!S_ISCHR(bstat.st_mode))
		dsw_error(gettext(
			"Bitmap volume is not a character device"), NULL);
	if (S_ISCHR(bstat.st_mode)) {
		if (mstat.st_rdev == bstat.st_rdev) {
			errno = EINVAL;
			dsw_error(gettext(
			    "Master and bitmap are the same device"), NULL);
		} else if (sstat.st_rdev == bstat.st_rdev) {
			errno = EINVAL;
			dsw_error(gettext(
			    "Shadow and bitmap are the same device"), NULL);
		}
	}

	(void) strncpy(parms.master_vol, master_volume, DSW_NAMELEN);
	(void) strncpy(parms.shadow_vol, shadow_volume, DSW_NAMELEN);
	(void) strncpy(parms.bitmap_vol, bitmap_volume, DSW_NAMELEN);
	errno = 0;
	parms.status = spcs_s_ucreate();

	/*
	 * Check that none of the member volumes forms part of another
	 * InstantImage group.
	 *
	 * -- this check has been removed; it is done in the kernel instead
	 * -- PJW
	 */

	/*
	 * Check against overflow volumes
	 */
	for (p = get_overflow_list(); *p != NULL; p += DSW_NAMELEN) {
		if (strncmp(master_volume, p, DSW_NAMELEN) == 0)
			dsw_error(gettext(
				"Master volume is already an overflow volume"),
				NULL);
		else if (strncmp(shadow_volume, p, DSW_NAMELEN) == 0)
			dsw_error(gettext(
				"Shadow volume is already an overflow volume"),
				NULL);
		else if (strncmp(bitmap_volume, p, DSW_NAMELEN) == 0)
			dsw_error(gettext(
				"Bitmap volume is already an overflow volume"),
				NULL);
	}

	/*
	 * Make sure that the shadow volume is not already configured
	 */
	if (find_shadow_config(shadow_volume, NULL, &temp))
		dsw_error(gettext(
			"Shadow volume is already configured"), NULL);
	if (perform_autosv()) {
		/*
		 * parse the dsvol entries to see if we need to place
		 * the master or shadow under SV control
		 */
		if (nsc_lookup(volhash, master_volume) == NULL) {
			if (cfg_vol_enable(cfg, master_volume, cfg_cluster_tag,
			    "ii") < 0) {
				dsw_error(
				    gettext("Cannot enable master volume"),
				    NULL);
			}
			mvol_enabled = 1;
		} else {
			mvol_enabled = 0;
		}
		if (nsc_lookup(volhash, shadow_volume) == NULL) {
			if (nflg) {
				cfg_resource(cfg, shd_dg);
				rc = cfg_vol_enable(cfg, shadow_volume,
					shd_dg, "ii");
				cfg_resource(cfg, cfg_cluster_tag);
			} else {
				rc = cfg_vol_enable(cfg, shadow_volume,
					cfg_cluster_tag, "ii");
			}
			if (rc < 0) {
				if (mvol_enabled) {
					if (cfg_vol_disable(cfg,
					    master_volume, cfg_cluster_tag,
					    "ii") < 0)
					    dsw_error(gettext(
						"SV disable of master failed"),
						NULL);
				}
				dsw_error(
				    gettext("Cannot enable shadow volume"),
				    NULL);
			}
		}
		unload_ii_vols();
		cfg_unload_shadows();
		cfg_unload_dsvols();
		cfg_unload_svols();
		reload_vols = 0;
	}

	add_cfg_entry(&parms);
	cfg_unlock(cfg);
	config_locked = 0;

	(void) sigset(SIGCHLD, sigchild);
	switch (child = fork()) {

	case (pid_t)-1:
		dsw_error(gettext("Unable to fork"), NULL);
		break;

	case 0:
		rc = do_ioctl(dsw_fd, DSWIOC_ENABLE, &parms);
		if (rc == -1 && errno != DSW_EABORTED && errno != DSW_EIO) {
			/*
			 * Failed to enable shadow group, log problem and remove
			 * the shadow group from the config file.
			 */
			spcs_log("ii", &parms.status,
			    gettext("Enable failed %s %s %s (%s)"),
			    master_volume, shadow_volume, bitmap_volume,
			    parms.flag & DSW_GOLDEN ?
			    "independent" : "dependent");

			if (!ii_lock(cfg, CFG_WRLOCK) ||
			    !find_shadow_config(shadow_volume, NULL, &temp)) {
				dsw_error(gettext(
					"Enable failed, can't tidy up cfg"),
					&parms.status);
			}
			config_locked = 1;
			remove_iiset(setnumber, shadow_volume, 0);
			dsw_error(gettext("Enable failed"), &parms.status);
		}

		if (rc == -1)
			sp_info = &parms.status;
		else
			sp_info = NULL;
		spcs_log("ii", sp_info, gettext("Enabled %s %s %s (%s)"),
			master_volume, shadow_volume, bitmap_volume,
			parms.flag & DSW_GOLDEN ? "independent" : "dependent");
		spcs_s_ufree(&parms.status);
		break;

	default:
		exit(child_wait(child, Status, shadow_volume));
		break;
	}
}

int
reset(char *volume)
{
	dsw_ioctl_t args;
	dsw_config_t parms;
	int	rc;
	int	wait_loc;
	pid_t	child = (pid_t)0;
	enum copy_wait wait_action;
	spcs_s_info_t *stat;
	dsw_stat_t prev_stat;
	int stat_flags;
	static int unlocked = 0;
	int	do_enable = 0;
	char	key[CFG_MAX_KEY];
	char	optval[CFG_MAX_BUF];
	unsigned int flags;

	wait_action = WaitForStart;

	if (unlocked && !ii_lock(cfg, CFG_RDLOCK)) {
		dsw_error(gettext("Unable to set locking on the configuration"),
		    NULL);
	}
	config_locked = 1;
	if (!find_shadow_config(volume, &parms, &args))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
		    "group"), NULL);

	cfg_unlock(cfg);
	config_locked = 0;
	unlocked = 1;

	spcs_log("ii", NULL, gettext("Start reset %s"), volume);
	(void) strncpy(prev_stat.shadow_vol, volume, DSW_NAMELEN);
	prev_stat.shadow_vol[DSW_NAMELEN - 1] = '\0';
	prev_stat.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_STAT, &prev_stat) == -1) {
		/* set is suspended, so we do the enable processing instead */
		do_enable = 1;

		/* first check to see whether the set was offline */
		(void) snprintf(key, CFG_MAX_KEY, "ii.set%d.options",
		    setnumber);
		if (!ii_lock(cfg, CFG_RDLOCK)) {
			dsw_error(gettext("Unable to set locking on the "
			    "configuration"), NULL);
		}
		config_locked = 1;
		unlocked = 0;
		if (cfg_get_single_option(cfg, CFG_SEC_CONF, key,
		    NSKERN_II_BMP_OPTION, optval, CFG_MAX_BUF) < 0) {
			dsw_error(gettext("unable to read config file"), NULL);
		}
		cfg_unlock(cfg);
		config_locked = 0;
		unlocked = 1;
		(void) sscanf(optval, "%x", &flags);
		if ((flags & DSW_OFFLINE) == 0) {
			/* set wasn't offline - don't reset */
			dsw_error(gettext("Set not offline, will not reset"),
			    NULL);
		}
		parms.status = spcs_s_ucreate();
		stat = &parms.status;
		stat_flags = DSW_BMPOFFLINE;
	} else {
		args.status = spcs_s_ucreate();
		stat = &args.status;
		stat_flags = prev_stat.stat;
	}
	spcs_s_ufree(&prev_stat.status);

	if (wait_action == WaitForStart)
		(void) sigset(SIGCHLD, sigchild);

	switch (child = fork()) {

	case (pid_t)-1:
		dsw_error(gettext("Unable to fork"), NULL);
		break;

	case 0:
		if (do_enable) {
			rc = do_ioctl(dsw_fd, DSWIOC_ENABLE, &parms);
		} else {
			rc = do_ioctl(dsw_fd, DSWIOC_RESET, &args);
		}
		if (rc == -1 && errno != DSW_EABORTED && errno != DSW_EIO) {
			spcs_log("ii", stat, gettext("Fail reset %s"), volume);
			dsw_error(gettext("Reset shadow failed"), stat);
		}
		/* last_overflow is set during find_shadow_config */
		if (strlen(last_overflow) > 0 &&
		    (stat_flags & (DSW_SHDOFFLINE | DSW_BMPOFFLINE)) != 0) {
			/* reattach it */
			(void) strncpy(parms.bitmap_vol, last_overflow,
			    DSW_NAMELEN);
			do_attach(&parms);
		}
		spcs_log("ii", stat, gettext("Finish reset %s"), volume);
		spcs_s_ufree(stat);

		exit(0);
		break;
	default:
		if (wait_action == WaitForStart) {
			rc = child_wait(child, CopyStart, args.shadow_vol);
		} else { /* wait_action == WaitForEnd */
			wait_loc = 0;
			(void) wait(&wait_loc);
			if (WIFEXITED(wait_loc) && (WEXITSTATUS(wait_loc) == 0))
				rc = 0;
			else
				rc = -1;
		}
		break;
	}
	/* if successful, remove flags entry from options field */
	if (rc >= 0) {
		if (!ii_lock(cfg, CFG_WRLOCK)) {
			dsw_error(gettext("Unable to set locking on the "
			    "configuration"), NULL);
		}
		config_locked = 1;
		if (!find_shadow_config(volume, &parms, &args)) {
			dsw_error(gettext("Volume is not in a Point-in-Time "
			    "Copy group"), NULL);
		}
		(void) snprintf(key, CFG_MAX_KEY, "ii.set%d.options",
		    setnumber);
		if (cfg_del_option(cfg, CFG_SEC_CONF, key, NSKERN_II_BMP_OPTION)
		    < 0) {
			dsw_error(gettext("Update of config failed"), NULL);
		}
		(void) cfg_commit(cfg);
		cfg_unlock(cfg);
		config_locked = 0;
	}

	return (rc);
}

int
overflow(char *volume)
{
	dsw_ioctl_t args;
	int	rc;
	spcs_s_info_t *stat;

	check_action(gettext("Initialize this overflow volume?"));
	if (find_matching_cf_line(volume, NULL, &args))
		dsw_error(gettext("Volume is part of a Point-in-Time Copy "
					    "group"), NULL);
	args.status = spcs_s_ucreate();
	(void) strncpy(args.shadow_vol, volume, DSW_NAMELEN);
	rc = do_ioctl(dsw_fd, DSWIOC_OCREAT, &args);
	if (rc == -1) {
		spcs_log("ii", &args.status,
			gettext("Create overflow failed %s"), volume);
		dsw_error(gettext("Create overflow failed"), &args.status);
	}
	if (rc == -1)
		stat = &args.status;
	else
		stat = NULL;
	spcs_log("ii", stat, gettext("Create overflow succeeded %s"), volume);
	spcs_s_ufree(&args.status);

	return (0);
}

void
bitmap_op(char *master_volume, int print_bitmap, int bitmap_percent, int used,
    int is_compact)
{
	unsigned char *bitmap;
	char *name;
	int i, x, y;
	unsigned j;
	unsigned long n;
	unsigned long percent;

	bitmap = allocate_bitmap(master_volume);
	if (bitmap == NULL)
		return;

	if (bitmap_percent) {
		/* count the number of bits set in bitmap */
		for (i = n = 0; i < bm_size; i++)
			for (j = (unsigned)bitmap[i]; j; j &= j -1)
				n++;
		if (is_compact)
			(void) printf(gettext("Chunks in map: %d used: %d\n"),
			    used, n);
		if (bm_actual < 100) {
			percent = 0;
		} else {
			percent = (n * 100) / bm_actual;
		}
		(void) printf(gettext("Percent of bitmap set: %u\n"), percent);
		percent = percent/100;
		/* distinguish between 0.0000% and 0.n% of bitmap set */
		if (percent < 1)
			(void) printf("\t(%s)\n", n > 0 ?
			    gettext("bitmap dirty") : gettext("bitmap clean"));
	}

	if (print_bitmap) {
		name = strrchr(master_volume, '/');
		if (name++ == NULL)
		name = master_volume;
		i = bm_size * 8;
		x = (int)ceil(sqrt((double)i));
		x += (8 - (x % 8));	/* round up to nearest multiple of 8 */
		y = i / x;
		if (y * x < i)
			y++;
		(void) printf("#define bm%s_width %d\n#define bm%s_height %d\n",
		    name, x, name, y);
		(void) printf("#define bm%s_x_hot 0\n#define bm%s_y_hot 0\n",
		    name, name);
		(void) printf("static char bm%s_bits[] = {\n", name);
		for (i = 0; i < bm_size; i++) {
			if (i % 12 == 0)
				(void) printf("\n");
			(void) printf("0x%02x, ", bitmap[i]);
		}
		y = x * y;
		for (; i < y; i++) {
			if (i % 12 == 0)
				(void) printf("\n");
			(void) printf("0x00, ");
		}
		(void) printf("\n};\n");
	}

	free_bitmap(bitmap);
}

static int
validate_group_names(char **vol_list, char *group)
{
	ENTRY item, *found;
	int i, rc, count;
	dsw_aioctl_t *group_list;
	char *ptr;

	if (group == NULL || *group == NULL) {
		/* no group set, just count volume list */
		for (i = 0; *vol_list++ != NULL; i++)
			;
		return (i);
	}

	if ((count = do_ioctl(dsw_fd, DSWIOC_LISTLEN, NULL)) < 0)
		dsw_error("DSWIOC_LISTLEN", NULL);

	group_list = malloc(sizeof (dsw_aioctl_t) + count * DSW_NAMELEN);
	if (group_list == NULL)
		dsw_error(gettext("Failed to allocate memory"), NULL);

	bzero(group_list, sizeof (dsw_aioctl_t) + count * DSW_NAMELEN);
	group_list->count = count;
	group_list->flags = 0;
	group_list->status = spcs_s_ucreate();
	(void) strncpy(group_list->shadow_vol, group, DSW_NAMELEN);

	rc = do_ioctl(dsw_fd, DSWIOC_GLIST, group_list);
	if (rc < 0)
		dsw_error(gettext("Group list access failure"),
		    &group_list->status);

	group_list->shadow_vol[DSW_NAMELEN * group_list->count] = '\0';

	/* create hash and enter all volumes into it */
	if (hcreate(group_list->count) == 0)
		dsw_error(gettext("Failed to allocate memory"), NULL);
	ptr = group_list->shadow_vol;
	count = group_list->count;
	i = 0;
	while (i < count) {
		ptr[ DSW_NAMELEN - 1 ] = '\0';
		item.key = ptr;
		item.data = (void *) 0;
		(void) hsearch(item, ENTER);
		++i;
		ptr += DSW_NAMELEN;
	}

	/* now compare the volume list with the hash */
	for (i = 0; vol_list[ i ]; i++) {
		item.key = vol_list[ i ];
		found = hsearch(item, FIND);
		if (!found)
			dsw_error(gettext("Group config does not match kernel"),
			    NULL);
		if (found->data != (void *) 0)
			dsw_error(gettext("Duplicate volume specified"), NULL);
		found->data = (void *) 1;
	}
	if (i != count)
		dsw_error(gettext("Group config does not match kernel"), NULL);

	/* everything checks out */
	free(group_list);
	hdestroy();

	return (count);
}

int
do_acopy(char **vol_list, enum copy_update update_mode,
		enum copy_direction direction)
{
	dsw_aioctl_t *acopy_args;
	dsw_ioctl_t copy_args;
	dsw_config_t parms;
	dsw_stat_t	stat_s;
	int	i;
	int	rc;
	int	n_vols;
	char	*t;
	char	buf[1024];
	char	*sp;
	char	*ppid;

	n_vols = validate_group_names(vol_list, group_name);

	acopy_args = calloc(sizeof (dsw_aioctl_t) + n_vols * DSW_NAMELEN, 1);
	if (acopy_args == NULL)
		dsw_error(gettext("Too many volumes given for update"), NULL);

	acopy_args->count = n_vols;

	acopy_args->flags = 0;

	if (update_mode == Update)
		acopy_args->flags |= CV_BMP_ONLY;
	if (direction == ToMaster)
		acopy_args->flags |= CV_SHD2MST;
	if (pflg) {
		acopy_args->flags |= CV_LOCK_PID;
#ifdef DEBUG
		ppid = getenv("IIADM_PPID");
		if (ppid) {
			acopy_args->pid = atoi(ppid);
			(void) fprintf(stderr, "(using %s for ppid)\n", ppid);
		} else {
			acopy_args->pid = getppid();
		}
#else
		acopy_args->pid = getppid();
#endif
	}

	for (i = 0; i < n_vols; i++) {
		if (!find_shadow_config(vol_list[i], &parms, &copy_args))
			dsw_error(gettext("Volume is not in a Point-in-Time "
			    "group"), NULL);
		if (direction == ToMaster) {
			t = parms.master_vol;
		} else {
			t = parms.shadow_vol;
		}

		if (mounted(t)) {
			errno = EBUSY;
			dsw_error(gettext("Target of copy/update is mounted, "
						"unmount it first"), NULL);
		}

		(void) strncpy(stat_s.shadow_vol, parms.shadow_vol,
		    DSW_NAMELEN);
		stat_s.shadow_vol[DSW_NAMELEN-1] = '\0';
		stat_s.status = spcs_s_ucreate();
		rc = do_ioctl(dsw_fd, DSWIOC_STAT, &stat_s);
		spcs_s_ufree(&stat_s.status);
		if (rc == -1) {
			(void) sprintf(buf,
			    gettext("Shadow group %s is suspended"),
			    vol_list[i]);
			dsw_error(buf, NULL);
		}

		if (stat_s.stat & DSW_COPYINGP) {
			(void) fprintf(stderr, "%s: %s\n", cmdnam,
			    gettext("Copy already in progress"));
			exit(1);
		}
	}
	acopy_args->status = spcs_s_ucreate();
	for (i = 0; i < n_vols; i++) {
		spcs_log("ii", NULL, gettext("Atomic %s %s %s"),
			update_mode == Update ?
				gettext("update") : gettext("copy"),
			vol_list[i],
			direction == ToMaster ?  gettext("from shadow") :
			gettext("to shadow"));
	}
	if (group_name == NULL || *group_name == NULL) {
		sp = acopy_args->shadow_vol;
		for (i = 0; i < n_vols; i++, sp += DSW_NAMELEN)
			(void) strncpy(sp, vol_list[i], DSW_NAMELEN);
	} else {
		(void) strncpy(acopy_args->shadow_vol, group_name, DSW_NAMELEN);
		acopy_args->flags |= CV_IS_GROUP;
	}
	rc = do_ioctl(dsw_fd, DSWIOC_ACOPY, acopy_args);
	if (rc == -1) {
		i = acopy_args->count;
		if (i < 0 || i >= n_vols) {
			spcs_log("ii", NULL, gettext("Atomic update failed"));
			(void) sprintf(buf, gettext("Update failed"));
		} else {
			spcs_log("ii", NULL,
				gettext("Atomic update of %s failed"),
				vol_list[acopy_args->count]);
			(void) sprintf(buf, gettext("Update of %s failed"),
			    vol_list[acopy_args->count]);
		}
		dsw_error(buf, &(acopy_args->status));
	}
	return (rc);
}

int
do_copy(char **vol_list, enum copy_update update_mode,
		enum copy_direction direction, enum copy_wait wait_action)
{
	dsw_ioctl_t copy_args;
	dsw_config_t parms;
	dsw_stat_t	stat_s;
	int	rc;
	int	wait_loc;
	char	*t;
	char	*volume;
	pid_t	child = (pid_t)0;
	char	*ppid;

	if (vol_list[0] && vol_list[1])
		return (do_acopy(vol_list, update_mode, direction));

	volume = vol_list[0];
	if (!find_shadow_config(volume, &parms, &copy_args))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
					    "group"), NULL);

	cfg_unlock(cfg);
	config_locked = 0;
	copy_args.flags = 0;

	if (update_mode == Update)
		copy_args.flags |= CV_BMP_ONLY;
	if (direction == ToMaster) {
		copy_args.flags |= CV_SHD2MST;
		t = parms.master_vol;
	} else {
		t = parms.shadow_vol;
	}
	if (pflg) {
		copy_args.flags |= CV_LOCK_PID;
#ifdef DEBUG
		ppid = getenv("IIADM_PPID");
		if (ppid) {
			copy_args.pid = atoi(ppid);
			(void) fprintf(stderr, "(using %s for ppid)\n", ppid);
		} else {
			copy_args.pid = getppid();
		}
#else
		copy_args.pid = getppid();
#endif
	}

	if (mounted(t)) {
		errno = EBUSY;
		dsw_error(gettext("Target of copy/update is mounted, "
					"unmount it first"), NULL);
	}

	(void) strncpy(stat_s.shadow_vol, copy_args.shadow_vol, DSW_NAMELEN);
	stat_s.shadow_vol[DSW_NAMELEN-1] = '\0';
	stat_s.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_STAT, &stat_s);
	spcs_s_ufree(&stat_s.status);
	if (rc == -1)
		dsw_error(gettext("Shadow group suspended"), NULL);

	if (stat_s.stat & DSW_COPYINGP) {
		(void) fprintf(stderr, "%s: %s\n", cmdnam,
		    gettext("Copy already in progress"));
		exit(1);
	}

	copy_args.status = spcs_s_ucreate();
	spcs_log("ii", NULL, gettext("Start %s %s %s"),
			update_mode == Update ?
				gettext("update") : gettext("copy"),
			volume,
			direction == ToMaster ?  gettext("from shadow") :
			gettext("to shadow"));

	if (wait_action == WaitForStart)
		(void) sigset(SIGCHLD, sigchild);
	switch (child = fork()) {

	case (pid_t)-1:
		dsw_error(gettext("Unable to fork"),
					NULL);
		break;

	case 0:
		rc = do_ioctl(dsw_fd, DSWIOC_COPY, &copy_args);
		if (rc == -1) {
			spcs_log("ii", &copy_args.status,
			    gettext("Fail %s %s %s"),
			    update_mode == Update ?
					gettext("update") : gettext("copy"),
			    volume,
			    direction == ToMaster ?  gettext("from shadow")
					: gettext("to shadow"));
			dsw_error(gettext("Copy failed"), &copy_args.status);
		}
		spcs_s_ufree(&copy_args.status);
		spcs_log("ii", NULL, gettext("Finish %s %s %s"),
		    update_mode == Update ?
				gettext("update") : gettext("copy"),
		    volume,
		    direction == ToMaster ?  gettext("from shadow") :
				gettext("to shadow"));

		exit(0);
		break;
	default:
		if (wait_action == WaitForStart) {
			rc = child_wait(child, CopyStart, copy_args.shadow_vol);
		} else { /* wait_action == WaitForEnd */
			wait_loc = 0;
			(void) wait(&wait_loc);
			if (WIFEXITED(wait_loc) && (WEXITSTATUS(wait_loc) == 0))
				rc = 0;
			else
				rc = 1;
		}
		break;
	}
	return (rc);
}

void
print_status(dsw_config_t *conf, int in_config)
{
	dsw_stat_t args;
	int	stat_flags;
	static int need_sep = 0;
	time_t tmp_time;

	if (need_sep++ > 0)
		(void) printf("--------------------------------------"
		    "----------------------------------------\n");
	(void) strncpy(args.shadow_vol, conf->shadow_vol, DSW_NAMELEN);
	args.shadow_vol[DSW_NAMELEN-1] = '\0';
	if (in_config) {
		(void) printf("%s: %s\n",
		    conf->master_vol, gettext("(master volume)"));
		(void) printf("%s: %s\n",
		    conf->shadow_vol, gettext("(shadow volume)"));
		(void) printf("%s: %s\n",
		    conf->bitmap_vol, gettext("(bitmap volume)"));
	}

	/*
	 * Do special checking on the status of this volume in a Sun Cluster
	 */
	if (check_cluster() == II_CLUSTER) {
	    char dgname[CFG_MAX_BUF], *other_node;

	    if (cfg_dgname(conf->bitmap_vol, dgname, sizeof (dgname))) {
		if (strlen(dgname)) {
		    int rc = cfg_dgname_islocal(dgname, &other_node);
		    if (rc < 0) {
			(void) printf(gettext(
			    "Suspended on this node, not active elsewhere\n"));
			return;
		    } else if (rc == 0) {
			(void) printf(gettext(
				"Suspended on this node, active on %s\n"),
				other_node);
			return;
		    }
		}
	    }
	}

	args.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_STAT, &args) == -1) {

		/* Handle Not found or not in config */
		if (errno != DSW_ENOTFOUND || !in_config)
			dsw_error(gettext("Stat failed"), &args.status);

		/* Just suspend */
		(void) printf(gettext("Suspended.\n"));
		return;
	}

	if (args.overflow_vol[0] != '\0')
		(void) printf("%s: %s\n", args.overflow_vol,
		    gettext("(overflow volume)"));

	if (conf->group_name[0] != '\0')
		(void) printf(gettext("Group name: %s\n"),
			    conf->group_name);

	if (conf->cluster_tag[0] != '\0')
		(void) printf(gettext("Cluster tag: %s\n"),
			    conf->cluster_tag);

	stat_flags = args.stat;
	spcs_s_ufree(&args.status);
	if (stat_flags & DSW_GOLDEN)
		(void) printf(gettext("Independent copy"));
	else
		(void) printf(gettext("Dependent copy"));

	if (stat_flags & DSW_TREEMAP)
		(void) printf(gettext(", compacted shadow space"));

	if (stat_flags & DSW_COPYINGP)
		(void) printf(gettext(", copy in progress"));
	else if (stat_flags & DSW_COPYING)
		(void) printf(gettext(", copy not active"));

	if (stat_flags & DSW_COPYINGM)
		(void) printf(gettext(", copying master to shadow"));

	if (stat_flags & DSW_COPYINGS)
		(void) printf(gettext(", copying shadow to master"));

	if (stat_flags & DSW_COPYINGX)
		(void) printf(gettext(", abort of copy requested"));

	if (stat_flags & DSW_MSTOFFLINE)
		(void) printf(gettext(", master volume offline"));

	if (stat_flags & DSW_SHDOFFLINE)
		(void) printf(gettext(", shadow volume offline"));

	if (stat_flags & DSW_BMPOFFLINE)
		(void) printf(gettext(", bitmap volume offline"));

	if (stat_flags & DSW_OVROFFLINE)
		(void) printf(gettext(", overflow volume offline"));

	if (stat_flags & DSW_SHDEXPORT)
		(void) printf(gettext(", shadow volume exported"));

	if (stat_flags & DSW_SHDIMPORT)
		(void) printf(gettext(", shadow volume imported"));

	if (stat_flags & DSW_OVERFLOW)
		(void) printf(gettext(", out of space"));

	if (stat_flags & DSW_VOVERFLOW)
		(void) printf(gettext(", spilled into overflow volume"));
	(void) printf("\n");

	tmp_time = args.mtime;
	if (tmp_time != 0)
		(void) printf("%s %s", gettext("Latest modified time:"),
			ctime(&tmp_time));
	else
		(void) printf("%s\n", gettext("Latest modified time: unknown"));

	(void) printf("%s %8llu\n", gettext("Volume size:"), args.size);
	if (args.shdsize != 0) {
		(void) printf("%s %lld %s %lld\n",
			gettext("Shadow chunks total:"), args.shdsize,
			gettext("Shadow chunks used:"), args.shdused);
	}
	bitmap_op(args.shadow_vol, 0, 1, 0, 0);
}

int
abort_copy(char *volume)
{
	dsw_ioctl_t args;

	if (!find_shadow_config(volume, NULL, &args))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
						"group"), NULL);
	args.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_ABORT, &args)  == -1)
		dsw_error(gettext("Abort failed"), &args.status);
	spcs_log("ii", NULL, gettext("Abort %s"), args.shadow_vol);
	spcs_s_ufree(&args.status);
	return (0);
}

void
iiversion()
{
	dsw_version_t args;

	args.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_VERSION, &args)  == -1)
		dsw_error(gettext("Version failed"), &args.status);
	spcs_s_ufree(&args.status);
#ifdef DEBUG
	(void) printf(gettext("Point in Time Copy version %d.%d.%d.%d\n"),
	    args.major, args.minor, args.micro, args.baseline);
#else
	if (args.micro) {
		(void) printf(gettext("Point in Time Copy version %d.%d.%d\n"),
		    args.major, args.minor, args.micro);
	} else {
		(void) printf(gettext("Point in Time Copy version %d.%d\n"),
		    args.major, args.minor);
	}
#endif
}

void
list_volumes()
{
	dsw_list_t args;
	int i, set, found;
	dsw_config_t *lp;
	ENTRY item, *ip;
	dsw_config_t parms;

	if ((i = do_ioctl(dsw_fd, DSWIOC_LISTLEN, &args)) == -1)
		dsw_error("DSWIOC_LISTLEN", NULL);

	args.status = spcs_s_ucreate();
	args.list_used = 0;
	args.list_size = i + 4;
	lp = args.list = (dsw_config_t *)
	    malloc(args.list_size * sizeof (dsw_config_t));

	if (args.list == NULL)
		dsw_error(gettext("Failed to allocate memory"), NULL);
	if (do_ioctl(dsw_fd, DSWIOC_LIST, &args)  == -1)
		dsw_error(gettext("List failed"), &args.status);
	spcs_s_ufree(&args.status);

	/* make a hashtable */
	if (args.list_used > 0) {
		if (hcreate(args.list_used) == 0) {
			dsw_error(gettext("Failed to allocate memory"), NULL);
			/*NOTREACHED*/
		}
	}

	/* populate the hashtable */
	for (i = 0; i < args.list_used; i++, lp++) {
		item.key = lp->shadow_vol;
		item.data = (char *)lp;
		if (hsearch(item, ENTER) == NULL) {
			dsw_error(gettext("Failed to allocate memory"), NULL);
			/*NOTREACHED*/
		}
	}

	/* perform action for each line of the stored config file */
	for (set = 1; get_dsw_config(set, &parms) == 0; set++) {

		/* Are there any II sets configured on this node? */
		if (args.list_used > 0) {
			item.key = parms.shadow_vol;

			/* Is this volume configured on this node? */
			if (ip = hsearch(item, FIND)) {

				/* Handle Imported Shadows */
				/* LINTED alignment of cast ok */
				lp = (dsw_config_t *)ip->data;
				if (strcmp(parms.master_vol,
					II_IMPORTED_SHADOW))
					found = !(lp->flag & DSW_SHDIMPORT);
				else
					found = (lp->flag & DSW_SHDIMPORT);
			}
			else
				found = FALSE;
		}
		else
			found = FALSE;

		if ((cfg_cluster_tag) &&
			strcmp(cfg_cluster_tag, parms.cluster_tag))
			continue;

		if ((group_name) && strcmp(group_name, parms.group_name))
			continue;

		(void) printf("%s %.*s %.*s %.*s%s\n",
		    (parms.flag & DSW_GOLDEN) ? "ind" : "dep",
		    DSW_NAMELEN, parms.master_vol,
		    DSW_NAMELEN, parms.shadow_vol,
		    DSW_NAMELEN, parms.bitmap_vol,
		    found ? "" : gettext(" (suspended)"));
	}
	hdestroy();
	free(args.list);
}

int
wait_for_copy(char *volume)
{
	dsw_ioctl_t parms;
	int rc;
	static int unlocked = 0;
	char *ppid;

	if (unlocked && !ii_lock(cfg, CFG_RDLOCK)) {
		dsw_error(gettext("Unable to set locking on the configuration"),
		    NULL);
	}
	config_locked = 1;
	if (!find_shadow_config(volume, NULL, &parms))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
						"group"), NULL);
	cfg_unlock(cfg);
	config_locked = 0;
	unlocked = 1;

	parms.status = spcs_s_ucreate();
	if (pflg) {
#ifdef DEBUG
		ppid = getenv("IIADM_PPID");
		if (ppid) {
			parms.pid = atoi(ppid);
			(void) fprintf(stderr, "(using %s for ppid)\n", ppid);
		} else {
			parms.pid = (nflg) ? -1 : getppid();
		}
#else
		parms.pid = (nflg) ? -1 : getppid();
#endif
		parms.flags |= CV_LOCK_PID;
	}

	rc = do_ioctl(dsw_fd, DSWIOC_WAIT, &parms);
	if (rc == -1)
		dsw_error(gettext("Wait failed"), &parms.status);
	spcs_s_ufree(&parms.status);
	return (0);
}

int
export(char *volume)
{
	dsw_ioctl_t parms;
	dsw_config_t conf;
	char *old_ctag, dgname[DSW_NAMELEN];
	int rc;

	if (!find_shadow_config(volume, &conf, &parms))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
				"group"), NULL);
	if (mounted(volume))
		dsw_error(gettext("Can't export a mounted volume"), NULL);

	/* If this is an exportable shadow in the cluster, change ctag */
	if (strlen(conf.cluster_tag) &&
	    (cfg_dgname(volume, dgname, sizeof (dgname)))) {
		old_ctag = cfg_cluster_tag;
		cfg_resource(cfg, cfg_cluster_tag = strdup(dgname));
	} else	old_ctag = NULL;

	if (cfg_load_dsvols(cfg) < 0 || cfg_load_shadows(cfg) < 0) {
		dsw_error(gettext("Unable to parse config file"), NULL);
	}
	reload_vols = LD_DSVOLS | LD_SHADOWS;
	conform_name(&volume);

	spcs_log("ii", NULL, gettext("Export %s"), volume);
	parms.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_EXPORT, &parms);
	if (rc == -1)
		dsw_error(gettext("Export failed"), &parms.status);
	if (perform_autosv()) {
		if (cfg_vol_disable(cfg, volume, cfg_cluster_tag, "ii") < 0) {
			dsw_error(gettext("SV-disable failed"), NULL);
		}
		(void) cfg_commit(cfg);
	}

	/* restore old cluster tag, if changed */
	if (old_ctag != NULL)
		cfg_resource(cfg, cfg_cluster_tag = old_ctag);

	spcs_s_ufree(&parms.status);
	return (0);
}

int
detach(char *volume)
{
	dsw_ioctl_t parms;
	int rc;

	if (!find_shadow_config(volume, NULL, &parms))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
						"group"), NULL);
	parms.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_ODETACH, &parms);
	if (rc == 0) {
		/* remove overflow from cfg line */
		(void) sprintf(key, "ii.set%d.overflow", setnumber);
		if (cfg_put_cstring(cfg, key, "-", 1) < 0) {
				perror("cfg_put_cstring");
		}
		(void) cfg_commit(cfg);
	} else {
		spcs_log("ii", NULL, gettext("Detach of overflow %s failed"),
				parms.shadow_vol);
		dsw_error(gettext("Failed to detach overflow volume"),
				&parms.status);
	}
	return (rc);
}

static void
can_disable(char *vol)
{
	dsw_stat_t args;

	if (mounted(vol)) {
		(void) strncpy(args.shadow_vol, vol, DSW_NAMELEN);
		args.shadow_vol[DSW_NAMELEN - 1] = '\0';
		args.status = spcs_s_ucreate();
		if (do_ioctl(dsw_fd, DSWIOC_STAT, &args) != -1 &&
		    (args.stat & DSW_GOLDEN) == 0) {
			errno = EBUSY;
			dsw_error(gettext("Shadow Volume is currently mounted "
			    "and dependent on the master volume"), NULL);
		}
		spcs_s_ufree(&args.status);
	}
}

static void
clean_up_after_failed_disable(dsw_ioctl_t *parms)
{
	char **p;
	dsw_stat_t args;

	for (p = group_volumes; *p; p++) {
		(void) strncpy(args.shadow_vol, *p, DSW_NAMELEN);
		args.shadow_vol[DSW_NAMELEN - 1] = '\0';
		args.status = spcs_s_ucreate();
		if (do_ioctl(dsw_fd, DSWIOC_STAT, &args) == -1) {
			/* set was successfully disabled */
			if (find_shadow_config(*p, NULL, NULL))
				remove_iiset(setnumber, *p, 0);
		}
		spcs_s_ufree(&args.status);
	}

	dsw_error(gettext("Some sets in the group failed to disable"),
	    &parms->status);
}

int
dsw_group_or_single_disable(int argc, char *argv[])
{
	int rc = 0;
	char **p;
	dsw_ioctl_t parms;
	int flags = 0;
	dsw_config_t conf;
	int shd_exported = 0;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	if (group_name) {
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
			    "has no members"), NULL);
		for (p = group_volumes; *p; p++) {
			can_disable(*p);
		}

		(void) strncpy(parms.shadow_vol, group_name, DSW_NAMELEN);
		if (*group_name)
			flags = CV_IS_GROUP;
	} else {
		if (!find_shadow_config(argv[1], &conf, &parms)) {
			dsw_error(gettext("Volume is not in a Point-in-Time "
			    "Copy group"), NULL);
		}

		can_disable(argv[1]);
		flags = 0;
	}

	if (group_name && !*group_name) {
		/* user typed iiadm -g "" -d */
		for (p = group_volumes; *p; p++) {
			parms.status = spcs_s_ucreate();
			parms.flags = flags;
			(void) strncpy(parms.shadow_vol, *p, DSW_NAMELEN);
			rc = do_ioctl(dsw_fd, DSWIOC_DISABLE, &parms);
			if (rc == -1 && errno != DSW_ENOTFOUND)
				dsw_error(gettext("Disable failed"),
				    &parms.status);
			if (!find_shadow_config(*p, NULL, NULL))
				dsw_error(gettext("Volume is not in a Point-in"
				    "-Time Copy group"), &parms.status);
			remove_iiset(setnumber, *p, 0);
			spcs_s_ufree(&parms.status);
			spcs_log("ii", NULL, gettext("Disabled %s"),
			    parms.shadow_vol);
		}
	} else {
		if (is_exported(conf.shadow_vol)) {
			shd_exported = 1;
		}
		if ((strcmp(conf.master_vol, II_IMPORTED_SHADOW) == 0) &&
		    is_exported(conf.shadow_vol)) {
			dsw_error(gettext(
			"Imported shadow not disabled"), NULL);
		}

		parms.status = spcs_s_ucreate();
		parms.flags = flags;
		rc = do_ioctl(dsw_fd, DSWIOC_DISABLE, &parms);
		if (rc == -1 && errno != DSW_ENOTFOUND) {
			if (errno == DSW_EDISABLE) {
				/*
				 * one or more sets within the group
				 * couldn't disable
				 */
				clean_up_after_failed_disable(&parms);
			} else {
				dsw_error(gettext("Disable failed"),
				    &parms.status);
			}
		}
		spcs_log("ii", NULL, gettext("Disabled %s"), parms.shadow_vol);
	}


	if (group_name && *group_name) {
		for (p = group_volumes; *p; p++) {
			if (!find_shadow_config(*p, NULL, NULL)) {
				/* argh! */
				(void) fprintf(stderr,
				    gettext("Volume '%s' is not "
				    "in a Point-in-Time Copy group"), *p);
			} else {
				remove_iiset(setnumber, *p, 0);
			}
		}
	} else if (!group_name) {
		if (!find_shadow_config(argv[1], NULL, NULL)) {
			/* argh! */
			dsw_error(gettext("Volume is not in a Point-in-Time "
			    "Copy group"), NULL);
		}

		remove_iiset(setnumber, argv[1], shd_exported);
	}

	return (0);
}

int
dsw_group_or_single_op(int argc, char *argv[], int (*op)(char *))
{
	int rc = 0;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	if (group_name) {
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		for (; *group_volumes; group_volumes++)
			rc |= (*op)(*group_volumes);
	} else {
		rc = (*op)(argv[1]);
	}
	return (rc);
}

void
dsw_list_clusters(char *cluster)
{
	dsw_aioctl_t *acopy_args;
	int rc, i, count;
	char *ptr;

	if ((count = do_ioctl(dsw_fd, DSWIOC_LISTLEN, NULL)) < 0)
		dsw_error("DSWIOC_LISTLEN", NULL);

	acopy_args = malloc(sizeof (dsw_aioctl_t) + count * DSW_NAMELEN);
	if (acopy_args == NULL)
		dsw_error(gettext("Can't get memory for list enquiry"), NULL);

	bzero(acopy_args, sizeof (dsw_aioctl_t) + count * DSW_NAMELEN);
	acopy_args->count = count;
	acopy_args->flags = 0;
	acopy_args->status = spcs_s_ucreate();
	if (cluster)
		(void) strncpy(acopy_args->shadow_vol, cluster, DSW_NAMELEN);

	rc = do_ioctl(dsw_fd, DSWIOC_CLIST, acopy_args);
	if (rc == -1)
		dsw_error(gettext("Cluster list access failure"),
		    &acopy_args->status);

	acopy_args->shadow_vol[DSW_NAMELEN*acopy_args->count] = NULL;

	if (cluster) {
		(void) printf(gettext("Sets in cluster resource group %s:\n"),
		    cluster);
	} else {
		(void) printf(
		    gettext("Currently configured resource groups\n"));
	}
	for (i = 0, ptr = acopy_args->shadow_vol; *ptr &&
	    i < acopy_args->count; i++, ptr += DSW_NAMELEN) {
		(void) printf("  %-64.64s\n", ptr);
	}
}

void
dsw_enable(int argc, char *argv[])
{
	if (argc != 5)
		usage(gettext("Incorrect number of arguments"));

	enable(argv[1], argv[2], argv[3], argv[4]);
	exit(0);
}


void
dsw_disable(int argc, char *argv[])
{
	(void) dsw_group_or_single_disable(argc, argv);
	exit(0);
}


void
dsw_copy_to_shadow(int argc, char *argv[])
{
	char	**volume_list;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));
	if (group_name == NULL)
		volume_list = ++argv;
	else {
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		volume_list = group_volumes;
	}

	exit(do_copy(volume_list, Copy, ToShadow, WaitForStart));
}


void
dsw_update_shadow(int argc, char *argv[])
{
	char	**volume_list;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));
	if (group_name == NULL)
		volume_list = ++argv;
	else {
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		volume_list = group_volumes;
	}

	exit(do_copy(volume_list, Update, ToShadow, WaitForStart));
}


void
dsw_copy_to_master(int argc, char *argv[])
{
	char	**volume_list;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));
	if (group_name == NULL) {
		volume_list = ++argv;
		check_action(gettext("Overwrite master with shadow volume?"));
	} else {
		check_action(gettext("Overwrite every"
			" master in this group with its shadow volume?"));
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		volume_list = group_volumes;
	}

	exit(do_copy(volume_list, Copy, ToMaster, WaitForStart));
}


void
dsw_update_master(int argc, char *argv[])
{
	char	**volume_list;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));
	if (group_name == NULL) {
		volume_list = ++argv;
		check_action(gettext("Overwrite master with shadow volume?"));
	} else {
		check_action(gettext("Overwrite every"
			" master in this group with its shadow volume?"));
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		volume_list = group_volumes;
	}

	exit(do_copy(volume_list, Update, ToMaster, WaitForStart));
}


void
dsw_abort_copy(int argc, char *argv[])
{
	exit(dsw_group_or_single_op(argc, argv, abort_copy));
}


void
dsw_display_status(int argc, char *argv[])
{
	dsw_config_t parms;
	int	in_config;

	if (argc != 2 && argc != 1)
		usage(gettext("Incorrect number of arguments"));

	/* "iiadm -i" and "iiadm -i all" are equivalent */
	if (argc == 2 && strcmp("all", argv[1]) != 0) {
		in_config = find_shadow_config(argv[1], &parms, NULL);
		if (!in_config) {
			(void) printf(gettext(
			    "Volume is not in configuration file\n"), NULL);
			(void) fflush(stdout);
			(void) strncpy(parms.shadow_vol, argv[1], DSW_NAMELEN);
			parms.shadow_vol[DSW_NAMELEN] = '\0';
		}
		print_status(&parms, in_config);
	} else if (group_name) {
		if (find_group_members(group_name) < 1)
			dsw_error(gettext("Group does not exist or "
				"has no members"),
						NULL);
		for (; *group_volumes; group_volumes++) {
			in_config = find_shadow_config(*group_volumes,
						&parms, NULL);
			if (in_config)
				print_status(&parms, in_config);
		}
	} else {
		/* perform action for each line of the stored config file */
		for (setnumber = 1;
			!get_dsw_config(setnumber, &parms); setnumber++) {
			switch (check_cluster()) {
			case II_CLUSTER:
			    if ((cfg_cluster_tag) &&
				(strcmp(cfg_cluster_tag, parms.cluster_tag)))
				    continue;
			    break;
			case II_CLUSTER_LCL:
			    if (strlen(parms.cluster_tag))
				    continue;
			    break;
			}
			print_status(&parms, 1);
		}
	}
	exit(0);
}

void
dsw_display_bitmap(int argc, char *argv[])
{
	dsw_config_t parms;
	int	in_config;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	in_config = find_shadow_config(argv[1], &parms, NULL);
	if (!in_config) {
		(void) printf(gettext(
		    "Volume is not in configuration file\n"), NULL);
		(void) fflush(stdout);
		(void) strncpy(parms.master_vol, argv[1], DSW_NAMELEN);
		parms.master_vol[DSW_NAMELEN] = '\0';
	}

	bitmap_op(parms.shadow_vol, 1, 0, 0, 0);
	exit(0);
}


/*ARGSUSED*/
void
dsw_version(int argc, char *argv[])
{
	iiversion();
	exit(0);
}

void
dsw_reset(int argc, char *argv[])
{
	exit(dsw_group_or_single_op(argc, argv, reset));
}

void
dsw_overflow(int argc, char *argv[])
{
	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	exit(overflow(argv[1]));
}

void
dsw_wait(int argc, char *argv[])
{
	exit(dsw_group_or_single_op(argc, argv, wait_for_copy));
}

/*ARGSUSED*/
void
dsw_list_volumes(int argc, char *argv[])
{
	if (argc != 1)
		usage(gettext("Incorrect number of arguments"));

	list_volumes();
	exit(0);
}

void
dsw_export(int argc, char *argv[])
{
	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	exit(dsw_group_or_single_op(argc, argv, export));
}

void
dsw_detach(int argc, char *argv[])
{
	(void) dsw_group_or_single_op(argc, argv, detach);
	exit(0);
}

void
import(char *shadow_volume, char *bitmap_volume)
{
	dsw_config_t parms = {0};
	int rc = 0;
	char	shd_dg[DSW_NAMELEN];
	char	bmp_dg[DSW_NAMELEN];

	/*
	 * If importing a shadow volume and the shadow volume is already
	 * configured, we only support this if we are in a Sun Cluster
	 * and the current user specified a cluster tag of -C local
	 */
	if (find_shadow_config(shadow_volume, &parms, NULL)) {
		dsw_error(gettext("Can't import volume on same node"), NULL);
	}

	switch (check_cluster()) {
	case II_CLUSTER:
	case II_CLUSTER_LCL:
		(void) check_resource_group(shadow_volume);
		if (cfg_cluster_tag) { /* check all volumes are in same dg */
			if (cfg_dgname(shadow_volume, shd_dg, DSW_NAMELEN)
			    == NULL)
				dsw_error(gettext("Shadow volume not in a"
				    " disk group"), NULL);
			if (cfg_dgname(bitmap_volume, bmp_dg, DSW_NAMELEN)
			    == NULL)
				dsw_error(gettext("Bitmap volume not in a"
				    " disk group"), NULL);
			if (strcmp(bmp_dg, shd_dg) != 0)
				dsw_error(gettext("Bitmap volume not in"
				    " same disk group as shadow set members"),
				    NULL);
		}
		break;
	case II_NOT_CLUSTER:
		/* do nothing */
		break;
	default:
		dsw_error(gettext(
		    "Unexpected return from check_cluster()"), NULL);
	}

	/* Local configuration volumes */
	if (cfg_load_dsvols(cfg) < 0 || cfg_load_shadows(cfg) < 0) {
		dsw_error(gettext("Unable to parse config file"), NULL);
	}

	reload_vols = LD_DSVOLS | LD_SHADOWS;
	conform_name(&shadow_volume);
	(void) strcpy(parms.master_vol, II_IMPORTED_SHADOW);
	(void) strncpy(parms.shadow_vol, shadow_volume, DSW_NAMELEN);
	parms.shadow_vol[DSW_NAMELEN-1] = '\0';
	(void) strncpy(parms.bitmap_vol, bitmap_volume, DSW_NAMELEN);
	parms.bitmap_vol[DSW_NAMELEN-1] = '\0';
	parms.flag = DSW_GOLDEN;

	spcs_log("ii", NULL, gettext("Import %s %s"),
	    parms.shadow_vol, parms.bitmap_vol);
	parms.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_IMPORT, &parms);
	if (rc == -1) {
		spcs_log("ii", NULL, gettext("Import failed %s %s"),
		    parms.shadow_vol, parms.bitmap_vol);
		dsw_error(gettext("Import failed"), &parms.status);
	}
	if (perform_autosv()) {
		if (cfg_vol_enable(cfg, shadow_volume, cfg_cluster_tag, "ii")
		    < 0) {
			dsw_error(gettext("SV-enable failed"), NULL);
		}
		/* cfg_commit is called by add_cfg_entry below */
	}
	spcs_s_ufree(&parms.status);
	add_cfg_entry(&parms);
}

void
dsw_import(int argc, char *argv[])
{
	if (argc != 3)
		usage(gettext("Incorrect number of arguments"));
	import(argv[1], argv[2]);

	exit(0);
}

void
join(char *shadow_volume, char *bitmap_file)
{
	dsw_ioctl_t shd;
	dsw_config_t conf;
	dsw_bitmap_t parms;
	int rc = 0;
	int size;
	FILE *bmpfp;
	uchar_t *shd_bitmap = 0;
	ii_header_t header;
	char dgname[DSW_NAMELEN];

	if (!find_shadow_config(shadow_volume, &conf, &shd))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
				"group"), NULL);

	/* If this is an exportable shadow in the cluster, change ctag */
	if (strlen(conf.cluster_tag) &&
	    (cfg_dgname(shadow_volume, dgname, sizeof (dgname))))
		cfg_resource(cfg, cfg_cluster_tag = strdup(dgname));

	if (cfg_load_dsvols(cfg) < 0 || cfg_load_shadows(cfg) < 0) {
		dsw_error(gettext("Unable to parse config file"), NULL);
	}
	reload_vols = LD_DSVOLS | LD_SHADOWS;
	conform_name(&shadow_volume);

	if ((bmpfp = fopen(bitmap_file, "r")) == NULL) {
		perror(bitmap_file);
		(void) fprintf(stderr,
		    gettext("Can't open imported bitmap volume\n"));
		exit(1);
	}

	if (fread(&header, sizeof (header), 1, bmpfp) != 1) {
		(void) fprintf(stderr,
		    gettext("Can't read imported bitmap volume\n"));
		exit(1);
	}

	/* See if this is a bitmap header */
	switch (header.ii_magic) {
	case DSW_DIRTY:		/* A copy of a enable bitmap volume */
	case DSW_CLEAN:
		check_action(gettext("Use the never imported bitmap?"));
		break;
	case DSW_INVALID:	/* A valid diskable secondary bitmap */
		break;
	default:
		(void) fprintf(stderr,
		    gettext("Secondary bitmap is not a valid bitmap volume\n"));
		exit(1);
	}

	size = FBA_SIZE(header.ii_copyfba - header.ii_shdfba);
	if ((shd_bitmap = malloc(size)) == NULL) {
		perror("malloc");
		exit(1);
	}

	if (fseek(bmpfp, FBA_SIZE(header.ii_shdfba), SEEK_SET)) {
		perror("fseek");
		exit(1);
	}

	if (fread(shd_bitmap, 1, size, bmpfp) != size) {
		(void) fprintf(stderr,
		    gettext("Can't read imported bitmap volume\n"));
		exit(1);
	}

	(void) fclose(bmpfp);

	(void) strncpy(parms.shadow_vol, shadow_volume, DSW_NAMELEN);
	parms.shadow_vol[DSW_NAMELEN-1] = '\0';
	parms.shd_bitmap = shd_bitmap;
	parms.shd_size = size;
	parms.copy_bitmap = NULL;
	parms.copy_size = 0;

	spcs_log("ii", NULL, gettext("Join %s %s"),
	    parms.shadow_vol, bitmap_file);
	parms.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_JOIN, &parms);
	if (rc == -1) {
		spcs_log("ii", NULL, gettext("Join failed %s %s"),
		    parms.shadow_vol, bitmap_file);
		dsw_error(gettext("Join failed"), &parms.status);
	}
	if (perform_autosv()) {
		rc = cfg_vol_enable(cfg, shadow_volume, cfg_cluster_tag, "ii");
		if (rc < 0) {
			dsw_error(gettext("SV-enable failed"), NULL);
		}
		(void) cfg_commit(cfg);
	}
	spcs_s_ufree(&parms.status);
}

int
params(char *shadow_volume)
{
	char *delay = param_delay;
	char *unit = param_unit;
	dsw_copyp_t parms;
	int rc = 0;
	int get = 0;
	int new_delay;
	int new_unit;

	(void) strncpy(parms.shadow_vol, shadow_volume, DSW_NAMELEN);
	parms.shadow_vol[DSW_NAMELEN-1] = '\0';
	if (delay == NULL || unit == NULL) {
		get = 1;
		parms.copy_delay = -1;
		parms.copy_unit = -1;
	} else {
		new_delay = parms.copy_delay = convert_int(delay);
		new_unit = parms.copy_unit = convert_int(unit);
	}

	parms.status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_COPYP, &parms);
	if (rc == -1) {
		(void) fprintf(stderr,
		    gettext("Parameter ranges are delay(%d - %d), "
		    "units(%d - %d)\n"), MIN_THROTTLE_DELAY, MAX_THROTTLE_DELAY,
		    MIN_THROTTLE_UNIT, MAX_THROTTLE_UNIT);
		dsw_error(gettext("Set Copy Parameters failed"), &parms.status);
	}
	if (!get)
		spcs_log("ii", NULL, gettext("Changed copy parameters %s from "
		    "%d %d to %d %d"), parms.shadow_vol, parms.copy_delay,
		    parms.copy_unit, new_delay, new_unit);
	else
		(void) printf(gettext("volume: %s\ncopy delay: %d\ncopy unit:"
		    " %d\n"), parms.shadow_vol, parms.copy_delay,
		    parms.copy_unit);
	spcs_s_ufree(&parms.status);
	return (0);
}

static void
do_attach(dsw_config_t *parms)
{
	dsw_config_t io;
	int rc;
	int check = 0;

	spcs_log("ii", NULL, gettext("Attach %s %s"),
		parms->shadow_vol, parms->bitmap_vol);
	parms->status = spcs_s_ucreate();
	rc = do_ioctl(dsw_fd, DSWIOC_OATTACH, parms);
	if (rc == -1) {
		check = 1;
		/* if overflow() fails, it calls dsw_error to exit */
		(void) overflow(parms->bitmap_vol);
	}
	spcs_s_ufree(&parms->status);
	if (check == 1) {
		if (!find_shadow_config(parms->shadow_vol, &io, NULL))
			dsw_error(
			    gettext("Volume is not in a Point-in-Time Copy "
			    "group"), NULL);
		(void) strncpy(io.bitmap_vol, parms->bitmap_vol, DSW_NAMELEN);
		io.bitmap_vol[DSW_NAMELEN-1] = '\0';
		io.status = spcs_s_ucreate();
		if (do_ioctl(dsw_fd, DSWIOC_OATTACH, &io) == -1) {
			spcs_log("ii", NULL, gettext("Attach failed %s %s"),
			    io.shadow_vol, parms->bitmap_vol);
			dsw_error(gettext("Attach failed"), &io.status);
		}
		spcs_s_ufree(&io.status);
	}
}

int
attach(char *shadow_volume)
{
	dsw_config_t parms;
	dsw_stat_t args;
	char	shd_dg[DSW_NAMELEN];
	char	ovr_dg[DSW_NAMELEN];

	switch (check_cluster()) {
	case II_CLUSTER:
	case II_CLUSTER_LCL:
		(void) check_resource_group(shadow_volume);
		if (cfg_cluster_tag) { /* check all volumes are in same dg */
			if (cfg_dgname(shadow_volume, shd_dg, DSW_NAMELEN)
			    == NULL)
				dsw_error(gettext("Shadow volume not in a"
				    " disk group"), NULL);
			if (cfg_dgname(overflow_file, ovr_dg, DSW_NAMELEN)
			    == NULL)
				dsw_error(gettext("Overflow volume not in a"
				    " disk group"), NULL);
			if (strcmp(ovr_dg, shd_dg) != 0)
				dsw_error(gettext("Overflow volume not in"
				    " same disk group as shadow set members"),
				    NULL);
		}
		break;
	case II_NOT_CLUSTER:
		/* do nothing */
		break;
	default:
		dsw_error(gettext(
		    "Unexpected return from check_cluster()"), NULL);
	}

	/* assure that the overflow_file is not an II volume */
	if (find_any_cf_line(overflow_file))
		dsw_error(gettext(
			"Overflow volume is already in a Point-in-Time Copy "
			"group"), NULL);

	/* use find_shadow_config() to find setnumber */
	if (!find_shadow_config(shadow_volume, &parms, NULL))
		dsw_error(gettext("Volume is not in a Point-in-Time Copy "
			"group"), NULL);

	/* can only attach an overflow volume to dependent, compact shadow */
	(void) strncpy(args.shadow_vol, shadow_volume, DSW_NAMELEN);
	args.shadow_vol[DSW_NAMELEN-1] = '\0';

	args.status = spcs_s_ucreate();
	if ((do_ioctl(dsw_fd, DSWIOC_STAT, &args) == -1) ||
	    !(args.stat & DSW_TREEMAP))
		dsw_error(gettext("Not a compact dependent shadow"), NULL);

	/* bitmap_vol is overloaded */
	(void) strncpy(parms.bitmap_vol, overflow_file, DSW_NAMELEN);
	parms.bitmap_vol[DSW_NAMELEN-1] = '\0';

	do_attach(&parms);

	/* add overflow to cfg line */
	(void) sprintf(key, "ii.set%d.overflow", setnumber);
	if (cfg_put_cstring(cfg, key, overflow_file,
		    strlen(overflow_file)) < 0) {
		perror("cfg_put_cstring");
	}
	(void) cfg_commit(cfg);
	return (0);
}

void
dsw_join(int argc, char *argv[])
{
	if (argc != 3)
		usage(gettext("Incorrect number of arguments"));

	join(argv[1], argv[2]);
	exit(0);
}

void
dsw_params(int argc, char *argv[])
{
	if (argc != 4 && argc != 2 && argc != 0)
		usage(gettext("Incorrect number of arguments"));

	if ((argc == 4) || (argc == 2)) {
		param_delay = argv[1];
		param_unit = argv[2];
		if (argc == 4) {
			argv[1] = argv[3];
			argv[2] = NULL;
		}
	}
	exit(dsw_group_or_single_op(2, argv, params));
}

/*ARGSUSED*/
void
dsw_attach(int argc, char *argv[])
{
	overflow_file = argv[1];
	argv[1] = argv[2];
	(void) dsw_group_or_single_op(2, argv, attach);
	exit(0);
}

/*ARGSUSED*/
void
dsw_olist(int argc, char *argv[])
{
	char	*sp, *overflow_list, **vol;
	int	count, i;
	ENTRY	item, *found;
	char	key[ CFG_MAX_KEY ], buf[ CFG_MAX_BUF ];

	overflow_list = get_overflow_list();

	/* count entries */
	count = 0;
	for (sp = overflow_list; *sp; sp += DSW_NAMELEN) {
		++count;
	}

	/* create hash (adding room for suspended overflow volumes) */
	if (hcreate(count + 1024) == 0) {
		dsw_error(gettext("Out of memory creating lookup table"), NULL);
		/*NOTREACHED*/
	}

	if (count > 0) {
		/* create memory to store copy of list */
		vol = (char **)calloc(count, sizeof (char *));
		if (!vol) {
			dsw_error(
			    gettext("Out of memory creating lookup table"),
			    NULL);
			/*NOTREACHED*/
		}

		/* fill hash */
		for (i = 0, sp = overflow_list; *sp; sp += DSW_NAMELEN, i++) {

			/* make copy of string */
			vol[ i ] = (char *)malloc(DSW_NAMELEN + 1);
			(void) strncpy(vol[ i ], sp, DSW_NAMELEN);
			vol[ i ][ DSW_NAMELEN ] = '\0';

			item.key = vol[ i ];
			item.data = (char *)0;
			(void) hsearch(item, ENTER);
		}
	}

	/* loop through config file entries */
	i = 0;
	cfg_rewind(cfg, CFG_SEC_CONF);

	/*CONSTCOND*/
	while (1) {
		++i;
		(void) snprintf(key, CFG_MAX_KEY, "ii.set%d.overflow", i);
		if (cfg_get_cstring(cfg, key, buf, CFG_MAX_BUF) < 0) {
			break;
		}

		/* has this set got an overflow volume? */
		if (!*buf) {
			continue;
		}

		/* look up overflow in hash */
		item.key = buf;
		if (count > 0 && (found = hsearch(item, FIND)) != NULL) {
			if (0 == (int)found->data) {
				(void) printf("%s\n", buf);
				found->data = (char *)1;
				(void) hsearch(*found, ENTER);
			}
		} else {
			/* must be part of a suspended set */
			(void) printf("%s (attached to suspended set)\n", buf);
			item.key = buf;
			item.data = (char *)1;
			(void) hsearch(item, ENTER);
		}
	}

	exit(0);
}

void
dsw_ostat(int argc, char *argv[])
{
	dsw_ostat_t	args;
	int	stat_flags;

	if (argc != 2)
		usage(gettext("Incorrect number of arguments"));

	(void) strncpy(args.overflow_vol, argv[1], DSW_NAMELEN);
	args.overflow_vol[DSW_NAMELEN-1] = '\0';

	args.status = spcs_s_ucreate();
	if (do_ioctl(dsw_fd, DSWIOC_OSTAT2, &args) == -1)
		dsw_error(gettext("Stat failed"), &args.status);
	spcs_s_ufree(&args.status);

	if ((args.hversion >= 1) && (args.hmagic == II_OMAGIC)) {
		stat_flags = args.flags;
		if (stat_flags & IIO_CNTR_INVLD)
			(void) printf(gettext("Clean shutdown of volume "
			"sets associated with overflow volume "
			"did not occur.\n"
			"Overflow counters will be inconsistent "
			"until new point-in-time(s) are taken.\n"));
	}
	(void) printf(gettext("Total number of attached shadows: %d\n"),
	    args.drefcnt);
	(void) printf(gettext("Number of currently attached shadows: %d\n"),
	    args.crefcnt);
	(void) printf(gettext("Total number of chunks: %lld\n"), args.nchunks);
	(void) printf(gettext("Number of chunks ever allocated: %lld\n"),
	    args.used);
	(void) printf(gettext("Number of used chunks: %lld\n"),
		(args.nchunks - args.unused));
	(void) printf(gettext("Number of unused chunks: %lld\n"), args.unused);
	exit(0);
}

/*ARGSUSED*/
void
dsw_move_2_group(int argc, char *argv[])
{
	dsw_config_t parms;
	dsw_movegrp_t movegrp;
	grptag_t *gdata;
	int waserr = 0;

	/* handle move to NULL group, or group of all spaces or tabs */
	(void) strncpy(movegrp.new_group, group_name, DSW_NAMELEN);
	if ((strlen(group_name) == 0) || (strcspn(group_name, " \t") == 0)) {
		group_name = "-";
		bzero(movegrp.new_group, DSW_NAMELEN);
		gdata = NULL;
	} else {
		/* get the ctag for this group (if any) */
		gdata = (grptag_t *)nsc_lookup(volhash, group_name);
	}

	movegrp.status = spcs_s_ucreate();

	for (++argv; *argv; argv++) {
		if (!find_shadow_config(*argv, &parms, NULL))
			dsw_error(gettext("Volume is not in a Point-in-Time "
					"Copy group"), NULL);

		/* ensure the ctag matches the group */
		if (gdata && *gdata->ctag) {
			if (strncmp(parms.cluster_tag, gdata->ctag,
			    DSW_NAMELEN) != 0) {
				(void) fprintf(stderr, "%s: %s %s %s\n", cmdnam,
				    gettext("unable to move set"), *argv,
				    gettext("into new group - cluster "
				    "resource mismatch"));
				waserr = 1;
				continue;
			}
		}

		/* move the set in the kernel */
		(void) strncpy(movegrp.shadow_vol, parms.shadow_vol,
		    DSW_NAMELEN);
		if (do_ioctl(dsw_fd, DSWIOC_MOVEGRP, &movegrp) < 0)
			dsw_error(gettext("Failed to move group in kernel"),
			    NULL);

		/* now update the config */
		(void) sprintf(key, "ii.set%d.group", setnumber);
		if (cfg_put_cstring(cfg, key, group_name,
		    strlen(group_name)) < 0) {
			perror("cfg_put_cstring");
		}
		(void) cfg_commit(cfg);
	}
	spcs_s_ufree(&movegrp.status);
	cfg_close(cfg);
	exit(waserr);
}

void
dsw_list_groups()
{
	FILE *pfp;

	if ((pfp = popen("/usr/bin/sort -u", "w")) == NULL) {
		dsw_error(gettext("Can't open sort program"), NULL);
	}

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

		/* skip if shadow set is not in any group */
		if (strcmp(buf, "") == 0)
			continue;
		(void) fprintf(pfp, "%s\n", buf);
	}
	(void) pclose(pfp);
}

void
dsw_list_group_volumes()
{
	FILE *pfp;

	if (find_group_members(group_name) < 1)
		dsw_error(gettext("Group does not exist or has no members"),
			NULL);

	if ((pfp = popen("/usr/bin/sort -u", "w")) == NULL) {
		dsw_error(gettext("Can't open sort program"), NULL);
	}

	(void) fflush(stdout);
	for (; *group_volumes; group_volumes++)
		(void) fprintf(pfp, "%s\n", *group_volumes);
	(void) pclose(pfp);
}

static void
load_ii_vols(CFGFILE *cfg)
{
	int set, entries;
	char *mst, *shd, *buf, **entry;
	char *ctag, *group;
	mstcount_t *mdata;
	shdvol_t *sdata;
	grptag_t *gdata;
	static int whinged = 0;

	if (volhash) {
		return;
	}

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

		/* grab master volume name */
		mst = strtok(buf, " ");
		if (!mst) {
			free(buf);
			break;
		}

		/* grab shadow, group & cnode fields */
		shd = strtok(NULL, " ");
		(void) strtok(NULL, " ");	/* bitmap */
		(void) strtok(NULL, " ");	/* mode */
		(void) strtok(NULL, " ");	/* overflow */
		ctag = strtok(NULL, " ");	/* cnode */
		(void) strtok(NULL, " ");	/* options */
		group = strtok(NULL, " ");	/* group */

		/* Fix optional tags */
		if (ctag)
			ctag += strspn(ctag, "-");
		if (group)
			group += strspn(group, "-");

		/* If cluster tags don't match, skip record */
		if ((cfg_cluster_tag && strcmp(ctag, cfg_cluster_tag)) ||
		    (!cfg_cluster_tag && strlen(ctag))) {
			free(buf);
			continue;
		}

		/* master volume, may be duplicates */
		mdata = (mstcount_t *)nsc_lookup(volhash, mst);
		if (mdata) {
			++mdata->count;
		} else {
			mdata = (mstcount_t *)malloc(sizeof (mstcount_t));
			mdata->count = 1;
			(void) nsc_insert_node(volhash, mdata, mst);
		}

		/* grab shadow volume name */
		sdata = (shdvol_t *)malloc(sizeof (shdvol_t));
		(void) strncpy(sdata->master, mst, DSW_NAMELEN);
		(void) nsc_insert_node(volhash, sdata, shd);

		/* No need to continue if no groups or ctags */
		if (!group || !*group || !ctag || !*ctag) {
			free(buf);
			continue;
		}

		gdata = (grptag_t *)nsc_lookup(volhash, group);
		if (gdata) {
			/* group already exists - check ctag */
			if (*ctag &&
			    (strncmp(ctag, gdata->ctag, DSW_NAMELEN) != 0)) {
				if (!whinged) {
					(void) printf(gettext(
					    "Warning: multiple "
					    "cluster resource groups "
					    "defined within a single "
					    "I/O group\n"));
					whinged = 1;
				}
			}
		} else {
			gdata = (grptag_t *)malloc(sizeof (grptag_t));
			(void) strncpy(gdata->ctag, ctag, DSW_NAMELEN);
			(void) nsc_insert_node(volhash, gdata, group);
		}

		free(buf);
	}

	/* free up any leftovers */
	while (set < entries)
		free(entry[set++]);
	if (entries)
		free(entry);
}

static void
unload_ii_vols()
{
	nsc_remove_all(volhash, free);
	volhash = 0;
}

static int
perform_autosv()
{
	static int result;
	static int calculated = 0;
	int rc;

#ifdef DEBUG
	if (getenv("II_SET_CLUSTER"))
		return (1);
#endif

	if (calculated) {
		return (result);
	}

	/*
	 * we only perform auto-sv if we're in a sun cluster or if
	 * we're on a standalone system.  I.e. we don't do auto-sv on Harry
	 */
	rc = check_cluster();

	if (II_NOT_CLUSTER == rc) {
		result = 1;
	} else {
		result = cfg_issuncluster();
	}

	calculated = 1;
	return (result);
}

/*
 * Returns true if set has had the shadow volume exported.
 * Returns false if shadow volume is not exported, or set is suspended.
 */
static int
is_exported(char *set)
{
	dsw_stat_t args;
	int rc;

	(void) strncpy(args.shadow_vol, set, DSW_NAMELEN);
	args.shadow_vol[DSW_NAMELEN-1] = '\0';
	args.status = spcs_s_ucreate();

	rc = do_ioctl(dsw_fd, DSWIOC_STAT, &args);
	spcs_s_ufree(&args.status);

	if (-1 == rc) {
		/* set must be suspended, or being disabled */
		return (0);
	}

	return ((args.stat & DSW_SHDEXPORT) == DSW_SHDEXPORT);
}

static void
conform_name(char **path)
{
	char *cfgname;
	int rc = cfg_get_canonical_name(cfg, *path, &cfgname);

	if (rc < 0) {
		dsw_error(gettext("Unable to parse config file"), NULL);
	}
	if (rc) {
		(void) printf("  '%s'\n%s\n  '%s'\n", *path,
		    gettext("is currently configured as"), cfgname);
		check_action(gettext("Perform operation with indicated volume"
		    " name?"));
		*path = cfgname;
		/*
		 * NOTE: *path ought to be deallocated ('free(*path)') after
		 * we're done with it, but since this routine is called just
		 * before we exit, it doesn't really matter
		 */
	}
}

/*
 * verify_groupname(char *, int);
 *
 * Check the group name for the following rules:
 *	1. The name does not start with a '-'
 *	2. The name does not contain any space characters as defined by
 *	   isspace(3C).
 * If either of these rules are broken, error immediately. The check for a
 * leading dash can be skipped if the 'testDash' argument is false. This is to
 * allow for the '-g -L' functionality.
 *
 */
static void
verify_groupname(char *grp, int testDash)
{
	int i;

	if (testDash && grp[0] == '-') {
		errno = EINVAL;
		dsw_error(gettext("group name cannot start with a '-'"), NULL);
	}

	for (i = 0; grp[i] != '\0'; i++) {
		if (isspace(grp[i])) {
			errno = EINVAL;
			dsw_error(gettext("group name cannot contain a space"),
			    NULL);
		}
	}
}

void
check_iishadow(char *shadow_vol) {
	int i;
	int entries;
	char **entry;
	char *shost;
	char *svol;
	char *buf;
	void *librdc;

	/*
	 * See if librdc is around
	 * If not, we can just return
	 */
	if (librdc = dlopen(RDC_LIB, RTLD_LAZY | RTLD_GLOBAL))
		self_check = (int (*)(char *)) dlsym(librdc, "self_check");
	else {
		return;
	}

	entry = NULL;
	entries = cfg_get_section(cfg, &entry, "sndr");
	for (i = 0; i < entries; i++) {
		buf = entry[i];

		(void) strtok(buf, " ");	/* phost */
		(void) strtok(NULL, " ");	/* primary */
		(void) strtok(NULL, " ");	/* pbitmap */
		shost = strtok(NULL, " ");	/* shost */
		svol = strtok(NULL, " ");	/* secondary */

		if (self_check(shost) && (strcmp(shadow_vol, svol) == 0)) {
			free(buf);
			if (entries)
				free(entry);
			errno = EINVAL;
			dsw_error(gettext(
			    "shadow volume is in use as SNDR secondary volume"),
			    NULL);
		}
		free(buf);
	}

	(void) dlclose(librdc);
	if (entries)
		free(entry);
}