gvinum.c revision 204665
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: head/sbin/gvinum/gvinum.c 204665 2010-03-03 21:27:54Z lulf $
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
60void	gvinum_attach(int, char **);
61void	gvinum_concat(int, char **);
62void	gvinum_create(int, char **);
63void	gvinum_detach(int, char **);
64void	gvinum_grow(int, char **);
65void	gvinum_help(void);
66void	gvinum_list(int, char **);
67void	gvinum_move(int, char **);
68void	gvinum_mirror(int, char **);
69void	gvinum_parityop(int, char **, int);
70void	gvinum_printconfig(int, char **);
71void	gvinum_raid5(int, char **);
72void	gvinum_rename(int, char **);
73void	gvinum_resetconfig(void);
74void	gvinum_rm(int, char **);
75void	gvinum_saveconfig(void);
76void	gvinum_setstate(int, char **);
77void	gvinum_start(int, char **);
78void	gvinum_stop(int, char **);
79void	gvinum_stripe(int, char **);
80void	parseline(int, char **);
81void	printconfig(FILE *, char *);
82
83char	*create_drive(char *);
84void	 create_volume(int, char **, char *);
85char	*find_name(const char *, int, int);
86char	*find_pattern(char *, char *);
87void	 copy_device(struct gv_drive *, const char *);
88#define find_drive() find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
89
90int
91main(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. */
132void
133gvinum_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
169void
170gvinum_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. */
402void
403gvinum_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. */
415char *
416create_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 */
480void
481create_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);
537bad:
538	gctl_free(req);
539}
540
541/* Parse a line of the config, return the word after <pattern>. */
542char *
543find_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. */
560char *
561find_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
630void
631copy_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. */
640void
641gvinum_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
678void
679gvinum_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
739void
740gvinum_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
794void
795gvinum_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. */
860void
861gvinum_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 [...]' */
872void
873gvinum_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
926void
927gvinum_printconfig(int argc, char **argv)
928{
929	printconfig(stdout, "");
930}
931
932void
933gvinum_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. */
987void
988gvinum_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
999void
1000gvinum_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
1052void
1053gvinum_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
1100void
1101gvinum_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
1135void
1136gvinum_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
1150void
1151gvinum_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
1203void
1204gvinum_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. */
1237void
1238gvinum_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. */
1249void
1250gvinum_grow(int argc, char **argv)
1251{
1252	struct gctl_req *req;
1253	char *drive, *sdname;
1254	char sdprefix[GV_MAXSDNAME];
1255	struct gv_drive *d;
1256	struct gv_sd *s;
1257	const char *errstr;
1258	int drives, volumes, plexes, subdisks, flags;
1259
1260	drives = volumes = plexes = subdisks = 0;
1261	if (argc < 3) {
1262		warnx("usage:\tgrow plex drive\n");
1263		return;
1264	}
1265
1266	s = gv_alloc_sd();
1267	if (s == NULL) {
1268		warn("unable to create subdisk");
1269		return;
1270	}
1271	d = gv_alloc_drive();
1272	if (d == NULL) {
1273		warn("unable to create drive");
1274		free(s);
1275		return;
1276	}
1277	/* Lookup device and set an appropriate drive name. */
1278	drive = find_drive();
1279	if (drive == NULL) {
1280		warn("unable to find an appropriate drive name");
1281		free(s);
1282		free(d);
1283		return;
1284	}
1285	strlcpy(d->name, drive, sizeof(d->name));
1286	copy_device(d, argv[2]);
1287
1288	drives = 1;
1289
1290	/* We try to use the plex name as basis for the subdisk name. */
1291	snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1292	sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1293	if (sdname == NULL) {
1294		warn("unable to find an appropriate subdisk name");
1295		free(s);
1296		free(d);
1297		free(drive);
1298		return;
1299	}
1300	strlcpy(s->name, sdname, sizeof(s->name));
1301	free(sdname);
1302	strlcpy(s->plex, argv[1], sizeof(s->plex));
1303	strlcpy(s->drive, d->name, sizeof(s->drive));
1304	subdisks = 1;
1305
1306	req = gctl_get_handle();
1307	gctl_ro_param(req, "class", -1, "VINUM");
1308	gctl_ro_param(req, "verb", -1, "create");
1309	gctl_ro_param(req, "flags", sizeof(int), &flags);
1310	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1311	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1312	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1313	gctl_ro_param(req, "drives", sizeof(int), &drives);
1314	gctl_ro_param(req, "drive0", sizeof(*d), d);
1315	gctl_ro_param(req, "sd0", sizeof(*s), s);
1316	errstr = gctl_issue(req);
1317	free(drive);
1318	if (errstr != NULL) {
1319		warnx("unable to grow plex: %s", errstr);
1320		free(s);
1321		free(d);
1322		return;
1323	}
1324	gctl_free(req);
1325}
1326
1327void
1328parseline(int argc, char **argv)
1329{
1330	if (argc <= 0)
1331		return;
1332
1333	if (!strcmp(argv[0], "create"))
1334		gvinum_create(argc, argv);
1335	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1336		exit(0);
1337	else if (!strcmp(argv[0], "attach"))
1338		gvinum_attach(argc, argv);
1339	else if (!strcmp(argv[0], "detach"))
1340		gvinum_detach(argc, argv);
1341	else if (!strcmp(argv[0], "concat"))
1342		gvinum_concat(argc, argv);
1343	else if (!strcmp(argv[0], "grow"))
1344		gvinum_grow(argc, argv);
1345	else if (!strcmp(argv[0], "help"))
1346		gvinum_help();
1347	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1348		gvinum_list(argc, argv);
1349	else if (!strcmp(argv[0], "ld"))
1350		gvinum_list(argc, argv);
1351	else if (!strcmp(argv[0], "lp"))
1352		gvinum_list(argc, argv);
1353	else if (!strcmp(argv[0], "ls"))
1354		gvinum_list(argc, argv);
1355	else if (!strcmp(argv[0], "lv"))
1356		gvinum_list(argc, argv);
1357	else if (!strcmp(argv[0], "mirror"))
1358		gvinum_mirror(argc, argv);
1359	else if (!strcmp(argv[0], "move"))
1360		gvinum_move(argc, argv);
1361	else if (!strcmp(argv[0], "mv"))
1362		gvinum_move(argc, argv);
1363	else if (!strcmp(argv[0], "printconfig"))
1364		gvinum_printconfig(argc, argv);
1365	else if (!strcmp(argv[0], "raid5"))
1366		gvinum_raid5(argc, argv);
1367	else if (!strcmp(argv[0], "rename"))
1368		gvinum_rename(argc, argv);
1369	else if (!strcmp(argv[0], "resetconfig"))
1370		gvinum_resetconfig();
1371	else if (!strcmp(argv[0], "rm"))
1372		gvinum_rm(argc, argv);
1373	else if (!strcmp(argv[0], "saveconfig"))
1374		gvinum_saveconfig();
1375	else if (!strcmp(argv[0], "setstate"))
1376		gvinum_setstate(argc, argv);
1377	else if (!strcmp(argv[0], "start"))
1378		gvinum_start(argc, argv);
1379	else if (!strcmp(argv[0], "stop"))
1380		gvinum_stop(argc, argv);
1381	else if (!strcmp(argv[0], "stripe"))
1382		gvinum_stripe(argc, argv);
1383	else if (!strcmp(argv[0], "checkparity"))
1384		gvinum_parityop(argc, argv, 0);
1385	else if (!strcmp(argv[0], "rebuildparity"))
1386		gvinum_parityop(argc, argv, 1);
1387	else
1388		printf("unknown command '%s'\n", argv[0]);
1389
1390	return;
1391}
1392
1393/*
1394 * The guts of printconfig.  This is called from gvinum_printconfig and from
1395 * gvinum_create when called without an argument, in order to give the user
1396 * something to edit.
1397 */
1398void
1399printconfig(FILE *of, char *comment)
1400{
1401	struct gctl_req *req;
1402	struct utsname uname_s;
1403	const char *errstr;
1404	time_t now;
1405	char buf[GV_CFG_LEN + 1];
1406
1407	uname(&uname_s);
1408	time(&now);
1409
1410	req = gctl_get_handle();
1411	gctl_ro_param(req, "class", -1, "VINUM");
1412	gctl_ro_param(req, "verb", -1, "getconfig");
1413	gctl_ro_param(req, "comment", -1, comment);
1414	gctl_rw_param(req, "config", sizeof(buf), buf);
1415	errstr = gctl_issue(req);
1416	if (errstr != NULL) {
1417		warnx("can't get configuration: %s", errstr);
1418		return;
1419	}
1420	gctl_free(req);
1421
1422	fprintf(of, "# Vinum configuration of %s, saved at %s",
1423	    uname_s.nodename,
1424	    ctime(&now));
1425
1426	if (*comment != '\0')
1427	    fprintf(of, "# Current configuration:\n");
1428
1429	fprintf(of, buf);
1430}
1431