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 <string.h>
54#include <paths.h>
55#include <readline/readline.h>
56#include <readline/history.h>
57#include <unistd.h>
58
59#include "gvinum.h"
60
61void	gvinum_attach(int, char **);
62void	gvinum_concat(int, char **);
63void	gvinum_create(int, char **);
64void	gvinum_detach(int, char **);
65void	gvinum_grow(int, char **);
66void	gvinum_help(void);
67void	gvinum_list(int, char **);
68void	gvinum_move(int, char **);
69void	gvinum_mirror(int, char **);
70void	gvinum_parityop(int, char **, int);
71void	gvinum_printconfig(int, char **);
72void	gvinum_raid5(int, char **);
73void	gvinum_rename(int, char **);
74void	gvinum_resetconfig(void);
75void	gvinum_rm(int, char **);
76void	gvinum_saveconfig(void);
77void	gvinum_setstate(int, char **);
78void	gvinum_start(int, char **);
79void	gvinum_stop(int, char **);
80void	gvinum_stripe(int, char **);
81void	parseline(int, char **);
82void	printconfig(FILE *, char *);
83
84char	*create_drive(char *);
85void	 create_volume(int, char **, char *);
86char	*find_name(const char *, int, int);
87char	*find_pattern(char *, char *);
88void	 copy_device(struct gv_drive *, const char *);
89#define find_drive() find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
90
91int
92main(int argc, char **argv)
93{
94	int line, tokens;
95	char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
96
97	/* Load the module if necessary. */
98	if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
99		err(1, GVINUMMOD ": Kernel module not available");
100
101	/* Arguments given on the command line. */
102	if (argc > 1) {
103		argc--;
104		argv++;
105		parseline(argc, argv);
106
107	/* Interactive mode. */
108	} else {
109		for (;;) {
110			inputline = readline("gvinum -> ");
111			if (inputline == NULL) {
112				if (ferror(stdin)) {
113					err(1, "can't read input");
114				} else {
115					printf("\n");
116					exit(0);
117				}
118			} else if (*inputline) {
119				add_history(inputline);
120				strcpy(buffer, inputline);
121				free(inputline);
122				line++;		    /* count the lines */
123				tokens = gv_tokenize(buffer, token, GV_MAXARGS);
124				if (tokens)
125					parseline(tokens, token);
126			}
127		}
128	}
129	exit(0);
130}
131
132/* Attach a plex to a volume or a subdisk to a plex. */
133void
134gvinum_attach(int argc, char **argv)
135{
136	struct gctl_req *req;
137	const char *errstr;
138	int rename;
139	off_t offset;
140
141	rename = 0;
142	offset = -1;
143	if (argc < 3) {
144		warnx("usage:\tattach <subdisk> <plex> [rename] "
145		    "[<plexoffset>]\n"
146		    "\tattach <plex> <volume> [rename]");
147		return;
148	}
149	if (argc > 3) {
150		if (!strcmp(argv[3], "rename")) {
151			rename = 1;
152			if (argc == 5)
153				offset = strtol(argv[4], NULL, 0);
154		} else
155			offset = strtol(argv[3], NULL, 0);
156	}
157	req = gctl_get_handle();
158	gctl_ro_param(req, "class", -1, "VINUM");
159	gctl_ro_param(req, "verb", -1, "attach");
160	gctl_ro_param(req, "child", -1, argv[1]);
161	gctl_ro_param(req, "parent", -1, argv[2]);
162	gctl_ro_param(req, "offset", sizeof(off_t), &offset);
163	gctl_ro_param(req, "rename", sizeof(int), &rename);
164	errstr = gctl_issue(req);
165	if (errstr != NULL)
166		warnx("attach failed: %s", errstr);
167	gctl_free(req);
168}
169
170void
171gvinum_create(int argc, char **argv)
172{
173	struct gctl_req *req;
174	struct gv_drive *d;
175	struct gv_plex *p;
176	struct gv_sd *s;
177	struct gv_volume *v;
178	FILE *tmp;
179	int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
180	int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
181	const char *errstr;
182	char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
183	char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
184	char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
185
186	tmp = NULL;
187	flags = 0;
188	for (i = 1; i < argc; i++) {
189		/* Force flag used to ignore already created drives. */
190		if (!strcmp(argv[i], "-f")) {
191			flags |= GV_FLAG_F;
192		/* Else it must be a file. */
193		} else {
194			if ((tmp = fopen(argv[1], "r")) == NULL) {
195				warn("can't open '%s' for reading", argv[1]);
196				return;
197			}
198		}
199	}
200
201	/* We didn't get a file. */
202	if (tmp == NULL) {
203		snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
204
205		if ((fd = mkstemp(tmpfile)) == -1) {
206			warn("temporary file not accessible");
207			return;
208		}
209		if ((tmp = fdopen(fd, "w")) == NULL) {
210			warn("can't open '%s' for writing", tmpfile);
211			return;
212		}
213		printconfig(tmp, "# ");
214		fclose(tmp);
215
216		ed = getenv("EDITOR");
217		if (ed == NULL)
218			ed = _PATH_VI;
219
220		snprintf(commandline, sizeof(commandline), "%s %s", ed,
221		    tmpfile);
222		status = system(commandline);
223		if (status != 0) {
224			warn("couldn't exec %s; status: %d", ed, status);
225			return;
226		}
227
228		if ((tmp = fopen(tmpfile, "r")) == NULL) {
229			warn("can't open '%s' for reading", tmpfile);
230			return;
231		}
232	}
233
234	req = gctl_get_handle();
235	gctl_ro_param(req, "class", -1, "VINUM");
236	gctl_ro_param(req, "verb", -1, "create");
237	gctl_ro_param(req, "flags", sizeof(int), &flags);
238
239	drives = volumes = plexes = subdisks = 0;
240	plex_in_volume = sd_in_plex = undeffd = 0;
241	plex[0] = '\0';
242	errors = 0;
243	line = 1;
244	while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
245
246		/* Skip empty lines and comments. */
247		if (*buf == '\0' || *buf == '#') {
248			line++;
249			continue;
250		}
251
252		/* Kill off the newline. */
253		buf[strlen(buf) - 1] = '\0';
254
255		/*
256		 * Copy the original input line in case we need it for error
257		 * output.
258		 */
259		strlcpy(original, buf, sizeof(original));
260
261		tokens = gv_tokenize(buf, token, GV_MAXARGS);
262		if (tokens <= 0) {
263			line++;
264			continue;
265		}
266
267		/* Volume definition. */
268		if (!strcmp(token[0], "volume")) {
269			v = gv_new_volume(tokens, token);
270			if (v == NULL) {
271				warnx("line %d: invalid volume definition",
272				    line);
273				warnx("line %d: '%s'", line, original);
274				errors++;
275				line++;
276				continue;
277			}
278
279			/* Reset plex count for this volume. */
280			plex_in_volume = 0;
281
282			/*
283			 * Set default volume name for following plex
284			 * definitions.
285			 */
286			strlcpy(volume, v->name, sizeof(volume));
287
288			snprintf(buf1, sizeof(buf1), "volume%d", volumes);
289			gctl_ro_param(req, buf1, sizeof(*v), v);
290			volumes++;
291
292		/* Plex definition. */
293		} else if (!strcmp(token[0], "plex")) {
294			p = gv_new_plex(tokens, token);
295			if (p == NULL) {
296				warnx("line %d: invalid plex definition", line);
297				warnx("line %d: '%s'", line, original);
298				errors++;
299				line++;
300				continue;
301			}
302
303			/* Reset subdisk count for this plex. */
304			sd_in_plex = 0;
305
306			/* Default name. */
307			if (strlen(p->name) == 0) {
308				snprintf(p->name, sizeof(p->name), "%s.p%d",
309				    volume, plex_in_volume++);
310			}
311
312			/* Default volume. */
313			if (strlen(p->volume) == 0) {
314				snprintf(p->volume, sizeof(p->volume), "%s",
315				    volume);
316			}
317
318			/*
319			 * Set default plex name for following subdisk
320			 * definitions.
321			 */
322			strlcpy(plex, p->name, sizeof(plex));
323
324			snprintf(buf1, sizeof(buf1), "plex%d", plexes);
325			gctl_ro_param(req, buf1, sizeof(*p), p);
326			plexes++;
327
328		/* Subdisk definition. */
329		} else if (!strcmp(token[0], "sd")) {
330			s = gv_new_sd(tokens, token);
331			if (s == NULL) {
332				warnx("line %d: invalid subdisk "
333				    "definition:", line);
334				warnx("line %d: '%s'", line, original);
335				errors++;
336				line++;
337				continue;
338			}
339
340			/* Default name. */
341			if (strlen(s->name) == 0) {
342				if (strlen(plex) == 0) {
343					sdname = find_name("gvinumsubdisk.p",
344					    GV_TYPE_SD, GV_MAXSDNAME);
345					snprintf(s->name, sizeof(s->name),
346					    "%s.s%d", sdname, undeffd++);
347					free(sdname);
348				} else {
349					snprintf(s->name, sizeof(s->name),
350					    "%s.s%d",plex, sd_in_plex++);
351				}
352			}
353
354			/* Default plex. */
355			if (strlen(s->plex) == 0)
356				snprintf(s->plex, sizeof(s->plex), "%s", plex);
357
358			snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
359			gctl_ro_param(req, buf1, sizeof(*s), s);
360			subdisks++;
361
362		/* Subdisk definition. */
363		} else if (!strcmp(token[0], "drive")) {
364			d = gv_new_drive(tokens, token);
365			if (d == NULL) {
366				warnx("line %d: invalid drive definition:",
367				    line);
368				warnx("line %d: '%s'", line, original);
369				errors++;
370				line++;
371				continue;
372			}
373
374			snprintf(buf1, sizeof(buf1), "drive%d", drives);
375			gctl_ro_param(req, buf1, sizeof(*d), d);
376			drives++;
377
378		/* Everything else is bogus. */
379		} else {
380			warnx("line %d: invalid definition:", line);
381			warnx("line %d: '%s'", line, original);
382			errors++;
383		}
384		line++;
385	}
386
387	fclose(tmp);
388	unlink(tmpfile);
389
390	if (!errors && (volumes || plexes || subdisks || drives)) {
391		gctl_ro_param(req, "volumes", sizeof(int), &volumes);
392		gctl_ro_param(req, "plexes", sizeof(int), &plexes);
393		gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
394		gctl_ro_param(req, "drives", sizeof(int), &drives);
395		errstr = gctl_issue(req);
396		if (errstr != NULL)
397			warnx("create failed: %s", errstr);
398	}
399	gctl_free(req);
400}
401
402/* Create a concatenated volume. */
403void
404gvinum_concat(int argc, char **argv)
405{
406
407	if (argc < 2) {
408		warnx("usage:\tconcat [-fv] [-n name] drives\n");
409		return;
410	}
411	create_volume(argc, argv, "concat");
412}
413
414
415/* Create a drive quick and dirty. */
416char *
417create_drive(char *device)
418{
419	struct gv_drive *d;
420	struct gctl_req *req;
421	const char *errstr;
422	char *drivename, *dname;
423	int drives, i, flags, volumes, subdisks, plexes;
424
425	flags = plexes = subdisks = volumes = 0;
426	drives = 1;
427	dname = NULL;
428
429	drivename = find_drive();
430	if (drivename == NULL)
431		return (NULL);
432
433	req = gctl_get_handle();
434	gctl_ro_param(req, "class", -1, "VINUM");
435	gctl_ro_param(req, "verb", -1, "create");
436	d = gv_alloc_drive();
437	if (d == NULL)
438		err(1, "unable to allocate for gv_drive object");
439
440	strlcpy(d->name, drivename, sizeof(d->name));
441	copy_device(d, device);
442	gctl_ro_param(req, "drive0", sizeof(*d), d);
443	gctl_ro_param(req, "flags", sizeof(int), &flags);
444	gctl_ro_param(req, "drives", sizeof(int), &drives);
445	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
446	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
447	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
448	errstr = gctl_issue(req);
449	if (errstr != NULL) {
450		warnx("error creating drive: %s", errstr);
451		gctl_free(req);
452		return (NULL);
453	} else {
454		gctl_free(req);
455		/* XXX: This is needed because we have to make sure the drives
456		 * are created before we return. */
457		/* Loop until it's in the config. */
458		for (i = 0; i < 100000; i++) {
459			dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
460			    GV_MAXDRIVENAME);
461			/* If we got a different name, quit. */
462			if (dname == NULL)
463				continue;
464			if (strcmp(dname, drivename)) {
465				free(dname);
466				return (drivename);
467			}
468			free(dname);
469			dname = NULL;
470			usleep(100000); /* Sleep for 0.1s */
471		}
472	}
473	gctl_free(req);
474	return (drivename);
475}
476
477/*
478 * General routine for creating a volume. Mainly for use by concat, mirror,
479 * raid5 and stripe commands.
480 */
481void
482create_volume(int argc, char **argv, char *verb)
483{
484	struct gctl_req *req;
485	const char *errstr;
486	char buf[BUFSIZ], *drivename, *volname;
487	int drives, flags, i;
488	off_t stripesize;
489
490	flags = 0;
491	drives = 0;
492	volname = NULL;
493	stripesize = 262144;
494
495	/* XXX: Should we check for argument length? */
496
497	req = gctl_get_handle();
498	gctl_ro_param(req, "class", -1, "VINUM");
499
500	for (i = 1; i < argc; i++) {
501		if (!strcmp(argv[i], "-f")) {
502			flags |= GV_FLAG_F;
503		} else if (!strcmp(argv[i], "-n")) {
504			volname = argv[++i];
505		} else if (!strcmp(argv[i], "-v")) {
506			flags |= GV_FLAG_V;
507		} else if (!strcmp(argv[i], "-s")) {
508			flags |= GV_FLAG_S;
509			if (!strcmp(verb, "raid5"))
510				stripesize = gv_sizespec(argv[++i]);
511		} else {
512			/* Assume it's a drive. */
513			snprintf(buf, sizeof(buf), "drive%d", drives++);
514
515			/* First we create the drive. */
516			drivename = create_drive(argv[i]);
517			if (drivename == NULL)
518				goto bad;
519			/* Then we add it to the request. */
520			gctl_ro_param(req, buf, -1, drivename);
521		}
522	}
523
524	gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
525
526	/* Find a free volume name. */
527	if (volname == NULL)
528		volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
529
530	/* Then we send a request to actually create the volumes. */
531	gctl_ro_param(req, "verb", -1, verb);
532	gctl_ro_param(req, "flags", sizeof(int), &flags);
533	gctl_ro_param(req, "drives", sizeof(int), &drives);
534	gctl_ro_param(req, "name", -1, volname);
535	errstr = gctl_issue(req);
536	if (errstr != NULL)
537		warnx("creating %s volume failed: %s", verb, errstr);
538bad:
539	gctl_free(req);
540}
541
542/* Parse a line of the config, return the word after <pattern>. */
543char *
544find_pattern(char *line, char *pattern)
545{
546	char *ptr;
547
548	ptr = strsep(&line, " ");
549	while (ptr != NULL) {
550		if (!strcmp(ptr, pattern)) {
551			/* Return the next. */
552			ptr = strsep(&line, " ");
553			return (ptr);
554		}
555		ptr = strsep(&line, " ");
556	}
557	return (NULL);
558}
559
560/* Find a free name for an object given a prefix. */
561char *
562find_name(const char *prefix, int type, int namelen)
563{
564	struct gctl_req *req;
565	char comment[1], buf[GV_CFG_LEN - 1], *name, *sname, *ptr;
566	const char *errstr;
567	int i, n, begin, len, conflict;
568	char line[1024];
569
570	comment[0] = '\0';
571
572	/* Find a name. Fetch out configuration first. */
573	req = gctl_get_handle();
574	gctl_ro_param(req, "class", -1, "VINUM");
575	gctl_ro_param(req, "verb", -1, "getconfig");
576	gctl_ro_param(req, "comment", -1, comment);
577	gctl_rw_param(req, "config", sizeof(buf), buf);
578	errstr = gctl_issue(req);
579	if (errstr != NULL) {
580		warnx("can't get configuration: %s", errstr);
581		return (NULL);
582	}
583	gctl_free(req);
584
585	begin = 0;
586	len = strlen(buf);
587	i = 0;
588	sname = malloc(namelen + 1);
589
590	/* XXX: Max object setting? */
591	for (n = 0; n < 10000; n++) {
592		snprintf(sname, namelen, "%s%d", prefix, n);
593		conflict = 0;
594		begin = 0;
595		/* Loop through the configuration line by line. */
596		for (i = 0; i < len; i++) {
597			if (buf[i] == '\n' || buf[i] == '\0') {
598				ptr = buf + begin;
599				strlcpy(line, ptr, (i - begin) + 1);
600				begin = i + 1;
601				switch (type) {
602				case GV_TYPE_DRIVE:
603					name = find_pattern(line, "drive");
604					break;
605				case GV_TYPE_VOL:
606					name = find_pattern(line, "volume");
607					break;
608				case GV_TYPE_PLEX:
609				case GV_TYPE_SD:
610					name = find_pattern(line, "name");
611					break;
612				default:
613					printf("Invalid type given\n");
614					continue;
615				}
616				if (name == NULL)
617					continue;
618				if (!strcmp(sname, name)) {
619					conflict = 1;
620					/* XXX: Could quit the loop earlier. */
621				}
622			}
623		}
624		if (!conflict)
625			return (sname);
626	}
627	free(sname);
628	return (NULL);
629}
630
631void
632copy_device(struct gv_drive *d, const char *device)
633{
634	if (strncmp(device, "/dev/", 5) == 0)
635		strlcpy(d->device, (device + 5), sizeof(d->device));
636	else
637		strlcpy(d->device, device, sizeof(d->device));
638}
639
640/* Detach a plex or subdisk from its parent. */
641void
642gvinum_detach(int argc, char **argv)
643{
644	const char *errstr;
645	struct gctl_req *req;
646	int flags, i;
647
648	flags = 0;
649	optreset = 1;
650	optind = 1;
651	while ((i = getopt(argc, argv, "f")) != -1) {
652		switch(i) {
653		case 'f':
654			flags |= GV_FLAG_F;
655			break;
656		default:
657			warn("invalid flag: %c", i);
658			return;
659		}
660	}
661	argc -= optind;
662	argv += optind;
663	if (argc != 1) {
664		warnx("usage: detach [-f] <subdisk> | <plex>");
665		return;
666	}
667
668	req = gctl_get_handle();
669	gctl_ro_param(req, "class", -1, "VINUM");
670	gctl_ro_param(req, "verb", -1, "detach");
671	gctl_ro_param(req, "object", -1, argv[0]);
672	gctl_ro_param(req, "flags", sizeof(int), &flags);
673
674	errstr = gctl_issue(req);
675	if (errstr != NULL)
676		warnx("detach failed: %s", errstr);
677	gctl_free(req);
678}
679
680void
681gvinum_help(void)
682{
683	printf("COMMANDS\n"
684	    "checkparity [-f] plex\n"
685	    "        Check the parity blocks of a RAID-5 plex.\n"
686	    "create [-f] description-file\n"
687	    "        Create as per description-file or open editor.\n"
688	    "attach plex volume [rename]\n"
689	    "attach subdisk plex [offset] [rename]\n"
690	    "        Attach a plex to a volume, or a subdisk to a plex\n"
691	    "concat [-fv] [-n name] drives\n"
692	    "        Create a concatenated volume from the specified drives.\n"
693	    "detach [-f] [plex | subdisk]\n"
694	    "        Detach a plex or a subdisk from the volume or plex to\n"
695	    "        which it is attached.\n"
696	    "grow plex drive\n"
697	    "        Grow plex by creating a properly sized subdisk on drive\n"
698	    "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
699	    "        List information about specified objects.\n"
700	    "ld [-r] [-v] [-V] [volume]\n"
701	    "        List information about drives.\n"
702	    "ls [-r] [-v] [-V] [subdisk]\n"
703	    "        List information about subdisks.\n"
704	    "lp [-r] [-v] [-V] [plex]\n"
705	    "        List information about plexes.\n"
706	    "lv [-r] [-v] [-V] [volume]\n"
707	    "        List information about volumes.\n"
708	    "mirror [-fsv] [-n name] drives\n"
709	    "        Create a mirrored volume from the specified drives.\n"
710	    "move | mv -f drive object ...\n"
711	    "        Move the object(s) to the specified drive.\n"
712	    "quit    Exit the vinum program when running in interactive mode."
713	    "  Nor-\n"
714	    "        mally this would be done by entering the EOF character.\n"
715	    "raid5 [-fv] [-s stripesize] [-n name] drives\n"
716	    "        Create a RAID-5 volume from the specified drives.\n"
717	    "rename [-r] [drive | subdisk | plex | volume] newname\n"
718	    "        Change the name of the specified object.\n"
719	    "rebuildparity plex [-f]\n"
720	    "        Rebuild the parity blocks of a RAID-5 plex.\n"
721	    "resetconfig\n"
722	    "        Reset the complete gvinum configuration\n"
723	    "rm [-r] [-f] volume | plex | subdisk | drive\n"
724	    "        Remove an object.\n"
725	    "saveconfig\n"
726	    "        Save vinum configuration to disk after configuration"
727	    " failures.\n"
728	    "setstate [-f] state [volume | plex | subdisk | drive]\n"
729	    "        Set state without influencing other objects, for"
730	    " diagnostic pur-\n"
731	    "        poses only.\n"
732	    "start [-S size] volume | plex | subdisk\n"
733	    "        Allow the system to access the objects.\n"
734	    "stripe [-fv] [-n name] drives\n"
735	    "        Create a striped volume from the specified drives.\n"
736	);
737
738	return;
739}
740
741void
742gvinum_setstate(int argc, char **argv)
743{
744	struct gctl_req *req;
745	int flags, i;
746	const char *errstr;
747
748	flags = 0;
749
750	optreset = 1;
751	optind = 1;
752
753	while ((i = getopt(argc, argv, "f")) != -1) {
754		switch (i) {
755		case 'f':
756			flags |= GV_FLAG_F;
757			break;
758		case '?':
759		default:
760			warn("invalid flag: %c", i);
761			return;
762		}
763	}
764
765	argc -= optind;
766	argv += optind;
767
768	if (argc != 2) {
769		warnx("usage: setstate [-f] <state> <obj>");
770		return;
771	}
772
773	/*
774	 * XXX: This hack is needed to avoid tripping over (now) invalid
775	 * 'classic' vinum states and will go away later.
776	 */
777	if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
778	    strcmp(argv[0], "stale")) {
779		warnx("invalid state '%s'", argv[0]);
780		return;
781	}
782
783	req = gctl_get_handle();
784	gctl_ro_param(req, "class", -1, "VINUM");
785	gctl_ro_param(req, "verb", -1, "setstate");
786	gctl_ro_param(req, "state", -1, argv[0]);
787	gctl_ro_param(req, "object", -1, argv[1]);
788	gctl_ro_param(req, "flags", sizeof(int), &flags);
789
790	errstr = gctl_issue(req);
791	if (errstr != NULL)
792		warnx("%s", errstr);
793	gctl_free(req);
794}
795
796void
797gvinum_list(int argc, char **argv)
798{
799	struct gctl_req *req;
800	int flags, i, j;
801	const char *errstr;
802	char buf[20], *cmd, config[GV_CFG_LEN + 1];
803
804	flags = 0;
805	cmd = "list";
806
807	if (argc) {
808		optreset = 1;
809		optind = 1;
810		cmd = argv[0];
811		while ((j = getopt(argc, argv, "rsvV")) != -1) {
812			switch (j) {
813			case 'r':
814				flags |= GV_FLAG_R;
815				break;
816			case 's':
817				flags |= GV_FLAG_S;
818				break;
819			case 'v':
820				flags |= GV_FLAG_V;
821				break;
822			case 'V':
823				flags |= GV_FLAG_V;
824				flags |= GV_FLAG_VV;
825				break;
826			case '?':
827			default:
828				return;
829			}
830		}
831		argc -= optind;
832		argv += optind;
833
834	}
835
836	req = gctl_get_handle();
837	gctl_ro_param(req, "class", -1, "VINUM");
838	gctl_ro_param(req, "verb", -1, "list");
839	gctl_ro_param(req, "cmd", -1, cmd);
840	gctl_ro_param(req, "argc", sizeof(int), &argc);
841	gctl_ro_param(req, "flags", sizeof(int), &flags);
842	gctl_rw_param(req, "config", sizeof(config), config);
843	if (argc) {
844		for (i = 0; i < argc; i++) {
845			snprintf(buf, sizeof(buf), "argv%d", i);
846			gctl_ro_param(req, buf, -1, argv[i]);
847		}
848	}
849	errstr = gctl_issue(req);
850	if (errstr != NULL) {
851		warnx("can't get configuration: %s", errstr);
852		gctl_free(req);
853		return;
854	}
855
856	printf("%s", config);
857	gctl_free(req);
858	return;
859}
860
861/* Create a mirrored volume. */
862void
863gvinum_mirror(int argc, char **argv)
864{
865
866	if (argc < 2) {
867		warnx("usage\tmirror [-fsv] [-n name] drives\n");
868		return;
869	}
870	create_volume(argc, argv, "mirror");
871}
872
873/* Note that move is currently of form '[-r] target object [...]' */
874void
875gvinum_move(int argc, char **argv)
876{
877	struct gctl_req *req;
878	const char *errstr;
879	char buf[20];
880	int flags, i, j;
881
882	flags = 0;
883	if (argc) {
884		optreset = 1;
885		optind = 1;
886		while ((j = getopt(argc, argv, "f")) != -1) {
887			switch (j) {
888			case 'f':
889				flags |= GV_FLAG_F;
890				break;
891			case '?':
892			default:
893				return;
894			}
895		}
896		argc -= optind;
897		argv += optind;
898	}
899
900	switch (argc) {
901		case 0:
902			warnx("no destination or object(s) to move specified");
903			return;
904		case 1:
905			warnx("no object(s) to move specified");
906			return;
907		default:
908			break;
909	}
910
911	req = gctl_get_handle();
912	gctl_ro_param(req, "class", -1, "VINUM");
913	gctl_ro_param(req, "verb", -1, "move");
914	gctl_ro_param(req, "argc", sizeof(int), &argc);
915	gctl_ro_param(req, "flags", sizeof(int), &flags);
916	gctl_ro_param(req, "destination", -1, argv[0]);
917	for (i = 1; i < argc; i++) {
918		snprintf(buf, sizeof(buf), "argv%d", i);
919		gctl_ro_param(req, buf, -1, argv[i]);
920	}
921	errstr = gctl_issue(req);
922	if (errstr != NULL)
923		warnx("can't move object(s):  %s", errstr);
924	gctl_free(req);
925	return;
926}
927
928void
929gvinum_printconfig(int argc, char **argv)
930{
931	printconfig(stdout, "");
932}
933
934void
935gvinum_parityop(int argc, char **argv, int rebuild)
936{
937	struct gctl_req *req;
938	int flags, i;
939	const char *errstr;
940	char *op;
941
942	if (rebuild) {
943		op = "rebuildparity";
944	} else {
945		op = "checkparity";
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];
1059
1060	flags = 0;
1061	optreset = 1;
1062	optind = 1;
1063	while ((j = getopt(argc, argv, "rf")) != -1) {
1064		switch (j) {
1065		case 'f':
1066			flags |= GV_FLAG_F;
1067			break;
1068		case 'r':
1069			flags |= GV_FLAG_R;
1070			break;
1071		case '?':
1072		default:
1073			return;
1074		}
1075	}
1076	argc -= optind;
1077	argv += optind;
1078
1079	req = gctl_get_handle();
1080	gctl_ro_param(req, "class", -1, "VINUM");
1081	gctl_ro_param(req, "verb", -1, "remove");
1082	gctl_ro_param(req, "argc", sizeof(int), &argc);
1083	gctl_ro_param(req, "flags", sizeof(int), &flags);
1084	if (argc) {
1085		for (i = 0; i < argc; i++) {
1086			snprintf(buf, sizeof(buf), "argv%d", i);
1087			gctl_ro_param(req, buf, -1, argv[i]);
1088		}
1089	}
1090	errstr = gctl_issue(req);
1091	if (errstr != NULL) {
1092		warnx("can't remove: %s", errstr);
1093		gctl_free(req);
1094		return;
1095	}
1096	gctl_free(req);
1097}
1098
1099void
1100gvinum_resetconfig(void)
1101{
1102	struct gctl_req *req;
1103	const char *errstr;
1104	char reply[32];
1105
1106	if (!isatty(STDIN_FILENO)) {
1107		warn("Please enter this command from a tty device\n");
1108		return;
1109	}
1110	printf(" WARNING!  This command will completely wipe out your gvinum"
1111	    "configuration.\n"
1112	    " All data will be lost.  If you really want to do this,"
1113	    " enter the text\n\n"
1114	    " NO FUTURE\n"
1115	    " Enter text -> ");
1116	fgets(reply, sizeof(reply), stdin);
1117	if (strcmp(reply, "NO FUTURE\n")) {
1118		printf("\n No change\n");
1119		return;
1120	}
1121	req = gctl_get_handle();
1122	gctl_ro_param(req, "class", -1, "VINUM");
1123	gctl_ro_param(req, "verb", -1, "resetconfig");
1124	errstr = gctl_issue(req);
1125	if (errstr != NULL) {
1126		warnx("can't reset config: %s", errstr);
1127		gctl_free(req);
1128		return;
1129	}
1130	gctl_free(req);
1131	printf("gvinum configuration obliterated\n");
1132}
1133
1134void
1135gvinum_saveconfig(void)
1136{
1137	struct gctl_req *req;
1138	const char *errstr;
1139
1140	req = gctl_get_handle();
1141	gctl_ro_param(req, "class", -1, "VINUM");
1142	gctl_ro_param(req, "verb", -1, "saveconfig");
1143	errstr = gctl_issue(req);
1144	if (errstr != NULL)
1145		warnx("can't save configuration: %s", errstr);
1146	gctl_free(req);
1147}
1148
1149void
1150gvinum_start(int argc, char **argv)
1151{
1152	struct gctl_req *req;
1153	int i, initsize, j;
1154	const char *errstr;
1155	char buf[20];
1156
1157	/* 'start' with no arguments is a no-op. */
1158	if (argc == 1)
1159		return;
1160
1161	initsize = 0;
1162
1163	optreset = 1;
1164	optind = 1;
1165	while ((j = getopt(argc, argv, "S")) != -1) {
1166		switch (j) {
1167		case 'S':
1168			initsize = atoi(optarg);
1169			break;
1170		case '?':
1171		default:
1172			return;
1173		}
1174	}
1175	argc -= optind;
1176	argv += optind;
1177
1178	if (!initsize)
1179		initsize = 512;
1180
1181	req = gctl_get_handle();
1182	gctl_ro_param(req, "class", -1, "VINUM");
1183	gctl_ro_param(req, "verb", -1, "start");
1184	gctl_ro_param(req, "argc", sizeof(int), &argc);
1185	gctl_ro_param(req, "initsize", sizeof(int), &initsize);
1186	if (argc) {
1187		for (i = 0; i < argc; i++) {
1188			snprintf(buf, sizeof(buf), "argv%d", i);
1189			gctl_ro_param(req, buf, -1, argv[i]);
1190		}
1191	}
1192	errstr = gctl_issue(req);
1193	if (errstr != NULL) {
1194		warnx("can't start: %s", errstr);
1195		gctl_free(req);
1196		return;
1197	}
1198
1199	gctl_free(req);
1200}
1201
1202void
1203gvinum_stop(int argc, char **argv)
1204{
1205	int err, fileid;
1206
1207	fileid = kldfind(GVINUMMOD);
1208	if (fileid == -1) {
1209		warn("cannot find " GVINUMMOD);
1210		return;
1211	}
1212
1213	/*
1214	 * This little hack prevents that we end up in an infinite loop in
1215	 * g_unload_class().  gv_unload() will return EAGAIN so that the GEOM
1216	 * event thread will be free for the g_wither_geom() call from
1217	 * gv_unload().  It's silly, but it works.
1218	 */
1219	printf("unloading " GVINUMMOD " kernel module... ");
1220	fflush(stdout);
1221	if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
1222		sleep(1);
1223		err = kldunload(fileid);
1224	}
1225	if (err != 0) {
1226		printf(" failed!\n");
1227		warn("cannot unload " GVINUMMOD);
1228		return;
1229	}
1230
1231	printf("done\n");
1232	exit(0);
1233}
1234
1235/* Create a striped volume. */
1236void
1237gvinum_stripe(int argc, char **argv)
1238{
1239
1240	if (argc < 2) {
1241		warnx("usage:\tstripe [-fv] [-n name] drives\n");
1242		return;
1243	}
1244	create_volume(argc, argv, "stripe");
1245}
1246
1247/* Grow a subdisk by adding disk backed by provider. */
1248void
1249gvinum_grow(int argc, char **argv)
1250{
1251	struct gctl_req *req;
1252	char *drive, *sdname;
1253	char sdprefix[GV_MAXSDNAME];
1254	struct gv_drive *d;
1255	struct gv_sd *s;
1256	const char *errstr;
1257	int drives, volumes, plexes, subdisks, flags;
1258
1259	drives = volumes = plexes = subdisks = 0;
1260	if (argc < 3) {
1261		warnx("usage:\tgrow plex drive\n");
1262		return;
1263	}
1264
1265	s = gv_alloc_sd();
1266	if (s == NULL) {
1267		warn("unable to create subdisk");
1268		return;
1269	}
1270	d = gv_alloc_drive();
1271	if (d == NULL) {
1272		warn("unable to create drive");
1273		free(s);
1274		return;
1275	}
1276	/* Lookup device and set an appropriate drive name. */
1277	drive = find_drive();
1278	if (drive == NULL) {
1279		warn("unable to find an appropriate drive name");
1280		free(s);
1281		free(d);
1282		return;
1283	}
1284	strlcpy(d->name, drive, sizeof(d->name));
1285	copy_device(d, argv[2]);
1286
1287	drives = 1;
1288
1289	/* We try to use the plex name as basis for the subdisk name. */
1290	snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
1291	sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
1292	if (sdname == NULL) {
1293		warn("unable to find an appropriate subdisk name");
1294		free(s);
1295		free(d);
1296		free(drive);
1297		return;
1298	}
1299	strlcpy(s->name, sdname, sizeof(s->name));
1300	free(sdname);
1301	strlcpy(s->plex, argv[1], sizeof(s->plex));
1302	strlcpy(s->drive, d->name, sizeof(s->drive));
1303	subdisks = 1;
1304
1305	req = gctl_get_handle();
1306	gctl_ro_param(req, "class", -1, "VINUM");
1307	gctl_ro_param(req, "verb", -1, "create");
1308	gctl_ro_param(req, "flags", sizeof(int), &flags);
1309	gctl_ro_param(req, "volumes", sizeof(int), &volumes);
1310	gctl_ro_param(req, "plexes", sizeof(int), &plexes);
1311	gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
1312	gctl_ro_param(req, "drives", sizeof(int), &drives);
1313	gctl_ro_param(req, "drive0", sizeof(*d), d);
1314	gctl_ro_param(req, "sd0", sizeof(*s), s);
1315	errstr = gctl_issue(req);
1316	free(drive);
1317	if (errstr != NULL) {
1318		warnx("unable to grow plex: %s", errstr);
1319		free(s);
1320		free(d);
1321		return;
1322	}
1323	gctl_free(req);
1324}
1325
1326void
1327parseline(int argc, char **argv)
1328{
1329	if (argc <= 0)
1330		return;
1331
1332	if (!strcmp(argv[0], "create"))
1333		gvinum_create(argc, argv);
1334	else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
1335		exit(0);
1336	else if (!strcmp(argv[0], "attach"))
1337		gvinum_attach(argc, argv);
1338	else if (!strcmp(argv[0], "detach"))
1339		gvinum_detach(argc, argv);
1340	else if (!strcmp(argv[0], "concat"))
1341		gvinum_concat(argc, argv);
1342	else if (!strcmp(argv[0], "grow"))
1343		gvinum_grow(argc, argv);
1344	else if (!strcmp(argv[0], "help"))
1345		gvinum_help();
1346	else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
1347		gvinum_list(argc, argv);
1348	else if (!strcmp(argv[0], "ld"))
1349		gvinum_list(argc, argv);
1350	else if (!strcmp(argv[0], "lp"))
1351		gvinum_list(argc, argv);
1352	else if (!strcmp(argv[0], "ls"))
1353		gvinum_list(argc, argv);
1354	else if (!strcmp(argv[0], "lv"))
1355		gvinum_list(argc, argv);
1356	else if (!strcmp(argv[0], "mirror"))
1357		gvinum_mirror(argc, argv);
1358	else if (!strcmp(argv[0], "move"))
1359		gvinum_move(argc, argv);
1360	else if (!strcmp(argv[0], "mv"))
1361		gvinum_move(argc, argv);
1362	else if (!strcmp(argv[0], "printconfig"))
1363		gvinum_printconfig(argc, argv);
1364	else if (!strcmp(argv[0], "raid5"))
1365		gvinum_raid5(argc, argv);
1366	else if (!strcmp(argv[0], "rename"))
1367		gvinum_rename(argc, argv);
1368	else if (!strcmp(argv[0], "resetconfig"))
1369		gvinum_resetconfig();
1370	else if (!strcmp(argv[0], "rm"))
1371		gvinum_rm(argc, argv);
1372	else if (!strcmp(argv[0], "saveconfig"))
1373		gvinum_saveconfig();
1374	else if (!strcmp(argv[0], "setstate"))
1375		gvinum_setstate(argc, argv);
1376	else if (!strcmp(argv[0], "start"))
1377		gvinum_start(argc, argv);
1378	else if (!strcmp(argv[0], "stop"))
1379		gvinum_stop(argc, argv);
1380	else if (!strcmp(argv[0], "stripe"))
1381		gvinum_stripe(argc, argv);
1382	else if (!strcmp(argv[0], "checkparity"))
1383		gvinum_parityop(argc, argv, 0);
1384	else if (!strcmp(argv[0], "rebuildparity"))
1385		gvinum_parityop(argc, argv, 1);
1386	else
1387		printf("unknown command '%s'\n", argv[0]);
1388
1389	return;
1390}
1391
1392/*
1393 * The guts of printconfig.  This is called from gvinum_printconfig and from
1394 * gvinum_create when called without an argument, in order to give the user
1395 * something to edit.
1396 */
1397void
1398printconfig(FILE *of, char *comment)
1399{
1400	struct gctl_req *req;
1401	struct utsname uname_s;
1402	const char *errstr;
1403	time_t now;
1404	char buf[GV_CFG_LEN + 1];
1405
1406	uname(&uname_s);
1407	time(&now);
1408
1409	req = gctl_get_handle();
1410	gctl_ro_param(req, "class", -1, "VINUM");
1411	gctl_ro_param(req, "verb", -1, "getconfig");
1412	gctl_ro_param(req, "comment", -1, comment);
1413	gctl_rw_param(req, "config", sizeof(buf), buf);
1414	errstr = gctl_issue(req);
1415	if (errstr != NULL) {
1416		warnx("can't get configuration: %s", errstr);
1417		return;
1418	}
1419	gctl_free(req);
1420
1421	fprintf(of, "# Vinum configuration of %s, saved at %s",
1422	    uname_s.nodename,
1423	    ctime(&now));
1424
1425	if (*comment != '\0')
1426	    fprintf(of, "# Current configuration:\n");
1427
1428	fprintf(of, "%s", buf);
1429}
1430