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