xref: /freebsd/sbin/gvinum/gvinum.c (revision 7aa383846770374466b1dcb2cefd71bde9acf463)
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 	flags = 0;
648 	optreset = 1;
649 	optind = 1;
650 	while ((i = getopt(argc, argv, "f")) != -1) {
651 		switch(i) {
652 		case 'f':
653 			flags |= GV_FLAG_F;
654 			break;
655 		default:
656 			warn("invalid flag: %c", i);
657 			return;
658 		}
659 	}
660 	argc -= optind;
661 	argv += optind;
662 	if (argc != 1) {
663 		warnx("usage: detach [-f] <subdisk> | <plex>");
664 		return;
665 	}
666 
667 	req = gctl_get_handle();
668 	gctl_ro_param(req, "class", -1, "VINUM");
669 	gctl_ro_param(req, "verb", -1, "detach");
670 	gctl_ro_param(req, "object", -1, argv[0]);
671 	gctl_ro_param(req, "flags", sizeof(int), &flags);
672 
673 	errstr = gctl_issue(req);
674 	if (errstr != NULL)
675 		warnx("detach failed: %s", errstr);
676 	gctl_free(req);
677 }
678 
679 void
680 gvinum_help(void)
681 {
682 	printf("COMMANDS\n"
683 	    "checkparity [-f] plex\n"
684 	    "        Check the parity blocks of a RAID-5 plex.\n"
685 	    "create [-f] description-file\n"
686 	    "        Create as per description-file or open editor.\n"
687 	    "attach plex volume [rename]\n"
688 	    "attach subdisk plex [offset] [rename]\n"
689 	    "        Attach a plex to a volume, or a subdisk to a plex\n"
690 	    "concat [-fv] [-n name] drives\n"
691 	    "        Create a concatenated volume from the specified drives.\n"
692 	    "detach [-f] [plex | subdisk]\n"
693 	    "        Detach a plex or a subdisk from the volume or plex to\n"
694 	    "        which it is attached.\n"
695 	    "grow plex drive\n"
696 	    "        Grow plex by creating a properly sized subdisk on drive\n"
697 	    "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
698 	    "        List information about specified objects.\n"
699 	    "ld [-r] [-v] [-V] [volume]\n"
700 	    "        List information about drives.\n"
701 	    "ls [-r] [-v] [-V] [subdisk]\n"
702 	    "        List information about subdisks.\n"
703 	    "lp [-r] [-v] [-V] [plex]\n"
704 	    "        List information about plexes.\n"
705 	    "lv [-r] [-v] [-V] [volume]\n"
706 	    "        List information about volumes.\n"
707 	    "mirror [-fsv] [-n name] drives\n"
708 	    "        Create a mirrored volume from the specified drives.\n"
709 	    "move | mv -f drive object ...\n"
710 	    "        Move the object(s) to the specified drive.\n"
711 	    "quit    Exit the vinum program when running in interactive mode."
712 	    "  Nor-\n"
713 	    "        mally this would be done by entering the EOF character.\n"
714 	    "raid5 [-fv] [-s stripesize] [-n name] drives\n"
715 	    "        Create a RAID-5 volume from the specified drives.\n"
716 	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
717 	    "        Change the name of the specified object.\n"
718 	    "rebuildparity plex [-f]\n"
719 	    "        Rebuild the parity blocks of a RAID-5 plex.\n"
720 	    "resetconfig\n"
721 	    "        Reset the complete gvinum configuration\n"
722 	    "rm [-r] [-f] volume | plex | subdisk | drive\n"
723 	    "        Remove an object.\n"
724 	    "saveconfig\n"
725 	    "        Save vinum configuration to disk after configuration"
726 	    " failures.\n"
727 	    "setstate [-f] state [volume | plex | subdisk | drive]\n"
728 	    "        Set state without influencing other objects, for"
729 	    " diagnostic pur-\n"
730 	    "        poses only.\n"
731 	    "start [-S size] volume | plex | subdisk\n"
732 	    "        Allow the system to access the objects.\n"
733 	    "stripe [-fv] [-n name] drives\n"
734 	    "        Create a striped volume from the specified drives.\n"
735 	);
736 
737 	return;
738 }
739 
740 void
741 gvinum_setstate(int argc, char **argv)
742 {
743 	struct gctl_req *req;
744 	int flags, i;
745 	const char *errstr;
746 
747 	flags = 0;
748 
749 	optreset = 1;
750 	optind = 1;
751 
752 	while ((i = getopt(argc, argv, "f")) != -1) {
753 		switch (i) {
754 		case 'f':
755 			flags |= GV_FLAG_F;
756 			break;
757 		case '?':
758 		default:
759 			warn("invalid flag: %c", i);
760 			return;
761 		}
762 	}
763 
764 	argc -= optind;
765 	argv += optind;
766 
767 	if (argc != 2) {
768 		warnx("usage: setstate [-f] <state> <obj>");
769 		return;
770 	}
771 
772 	/*
773 	 * XXX: This hack is needed to avoid tripping over (now) invalid
774 	 * 'classic' vinum states and will go away later.
775 	 */
776 	if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
777 	    strcmp(argv[0], "stale")) {
778 		warnx("invalid state '%s'", argv[0]);
779 		return;
780 	}
781 
782 	req = gctl_get_handle();
783 	gctl_ro_param(req, "class", -1, "VINUM");
784 	gctl_ro_param(req, "verb", -1, "setstate");
785 	gctl_ro_param(req, "state", -1, argv[0]);
786 	gctl_ro_param(req, "object", -1, argv[1]);
787 	gctl_ro_param(req, "flags", sizeof(int), &flags);
788 
789 	errstr = gctl_issue(req);
790 	if (errstr != NULL)
791 		warnx("%s", errstr);
792 	gctl_free(req);
793 }
794 
795 void
796 gvinum_list(int argc, char **argv)
797 {
798 	struct gctl_req *req;
799 	int flags, i, j;
800 	const char *errstr;
801 	char buf[20], *cmd, config[GV_CFG_LEN + 1];
802 
803 	flags = 0;
804 	cmd = "list";
805 
806 	if (argc) {
807 		optreset = 1;
808 		optind = 1;
809 		cmd = argv[0];
810 		while ((j = getopt(argc, argv, "rsvV")) != -1) {
811 			switch (j) {
812 			case 'r':
813 				flags |= GV_FLAG_R;
814 				break;
815 			case 's':
816 				flags |= GV_FLAG_S;
817 				break;
818 			case 'v':
819 				flags |= GV_FLAG_V;
820 				break;
821 			case 'V':
822 				flags |= GV_FLAG_V;
823 				flags |= GV_FLAG_VV;
824 				break;
825 			case '?':
826 			default:
827 				return;
828 			}
829 		}
830 		argc -= optind;
831 		argv += optind;
832 
833 	}
834 
835 	req = gctl_get_handle();
836 	gctl_ro_param(req, "class", -1, "VINUM");
837 	gctl_ro_param(req, "verb", -1, "list");
838 	gctl_ro_param(req, "cmd", -1, cmd);
839 	gctl_ro_param(req, "argc", sizeof(int), &argc);
840 	gctl_ro_param(req, "flags", sizeof(int), &flags);
841 	gctl_rw_param(req, "config", sizeof(config), config);
842 	if (argc) {
843 		for (i = 0; i < argc; i++) {
844 			snprintf(buf, sizeof(buf), "argv%d", i);
845 			gctl_ro_param(req, buf, -1, argv[i]);
846 		}
847 	}
848 	errstr = gctl_issue(req);
849 	if (errstr != NULL) {
850 		warnx("can't get configuration: %s", errstr);
851 		gctl_free(req);
852 		return;
853 	}
854 
855 	printf("%s", config);
856 	gctl_free(req);
857 	return;
858 }
859 
860 /* Create a mirrored volume. */
861 void
862 gvinum_mirror(int argc, char **argv)
863 {
864 
865 	if (argc < 2) {
866 		warnx("usage\tmirror [-fsv] [-n name] drives\n");
867 		return;
868 	}
869 	create_volume(argc, argv, "mirror");
870 }
871 
872 /* Note that move is currently of form '[-r] target object [...]' */
873 void
874 gvinum_move(int argc, char **argv)
875 {
876 	struct gctl_req *req;
877 	const char *errstr;
878 	char buf[20];
879 	int flags, i, j;
880 
881 	flags = 0;
882 	if (argc) {
883 		optreset = 1;
884 		optind = 1;
885 		while ((j = getopt(argc, argv, "f")) != -1) {
886 			switch (j) {
887 			case 'f':
888 				flags |= GV_FLAG_F;
889 				break;
890 			case '?':
891 			default:
892 				return;
893 			}
894 		}
895 		argc -= optind;
896 		argv += optind;
897 	}
898 
899 	switch (argc) {
900 		case 0:
901 			warnx("no destination or object(s) to move specified");
902 			return;
903 		case 1:
904 			warnx("no object(s) to move specified");
905 			return;
906 		default:
907 			break;
908 	}
909 
910 	req = gctl_get_handle();
911 	gctl_ro_param(req, "class", -1, "VINUM");
912 	gctl_ro_param(req, "verb", -1, "move");
913 	gctl_ro_param(req, "argc", sizeof(int), &argc);
914 	gctl_ro_param(req, "flags", sizeof(int), &flags);
915 	gctl_ro_param(req, "destination", -1, argv[0]);
916 	for (i = 1; i < argc; i++) {
917 		snprintf(buf, sizeof(buf), "argv%d", i);
918 		gctl_ro_param(req, buf, -1, argv[i]);
919 	}
920 	errstr = gctl_issue(req);
921 	if (errstr != NULL)
922 		warnx("can't move object(s):  %s", errstr);
923 	gctl_free(req);
924 	return;
925 }
926 
927 void
928 gvinum_printconfig(int argc, char **argv)
929 {
930 	printconfig(stdout, "");
931 }
932 
933 void
934 gvinum_parityop(int argc, char **argv, int rebuild)
935 {
936 	struct gctl_req *req;
937 	int flags, i;
938 	const char *errstr;
939 	char *op, *msg;
940 
941 	if (rebuild) {
942 		op = "rebuildparity";
943 		msg = "Rebuilding";
944 	} else {
945 		op = "checkparity";
946 		msg = "Checking";
947 	}
948 
949 	optreset = 1;
950 	optind = 1;
951 	flags = 0;
952 	while ((i = getopt(argc, argv, "fv")) != -1) {
953 		switch (i) {
954 		case 'f':
955 			flags |= GV_FLAG_F;
956 			break;
957 		case 'v':
958 			flags |= GV_FLAG_V;
959 			break;
960 		case '?':
961 		default:
962 			warnx("invalid flag '%c'", i);
963 			return;
964 		}
965 	}
966 	argc -= optind;
967 	argv += optind;
968 
969 	if (argc != 1) {
970 		warn("usage: %s [-f] [-v] <plex>", op);
971 		return;
972 	}
973 
974 	req = gctl_get_handle();
975 	gctl_ro_param(req, "class", -1, "VINUM");
976 	gctl_ro_param(req, "verb", -1, op);
977 	gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
978 	gctl_ro_param(req, "flags", sizeof(int), &flags);
979 	gctl_ro_param(req, "plex", -1, argv[0]);
980 
981 	errstr = gctl_issue(req);
982 	if (errstr)
983 		warnx("%s\n", errstr);
984 	gctl_free(req);
985 }
986 
987 /* Create a RAID-5 volume. */
988 void
989 gvinum_raid5(int argc, char **argv)
990 {
991 
992 	if (argc < 2) {
993 		warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
994 		return;
995 	}
996 	create_volume(argc, argv, "raid5");
997 }
998 
999 
1000 void
1001 gvinum_rename(int argc, char **argv)
1002 {
1003 	struct gctl_req *req;
1004 	const char *errstr;
1005 	int flags, j;
1006 
1007 	flags = 0;
1008 
1009 	if (argc) {
1010 		optreset = 1;
1011 		optind = 1;
1012 		while ((j = getopt(argc, argv, "r")) != -1) {
1013 			switch (j) {
1014 			case 'r':
1015 				flags |= GV_FLAG_R;
1016 				break;
1017 			case '?':
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 	return;
1051 }
1052 
1053 void
1054 gvinum_rm(int argc, char **argv)
1055 {
1056 	struct gctl_req *req;
1057 	int flags, i, j;
1058 	const char *errstr;
1059 	char buf[20], *cmd;
1060 
1061 	cmd = argv[0];
1062 	flags = 0;
1063 	optreset = 1;
1064 	optind = 1;
1065 	while ((j = getopt(argc, argv, "rf")) != -1) {
1066 		switch (j) {
1067 		case 'f':
1068 			flags |= GV_FLAG_F;
1069 			break;
1070 		case 'r':
1071 			flags |= GV_FLAG_R;
1072 			break;
1073 		case '?':
1074 		default:
1075 			return;
1076 		}
1077 	}
1078 	argc -= optind;
1079 	argv += optind;
1080 
1081 	req = gctl_get_handle();
1082 	gctl_ro_param(req, "class", -1, "VINUM");
1083 	gctl_ro_param(req, "verb", -1, "remove");
1084 	gctl_ro_param(req, "argc", sizeof(int), &argc);
1085 	gctl_ro_param(req, "flags", sizeof(int), &flags);
1086 	if (argc) {
1087 		for (i = 0; i < argc; i++) {
1088 			snprintf(buf, sizeof(buf), "argv%d", i);
1089 			gctl_ro_param(req, buf, -1, argv[i]);
1090 		}
1091 	}
1092 	errstr = gctl_issue(req);
1093 	if (errstr != NULL) {
1094 		warnx("can't remove: %s", errstr);
1095 		gctl_free(req);
1096 		return;
1097 	}
1098 	gctl_free(req);
1099 }
1100 
1101 void
1102 gvinum_resetconfig(void)
1103 {
1104 	struct gctl_req *req;
1105 	const char *errstr;
1106 	char reply[32];
1107 
1108 	if (!isatty(STDIN_FILENO)) {
1109 		warn("Please enter this command from a tty device\n");
1110 		return;
1111 	}
1112 	printf(" WARNING!  This command will completely wipe out your gvinum"
1113 	    "configuration.\n"
1114 	    " All data will be lost.  If you really want to do this,"
1115 	    " enter the text\n\n"
1116 	    " NO FUTURE\n"
1117 	    " Enter text -> ");
1118 	fgets(reply, sizeof(reply), stdin);
1119 	if (strcmp(reply, "NO FUTURE\n")) {
1120 		printf("\n No change\n");
1121 		return;
1122 	}
1123 	req = gctl_get_handle();
1124 	gctl_ro_param(req, "class", -1, "VINUM");
1125 	gctl_ro_param(req, "verb", -1, "resetconfig");
1126 	errstr = gctl_issue(req);
1127 	if (errstr != NULL) {
1128 		warnx("can't reset config: %s", errstr);
1129 		gctl_free(req);
1130 		return;
1131 	}
1132 	gctl_free(req);
1133 	printf("gvinum configuration obliterated\n");
1134 }
1135 
1136 void
1137 gvinum_saveconfig(void)
1138 {
1139 	struct gctl_req *req;
1140 	const char *errstr;
1141 
1142 	req = gctl_get_handle();
1143 	gctl_ro_param(req, "class", -1, "VINUM");
1144 	gctl_ro_param(req, "verb", -1, "saveconfig");
1145 	errstr = gctl_issue(req);
1146 	if (errstr != NULL)
1147 		warnx("can't save configuration: %s", errstr);
1148 	gctl_free(req);
1149 }
1150 
1151 void
1152 gvinum_start(int argc, char **argv)
1153 {
1154 	struct gctl_req *req;
1155 	int i, initsize, j;
1156 	const char *errstr;
1157 	char buf[20];
1158 
1159 	/* 'start' with no arguments is a no-op. */
1160 	if (argc == 1)
1161 		return;
1162 
1163 	initsize = 0;
1164 
1165 	optreset = 1;
1166 	optind = 1;
1167 	while ((j = getopt(argc, argv, "S")) != -1) {
1168 		switch (j) {
1169 		case 'S':
1170 			initsize = atoi(optarg);
1171 			break;
1172 		case '?':
1173 		default:
1174 			return;
1175 		}
1176 	}
1177 	argc -= optind;
1178 	argv += optind;
1179 
1180 	if (!initsize)
1181 		initsize = 512;
1182 
1183 	req = gctl_get_handle();
1184 	gctl_ro_param(req, "class", -1, "VINUM");
1185 	gctl_ro_param(req, "verb", -1, "start");
1186 	gctl_ro_param(req, "argc", sizeof(int), &argc);
1187 	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
1188 	if (argc) {
1189 		for (i = 0; i < argc; i++) {
1190 			snprintf(buf, sizeof(buf), "argv%d", i);
1191 			gctl_ro_param(req, buf, -1, argv[i]);
1192 		}
1193 	}
1194 	errstr = gctl_issue(req);
1195 	if (errstr != NULL) {
1196 		warnx("can't start: %s", errstr);
1197 		gctl_free(req);
1198 		return;
1199 	}
1200 
1201 	gctl_free(req);
1202 }
1203 
1204 void
1205 gvinum_stop(int argc, char **argv)
1206 {
1207 	int err, fileid;
1208 
1209 	fileid = kldfind(GVINUMMOD);
1210 	if (fileid == -1) {
1211 		warn("cannot find " GVINUMMOD);
1212 		return;
1213 	}
1214 
1215 	/*
1216 	 * This little hack prevents that we end up in an infinite loop in
1217 	 * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
1218 	 * event thread will be free for the g_wither_geom() call from
1219 	 * gv_unload().  It's silly, but it works.
1220 	 */
1221 	printf("unloading " GVINUMMOD " kernel module... ");
1222 	fflush(stdout);
1223 	if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
1224 		sleep(1);
1225 		err = kldunload(fileid);
1226 	}
1227 	if (err != 0) {
1228 		printf(" failed!\n");
1229 		warn("cannot unload " GVINUMMOD);
1230 		return;
1231 	}
1232 
1233 	printf("done\n");
1234 	exit(0);
1235 }
1236 
1237 /* Create a striped volume. */
1238 void
1239 gvinum_stripe(int argc, char **argv)
1240 {
1241 
1242 	if (argc < 2) {
1243 		warnx("usage:\tstripe [-fv] [-n name] drives\n");
1244 		return;
1245 	}
1246 	create_volume(argc, argv, "stripe");
1247 }
1248 
1249 /* Grow a subdisk by adding disk backed by provider. */
1250 void
1251 gvinum_grow(int argc, char **argv)
1252 {
1253 	struct gctl_req *req;
1254 	char *drive, *sdname;
1255 	char sdprefix[GV_MAXSDNAME];
1256 	struct gv_drive *d;
1257 	struct gv_sd *s;
1258 	const char *errstr;
1259 	int drives, volumes, plexes, subdisks, flags;
1260 
1261 	drives = volumes = plexes = subdisks = 0;
1262 	if (argc < 3) {
1263 		warnx("usage:\tgrow plex drive\n");
1264 		return;
1265 	}
1266 
1267 	s = gv_alloc_sd();
1268 	if (s == NULL) {
1269 		warn("unable to create subdisk");
1270 		return;
1271 	}
1272 	d = gv_alloc_drive();
1273 	if (d == NULL) {
1274 		warn("unable to create drive");
1275 		free(s);
1276 		return;
1277 	}
1278 	/* Lookup device and set an appropriate drive name. */
1279 	drive = find_drive();
1280 	if (drive == NULL) {
1281 		warn("unable to find an appropriate drive name");
1282 		free(s);
1283 		free(d);
1284 		return;
1285 	}
1286 	strlcpy(d->name, drive, sizeof(d->name));
1287 	copy_device(d, argv[2]);
1288 
1289 	drives = 1;
1290 
1291 	/* We try to use the plex name as basis for the subdisk name. */
1292 	snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1293 	sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1294 	if (sdname == NULL) {
1295 		warn("unable to find an appropriate subdisk name");
1296 		free(s);
1297 		free(d);
1298 		free(drive);
1299 		return;
1300 	}
1301 	strlcpy(s->name, sdname, sizeof(s->name));
1302 	free(sdname);
1303 	strlcpy(s->plex, argv[1], sizeof(s->plex));
1304 	strlcpy(s->drive, d->name, sizeof(s->drive));
1305 	subdisks = 1;
1306 
1307 	req = gctl_get_handle();
1308 	gctl_ro_param(req, "class", -1, "VINUM");
1309 	gctl_ro_param(req, "verb", -1, "create");
1310 	gctl_ro_param(req, "flags", sizeof(int), &flags);
1311 	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1312 	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1313 	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1314 	gctl_ro_param(req, "drives", sizeof(int), &drives);
1315 	gctl_ro_param(req, "drive0", sizeof(*d), d);
1316 	gctl_ro_param(req, "sd0", sizeof(*s), s);
1317 	errstr = gctl_issue(req);
1318 	free(drive);
1319 	if (errstr != NULL) {
1320 		warnx("unable to grow plex: %s", errstr);
1321 		free(s);
1322 		free(d);
1323 		return;
1324 	}
1325 	gctl_free(req);
1326 }
1327 
1328 void
1329 parseline(int argc, char **argv)
1330 {
1331 	if (argc <= 0)
1332 		return;
1333 
1334 	if (!strcmp(argv[0], "create"))
1335 		gvinum_create(argc, argv);
1336 	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1337 		exit(0);
1338 	else if (!strcmp(argv[0], "attach"))
1339 		gvinum_attach(argc, argv);
1340 	else if (!strcmp(argv[0], "detach"))
1341 		gvinum_detach(argc, argv);
1342 	else if (!strcmp(argv[0], "concat"))
1343 		gvinum_concat(argc, argv);
1344 	else if (!strcmp(argv[0], "grow"))
1345 		gvinum_grow(argc, argv);
1346 	else if (!strcmp(argv[0], "help"))
1347 		gvinum_help();
1348 	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1349 		gvinum_list(argc, argv);
1350 	else if (!strcmp(argv[0], "ld"))
1351 		gvinum_list(argc, argv);
1352 	else if (!strcmp(argv[0], "lp"))
1353 		gvinum_list(argc, argv);
1354 	else if (!strcmp(argv[0], "ls"))
1355 		gvinum_list(argc, argv);
1356 	else if (!strcmp(argv[0], "lv"))
1357 		gvinum_list(argc, argv);
1358 	else if (!strcmp(argv[0], "mirror"))
1359 		gvinum_mirror(argc, argv);
1360 	else if (!strcmp(argv[0], "move"))
1361 		gvinum_move(argc, argv);
1362 	else if (!strcmp(argv[0], "mv"))
1363 		gvinum_move(argc, argv);
1364 	else if (!strcmp(argv[0], "printconfig"))
1365 		gvinum_printconfig(argc, argv);
1366 	else if (!strcmp(argv[0], "raid5"))
1367 		gvinum_raid5(argc, argv);
1368 	else if (!strcmp(argv[0], "rename"))
1369 		gvinum_rename(argc, argv);
1370 	else if (!strcmp(argv[0], "resetconfig"))
1371 		gvinum_resetconfig();
1372 	else if (!strcmp(argv[0], "rm"))
1373 		gvinum_rm(argc, argv);
1374 	else if (!strcmp(argv[0], "saveconfig"))
1375 		gvinum_saveconfig();
1376 	else if (!strcmp(argv[0], "setstate"))
1377 		gvinum_setstate(argc, argv);
1378 	else if (!strcmp(argv[0], "start"))
1379 		gvinum_start(argc, argv);
1380 	else if (!strcmp(argv[0], "stop"))
1381 		gvinum_stop(argc, argv);
1382 	else if (!strcmp(argv[0], "stripe"))
1383 		gvinum_stripe(argc, argv);
1384 	else if (!strcmp(argv[0], "checkparity"))
1385 		gvinum_parityop(argc, argv, 0);
1386 	else if (!strcmp(argv[0], "rebuildparity"))
1387 		gvinum_parityop(argc, argv, 1);
1388 	else
1389 		printf("unknown command '%s'\n", argv[0]);
1390 
1391 	return;
1392 }
1393 
1394 /*
1395  * The guts of printconfig.  This is called from gvinum_printconfig and from
1396  * gvinum_create when called without an argument, in order to give the user
1397  * something to edit.
1398  */
1399 void
1400 printconfig(FILE *of, char *comment)
1401 {
1402 	struct gctl_req *req;
1403 	struct utsname uname_s;
1404 	const char *errstr;
1405 	time_t now;
1406 	char buf[GV_CFG_LEN + 1];
1407 
1408 	uname(&uname_s);
1409 	time(&now);
1410 
1411 	req = gctl_get_handle();
1412 	gctl_ro_param(req, "class", -1, "VINUM");
1413 	gctl_ro_param(req, "verb", -1, "getconfig");
1414 	gctl_ro_param(req, "comment", -1, comment);
1415 	gctl_rw_param(req, "config", sizeof(buf), buf);
1416 	errstr = gctl_issue(req);
1417 	if (errstr != NULL) {
1418 		warnx("can't get configuration: %s", errstr);
1419 		return;
1420 	}
1421 	gctl_free(req);
1422 
1423 	fprintf(of, "# Vinum configuration of %s, saved at %s",
1424 	    uname_s.nodename,
1425 	    ctime(&now));
1426 
1427 	if (*comment != '\0')
1428 	    fprintf(of, "# Current configuration:\n");
1429 
1430 	fprintf(of, buf);
1431 }
1432