xref: /freebsd/sbin/gvinum/gvinum.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  *  Copyright (c) 2004 Lukas Ertl
5  *  Copyright (c) 2005 Chris Jones
6  *  Copyright (c) 2007 Ulf Lilleengen
7  *  All rights reserved.
8  *
9  * Portions of this software were developed for the FreeBSD Project
10  * by Chris Jones thanks to the support of Google's Summer of Code
11  * program and mentoring by Lukas Ertl.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $FreeBSD$
35  */
36 
37 #include <sys/param.h>
38 #include <sys/linker.h>
39 #include <sys/lock.h>
40 #include <sys/module.h>
41 #include <sys/mutex.h>
42 #include <sys/queue.h>
43 #include <sys/utsname.h>
44 
45 #include <geom/vinum/geom_vinum_var.h>
46 #include <geom/vinum/geom_vinum_share.h>
47 
48 #include <ctype.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <libgeom.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <paths.h>
57 #include <readline/readline.h>
58 #include <readline/history.h>
59 #include <unistd.h>
60 
61 #include "gvinum.h"
62 
63 static void gvinum_attach(int, char * const *);
64 static void gvinum_concat(int, char * const *);
65 static void gvinum_create(int, char * const *);
66 static void gvinum_detach(int, char * const *);
67 static void gvinum_grow(int, char * const *);
68 static void gvinum_help(void);
69 static void gvinum_list(int, char * const *);
70 static void gvinum_move(int, char * const *);
71 static void gvinum_mirror(int, char * const *);
72 static void gvinum_parityop(int, char * const * , int);
73 static void gvinum_printconfig(int, char * const *);
74 static void gvinum_raid5(int, char * const *);
75 static void gvinum_rename(int, char * const *);
76 static void gvinum_resetconfig(int, char * const *);
77 static void gvinum_rm(int, char * const *);
78 static void gvinum_saveconfig(void);
79 static void gvinum_setstate(int, char * const *);
80 static void gvinum_start(int, char * const *);
81 static void gvinum_stop(int, char * const *);
82 static void gvinum_stripe(int, char * const *);
83 static void parseline(int, char * const *);
84 static void printconfig(FILE *, const char *);
85 
86 static char *create_drive(const char *);
87 static void create_volume(int, char * const * , const char *);
88 static char *find_name(const char *, int, int);
89 static const char *find_pattern(char *, const char *);
90 static void copy_device(struct gv_drive *, const char *);
91 #define	find_drive()							\
92     find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
93 
94 int
95 main(int argc, char **argv)
96 {
97 	int tokens;
98 	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
99 
100 	/* Load the module if necessary. */
101 	if (modfind(GVINUMMOD) < 0) {
102 		if (kldload(GVINUMKLD) < 0 && modfind(GVINUMMOD) < 0)
103 			err(1, GVINUMKLD ": Kernel module not available");
104 	}
105 
106 	/* Arguments given on the command line. */
107 	if (argc > 1) {
108 		argc--;
109 		argv++;
110 		parseline(argc, argv);
111 
112 	/* Interactive mode. */
113 	} else {
114 		for (;;) {
115 			inputline = readline("gvinum -> ");
116 			if (inputline == NULL) {
117 				if (ferror(stdin)) {
118 					err(1, "can't read input");
119 				} else {
120 					printf("\n");
121 					exit(0);
122 				}
123 			} else if (*inputline) {
124 				add_history(inputline);
125 				strcpy(buffer, inputline);
126 				free(inputline);
127 				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
128 				if (tokens)
129 					parseline(tokens, token);
130 			}
131 		}
132 	}
133 	exit(0);
134 }
135 
136 /* Attach a plex to a volume or a subdisk to a plex. */
137 static void
138 gvinum_attach(int argc, char * const *argv)
139 {
140 	struct gctl_req *req;
141 	const char *errstr;
142 	int rename;
143 	off_t offset;
144 
145 	rename = 0;
146 	offset = -1;
147 	if (argc < 3) {
148 		warnx("usage:\tattach <subdisk> <plex> [rename] "
149 		    "[<plexoffset>]\n"
150 		    "\tattach <plex> <volume> [rename]");
151 		return;
152 	}
153 	if (argc > 3) {
154 		if (!strcmp(argv[3], "rename")) {
155 			rename = 1;
156 			if (argc == 5)
157 				offset = strtol(argv[4], NULL, 0);
158 		} else
159 			offset = strtol(argv[3], NULL, 0);
160 	}
161 	req = gctl_get_handle();
162 	gctl_ro_param(req, "class", -1, "VINUM");
163 	gctl_ro_param(req, "verb", -1, "attach");
164 	gctl_ro_param(req, "child", -1, argv[1]);
165 	gctl_ro_param(req, "parent", -1, argv[2]);
166 	gctl_ro_param(req, "offset", sizeof(off_t), &offset);
167 	gctl_ro_param(req, "rename", sizeof(int), &rename);
168 	errstr = gctl_issue(req);
169 	if (errstr != NULL)
170 		warnx("attach failed: %s", errstr);
171 	gctl_free(req);
172 }
173 
174 static void
175 gvinum_create(int argc, char * const *argv)
176 {
177 	struct gctl_req *req;
178 	struct gv_drive *d;
179 	struct gv_plex *p;
180 	struct gv_sd *s;
181 	struct gv_volume *v;
182 	FILE *tmp;
183 	int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
184 	int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
185 	const char *errstr;
186 	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *sdname;
187 	const char *ed;
188 	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
189 	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
190 
191 	tmp = NULL;
192 	flags = 0;
193 	for (i = 1; i < argc; i++) {
194 		/* Force flag used to ignore already created drives. */
195 		if (!strcmp(argv[i], "-f")) {
196 			flags |= GV_FLAG_F;
197 		/* Else it must be a file. */
198 		} else {
199 			if ((tmp = fopen(argv[i], "r")) == NULL) {
200 				warn("can't open '%s' for reading", argv[i]);
201 				return;
202 			}
203 		}
204 	}
205 
206 	/* We didn't get a file. */
207 	if (tmp == NULL) {
208 		snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
209 
210 		if ((fd = mkstemp(tmpfile)) == -1) {
211 			warn("temporary file not accessible");
212 			return;
213 		}
214 		if ((tmp = fdopen(fd, "w")) == NULL) {
215 			warn("can't open '%s' for writing", tmpfile);
216 			return;
217 		}
218 		printconfig(tmp, "# ");
219 		fclose(tmp);
220 
221 		ed = getenv("EDITOR");
222 		if (ed == NULL)
223 			ed = _PATH_VI;
224 
225 		snprintf(commandline, sizeof(commandline), "%s %s", ed,
226 		    tmpfile);
227 		status = system(commandline);
228 		if (status != 0) {
229 			warn("couldn't exec %s; status: %d", ed, status);
230 			return;
231 		}
232 
233 		if ((tmp = fopen(tmpfile, "r")) == NULL) {
234 			warn("can't open '%s' for reading", tmpfile);
235 			return;
236 		}
237 	}
238 
239 	req = gctl_get_handle();
240 	gctl_ro_param(req, "class", -1, "VINUM");
241 	gctl_ro_param(req, "verb", -1, "create");
242 	gctl_ro_param(req, "flags", sizeof(int), &flags);
243 
244 	drives = volumes = plexes = subdisks = 0;
245 	plex_in_volume = sd_in_plex = undeffd = 0;
246 	plex[0] = '\0';
247 	errors = 0;
248 	line = 1;
249 	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
250 
251 		/* Skip empty lines and comments. */
252 		if (*buf == '\0' || *buf == '#') {
253 			line++;
254 			continue;
255 		}
256 
257 		/* Kill off the newline. */
258 		buf[strlen(buf) - 1] = '\0';
259 
260 		/*
261 		 * Copy the original input line in case we need it for error
262 		 * output.
263 		 */
264 		strlcpy(original, buf, sizeof(original));
265 
266 		tokens = gv_tokenize(buf, token, GV_MAXARGS);
267 		if (tokens <= 0) {
268 			line++;
269 			continue;
270 		}
271 
272 		/* Volume definition. */
273 		if (!strcmp(token[0], "volume")) {
274 			v = gv_new_volume(tokens, token);
275 			if (v == NULL) {
276 				warnx("line %d: invalid volume definition",
277 				    line);
278 				warnx("line %d: '%s'", line, original);
279 				errors++;
280 				line++;
281 				continue;
282 			}
283 
284 			/* Reset plex count for this volume. */
285 			plex_in_volume = 0;
286 
287 			/*
288 			 * Set default volume name for following plex
289 			 * definitions.
290 			 */
291 			strlcpy(volume, v->name, sizeof(volume));
292 
293 			snprintf(buf1, sizeof(buf1), "volume%d", volumes);
294 			gctl_ro_param(req, buf1, sizeof(*v), v);
295 			volumes++;
296 
297 		/* Plex definition. */
298 		} else if (!strcmp(token[0], "plex")) {
299 			p = gv_new_plex(tokens, token);
300 			if (p == NULL) {
301 				warnx("line %d: invalid plex definition", line);
302 				warnx("line %d: '%s'", line, original);
303 				errors++;
304 				line++;
305 				continue;
306 			}
307 
308 			/* Reset subdisk count for this plex. */
309 			sd_in_plex = 0;
310 
311 			/* Default name. */
312 			if (strlen(p->name) == 0) {
313 				snprintf(p->name, sizeof(p->name), "%s.p%d",
314 				    volume, plex_in_volume++);
315 			}
316 
317 			/* Default volume. */
318 			if (strlen(p->volume) == 0) {
319 				snprintf(p->volume, sizeof(p->volume), "%s",
320 				    volume);
321 			}
322 
323 			/*
324 			 * Set default plex name for following subdisk
325 			 * definitions.
326 			 */
327 			strlcpy(plex, p->name, sizeof(plex));
328 
329 			snprintf(buf1, sizeof(buf1), "plex%d", plexes);
330 			gctl_ro_param(req, buf1, sizeof(*p), p);
331 			plexes++;
332 
333 		/* Subdisk definition. */
334 		} else if (!strcmp(token[0], "sd")) {
335 			s = gv_new_sd(tokens, token);
336 			if (s == NULL) {
337 				warnx("line %d: invalid subdisk "
338 				    "definition:", line);
339 				warnx("line %d: '%s'", line, original);
340 				errors++;
341 				line++;
342 				continue;
343 			}
344 
345 			/* Default name. */
346 			if (strlen(s->name) == 0) {
347 				if (strlen(plex) == 0) {
348 					sdname = find_name("gvinumsubdisk.p",
349 					    GV_TYPE_SD, GV_MAXSDNAME);
350 					snprintf(s->name, sizeof(s->name),
351 					    "%s.s%d", sdname, undeffd++);
352 					free(sdname);
353 				} else {
354 					snprintf(s->name, sizeof(s->name),
355 					    "%s.s%d",plex, sd_in_plex++);
356 				}
357 			}
358 
359 			/* Default plex. */
360 			if (strlen(s->plex) == 0)
361 				snprintf(s->plex, sizeof(s->plex), "%s", plex);
362 
363 			snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
364 			gctl_ro_param(req, buf1, sizeof(*s), s);
365 			subdisks++;
366 
367 		/* Subdisk definition. */
368 		} else if (!strcmp(token[0], "drive")) {
369 			d = gv_new_drive(tokens, token);
370 			if (d == NULL) {
371 				warnx("line %d: invalid drive definition:",
372 				    line);
373 				warnx("line %d: '%s'", line, original);
374 				errors++;
375 				line++;
376 				continue;
377 			}
378 
379 			snprintf(buf1, sizeof(buf1), "drive%d", drives);
380 			gctl_ro_param(req, buf1, sizeof(*d), d);
381 			drives++;
382 
383 		/* Everything else is bogus. */
384 		} else {
385 			warnx("line %d: invalid definition:", line);
386 			warnx("line %d: '%s'", line, original);
387 			errors++;
388 		}
389 		line++;
390 	}
391 
392 	fclose(tmp);
393 	unlink(tmpfile);
394 
395 	if (!errors && (volumes || plexes || subdisks || drives)) {
396 		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
397 		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
398 		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
399 		gctl_ro_param(req, "drives", sizeof(int), &drives);
400 		errstr = gctl_issue(req);
401 		if (errstr != NULL)
402 			warnx("create failed: %s", errstr);
403 	}
404 	gctl_free(req);
405 }
406 
407 /* Create a concatenated volume. */
408 static void
409 gvinum_concat(int argc, char * const *argv)
410 {
411 
412 	if (argc < 2) {
413 		warnx("usage:\tconcat [-fv] [-n name] drives\n");
414 		return;
415 	}
416 	create_volume(argc, argv, "concat");
417 }
418 
419 /* Create a drive quick and dirty. */
420 static char *
421 create_drive(const char *device)
422 {
423 	struct gv_drive *d;
424 	struct gctl_req *req;
425 	const char *errstr;
426 	char *drivename, *dname;
427 	int drives, i, flags, volumes, subdisks, plexes;
428 	int found = 0;
429 
430 	flags = plexes = subdisks = volumes = 0;
431 	drives = 1;
432 	dname = NULL;
433 
434 	drivename = find_drive();
435 	if (drivename == NULL)
436 		return (NULL);
437 
438 	req = gctl_get_handle();
439 	gctl_ro_param(req, "class", -1, "VINUM");
440 	gctl_ro_param(req, "verb", -1, "create");
441 	d = gv_alloc_drive();
442 	if (d == NULL)
443 		err(1, "unable to allocate for gv_drive object");
444 
445 	strlcpy(d->name, drivename, sizeof(d->name));
446 	copy_device(d, device);
447 	gctl_ro_param(req, "drive0", sizeof(*d), d);
448 	gctl_ro_param(req, "flags", sizeof(int), &flags);
449 	gctl_ro_param(req, "drives", sizeof(int), &drives);
450 	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
451 	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
452 	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
453 	errstr = gctl_issue(req);
454 	if (errstr != NULL) {
455 		warnx("error creating drive: %s", errstr);
456 		drivename = NULL;
457 	} else {
458 		/* XXX: This is needed because we have to make sure the drives
459 		 * are created before we return. */
460 		/* Loop until it's in the config. */
461 		for (i = 0; i < 100000; i++) {
462 			dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
463 			    GV_MAXDRIVENAME);
464 			/* If we got a different name, quit. */
465 			if (dname == NULL)
466 				continue;
467 			if (strcmp(dname, drivename))
468 				found = 1;
469 			free(dname);
470 			dname = NULL;
471 			if (found)
472 				break;
473 			usleep(100000); /* Sleep for 0.1s */
474 		}
475 		if (found == 0) {
476 			warnx("error creating drive");
477 			drivename = NULL;
478 		}
479 	}
480 	gctl_free(req);
481 	return (drivename);
482 }
483 
484 /*
485  * General routine for creating a volume. Mainly for use by concat, mirror,
486  * raid5 and stripe commands.
487  */
488 static void
489 create_volume(int argc, char * const *argv, const char *verb)
490 {
491 	struct gctl_req *req;
492 	const char *errstr;
493 	char buf[BUFSIZ], *drivename, *volname;
494 	int drives, flags, i;
495 	off_t stripesize;
496 
497 	flags = 0;
498 	drives = 0;
499 	volname = NULL;
500 	stripesize = 262144;
501 
502 	/* XXX: Should we check for argument length? */
503 
504 	req = gctl_get_handle();
505 	gctl_ro_param(req, "class", -1, "VINUM");
506 
507 	for (i = 1; i < argc; i++) {
508 		if (!strcmp(argv[i], "-f")) {
509 			flags |= GV_FLAG_F;
510 		} else if (!strcmp(argv[i], "-n")) {
511 			volname = argv[++i];
512 		} else if (!strcmp(argv[i], "-v")) {
513 			flags |= GV_FLAG_V;
514 		} else if (!strcmp(argv[i], "-s")) {
515 			flags |= GV_FLAG_S;
516 			if (!strcmp(verb, "raid5"))
517 				stripesize = gv_sizespec(argv[++i]);
518 		} else {
519 			/* Assume it's a drive. */
520 			snprintf(buf, sizeof(buf), "drive%d", drives++);
521 
522 			/* First we create the drive. */
523 			drivename = create_drive(argv[i]);
524 			if (drivename == NULL)
525 				goto bad;
526 			/* Then we add it to the request. */
527 			gctl_ro_param(req, buf, -1, drivename);
528 		}
529 	}
530 
531 	gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
532 
533 	/* Find a free volume name. */
534 	if (volname == NULL)
535 		volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
536 
537 	/* Then we send a request to actually create the volumes. */
538 	gctl_ro_param(req, "verb", -1, verb);
539 	gctl_ro_param(req, "flags", sizeof(int), &flags);
540 	gctl_ro_param(req, "drives", sizeof(int), &drives);
541 	gctl_ro_param(req, "name", -1, volname);
542 	errstr = gctl_issue(req);
543 	if (errstr != NULL)
544 		warnx("creating %s volume failed: %s", verb, errstr);
545 bad:
546 	gctl_free(req);
547 }
548 
549 /* Parse a line of the config, return the word after <pattern>. */
550 static const char *
551 find_pattern(char *line, const char *pattern)
552 {
553 	char *ptr;
554 
555 	ptr = strsep(&line, " ");
556 	while (ptr != NULL) {
557 		if (!strcmp(ptr, pattern)) {
558 			/* Return the next. */
559 			ptr = strsep(&line, " ");
560 			return (ptr);
561 		}
562 		ptr = strsep(&line, " ");
563 	}
564 	return (NULL);
565 }
566 
567 /* Find a free name for an object given a prefix. */
568 static char *
569 find_name(const char *prefix, int type, int namelen)
570 {
571 	struct gctl_req *req;
572 	char comment[1], buf[GV_CFG_LEN - 1], *sname, *ptr;
573 	const char *errstr, *name;
574 	int i, n, begin, len, conflict;
575 	char line[1024];
576 
577 	comment[0] = '\0';
578 	buf[0] = '\0';
579 
580 	/* Find a name. Fetch out configuration first. */
581 	req = gctl_get_handle();
582 	gctl_ro_param(req, "class", -1, "VINUM");
583 	gctl_ro_param(req, "verb", -1, "getconfig");
584 	gctl_ro_param(req, "comment", -1, comment);
585 	gctl_add_param(req, "config", sizeof(buf), buf,
586 	    GCTL_PARAM_WR | GCTL_PARAM_ASCII);
587 	errstr = gctl_issue(req);
588 	if (errstr != NULL) {
589 		warnx("can't get configuration: %s", errstr);
590 		return (NULL);
591 	}
592 	gctl_free(req);
593 
594 	begin = 0;
595 	len = strlen(buf);
596 	i = 0;
597 	sname = malloc(namelen + 1);
598 
599 	/* XXX: Max object setting? */
600 	for (n = 0; n < 10000; n++) {
601 		snprintf(sname, namelen, "%s%d", prefix, n);
602 		conflict = 0;
603 		begin = 0;
604 		/* Loop through the configuration line by line. */
605 		for (i = 0; i < len; i++) {
606 			if (buf[i] == '\n' || buf[i] == '\0') {
607 				ptr = buf + begin;
608 				strlcpy(line, ptr, (i - begin) + 1);
609 				begin = i + 1;
610 				switch (type) {
611 				case GV_TYPE_DRIVE:
612 					name = find_pattern(line, "drive");
613 					break;
614 				case GV_TYPE_VOL:
615 					name = find_pattern(line, "volume");
616 					break;
617 				case GV_TYPE_PLEX:
618 				case GV_TYPE_SD:
619 					name = find_pattern(line, "name");
620 					break;
621 				default:
622 					printf("Invalid type given\n");
623 					continue;
624 				}
625 				if (name == NULL)
626 					continue;
627 				if (!strcmp(sname, name)) {
628 					conflict = 1;
629 					/* XXX: Could quit the loop earlier. */
630 				}
631 			}
632 		}
633 		if (!conflict)
634 			return (sname);
635 	}
636 	free(sname);
637 	return (NULL);
638 }
639 
640 static void
641 copy_device(struct gv_drive *d, const char *device)
642 {
643 
644 	if (strncmp(device, "/dev/", 5) == 0)
645 		strlcpy(d->device, (device + 5), sizeof(d->device));
646 	else
647 		strlcpy(d->device, device, sizeof(d->device));
648 }
649 
650 /* Detach a plex or subdisk from its parent. */
651 static void
652 gvinum_detach(int argc, char * const *argv)
653 {
654 	const char *errstr;
655 	struct gctl_req *req;
656 	int flags, i;
657 
658 	flags = 0;
659 	optreset = 1;
660 	optind = 1;
661 	while ((i = getopt(argc, argv, "f")) != -1) {
662 		switch (i) {
663 		case 'f':
664 			flags |= GV_FLAG_F;
665 			break;
666 		default:
667 			warn("invalid flag: %c", i);
668 			return;
669 		}
670 	}
671 	argc -= optind;
672 	argv += optind;
673 	if (argc != 1) {
674 		warnx("usage: detach [-f] <subdisk> | <plex>");
675 		return;
676 	}
677 
678 	req = gctl_get_handle();
679 	gctl_ro_param(req, "class", -1, "VINUM");
680 	gctl_ro_param(req, "verb", -1, "detach");
681 	gctl_ro_param(req, "object", -1, argv[0]);
682 	gctl_ro_param(req, "flags", sizeof(int), &flags);
683 
684 	errstr = gctl_issue(req);
685 	if (errstr != NULL)
686 		warnx("detach failed: %s", errstr);
687 	gctl_free(req);
688 }
689 
690 static void
691 gvinum_help(void)
692 {
693 
694 	printf("COMMANDS\n"
695 	    "checkparity [-f] plex\n"
696 	    "        Check the parity blocks of a RAID-5 plex.\n"
697 	    "create [-f] description-file\n"
698 	    "        Create as per description-file or open editor.\n"
699 	    "attach plex volume [rename]\n"
700 	    "attach subdisk plex [offset] [rename]\n"
701 	    "        Attach a plex to a volume, or a subdisk to a plex\n"
702 	    "concat [-fv] [-n name] drives\n"
703 	    "        Create a concatenated volume from the specified drives.\n"
704 	    "detach [-f] [plex | subdisk]\n"
705 	    "        Detach a plex or a subdisk from the volume or plex to\n"
706 	    "        which it is attached.\n"
707 	    "grow plex drive\n"
708 	    "        Grow plex by creating a properly sized subdisk on drive\n"
709 	    "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
710 	    "        List information about specified objects.\n"
711 	    "ld [-r] [-v] [-V] [volume]\n"
712 	    "        List information about drives.\n"
713 	    "ls [-r] [-v] [-V] [subdisk]\n"
714 	    "        List information about subdisks.\n"
715 	    "lp [-r] [-v] [-V] [plex]\n"
716 	    "        List information about plexes.\n"
717 	    "lv [-r] [-v] [-V] [volume]\n"
718 	    "        List information about volumes.\n"
719 	    "mirror [-fsv] [-n name] drives\n"
720 	    "        Create a mirrored volume from the specified drives.\n"
721 	    "move | mv -f drive object ...\n"
722 	    "        Move the object(s) to the specified drive.\n"
723 	    "quit    Exit the vinum program when running in interactive mode."
724 	    "  Nor-\n"
725 	    "        mally this would be done by entering the EOF character.\n"
726 	    "raid5 [-fv] [-s stripesize] [-n name] drives\n"
727 	    "        Create a RAID-5 volume from the specified drives.\n"
728 	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
729 	    "        Change the name of the specified object.\n"
730 	    "rebuildparity plex [-f]\n"
731 	    "        Rebuild the parity blocks of a RAID-5 plex.\n"
732 	    "resetconfig [-f]\n"
733 	    "        Reset the complete gvinum configuration\n"
734 	    "rm [-r] [-f] volume | plex | subdisk | drive\n"
735 	    "        Remove an object.\n"
736 	    "saveconfig\n"
737 	    "        Save vinum configuration to disk after configuration"
738 	    " failures.\n"
739 	    "setstate [-f] state [volume | plex | subdisk | drive]\n"
740 	    "        Set state without influencing other objects, for"
741 	    " diagnostic pur-\n"
742 	    "        poses only.\n"
743 	    "start [-S size] volume | plex | subdisk\n"
744 	    "        Allow the system to access the objects.\n"
745 	    "stripe [-fv] [-n name] drives\n"
746 	    "        Create a striped volume from the specified drives.\n"
747 	);
748 }
749 
750 static void
751 gvinum_setstate(int argc, char * const *argv)
752 {
753 	struct gctl_req *req;
754 	int flags, i;
755 	const char *errstr;
756 
757 	flags = 0;
758 
759 	optreset = 1;
760 	optind = 1;
761 
762 	while ((i = getopt(argc, argv, "f")) != -1) {
763 		switch (i) {
764 		case 'f':
765 			flags |= GV_FLAG_F;
766 			break;
767 		case '?':
768 		default:
769 			warn("invalid flag: %c", i);
770 			return;
771 		}
772 	}
773 
774 	argc -= optind;
775 	argv += optind;
776 
777 	if (argc != 2) {
778 		warnx("usage: setstate [-f] <state> <obj>");
779 		return;
780 	}
781 
782 	/*
783 	 * XXX: This hack is needed to avoid tripping over (now) invalid
784 	 * 'classic' vinum states and will go away later.
785 	 */
786 	if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
787 	    strcmp(argv[0], "stale")) {
788 		warnx("invalid state '%s'", argv[0]);
789 		return;
790 	}
791 
792 	req = gctl_get_handle();
793 	gctl_ro_param(req, "class", -1, "VINUM");
794 	gctl_ro_param(req, "verb", -1, "setstate");
795 	gctl_ro_param(req, "state", -1, argv[0]);
796 	gctl_ro_param(req, "object", -1, argv[1]);
797 	gctl_ro_param(req, "flags", sizeof(int), &flags);
798 
799 	errstr = gctl_issue(req);
800 	if (errstr != NULL)
801 		warnx("%s", errstr);
802 	gctl_free(req);
803 }
804 
805 static void
806 gvinum_list(int argc, char * const *argv)
807 {
808 	struct gctl_req *req;
809 	int flags, i, j;
810 	const char *errstr;
811 	char buf[20], config[GV_CFG_LEN + 1];
812 	const char *cmd;
813 
814 	flags = 0;
815 	cmd = "list";
816 
817 	if (argc) {
818 		optreset = 1;
819 		optind = 1;
820 		cmd = argv[0];
821 		while ((j = getopt(argc, argv, "rsvV")) != -1) {
822 			switch (j) {
823 			case 'r':
824 				flags |= GV_FLAG_R;
825 				break;
826 			case 's':
827 				flags |= GV_FLAG_S;
828 				break;
829 			case 'v':
830 				flags |= GV_FLAG_V;
831 				break;
832 			case 'V':
833 				flags |= GV_FLAG_V;
834 				flags |= GV_FLAG_VV;
835 				break;
836 			case '?':
837 			default:
838 				return;
839 			}
840 		}
841 		argc -= optind;
842 		argv += optind;
843 
844 	}
845 
846 	config[0] = '\0';
847 
848 	req = gctl_get_handle();
849 	gctl_ro_param(req, "class", -1, "VINUM");
850 	gctl_ro_param(req, "verb", -1, "list");
851 	gctl_ro_param(req, "cmd", -1, cmd);
852 	gctl_ro_param(req, "argc", sizeof(int), &argc);
853 	gctl_ro_param(req, "flags", sizeof(int), &flags);
854 	gctl_add_param(req, "config", sizeof(config), config,
855 	    GCTL_PARAM_WR | GCTL_PARAM_ASCII);
856 	if (argc) {
857 		for (i = 0; i < argc; i++) {
858 			snprintf(buf, sizeof(buf), "argv%d", i);
859 			gctl_ro_param(req, buf, -1, argv[i]);
860 		}
861 	}
862 	errstr = gctl_issue(req);
863 	if (errstr != NULL) {
864 		warnx("can't get configuration: %s", errstr);
865 		gctl_free(req);
866 		return;
867 	}
868 
869 	printf("%s", config);
870 	gctl_free(req);
871 }
872 
873 /* Create a mirrored volume. */
874 static void
875 gvinum_mirror(int argc, char * const *argv)
876 {
877 
878 	if (argc < 2) {
879 		warnx("usage\tmirror [-fsv] [-n name] drives\n");
880 		return;
881 	}
882 	create_volume(argc, argv, "mirror");
883 }
884 
885 /* Note that move is currently of form '[-r] target object [...]' */
886 static void
887 gvinum_move(int argc, char * const *argv)
888 {
889 	struct gctl_req *req;
890 	const char *errstr;
891 	char buf[20];
892 	int flags, i, j;
893 
894 	flags = 0;
895 	if (argc) {
896 		optreset = 1;
897 		optind = 1;
898 		while ((j = getopt(argc, argv, "f")) != -1) {
899 			switch (j) {
900 			case 'f':
901 				flags |= GV_FLAG_F;
902 				break;
903 			case '?':
904 			default:
905 				return;
906 			}
907 		}
908 		argc -= optind;
909 		argv += optind;
910 	}
911 
912 	switch (argc) {
913 		case 0:
914 			warnx("no destination or object(s) to move specified");
915 			return;
916 		case 1:
917 			warnx("no object(s) to move specified");
918 			return;
919 		default:
920 			break;
921 	}
922 
923 	req = gctl_get_handle();
924 	gctl_ro_param(req, "class", -1, "VINUM");
925 	gctl_ro_param(req, "verb", -1, "move");
926 	gctl_ro_param(req, "argc", sizeof(int), &argc);
927 	gctl_ro_param(req, "flags", sizeof(int), &flags);
928 	gctl_ro_param(req, "destination", -1, argv[0]);
929 	for (i = 1; i < argc; i++) {
930 		snprintf(buf, sizeof(buf), "argv%d", i);
931 		gctl_ro_param(req, buf, -1, argv[i]);
932 	}
933 	errstr = gctl_issue(req);
934 	if (errstr != NULL)
935 		warnx("can't move object(s):  %s", errstr);
936 	gctl_free(req);
937 }
938 
939 static void
940 gvinum_printconfig(int argc __unused, char * const *argv __unused)
941 {
942 
943 	printconfig(stdout, "");
944 }
945 
946 static void
947 gvinum_parityop(int argc, char * const *argv, int rebuild)
948 {
949 	struct gctl_req *req;
950 	int flags, i;
951 	const char *errstr;
952 	const char *op;
953 
954 	if (rebuild) {
955 		op = "rebuildparity";
956 	} else {
957 		op = "checkparity";
958 	}
959 
960 	optreset = 1;
961 	optind = 1;
962 	flags = 0;
963 	while ((i = getopt(argc, argv, "fv")) != -1) {
964 		switch (i) {
965 		case 'f':
966 			flags |= GV_FLAG_F;
967 			break;
968 		case 'v':
969 			flags |= GV_FLAG_V;
970 			break;
971 		default:
972 			warnx("invalid flag '%c'", i);
973 			return;
974 		}
975 	}
976 	argc -= optind;
977 	argv += optind;
978 
979 	if (argc != 1) {
980 		warn("usage: %s [-f] [-v] <plex>", op);
981 		return;
982 	}
983 
984 	req = gctl_get_handle();
985 	gctl_ro_param(req, "class", -1, "VINUM");
986 	gctl_ro_param(req, "verb", -1, op);
987 	gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
988 	gctl_ro_param(req, "flags", sizeof(int), &flags);
989 	gctl_ro_param(req, "plex", -1, argv[0]);
990 
991 	errstr = gctl_issue(req);
992 	if (errstr)
993 		warnx("%s\n", errstr);
994 	gctl_free(req);
995 }
996 
997 /* Create a RAID-5 volume. */
998 static void
999 gvinum_raid5(int argc, char * const *argv)
1000 {
1001 
1002 	if (argc < 2) {
1003 		warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
1004 		return;
1005 	}
1006 	create_volume(argc, argv, "raid5");
1007 }
1008 
1009 static void
1010 gvinum_rename(int argc, char * const *argv)
1011 {
1012 	struct gctl_req *req;
1013 	const char *errstr;
1014 	int flags, j;
1015 
1016 	flags = 0;
1017 
1018 	if (argc) {
1019 		optreset = 1;
1020 		optind = 1;
1021 		while ((j = getopt(argc, argv, "r")) != -1) {
1022 			switch (j) {
1023 			case 'r':
1024 				flags |= GV_FLAG_R;
1025 				break;
1026 			default:
1027 				return;
1028 			}
1029 		}
1030 		argc -= optind;
1031 		argv += optind;
1032 	}
1033 
1034 	switch (argc) {
1035 		case 0:
1036 			warnx("no object to rename specified");
1037 			return;
1038 		case 1:
1039 			warnx("no new name specified");
1040 			return;
1041 		case 2:
1042 			break;
1043 		default:
1044 			warnx("more than one new name specified");
1045 			return;
1046 	}
1047 
1048 	req = gctl_get_handle();
1049 	gctl_ro_param(req, "class", -1, "VINUM");
1050 	gctl_ro_param(req, "verb", -1, "rename");
1051 	gctl_ro_param(req, "flags", sizeof(int), &flags);
1052 	gctl_ro_param(req, "object", -1, argv[0]);
1053 	gctl_ro_param(req, "newname", -1, argv[1]);
1054 	errstr = gctl_issue(req);
1055 	if (errstr != NULL)
1056 		warnx("can't rename object:  %s", errstr);
1057 	gctl_free(req);
1058 }
1059 
1060 static void
1061 gvinum_rm(int argc, char * const *argv)
1062 {
1063 	struct gctl_req *req;
1064 	int flags, i, j;
1065 	const char *errstr;
1066 	char buf[20];
1067 
1068 	flags = 0;
1069 	optreset = 1;
1070 	optind = 1;
1071 	while ((j = getopt(argc, argv, "rf")) != -1) {
1072 		switch (j) {
1073 		case 'f':
1074 			flags |= GV_FLAG_F;
1075 			break;
1076 		case 'r':
1077 			flags |= GV_FLAG_R;
1078 			break;
1079 		default:
1080 			return;
1081 		}
1082 	}
1083 	argc -= optind;
1084 	argv += optind;
1085 
1086 	req = gctl_get_handle();
1087 	gctl_ro_param(req, "class", -1, "VINUM");
1088 	gctl_ro_param(req, "verb", -1, "remove");
1089 	gctl_ro_param(req, "argc", sizeof(int), &argc);
1090 	gctl_ro_param(req, "flags", sizeof(int), &flags);
1091 	if (argc) {
1092 		for (i = 0; i < argc; i++) {
1093 			snprintf(buf, sizeof(buf), "argv%d", i);
1094 			gctl_ro_param(req, buf, -1, argv[i]);
1095 		}
1096 	}
1097 	errstr = gctl_issue(req);
1098 	if (errstr != NULL) {
1099 		warnx("can't remove: %s", errstr);
1100 		gctl_free(req);
1101 		return;
1102 	}
1103 	gctl_free(req);
1104 }
1105 
1106 static void
1107 gvinum_resetconfig(int argc, char * const *argv)
1108 {
1109 	struct gctl_req *req;
1110 	const char *errstr;
1111 	char reply[32];
1112 	int flags, i;
1113 
1114 	flags = 0;
1115 	while ((i = getopt(argc, argv, "f")) != -1) {
1116 		switch (i) {
1117 		case 'f':
1118 			flags |= GV_FLAG_F;
1119 			break;
1120 		default:
1121 			warn("invalid flag: %c", i);
1122 			return;
1123 		}
1124 	}
1125 	if ((flags & GV_FLAG_F) == 0) {
1126 		if (!isatty(STDIN_FILENO)) {
1127 			warn("Please enter this command from a tty device\n");
1128 			return;
1129 		}
1130 		printf(" WARNING!  This command will completely wipe out"
1131 		    " your gvinum configuration.\n"
1132 		    " All data will be lost.  If you really want to do this,"
1133 		    " enter the text\n\n"
1134 		    " NO FUTURE\n"
1135 		    " Enter text -> ");
1136 		fgets(reply, sizeof(reply), stdin);
1137 		if (strcmp(reply, "NO FUTURE\n")) {
1138 			printf("\n No change\n");
1139 			return;
1140 		}
1141 	}
1142 	req = gctl_get_handle();
1143 	gctl_ro_param(req, "class", -1, "VINUM");
1144 	gctl_ro_param(req, "verb", -1, "resetconfig");
1145 	errstr = gctl_issue(req);
1146 	if (errstr != NULL) {
1147 		warnx("can't reset config: %s", errstr);
1148 		gctl_free(req);
1149 		return;
1150 	}
1151 	gctl_free(req);
1152 	printf("gvinum configuration obliterated\n");
1153 }
1154 
1155 static void
1156 gvinum_saveconfig(void)
1157 {
1158 	struct gctl_req *req;
1159 	const char *errstr;
1160 
1161 	req = gctl_get_handle();
1162 	gctl_ro_param(req, "class", -1, "VINUM");
1163 	gctl_ro_param(req, "verb", -1, "saveconfig");
1164 	errstr = gctl_issue(req);
1165 	if (errstr != NULL)
1166 		warnx("can't save configuration: %s", errstr);
1167 	gctl_free(req);
1168 }
1169 
1170 static void
1171 gvinum_start(int argc, char * const *argv)
1172 {
1173 	struct gctl_req *req;
1174 	int i, initsize, j;
1175 	const char *errstr;
1176 	char buf[20];
1177 
1178 	/* 'start' with no arguments is a no-op. */
1179 	if (argc == 1)
1180 		return;
1181 
1182 	initsize = 0;
1183 
1184 	optreset = 1;
1185 	optind = 1;
1186 	while ((j = getopt(argc, argv, "S")) != -1) {
1187 		switch (j) {
1188 		case 'S':
1189 			initsize = atoi(optarg);
1190 			break;
1191 		default:
1192 			return;
1193 		}
1194 	}
1195 	argc -= optind;
1196 	argv += optind;
1197 
1198 	if (!initsize)
1199 		initsize = 512;
1200 
1201 	req = gctl_get_handle();
1202 	gctl_ro_param(req, "class", -1, "VINUM");
1203 	gctl_ro_param(req, "verb", -1, "start");
1204 	gctl_ro_param(req, "argc", sizeof(int), &argc);
1205 	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
1206 	if (argc) {
1207 		for (i = 0; i < argc; i++) {
1208 			snprintf(buf, sizeof(buf), "argv%d", i);
1209 			gctl_ro_param(req, buf, -1, argv[i]);
1210 		}
1211 	}
1212 	errstr = gctl_issue(req);
1213 	if (errstr != NULL) {
1214 		warnx("can't start: %s", errstr);
1215 		gctl_free(req);
1216 		return;
1217 	}
1218 
1219 	gctl_free(req);
1220 }
1221 
1222 static void
1223 gvinum_stop(int argc __unused, char * const *argv __unused)
1224 {
1225 	int err, fileid;
1226 
1227 	fileid = kldfind(GVINUMKLD);
1228 	if (fileid == -1) {
1229 		if (modfind(GVINUMMOD) < 0)
1230 			warn("cannot find " GVINUMKLD);
1231 		return;
1232 	}
1233 
1234 	/*
1235 	 * This little hack prevents that we end up in an infinite loop in
1236 	 * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
1237 	 * event thread will be free for the g_wither_geom() call from
1238 	 * gv_unload().  It's silly, but it works.
1239 	 */
1240 	printf("unloading " GVINUMKLD " kernel module... ");
1241 	fflush(stdout);
1242 	if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
1243 		sleep(1);
1244 		err = kldunload(fileid);
1245 	}
1246 	if (err != 0) {
1247 		printf(" failed!\n");
1248 		warn("cannot unload " GVINUMKLD);
1249 		return;
1250 	}
1251 
1252 	printf("done\n");
1253 	exit(0);
1254 }
1255 
1256 /* Create a striped volume. */
1257 static void
1258 gvinum_stripe(int argc, char * const *argv)
1259 {
1260 
1261 	if (argc < 2) {
1262 		warnx("usage:\tstripe [-fv] [-n name] drives\n");
1263 		return;
1264 	}
1265 	create_volume(argc, argv, "stripe");
1266 }
1267 
1268 /* Grow a subdisk by adding disk backed by provider. */
1269 static void
1270 gvinum_grow(int argc, char * const *argv)
1271 {
1272 	struct gctl_req *req;
1273 	char *drive, *sdname;
1274 	char sdprefix[GV_MAXSDNAME];
1275 	struct gv_drive *d;
1276 	struct gv_sd *s;
1277 	const char *errstr;
1278 	int drives, volumes, plexes, subdisks, flags;
1279 
1280 	flags = 0;
1281 	drives = volumes = plexes = subdisks = 0;
1282 	if (argc < 3) {
1283 		warnx("usage:\tgrow plex drive\n");
1284 		return;
1285 	}
1286 
1287 	s = gv_alloc_sd();
1288 	if (s == NULL) {
1289 		warn("unable to create subdisk");
1290 		return;
1291 	}
1292 	d = gv_alloc_drive();
1293 	if (d == NULL) {
1294 		warn("unable to create drive");
1295 		free(s);
1296 		return;
1297 	}
1298 	/* Lookup device and set an appropriate drive name. */
1299 	drive = find_drive();
1300 	if (drive == NULL) {
1301 		warn("unable to find an appropriate drive name");
1302 		free(s);
1303 		free(d);
1304 		return;
1305 	}
1306 	strlcpy(d->name, drive, sizeof(d->name));
1307 	copy_device(d, argv[2]);
1308 
1309 	drives = 1;
1310 
1311 	/* We try to use the plex name as basis for the subdisk name. */
1312 	snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1313 	sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1314 	if (sdname == NULL) {
1315 		warn("unable to find an appropriate subdisk name");
1316 		free(s);
1317 		free(d);
1318 		free(drive);
1319 		return;
1320 	}
1321 	strlcpy(s->name, sdname, sizeof(s->name));
1322 	free(sdname);
1323 	strlcpy(s->plex, argv[1], sizeof(s->plex));
1324 	strlcpy(s->drive, d->name, sizeof(s->drive));
1325 	subdisks = 1;
1326 
1327 	req = gctl_get_handle();
1328 	gctl_ro_param(req, "class", -1, "VINUM");
1329 	gctl_ro_param(req, "verb", -1, "create");
1330 	gctl_ro_param(req, "flags", sizeof(int), &flags);
1331 	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1332 	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1333 	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1334 	gctl_ro_param(req, "drives", sizeof(int), &drives);
1335 	gctl_ro_param(req, "drive0", sizeof(*d), d);
1336 	gctl_ro_param(req, "sd0", sizeof(*s), s);
1337 	errstr = gctl_issue(req);
1338 	free(drive);
1339 	if (errstr != NULL) {
1340 		warnx("unable to grow plex: %s", errstr);
1341 		free(s);
1342 		free(d);
1343 		return;
1344 	}
1345 	gctl_free(req);
1346 }
1347 
1348 static void
1349 parseline(int argc, char * const *argv)
1350 {
1351 
1352 	if (argc <= 0)
1353 		return;
1354 
1355 	if (!strcmp(argv[0], "create"))
1356 		gvinum_create(argc, argv);
1357 	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1358 		exit(0);
1359 	else if (!strcmp(argv[0], "attach"))
1360 		gvinum_attach(argc, argv);
1361 	else if (!strcmp(argv[0], "detach"))
1362 		gvinum_detach(argc, argv);
1363 	else if (!strcmp(argv[0], "concat"))
1364 		gvinum_concat(argc, argv);
1365 	else if (!strcmp(argv[0], "grow"))
1366 		gvinum_grow(argc, argv);
1367 	else if (!strcmp(argv[0], "help"))
1368 		gvinum_help();
1369 	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1370 		gvinum_list(argc, argv);
1371 	else if (!strcmp(argv[0], "ld"))
1372 		gvinum_list(argc, argv);
1373 	else if (!strcmp(argv[0], "lp"))
1374 		gvinum_list(argc, argv);
1375 	else if (!strcmp(argv[0], "ls"))
1376 		gvinum_list(argc, argv);
1377 	else if (!strcmp(argv[0], "lv"))
1378 		gvinum_list(argc, argv);
1379 	else if (!strcmp(argv[0], "mirror"))
1380 		gvinum_mirror(argc, argv);
1381 	else if (!strcmp(argv[0], "move"))
1382 		gvinum_move(argc, argv);
1383 	else if (!strcmp(argv[0], "mv"))
1384 		gvinum_move(argc, argv);
1385 	else if (!strcmp(argv[0], "printconfig"))
1386 		gvinum_printconfig(argc, argv);
1387 	else if (!strcmp(argv[0], "raid5"))
1388 		gvinum_raid5(argc, argv);
1389 	else if (!strcmp(argv[0], "rename"))
1390 		gvinum_rename(argc, argv);
1391 	else if (!strcmp(argv[0], "resetconfig"))
1392 		gvinum_resetconfig(argc, argv);
1393 	else if (!strcmp(argv[0], "rm"))
1394 		gvinum_rm(argc, argv);
1395 	else if (!strcmp(argv[0], "saveconfig"))
1396 		gvinum_saveconfig();
1397 	else if (!strcmp(argv[0], "setstate"))
1398 		gvinum_setstate(argc, argv);
1399 	else if (!strcmp(argv[0], "start"))
1400 		gvinum_start(argc, argv);
1401 	else if (!strcmp(argv[0], "stop"))
1402 		gvinum_stop(argc, argv);
1403 	else if (!strcmp(argv[0], "stripe"))
1404 		gvinum_stripe(argc, argv);
1405 	else if (!strcmp(argv[0], "checkparity"))
1406 		gvinum_parityop(argc, argv, 0);
1407 	else if (!strcmp(argv[0], "rebuildparity"))
1408 		gvinum_parityop(argc, argv, 1);
1409 	else
1410 		printf("unknown command '%s'\n", argv[0]);
1411 }
1412 
1413 /*
1414  * The guts of printconfig.  This is called from gvinum_printconfig and from
1415  * gvinum_create when called without an argument, in order to give the user
1416  * something to edit.
1417  */
1418 static void
1419 printconfig(FILE *of, const char *comment)
1420 {
1421 	struct gctl_req *req;
1422 	struct utsname uname_s;
1423 	const char *errstr;
1424 	time_t now;
1425 	char buf[GV_CFG_LEN + 1];
1426 
1427 	uname(&uname_s);
1428 	time(&now);
1429 	buf[0] = '\0';
1430 
1431 	req = gctl_get_handle();
1432 	gctl_ro_param(req, "class", -1, "VINUM");
1433 	gctl_ro_param(req, "verb", -1, "getconfig");
1434 	gctl_ro_param(req, "comment", -1, comment);
1435 	gctl_add_param(req, "config", sizeof(buf), buf,
1436 	    GCTL_PARAM_WR | GCTL_PARAM_ASCII);
1437 	errstr = gctl_issue(req);
1438 	if (errstr != NULL) {
1439 		warnx("can't get configuration: %s", errstr);
1440 		return;
1441 	}
1442 	gctl_free(req);
1443 
1444 	fprintf(of, "# Vinum configuration of %s, saved at %s",
1445 	    uname_s.nodename,
1446 	    ctime(&now));
1447 
1448 	if (*comment != '\0')
1449 	    fprintf(of, "# Current configuration:\n");
1450 
1451 	fprintf(of, "%s", buf);
1452 }
1453