xref: /freebsd/sbin/gvinum/gvinum.c (revision 3642298923e528d795e3a30ec165d2b469e28b40)
1 /*
2  *  Copyright (c) 2004 Lukas Ertl
3  *  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 #include <sys/param.h>
30 #include <sys/linker.h>
31 #include <sys/lock.h>
32 #include <sys/module.h>
33 #include <sys/mutex.h>
34 #include <sys/queue.h>
35 #include <sys/utsname.h>
36 
37 #include <geom/vinum/geom_vinum_var.h>
38 #include <geom/vinum/geom_vinum_share.h>
39 
40 #include <ctype.h>
41 #include <err.h>
42 #include <libgeom.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <paths.h>
47 #include <readline/readline.h>
48 #include <readline/history.h>
49 #include <unistd.h>
50 
51 #include "gvinum.h"
52 
53 void	gvinum_create(int, char **);
54 void	gvinum_help(void);
55 void	gvinum_list(int, char **);
56 void	gvinum_parityop(int, char **, int);
57 void	gvinum_printconfig(int, char **);
58 void	gvinum_rm(int, char **);
59 void	gvinum_saveconfig(void);
60 void	gvinum_setstate(int, char **);
61 void	gvinum_start(int, char **);
62 void	gvinum_stop(int, char **);
63 void	parseline(int, char **);
64 void	printconfig(FILE *, char *);
65 
66 int
67 main(int argc, char **argv)
68 {
69 	int line, tokens;
70 	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
71 
72 	/* Load the module if necessary. */
73 	if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
74 		err(1, GVINUMMOD ": Kernel module not available");
75 
76 	/* Arguments given on the command line. */
77 	if (argc > 1) {
78 		argc--;
79 		argv++;
80 		parseline(argc, argv);
81 
82 	/* Interactive mode. */
83 	} else {
84 		for (;;) {
85 			inputline = readline("gvinum -> ");
86 			if (inputline == NULL) {
87 				if (ferror(stdin)) {
88 					err(1, "can't read input");
89 				} else {
90 					printf("\n");
91 					exit(0);
92 				}
93 			} else if (*inputline) {
94 				add_history(inputline);
95 				strcpy(buffer, inputline);
96 				free(inputline);
97 				line++;		    /* count the lines */
98 				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
99 				if (tokens)
100 					parseline(tokens, token);
101 			}
102 		}
103 	}
104 	exit(0);
105 }
106 
107 void
108 gvinum_create(int argc, char **argv)
109 {
110 	struct gctl_req *req;
111 	struct gv_drive *d;
112 	struct gv_plex *p;
113 	struct gv_sd *s;
114 	struct gv_volume *v;
115 	FILE *tmp;
116 	int drives, errors, fd, line, plexes, plex_in_volume;
117 	int sd_in_plex, status, subdisks, tokens, volumes;
118 	const char *errstr;
119 	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed;
120 	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
121 	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
122 
123 	if (argc == 2) {
124 		if ((tmp = fopen(argv[1], "r")) == NULL) {
125 			warn("can't open '%s' for reading", argv[1]);
126 			return;
127 		}
128 	} else {
129 		snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
130 
131 		if ((fd = mkstemp(tmpfile)) == -1) {
132 			warn("temporary file not accessible");
133 			return;
134 		}
135 		if ((tmp = fdopen(fd, "w")) == NULL) {
136 			warn("can't open '%s' for writing", tmpfile);
137 			return;
138 		}
139 		printconfig(tmp, "# ");
140 		fclose(tmp);
141 
142 		ed = getenv("EDITOR");
143 		if (ed == NULL)
144 			ed = _PATH_VI;
145 
146 		snprintf(commandline, sizeof(commandline), "%s %s", ed,
147 		    tmpfile);
148 		status = system(commandline);
149 		if (status != 0) {
150 			warn("couldn't exec %s; status: %d", ed, status);
151 			return;
152 		}
153 
154 		if ((tmp = fopen(tmpfile, "r")) == NULL) {
155 			warn("can't open '%s' for reading", tmpfile);
156 			return;
157 		}
158 	}
159 
160 	req = gctl_get_handle();
161 	gctl_ro_param(req, "class", -1, "VINUM");
162 	gctl_ro_param(req, "verb", -1, "create");
163 
164 	drives = volumes = plexes = subdisks = 0;
165 	plex_in_volume = sd_in_plex = 0;
166 	errors = 0;
167 	line = 1;
168 	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
169 
170 		/* Skip empty lines and comments. */
171 		if (*buf == '\0' || *buf == '#') {
172 			line++;
173 			continue;
174 		}
175 
176 		/* Kill off the newline. */
177 		buf[strlen(buf) - 1] = '\0';
178 
179 		/*
180 		 * Copy the original input line in case we need it for error
181 		 * output.
182 		 */
183 		strncpy(original, buf, sizeof(buf));
184 
185 		tokens = gv_tokenize(buf, token, GV_MAXARGS);
186 		if (tokens <= 0) {
187 			line++;
188 			continue;
189 		}
190 
191 		/* Volume definition. */
192 		if (!strcmp(token[0], "volume")) {
193 			v = gv_new_volume(tokens, token);
194 			if (v == NULL) {
195 				warnx("line %d: invalid volume definition",
196 				    line);
197 				warnx("line %d: '%s'", line, original);
198 				errors++;
199 				line++;
200 				continue;
201 			}
202 
203 			/* Reset plex count for this volume. */
204 			plex_in_volume = 0;
205 
206 			/*
207 			 * Set default volume name for following plex
208 			 * definitions.
209 			 */
210 			strncpy(volume, v->name, sizeof(volume));
211 
212 			snprintf(buf1, sizeof(buf1), "volume%d", volumes);
213 			gctl_ro_param(req, buf1, sizeof(*v), v);
214 			volumes++;
215 
216 		/* Plex definition. */
217 		} else if (!strcmp(token[0], "plex")) {
218 			p = gv_new_plex(tokens, token);
219 			if (p == NULL) {
220 				warnx("line %d: invalid plex definition", line);
221 				warnx("line %d: '%s'", line, original);
222 				errors++;
223 				line++;
224 				continue;
225 			}
226 
227 			/* Reset subdisk count for this plex. */
228 			sd_in_plex = 0;
229 
230 			/* Default name. */
231 			if (strlen(p->name) == 0) {
232 				snprintf(p->name, GV_MAXPLEXNAME, "%s.p%d",
233 				    volume, plex_in_volume++);
234 			}
235 
236 			/* Default volume. */
237 			if (strlen(p->volume) == 0) {
238 				snprintf(p->volume, GV_MAXVOLNAME, "%s",
239 				    volume);
240 			}
241 
242 			/*
243 			 * Set default plex name for following subdisk
244 			 * definitions.
245 			 */
246 			strncpy(plex, p->name, GV_MAXPLEXNAME);
247 
248 			snprintf(buf1, sizeof(buf1), "plex%d", plexes);
249 			gctl_ro_param(req, buf1, sizeof(*p), p);
250 			plexes++;
251 
252 		/* Subdisk definition. */
253 		} else if (!strcmp(token[0], "sd")) {
254 			s = gv_new_sd(tokens, token);
255 			if (s == NULL) {
256 				warnx("line %d: invalid subdisk "
257 				    "definition:", line);
258 				warnx("line %d: '%s'", line, original);
259 				errors++;
260 				line++;
261 				continue;
262 			}
263 
264 			/* Default name. */
265 			if (strlen(s->name) == 0) {
266 				snprintf(s->name, GV_MAXSDNAME, "%s.s%d",
267 				    plex, sd_in_plex++);
268 			}
269 
270 			/* Default plex. */
271 			if (strlen(s->plex) == 0)
272 				snprintf(s->plex, GV_MAXPLEXNAME, "%s", plex);
273 
274 			snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
275 			gctl_ro_param(req, buf1, sizeof(*s), s);
276 			subdisks++;
277 
278 		/* Subdisk definition. */
279 		} else if (!strcmp(token[0], "drive")) {
280 			d = gv_new_drive(tokens, token);
281 			if (d == NULL) {
282 				warnx("line %d: invalid drive definition:",
283 				    line);
284 				warnx("line %d: '%s'", line, original);
285 				errors++;
286 				line++;
287 				continue;
288 			}
289 
290 			snprintf(buf1, sizeof(buf1), "drive%d", drives);
291 			gctl_ro_param(req, buf1, sizeof(*d), d);
292 			drives++;
293 
294 		/* Everything else is bogus. */
295 		} else {
296 			warnx("line %d: invalid definition:", line);
297 			warnx("line %d: '%s'", line, original);
298 			errors++;
299 		}
300 		line++;
301 	}
302 
303 	fclose(tmp);
304 	unlink(tmpfile);
305 
306 	if (!errors && (volumes || plexes || subdisks || drives)) {
307 		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
308 		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
309 		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
310 		gctl_ro_param(req, "drives", sizeof(int), &drives);
311 		errstr = gctl_issue(req);
312 		if (errstr != NULL)
313 			warnx("create failed: %s", errstr);
314 	}
315 	gctl_free(req);
316 	gvinum_list(0, NULL);
317 }
318 
319 void
320 gvinum_help(void)
321 {
322 	printf("COMMANDS\n"
323 	    "attach plex volume [rename]\n"
324 	    "attach subdisk plex [offset] [rename]\n"
325 	    "        Attach a plex to a volume, or a subdisk to a plex.\n"
326 	    "checkparity plex [-f] [-v]\n"
327 	    "        Check the parity blocks of a RAID-4 or RAID-5 plex.\n"
328 	    "concat [-f] [-n name] [-v] drives\n"
329 	    "        Create a concatenated volume from the specified drives.\n"
330 	    "create [-f] description-file\n"
331 	    "        Create a volume as described in description-file.\n"
332 	    "detach [-f] [plex | subdisk]\n"
333 	    "        Detach a plex or subdisk from the volume or plex to"
334 	    "which it is\n"
335 	    "        attached.\n"
336 	    "dumpconfig [drive ...]\n"
337 	    "        List the configuration information stored on the"
338 	    " specified\n"
339 	    "        drives, or all drives in the system if no drive names"
340 	    " are speci-\n"
341 	    "        fied.\n"
342 	    "info [-v] [-V]\n"
343 	    "        List information about volume manager state.\n"
344 	    "init [-S size] [-w] plex | subdisk\n"
345 	    "        Initialize the contents of a subdisk or all the subdisks"
346 	    " of a\n"
347 	    "        plex to all zeros.\n"
348 	    "label volume\n"
349 	    "        Create a volume label.\n"
350 	    "l | list [-r] [-s] [-v] [-V] [volume | plex | subdisk]\n"
351 	    "        List information about specified objects.\n"
352 	    "ld [-r] [-s] [-v] [-V] [volume]\n"
353 	    "        List information about drives.\n"
354 	    "ls [-r] [-s] [-v] [-V] [subdisk]\n"
355 	    "        List information about subdisks.\n"
356 	    "lp [-r] [-s] [-v] [-V] [plex]\n"
357 	    "        List information about plexes.\n"
358 	    "lv [-r] [-s] [-v] [-V] [volume]\n"
359 	    "        List information about volumes.\n"
360 	    "mirror [-f] [-n name] [-s] [-v] drives\n"
361 	    "        Create a mirrored volume from the specified drives.\n"
362 	    "move | mv -f drive object ...\n"
363 	    "        Move the object(s) to the specified drive.\n"
364 	    "printconfig [file]\n"
365 	    "        Write a copy of the current configuration to file.\n"
366 	    "quit    Exit the vinum program when running in interactive mode."
367 	    "  Nor-\n"
368 	    "        mally this would be done by entering the EOF character.\n"
369 	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
370 	    "        Change the name of the specified object.\n"
371 	    "rebuildparity plex [-f] [-v] [-V]\n"
372 	    "        Rebuild the parity blocks of a RAID-4 or RAID-5 plex.\n"
373 	    "resetconfig\n"
374 	    "        Reset the complete vinum configuration.\n"
375 	    "rm [-f] [-r] volume | plex | subdisk\n"
376 	    "        Remove an object.\n"
377 	    "saveconfig\n"
378 	    "        Save vinum configuration to disk after configuration"
379 	    " failures.\n"
380 	    "setstate state [volume | plex | subdisk | drive]\n"
381 	    "        Set state without influencing other objects, for"
382 	    " diagnostic pur-\n"
383 	    "        poses only.\n"
384 	    "start [-i interval] [-S size] [-w] volume | plex | subdisk\n"
385 	    "        Allow the system to access the objects.\n"
386 	    "stop [-f] [volume | plex | subdisk]\n"
387 	    "        Terminate access to the objects, or stop vinum if no"
388 	    " parameters\n"
389 	    "        are specified.\n"
390 	    "stripe [-f] [-n name] [-v] drives\n"
391 	    "        Create a striped volume from the specified drives.\n"
392 	);
393 
394 	return;
395 }
396 
397 void
398 gvinum_setstate(int argc, char **argv)
399 {
400 	struct gctl_req *req;
401 	int flags, i;
402 	const char *errstr;
403 
404 	flags = 0;
405 
406 	optreset = 1;
407 	optind = 1;
408 
409 	while ((i = getopt(argc, argv, "f")) != -1) {
410 		switch (i) {
411 		case 'f':
412 			flags |= GV_FLAG_F;
413 			break;
414 		case '?':
415 		default:
416 			warn("invalid flag: %c", i);
417 			return;
418 		}
419 	}
420 
421 	argc -= optind;
422 	argv += optind;
423 
424 	if (argc != 2) {
425 		warnx("usage: setstate [-f] <state> <obj>");
426 		return;
427 	}
428 
429 	/*
430 	 * XXX: This hack is needed to avoid tripping over (now) invalid
431 	 * 'classic' vinum states and will go away later.
432 	 */
433 	if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
434 	    strcmp(argv[0], "stale")) {
435 		warnx("invalid state '%s'", argv[0]);
436 		return;
437 	}
438 
439 	req = gctl_get_handle();
440 	gctl_ro_param(req, "class", -1, "VINUM");
441 	gctl_ro_param(req, "verb", -1, "setstate");
442 	gctl_ro_param(req, "state", -1, argv[0]);
443 	gctl_ro_param(req, "object", -1, argv[1]);
444 	gctl_ro_param(req, "flags", sizeof(int), &flags);
445 
446 	errstr = gctl_issue(req);
447 	if (errstr != NULL)
448 		warnx("%s", errstr);
449 	gctl_free(req);
450 }
451 
452 void
453 gvinum_list(int argc, char **argv)
454 {
455 	struct gctl_req *req;
456 	int flags, i, j;
457 	const char *errstr;
458 	char buf[20], *cmd, config[GV_CFG_LEN + 1];
459 
460 	flags = 0;
461 	cmd = "list";
462 
463 	if (argc) {
464 		optreset = 1;
465 		optind = 1;
466 		cmd = argv[0];
467 		while ((j = getopt(argc, argv, "rsvV")) != -1) {
468 			switch (j) {
469 			case 'r':
470 				flags |= GV_FLAG_R;
471 				break;
472 			case 's':
473 				flags |= GV_FLAG_S;
474 				break;
475 			case 'v':
476 				flags |= GV_FLAG_V;
477 				break;
478 			case 'V':
479 				flags |= GV_FLAG_V;
480 				flags |= GV_FLAG_VV;
481 				break;
482 			case '?':
483 			default:
484 				return;
485 			}
486 		}
487 		argc -= optind;
488 		argv += optind;
489 
490 	}
491 
492 	req = gctl_get_handle();
493 	gctl_ro_param(req, "class", -1, "VINUM");
494 	gctl_ro_param(req, "verb", -1, "list");
495 	gctl_ro_param(req, "cmd", -1, cmd);
496 	gctl_ro_param(req, "argc", sizeof(int), &argc);
497 	gctl_ro_param(req, "flags", sizeof(int), &flags);
498 	gctl_rw_param(req, "config", sizeof(config), config);
499 	if (argc) {
500 		for (i = 0; i < argc; i++) {
501 			snprintf(buf, sizeof(buf), "argv%d", i);
502 			gctl_ro_param(req, buf, -1, argv[i]);
503 		}
504 	}
505 	errstr = gctl_issue(req);
506 	if (errstr != NULL) {
507 		warnx("can't get configuration: %s", errstr);
508 		gctl_free(req);
509 		return;
510 	}
511 
512 	printf("%s", config);
513 	gctl_free(req);
514 	return;
515 }
516 
517 void
518 gvinum_printconfig(int argc, char **argv)
519 {
520 	printconfig(stdout, "");
521 }
522 
523 void
524 gvinum_parityop(int argc, char **argv, int rebuild)
525 {
526 	struct gctl_req *req;
527 	int flags, i, rv;
528 	off_t offset;
529 	const char *errstr;
530 	char *op, *msg;
531 
532 	if (rebuild) {
533 		op = "rebuildparity";
534 		msg = "Rebuilding";
535 	} else {
536 		op = "checkparity";
537 		msg = "Checking";
538 	}
539 
540 	optreset = 1;
541 	optind = 1;
542 	flags = 0;
543 	while ((i = getopt(argc, argv, "fv")) != -1) {
544 		switch (i) {
545 		case 'f':
546 			flags |= GV_FLAG_F;
547 			break;
548 		case 'v':
549 			flags |= GV_FLAG_V;
550 			break;
551 		case '?':
552 		default:
553 			warnx("invalid flag '%c'", i);
554 			return;
555 		}
556 	}
557 	argc -= optind;
558 	argv += optind;
559 
560 	if (argc != 1) {
561 		warn("usage: %s [-f] [-v] <plex>", op);
562 		return;
563 	}
564 
565 	do {
566 		rv = 0;
567 		req = gctl_get_handle();
568 		gctl_ro_param(req, "class", -1, "VINUM");
569 		gctl_ro_param(req, "verb", -1, "parityop");
570 		gctl_ro_param(req, "flags", sizeof(int), &flags);
571 		gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
572 		gctl_rw_param(req, "rv", sizeof(int), &rv);
573 		gctl_rw_param(req, "offset", sizeof(off_t), &offset);
574 		gctl_ro_param(req, "plex", -1, argv[0]);
575 		errstr = gctl_issue(req);
576 		if (errstr) {
577 			warnx("%s\n", errstr);
578 			gctl_free(req);
579 			break;
580 		}
581 		gctl_free(req);
582 		if (flags & GV_FLAG_V) {
583 			printf("\r%s at %s ... ", msg,
584 			    gv_roughlength(offset, 1));
585 		}
586 		if (rv == 1) {
587 			printf("Parity incorrect at offset 0x%jx\n",
588 			    (intmax_t)offset);
589 			if (!rebuild)
590 				break;
591 		}
592 		fflush(stdout);
593 
594 		/* Clear the -f flag. */
595 		flags &= ~GV_FLAG_F;
596 	} while (rv >= 0);
597 
598 	if ((rv == 2) && (flags & GV_FLAG_V)) {
599 		if (rebuild)
600 			printf("Rebuilt parity on %s\n", argv[0]);
601 		else
602 			printf("%s has correct parity\n", argv[0]);
603 	}
604 }
605 
606 void
607 gvinum_rm(int argc, char **argv)
608 {
609 	struct gctl_req *req;
610 	int flags, i, j;
611 	const char *errstr;
612 	char buf[20], *cmd;
613 
614 	cmd = argv[0];
615 	flags = 0;
616 	optreset = 1;
617 	optind = 1;
618 	while ((j = getopt(argc, argv, "r")) != -1) {
619 		switch (j) {
620 		case 'r':
621 			flags |= GV_FLAG_R;
622 			break;
623 		case '?':
624 		default:
625 			return;
626 		}
627 	}
628 	argc -= optind;
629 	argv += optind;
630 
631 	req = gctl_get_handle();
632 	gctl_ro_param(req, "class", -1, "VINUM");
633 	gctl_ro_param(req, "verb", -1, "remove");
634 	gctl_ro_param(req, "argc", sizeof(int), &argc);
635 	gctl_ro_param(req, "flags", sizeof(int), &flags);
636 	if (argc) {
637 		for (i = 0; i < argc; i++) {
638 			snprintf(buf, sizeof(buf), "argv%d", i);
639 			gctl_ro_param(req, buf, -1, argv[i]);
640 		}
641 	}
642 	errstr = gctl_issue(req);
643 	if (errstr != NULL) {
644 		warnx("can't remove: %s", errstr);
645 		gctl_free(req);
646 		return;
647 	}
648 	gctl_free(req);
649 	gvinum_list(0, NULL);
650 }
651 
652 void
653 gvinum_saveconfig(void)
654 {
655 	struct gctl_req *req;
656 	const char *errstr;
657 
658 	req = gctl_get_handle();
659 	gctl_ro_param(req, "class", -1, "VINUM");
660 	gctl_ro_param(req, "verb", -1, "saveconfig");
661 	errstr = gctl_issue(req);
662 	if (errstr != NULL)
663 		warnx("can't save configuration: %s", errstr);
664 	gctl_free(req);
665 }
666 
667 void
668 gvinum_start(int argc, char **argv)
669 {
670 	struct gctl_req *req;
671 	int i, initsize, j;
672 	const char *errstr;
673 	char buf[20];
674 
675 	/* 'start' with no arguments is a no-op. */
676 	if (argc == 1)
677 		return;
678 
679 	initsize = 0;
680 
681 	optreset = 1;
682 	optind = 1;
683 	while ((j = getopt(argc, argv, "S")) != -1) {
684 		switch (j) {
685 		case 'S':
686 			initsize = atoi(optarg);
687 			break;
688 		case '?':
689 		default:
690 			return;
691 		}
692 	}
693 	argc -= optind;
694 	argv += optind;
695 
696 	if (!initsize)
697 		initsize = 512;
698 
699 	req = gctl_get_handle();
700 	gctl_ro_param(req, "class", -1, "VINUM");
701 	gctl_ro_param(req, "verb", -1, "start");
702 	gctl_ro_param(req, "argc", sizeof(int), &argc);
703 	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
704 	if (argc) {
705 		for (i = 0; i < argc; i++) {
706 			snprintf(buf, sizeof(buf), "argv%d", i);
707 			gctl_ro_param(req, buf, -1, argv[i]);
708 		}
709 	}
710 	errstr = gctl_issue(req);
711 	if (errstr != NULL) {
712 		warnx("can't start: %s", errstr);
713 		gctl_free(req);
714 		return;
715 	}
716 
717 	gctl_free(req);
718 	gvinum_list(0, NULL);
719 }
720 
721 void
722 gvinum_stop(int argc, char **argv)
723 {
724 	int fileid;
725 
726 	fileid = kldfind(GVINUMMOD);
727 	if (fileid == -1) {
728 		warn("cannot find " GVINUMMOD);
729 		return;
730 	}
731 	if (kldunload(fileid) != 0) {
732 		warn("cannot unload " GVINUMMOD);
733 		return;
734 	}
735 
736 	warnx(GVINUMMOD " unloaded");
737 	exit(0);
738 }
739 
740 void
741 parseline(int argc, char **argv)
742 {
743 	if (argc <= 0)
744 		return;
745 
746 	if (!strcmp(argv[0], "create"))
747 		gvinum_create(argc, argv);
748 	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
749 		exit(0);
750 	else if (!strcmp(argv[0], "help"))
751 		gvinum_help();
752 	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
753 		gvinum_list(argc, argv);
754 	else if (!strcmp(argv[0], "ld"))
755 		gvinum_list(argc, argv);
756 	else if (!strcmp(argv[0], "lp"))
757 		gvinum_list(argc, argv);
758 	else if (!strcmp(argv[0], "ls"))
759 		gvinum_list(argc, argv);
760 	else if (!strcmp(argv[0], "lv"))
761 		gvinum_list(argc, argv);
762 	else if (!strcmp(argv[0], "printconfig"))
763 		gvinum_printconfig(argc, argv);
764 	else if (!strcmp(argv[0], "rm"))
765 		gvinum_rm(argc, argv);
766 	else if (!strcmp(argv[0], "saveconfig"))
767 		gvinum_saveconfig();
768 	else if (!strcmp(argv[0], "setstate"))
769 		gvinum_setstate(argc, argv);
770 	else if (!strcmp(argv[0], "start"))
771 		gvinum_start(argc, argv);
772 	else if (!strcmp(argv[0], "stop"))
773 		gvinum_stop(argc, argv);
774 	else if (!strcmp(argv[0], "checkparity"))
775 		gvinum_parityop(argc, argv, 0);
776 	else if (!strcmp(argv[0], "rebuildparity"))
777 		gvinum_parityop(argc, argv, 1);
778 	else
779 		printf("unknown command '%s'\n", argv[0]);
780 
781 	return;
782 }
783 
784 /*
785  * The guts of printconfig.  This is called from gvinum_printconfig and from
786  * gvinum_create when called without an argument, in order to give the user
787  * something to edit.
788  */
789 void
790 printconfig(FILE *of, char *comment)
791 {
792 	struct gctl_req *req;
793 	struct utsname uname_s;
794 	const char *errstr;
795 	time_t now;
796 	char buf[GV_CFG_LEN + 1];
797 
798 	uname(&uname_s);
799 	time(&now);
800 
801 	req = gctl_get_handle();
802 	gctl_ro_param(req, "class", -1, "VINUM");
803 	gctl_ro_param(req, "verb", -1, "getconfig");
804 	gctl_ro_param(req, "comment", -1, comment);
805 	gctl_rw_param(req, "config", sizeof(buf), buf);
806 	errstr = gctl_issue(req);
807 	if (errstr != NULL) {
808 		warnx("can't get configuration: %s", errstr);
809 		return;
810 	}
811 	gctl_free(req);
812 
813 	fprintf(of, "# Vinum configuration of %s, saved at %s",
814 	    uname_s.nodename,
815 	    ctime(&now));
816 
817 	if (*comment != '\0')
818 	    fprintf(of, "# Current configuration:\n");
819 
820 	fprintf(of, buf);
821 }
822