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