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