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