/*
 *  Copyright (c) 2004 Lukas Ertl
 *  Copyright (c) 2005 Chris Jones
 *  Copyright (c) 2007 Ulf Lilleengen
 *  All rights reserved.
 *
 * Portions of this software were developed for the FreeBSD Project
 * by Chris Jones thanks to the support of Google's Summer of Code
 * program and mentoring by Lukas Ertl.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: release/9.1.0/sbin/gvinum/gvinum.c 233640 2012-03-29 02:45:28Z eadler $
 */

#include <sys/param.h>
#include <sys/linker.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/utsname.h>

#include <geom/vinum/geom_vinum_var.h>
#include <geom/vinum/geom_vinum_share.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <libgeom.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>

#include "gvinum.h"

void	gvinum_attach(int, char **);
void	gvinum_concat(int, char **);
void	gvinum_create(int, char **);
void	gvinum_detach(int, char **);
void	gvinum_grow(int, char **);
void	gvinum_help(void);
void	gvinum_list(int, char **);
void	gvinum_move(int, char **);
void	gvinum_mirror(int, char **);
void	gvinum_parityop(int, char **, int);
void	gvinum_printconfig(int, char **);
void	gvinum_raid5(int, char **);
void	gvinum_rename(int, char **);
void	gvinum_resetconfig(void);
void	gvinum_rm(int, char **);
void	gvinum_saveconfig(void);
void	gvinum_setstate(int, char **);
void	gvinum_start(int, char **);
void	gvinum_stop(int, char **);
void	gvinum_stripe(int, char **);
void	parseline(int, char **);
void	printconfig(FILE *, char *);

char	*create_drive(char *);
void	 create_volume(int, char **, char *);
char	*find_name(const char *, int, int);
char	*find_pattern(char *, char *);
void	 copy_device(struct gv_drive *, const char *);
#define find_drive() find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)

int
main(int argc, char **argv)
{
	int line, tokens;
	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];

	/* Load the module if necessary. */
	if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
		err(1, GVINUMMOD ": Kernel module not available");

	/* Arguments given on the command line. */
	if (argc > 1) {
		argc--;
		argv++;
		parseline(argc, argv);

	/* Interactive mode. */
	} else {
		for (;;) {
			inputline = readline("gvinum -> ");
			if (inputline == NULL) {
				if (ferror(stdin)) {
					err(1, "can't read input");
				} else {
					printf("\n");
					exit(0);
				}
			} else if (*inputline) {
				add_history(inputline);
				strcpy(buffer, inputline);
				free(inputline);
				line++;		    /* count the lines */
				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
				if (tokens)
					parseline(tokens, token);
			}
		}
	}
	exit(0);
}

/* Attach a plex to a volume or a subdisk to a plex. */
void
gvinum_attach(int argc, char **argv)
{
	struct gctl_req *req;
	const char *errstr;
	int rename;
	off_t offset;

	rename = 0;
	offset = -1;
	if (argc < 3) {
		warnx("usage:\tattach <subdisk> <plex> [rename] "
		    "[<plexoffset>]\n"
		    "\tattach <plex> <volume> [rename]");
		return;
	}
	if (argc > 3) {
		if (!strcmp(argv[3], "rename")) {
			rename = 1;
			if (argc == 5)
				offset = strtol(argv[4], NULL, 0);
		} else
			offset = strtol(argv[3], NULL, 0);
	}
	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "attach");
	gctl_ro_param(req, "child", -1, argv[1]);
	gctl_ro_param(req, "parent", -1, argv[2]);
	gctl_ro_param(req, "offset", sizeof(off_t), &offset);
	gctl_ro_param(req, "rename", sizeof(int), &rename);
	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("attach failed: %s", errstr);
	gctl_free(req);
}

void
gvinum_create(int argc, char **argv)
{
	struct gctl_req *req;
	struct gv_drive *d;
	struct gv_plex *p;
	struct gv_sd *s;
	struct gv_volume *v;
	FILE *tmp;
	int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
	int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
	const char *errstr;
	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];

	tmp = NULL;
	flags = 0;
	for (i = 1; i < argc; i++) {
		/* Force flag used to ignore already created drives. */
		if (!strcmp(argv[i], "-f")) {
			flags |= GV_FLAG_F;
		/* Else it must be a file. */
		} else {
			if ((tmp = fopen(argv[1], "r")) == NULL) {
				warn("can't open '%s' for reading", argv[1]);
				return;
			}
		}	
	}

	/* We didn't get a file. */
	if (tmp == NULL) {
		snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
		
		if ((fd = mkstemp(tmpfile)) == -1) {
			warn("temporary file not accessible");
			return;
		}
		if ((tmp = fdopen(fd, "w")) == NULL) {
			warn("can't open '%s' for writing", tmpfile);
			return;
		}
		printconfig(tmp, "# ");
		fclose(tmp);
		
		ed = getenv("EDITOR");
		if (ed == NULL)
			ed = _PATH_VI;
		
		snprintf(commandline, sizeof(commandline), "%s %s", ed,
		    tmpfile);
		status = system(commandline);
		if (status != 0) {
			warn("couldn't exec %s; status: %d", ed, status);
			return;
		}
		
		if ((tmp = fopen(tmpfile, "r")) == NULL) {
			warn("can't open '%s' for reading", tmpfile);
			return;
		}
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "create");
	gctl_ro_param(req, "flags", sizeof(int), &flags);

	drives = volumes = plexes = subdisks = 0;
	plex_in_volume = sd_in_plex = undeffd = 0;
	plex[0] = '\0';
	errors = 0;
	line = 1;
	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {

		/* Skip empty lines and comments. */
		if (*buf == '\0' || *buf == '#') {
			line++;
			continue;
		}

		/* Kill off the newline. */
		buf[strlen(buf) - 1] = '\0';

		/*
		 * Copy the original input line in case we need it for error
		 * output.
		 */
		strlcpy(original, buf, sizeof(original));

		tokens = gv_tokenize(buf, token, GV_MAXARGS);
		if (tokens <= 0) {
			line++;
			continue;
		}

		/* Volume definition. */
		if (!strcmp(token[0], "volume")) {
			v = gv_new_volume(tokens, token);
			if (v == NULL) {
				warnx("line %d: invalid volume definition",
				    line);
				warnx("line %d: '%s'", line, original);
				errors++;
				line++;
				continue;
			}

			/* Reset plex count for this volume. */
			plex_in_volume = 0;

			/*
			 * Set default volume name for following plex
			 * definitions.
			 */
			strlcpy(volume, v->name, sizeof(volume));

			snprintf(buf1, sizeof(buf1), "volume%d", volumes);
			gctl_ro_param(req, buf1, sizeof(*v), v);
			volumes++;

		/* Plex definition. */
		} else if (!strcmp(token[0], "plex")) {
			p = gv_new_plex(tokens, token);
			if (p == NULL) {
				warnx("line %d: invalid plex definition", line);
				warnx("line %d: '%s'", line, original);
				errors++;
				line++;
				continue;
			}

			/* Reset subdisk count for this plex. */
			sd_in_plex = 0;

			/* Default name. */
			if (strlen(p->name) == 0) {
				snprintf(p->name, sizeof(p->name), "%s.p%d",
				    volume, plex_in_volume++);
			}

			/* Default volume. */
			if (strlen(p->volume) == 0) {
				snprintf(p->volume, sizeof(p->volume), "%s",
				    volume);
			}

			/*
			 * Set default plex name for following subdisk
			 * definitions.
			 */
			strlcpy(plex, p->name, sizeof(plex));

			snprintf(buf1, sizeof(buf1), "plex%d", plexes);
			gctl_ro_param(req, buf1, sizeof(*p), p);
			plexes++;

		/* Subdisk definition. */
		} else if (!strcmp(token[0], "sd")) {
			s = gv_new_sd(tokens, token);
			if (s == NULL) {
				warnx("line %d: invalid subdisk "
				    "definition:", line);
				warnx("line %d: '%s'", line, original);
				errors++;
				line++;
				continue;
			}

			/* Default name. */
			if (strlen(s->name) == 0) {
				if (strlen(plex) == 0) {
					sdname = find_name("gvinumsubdisk.p",
					    GV_TYPE_SD, GV_MAXSDNAME);
					snprintf(s->name, sizeof(s->name),
					    "%s.s%d", sdname, undeffd++);
					free(sdname);
				} else {
					snprintf(s->name, sizeof(s->name),
					    "%s.s%d",plex, sd_in_plex++);
				}
			}

			/* Default plex. */
			if (strlen(s->plex) == 0)
				snprintf(s->plex, sizeof(s->plex), "%s", plex);

			snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
			gctl_ro_param(req, buf1, sizeof(*s), s);
			subdisks++;

		/* Subdisk definition. */
		} else if (!strcmp(token[0], "drive")) {
			d = gv_new_drive(tokens, token);
			if (d == NULL) {
				warnx("line %d: invalid drive definition:",
				    line);
				warnx("line %d: '%s'", line, original);
				errors++;
				line++;
				continue;
			}

			snprintf(buf1, sizeof(buf1), "drive%d", drives);
			gctl_ro_param(req, buf1, sizeof(*d), d);
			drives++;

		/* Everything else is bogus. */
		} else {
			warnx("line %d: invalid definition:", line);
			warnx("line %d: '%s'", line, original);
			errors++;
		}
		line++;
	}

	fclose(tmp);
	unlink(tmpfile);

	if (!errors && (volumes || plexes || subdisks || drives)) {
		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
		gctl_ro_param(req, "drives", sizeof(int), &drives);
		errstr = gctl_issue(req);
		if (errstr != NULL)
			warnx("create failed: %s", errstr);
	}
	gctl_free(req);
}

/* Create a concatenated volume. */
void
gvinum_concat(int argc, char **argv)
{

	if (argc < 2) {
		warnx("usage:\tconcat [-fv] [-n name] drives\n");
		return;
	}
	create_volume(argc, argv, "concat");
}


/* Create a drive quick and dirty. */
char *
create_drive(char *device)
{
	struct gv_drive *d;
	struct gctl_req *req;
	const char *errstr;
	char *drivename, *dname;
	int drives, i, flags, volumes, subdisks, plexes;

	flags = plexes = subdisks = volumes = 0;
	drives = 1;
	dname = NULL;

	drivename = find_drive();
	if (drivename == NULL)
		return (NULL);

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "create");
	d = gv_alloc_drive();
	if (d == NULL)
		err(1, "unable to allocate for gv_drive object");

	strlcpy(d->name, drivename, sizeof(d->name));
	copy_device(d, device);
	gctl_ro_param(req, "drive0", sizeof(*d), d);
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_ro_param(req, "drives", sizeof(int), &drives);
	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("error creating drive: %s", errstr);
		gctl_free(req);
		return (NULL);
	} else {
		gctl_free(req);
		/* XXX: This is needed because we have to make sure the drives
		 * are created before we return. */
		/* Loop until it's in the config. */
		for (i = 0; i < 100000; i++) {
			dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
			    GV_MAXDRIVENAME);
			/* If we got a different name, quit. */
			if (dname == NULL)
				continue;
			if (strcmp(dname, drivename)) {
				free(dname);
				return (drivename);
			}
			free(dname);
			dname = NULL;
			usleep(100000); /* Sleep for 0.1s */
		}
	}
	gctl_free(req);
	return (drivename);
}

/* 
 * General routine for creating a volume. Mainly for use by concat, mirror,
 * raid5 and stripe commands.
 */
void
create_volume(int argc, char **argv, char *verb)
{
	struct gctl_req *req;
	const char *errstr;
	char buf[BUFSIZ], *drivename, *volname;
	int drives, flags, i;
	off_t stripesize;

	flags = 0;
	drives = 0;
	volname = NULL;
	stripesize = 262144;

	/* XXX: Should we check for argument length? */

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");

	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-f")) {
			flags |= GV_FLAG_F;
		} else if (!strcmp(argv[i], "-n")) {
			volname = argv[++i];
		} else if (!strcmp(argv[i], "-v")) {
			flags |= GV_FLAG_V;
		} else if (!strcmp(argv[i], "-s")) {
			flags |= GV_FLAG_S;
			if (!strcmp(verb, "raid5"))
				stripesize = gv_sizespec(argv[++i]);
		} else {
			/* Assume it's a drive. */
			snprintf(buf, sizeof(buf), "drive%d", drives++);

			/* First we create the drive. */
			drivename = create_drive(argv[i]); 
			if (drivename == NULL)
				goto bad;
			/* Then we add it to the request. */
			gctl_ro_param(req, buf, -1, drivename);
		}
	}

	gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);

	/* Find a free volume name. */
	if (volname == NULL)
		volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);

	/* Then we send a request to actually create the volumes. */
	gctl_ro_param(req, "verb", -1, verb);
	gctl_ro_param(req, "flags", sizeof(int), &flags); 
	gctl_ro_param(req, "drives", sizeof(int), &drives);
	gctl_ro_param(req, "name", -1, volname);
	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("creating %s volume failed: %s", verb, errstr);
bad:
	gctl_free(req);
}

/* Parse a line of the config, return the word after <pattern>. */
char *
find_pattern(char *line, char *pattern)
{
	char *ptr;

	ptr = strsep(&line, " ");
	while (ptr != NULL) {
		if (!strcmp(ptr, pattern)) {
			/* Return the next. */
			ptr = strsep(&line, " ");
			return (ptr);
		}
		ptr = strsep(&line, " ");
	}
	return (NULL);
}

/* Find a free name for an object given a prefix. */
char *
find_name(const char *prefix, int type, int namelen)
{
	struct gctl_req *req;
	char comment[1], buf[GV_CFG_LEN - 1], *name, *sname, *ptr;
	const char *errstr;
	int i, n, begin, len, conflict;
	char line[1024];

	comment[0] = '\0';

	/* Find a name. Fetch out configuration first. */
	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "getconfig");
	gctl_ro_param(req, "comment", -1, comment);
	gctl_rw_param(req, "config", sizeof(buf), buf);
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't get configuration: %s", errstr);
		return (NULL);
	}
	gctl_free(req);

	begin = 0;
	len = strlen(buf);
	i = 0;
	sname = malloc(namelen + 1);

	/* XXX: Max object setting? */
	for (n = 0; n < 10000; n++) {
		snprintf(sname, namelen, "%s%d", prefix, n);
		conflict = 0;
		begin = 0;
		/* Loop through the configuration line by line. */
		for (i = 0; i < len; i++) {
			if (buf[i] == '\n' || buf[i] == '\0') {
				ptr = buf + begin;
				strlcpy(line, ptr, (i - begin) + 1);
				begin = i + 1;
				switch (type) {
				case GV_TYPE_DRIVE:
					name = find_pattern(line, "drive");
					break;
				case GV_TYPE_VOL:
					name = find_pattern(line, "volume");
					break;
				case GV_TYPE_PLEX:
				case GV_TYPE_SD:
					name = find_pattern(line, "name");
					break;
				default:
					printf("Invalid type given\n");
					continue;
				}
				if (name == NULL)
					continue;
				if (!strcmp(sname, name)) {
					conflict = 1;
					/* XXX: Could quit the loop earlier. */
				}
			}
		}
		if (!conflict)
			return (sname);
	}
	free(sname);
	return (NULL);
}

void
copy_device(struct gv_drive *d, const char *device)
{
	if (strncmp(device, "/dev/", 5) == 0)
		strlcpy(d->device, (device + 5), sizeof(d->device));
	else
		strlcpy(d->device, device, sizeof(d->device));
}

/* Detach a plex or subdisk from its parent. */
void
gvinum_detach(int argc, char **argv)
{
	const char *errstr;
	struct gctl_req *req;
	int flags, i;

	flags = 0;
	optreset = 1;
	optind = 1;
	while ((i = getopt(argc, argv, "f")) != -1) {
		switch(i) {
		case 'f':
			flags |= GV_FLAG_F;
			break;
		default:
			warn("invalid flag: %c", i);
			return;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 1) {
		warnx("usage: detach [-f] <subdisk> | <plex>");
		return;
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "detach");
	gctl_ro_param(req, "object", -1, argv[0]);
	gctl_ro_param(req, "flags", sizeof(int), &flags);

	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("detach failed: %s", errstr);
	gctl_free(req);
}

void
gvinum_help(void)
{
	printf("COMMANDS\n"
	    "checkparity [-f] plex\n"
	    "        Check the parity blocks of a RAID-5 plex.\n"
	    "create [-f] description-file\n"
	    "        Create as per description-file or open editor.\n"
	    "attach plex volume [rename]\n"
	    "attach subdisk plex [offset] [rename]\n"
	    "        Attach a plex to a volume, or a subdisk to a plex\n"
	    "concat [-fv] [-n name] drives\n"
	    "        Create a concatenated volume from the specified drives.\n"
	    "detach [-f] [plex | subdisk]\n"
	    "        Detach a plex or a subdisk from the volume or plex to\n"
	    "        which it is attached.\n"
	    "grow plex drive\n"
	    "        Grow plex by creating a properly sized subdisk on drive\n"
	    "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
	    "        List information about specified objects.\n"
	    "ld [-r] [-v] [-V] [volume]\n"
	    "        List information about drives.\n"
	    "ls [-r] [-v] [-V] [subdisk]\n"
	    "        List information about subdisks.\n"
	    "lp [-r] [-v] [-V] [plex]\n"
	    "        List information about plexes.\n"
	    "lv [-r] [-v] [-V] [volume]\n"
	    "        List information about volumes.\n"
	    "mirror [-fsv] [-n name] drives\n"
	    "        Create a mirrored volume from the specified drives.\n"
	    "move | mv -f drive object ...\n"
	    "        Move the object(s) to the specified drive.\n"
	    "quit    Exit the vinum program when running in interactive mode."
	    "  Nor-\n"
	    "        mally this would be done by entering the EOF character.\n"
	    "raid5 [-fv] [-s stripesize] [-n name] drives\n"
	    "        Create a RAID-5 volume from the specified drives.\n"
	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
	    "        Change the name of the specified object.\n"
	    "rebuildparity plex [-f]\n"
	    "        Rebuild the parity blocks of a RAID-5 plex.\n"
	    "resetconfig\n"
	    "        Reset the complete gvinum configuration\n"
	    "rm [-r] [-f] volume | plex | subdisk | drive\n"
	    "        Remove an object.\n"
	    "saveconfig\n"
	    "        Save vinum configuration to disk after configuration"
	    " failures.\n"
	    "setstate [-f] state [volume | plex | subdisk | drive]\n"
	    "        Set state without influencing other objects, for"
	    " diagnostic pur-\n"
	    "        poses only.\n"
	    "start [-S size] volume | plex | subdisk\n"
	    "        Allow the system to access the objects.\n"
	    "stripe [-fv] [-n name] drives\n"
	    "        Create a striped volume from the specified drives.\n"
	);

	return;
}

void
gvinum_setstate(int argc, char **argv)
{
	struct gctl_req *req;
	int flags, i;
	const char *errstr;

	flags = 0;

	optreset = 1;
	optind = 1;

	while ((i = getopt(argc, argv, "f")) != -1) {
		switch (i) {
		case 'f':
			flags |= GV_FLAG_F;
			break;
		case '?':
		default:
			warn("invalid flag: %c", i);
			return;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc != 2) {
		warnx("usage: setstate [-f] <state> <obj>");
		return;
	}

	/*
	 * XXX: This hack is needed to avoid tripping over (now) invalid
	 * 'classic' vinum states and will go away later.
	 */
	if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
	    strcmp(argv[0], "stale")) {
		warnx("invalid state '%s'", argv[0]);
		return;
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "setstate");
	gctl_ro_param(req, "state", -1, argv[0]);
	gctl_ro_param(req, "object", -1, argv[1]);
	gctl_ro_param(req, "flags", sizeof(int), &flags);

	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("%s", errstr);
	gctl_free(req);
}

void
gvinum_list(int argc, char **argv)
{
	struct gctl_req *req;
	int flags, i, j;
	const char *errstr;
	char buf[20], *cmd, config[GV_CFG_LEN + 1];

	flags = 0;
	cmd = "list";

	if (argc) {
		optreset = 1;
		optind = 1;
		cmd = argv[0];
		while ((j = getopt(argc, argv, "rsvV")) != -1) {
			switch (j) {
			case 'r':
				flags |= GV_FLAG_R;
				break;
			case 's':
				flags |= GV_FLAG_S;
				break;
			case 'v':
				flags |= GV_FLAG_V;
				break;
			case 'V':
				flags |= GV_FLAG_V;
				flags |= GV_FLAG_VV;
				break;
			case '?':
			default:
				return;
			}
		}
		argc -= optind;
		argv += optind;

	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "list");
	gctl_ro_param(req, "cmd", -1, cmd);
	gctl_ro_param(req, "argc", sizeof(int), &argc);
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_rw_param(req, "config", sizeof(config), config);
	if (argc) {
		for (i = 0; i < argc; i++) {
			snprintf(buf, sizeof(buf), "argv%d", i);
			gctl_ro_param(req, buf, -1, argv[i]);
		}
	}
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't get configuration: %s", errstr);
		gctl_free(req);
		return;
	}

	printf("%s", config);
	gctl_free(req);
	return;
}

/* Create a mirrored volume. */
void
gvinum_mirror(int argc, char **argv)
{

	if (argc < 2) {
		warnx("usage\tmirror [-fsv] [-n name] drives\n");
		return;
	}
	create_volume(argc, argv, "mirror");
}

/* Note that move is currently of form '[-r] target object [...]' */
void
gvinum_move(int argc, char **argv)
{
	struct gctl_req *req;
	const char *errstr;
	char buf[20];
	int flags, i, j;

	flags = 0;
	if (argc) {
		optreset = 1;
		optind = 1;
		while ((j = getopt(argc, argv, "f")) != -1) {
			switch (j) {
			case 'f':
				flags |= GV_FLAG_F;
				break;
			case '?':
			default:
				return;
			}
		}
		argc -= optind;
		argv += optind;
	}

	switch (argc) {
		case 0:
			warnx("no destination or object(s) to move specified");
			return;
		case 1:
			warnx("no object(s) to move specified");
			return;
		default:
			break;
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "move");
	gctl_ro_param(req, "argc", sizeof(int), &argc);
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_ro_param(req, "destination", -1, argv[0]);
	for (i = 1; i < argc; i++) {
		snprintf(buf, sizeof(buf), "argv%d", i);
		gctl_ro_param(req, buf, -1, argv[i]);
	}
	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("can't move object(s):  %s", errstr);
	gctl_free(req);
	return;
}

void
gvinum_printconfig(int argc, char **argv)
{
	printconfig(stdout, "");
}

void
gvinum_parityop(int argc, char **argv, int rebuild)
{
	struct gctl_req *req;
	int flags, i;
	const char *errstr;
	char *op;

	if (rebuild) {
		op = "rebuildparity";
	} else {
		op = "checkparity";
	}

	optreset = 1;
	optind = 1;
	flags = 0;
	while ((i = getopt(argc, argv, "fv")) != -1) {
		switch (i) {
		case 'f':
			flags |= GV_FLAG_F;
			break;
		case 'v':
			flags |= GV_FLAG_V;
			break;
		case '?':
		default:
			warnx("invalid flag '%c'", i);
			return;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc != 1) {
		warn("usage: %s [-f] [-v] <plex>", op);
		return;
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, op);
	gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_ro_param(req, "plex", -1, argv[0]);

	errstr = gctl_issue(req);
	if (errstr)
		warnx("%s\n", errstr);
	gctl_free(req);
}

/* Create a RAID-5 volume. */
void
gvinum_raid5(int argc, char **argv)
{

	if (argc < 2) {
		warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
		return;
	}
	create_volume(argc, argv, "raid5");
}


void
gvinum_rename(int argc, char **argv)
{
	struct gctl_req *req;
	const char *errstr;
	int flags, j;

	flags = 0;

	if (argc) {
		optreset = 1;
		optind = 1;
		while ((j = getopt(argc, argv, "r")) != -1) {
			switch (j) {
			case 'r':
				flags |= GV_FLAG_R;
				break;
			case '?':
			default:
				return;
			}
		}
		argc -= optind;
		argv += optind;
	}

	switch (argc) {
		case 0:
			warnx("no object to rename specified");
			return;
		case 1:
			warnx("no new name specified");
			return;
		case 2:
			break;
		default:
			warnx("more than one new name specified");
			return;
	}

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "rename");
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_ro_param(req, "object", -1, argv[0]);
	gctl_ro_param(req, "newname", -1, argv[1]);
	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("can't rename object:  %s", errstr);
	gctl_free(req);
	return;
}

void
gvinum_rm(int argc, char **argv)
{
	struct gctl_req *req;
	int flags, i, j;
	const char *errstr;
	char buf[20];

	flags = 0;
	optreset = 1;
	optind = 1;
	while ((j = getopt(argc, argv, "rf")) != -1) {
		switch (j) {
		case 'f':
			flags |= GV_FLAG_F;
			break;
		case 'r':
			flags |= GV_FLAG_R;
			break;
		case '?':
		default:
			return;
		}
	}
	argc -= optind;
	argv += optind;

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "remove");
	gctl_ro_param(req, "argc", sizeof(int), &argc);
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	if (argc) {
		for (i = 0; i < argc; i++) {
			snprintf(buf, sizeof(buf), "argv%d", i);
			gctl_ro_param(req, buf, -1, argv[i]);
		}
	}
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't remove: %s", errstr);
		gctl_free(req);
		return;
	}
	gctl_free(req);
}

void
gvinum_resetconfig(void)
{
	struct gctl_req *req;
	const char *errstr;
	char reply[32];

	if (!isatty(STDIN_FILENO)) {
		warn("Please enter this command from a tty device\n");
		return;
	}
	printf(" WARNING!  This command will completely wipe out your gvinum"
	    "configuration.\n"
	    " All data will be lost.  If you really want to do this,"
	    " enter the text\n\n"
	    " NO FUTURE\n"
	    " Enter text -> ");
	fgets(reply, sizeof(reply), stdin);
	if (strcmp(reply, "NO FUTURE\n")) {
		printf("\n No change\n");
		return;
	}
	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "resetconfig");
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't reset config: %s", errstr);
		gctl_free(req);
		return;
	}
	gctl_free(req);
	printf("gvinum configuration obliterated\n");
}

void
gvinum_saveconfig(void)
{
	struct gctl_req *req;
	const char *errstr;

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "saveconfig");
	errstr = gctl_issue(req);
	if (errstr != NULL)
		warnx("can't save configuration: %s", errstr);
	gctl_free(req);
}

void
gvinum_start(int argc, char **argv)
{
	struct gctl_req *req;
	int i, initsize, j;
	const char *errstr;
	char buf[20];

	/* 'start' with no arguments is a no-op. */
	if (argc == 1)
		return;

	initsize = 0;

	optreset = 1;
	optind = 1;
	while ((j = getopt(argc, argv, "S")) != -1) {
		switch (j) {
		case 'S':
			initsize = atoi(optarg);
			break;
		case '?':
		default:
			return;
		}
	}
	argc -= optind;
	argv += optind;

	if (!initsize)
		initsize = 512;

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "start");
	gctl_ro_param(req, "argc", sizeof(int), &argc);
	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
	if (argc) {
		for (i = 0; i < argc; i++) {
			snprintf(buf, sizeof(buf), "argv%d", i);
			gctl_ro_param(req, buf, -1, argv[i]);
		}
	}
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't start: %s", errstr);
		gctl_free(req);
		return;
	}

	gctl_free(req);
}

void
gvinum_stop(int argc, char **argv)
{
	int err, fileid;

	fileid = kldfind(GVINUMMOD);
	if (fileid == -1) {
		warn("cannot find " GVINUMMOD);
		return;
	}

	/*
	 * This little hack prevents that we end up in an infinite loop in
	 * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
	 * event thread will be free for the g_wither_geom() call from
	 * gv_unload().  It's silly, but it works.
	 */
	printf("unloading " GVINUMMOD " kernel module... ");
	fflush(stdout);
	if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
		sleep(1);
		err = kldunload(fileid);
	}
	if (err != 0) {
		printf(" failed!\n");
		warn("cannot unload " GVINUMMOD);
		return;
	}

	printf("done\n");
	exit(0);
}

/* Create a striped volume. */
void
gvinum_stripe(int argc, char **argv)
{

	if (argc < 2) {
		warnx("usage:\tstripe [-fv] [-n name] drives\n");
		return;
	}
	create_volume(argc, argv, "stripe");
}

/* Grow a subdisk by adding disk backed by provider. */
void
gvinum_grow(int argc, char **argv)
{
	struct gctl_req *req;
	char *drive, *sdname;
	char sdprefix[GV_MAXSDNAME];
	struct gv_drive *d;
	struct gv_sd *s;
	const char *errstr;
	int drives, volumes, plexes, subdisks, flags;

	drives = volumes = plexes = subdisks = 0;
	if (argc < 3) {
		warnx("usage:\tgrow plex drive\n");
		return;
	}

	s = gv_alloc_sd();
	if (s == NULL) {
		warn("unable to create subdisk");
		return;
	}
	d = gv_alloc_drive();
	if (d == NULL) {
		warn("unable to create drive");
		free(s);
		return;
	}
	/* Lookup device and set an appropriate drive name. */
	drive = find_drive();
	if (drive == NULL) {
		warn("unable to find an appropriate drive name");
		free(s);
		free(d);
		return;
	}
	strlcpy(d->name, drive, sizeof(d->name));
	copy_device(d, argv[2]);

	drives = 1;

	/* We try to use the plex name as basis for the subdisk name. */
	snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
	sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
	if (sdname == NULL) {
		warn("unable to find an appropriate subdisk name");
		free(s);
		free(d);
		free(drive);
		return;
	}
	strlcpy(s->name, sdname, sizeof(s->name));
	free(sdname);
	strlcpy(s->plex, argv[1], sizeof(s->plex));
	strlcpy(s->drive, d->name, sizeof(s->drive));
	subdisks = 1;

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "create");
	gctl_ro_param(req, "flags", sizeof(int), &flags);
	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
	gctl_ro_param(req, "drives", sizeof(int), &drives);
	gctl_ro_param(req, "drive0", sizeof(*d), d);
	gctl_ro_param(req, "sd0", sizeof(*s), s);
	errstr = gctl_issue(req);
	free(drive);
	if (errstr != NULL) {
		warnx("unable to grow plex: %s", errstr);
		free(s);
		free(d);
		return;
	}
	gctl_free(req);
}

void
parseline(int argc, char **argv)
{
	if (argc <= 0)
		return;

	if (!strcmp(argv[0], "create"))
		gvinum_create(argc, argv);
	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
		exit(0);
	else if (!strcmp(argv[0], "attach"))
		gvinum_attach(argc, argv);
	else if (!strcmp(argv[0], "detach"))
		gvinum_detach(argc, argv);
	else if (!strcmp(argv[0], "concat"))
		gvinum_concat(argc, argv);
	else if (!strcmp(argv[0], "grow"))
		gvinum_grow(argc, argv);
	else if (!strcmp(argv[0], "help"))
		gvinum_help();
	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
		gvinum_list(argc, argv);
	else if (!strcmp(argv[0], "ld"))
		gvinum_list(argc, argv);
	else if (!strcmp(argv[0], "lp"))
		gvinum_list(argc, argv);
	else if (!strcmp(argv[0], "ls"))
		gvinum_list(argc, argv);
	else if (!strcmp(argv[0], "lv"))
		gvinum_list(argc, argv);
	else if (!strcmp(argv[0], "mirror"))
		gvinum_mirror(argc, argv);
	else if (!strcmp(argv[0], "move"))
		gvinum_move(argc, argv);
	else if (!strcmp(argv[0], "mv"))
		gvinum_move(argc, argv);
	else if (!strcmp(argv[0], "printconfig"))
		gvinum_printconfig(argc, argv);
	else if (!strcmp(argv[0], "raid5"))
		gvinum_raid5(argc, argv);
	else if (!strcmp(argv[0], "rename"))
		gvinum_rename(argc, argv);
	else if (!strcmp(argv[0], "resetconfig"))
		gvinum_resetconfig();
	else if (!strcmp(argv[0], "rm"))
		gvinum_rm(argc, argv);
	else if (!strcmp(argv[0], "saveconfig"))
		gvinum_saveconfig();
	else if (!strcmp(argv[0], "setstate"))
		gvinum_setstate(argc, argv);
	else if (!strcmp(argv[0], "start"))
		gvinum_start(argc, argv);
	else if (!strcmp(argv[0], "stop"))
		gvinum_stop(argc, argv);
	else if (!strcmp(argv[0], "stripe"))
		gvinum_stripe(argc, argv);
	else if (!strcmp(argv[0], "checkparity"))
		gvinum_parityop(argc, argv, 0);
	else if (!strcmp(argv[0], "rebuildparity"))
		gvinum_parityop(argc, argv, 1);
	else
		printf("unknown command '%s'\n", argv[0]);

	return;
}

/*
 * The guts of printconfig.  This is called from gvinum_printconfig and from
 * gvinum_create when called without an argument, in order to give the user
 * something to edit.
 */
void
printconfig(FILE *of, char *comment)
{
	struct gctl_req *req;
	struct utsname uname_s;
	const char *errstr;
	time_t now;
	char buf[GV_CFG_LEN + 1];
	
	uname(&uname_s);
	time(&now);

	req = gctl_get_handle();
	gctl_ro_param(req, "class", -1, "VINUM");
	gctl_ro_param(req, "verb", -1, "getconfig");
	gctl_ro_param(req, "comment", -1, comment);
	gctl_rw_param(req, "config", sizeof(buf), buf);
	errstr = gctl_issue(req);
	if (errstr != NULL) {
		warnx("can't get configuration: %s", errstr);
		return;
	}
	gctl_free(req);

	fprintf(of, "# Vinum configuration of %s, saved at %s",
	    uname_s.nodename,
	    ctime(&now));
	
	if (*comment != '\0')
	    fprintf(of, "# Current configuration:\n");

	fprintf(of, "%s", buf);
}
