diff --git a/sbin/gvinum/Makefile b/sbin/gvinum/Makefile new file mode 100644 index 000000000000..f56580d94d08 --- /dev/null +++ b/sbin/gvinum/Makefile @@ -0,0 +1,14 @@ +# $FreeBSD$ + +PROG= gvinum +SRCS= gvinum.c gvinum.h geom_vinum_share.c +NOMAN= notyet + +CFLAGS+= -I${.CURDIR}/../../sys + +DPADD= ${LIBREADLINE} ${LIBTERMCAP} ${LIBDEVSTAT} ${LIBKVM} ${LIBGEOM} +LDADD= -lreadline -ltermcap -ldevstat -lkvm -lgeom + +.PATH: ${.CURDIR}/../../sys/geom/vinum + +.include diff --git a/sbin/gvinum/gvinum.c b/sbin/gvinum/gvinum.c new file mode 100644 index 000000000000..2757afdef85d --- /dev/null +++ b/sbin/gvinum/gvinum.c @@ -0,0 +1,758 @@ +/* + * Copyright (c) 2004 Lukas Ertl + * All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gvinum.h" + +void gvinum_cancelinit(int, char **); +void gvinum_create(int, char **); +void gvinum_help(void); +void gvinum_init(int, char **); +void gvinum_list(int, char **); +void gvinum_printconfig(int, char **); +void gvinum_rm(int, char **); +void gvinum_saveconfig(void); +void gvinum_start(int, char **); +void gvinum_stop(int, char **); +void parseline(int, char **); +void printconfig(FILE *, 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); +} + +void +gvinum_cancelinit(int argc, char **argv) +{ + struct gctl_req *req; + int i; + const char *errstr; + char buf[20]; + + if (argc == 1) + return; + + argc--; + argv++; + + req = gctl_get_handle(); + gctl_ro_param(req, "class", -1, "VINUM"); + gctl_ro_param(req, "verb", -1, "cancelinit"); + gctl_ro_param(req, "argc", sizeof(int), &argc); + 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 init: %s", errstr); + gctl_free(req); + return; + } + + gctl_free(req); + gvinum_list(0, NULL); +} + +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, line, plexes, plex_in_volume; + int sd_in_plex, status, subdisks, tokens, volumes; + const char *errstr; + char buf[BUFSIZ], buf1[BUFSIZ], commandline[BUFSIZ], *ed; + char original[BUFSIZ], tmpfile[20], *token[GV_MAXARGS]; + char plex[GV_MAXPLEXNAME], volume[GV_MAXVOLNAME]; + + 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); + } + + req = gctl_get_handle(); + gctl_ro_param(req, "class", -1, "VINUM"); + gctl_ro_param(req, "verb", -1, "create"); + + drives = volumes = plexes = subdisks = 0; + plex_in_volume = sd_in_plex = 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. + */ + strncpy(original, buf, sizeof(buf)); + + tokens = gv_tokenize(buf, token, GV_MAXARGS); + + if (tokens > 0) { + /* 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++; + } else { + /* Reset plex count for this volume. */ + plex_in_volume = 0; + + /* + * Set default volume name for + * following plex definitions. + */ + strncpy(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++; + } else { + /* Reset subdisk count for this plex. */ + sd_in_plex = 0; + + /* Default name. */ + if (strlen(p->name) == 0) { + snprintf(p->name, + GV_MAXPLEXNAME, + "%s.p%d", volume, + plex_in_volume++); + } + + /* Default volume. */ + if (strlen(p->volume) == 0) { + snprintf(p->volume, + GV_MAXVOLNAME, "%s", + volume); + } + + /* + * Set default plex name for following + * subdisk definitions. + */ + strncpy(plex, p->name, GV_MAXPLEXNAME); + + 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++; + } else { + /* Default name. */ + if (strlen(s->name) == 0) { + snprintf(s->name, GV_MAXSDNAME, + "%s.s%d", plex, + sd_in_plex++); + } + + /* Default plex. */ + if (strlen(s->plex) == 0) { + snprintf(s->plex, + GV_MAXPLEXNAME, "%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++; + } else { + 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); + gvinum_list(0, NULL); +} + +void +gvinum_help(void) +{ + printf("COMMANDS\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" + "checkparity plex [-f] [-v]\n" + " Check the parity blocks of a RAID-4 or RAID-5 plex.\n" + "concat [-f] [-n name] [-v] drives\n" + " Create a concatenated volume from the specified drives.\n" + "create [-f] description-file\n" + " Create a volume as described in description-file.\n" + "detach [-f] [plex | subdisk]\n" + " Detach a plex or subdisk from the volume or plex to" + "which it is\n" + " attached.\n" + "dumpconfig [drive ...]\n" + " List the configuration information stored on the" + " specified\n" + " drives, or all drives in the system if no drive names" + " are speci-\n" + " fied.\n" + "info [-v] [-V]\n" + " List information about volume manager state.\n" + "init [-S size] [-w] plex | subdisk\n" + " Initialize the contents of a subdisk or all the subdisks" + " of a\n" + " plex to all zeros.\n" + "label volume\n" + " Create a volume label.\n" + "l | list [-r] [-s] [-v] [-V] [volume | plex | subdisk]\n" + " List information about specified objects.\n" + "ld [-r] [-s] [-v] [-V] [volume]\n" + " List information about drives.\n" + "ls [-r] [-s] [-v] [-V] [subdisk]\n" + " List information about subdisks.\n" + "lp [-r] [-s] [-v] [-V] [plex]\n" + " List information about plexes.\n" + "lv [-r] [-s] [-v] [-V] [volume]\n" + " List information about volumes.\n" + "mirror [-f] [-n name] [-s] [-v] 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" + "printconfig [file]\n" + " Write a copy of the current configuration to file.\n" + "quit Exit the vinum program when running in interactive mode." + " Nor-\n" + " mally this would be done by entering the EOF character.\n" + "rename [-r] [drive | subdisk | plex | volume] newname\n" + " Change the name of the specified object.\n" + "rebuildparity plex [-f] [-v] [-V]\n" + " Rebuild the parity blocks of a RAID-4 or RAID-5 plex.\n" + "resetconfig\n" + " Reset the complete vinum configuration.\n" + "rm [-f] [-r] volume | plex | subdisk\n" + " Remove an object.\n" + "saveconfig\n" + " Save vinum configuration to disk after configuration" + " failures.\n" + "setstate state [volume | plex | subdisk | drive]\n" + " Set state without influencing other objects, for" + " diagnostic pur-\n" + " poses only.\n" + "start [-i interval] [-S size] [-w] volume | plex | subdisk\n" + " Allow the system to access the objects.\n" + "stop [-f] [volume | plex | subdisk]\n" + " Terminate access to the objects, or stop vinum if no" + " parameters\n" + " are specified.\n" + "stripe [-f] [-n name] [-v] drives\n" + " Create a striped volume from the specified drives.\n" + ); + + return; +} + +void +gvinum_init(int argc, char **argv) +{ + struct gctl_req *req; + int i, initsize, j; + const char *errstr; + char buf[20]; + + 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, "init"); + 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 init: %s", errstr); + gctl_free(req); + return; + } + + gctl_free(req); + gvinum_list(0, NULL); +} + +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; +} + +void +gvinum_printconfig(int argc, char **argv) +{ + printconfig(stdout, ""); +} + +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, "r")) != -1) { + switch (j) { + 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); + gvinum_list(0, NULL); +} + +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); + gvinum_list(0, NULL); +} + +void +gvinum_stop(int argc, char **argv) +{ + int fileid; + + fileid = kldfind(GVINUMMOD); + if (fileid == -1) { + warn("cannot find " GVINUMMOD); + return; + } + if (kldunload(fileid) != 0) { + warn("cannot unload " GVINUMMOD); + return; + } + + warnx(GVINUMMOD " unloaded"); + exit(0); +} + +void +parseline(int argc, char **argv) +{ + if (argc <= 0) + return; + + if (!strcmp(argv[0], "cancelinit")) + gvinum_cancelinit(argc, argv); + else 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], "help")) + gvinum_help(); + else if (!strcmp(argv[0], "init")) + gvinum_init(argc, argv); + 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], "printconfig")) + gvinum_printconfig(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], "start")) + gvinum_start(argc, argv); + else if (!strcmp(argv[0], "stop")) + gvinum_stop(argc, argv); + 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); +} diff --git a/sbin/gvinum/gvinum.h b/sbin/gvinum/gvinum.h new file mode 100644 index 000000000000..d1b45a07749b --- /dev/null +++ b/sbin/gvinum/gvinum.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 1997, 1998 + * Nan Yang Computer Services Limited. All rights reserved. + * + * This software is distributed under the so-called ``Berkeley + * License'': + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Nan Yang Computer + * Services Limited. + * 4. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided ``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 the company 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$ */ + +#define GVINUMMOD "geom_vinum"