diff --git a/sbin/gvinum/gvinum.c b/sbin/gvinum/gvinum.c index 524a08ebbab2..bb3166c6ff64 100644 --- a/sbin/gvinum/gvinum.c +++ b/sbin/gvinum/gvinum.c @@ -1,855 +1,913 @@ /* * 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_parityop(int, char **, int); void gvinum_printconfig(int, char **); 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 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]; if (argc == 2) { if ((tmp = fopen(argv[1], "r")) == NULL) { warn("can't open '%s' for reading", argv[1]); return; } } else { 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"); 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_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] "); + 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; } 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, rv; off_t offset; 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] ", op); return; } do { rv = 0; req = gctl_get_handle(); gctl_ro_param(req, "class", -1, "VINUM"); gctl_ro_param(req, "verb", -1, "parityop"); gctl_ro_param(req, "flags", sizeof(int), &flags); gctl_ro_param(req, "rebuild", sizeof(int), &rebuild); gctl_rw_param(req, "rv", sizeof(int), &rv); gctl_rw_param(req, "offset", sizeof(off_t), &offset); gctl_ro_param(req, "plex", -1, argv[0]); errstr = gctl_issue(req); if (errstr) { warnx("%s\n", errstr); gctl_free(req); break; } gctl_free(req); if (flags & GV_FLAG_V) { printf("\r%s at %s ... ", msg, gv_roughlength(offset, 1)); } if (rv == 1) { printf("Parity incorrect at offset 0x%jx\n", (intmax_t)offset); if (!rebuild) break; } fflush(stdout); /* Clear the -f flag. */ flags &= ~GV_FLAG_F; } while (rv >= 0); if ((rv == 2) && (flags & GV_FLAG_V)) { if (rebuild) printf("Rebuilt parity on %s\n", argv[0]); else printf("%s has correct parity\n", argv[0]); } } 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], "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], "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); } diff --git a/sys/geom/vinum/geom_vinum.c b/sys/geom/vinum/geom_vinum.c index 5a54bee58729..7b5b456abddb 100644 --- a/sys/geom/vinum/geom_vinum.c +++ b/sys/geom/vinum/geom_vinum.c @@ -1,612 +1,615 @@ /* * 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #if 0 SYSCTL_DECL(_kern_geom); SYSCTL_NODE(_kern_geom, OID_AUTO, vinum, CTLFLAG_RW, 0, "GEOM_VINUM stuff"); SYSCTL_UINT(_kern_geom_vinum, OID_AUTO, debug, CTLFLAG_RW, &gv_debug, 0, "Debug level"); #endif int gv_create(struct g_geom *, struct gctl_req *); static void gv_orphan(struct g_consumer *cp) { struct g_geom *gp; struct gv_softc *sc; int error; g_topology_assert(); KASSERT(cp != NULL, ("gv_orphan: null cp")); gp = cp->geom; KASSERT(gp != NULL, ("gv_orphan: null gp")); sc = gp->softc; g_trace(G_T_TOPOLOGY, "gv_orphan(%s)", gp->name); if (cp->acr != 0 || cp->acw != 0 || cp->ace != 0) g_access(cp, -cp->acr, -cp->acw, -cp->ace); error = cp->provider->error; if (error == 0) error = ENXIO; g_detach(cp); g_destroy_consumer(cp); if (!LIST_EMPTY(&gp->consumer)) return; g_free(sc); g_wither_geom(gp, error); } static void gv_start(struct bio *bp) { struct bio *bp2; struct g_geom *gp; gp = bp->bio_to->geom; switch(bp->bio_cmd) { case BIO_READ: case BIO_WRITE: case BIO_DELETE: bp2 = g_clone_bio(bp); if (bp2 == NULL) g_io_deliver(bp, ENOMEM); else { bp2->bio_done = g_std_done; g_io_request(bp2, LIST_FIRST(&gp->consumer)); } return; default: g_io_deliver(bp, EOPNOTSUPP); return; } } static int gv_access(struct g_provider *pp, int dr, int dw, int de) { struct g_geom *gp; struct g_consumer *cp; int error; gp = pp->geom; error = ENXIO; cp = LIST_FIRST(&gp->consumer); error = g_access(cp, dr, dw, de); return (error); } static struct g_geom * gv_taste(struct g_class *mp, struct g_provider *pp, int flags __unused) { struct g_geom *gp; struct g_consumer *cp; struct gv_softc *sc; struct gv_hdr *vhdr; int error, first; char *buf; vhdr = NULL; buf = NULL; first = 0; g_trace(G_T_TOPOLOGY, "gv_taste(%s, %s)", mp->name, pp->name); g_topology_assert(); if (pp->sectorsize == 0) return (NULL); /* Check if we already have a VINUM geom, or create a new one. */ if (LIST_EMPTY(&mp->geom)) { gp = g_new_geomf(mp, "VINUM"); gp->spoiled = gv_orphan; gp->orphan = gv_orphan; gp->access = gv_access; gp->start = gv_start; gp->softc = g_malloc(sizeof(struct gv_softc), M_WAITOK | M_ZERO); sc = gp->softc; sc->geom = gp; LIST_INIT(&sc->drives); LIST_INIT(&sc->subdisks); LIST_INIT(&sc->plexes); LIST_INIT(&sc->volumes); first++; } else { gp = LIST_FIRST(&mp->geom); sc = gp->softc; } /* We need a temporary consumer to read the config from. */ cp = g_new_consumer(gp); error = g_attach(cp, pp); if (error) { g_destroy_consumer(cp); if (first) { g_free(sc); g_destroy_geom(gp); } return (NULL); } error = g_access(cp, 1, 0, 0); if (error) { g_detach(cp); g_destroy_consumer(cp); if (first) { g_free(gp->softc); g_destroy_geom(gp); } return (NULL); } g_topology_unlock(); /* Check if the provided slice is a valid vinum drive. */ vhdr = g_read_data(cp, GV_HDR_OFFSET, pp->sectorsize, &error); if (vhdr == NULL || error != 0) { g_topology_lock(); g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); if (first) { g_free(sc); g_destroy_geom(gp); } return (NULL); } /* This provider has no vinum magic on board. */ if (vhdr->magic != GV_MAGIC) { /* Release the temporary consumer, we don't need it anymore. */ g_topology_lock(); g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); g_free(vhdr); /* * If there is no other VINUM geom yet just take this one; the * configuration is still empty, but it can be filled by other * valid vinum drives later. */ if (first) return (gp); else return (NULL); /* * We have found a valid vinum drive, now read the on-disk * configuration. */ } else { g_free(vhdr); buf = g_read_data(cp, GV_CFG_OFFSET, GV_CFG_LEN, &error); if (buf == NULL || error != 0) { g_topology_lock(); g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); if (first) { g_free(sc); g_destroy_geom(gp); } return (NULL); } /* Release the temporary consumer, we don't need it anymore. */ g_topology_lock(); g_access(cp, -1, 0, 0); g_detach(cp); g_destroy_consumer(cp); /* We are the first VINUM geom. */ if (first) { gv_parse_config(sc, buf, 0); g_free(buf); return (gp); /* Just merge the configs. */ } else { gv_parse_config(sc, buf, 1); g_free(buf); return (NULL); } } } /* Handle userland requests for creating new objects. */ int gv_create(struct g_geom *gp, struct gctl_req *req) { struct gv_softc *sc; struct gv_drive *d, *d2; struct gv_plex *p, *p2; struct gv_sd *s, *s2; struct gv_volume *v, *v2; struct g_consumer *cp; struct g_provider *pp; int error, i, *drives, *plexes, *subdisks, *volumes; char buf[20], errstr[ERRBUFSIZ]; g_topology_assert(); sc = gp->softc; /* Find out how many of each object have been passed in. */ volumes = gctl_get_paraml(req, "volumes", sizeof(*volumes)); plexes = gctl_get_paraml(req, "plexes", sizeof(*plexes)); subdisks = gctl_get_paraml(req, "subdisks", sizeof(*subdisks)); drives = gctl_get_paraml(req, "drives", sizeof(*drives)); /* First, handle drive definitions ... */ for (i = 0; i < *drives; i++) { snprintf(buf, sizeof(buf), "drive%d", i); d2 = gctl_get_paraml(req, buf, sizeof(*d2)); d = gv_find_drive(sc, d2->name); if (d != NULL) { gctl_error(req, "drive '%s' is already known", d->name); continue; } d = g_malloc(sizeof(*d), M_WAITOK | M_ZERO); bcopy(d2, d, sizeof(*d)); /* * Make sure that the provider specified in the drive * specification is an active GEOM provider. */ pp = g_provider_by_name(d->device); if (pp == NULL) { gctl_error(req, "%s: drive not found", d->device); g_free(d); return (-1); } d->size = pp->mediasize - GV_DATA_START; d->avail = d->size; gv_config_new_drive(d); LIST_INSERT_HEAD(&sc->drives, d, drive); } /* ... then volume definitions ... */ for (i = 0; i < *volumes; i++) { error = 0; snprintf(buf, sizeof(buf), "volume%d", i); v2 = gctl_get_paraml(req, buf, sizeof(*v2)); v = gv_find_vol(sc, v2->name); if (v != NULL) { gctl_error(req, "volume '%s' is already known", v->name); return (-1); } v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); bcopy(v2, v, sizeof(*v)); v->vinumconf = sc; LIST_INIT(&v->plexes); LIST_INSERT_HEAD(&sc->volumes, v, volume); } /* ... then plex definitions ... */ for (i = 0; i < *plexes; i++) { error = 0; snprintf(buf, sizeof(buf), "plex%d", i); p2 = gctl_get_paraml(req, buf, sizeof(*p2)); p = gv_find_plex(sc, p2->name); if (p != NULL) { gctl_error(req, "plex '%s' is already known", p->name); return (-1); } p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO); bcopy(p2, p, sizeof(*p)); /* Find the volume this plex should be attached to. */ v = gv_find_vol(sc, p->volume); if (v != NULL) { if (v->plexcount) p->flags |= GV_PLEX_ADDED; p->vol_sc = v; v->plexcount++; LIST_INSERT_HEAD(&v->plexes, p, in_volume); } p->vinumconf = sc; p->flags |= GV_PLEX_NEWBORN; LIST_INIT(&p->subdisks); LIST_INSERT_HEAD(&sc->plexes, p, plex); } /* ... and finally, subdisk definitions. */ for (i = 0; i < *subdisks; i++) { error = 0; snprintf(buf, sizeof(buf), "sd%d", i); s2 = gctl_get_paraml(req, buf, sizeof(*s2)); s = gv_find_sd(sc, s2->name); if (s != NULL) { gctl_error(req, "subdisk '%s' is already known", s->name); return (-1); } s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO); bcopy(s2, s, sizeof(*s)); /* Find the drive where this subdisk should be put on. */ d = gv_find_drive(sc, s->drive); /* drive not found - XXX */ if (d == NULL) { printf("FOO: drive '%s' not found\n", s->drive); g_free(s); continue; } /* Find the plex where this subdisk belongs to. */ p = gv_find_plex(sc, s->plex); /* plex not found - XXX */ if (p == NULL) { printf("FOO: plex '%s' not found\n", s->plex); g_free(s); continue; } /* * First we give the subdisk to the drive, to handle autosized * values ... */ error = gv_sd_to_drive(sc, d, s, errstr, sizeof(errstr)); if (error) { gctl_error(req, errstr); g_free(s); continue; } /* * Then, we give the subdisk to the plex; we check if the * given values are correct and maybe adjust them. */ error = gv_sd_to_plex(p, s, 1); if (error) { printf("FOO: couldn't give sd '%s' to plex '%s'\n", s->name, p->name); } s->flags |= GV_SD_NEWBORN; s->vinumconf = sc; LIST_INSERT_HEAD(&sc->subdisks, s, sd); } LIST_FOREACH(s, &sc->subdisks, sd) gv_update_sd_state(s); LIST_FOREACH(p, &sc->plexes, plex) gv_update_plex_config(p); LIST_FOREACH(v, &sc->volumes, volume) gv_update_vol_state(v); /* * Write out the configuration to each drive. If the drive doesn't * have a valid geom_slice geom yet, attach it temporarily to our VINUM * geom. */ LIST_FOREACH(d, &sc->drives, drive) { if (d->geom == NULL) { /* * XXX if the provider disapears before we get a chance * to write the config out to the drive, should this * be handled any differently? */ pp = g_provider_by_name(d->device); if (pp == NULL) { printf("geom_vinum: %s: drive disapeared?\n", d->device); continue; } cp = g_new_consumer(gp); g_attach(cp, pp); gv_save_config(cp, d, sc); g_detach(cp); g_destroy_consumer(cp); } else gv_save_config(NULL, d, sc); } return (0); } static void gv_config(struct gctl_req *req, struct g_class *mp, char const *verb) { struct g_geom *gp; struct gv_softc *sc; struct sbuf *sb; char *comment; g_topology_assert(); gp = LIST_FIRST(&mp->geom); sc = gp->softc; if (!strcmp(verb, "list")) { gv_list(gp, req); /* Save our configuration back to disk. */ } else if (!strcmp(verb, "saveconfig")) { gv_save_config_all(sc); /* Return configuration in string form. */ } else if (!strcmp(verb, "getconfig")) { comment = gctl_get_param(req, "comment", NULL); sb = sbuf_new(NULL, NULL, GV_CFG_LEN, SBUF_FIXEDLEN); gv_format_config(sc, sb, 0, comment); sbuf_finish(sb); gctl_set_param(req, "config", sbuf_data(sb), sbuf_len(sb) + 1); sbuf_delete(sb); } else if (!strcmp(verb, "create")) { gv_create(gp, req); } else if (!strcmp(verb, "parityop")) { gv_parityop(gp, req); } else if (!strcmp(verb, "remove")) { gv_remove(gp, req); } else if (!strcmp(verb, "start")) { gv_start_obj(gp, req); + } else if (!strcmp(verb, "setstate")) { + gv_setstate(gp, req); + } else gctl_error(req, "Unknown verb parameter"); } #if 0 static int gv_destroy_geom(struct gctl_req *req, struct g_class *mp, struct g_geom *gp) { struct g_geom *gp2; struct g_consumer *cp; struct gv_softc *sc; struct gv_drive *d, *d2; struct gv_plex *p, *p2; struct gv_sd *s, *s2; struct gv_volume *v, *v2; struct gv_freelist *fl, *fl2; g_trace(G_T_TOPOLOGY, "gv_destroy_geom: %s", gp->name); g_topology_assert(); KASSERT(gp != NULL, ("gv_destroy_geom: null gp")); KASSERT(gp->softc != NULL, ("gv_destroy_geom: null sc")); sc = gp->softc; /* * Check if any of our drives is still open; if so, refuse destruction. */ LIST_FOREACH(d, &sc->drives, drive) { gp2 = d->geom; cp = LIST_FIRST(&gp2->consumer); if (cp != NULL) g_access(cp, -1, -1, -1); if (gv_is_open(gp2)) return (EBUSY); } /* Clean up and deallocate what we allocated. */ LIST_FOREACH_SAFE(d, &sc->drives, drive, d2) { LIST_REMOVE(d, drive); g_free(d->hdr); d->hdr = NULL; LIST_FOREACH_SAFE(fl, &d->freelist, freelist, fl2) { d->freelist_entries--; LIST_REMOVE(fl, freelist); g_free(fl); fl = NULL; } d->geom->softc = NULL; g_free(d); } LIST_FOREACH_SAFE(s, &sc->subdisks, sd, s2) { LIST_REMOVE(s, sd); s->drive_sc = NULL; s->plex_sc = NULL; s->provider = NULL; s->consumer = NULL; g_free(s); } LIST_FOREACH_SAFE(p, &sc->plexes, plex, p2) { LIST_REMOVE(p, plex); gv_kill_thread(p); p->vol_sc = NULL; p->geom->softc = NULL; p->provider = NULL; p->consumer = NULL; if (p->org == GV_PLEX_RAID5) { mtx_destroy(&p->worklist_mtx); } g_free(p); } LIST_FOREACH_SAFE(v, &sc->volumes, volume, v2) { LIST_REMOVE(v, volume); v->geom->softc = NULL; g_free(v); } gp->softc = NULL; g_free(sc); g_wither_geom(gp, ENXIO); return (0); } #endif #define VINUM_CLASS_NAME "VINUM" static struct g_class g_vinum_class = { .name = VINUM_CLASS_NAME, .version = G_VERSION, .taste = gv_taste, /*.destroy_geom = gv_destroy_geom,*/ .ctlreq = gv_config, }; DECLARE_GEOM_CLASS(g_vinum_class, g_vinum); diff --git a/sys/geom/vinum/geom_vinum.h b/sys/geom/vinum/geom_vinum.h index c215e2ea0095..15216dc09549 100644 --- a/sys/geom/vinum/geom_vinum.h +++ b/sys/geom/vinum/geom_vinum.h @@ -1,83 +1,84 @@ /*- * 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 THE 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 THE 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$ */ #ifndef _GEOM_VINUM_H_ #define _GEOM_VINUM_H_ #define ERRBUFSIZ 1024 /* geom_vinum_drive.c */ void gv_config_new_drive(struct gv_drive *); void gv_save_config_all(struct gv_softc *); void gv_save_config(struct g_consumer *, struct gv_drive *, struct gv_softc *); /* geom_vinum_init.c */ void gv_parityop(struct g_geom *, struct gctl_req *); void gv_start_obj(struct g_geom *, struct gctl_req *); /* geom_vinum_list.c */ void gv_ld(struct g_geom *, struct gctl_req *, struct sbuf *); void gv_lp(struct g_geom *, struct gctl_req *, struct sbuf *); void gv_ls(struct g_geom *, struct gctl_req *, struct sbuf *); void gv_lv(struct g_geom *, struct gctl_req *, struct sbuf *); void gv_list(struct g_geom *, struct gctl_req *); /* geom_vinum_rm.c */ void gv_remove(struct g_geom *, struct gctl_req *); /* geom_vinum_state.c */ int gv_sdstatemap(struct gv_plex *); +void gv_setstate(struct g_geom *, struct gctl_req *); int gv_set_drive_state(struct gv_drive *, int, int); int gv_set_sd_state(struct gv_sd *, int, int); void gv_update_sd_state(struct gv_sd *); void gv_update_plex_state(struct gv_plex *); void gv_update_vol_state(struct gv_volume *); /* geom_vinum_subr.c */ void gv_adjust_freespace(struct gv_sd *, off_t); struct g_geom *find_vinum_geom(void); struct gv_drive *gv_find_drive(struct gv_softc *, char *); struct gv_plex *gv_find_plex(struct gv_softc *, char *); struct gv_sd *gv_find_sd(struct gv_softc *, char *); struct gv_volume *gv_find_vol(struct gv_softc *, char *); void gv_format_config(struct gv_softc *, struct sbuf *, int, char *); int gv_is_striped(struct gv_plex *); int gv_is_open(struct g_geom *); void gv_kill_drive_thread(struct gv_drive *); void gv_kill_plex_thread(struct gv_plex *); void gv_kill_vol_thread(struct gv_volume *); int gv_object_type(struct gv_softc *, char *); void gv_parse_config(struct gv_softc *, u_char *, int); int gv_sd_to_drive(struct gv_softc *, struct gv_drive *, struct gv_sd *, char *, int); int gv_sd_to_plex(struct gv_plex *, struct gv_sd *, int); void gv_update_plex_config(struct gv_plex *); void gv_update_vol_size(struct gv_volume *, off_t); #endif /* !_GEOM_VINUM_H_ */ diff --git a/sys/geom/vinum/geom_vinum_state.c b/sys/geom/vinum/geom_vinum_state.c index 4e1710815dfe..6913a4bf66c9 100644 --- a/sys/geom/vinum/geom_vinum_state.c +++ b/sys/geom/vinum/geom_vinum_state.c @@ -1,311 +1,384 @@ /*- * 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 THE 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 THE 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include -/* Update drive state; return 1 if the state changes, otherwise 0. */ +void +gv_setstate(struct g_geom *gp, struct gctl_req *req) +{ + struct gv_softc *sc; + struct gv_sd *s; + struct gv_drive *d; + char *obj, *state; + int err, f, *flags, newstate, type; + + f = 0; + obj = gctl_get_param(req, "object", NULL); + if (obj == NULL) { + gctl_error(req, "no object given"); + return; + } + + state = gctl_get_param(req, "state", NULL); + if (state == NULL) { + gctl_error(req, "no state given"); + return; + } + + flags = gctl_get_paraml(req, "flags", sizeof(*flags)); + if (flags == NULL) { + gctl_error(req, "no flags given"); + return; + } + + if (*flags & GV_FLAG_F) + f = GV_SETSTATE_FORCE; + + sc = gp->softc; + type = gv_object_type(sc, obj); + switch (type) { + case GV_TYPE_VOL: + case GV_TYPE_PLEX: + gctl_error(req, "volume or plex state cannot be set currently"); + break; + + case GV_TYPE_SD: + newstate = gv_sdstatei(state); + if (newstate < 0) { + gctl_error(req, "invalid subdisk state '%s'", state); + break; + } + s = gv_find_sd(sc, obj); + err = gv_set_sd_state(s, newstate, f); + if (err) + gctl_error(req, "cannot set subdisk state"); + break; + + case GV_TYPE_DRIVE: + newstate = gv_drivestatei(state); + if (newstate < 0) { + gctl_error(req, "invalid drive state '%s'", state); + break; + } + d = gv_find_drive(sc, obj); + err = gv_set_drive_state(d, newstate, f); + if (err) + gctl_error(req, "cannot set drive state"); + break; + + default: + gctl_error(req, "unknown object '%s'", obj); + break; + } + + return; +} + +/* Update drive state; return 0 if the state changes, otherwise -1. */ int gv_set_drive_state(struct gv_drive *d, int newstate, int flags) { struct gv_sd *s; int oldstate; KASSERT(d != NULL, ("gv_set_drive_state: NULL d")); oldstate = d->state; if (newstate == oldstate) - return (1); + return (0); /* We allow to take down an open drive only with force. */ if ((newstate == GV_DRIVE_DOWN) && gv_is_open(d->geom) && (!(flags & GV_SETSTATE_FORCE))) - return (0); + return (-1); d->state = newstate; if (d->state != oldstate) { LIST_FOREACH(s, &d->subdisks, from_drive) gv_update_sd_state(s); } /* Save the config back to disk. */ if (flags & GV_SETSTATE_CONFIG) gv_save_config_all(d->vinumconf); - return (1); + return (0); } int gv_set_sd_state(struct gv_sd *s, int newstate, int flags) { struct gv_drive *d; struct gv_plex *p; int oldstate, status; KASSERT(s != NULL, ("gv_set_sd_state: NULL s")); oldstate = s->state; /* We are optimistic and assume it will work. */ status = 0; if (newstate == oldstate) return (0); switch (newstate) { case GV_SD_DOWN: /* * If we're attached to a plex, we won't go down without use of * force. */ if ((s->plex_sc != NULL) && !(flags & GV_SETSTATE_FORCE)) return (-1); break; case GV_SD_UP: /* We can't bring the subdisk up if our drive is dead. */ d = s->drive_sc; if ((d == NULL) || (d->state != GV_DRIVE_UP)) return (-1); /* Check from where we want to be brought up. */ switch (s->state) { case GV_SD_REVIVING: case GV_SD_INITIALIZING: /* * The subdisk was initializing. We allow it to be * brought up. */ break; case GV_SD_DOWN: /* * The subdisk is currently down. We allow it to be * brought up if it is not attached to a plex. */ p = s->plex_sc; if (p == NULL) break; /* * If this subdisk is attached to a plex, we allow it * to be brought up if the plex if it's not a RAID5 * plex, otherwise it's made 'stale'. */ if (p->org != GV_PLEX_RAID5) break; + else if (flags & GV_SETSTATE_FORCE) + break; else s->state = GV_SD_STALE; status = -1; break; case GV_SD_STALE: /* * A stale subdisk can be brought up only if it's part * of a concat or striped plex that's the only one in a * volume, or if the subdisk isn't attached to a plex. * Otherwise it needs to be revived or initialized * first. */ p = s->plex_sc; - if (p == NULL) + if (p == NULL || flags & GV_SETSTATE_FORCE) break; if ((p->org != GV_PLEX_RAID5) && (p->vol_sc->plexcount == 1)) break; else return (-1); default: return (-1); } break; /* Other state transitions are only possible with force. */ default: if (!(flags & GV_SETSTATE_FORCE)) return (-1); } /* We can change the state and do it. */ if (status == 0) s->state = newstate; /* Update our plex, if we're attached to one. */ if (s->plex_sc != NULL) gv_update_plex_state(s->plex_sc); /* Save the config back to disk. */ if (flags & GV_SETSTATE_CONFIG) gv_save_config_all(s->vinumconf); return (status); } /* Update the state of a subdisk based on its environment. */ void gv_update_sd_state(struct gv_sd *s) { struct gv_drive *d; KASSERT(s != NULL, ("gv_update_sd_state: NULL s")); d = s->drive_sc; KASSERT(d != NULL, ("gv_update_sd_state: NULL d")); /* If our drive isn't up we cannot be up either. */ if (d->state != GV_DRIVE_UP) s->state = GV_SD_DOWN; /* If this subdisk was just created, we assume it is good.*/ else if (s->flags & GV_SD_NEWBORN) { s->state = GV_SD_UP; s->flags &= ~GV_SD_NEWBORN; } else if (s->state != GV_SD_UP) s->state = GV_SD_STALE; else s->state = GV_SD_UP; printf("GEOM_VINUM: subdisk %s is %s\n", s->name, gv_sdstate(s->state)); /* Update the plex, if we have one. */ if (s->plex_sc != NULL) gv_update_plex_state(s->plex_sc); } /* Update the state of a plex based on its environment. */ void gv_update_plex_state(struct gv_plex *p) { int sdstates; KASSERT(p != NULL, ("gv_update_plex_state: NULL p")); /* First, check the state of our subdisks. */ sdstates = gv_sdstatemap(p); /* If all subdisks are up, our plex can be up, too. */ if (sdstates == GV_SD_UPSTATE) p->state = GV_PLEX_UP; /* One or more of our subdisks are down. */ else if (sdstates & GV_SD_DOWNSTATE) { /* A RAID5 plex can handle one dead subdisk. */ if ((p->org == GV_PLEX_RAID5) && (p->sddown == 1)) p->state = GV_PLEX_DEGRADED; else p->state = GV_PLEX_DOWN; /* Some of our subdisks are initializing. */ } else if (sdstates & GV_SD_INITSTATE) { if (p->flags & GV_PLEX_SYNCING) p->state = GV_PLEX_DEGRADED; else p->state = GV_PLEX_DOWN; } else p->state = GV_PLEX_DOWN; printf("GEOM_VINUM: plex %s is %s\n", p->name, gv_plexstate(p->state)); /* Update our volume, if we have one. */ if (p->vol_sc != NULL) gv_update_vol_state(p->vol_sc); } /* Update the volume state based on its plexes. */ void gv_update_vol_state(struct gv_volume *v) { struct gv_plex *p; KASSERT(v != NULL, ("gv_update_vol_state: NULL v")); LIST_FOREACH(p, &v->plexes, in_volume) { /* One of our plexes is accessible, and so are we. */ if (p->state > GV_PLEX_DEGRADED) { v->state = GV_VOL_UP; return; /* We can handle a RAID5 plex with one dead subdisk as well. */ } else if ((p->org == GV_PLEX_RAID5) && (p->state == GV_PLEX_DEGRADED)) { v->state = GV_VOL_UP; return; } } /* Not one of our plexes is up, so we can't be either. */ v->state = GV_VOL_DOWN; } /* Return a state map for the subdisks of a plex. */ int gv_sdstatemap(struct gv_plex *p) { struct gv_sd *s; int statemap; KASSERT(p != NULL, ("gv_sdstatemap: NULL p")); statemap = 0; p->sddown = 0; /* No subdisks down yet. */ LIST_FOREACH(s, &p->subdisks, in_plex) { switch (s->state) { case GV_SD_DOWN: case GV_SD_STALE: statemap |= GV_SD_DOWNSTATE; p->sddown++; /* Another unusable subdisk. */ break; case GV_SD_UP: statemap |= GV_SD_UPSTATE; break; case GV_SD_INITIALIZING: statemap |= GV_SD_INITSTATE; break; case GV_SD_REVIVING: statemap |= GV_SD_INITSTATE; p->sddown++; /* XXX: Another unusable subdisk? */ break; } } return (statemap); }