xref: /freebsd/sbin/gvinum/gvinum.c (revision c0020399)
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_drive(const char *);
87 char	*find_pattern(char *, char *);
88 
89 int
90 main(int argc, char **argv)
91 {
92 	int line, tokens;
93 	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
94 
95 	/* Load the module if necessary. */
96 	if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
97 		err(1, GVINUMMOD ": Kernel module not available");
98 
99 	/* Arguments given on the command line. */
100 	if (argc > 1) {
101 		argc--;
102 		argv++;
103 		parseline(argc, argv);
104 
105 	/* Interactive mode. */
106 	} else {
107 		for (;;) {
108 			inputline = readline("gvinum -> ");
109 			if (inputline == NULL) {
110 				if (ferror(stdin)) {
111 					err(1, "can't read input");
112 				} else {
113 					printf("\n");
114 					exit(0);
115 				}
116 			} else if (*inputline) {
117 				add_history(inputline);
118 				strcpy(buffer, inputline);
119 				free(inputline);
120 				line++;		    /* count the lines */
121 				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
122 				if (tokens)
123 					parseline(tokens, token);
124 			}
125 		}
126 	}
127 	exit(0);
128 }
129 
130 /* Attach a plex to a volume or a subdisk to a plex. */
131 void
132 gvinum_attach(int argc, char **argv)
133 {
134 	struct gctl_req *req;
135 	const char *errstr;
136 	int rename;
137 	off_t offset;
138 
139 	rename = 0;
140 	offset = -1;
141 	if (argc < 3) {
142 		warnx("usage:\tattach <subdisk> <plex> [rename] "
143 		    "[<plexoffset>]\n"
144 		    "\tattach <plex> <volume> [rename]");
145 		return;
146 	}
147 	if (argc > 3) {
148 		if (!strcmp(argv[3], "rename")) {
149 			rename = 1;
150 			if (argc == 5)
151 				offset = strtol(argv[4], NULL, 0);
152 		} else
153 			offset = strtol(argv[3], NULL, 0);
154 	}
155 	req = gctl_get_handle();
156 	gctl_ro_param(req, "class", -1, "VINUM");
157 	gctl_ro_param(req, "verb", -1, "attach");
158 	gctl_ro_param(req, "child", -1, argv[1]);
159 	gctl_ro_param(req, "parent", -1, argv[2]);
160 	gctl_ro_param(req, "offset", sizeof(off_t), &offset);
161 	gctl_ro_param(req, "rename", sizeof(int), &rename);
162 	errstr = gctl_issue(req);
163 	if (errstr != NULL)
164 		warnx("attach failed: %s", errstr);
165 	gctl_free(req);
166 }
167 
168 void
169 gvinum_create(int argc, char **argv)
170 {
171 	struct gctl_req *req;
172 	struct gv_drive *d;
173 	struct gv_plex *p;
174 	struct gv_sd *s;
175 	struct gv_volume *v;
176 	FILE *tmp;
177 	int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
178 	int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
179 	const char *errstr;
180 	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
181 	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
182 	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
183 
184 	tmp = NULL;
185 	flags = 0;
186 	for (i = 1; i < argc; i++) {
187 		/* Force flag used to ignore already created drives. */
188 		if (!strcmp(argv[i], "-f")) {
189 			flags |= GV_FLAG_F;
190 		/* Else it must be a file. */
191 		} else {
192 			if ((tmp = fopen(argv[1], "r")) == NULL) {
193 				warn("can't open '%s' for reading", argv[1]);
194 				return;
195 			}
196 		}
197 	}
198 
199 	/* We didn't get a file. */
200 	if (tmp == NULL) {
201 		snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
202 
203 		if ((fd = mkstemp(tmpfile)) == -1) {
204 			warn("temporary file not accessible");
205 			return;
206 		}
207 		if ((tmp = fdopen(fd, "w")) == NULL) {
208 			warn("can't open '%s' for writing", tmpfile);
209 			return;
210 		}
211 		printconfig(tmp, "# ");
212 		fclose(tmp);
213 
214 		ed = getenv("EDITOR");
215 		if (ed == NULL)
216 			ed = _PATH_VI;
217 
218 		snprintf(commandline, sizeof(commandline), "%s %s", ed,
219 		    tmpfile);
220 		status = system(commandline);
221 		if (status != 0) {
222 			warn("couldn't exec %s; status: %d", ed, status);
223 			return;
224 		}
225 
226 		if ((tmp = fopen(tmpfile, "r")) == NULL) {
227 			warn("can't open '%s' for reading", tmpfile);
228 			return;
229 		}
230 	}
231 
232 	req = gctl_get_handle();
233 	gctl_ro_param(req, "class", -1, "VINUM");
234 	gctl_ro_param(req, "verb", -1, "create");
235 	gctl_ro_param(req, "flags", sizeof(int), &flags);
236 
237 	drives = volumes = plexes = subdisks = 0;
238 	plex_in_volume = sd_in_plex = undeffd = 0;
239 	plex[0] = '\0';
240 	errors = 0;
241 	line = 1;
242 	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
243 
244 		/* Skip empty lines and comments. */
245 		if (*buf == '\0' || *buf == '#') {
246 			line++;
247 			continue;
248 		}
249 
250 		/* Kill off the newline. */
251 		buf[strlen(buf) - 1] = '\0';
252 
253 		/*
254 		 * Copy the original input line in case we need it for error
255 		 * output.
256 		 */
257 		strlcpy(original, buf, sizeof(original));
258 
259 		tokens = gv_tokenize(buf, token, GV_MAXARGS);
260 		if (tokens <= 0) {
261 			line++;
262 			continue;
263 		}
264 
265 		/* Volume definition. */
266 		if (!strcmp(token[0], "volume")) {
267 			v = gv_new_volume(tokens, token);
268 			if (v == NULL) {
269 				warnx("line %d: invalid volume definition",
270 				    line);
271 				warnx("line %d: '%s'", line, original);
272 				errors++;
273 				line++;
274 				continue;
275 			}
276 
277 			/* Reset plex count for this volume. */
278 			plex_in_volume = 0;
279 
280 			/*
281 			 * Set default volume name for following plex
282 			 * definitions.
283 			 */
284 			strlcpy(volume, v->name, sizeof(volume));
285 
286 			snprintf(buf1, sizeof(buf1), "volume%d", volumes);
287 			gctl_ro_param(req, buf1, sizeof(*v), v);
288 			volumes++;
289 
290 		/* Plex definition. */
291 		} else if (!strcmp(token[0], "plex")) {
292 			p = gv_new_plex(tokens, token);
293 			if (p == NULL) {
294 				warnx("line %d: invalid plex definition", line);
295 				warnx("line %d: '%s'", line, original);
296 				errors++;
297 				line++;
298 				continue;
299 			}
300 
301 			/* Reset subdisk count for this plex. */
302 			sd_in_plex = 0;
303 
304 			/* Default name. */
305 			if (strlen(p->name) == 0) {
306 				snprintf(p->name, sizeof(p->name), "%s.p%d",
307 				    volume, plex_in_volume++);
308 			}
309 
310 			/* Default volume. */
311 			if (strlen(p->volume) == 0) {
312 				snprintf(p->volume, sizeof(p->volume), "%s",
313 				    volume);
314 			}
315 
316 			/*
317 			 * Set default plex name for following subdisk
318 			 * definitions.
319 			 */
320 			strlcpy(plex, p->name, sizeof(plex));
321 
322 			snprintf(buf1, sizeof(buf1), "plex%d", plexes);
323 			gctl_ro_param(req, buf1, sizeof(*p), p);
324 			plexes++;
325 
326 		/* Subdisk definition. */
327 		} else if (!strcmp(token[0], "sd")) {
328 			s = gv_new_sd(tokens, token);
329 			if (s == NULL) {
330 				warnx("line %d: invalid subdisk "
331 				    "definition:", line);
332 				warnx("line %d: '%s'", line, original);
333 				errors++;
334 				line++;
335 				continue;
336 			}
337 
338 			/* Default name. */
339 			if (strlen(s->name) == 0) {
340 				if (strlen(plex) == 0) {
341 					sdname = find_name("gvinumsubdisk.p",
342 					    GV_TYPE_SD, GV_MAXSDNAME);
343 					snprintf(s->name, sizeof(s->name),
344 					    "%s.s%d", sdname, undeffd++);
345 					free(sdname);
346 				} else {
347 					snprintf(s->name, sizeof(s->name),
348 					    "%s.s%d",plex, sd_in_plex++);
349 				}
350 			}
351 
352 			/* Default plex. */
353 			if (strlen(s->plex) == 0)
354 				snprintf(s->plex, sizeof(s->plex), "%s", plex);
355 
356 			snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
357 			gctl_ro_param(req, buf1, sizeof(*s), s);
358 			subdisks++;
359 
360 		/* Subdisk definition. */
361 		} else if (!strcmp(token[0], "drive")) {
362 			d = gv_new_drive(tokens, token);
363 			if (d == NULL) {
364 				warnx("line %d: invalid drive definition:",
365 				    line);
366 				warnx("line %d: '%s'", line, original);
367 				errors++;
368 				line++;
369 				continue;
370 			}
371 
372 			snprintf(buf1, sizeof(buf1), "drive%d", drives);
373 			gctl_ro_param(req, buf1, sizeof(*d), d);
374 			drives++;
375 
376 		/* Everything else is bogus. */
377 		} else {
378 			warnx("line %d: invalid definition:", line);
379 			warnx("line %d: '%s'", line, original);
380 			errors++;
381 		}
382 		line++;
383 	}
384 
385 	fclose(tmp);
386 	unlink(tmpfile);
387 
388 	if (!errors && (volumes || plexes || subdisks || drives)) {
389 		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
390 		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
391 		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
392 		gctl_ro_param(req, "drives", sizeof(int), &drives);
393 		errstr = gctl_issue(req);
394 		if (errstr != NULL)
395 			warnx("create failed: %s", errstr);
396 	}
397 	gctl_free(req);
398 }
399 
400 /* Create a concatenated volume. */
401 void
402 gvinum_concat(int argc, char **argv)
403 {
404 
405 	if (argc < 2) {
406 		warnx("usage:\tconcat [-fv] [-n name] drives\n");
407 		return;
408 	}
409 	create_volume(argc, argv, "concat");
410 }
411 
412 
413 /* Create a drive quick and dirty. */
414 char *
415 create_drive(char *device)
416 {
417 	struct gv_drive *d;
418 	struct gctl_req *req;
419 	const char *errstr;
420 	char *drivename, *dname;
421 	int drives, i, flags, volumes, subdisks, plexes;
422 
423 	flags = plexes = subdisks = volumes = 0;
424 	drives = 1;
425 	dname = NULL;
426 
427 	drivename = find_drive(device);
428 	if (drivename == NULL)
429 		return (NULL);
430 
431 	req = gctl_get_handle();
432 	gctl_ro_param(req, "class", -1, "VINUM");
433 	gctl_ro_param(req, "verb", -1, "create");
434 	d = gv_alloc_drive();
435 	if (d == NULL)
436 		err(1, "unable to allocate for gv_drive object");
437 
438 	strlcpy(d->name, drivename, sizeof(d->name));
439 	strlcpy(d->device, device, sizeof(d->device));
440 	gctl_ro_param(req, "drive0", sizeof(*d), d);
441 	gctl_ro_param(req, "flags", sizeof(int), &flags);
442 	gctl_ro_param(req, "drives", sizeof(int), &drives);
443 	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
444 	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
445 	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
446 	errstr = gctl_issue(req);
447 	if (errstr != NULL) {
448 		warnx("error creating drive: %s", errstr);
449 		gctl_free(req);
450 		return (NULL);
451 	} else {
452 		gctl_free(req);
453 		/* XXX: This is needed because we have to make sure the drives
454 		 * are created before we return. */
455 		/* Loop until it's in the config. */
456 		for (i = 0; i < 100000; i++) {
457 			dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
458 			    GV_MAXDRIVENAME);
459 			/* If we got a different name, quit. */
460 			if (dname == NULL)
461 				continue;
462 			if (strcmp(dname, drivename)) {
463 				free(dname);
464 				return (drivename);
465 			}
466 			free(dname);
467 			dname = NULL;
468 			usleep(100000); /* Sleep for 0.1s */
469 		}
470 	}
471 	gctl_free(req);
472 	return (drivename);
473 }
474 
475 /*
476  * General routine for creating a volume. Mainly for use by concat, mirror,
477  * raid5 and stripe commands.
478  */
479 void
480 create_volume(int argc, char **argv, char *verb)
481 {
482 	struct gctl_req *req;
483 	const char *errstr;
484 	char buf[BUFSIZ], *drivename, *volname;
485 	int drives, flags, i;
486 	off_t stripesize;
487 
488 	flags = 0;
489 	drives = 0;
490 	volname = NULL;
491 	stripesize = 262144;
492 
493 	/* XXX: Should we check for argument length? */
494 
495 	req = gctl_get_handle();
496 	gctl_ro_param(req, "class", -1, "VINUM");
497 
498 	for (i = 1; i < argc; i++) {
499 		if (!strcmp(argv[i], "-f")) {
500 			flags |= GV_FLAG_F;
501 		} else if (!strcmp(argv[i], "-n")) {
502 			volname = argv[++i];
503 		} else if (!strcmp(argv[i], "-v")) {
504 			flags |= GV_FLAG_V;
505 		} else if (!strcmp(argv[i], "-s")) {
506 			flags |= GV_FLAG_S;
507 			if (!strcmp(verb, "raid5"))
508 				stripesize = gv_sizespec(argv[++i]);
509 		} else {
510 			/* Assume it's a drive. */
511 			snprintf(buf, sizeof(buf), "drive%d", drives++);
512 
513 			/* First we create the drive. */
514 			drivename = create_drive(argv[i]);
515 			if (drivename == NULL)
516 				goto bad;
517 			/* Then we add it to the request. */
518 			gctl_ro_param(req, buf, -1, drivename);
519 		}
520 	}
521 
522 	gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
523 
524 	/* Find a free volume name. */
525 	if (volname == NULL)
526 		volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
527 
528 	/* Then we send a request to actually create the volumes. */
529 	gctl_ro_param(req, "verb", -1, verb);
530 	gctl_ro_param(req, "flags", sizeof(int), &flags);
531 	gctl_ro_param(req, "drives", sizeof(int), &drives);
532 	gctl_ro_param(req, "name", -1, volname);
533 	errstr = gctl_issue(req);
534 	if (errstr != NULL)
535 		warnx("creating %s volume failed: %s", verb, errstr);
536 bad:
537 	gctl_free(req);
538 }
539 
540 /* Parse a line of the config, return the word after <pattern>. */
541 char *
542 find_pattern(char *line, char *pattern)
543 {
544 	char *ptr;
545 
546 	ptr = strsep(&line, " ");
547 	while (ptr != NULL) {
548 		if (!strcmp(ptr, pattern)) {
549 			/* Return the next. */
550 			ptr = strsep(&line, " ");
551 			return (ptr);
552 		}
553 		ptr = strsep(&line, " ");
554 	}
555 	return (NULL);
556 }
557 
558 /* Find a free name for an object given a a prefix. */
559 char *
560 find_name(const char *prefix, int type, int namelen)
561 {
562 	struct gctl_req *req;
563 	char comment[1], buf[GV_CFG_LEN - 1], *name, *sname, *ptr;
564 	const char *errstr;
565 	int i, n, begin, len, conflict;
566 	char line[1024];
567 
568 	comment[0] = '\0';
569 
570 	/* Find a name. Fetch out configuration first. */
571 	req = gctl_get_handle();
572 	gctl_ro_param(req, "class", -1, "VINUM");
573 	gctl_ro_param(req, "verb", -1, "getconfig");
574 	gctl_ro_param(req, "comment", -1, comment);
575 	gctl_rw_param(req, "config", sizeof(buf), buf);
576 	errstr = gctl_issue(req);
577 	if (errstr != NULL) {
578 		warnx("can't get configuration: %s", errstr);
579 		return (NULL);
580 	}
581 	gctl_free(req);
582 
583 	begin = 0;
584 	len = strlen(buf);
585 	i = 0;
586 	sname = malloc(namelen + 1);
587 
588 	/* XXX: Max object setting? */
589 	for (n = 0; n < 10000; n++) {
590 		snprintf(sname, namelen, "%s%d", prefix, n);
591 		conflict = 0;
592 		begin = 0;
593 		/* Loop through the configuration line by line. */
594 		for (i = 0; i < len; i++) {
595 			if (buf[i] == '\n' || buf[i] == '\0') {
596 				ptr = buf + begin;
597 				strlcpy(line, ptr, (i - begin) + 1);
598 				begin = i + 1;
599 				switch (type) {
600 				case GV_TYPE_DRIVE:
601 					name = find_pattern(line, "drive");
602 					break;
603 				case GV_TYPE_VOL:
604 					name = find_pattern(line, "volume");
605 					break;
606 				case GV_TYPE_PLEX:
607 				case GV_TYPE_SD:
608 					name = find_pattern(line, "name");
609 					break;
610 				default:
611 					printf("Invalid type given\n");
612 					continue;
613 				}
614 				if (name == NULL)
615 					continue;
616 				if (!strcmp(sname, name)) {
617 					conflict = 1;
618 					/* XXX: Could quit the loop earlier. */
619 				}
620 			}
621 		}
622 		if (!conflict)
623 			return (sname);
624 	}
625 	free(sname);
626 	return (NULL);
627 }
628 
629 char *
630 find_drive(const char *device)
631 {
632 
633 	/* Strip possible /dev/ in front. */
634 	if (strncmp(device, "/dev/", 5) == 0)
635 		device += 5;
636 	return (find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME));
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(argv[2]);
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 	if (strncmp(argv[2], "/dev/", 5) == 0)
1287 		strlcpy(d->device, (argv[2] + 5), sizeof(d->device));
1288 	else
1289 		strlcpy(d->device, argv[2], sizeof(d->device));
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, buf);
1432 }
1433