c0b9797aa8
The work have been under testing and fixing since then, and it is mature enough to be put into HEAD for further testing. A lot have changed in this time, and here are the most important: - Gvinum now uses one single workerthread instead of one thread for each volume and each plex. The reason for this is that the previous scheme was very complex, and was the cause of many of the bugs discovered in gvinum. Instead, gvinum now uses one worker thread with an event queue, quite similar to what used in gmirror. - The rebuild/grow/initialize/parity check routines no longer runs in separate threads, but are run as regular I/O requests with special flags. This made it easier to support mounted growing and parity rebuild. - Support for growing striped and raid5-plexes, meaning that one can extend the volumes for these plex types in addition to the concat type. Also works while the volume is mounted. - Implementation of many of the missing commands from the old vinum: attach/detach, start (was partially implemented), stop (was partially implemented), concat, mirror, stripe, raid5 (shortcuts for creating volumes with one plex of these organizations). - The parity check and rebuild no longer goes between userland/kernel, meaning that the gvinum command will not stay and wait forever for the rebuild to finish. You can instead watch the status with the list command. - Many problems with gvinum have been reported since 5.x, and some has been hard to fix due to the complicated architecture. Hopefully, it should be more stable and better handle edge cases that previously made gvinum crash. - Failed drives no longer disappears entirely, but now leave behind a dummy drive that makes sure the original state is not forgotten in case the system is rebooted between drive failures/swaps. - Update manpage to reflect new commands and extend it with some examples. Sponsored by: Google Summer of Code 2007 Mentored by: le Tested by: Rick C. Petty <rick-freebsd2008 -at- kiwi-computer.com>
1341 lines
31 KiB
C
1341 lines
31 KiB
C
/*
|
|
* Copyright (c) 2004 Lukas Ertl
|
|
* Copyright (c) 2005 Chris Jones
|
|
* Copyright (c) 2007 Ulf Lilleengen
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this software were developed for the FreeBSD Project
|
|
* by Chris Jones thanks to the support of Google's Summer of Code
|
|
* program and mentoring by Lukas Ertl.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/linker.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/module.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#include <geom/vinum/geom_vinum_var.h>
|
|
#include <geom/vinum/geom_vinum_share.h>
|
|
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <libgeom.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <paths.h>
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gvinum.h"
|
|
|
|
void gvinum_attach(int, char **);
|
|
void gvinum_concat(int, char **);
|
|
void gvinum_create(int, char **);
|
|
void gvinum_detach(int, char **);
|
|
void gvinum_help(void);
|
|
void gvinum_list(int, char **);
|
|
void gvinum_move(int, char **);
|
|
void gvinum_mirror(int, char **);
|
|
void gvinum_parityop(int, char **, int);
|
|
void gvinum_printconfig(int, char **);
|
|
void gvinum_raid5(int, char **);
|
|
void gvinum_rename(int, char **);
|
|
void gvinum_resetconfig(void);
|
|
void gvinum_rm(int, char **);
|
|
void gvinum_saveconfig(void);
|
|
void gvinum_setstate(int, char **);
|
|
void gvinum_start(int, char **);
|
|
void gvinum_stop(int, char **);
|
|
void gvinum_stripe(int, char **);
|
|
void parseline(int, char **);
|
|
void printconfig(FILE *, char *);
|
|
|
|
char *create_drive(char *);
|
|
void create_volume(int, char **, char *);
|
|
char *find_name(const char *, int, int);
|
|
char *find_pattern(char *, char *);
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int line, tokens;
|
|
char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
|
|
|
|
/* Load the module if necessary. */
|
|
if (kldfind(GVINUMMOD) < 0 && kldload(GVINUMMOD) < 0)
|
|
err(1, GVINUMMOD ": Kernel module not available");
|
|
|
|
/* Arguments given on the command line. */
|
|
if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
parseline(argc, argv);
|
|
|
|
/* Interactive mode. */
|
|
} else {
|
|
for (;;) {
|
|
inputline = readline("gvinum -> ");
|
|
if (inputline == NULL) {
|
|
if (ferror(stdin)) {
|
|
err(1, "can't read input");
|
|
} else {
|
|
printf("\n");
|
|
exit(0);
|
|
}
|
|
} else if (*inputline) {
|
|
add_history(inputline);
|
|
strcpy(buffer, inputline);
|
|
free(inputline);
|
|
line++; /* count the lines */
|
|
tokens = gv_tokenize(buffer, token, GV_MAXARGS);
|
|
if (tokens)
|
|
parseline(tokens, token);
|
|
}
|
|
}
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
/* Attach a plex to a volume or a subdisk to a plex. */
|
|
void
|
|
gvinum_attach(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
int rename;
|
|
off_t offset;
|
|
|
|
rename = 0;
|
|
offset = -1;
|
|
if (argc < 3) {
|
|
warnx("usage:\tattach <subdisk> <plex> [rename] "
|
|
"[<plexoffset>]\n"
|
|
"\tattach <plex> <volume> [rename]");
|
|
return;
|
|
}
|
|
if (argc > 3) {
|
|
if (!strcmp(argv[3], "rename")) {
|
|
rename = 1;
|
|
if (argc == 5)
|
|
offset = strtol(argv[4], NULL, 0);
|
|
} else
|
|
offset = strtol(argv[3], NULL, 0);
|
|
}
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "attach");
|
|
gctl_ro_param(req, "child", -1, argv[1]);
|
|
gctl_ro_param(req, "parent", -1, argv[2]);
|
|
gctl_ro_param(req, "offset", sizeof(off_t), &offset);
|
|
gctl_ro_param(req, "rename", sizeof(int), &rename);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("attach failed: %s", errstr);
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_create(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
struct gv_drive *d;
|
|
struct gv_plex *p;
|
|
struct gv_sd *s;
|
|
struct gv_volume *v;
|
|
FILE *tmp;
|
|
int drives, errors, fd, flags, i, line, plexes, plex_in_volume;
|
|
int sd_in_plex, status, subdisks, tokens, undeffd, volumes;
|
|
const char *errstr;
|
|
char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed, *sdname;
|
|
char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS];
|
|
char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME];
|
|
|
|
tmp = NULL;
|
|
flags = 0;
|
|
for (i = 1; i < argc; i++) {
|
|
/* Force flag used to ignore already created drives. */
|
|
if (!strcmp(argv[i], "-f")) {
|
|
flags |= GV_FLAG_F;
|
|
/* Else it must be a file. */
|
|
} else {
|
|
if ((tmp = fopen(argv[1], "r")) == NULL) {
|
|
warn("can't open '%s' for reading", argv[1]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We didn't get a file. */
|
|
if (tmp == NULL) {
|
|
snprintf(tmpfile, sizeof(tmpfile), "/tmp/gvinum.XXXXXX");
|
|
|
|
if ((fd = mkstemp(tmpfile)) == -1) {
|
|
warn("temporary file not accessible");
|
|
return;
|
|
}
|
|
if ((tmp = fdopen(fd, "w")) == NULL) {
|
|
warn("can't open '%s' for writing", tmpfile);
|
|
return;
|
|
}
|
|
printconfig(tmp, "# ");
|
|
fclose(tmp);
|
|
|
|
ed = getenv("EDITOR");
|
|
if (ed == NULL)
|
|
ed = _PATH_VI;
|
|
|
|
snprintf(commandline, sizeof(commandline), "%s %s", ed,
|
|
tmpfile);
|
|
status = system(commandline);
|
|
if (status != 0) {
|
|
warn("couldn't exec %s; status: %d", ed, status);
|
|
return;
|
|
}
|
|
|
|
if ((tmp = fopen(tmpfile, "r")) == NULL) {
|
|
warn("can't open '%s' for reading", tmpfile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "create");
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
|
|
drives = volumes = plexes = subdisks = 0;
|
|
plex_in_volume = sd_in_plex = undeffd = 0;
|
|
plex[0] = '\0';
|
|
errors = 0;
|
|
line = 1;
|
|
while ((fgets(buf, BUFSIZ, tmp)) != NULL) {
|
|
|
|
/* Skip empty lines and comments. */
|
|
if (*buf == '\0' || *buf == '#') {
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
/* Kill off the newline. */
|
|
buf[strlen(buf) - 1] = '\0';
|
|
|
|
/*
|
|
* Copy the original input line in case we need it for error
|
|
* output.
|
|
*/
|
|
strlcpy(original, buf, sizeof(original));
|
|
|
|
tokens = gv_tokenize(buf, token, GV_MAXARGS);
|
|
if (tokens <= 0) {
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
/* Volume definition. */
|
|
if (!strcmp(token[0], "volume")) {
|
|
v = gv_new_volume(tokens, token);
|
|
if (v == NULL) {
|
|
warnx("line %d: invalid volume definition",
|
|
line);
|
|
warnx("line %d: '%s'", line, original);
|
|
errors++;
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
/* Reset plex count for this volume. */
|
|
plex_in_volume = 0;
|
|
|
|
/*
|
|
* Set default volume name for following plex
|
|
* definitions.
|
|
*/
|
|
strlcpy(volume, v->name, sizeof(volume));
|
|
|
|
snprintf(buf1, sizeof(buf1), "volume%d", volumes);
|
|
gctl_ro_param(req, buf1, sizeof(*v), v);
|
|
volumes++;
|
|
|
|
/* Plex definition. */
|
|
} else if (!strcmp(token[0], "plex")) {
|
|
p = gv_new_plex(tokens, token);
|
|
if (p == NULL) {
|
|
warnx("line %d: invalid plex definition", line);
|
|
warnx("line %d: '%s'", line, original);
|
|
errors++;
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
/* Reset subdisk count for this plex. */
|
|
sd_in_plex = 0;
|
|
|
|
/* Default name. */
|
|
if (strlen(p->name) == 0) {
|
|
snprintf(p->name, sizeof(p->name), "%s.p%d",
|
|
volume, plex_in_volume++);
|
|
}
|
|
|
|
/* Default volume. */
|
|
if (strlen(p->volume) == 0) {
|
|
snprintf(p->volume, sizeof(p->volume), "%s",
|
|
volume);
|
|
}
|
|
|
|
/*
|
|
* Set default plex name for following subdisk
|
|
* definitions.
|
|
*/
|
|
strlcpy(plex, p->name, sizeof(plex));
|
|
|
|
snprintf(buf1, sizeof(buf1), "plex%d", plexes);
|
|
gctl_ro_param(req, buf1, sizeof(*p), p);
|
|
plexes++;
|
|
|
|
/* Subdisk definition. */
|
|
} else if (!strcmp(token[0], "sd")) {
|
|
s = gv_new_sd(tokens, token);
|
|
if (s == NULL) {
|
|
warnx("line %d: invalid subdisk "
|
|
"definition:", line);
|
|
warnx("line %d: '%s'", line, original);
|
|
errors++;
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
/* Default name. */
|
|
if (strlen(s->name) == 0) {
|
|
if (strlen(plex) == 0) {
|
|
sdname = find_name("gvinumsubdisk.p",
|
|
GV_TYPE_SD, GV_MAXSDNAME);
|
|
snprintf(s->name, sizeof(s->name),
|
|
"%s.s%d", sdname, undeffd++);
|
|
free(sdname);
|
|
} else {
|
|
snprintf(s->name, sizeof(s->name),
|
|
"%s.s%d",plex, sd_in_plex++);
|
|
}
|
|
}
|
|
|
|
/* Default plex. */
|
|
if (strlen(s->plex) == 0)
|
|
snprintf(s->plex, sizeof(s->plex), "%s", plex);
|
|
|
|
snprintf(buf1, sizeof(buf1), "sd%d", subdisks);
|
|
gctl_ro_param(req, buf1, sizeof(*s), s);
|
|
subdisks++;
|
|
|
|
/* Subdisk definition. */
|
|
} else if (!strcmp(token[0], "drive")) {
|
|
d = gv_new_drive(tokens, token);
|
|
if (d == NULL) {
|
|
warnx("line %d: invalid drive definition:",
|
|
line);
|
|
warnx("line %d: '%s'", line, original);
|
|
errors++;
|
|
line++;
|
|
continue;
|
|
}
|
|
|
|
snprintf(buf1, sizeof(buf1), "drive%d", drives);
|
|
gctl_ro_param(req, buf1, sizeof(*d), d);
|
|
drives++;
|
|
|
|
/* Everything else is bogus. */
|
|
} else {
|
|
warnx("line %d: invalid definition:", line);
|
|
warnx("line %d: '%s'", line, original);
|
|
errors++;
|
|
}
|
|
line++;
|
|
}
|
|
|
|
fclose(tmp);
|
|
unlink(tmpfile);
|
|
|
|
if (!errors && (volumes || plexes || subdisks || drives)) {
|
|
gctl_ro_param(req, "volumes", sizeof(int), &volumes);
|
|
gctl_ro_param(req, "plexes", sizeof(int), &plexes);
|
|
gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
|
|
gctl_ro_param(req, "drives", sizeof(int), &drives);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("create failed: %s", errstr);
|
|
}
|
|
gctl_free(req);
|
|
}
|
|
|
|
/* Create a concatenated volume. */
|
|
void
|
|
gvinum_concat(int argc, char **argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\tconcat [-fv] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "concat");
|
|
}
|
|
|
|
|
|
/* Create a drive quick and dirty. */
|
|
char *
|
|
create_drive(char *device)
|
|
{
|
|
struct gv_drive *d;
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char *drivename, *dname;
|
|
int drives, i, flags, volumes, subdisks, plexes;
|
|
|
|
flags = plexes = subdisks = volumes = 0;
|
|
drives = 1;
|
|
dname = NULL;
|
|
|
|
/* Strip away eventual /dev/ in front. */
|
|
if (strncmp(device, "/dev/", 5) == 0)
|
|
device += 5;
|
|
|
|
drivename = find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME);
|
|
if (drivename == NULL)
|
|
return (NULL);
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "create");
|
|
d = malloc(sizeof(struct gv_drive));
|
|
if (d == NULL)
|
|
err(1, "unable to allocate for gv_drive object");
|
|
memset(d, 0, sizeof(struct gv_drive));
|
|
|
|
strlcpy(d->name, drivename, sizeof(d->name));
|
|
strlcpy(d->device, device, sizeof(d->device));
|
|
gctl_ro_param(req, "drive0", sizeof(*d), d);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_ro_param(req, "drives", sizeof(int), &drives);
|
|
gctl_ro_param(req, "volumes", sizeof(int), &volumes);
|
|
gctl_ro_param(req, "plexes", sizeof(int), &plexes);
|
|
gctl_ro_param(req, "subdisks", sizeof(int), &subdisks);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("error creating drive: %s", errstr);
|
|
gctl_free(req);
|
|
return (NULL);
|
|
} else {
|
|
gctl_free(req);
|
|
/* XXX: This is needed because we have to make sure the drives
|
|
* are created before we return. */
|
|
/* Loop until it's in the config. */
|
|
for (i = 0; i < 100000; i++) {
|
|
dname = find_name("gvinumdrive", GV_TYPE_DRIVE,
|
|
GV_MAXDRIVENAME);
|
|
/* If we got a different name, quit. */
|
|
if (dname == NULL)
|
|
continue;
|
|
if (strcmp(dname, drivename)) {
|
|
free(dname);
|
|
return (drivename);
|
|
}
|
|
free(dname);
|
|
dname = NULL;
|
|
usleep(100000); /* Sleep for 0.1s */
|
|
}
|
|
}
|
|
gctl_free(req);
|
|
return (drivename);
|
|
}
|
|
|
|
/*
|
|
* General routine for creating a volume. Mainly for use by concat, mirror,
|
|
* raid5 and stripe commands.
|
|
*/
|
|
void
|
|
create_volume(int argc, char **argv, char *verb)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char buf[BUFSIZ], *drivename, *volname;
|
|
int drives, flags, i;
|
|
off_t stripesize;
|
|
|
|
flags = 0;
|
|
drives = 0;
|
|
volname = NULL;
|
|
stripesize = 262144;
|
|
|
|
/* XXX: Should we check for argument length? */
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (!strcmp(argv[i], "-f")) {
|
|
flags |= GV_FLAG_F;
|
|
} else if (!strcmp(argv[i], "-n")) {
|
|
volname = argv[++i];
|
|
} else if (!strcmp(argv[i], "-v")) {
|
|
flags |= GV_FLAG_V;
|
|
} else if (!strcmp(argv[i], "-s")) {
|
|
flags |= GV_FLAG_S;
|
|
if (!strcmp(verb, "raid5"))
|
|
stripesize = gv_sizespec(argv[++i]);
|
|
} else {
|
|
/* Assume it's a drive. */
|
|
snprintf(buf, sizeof(buf), "drive%d", drives++);
|
|
|
|
/* First we create the drive. */
|
|
drivename = create_drive(argv[i]);
|
|
if (drivename == NULL)
|
|
goto bad;
|
|
/* Then we add it to the request. */
|
|
gctl_ro_param(req, buf, -1, drivename);
|
|
}
|
|
}
|
|
|
|
gctl_ro_param(req, "stripesize", sizeof(off_t), &stripesize);
|
|
|
|
/* Find a free volume name. */
|
|
if (volname == NULL)
|
|
volname = find_name("gvinumvolume", GV_TYPE_VOL, GV_MAXVOLNAME);
|
|
|
|
/* Then we send a request to actually create the volumes. */
|
|
gctl_ro_param(req, "verb", -1, verb);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_ro_param(req, "drives", sizeof(int), &drives);
|
|
gctl_ro_param(req, "name", -1, volname);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("creating %s volume failed: %s", verb, errstr);
|
|
bad:
|
|
gctl_free(req);
|
|
}
|
|
|
|
/* Parse a line of the config, return the word after <pattern>. */
|
|
char *
|
|
find_pattern(char *line, char *pattern)
|
|
{
|
|
char *ptr;
|
|
|
|
ptr = strsep(&line, " ");
|
|
while (ptr != NULL) {
|
|
if (!strcmp(ptr, pattern)) {
|
|
/* Return the next. */
|
|
ptr = strsep(&line, " ");
|
|
return (ptr);
|
|
}
|
|
ptr = strsep(&line, " ");
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/* Find a free name for an object given a a prefix. */
|
|
char *
|
|
find_name(const char *prefix, int type, int namelen)
|
|
{
|
|
struct gctl_req *req;
|
|
char comment[1], buf[GV_CFG_LEN - 1], *name, *sname, *ptr;
|
|
const char *errstr;
|
|
int i, n, begin, len, conflict;
|
|
char line[1024];
|
|
|
|
comment[0] = '\0';
|
|
|
|
/* Find a name. Fetch out configuration first. */
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "getconfig");
|
|
gctl_ro_param(req, "comment", -1, comment);
|
|
gctl_rw_param(req, "config", sizeof(buf), buf);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't get configuration: %s", errstr);
|
|
return (NULL);
|
|
}
|
|
gctl_free(req);
|
|
|
|
begin = 0;
|
|
len = strlen(buf);
|
|
i = 0;
|
|
sname = malloc(namelen + 1);
|
|
|
|
/* XXX: Max object setting? */
|
|
for (n = 0; n < 10000; n++) {
|
|
snprintf(sname, namelen, "%s%d", prefix, n);
|
|
conflict = 0;
|
|
begin = 0;
|
|
/* Loop through the configuration line by line. */
|
|
for (i = 0; i < len; i++) {
|
|
if (buf[i] == '\n' || buf[i] == '\0') {
|
|
ptr = buf + begin;
|
|
strlcpy(line, ptr, (i - begin) + 1);
|
|
begin = i + 1;
|
|
switch (type) {
|
|
case GV_TYPE_DRIVE:
|
|
name = find_pattern(line, "drive");
|
|
break;
|
|
case GV_TYPE_VOL:
|
|
name = find_pattern(line, "volume");
|
|
break;
|
|
case GV_TYPE_PLEX:
|
|
case GV_TYPE_SD:
|
|
name = find_pattern(line, "name");
|
|
break;
|
|
default:
|
|
printf("Invalid type given\n");
|
|
continue;
|
|
}
|
|
if (name == NULL)
|
|
continue;
|
|
if (!strcmp(sname, name)) {
|
|
conflict = 1;
|
|
/* XXX: Could quit the loop earlier. */
|
|
}
|
|
}
|
|
}
|
|
if (!conflict)
|
|
return (sname);
|
|
}
|
|
free(sname);
|
|
return (NULL);
|
|
}
|
|
|
|
/* Detach a plex or subdisk from its parent. */
|
|
void
|
|
gvinum_detach(int argc, char **argv)
|
|
{
|
|
const char *errstr;
|
|
struct gctl_req *req;
|
|
int flags, i;
|
|
|
|
optreset = 1;
|
|
optind = 1;
|
|
while ((i = getopt(argc, argv, "f")) != -1) {
|
|
switch(i) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
default:
|
|
warn("invalid flag: %c", i);
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc != 1) {
|
|
warnx("usage: detach [-f] <subdisk> | <plex>");
|
|
return;
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "detach");
|
|
gctl_ro_param(req, "object", -1, argv[0]);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("detach failed: %s", errstr);
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_help(void)
|
|
{
|
|
printf("COMMANDS\n"
|
|
"checkparity [-f] plex\n"
|
|
" Check the parity blocks of a RAID-5 plex.\n"
|
|
"create [-f] description-file\n"
|
|
" Create as per description-file or open editor.\n"
|
|
"attach plex volume [rename]\n"
|
|
"attach subdisk plex [offset] [rename]\n"
|
|
" Attach a plex to a volume, or a subdisk to a plex\n"
|
|
"concat [-fv] [-n name] drives\n"
|
|
" Create a concatenated volume from the specified drives.\n"
|
|
"detach [-f] [plex | subdisk]\n"
|
|
" Detach a plex or a subdisk from the volume or plex to\n"
|
|
" which it is attached.\n"
|
|
"l | list [-r] [-v] [-V] [volume | plex | subdisk]\n"
|
|
" List information about specified objects.\n"
|
|
"ld [-r] [-v] [-V] [volume]\n"
|
|
" List information about drives.\n"
|
|
"ls [-r] [-v] [-V] [subdisk]\n"
|
|
" List information about subdisks.\n"
|
|
"lp [-r] [-v] [-V] [plex]\n"
|
|
" List information about plexes.\n"
|
|
"lv [-r] [-v] [-V] [volume]\n"
|
|
" List information about volumes.\n"
|
|
"mirror [-fsv] [-n name] drives\n"
|
|
" Create a mirrored volume from the specified drives.\n"
|
|
"move | mv -f drive object ...\n"
|
|
" Move the object(s) to the specified drive.\n"
|
|
"quit Exit the vinum program when running in interactive mode."
|
|
" Nor-\n"
|
|
" mally this would be done by entering the EOF character.\n"
|
|
"raid5 [-fv] [-s stripesize] [-n name] drives\n"
|
|
" Create a RAID-5 volume from the specified drives.\n"
|
|
"rename [-r] [drive | subdisk | plex | volume] newname\n"
|
|
" Change the name of the specified object.\n"
|
|
"rebuildparity plex [-f]\n"
|
|
" Rebuild the parity blocks of a RAID-5 plex.\n"
|
|
"resetconfig\n"
|
|
" Reset the complete gvinum configuration\n"
|
|
"rm [-r] [-f] volume | plex | subdisk | drive\n"
|
|
" Remove an object.\n"
|
|
"saveconfig\n"
|
|
" Save vinum configuration to disk after configuration"
|
|
" failures.\n"
|
|
"setstate [-f] state [volume | plex | subdisk | drive]\n"
|
|
" Set state without influencing other objects, for"
|
|
" diagnostic pur-\n"
|
|
" poses only.\n"
|
|
"start [-S size] volume | plex | subdisk\n"
|
|
" Allow the system to access the objects.\n"
|
|
"stripe [-fv] [-n name] drives\n"
|
|
" Create a striped volume from the specified drives.\n"
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
gvinum_setstate(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i;
|
|
const char *errstr;
|
|
|
|
flags = 0;
|
|
|
|
optreset = 1;
|
|
optind = 1;
|
|
|
|
while ((i = getopt(argc, argv, "f")) != -1) {
|
|
switch (i) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
case '?':
|
|
default:
|
|
warn("invalid flag: %c", i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 2) {
|
|
warnx("usage: setstate [-f] <state> <obj>");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* XXX: This hack is needed to avoid tripping over (now) invalid
|
|
* 'classic' vinum states and will go away later.
|
|
*/
|
|
if (strcmp(argv[0], "up") && strcmp(argv[0], "down") &&
|
|
strcmp(argv[0], "stale")) {
|
|
warnx("invalid state '%s'", argv[0]);
|
|
return;
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "setstate");
|
|
gctl_ro_param(req, "state", -1, argv[0]);
|
|
gctl_ro_param(req, "object", -1, argv[1]);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("%s", errstr);
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_list(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i, j;
|
|
const char *errstr;
|
|
char buf[20], *cmd, config[GV_CFG_LEN + 1];
|
|
|
|
flags = 0;
|
|
cmd = "list";
|
|
|
|
if (argc) {
|
|
optreset = 1;
|
|
optind = 1;
|
|
cmd = argv[0];
|
|
while ((j = getopt(argc, argv, "rsvV")) != -1) {
|
|
switch (j) {
|
|
case 'r':
|
|
flags |= GV_FLAG_R;
|
|
break;
|
|
case 's':
|
|
flags |= GV_FLAG_S;
|
|
break;
|
|
case 'v':
|
|
flags |= GV_FLAG_V;
|
|
break;
|
|
case 'V':
|
|
flags |= GV_FLAG_V;
|
|
flags |= GV_FLAG_VV;
|
|
break;
|
|
case '?':
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "list");
|
|
gctl_ro_param(req, "cmd", -1, cmd);
|
|
gctl_ro_param(req, "argc", sizeof(int), &argc);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_rw_param(req, "config", sizeof(config), config);
|
|
if (argc) {
|
|
for (i = 0; i < argc; i++) {
|
|
snprintf(buf, sizeof(buf), "argv%d", i);
|
|
gctl_ro_param(req, buf, -1, argv[i]);
|
|
}
|
|
}
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't get configuration: %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
|
|
printf("%s", config);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
|
|
/* Create a mirrored volume. */
|
|
void
|
|
gvinum_mirror(int argc, char **argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage\tmirror [-fsv] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "mirror");
|
|
}
|
|
|
|
/* Note that move is currently of form '[-r] target object [...]' */
|
|
void
|
|
gvinum_move(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char buf[20];
|
|
int flags, i, j;
|
|
|
|
flags = 0;
|
|
if (argc) {
|
|
optreset = 1;
|
|
optind = 1;
|
|
while ((j = getopt(argc, argv, "f")) != -1) {
|
|
switch (j) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
case '?':
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
}
|
|
|
|
switch (argc) {
|
|
case 0:
|
|
warnx("no destination or object(s) to move specified");
|
|
return;
|
|
case 1:
|
|
warnx("no object(s) to move specified");
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "move");
|
|
gctl_ro_param(req, "argc", sizeof(int), &argc);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_ro_param(req, "destination", -1, argv[0]);
|
|
for (i = 1; i < argc; i++) {
|
|
snprintf(buf, sizeof(buf), "argv%d", i);
|
|
gctl_ro_param(req, buf, -1, argv[i]);
|
|
}
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("can't move object(s): %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
|
|
void
|
|
gvinum_printconfig(int argc, char **argv)
|
|
{
|
|
printconfig(stdout, "");
|
|
}
|
|
|
|
void
|
|
gvinum_parityop(int argc, char **argv, int rebuild)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i;
|
|
const char *errstr;
|
|
char *op, *msg;
|
|
|
|
if (rebuild) {
|
|
op = "rebuildparity";
|
|
msg = "Rebuilding";
|
|
} else {
|
|
op = "checkparity";
|
|
msg = "Checking";
|
|
}
|
|
|
|
optreset = 1;
|
|
optind = 1;
|
|
flags = 0;
|
|
while ((i = getopt(argc, argv, "fv")) != -1) {
|
|
switch (i) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
case 'v':
|
|
flags |= GV_FLAG_V;
|
|
break;
|
|
case '?':
|
|
default:
|
|
warnx("invalid flag '%c'", i);
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 1) {
|
|
warn("usage: %s [-f] [-v] <plex>", op);
|
|
return;
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, op);
|
|
gctl_ro_param(req, "rebuild", sizeof(int), &rebuild);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_ro_param(req, "plex", -1, argv[0]);
|
|
|
|
errstr = gctl_issue(req);
|
|
if (errstr)
|
|
warnx("%s\n", errstr);
|
|
gctl_free(req);
|
|
}
|
|
|
|
/* Create a RAID-5 volume. */
|
|
void
|
|
gvinum_raid5(int argc, char **argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "raid5");
|
|
}
|
|
|
|
|
|
void
|
|
gvinum_rename(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
int flags, j;
|
|
|
|
flags = 0;
|
|
|
|
if (argc) {
|
|
optreset = 1;
|
|
optind = 1;
|
|
while ((j = getopt(argc, argv, "r")) != -1) {
|
|
switch (j) {
|
|
case 'r':
|
|
flags |= GV_FLAG_R;
|
|
break;
|
|
case '?':
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
}
|
|
|
|
switch (argc) {
|
|
case 0:
|
|
warnx("no object to rename specified");
|
|
return;
|
|
case 1:
|
|
warnx("no new name specified");
|
|
return;
|
|
case 2:
|
|
break;
|
|
default:
|
|
warnx("more than one new name specified");
|
|
return;
|
|
}
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "rename");
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
gctl_ro_param(req, "object", -1, argv[0]);
|
|
gctl_ro_param(req, "newname", -1, argv[1]);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("can't rename object: %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
|
|
void
|
|
gvinum_rm(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i, j;
|
|
const char *errstr;
|
|
char buf[20], *cmd;
|
|
|
|
cmd = argv[0];
|
|
flags = 0;
|
|
optreset = 1;
|
|
optind = 1;
|
|
while ((j = getopt(argc, argv, "rf")) != -1) {
|
|
switch (j) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
case 'r':
|
|
flags |= GV_FLAG_R;
|
|
break;
|
|
case '?':
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "remove");
|
|
gctl_ro_param(req, "argc", sizeof(int), &argc);
|
|
gctl_ro_param(req, "flags", sizeof(int), &flags);
|
|
if (argc) {
|
|
for (i = 0; i < argc; i++) {
|
|
snprintf(buf, sizeof(buf), "argv%d", i);
|
|
gctl_ro_param(req, buf, -1, argv[i]);
|
|
}
|
|
}
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't remove: %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_resetconfig(void)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char reply[32];
|
|
|
|
if (!isatty(STDIN_FILENO)) {
|
|
warn("Please enter this command from a tty device\n");
|
|
return;
|
|
}
|
|
printf(" WARNING! This command will completely wipe out your gvinum"
|
|
"configuration.\n"
|
|
" All data will be lost. If you really want to do this,"
|
|
" enter the text\n\n"
|
|
" NO FUTURE\n"
|
|
" Enter text -> ");
|
|
fgets(reply, sizeof(reply), stdin);
|
|
if (strcmp(reply, "NO FUTURE\n")) {
|
|
printf("\n No change\n");
|
|
return;
|
|
}
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "resetconfig");
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't reset config: %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
gctl_free(req);
|
|
printf("gvinum configuration obliterated\n");
|
|
}
|
|
|
|
void
|
|
gvinum_saveconfig(void)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "saveconfig");
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL)
|
|
warnx("can't save configuration: %s", errstr);
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_start(int argc, char **argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int i, initsize, j;
|
|
const char *errstr;
|
|
char buf[20];
|
|
|
|
/* 'start' with no arguments is a no-op. */
|
|
if (argc == 1)
|
|
return;
|
|
|
|
initsize = 0;
|
|
|
|
optreset = 1;
|
|
optind = 1;
|
|
while ((j = getopt(argc, argv, "S")) != -1) {
|
|
switch (j) {
|
|
case 'S':
|
|
initsize = atoi(optarg);
|
|
break;
|
|
case '?':
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (!initsize)
|
|
initsize = 512;
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "start");
|
|
gctl_ro_param(req, "argc", sizeof(int), &argc);
|
|
gctl_ro_param(req, "initsize", sizeof(int), &initsize);
|
|
if (argc) {
|
|
for (i = 0; i < argc; i++) {
|
|
snprintf(buf, sizeof(buf), "argv%d", i);
|
|
gctl_ro_param(req, buf, -1, argv[i]);
|
|
}
|
|
}
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't start: %s", errstr);
|
|
gctl_free(req);
|
|
return;
|
|
}
|
|
|
|
gctl_free(req);
|
|
}
|
|
|
|
void
|
|
gvinum_stop(int argc, char **argv)
|
|
{
|
|
int err, fileid;
|
|
|
|
fileid = kldfind(GVINUMMOD);
|
|
if (fileid == -1) {
|
|
warn("cannot find " GVINUMMOD);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This little hack prevents that we end up in an infinite loop in
|
|
* g_unload_class(). gv_unload() will return EAGAIN so that the GEOM
|
|
* event thread will be free for the g_wither_geom() call from
|
|
* gv_unload(). It's silly, but it works.
|
|
*/
|
|
printf("unloading " GVINUMMOD " kernel module... ");
|
|
fflush(stdout);
|
|
if ((err = kldunload(fileid)) != 0 && (errno == EAGAIN)) {
|
|
sleep(1);
|
|
err = kldunload(fileid);
|
|
}
|
|
if (err != 0) {
|
|
printf(" failed!\n");
|
|
warn("cannot unload " GVINUMMOD);
|
|
return;
|
|
}
|
|
|
|
printf("done\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* Create a striped volume. */
|
|
void
|
|
gvinum_stripe(int argc, char **argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\tstripe [-fv] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "stripe");
|
|
}
|
|
|
|
void
|
|
parseline(int argc, char **argv)
|
|
{
|
|
if (argc <= 0)
|
|
return;
|
|
|
|
if (!strcmp(argv[0], "create"))
|
|
gvinum_create(argc, argv);
|
|
else if (!strcmp(argv[0], "exit") || !strcmp(argv[0], "quit"))
|
|
exit(0);
|
|
else if (!strcmp(argv[0], "attach"))
|
|
gvinum_attach(argc, argv);
|
|
else if (!strcmp(argv[0], "detach"))
|
|
gvinum_detach(argc, argv);
|
|
else if (!strcmp(argv[0], "concat"))
|
|
gvinum_concat(argc, argv);
|
|
else if (!strcmp(argv[0], "help"))
|
|
gvinum_help();
|
|
else if (!strcmp(argv[0], "list") || !strcmp(argv[0], "l"))
|
|
gvinum_list(argc, argv);
|
|
else if (!strcmp(argv[0], "ld"))
|
|
gvinum_list(argc, argv);
|
|
else if (!strcmp(argv[0], "lp"))
|
|
gvinum_list(argc, argv);
|
|
else if (!strcmp(argv[0], "ls"))
|
|
gvinum_list(argc, argv);
|
|
else if (!strcmp(argv[0], "lv"))
|
|
gvinum_list(argc, argv);
|
|
else if (!strcmp(argv[0], "mirror"))
|
|
gvinum_mirror(argc, argv);
|
|
else if (!strcmp(argv[0], "move"))
|
|
gvinum_move(argc, argv);
|
|
else if (!strcmp(argv[0], "mv"))
|
|
gvinum_move(argc, argv);
|
|
else if (!strcmp(argv[0], "printconfig"))
|
|
gvinum_printconfig(argc, argv);
|
|
else if (!strcmp(argv[0], "raid5"))
|
|
gvinum_raid5(argc, argv);
|
|
else if (!strcmp(argv[0], "rename"))
|
|
gvinum_rename(argc, argv);
|
|
else if (!strcmp(argv[0], "resetconfig"))
|
|
gvinum_resetconfig();
|
|
else if (!strcmp(argv[0], "rm"))
|
|
gvinum_rm(argc, argv);
|
|
else if (!strcmp(argv[0], "saveconfig"))
|
|
gvinum_saveconfig();
|
|
else if (!strcmp(argv[0], "setstate"))
|
|
gvinum_setstate(argc, argv);
|
|
else if (!strcmp(argv[0], "start"))
|
|
gvinum_start(argc, argv);
|
|
else if (!strcmp(argv[0], "stop"))
|
|
gvinum_stop(argc, argv);
|
|
else if (!strcmp(argv[0], "stripe"))
|
|
gvinum_stripe(argc, argv);
|
|
else if (!strcmp(argv[0], "checkparity"))
|
|
gvinum_parityop(argc, argv, 0);
|
|
else if (!strcmp(argv[0], "rebuildparity"))
|
|
gvinum_parityop(argc, argv, 1);
|
|
else
|
|
printf("unknown command '%s'\n", argv[0]);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The guts of printconfig. This is called from gvinum_printconfig and from
|
|
* gvinum_create when called without an argument, in order to give the user
|
|
* something to edit.
|
|
*/
|
|
void
|
|
printconfig(FILE *of, char *comment)
|
|
{
|
|
struct gctl_req *req;
|
|
struct utsname uname_s;
|
|
const char *errstr;
|
|
time_t now;
|
|
char buf[GV_CFG_LEN + 1];
|
|
|
|
uname(&uname_s);
|
|
time(&now);
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "getconfig");
|
|
gctl_ro_param(req, "comment", -1, comment);
|
|
gctl_rw_param(req, "config", sizeof(buf), buf);
|
|
errstr = gctl_issue(req);
|
|
if (errstr != NULL) {
|
|
warnx("can't get configuration: %s", errstr);
|
|
return;
|
|
}
|
|
gctl_free(req);
|
|
|
|
fprintf(of, "# Vinum configuration of %s, saved at %s",
|
|
uname_s.nodename,
|
|
ctime(&now));
|
|
|
|
if (*comment != '\0')
|
|
fprintf(of, "# Current configuration:\n");
|
|
|
|
fprintf(of, buf);
|
|
}
|