2117cdd4b4
Make gctl_add_param() API public, allowing more precise control over parameter flags. Previously it was impossible to properly declare write-only ASCII parameters, used for result reporting, they were declared as read-write binary instead, that was not nice. MFC after: 1 month
1453 lines
35 KiB
C
1453 lines
35 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* 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 <string.h>
|
|
#include <paths.h>
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gvinum.h"
|
|
|
|
static void gvinum_attach(int, char * const *);
|
|
static void gvinum_concat(int, char * const *);
|
|
static void gvinum_create(int, char * const *);
|
|
static void gvinum_detach(int, char * const *);
|
|
static void gvinum_grow(int, char * const *);
|
|
static void gvinum_help(void);
|
|
static void gvinum_list(int, char * const *);
|
|
static void gvinum_move(int, char * const *);
|
|
static void gvinum_mirror(int, char * const *);
|
|
static void gvinum_parityop(int, char * const * , int);
|
|
static void gvinum_printconfig(int, char * const *);
|
|
static void gvinum_raid5(int, char * const *);
|
|
static void gvinum_rename(int, char * const *);
|
|
static void gvinum_resetconfig(int, char * const *);
|
|
static void gvinum_rm(int, char * const *);
|
|
static void gvinum_saveconfig(void);
|
|
static void gvinum_setstate(int, char * const *);
|
|
static void gvinum_start(int, char * const *);
|
|
static void gvinum_stop(int, char * const *);
|
|
static void gvinum_stripe(int, char * const *);
|
|
static void parseline(int, char * const *);
|
|
static void printconfig(FILE *, const char *);
|
|
|
|
static char *create_drive(const char *);
|
|
static void create_volume(int, char * const * , const char *);
|
|
static char *find_name(const char *, int, int);
|
|
static const char *find_pattern(char *, const char *);
|
|
static void copy_device(struct gv_drive *, const char *);
|
|
#define find_drive() \
|
|
find_name("gvinumdrive", GV_TYPE_DRIVE, GV_MAXDRIVENAME)
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int tokens;
|
|
char buffer[BUFSIZ], *inputline, *token[GV_MAXARGS];
|
|
|
|
/* Load the module if necessary. */
|
|
if (modfind(GVINUMMOD) < 0) {
|
|
if (kldload(GVINUMKLD) < 0 && modfind(GVINUMMOD) < 0)
|
|
err(1, GVINUMKLD ": 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);
|
|
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. */
|
|
static void
|
|
gvinum_attach(int argc, char * const *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);
|
|
}
|
|
|
|
static void
|
|
gvinum_create(int argc, char * const *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], *sdname;
|
|
const char *ed;
|
|
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[i], "r")) == NULL) {
|
|
warn("can't open '%s' for reading", argv[i]);
|
|
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. */
|
|
static void
|
|
gvinum_concat(int argc, char * const *argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\tconcat [-fv] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "concat");
|
|
}
|
|
|
|
/* Create a drive quick and dirty. */
|
|
static char *
|
|
create_drive(const char *device)
|
|
{
|
|
struct gv_drive *d;
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char *drivename, *dname;
|
|
int drives, i, flags, volumes, subdisks, plexes;
|
|
int found = 0;
|
|
|
|
flags = plexes = subdisks = volumes = 0;
|
|
drives = 1;
|
|
dname = NULL;
|
|
|
|
drivename = find_drive();
|
|
if (drivename == NULL)
|
|
return (NULL);
|
|
|
|
req = gctl_get_handle();
|
|
gctl_ro_param(req, "class", -1, "VINUM");
|
|
gctl_ro_param(req, "verb", -1, "create");
|
|
d = gv_alloc_drive();
|
|
if (d == NULL)
|
|
err(1, "unable to allocate for gv_drive object");
|
|
|
|
strlcpy(d->name, drivename, sizeof(d->name));
|
|
copy_device(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);
|
|
drivename = NULL;
|
|
} else {
|
|
/* 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))
|
|
found = 1;
|
|
free(dname);
|
|
dname = NULL;
|
|
if (found)
|
|
break;
|
|
usleep(100000); /* Sleep for 0.1s */
|
|
}
|
|
if (found == 0) {
|
|
warnx("error creating drive");
|
|
drivename = NULL;
|
|
}
|
|
}
|
|
gctl_free(req);
|
|
return (drivename);
|
|
}
|
|
|
|
/*
|
|
* General routine for creating a volume. Mainly for use by concat, mirror,
|
|
* raid5 and stripe commands.
|
|
*/
|
|
static void
|
|
create_volume(int argc, char * const *argv, const 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>. */
|
|
static const char *
|
|
find_pattern(char *line, const 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 prefix. */
|
|
static char *
|
|
find_name(const char *prefix, int type, int namelen)
|
|
{
|
|
struct gctl_req *req;
|
|
char comment[1], buf[GV_CFG_LEN - 1], *sname, *ptr;
|
|
const char *errstr, *name;
|
|
int i, n, begin, len, conflict;
|
|
char line[1024];
|
|
|
|
comment[0] = '\0';
|
|
buf[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_add_param(req, "config", sizeof(buf), buf,
|
|
GCTL_PARAM_WR | GCTL_PARAM_ASCII);
|
|
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);
|
|
}
|
|
|
|
static void
|
|
copy_device(struct gv_drive *d, const char *device)
|
|
{
|
|
|
|
if (strncmp(device, "/dev/", 5) == 0)
|
|
strlcpy(d->device, (device + 5), sizeof(d->device));
|
|
else
|
|
strlcpy(d->device, device, sizeof(d->device));
|
|
}
|
|
|
|
/* Detach a plex or subdisk from its parent. */
|
|
static void
|
|
gvinum_detach(int argc, char * const *argv)
|
|
{
|
|
const char *errstr;
|
|
struct gctl_req *req;
|
|
int flags, i;
|
|
|
|
flags = 0;
|
|
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);
|
|
}
|
|
|
|
static 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"
|
|
"grow plex drive\n"
|
|
" Grow plex by creating a properly sized subdisk on drive\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 [-f]\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"
|
|
);
|
|
}
|
|
|
|
static void
|
|
gvinum_setstate(int argc, char * const *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);
|
|
}
|
|
|
|
static void
|
|
gvinum_list(int argc, char * const *argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i, j;
|
|
const char *errstr;
|
|
char buf[20], config[GV_CFG_LEN + 1];
|
|
const char *cmd;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
config[0] = '\0';
|
|
|
|
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_add_param(req, "config", sizeof(config), config,
|
|
GCTL_PARAM_WR | GCTL_PARAM_ASCII);
|
|
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);
|
|
}
|
|
|
|
/* Create a mirrored volume. */
|
|
static void
|
|
gvinum_mirror(int argc, char * const *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 [...]' */
|
|
static void
|
|
gvinum_move(int argc, char * const *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);
|
|
}
|
|
|
|
static void
|
|
gvinum_printconfig(int argc __unused, char * const *argv __unused)
|
|
{
|
|
|
|
printconfig(stdout, "");
|
|
}
|
|
|
|
static void
|
|
gvinum_parityop(int argc, char * const *argv, int rebuild)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i;
|
|
const char *errstr;
|
|
const char *op;
|
|
|
|
if (rebuild) {
|
|
op = "rebuildparity";
|
|
} else {
|
|
op = "checkparity";
|
|
}
|
|
|
|
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;
|
|
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. */
|
|
static void
|
|
gvinum_raid5(int argc, char * const *argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\traid5 [-fv] [-s stripesize] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "raid5");
|
|
}
|
|
|
|
static void
|
|
gvinum_rename(int argc, char * const *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;
|
|
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);
|
|
}
|
|
|
|
static void
|
|
gvinum_rm(int argc, char * const *argv)
|
|
{
|
|
struct gctl_req *req;
|
|
int flags, i, j;
|
|
const char *errstr;
|
|
char buf[20];
|
|
|
|
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;
|
|
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);
|
|
}
|
|
|
|
static void
|
|
gvinum_resetconfig(int argc, char * const *argv)
|
|
{
|
|
struct gctl_req *req;
|
|
const char *errstr;
|
|
char reply[32];
|
|
int flags, i;
|
|
|
|
flags = 0;
|
|
while ((i = getopt(argc, argv, "f")) != -1) {
|
|
switch (i) {
|
|
case 'f':
|
|
flags |= GV_FLAG_F;
|
|
break;
|
|
default:
|
|
warn("invalid flag: %c", i);
|
|
return;
|
|
}
|
|
}
|
|
if ((flags & GV_FLAG_F) == 0) {
|
|
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");
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
static void
|
|
gvinum_start(int argc, char * const *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;
|
|
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);
|
|
}
|
|
|
|
static void
|
|
gvinum_stop(int argc __unused, char * const *argv __unused)
|
|
{
|
|
int err, fileid;
|
|
|
|
fileid = kldfind(GVINUMKLD);
|
|
if (fileid == -1) {
|
|
if (modfind(GVINUMMOD) < 0)
|
|
warn("cannot find " GVINUMKLD);
|
|
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 " GVINUMKLD " 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 " GVINUMKLD);
|
|
return;
|
|
}
|
|
|
|
printf("done\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* Create a striped volume. */
|
|
static void
|
|
gvinum_stripe(int argc, char * const *argv)
|
|
{
|
|
|
|
if (argc < 2) {
|
|
warnx("usage:\tstripe [-fv] [-n name] drives\n");
|
|
return;
|
|
}
|
|
create_volume(argc, argv, "stripe");
|
|
}
|
|
|
|
/* Grow a subdisk by adding disk backed by provider. */
|
|
static void
|
|
gvinum_grow(int argc, char * const *argv)
|
|
{
|
|
struct gctl_req *req;
|
|
char *drive, *sdname;
|
|
char sdprefix[GV_MAXSDNAME];
|
|
struct gv_drive *d;
|
|
struct gv_sd *s;
|
|
const char *errstr;
|
|
int drives, volumes, plexes, subdisks, flags;
|
|
|
|
flags = 0;
|
|
drives = volumes = plexes = subdisks = 0;
|
|
if (argc < 3) {
|
|
warnx("usage:\tgrow plex drive\n");
|
|
return;
|
|
}
|
|
|
|
s = gv_alloc_sd();
|
|
if (s == NULL) {
|
|
warn("unable to create subdisk");
|
|
return;
|
|
}
|
|
d = gv_alloc_drive();
|
|
if (d == NULL) {
|
|
warn("unable to create drive");
|
|
free(s);
|
|
return;
|
|
}
|
|
/* Lookup device and set an appropriate drive name. */
|
|
drive = find_drive();
|
|
if (drive == NULL) {
|
|
warn("unable to find an appropriate drive name");
|
|
free(s);
|
|
free(d);
|
|
return;
|
|
}
|
|
strlcpy(d->name, drive, sizeof(d->name));
|
|
copy_device(d, argv[2]);
|
|
|
|
drives = 1;
|
|
|
|
/* We try to use the plex name as basis for the subdisk name. */
|
|
snprintf(sdprefix, sizeof(sdprefix), "%s.s", argv[1]);
|
|
sdname = find_name(sdprefix, GV_TYPE_SD, GV_MAXSDNAME);
|
|
if (sdname == NULL) {
|
|
warn("unable to find an appropriate subdisk name");
|
|
free(s);
|
|
free(d);
|
|
free(drive);
|
|
return;
|
|
}
|
|
strlcpy(s->name, sdname, sizeof(s->name));
|
|
free(sdname);
|
|
strlcpy(s->plex, argv[1], sizeof(s->plex));
|
|
strlcpy(s->drive, d->name, sizeof(s->drive));
|
|
subdisks = 1;
|
|
|
|
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);
|
|
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);
|
|
gctl_ro_param(req, "drive0", sizeof(*d), d);
|
|
gctl_ro_param(req, "sd0", sizeof(*s), s);
|
|
errstr = gctl_issue(req);
|
|
free(drive);
|
|
if (errstr != NULL) {
|
|
warnx("unable to grow plex: %s", errstr);
|
|
free(s);
|
|
free(d);
|
|
return;
|
|
}
|
|
gctl_free(req);
|
|
}
|
|
|
|
static void
|
|
parseline(int argc, char * const *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], "grow"))
|
|
gvinum_grow(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(argc, argv);
|
|
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]);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static void
|
|
printconfig(FILE *of, const 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);
|
|
buf[0] = '\0';
|
|
|
|
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_add_param(req, "config", sizeof(buf), buf,
|
|
GCTL_PARAM_WR | GCTL_PARAM_ASCII);
|
|
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, "%s", buf);
|
|
}
|