diff --git a/lib/libgeom/geom_ctl.c b/lib/libgeom/geom_ctl.c index b60c4c297257..b1d738333b3f 100644 --- a/lib/libgeom/geom_ctl.c +++ b/lib/libgeom/geom_ctl.c @@ -1,239 +1,239 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2003 Poul-Henning Kamp * 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. * 3. The names of the authors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * 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$ */ #include #include #include #include #include #include #include #include #include #include #include #define GCTL_TABLE 1 #include /* * Global pointer to a string that is used to avoid an errorneous free in * gctl_free. */ static char nomemmsg[] = "Could not allocate memory"; void gctl_dump(struct gctl_req *req, FILE *f) { unsigned int i; int j; struct gctl_req_arg *ap; if (req == NULL) { fprintf(f, "Dump of gctl request at NULL\n"); return; } fprintf(f, "Dump of gctl request at %p:\n", req); if (req->error != NULL) fprintf(f, " error:\t\"%s\"\n", req->error); else fprintf(f, " error:\tNULL\n"); for (i = 0; i < req->narg; i++) { ap = &req->arg[i]; fprintf(f, " param:\t\"%s\" (%d)", ap->name, ap->nlen); fprintf(f, " [%s%s", ap->flag & GCTL_PARAM_RD ? "R" : "", ap->flag & GCTL_PARAM_WR ? "W" : ""); fflush(f); if (ap->flag & GCTL_PARAM_ASCII) fprintf(f, "%d] = \"%s\"", ap->len, (char *)ap->value); else if (ap->len > 0) { fprintf(f, "%d] = ", ap->len); fflush(f); for (j = 0; j < ap->len; j++) { fprintf(f, " %02x", ((u_char *)ap->value)[j]); } } else { fprintf(f, "0] = %p", ap->value); } fprintf(f, "\n"); } } /* * Set an error message, if one does not already exist. */ static void gctl_set_error(struct gctl_req *req, const char *error, ...) { va_list ap; if (req->error != NULL) return; va_start(ap, error); vasprintf(&req->error, error, ap); va_end(ap); } /* * Check that a malloc operation succeeded, and set a consistent error * message if not. */ static void gctl_check_alloc(struct gctl_req *req, void *ptr) { if (ptr != NULL) return; gctl_set_error(req, nomemmsg); if (req->error == NULL) req->error = nomemmsg; } /* * Allocate a new request handle of the specified type. * XXX: Why bother checking the type ? */ struct gctl_req * gctl_get_handle(void) { return (calloc(1, sizeof(struct gctl_req))); } /* * Allocate space for another argument. */ static struct gctl_req_arg * gctl_new_arg(struct gctl_req *req) { struct gctl_req_arg *ap; req->narg++; req->arg = reallocf(req->arg, sizeof *ap * req->narg); gctl_check_alloc(req, req->arg); if (req->arg == NULL) { req->narg = 0; return (NULL); } ap = req->arg + (req->narg - 1); memset(ap, 0, sizeof *ap); return (ap); } void gctl_add_param(struct gctl_req *req, const char *name, int len, void *value, int flag) { struct gctl_req_arg *ap; if (req == NULL || req->error != NULL) return; ap = gctl_new_arg(req); if (ap == NULL) return; ap->name = strdup(name); gctl_check_alloc(req, ap->name); if (ap->name == NULL) return; ap->nlen = strlen(ap->name) + 1; ap->value = value; ap->flag = flag; if (len >= 0) ap->len = len; else if (len < 0) { ap->flag |= GCTL_PARAM_ASCII; ap->len = strlen(value) + 1; } } void gctl_ro_param(struct gctl_req *req, const char *name, int len, const void* value) { gctl_add_param(req, name, len, __DECONST(void *, value), GCTL_PARAM_RD); } void gctl_rw_param(struct gctl_req *req, const char *name, int len, void *value) { gctl_add_param(req, name, len, value, GCTL_PARAM_RW); } const char * gctl_issue(struct gctl_req *req) { - int fd, error; + int fd; if (req == NULL) return ("NULL request pointer"); if (req->error != NULL) return (req->error); req->version = GCTL_VERSION; req->lerror = BUFSIZ; /* XXX: arbitrary number */ req->error = calloc(1, req->lerror); if (req->error == NULL) { gctl_check_alloc(req, req->error); return (req->error); } req->lerror--; fd = open(_PATH_DEV PATH_GEOM_CTL, O_RDONLY); if (fd < 0) return(strerror(errno)); - error = ioctl(fd, GEOM_CTL, req); + req->nerror = ioctl(fd, GEOM_CTL, req); close(fd); if (req->error[0] != '\0') return (req->error); - if (error != 0) + if (req->nerror == -1) return(strerror(errno)); return (NULL); } void gctl_free(struct gctl_req *req) { unsigned int i; if (req == NULL) return; for (i = 0; i < req->narg; i++) { if (req->arg[i].name != NULL) free(req->arg[i].name); } free(req->arg); if (req->error != NULL && req->error != nomemmsg) free(req->error); free(req); } diff --git a/sbin/geom/core/geom.c b/sbin/geom/core/geom.c index 9b43910b88f9..535d7d9f6f21 100644 --- a/sbin/geom/core/geom.c +++ b/sbin/geom/core/geom.c @@ -1,1408 +1,1411 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004-2009 Pawel Jakub Dawidek * 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 AUTHORS 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 AUTHORS 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 #include #include #include #include #include #include #include #include #include #include #include #include "misc/subr.h" #ifdef STATIC_GEOM_CLASSES extern uint32_t gpart_version; extern struct g_command gpart_class_commands[]; extern uint32_t glabel_version; extern struct g_command glabel_class_commands[]; #endif static char comm[MAXPATHLEN], *class_name = NULL, *gclass_name = NULL; static uint32_t *version = NULL; static int verbose = 0; static struct g_command *class_commands = NULL; #define GEOM_CLASS_CMDS 0x01 #define GEOM_STD_CMDS 0x02 #define GEOM_CLASS_WIDTH 10 static struct g_command *find_command(const char *cmdstr, int flags); static void list_one_geom_by_provider(const char *provider_name); static int std_available(const char *name); static int std_list_available(void); static int std_load_available(void); static void std_help(struct gctl_req *req, unsigned flags); static void std_list(struct gctl_req *req, unsigned flags); static void std_status(struct gctl_req *req, unsigned flags); static void std_load(struct gctl_req *req, unsigned flags); static void std_unload(struct gctl_req *req, unsigned flags); static struct g_command std_commands[] = { { "help", 0, std_help, G_NULL_OPTS, NULL }, { "list", 0, std_list, { { 'a', "all", NULL, G_TYPE_BOOL }, G_OPT_SENTINEL }, "[-a] [name ...]" }, { "status", 0, std_status, { { 'a', "all", NULL, G_TYPE_BOOL }, { 'g', "geoms", NULL, G_TYPE_BOOL }, { 's', "script", NULL, G_TYPE_BOOL }, G_OPT_SENTINEL }, "[-ags] [name ...]" }, { "load", G_FLAG_VERBOSE | G_FLAG_LOADKLD, std_load, G_NULL_OPTS, NULL }, { "unload", G_FLAG_VERBOSE, std_unload, G_NULL_OPTS, NULL }, G_CMD_SENTINEL }; static void usage_command(struct g_command *cmd, const char *prefix) { struct g_option *opt; unsigned i; if (cmd->gc_usage != NULL) { char *pos, *ptr, *sptr; sptr = ptr = strdup(cmd->gc_usage); while ((pos = strsep(&ptr, "\n")) != NULL) { if (*pos == '\0') continue; fprintf(stderr, "%s %s %s %s\n", prefix, comm, cmd->gc_name, pos); } free(sptr); return; } fprintf(stderr, "%s %s %s", prefix, comm, cmd->gc_name); if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0) fprintf(stderr, " [-v]"); for (i = 0; ; i++) { opt = &cmd->gc_options[i]; if (opt->go_name == NULL) break; if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL) fprintf(stderr, " ["); else fprintf(stderr, " "); fprintf(stderr, "-%c", opt->go_char); if (G_OPT_TYPE(opt) != G_TYPE_BOOL) fprintf(stderr, " %s", opt->go_name); if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL) fprintf(stderr, "]"); } fprintf(stderr, "\n"); } static void usage(void) { if (class_name == NULL) { fprintf(stderr, "usage: geom [options]\n"); fprintf(stderr, " geom -p \n"); fprintf(stderr, " geom -t\n"); exit(EXIT_FAILURE); } else { struct g_command *cmd; const char *prefix; unsigned i; prefix = "usage:"; if (class_commands != NULL) { for (i = 0; ; i++) { cmd = &class_commands[i]; if (cmd->gc_name == NULL) break; usage_command(cmd, prefix); prefix = " "; } } for (i = 0; ; i++) { cmd = &std_commands[i]; if (cmd->gc_name == NULL) break; /* * If class defines command, which has the same name as * standard command, skip it, because it was already * shown on usage(). */ if (find_command(cmd->gc_name, GEOM_CLASS_CMDS) != NULL) continue; usage_command(cmd, prefix); prefix = " "; } exit(EXIT_FAILURE); } } static void load_module(void) { char name1[64], name2[64]; snprintf(name1, sizeof(name1), "g_%s", class_name); snprintf(name2, sizeof(name2), "geom_%s", class_name); if (modfind(name1) < 0) { /* Not present in kernel, try loading it. */ if (kldload(name2) < 0 || modfind(name1) < 0) { if (errno != EEXIST) { err(EXIT_FAILURE, "cannot load %s", name2); } } } } static int strlcatf(char *str, size_t size, const char *format, ...) { size_t len; va_list ap; int ret; len = strlen(str); str += len; size -= len; va_start(ap, format); ret = vsnprintf(str, size, format, ap); va_end(ap); return (ret); } /* * Find given option in options available for given command. */ static struct g_option * find_option(struct g_command *cmd, char ch) { struct g_option *opt; unsigned i; for (i = 0; ; i++) { opt = &cmd->gc_options[i]; if (opt->go_name == NULL) return (NULL); if (opt->go_char == ch) return (opt); } /* NOTREACHED */ return (NULL); } /* * Add given option to gctl_req. */ static void set_option(struct gctl_req *req, struct g_option *opt, const char *val) { const char *optname; uint64_t number; void *ptr; if (G_OPT_ISMULTI(opt)) { size_t optnamesize; if (G_OPT_NUM(opt) == UCHAR_MAX) errx(EXIT_FAILURE, "Too many -%c options.", opt->go_char); /* * Base option name length plus 3 bytes for option number * (max. 255 options) plus 1 byte for terminating '\0'. */ optnamesize = strlen(opt->go_name) + 3 + 1; ptr = malloc(optnamesize); if (ptr == NULL) errx(EXIT_FAILURE, "No memory."); snprintf(ptr, optnamesize, "%s%u", opt->go_name, G_OPT_NUM(opt)); G_OPT_NUMINC(opt); optname = ptr; } else { optname = opt->go_name; } if (G_OPT_TYPE(opt) == G_TYPE_NUMBER) { if (expand_number(val, &number) == -1) { err(EXIT_FAILURE, "Invalid value for '%c' argument", opt->go_char); } ptr = malloc(sizeof(intmax_t)); if (ptr == NULL) errx(EXIT_FAILURE, "No memory."); *(intmax_t *)ptr = number; opt->go_val = ptr; gctl_ro_param(req, optname, sizeof(intmax_t), opt->go_val); } else if (G_OPT_TYPE(opt) == G_TYPE_STRING) { gctl_ro_param(req, optname, -1, val); } else if (G_OPT_TYPE(opt) == G_TYPE_BOOL) { ptr = malloc(sizeof(int)); if (ptr == NULL) errx(EXIT_FAILURE, "No memory."); *(int *)ptr = *val - '0'; opt->go_val = ptr; gctl_ro_param(req, optname, sizeof(int), opt->go_val); } else { assert(!"Invalid type"); } if (G_OPT_ISMULTI(opt)) free(__DECONST(char *, optname)); } /* * 1. Add given argument by caller. * 2. Add default values of not given arguments. * 3. Add the rest of arguments. */ static void parse_arguments(struct g_command *cmd, struct gctl_req *req, int *argc, char ***argv) { struct g_option *opt; char opts[64]; unsigned i; int ch, vcount; *opts = '\0'; if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0) strlcat(opts, "v", sizeof(opts)); for (i = 0; ; i++) { opt = &cmd->gc_options[i]; if (opt->go_name == NULL) break; assert(G_OPT_TYPE(opt) != 0); assert((opt->go_type & ~(G_TYPE_MASK | G_TYPE_MULTI)) == 0); /* Multiple bool arguments makes no sense. */ assert(G_OPT_TYPE(opt) != G_TYPE_BOOL || (opt->go_type & G_TYPE_MULTI) == 0); strlcatf(opts, sizeof(opts), "%c", opt->go_char); if (G_OPT_TYPE(opt) != G_TYPE_BOOL) strlcat(opts, ":", sizeof(opts)); } /* * Add specified arguments. */ vcount = 0; while ((ch = getopt(*argc, *argv, opts)) != -1) { /* Standard (not passed to kernel) options. */ if (ch == 'v' && (cmd->gc_flags & G_FLAG_VERBOSE) != 0) verbose = 1; /* Options passed to kernel. */ opt = find_option(cmd, ch); if (opt == NULL) { if (ch == 'v' && (cmd->gc_flags & G_FLAG_VERBOSE) != 0){ if (++vcount < 2) continue; else warnx("Option 'v' specified twice."); } usage(); } if (!G_OPT_ISMULTI(opt) && G_OPT_ISDONE(opt)) { warnx("Option '%c' specified twice.", opt->go_char); usage(); } G_OPT_DONE(opt); if (G_OPT_TYPE(opt) == G_TYPE_BOOL) set_option(req, opt, "1"); else set_option(req, opt, optarg); } *argc -= optind; *argv += optind; /* * Add not specified arguments, but with default values. */ for (i = 0; ; i++) { opt = &cmd->gc_options[i]; if (opt->go_name == NULL) break; if (G_OPT_ISDONE(opt)) continue; if (G_OPT_TYPE(opt) == G_TYPE_BOOL) { assert(opt->go_val == NULL); set_option(req, opt, "0"); } else { if (opt->go_val == NULL) { warnx("Option '%c' not specified.", opt->go_char); usage(); } else if (opt->go_val == G_VAL_OPTIONAL) { /* add nothing. */ } else { set_option(req, opt, opt->go_val); } } } /* * Add rest of given arguments. */ gctl_ro_param(req, "nargs", sizeof(int), argc); for (i = 0; i < (unsigned)*argc; i++) { char argname[16]; snprintf(argname, sizeof(argname), "arg%u", i); gctl_ro_param(req, argname, -1, (*argv)[i]); } } /* * Find given command in commands available for given class. */ static struct g_command * find_command(const char *cmdstr, int flags) { struct g_command *cmd; unsigned i; /* * First try to find command defined by loaded library. */ if ((flags & GEOM_CLASS_CMDS) != 0 && class_commands != NULL) { for (i = 0; ; i++) { cmd = &class_commands[i]; if (cmd->gc_name == NULL) break; if (strcmp(cmd->gc_name, cmdstr) == 0) return (cmd); } } /* * Now try to find in standard commands. */ if ((flags & GEOM_STD_CMDS) != 0) { for (i = 0; ; i++) { cmd = &std_commands[i]; if (cmd->gc_name == NULL) break; if (strcmp(cmd->gc_name, cmdstr) == 0) return (cmd); } } return (NULL); } static unsigned set_flags(struct g_command *cmd) { unsigned flags = 0; if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0 && verbose) flags |= G_FLAG_VERBOSE; return (flags); } /* * Run command. */ static void run_command(int argc, char *argv[]) { struct g_command *cmd; struct gctl_req *req; const char *errstr; char buf[4096]; /* First try to find a command defined by a class. */ cmd = find_command(argv[0], GEOM_CLASS_CMDS); if (cmd == NULL) { /* Now, try to find a standard command. */ cmd = find_command(argv[0], GEOM_STD_CMDS); if (cmd == NULL) { warnx("Unknown command: %s.", argv[0]); usage(); } if (!std_available(cmd->gc_name)) { warnx("Command '%s' not available; " "try 'load' first.", argv[0]); exit(EXIT_FAILURE); } } if ((cmd->gc_flags & G_FLAG_LOADKLD) != 0) load_module(); req = gctl_get_handle(); gctl_ro_param(req, "class", -1, gclass_name); gctl_ro_param(req, "verb", -1, argv[0]); if (version != NULL) gctl_ro_param(req, "version", sizeof(*version), version); parse_arguments(cmd, req, &argc, &argv); buf[0] = '\0'; if (cmd->gc_func != NULL) { unsigned flags; flags = set_flags(cmd); cmd->gc_func(req, flags); errstr = req->error; } else { gctl_add_param(req, "output", sizeof(buf), buf, GCTL_PARAM_WR | GCTL_PARAM_ASCII); errstr = gctl_issue(req); } if (errstr != NULL && errstr[0] != '\0') { warnx("%s", errstr); - if (strncmp(errstr, "warning: ", strlen("warning: ")) != 0) { + /* Suppress EXIT_FAILURE for warnings */ + if (strncmp(errstr, "warning: ", strlen("warning: ")) == 0) + req->nerror = 0; + if (req->nerror != 0) { gctl_free(req); exit(EXIT_FAILURE); } } if (buf[0] != '\0') printf("%s", buf); gctl_free(req); if (verbose) printf("Done.\n"); exit(EXIT_SUCCESS); } #ifndef STATIC_GEOM_CLASSES static const char * library_path(void) { const char *path; path = getenv("GEOM_LIBRARY_PATH"); if (path == NULL) path = GEOM_CLASS_DIR; return (path); } static void load_library(void) { char *curpath, path[MAXPATHLEN], *tofree, *totalpath; uint32_t *lib_version; void *dlh; int ret; ret = 0; tofree = totalpath = strdup(library_path()); if (totalpath == NULL) err(EXIT_FAILURE, "Not enough memory for library path"); if (strchr(totalpath, ':') != NULL) curpath = strsep(&totalpath, ":"); else curpath = totalpath; /* Traverse the paths to find one that contains the library we want. */ while (curpath != NULL) { snprintf(path, sizeof(path), "%s/geom_%s.so", curpath, class_name); ret = access(path, F_OK); if (ret == -1) { if (errno == ENOENT) { /* * If we cannot find library, try the next * path. */ curpath = strsep(&totalpath, ":"); continue; } err(EXIT_FAILURE, "Cannot access library"); } break; } free(tofree); /* No library was found, but standard commands can still be used */ if (ret == -1) return; dlh = dlopen(path, RTLD_NOW); if (dlh == NULL) errx(EXIT_FAILURE, "Cannot open library: %s.", dlerror()); lib_version = dlsym(dlh, "lib_version"); if (lib_version == NULL) { warnx("Cannot find symbol %s: %s.", "lib_version", dlerror()); dlclose(dlh); exit(EXIT_FAILURE); } if (*lib_version != G_LIB_VERSION) { dlclose(dlh); errx(EXIT_FAILURE, "%s and %s are not synchronized.", getprogname(), path); } version = dlsym(dlh, "version"); if (version == NULL) { warnx("Cannot find symbol %s: %s.", "version", dlerror()); dlclose(dlh); exit(EXIT_FAILURE); } class_commands = dlsym(dlh, "class_commands"); if (class_commands == NULL) { warnx("Cannot find symbol %s: %s.", "class_commands", dlerror()); dlclose(dlh); exit(EXIT_FAILURE); } } #endif /* !STATIC_GEOM_CLASSES */ /* * Class name should be all capital letters. */ static void set_class_name(void) { char *s1, *s2; s1 = class_name; for (; *s1 != '\0'; s1++) *s1 = tolower(*s1); gclass_name = malloc(strlen(class_name) + 1); if (gclass_name == NULL) errx(EXIT_FAILURE, "No memory"); s1 = gclass_name; s2 = class_name; for (; *s2 != '\0'; s2++) *s1++ = toupper(*s2); *s1 = '\0'; } static void get_class(int *argc, char ***argv) { snprintf(comm, sizeof(comm), "%s", basename((*argv)[0])); if (strcmp(comm, "geom") == 0) { if (*argc < 2) usage(); else if (*argc == 2) { if (strcmp((*argv)[1], "-h") == 0 || strcmp((*argv)[1], "help") == 0) { usage(); } } strlcatf(comm, sizeof(comm), " %s", (*argv)[1]); class_name = (*argv)[1]; *argc -= 2; *argv += 2; } else if (*comm == 'g') { class_name = comm + 1; *argc -= 1; *argv += 1; } else { errx(EXIT_FAILURE, "Invalid utility name."); } #ifndef STATIC_GEOM_CLASSES load_library(); #else if (!strcasecmp(class_name, "part")) { version = &gpart_version; class_commands = gpart_class_commands; } else if (!strcasecmp(class_name, "label")) { version = &glabel_version; class_commands = glabel_class_commands; } #endif /* !STATIC_GEOM_CLASSES */ set_class_name(); /* If we can't load or list, it's not a class. */ if (!std_load_available() && !std_list_available()) errx(EXIT_FAILURE, "Invalid class name '%s'.", class_name); if (*argc < 1) usage(); } static struct ggeom * find_geom_by_provider(struct gmesh *mesh, const char *name) { struct gclass *classp; struct ggeom *gp; struct gprovider *pp; LIST_FOREACH(classp, &mesh->lg_class, lg_class) { LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { if (strcmp(pp->lg_name, name) == 0) return (gp); } } } return (NULL); } static int compute_tree_width_geom(struct gmesh *mesh, struct ggeom *gp, int indent) { struct gclass *classp2; struct ggeom *gp2; struct gconsumer *cp2; struct gprovider *pp; int max_width, width; max_width = width = indent + strlen(gp->lg_name); LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { LIST_FOREACH(classp2, &mesh->lg_class, lg_class) { LIST_FOREACH(gp2, &classp2->lg_geom, lg_geom) { LIST_FOREACH(cp2, &gp2->lg_consumer, lg_consumer) { if (pp != cp2->lg_provider) continue; width = compute_tree_width_geom(mesh, gp2, indent + 2); if (width > max_width) max_width = width; } } } } return (max_width); } static int compute_tree_width(struct gmesh *mesh) { struct gclass *classp; struct ggeom *gp; int max_width, width; max_width = width = 0; LIST_FOREACH(classp, &mesh->lg_class, lg_class) { LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (!LIST_EMPTY(&gp->lg_consumer)) continue; width = compute_tree_width_geom(mesh, gp, 0); if (width > max_width) max_width = width; } } return (max_width); } static void show_tree_geom(struct gmesh *mesh, struct ggeom *gp, int indent, int width) { struct gclass *classp2; struct ggeom *gp2; struct gconsumer *cp2; struct gprovider *pp; if (LIST_EMPTY(&gp->lg_provider)) { printf("%*s%-*.*s %-*.*s\n", indent, "", width - indent, width - indent, gp->lg_name, GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, gp->lg_class->lg_name); return; } LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { printf("%*s%-*.*s %-*.*s %s\n", indent, "", width - indent, width - indent, gp->lg_name, GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, gp->lg_class->lg_name, pp->lg_name); LIST_FOREACH(classp2, &mesh->lg_class, lg_class) { LIST_FOREACH(gp2, &classp2->lg_geom, lg_geom) { LIST_FOREACH(cp2, &gp2->lg_consumer, lg_consumer) { if (pp != cp2->lg_provider) continue; show_tree_geom(mesh, gp2, indent + 2, width); } } } } } static void show_tree(void) { struct gmesh mesh; struct gclass *classp; struct ggeom *gp; int error, width; error = geom_gettree(&mesh); if (error != 0) errc(EXIT_FAILURE, error, "Cannot get GEOM tree"); width = compute_tree_width(&mesh); printf("%-*.*s %-*.*s %s\n", width, width, "Geom", GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, "Class", "Provider"); LIST_FOREACH(classp, &mesh.lg_class, lg_class) { LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (!LIST_EMPTY(&gp->lg_consumer)) continue; show_tree_geom(&mesh, gp, 0, width); } } } int main(int argc, char *argv[]) { char *provider_name; bool tflag; int ch; provider_name = NULL; tflag = false; if (strcmp(getprogname(), "geom") == 0) { while ((ch = getopt(argc, argv, "hp:t")) != -1) { switch (ch) { case 'p': provider_name = strdup(optarg); if (provider_name == NULL) err(1, "strdup"); break; case 't': tflag = true; break; case 'h': default: usage(); } } /* * Don't adjust argc and argv, it would break get_class(). */ } if (tflag && provider_name != NULL) { errx(EXIT_FAILURE, "At most one of -P and -t may be specified."); } if (provider_name != NULL) { list_one_geom_by_provider(provider_name); return (0); } if (tflag) { show_tree(); return (0); } get_class(&argc, &argv); run_command(argc, argv); /* NOTREACHED */ exit(EXIT_FAILURE); } static struct gclass * find_class(struct gmesh *mesh, const char *name) { struct gclass *classp; LIST_FOREACH(classp, &mesh->lg_class, lg_class) { if (strcmp(classp->lg_name, name) == 0) return (classp); } return (NULL); } static struct ggeom * find_geom(struct gclass *classp, const char *name) { struct ggeom *gp; if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) name += sizeof(_PATH_DEV) - 1; LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (strcmp(gp->lg_name, name) == 0) return (gp); } return (NULL); } static void list_one_provider(struct gprovider *pp, const char *prefix) { struct gconfig *conf; char buf[5]; printf("Name: %s\n", pp->lg_name); humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); printf("%sMediasize: %jd (%s)\n", prefix, (intmax_t)pp->lg_mediasize, buf); printf("%sSectorsize: %u\n", prefix, pp->lg_sectorsize); if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) { printf("%sStripesize: %ju\n", prefix, pp->lg_stripesize); printf("%sStripeoffset: %ju\n", prefix, pp->lg_stripeoffset); } printf("%sMode: %s\n", prefix, pp->lg_mode); LIST_FOREACH(conf, &pp->lg_config, lg_config) { printf("%s%s: %s\n", prefix, conf->lg_name, conf->lg_val); } } static void list_one_consumer(struct gconsumer *cp, const char *prefix) { struct gprovider *pp; struct gconfig *conf; pp = cp->lg_provider; if (pp == NULL) printf("[no provider]\n"); else { char buf[5]; printf("Name: %s\n", pp->lg_name); humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); printf("%sMediasize: %jd (%s)\n", prefix, (intmax_t)pp->lg_mediasize, buf); printf("%sSectorsize: %u\n", prefix, pp->lg_sectorsize); if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) { printf("%sStripesize: %ju\n", prefix, pp->lg_stripesize); printf("%sStripeoffset: %ju\n", prefix, pp->lg_stripeoffset); } printf("%sMode: %s\n", prefix, cp->lg_mode); } LIST_FOREACH(conf, &cp->lg_config, lg_config) { printf("%s%s: %s\n", prefix, conf->lg_name, conf->lg_val); } } static void list_one_geom(struct ggeom *gp) { struct gprovider *pp; struct gconsumer *cp; struct gconfig *conf; unsigned n; printf("Geom name: %s\n", gp->lg_name); LIST_FOREACH(conf, &gp->lg_config, lg_config) { printf("%s: %s\n", conf->lg_name, conf->lg_val); } if (!LIST_EMPTY(&gp->lg_provider)) { printf("Providers:\n"); n = 1; LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { printf("%u. ", n++); list_one_provider(pp, " "); } } if (!LIST_EMPTY(&gp->lg_consumer)) { printf("Consumers:\n"); n = 1; LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) { printf("%u. ", n++); list_one_consumer(cp, " "); } } printf("\n"); } static void list_one_geom_by_provider(const char *provider_name) { struct gmesh mesh; struct ggeom *gp; int error; error = geom_gettree(&mesh); if (error != 0) errc(EXIT_FAILURE, error, "Cannot get GEOM tree"); gp = find_geom_by_provider(&mesh, provider_name); if (gp == NULL) errx(EXIT_FAILURE, "Cannot find provider '%s'.", provider_name); printf("Geom class: %s\n", gp->lg_class->lg_name); list_one_geom(gp); } static void std_help(struct gctl_req *req __unused, unsigned flags __unused) { usage(); } static int std_list_available(void) { struct gmesh mesh; struct gclass *classp; int error; error = geom_gettree_geom(&mesh, gclass_name, "", 0); if (error != 0) errc(EXIT_FAILURE, error, "Cannot get GEOM tree"); classp = find_class(&mesh, gclass_name); geom_deletetree(&mesh); if (classp != NULL) return (1); return (0); } static void std_list(struct gctl_req *req, unsigned flags __unused) { struct gmesh mesh; struct gclass *classp; struct ggeom *gp; const char *name; int all, error, i, nargs; nargs = gctl_get_int(req, "nargs"); if (nargs == 1) { error = geom_gettree_geom(&mesh, gclass_name, gctl_get_ascii(req, "arg0"), 1); } else error = geom_gettree(&mesh); if (error != 0) errc(EXIT_FAILURE, error, "Cannot get GEOM tree"); classp = find_class(&mesh, gclass_name); if (classp == NULL) { geom_deletetree(&mesh); errx(EXIT_FAILURE, "Class '%s' not found.", gclass_name); } all = gctl_get_int(req, "all"); if (nargs > 0) { for (i = 0; i < nargs; i++) { name = gctl_get_ascii(req, "arg%d", i); gp = find_geom(classp, name); if (gp == NULL) { errx(EXIT_FAILURE, "Class '%s' does not have " "an instance named '%s'.", gclass_name, name); } list_one_geom(gp); } } else { LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (LIST_EMPTY(&gp->lg_provider) && !all) continue; list_one_geom(gp); } } geom_deletetree(&mesh); } static int std_status_available(void) { /* 'status' command is available when 'list' command is. */ return (std_list_available()); } static void status_update_len(struct ggeom *gp, int *name_len, int *status_len) { struct gconfig *conf; int len; assert(gp != NULL); assert(name_len != NULL); assert(status_len != NULL); len = strlen(gp->lg_name); if (*name_len < len) *name_len = len; LIST_FOREACH(conf, &gp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { len = strlen(conf->lg_val); if (*status_len < len) *status_len = len; } } } static void status_update_len_prs(struct ggeom *gp, int *name_len, int *status_len) { struct gprovider *pp; struct gconfig *conf; int len, glen; assert(gp != NULL); assert(name_len != NULL); assert(status_len != NULL); glen = 0; LIST_FOREACH(conf, &gp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { glen = strlen(conf->lg_val); break; } } LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { len = strlen(pp->lg_name); if (*name_len < len) *name_len = len; len = glen; LIST_FOREACH(conf, &pp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { len = strlen(conf->lg_val); break; } } if (*status_len < len) *status_len = len; } } static char * status_one_consumer(struct gconsumer *cp) { static char buf[256]; struct gprovider *pp; struct gconfig *conf; const char *state, *syncr; pp = cp->lg_provider; if (pp == NULL) return (NULL); state = NULL; syncr = NULL; LIST_FOREACH(conf, &cp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) state = conf->lg_val; if (strcasecmp(conf->lg_name, "synchronized") == 0) syncr = conf->lg_val; } if (state == NULL && syncr == NULL) snprintf(buf, sizeof(buf), "%s", pp->lg_name); else if (state != NULL && syncr != NULL) { snprintf(buf, sizeof(buf), "%s (%s, %s)", pp->lg_name, state, syncr); } else { snprintf(buf, sizeof(buf), "%s (%s)", pp->lg_name, state ? state : syncr); } return (buf); } static void status_one_geom(struct ggeom *gp, int script, int name_len, int status_len) { struct gconsumer *cp; struct gconfig *conf; const char *name, *status, *component; int gotone; name = gp->lg_name; status = "N/A"; LIST_FOREACH(conf, &gp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { status = conf->lg_val; break; } } gotone = 0; LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) { component = status_one_consumer(cp); if (component == NULL) continue; gotone = 1; printf("%*s %*s %s\n", name_len, name, status_len, status, component); if (!script) name = status = ""; } if (!gotone) { printf("%*s %*s %s\n", name_len, name, status_len, status, "N/A"); } } static void status_one_geom_prs(struct ggeom *gp, int script, int name_len, int status_len) { struct gprovider *pp; struct gconsumer *cp; struct gconfig *conf; const char *name, *status, *component; int gotone; LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { name = pp->lg_name; status = "N/A"; LIST_FOREACH(conf, &gp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { status = conf->lg_val; break; } } LIST_FOREACH(conf, &pp->lg_config, lg_config) { if (strcasecmp(conf->lg_name, "state") == 0) { status = conf->lg_val; break; } } gotone = 0; LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) { component = status_one_consumer(cp); if (component == NULL) continue; gotone = 1; printf("%*s %*s %s\n", name_len, name, status_len, status, component); if (!script) name = status = ""; } if (!gotone) { printf("%*s %*s %s\n", name_len, name, status_len, status, "N/A"); } } } static void std_status(struct gctl_req *req, unsigned flags __unused) { struct gmesh mesh; struct gclass *classp; struct ggeom *gp; const char *name; int name_len, status_len; int all, error, geoms, i, n, nargs, script; error = geom_gettree(&mesh); if (error != 0) errc(EXIT_FAILURE, error, "Cannot get GEOM tree"); classp = find_class(&mesh, gclass_name); if (classp == NULL) errx(EXIT_FAILURE, "Class %s not found.", gclass_name); nargs = gctl_get_int(req, "nargs"); all = gctl_get_int(req, "all"); geoms = gctl_get_int(req, "geoms"); script = gctl_get_int(req, "script"); if (script) { name_len = 0; status_len = 0; } else { name_len = strlen("Name"); status_len = strlen("Status"); } if (nargs > 0) { for (i = 0, n = 0; i < nargs; i++) { name = gctl_get_ascii(req, "arg%d", i); gp = find_geom(classp, name); if (gp == NULL) errx(EXIT_FAILURE, "No such geom: %s.", name); if (geoms) { status_update_len(gp, &name_len, &status_len); } else { status_update_len_prs(gp, &name_len, &status_len); } n++; } if (n == 0) goto end; } else { n = 0; LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (LIST_EMPTY(&gp->lg_provider) && !all) continue; if (geoms) { status_update_len(gp, &name_len, &status_len); } else { status_update_len_prs(gp, &name_len, &status_len); } n++; } if (n == 0) goto end; } if (!script) { printf("%*s %*s %s\n", name_len, "Name", status_len, "Status", "Components"); } if (nargs > 0) { for (i = 0; i < nargs; i++) { name = gctl_get_ascii(req, "arg%d", i); gp = find_geom(classp, name); if (gp == NULL) continue; if (geoms) { status_one_geom(gp, script, name_len, status_len); } else { status_one_geom_prs(gp, script, name_len, status_len); } } } else { LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { if (LIST_EMPTY(&gp->lg_provider) && !all) continue; if (geoms) { status_one_geom(gp, script, name_len, status_len); } else { status_one_geom_prs(gp, script, name_len, status_len); } } } end: geom_deletetree(&mesh); } static int std_load_available(void) { char name[MAXPATHLEN], paths[MAXPATHLEN * 8], *p; struct stat sb; size_t len; snprintf(name, sizeof(name), "g_%s", class_name); /* * If already in kernel, "load" command is NOP. */ if (modfind(name) >= 0) return (1); bzero(paths, sizeof(paths)); len = sizeof(paths); if (sysctlbyname("kern.module_path", paths, &len, NULL, 0) < 0) err(EXIT_FAILURE, "sysctl(kern.module_path)"); for (p = strtok(paths, ";"); p != NULL; p = strtok(NULL, ";")) { snprintf(name, sizeof(name), "%s/geom_%s.ko", p, class_name); /* * If geom_.ko file exists, "load" command is available. */ if (stat(name, &sb) == 0) return (1); } return (0); } static void std_load(struct gctl_req *req __unused, unsigned flags) { /* * Do nothing special here, because of G_FLAG_LOADKLD flag, * module is already loaded. */ if ((flags & G_FLAG_VERBOSE) != 0) printf("Module available.\n"); } static int std_unload_available(void) { char name[64]; int id; snprintf(name, sizeof(name), "geom_%s", class_name); id = kldfind(name); if (id >= 0) return (1); return (0); } static void std_unload(struct gctl_req *req, unsigned flags __unused) { char name[64]; int id; snprintf(name, sizeof(name), "geom_%s", class_name); id = kldfind(name); if (id < 0) { gctl_error(req, "Could not find module: %s.", strerror(errno)); return; } if (kldunload(id) < 0) { gctl_error(req, "Could not unload module: %s.", strerror(errno)); return; } } static int std_available(const char *name) { if (strcmp(name, "help") == 0) return (1); else if (strcmp(name, "list") == 0) return (std_list_available()); else if (strcmp(name, "status") == 0) return (std_status_available()); else if (strcmp(name, "load") == 0) return (std_load_available()); else if (strcmp(name, "unload") == 0) return (std_unload_available()); else assert(!"Unknown standard command."); return (0); } diff --git a/sys/geom/geom.h b/sys/geom/geom.h index 70d21e346c9b..a9990f669863 100644 --- a/sys/geom/geom.h +++ b/sys/geom/geom.h @@ -1,445 +1,445 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2002 Poul-Henning Kamp * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * * This software was developed for the FreeBSD Project by Poul-Henning Kamp * and NAI Labs, the Security Research Division of Network Associates, Inc. * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the * DARPA CHATS research program. * * 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. The names of the authors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * 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_GEOM_H_ #define _GEOM_GEOM_H_ #include #include #include #include #include #include #include struct g_class; struct g_geom; struct g_consumer; struct g_provider; struct g_stat; struct thread; struct bio; struct sbuf; struct gctl_req; struct g_configargs; struct disk_zone_args; typedef int g_config_t (struct g_configargs *ca); typedef void g_ctl_req_t (struct gctl_req *, struct g_class *cp, char const *verb); typedef int g_ctl_create_geom_t (struct gctl_req *, struct g_class *cp, struct g_provider *pp); typedef int g_ctl_destroy_geom_t (struct gctl_req *, struct g_class *cp, struct g_geom *gp); typedef int g_ctl_config_geom_t (struct gctl_req *, struct g_geom *gp, const char *verb); typedef void g_init_t (struct g_class *mp); typedef void g_fini_t (struct g_class *mp); typedef struct g_geom * g_taste_t (struct g_class *, struct g_provider *, int flags); typedef int g_ioctl_t(struct g_provider *pp, u_long cmd, void *data, int fflag, struct thread *td); #define G_TF_NORMAL 0 #define G_TF_INSIST 1 #define G_TF_TRANSPARENT 2 typedef int g_access_t (struct g_provider *, int, int, int); /* XXX: not sure about the thread arg */ typedef void g_orphan_t (struct g_consumer *); typedef void g_start_t (struct bio *); typedef void g_spoiled_t (struct g_consumer *); typedef void g_attrchanged_t (struct g_consumer *, const char *attr); typedef void g_provgone_t (struct g_provider *); typedef void g_dumpconf_t (struct sbuf *, const char *indent, struct g_geom *, struct g_consumer *, struct g_provider *); typedef void g_resize_t(struct g_consumer *cp); /* * The g_class structure describes a transformation class. In other words * all BSD disklabel handlers share one g_class, all MBR handlers share * one common g_class and so on. * Certain operations are instantiated on the class, most notably the * taste and config_geom functions. */ struct g_class { const char *name; u_int version; u_int spare0; g_taste_t *taste; g_ctl_req_t *ctlreq; g_init_t *init; g_fini_t *fini; g_ctl_destroy_geom_t *destroy_geom; /* * Default values for geom methods */ g_start_t *start; g_spoiled_t *spoiled; g_attrchanged_t *attrchanged; g_dumpconf_t *dumpconf; g_access_t *access; g_orphan_t *orphan; g_ioctl_t *ioctl; g_provgone_t *providergone; g_resize_t *resize; void *spare1; void *spare2; /* * The remaining elements are private */ LIST_ENTRY(g_class) class; LIST_HEAD(,g_geom) geom; }; #define G_VERSION_00 0x19950323 #define G_VERSION_01 0x20041207 /* add fflag to g_ioctl_t */ #define G_VERSION G_VERSION_01 /* * The g_geom is an instance of a g_class. */ struct g_geom { char *name; struct g_class *class; LIST_ENTRY(g_geom) geom; LIST_HEAD(,g_consumer) consumer; LIST_HEAD(,g_provider) provider; TAILQ_ENTRY(g_geom) geoms; /* XXX: better name */ int rank; g_start_t *start; g_spoiled_t *spoiled; g_attrchanged_t *attrchanged; g_dumpconf_t *dumpconf; g_access_t *access; g_orphan_t *orphan; g_ioctl_t *ioctl; g_provgone_t *providergone; g_resize_t *resize; void *spare0; void *spare1; void *softc; unsigned flags; #define G_GEOM_WITHER 0x01 #define G_GEOM_VOLATILE_BIO 0x02 #define G_GEOM_IN_ACCESS 0x04 #define G_GEOM_ACCESS_WAIT 0x08 }; /* * The g_bioq is a queue of struct bio's. * XXX: possibly collection point for statistics. * XXX: should (possibly) be collapsed with sys/bio.h::bio_queue_head. */ struct g_bioq { TAILQ_HEAD(, bio) bio_queue; struct mtx bio_queue_lock; int bio_queue_length; }; /* * A g_consumer is an attachment point for a g_provider. One g_consumer * can only be attached to one g_provider, but multiple g_consumers * can be attached to one g_provider. */ struct g_consumer { struct g_geom *geom; LIST_ENTRY(g_consumer) consumer; struct g_provider *provider; LIST_ENTRY(g_consumer) consumers; /* XXX: better name */ int acr, acw, ace; int flags; #define G_CF_SPOILED 0x1 #define G_CF_ORPHAN 0x4 #define G_CF_DIRECT_SEND 0x10 #define G_CF_DIRECT_RECEIVE 0x20 struct devstat *stat; u_int nstart, nend; /* Two fields for the implementing class to use */ void *private; u_int index; }; /* * The g_geom_alias is a list node for aliases for the provider name for device * node creation. */ struct g_geom_alias { LIST_ENTRY(g_geom_alias) ga_next; const char *ga_alias; }; /* * A g_provider is a "logical disk". */ struct g_provider { char *name; LIST_ENTRY(g_provider) provider; struct g_geom *geom; LIST_HEAD(,g_consumer) consumers; int acr, acw, ace; int error; TAILQ_ENTRY(g_provider) orphan; off_t mediasize; u_int sectorsize; off_t stripesize; off_t stripeoffset; struct devstat *stat; u_int spare1; u_int spare2; u_int flags; #define G_PF_WITHER 0x2 #define G_PF_ORPHAN 0x4 #define G_PF_ACCEPT_UNMAPPED 0x8 #define G_PF_DIRECT_SEND 0x10 #define G_PF_DIRECT_RECEIVE 0x20 LIST_HEAD(,g_geom_alias) aliases; /* Two fields for the implementing class to use */ void *private; u_int index; }; /* BIO_GETATTR("GEOM::setstate") argument values. */ #define G_STATE_FAILED 0 #define G_STATE_REBUILD 1 #define G_STATE_RESYNC 2 #define G_STATE_ACTIVE 3 /* geom_dev.c */ struct cdev; void g_dev_print(void); void g_dev_physpath_changed(void); struct g_provider *g_dev_getprovider(struct cdev *dev); /* geom_dump.c */ void (g_trace)(int level, const char *, ...) __printflike(2, 3); #define G_T_TOPOLOGY 0x01 #define G_T_BIO 0x02 #define G_T_ACCESS 0x04 extern int g_debugflags; #define G_F_FOOTSHOOTING 0x10 #define G_F_DISKIOCTL 0x40 #define G_F_CTLDUMP 0x80 #define g_trace(level, fmt, ...) do { \ if (__predict_false(g_debugflags & (level))) \ (g_trace)(level, fmt, ## __VA_ARGS__); \ } while (0) /* geom_event.c */ typedef void g_event_t(void *, int flag); struct g_event; #define EV_CANCEL 1 int g_post_event(g_event_t *func, void *arg, int flag, ...); int g_waitfor_event(g_event_t *func, void *arg, int flag, ...); void g_cancel_event(void *ref); int g_attr_changed(struct g_provider *pp, const char *attr, int flag); int g_media_changed(struct g_provider *pp, int flag); int g_media_gone(struct g_provider *pp, int flag); void g_orphan_provider(struct g_provider *pp, int error); struct g_event *g_alloc_event(int flag); void g_post_event_ep(g_event_t *func, void *arg, struct g_event *ep, ...); /* geom_subr.c */ int g_access(struct g_consumer *cp, int nread, int nwrite, int nexcl); int g_attach(struct g_consumer *cp, struct g_provider *pp); int g_compare_names(const char *namea, const char *nameb); void g_destroy_consumer(struct g_consumer *cp); void g_destroy_geom(struct g_geom *pp); void g_destroy_provider(struct g_provider *pp); void g_detach(struct g_consumer *cp); void g_error_provider(struct g_provider *pp, int error); struct g_provider *g_provider_by_name(char const *arg); int g_getattr__(const char *attr, struct g_consumer *cp, void *var, int len); #define g_getattr(a, c, v) g_getattr__((a), (c), (v), sizeof *(v)) int g_handleattr(struct bio *bp, const char *attribute, const void *val, int len); int g_handleattr_int(struct bio *bp, const char *attribute, int val); int g_handleattr_off_t(struct bio *bp, const char *attribute, off_t val); int g_handleattr_uint16_t(struct bio *bp, const char *attribute, uint16_t val); int g_handleattr_str(struct bio *bp, const char *attribute, const char *str); struct g_consumer * g_new_consumer(struct g_geom *gp); struct g_geom * g_new_geomf(struct g_class *mp, const char *fmt, ...) __printflike(2, 3); struct g_provider * g_new_providerf(struct g_geom *gp, const char *fmt, ...) __printflike(2, 3); void g_provider_add_alias(struct g_provider *pp, const char *fmt, ...) __printflike(2, 3); void g_resize_provider(struct g_provider *pp, off_t size); int g_retaste(struct g_class *mp); void g_spoil(struct g_provider *pp, struct g_consumer *cp); int g_std_access(struct g_provider *pp, int dr, int dw, int de); void g_std_done(struct bio *bp); void g_std_spoiled(struct g_consumer *cp); void g_wither_geom(struct g_geom *gp, int error); void g_wither_geom_close(struct g_geom *gp, int error); void g_wither_provider(struct g_provider *pp, int error); #if defined(DIAGNOSTIC) || defined(DDB) int g_valid_obj(void const *ptr); #endif #ifdef DIAGNOSTIC #define G_VALID_CLASS(foo) \ KASSERT(g_valid_obj(foo) == 1, ("%p is not a g_class", foo)) #define G_VALID_GEOM(foo) \ KASSERT(g_valid_obj(foo) == 2, ("%p is not a g_geom", foo)) #define G_VALID_CONSUMER(foo) \ KASSERT(g_valid_obj(foo) == 3, ("%p is not a g_consumer", foo)) #define G_VALID_PROVIDER(foo) \ KASSERT(g_valid_obj(foo) == 4, ("%p is not a g_provider", foo)) #else #define G_VALID_CLASS(foo) do { } while (0) #define G_VALID_GEOM(foo) do { } while (0) #define G_VALID_CONSUMER(foo) do { } while (0) #define G_VALID_PROVIDER(foo) do { } while (0) #endif int g_modevent(module_t, int, void *); /* geom_io.c */ struct bio * g_clone_bio(struct bio *); struct bio * g_duplicate_bio(struct bio *); void g_destroy_bio(struct bio *); void g_io_deliver(struct bio *bp, int error); int g_io_getattr(const char *attr, struct g_consumer *cp, int *len, void *ptr); int g_io_zonecmd(struct disk_zone_args *zone_args, struct g_consumer *cp); int g_io_flush(struct g_consumer *cp); int g_io_speedup(off_t shortage, u_int flags, size_t *resid, struct g_consumer *cp); void g_io_request(struct bio *bp, struct g_consumer *cp); struct bio *g_new_bio(void); struct bio *g_alloc_bio(void); void g_reset_bio(struct bio *); void * g_read_data(struct g_consumer *cp, off_t offset, off_t length, int *error); int g_write_data(struct g_consumer *cp, off_t offset, void *ptr, off_t length); int g_delete_data(struct g_consumer *cp, off_t offset, off_t length); void g_format_bio(struct sbuf *, const struct bio *bp); void g_print_bio(const char *prefix, const struct bio *bp, const char *fmtsuffix, ...) __printflike(3, 4); int g_use_g_read_data(void *, off_t, void **, int); int g_use_g_write_data(void *, off_t, void *, int); /* geom_kern.c / geom_kernsim.c */ #ifdef _KERNEL extern struct sx topology_lock; struct g_kerneldump { off_t offset; off_t length; struct dumperinfo di; }; MALLOC_DECLARE(M_GEOM); static __inline void * g_malloc(int size, int flags) { void *p; p = malloc(size, M_GEOM, flags); return (p); } static __inline void g_free(void *ptr) { #ifdef DIAGNOSTIC if (sx_xlocked(&topology_lock)) { KASSERT(g_valid_obj(ptr) == 0, ("g_free(%p) of live object, type %d", ptr, g_valid_obj(ptr))); } #endif free(ptr, M_GEOM); } #define g_topology_lock() \ do { \ sx_xlock(&topology_lock); \ } while (0) #define g_topology_try_lock() sx_try_xlock(&topology_lock) #define g_topology_unlock() \ do { \ sx_xunlock(&topology_lock); \ } while (0) #define g_topology_locked() sx_xlocked(&topology_lock) #define g_topology_assert() \ do { \ sx_assert(&topology_lock, SX_XLOCKED); \ } while (0) #define g_topology_assert_not() \ do { \ sx_assert(&topology_lock, SX_UNLOCKED); \ } while (0) #define g_topology_sleep(chan, timo) \ sx_sleep(chan, &topology_lock, 0, "gtopol", timo) #define DECLARE_GEOM_CLASS(class, name) \ static moduledata_t name##_mod = { \ #name, g_modevent, &class \ }; \ DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND); int g_is_geom_thread(struct thread *td); #ifndef _PATH_DEV #define _PATH_DEV "/dev/" #endif #endif /* _KERNEL */ /* geom_ctl.c */ int gctl_set_param(struct gctl_req *req, const char *param, void const *ptr, int len); void gctl_set_param_err(struct gctl_req *req, const char *param, void const *ptr, int len); void *gctl_get_param(struct gctl_req *req, const char *param, int *len); void *gctl_get_param_flags(struct gctl_req *req, const char *param, int flags, int *len); char const *gctl_get_asciiparam(struct gctl_req *req, const char *param); void *gctl_get_paraml(struct gctl_req *req, const char *param, int len); void *gctl_get_paraml_opt(struct gctl_req *req, const char *param, int len); int gctl_error(struct gctl_req *req, const char *fmt, ...) __printflike(2, 3); -void gctl_msg(struct gctl_req *req, const char *fmt, ...) __printflike(2, 3); +void gctl_msg(struct gctl_req *req, int, const char *fmt, ...) __printflike(3, 4); void gctl_post_messages(struct gctl_req *req); struct g_class *gctl_get_class(struct gctl_req *req, char const *arg); struct g_geom *gctl_get_geom(struct gctl_req *req, struct g_class *mp, char const *arg); struct g_provider *gctl_get_provider(struct gctl_req *req, char const *arg); #endif /* _GEOM_GEOM_H_ */ diff --git a/sys/geom/geom_ctl.c b/sys/geom/geom_ctl.c index 0f1dce934b63..132f30070b3c 100644 --- a/sys/geom/geom_ctl.c +++ b/sys/geom/geom_ctl.c @@ -1,638 +1,646 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2002 Poul-Henning Kamp * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * Copyright (c) 2022 Alexander Motin * * This software was developed for the FreeBSD Project by Poul-Henning Kamp * and NAI Labs, the Security Research Division of Network Associates, Inc. * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the * DARPA CHATS research program. * * 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. The names of the authors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * 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 #include #define GCTL_TABLE 1 #include #include static d_ioctl_t g_ctl_ioctl; static struct cdevsw g_ctl_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_ioctl = g_ctl_ioctl, .d_name = "g_ctl", }; CTASSERT(GCTL_PARAM_RD == VM_PROT_READ); CTASSERT(GCTL_PARAM_WR == VM_PROT_WRITE); void g_ctl_init(void) { make_dev_credf(MAKEDEV_ETERNAL, &g_ctl_cdevsw, 0, NULL, UID_ROOT, GID_OPERATOR, 0640, PATH_GEOM_CTL); } /* * Report an error back to the user in ascii format. Return nerror * or EINVAL if nerror isn't specified. */ int gctl_error(struct gctl_req *req, const char *fmt, ...) { va_list ap; if (req == NULL) return (EINVAL); /* We only record the first error */ if (sbuf_done(req->serror)) { if (!req->nerror) req->nerror = EEXIST; #ifdef DIAGNOSTIC printf("gctl_error: buffer closed, message discarded.\n"); #endif return (req->nerror); } if (!req->nerror) req->nerror = EINVAL; /* If this is the last of several messages, indent it on a new line */ if (sbuf_len(req->serror) > 0) sbuf_cat(req->serror, "\n\t"); va_start(ap, fmt); sbuf_vprintf(req->serror, fmt, ap); va_end(ap); gctl_post_messages(req); return (req->nerror); } /* * The gctl_error() function will only report a single message. * Commands that handle multiple devices may want to report a * message for each of the devices. The gctl_msg() function * can be called multiple times to post messages. When done * the application must either call gctl_post_messages() or * call gctl_error() to cause the messages to be reported to * the calling process. + * + * The errno argument should be zero if it is an informational + * message or an errno value (EINVAL, EBUSY, etc) if it is an error. + * If any of the messages has a non-zero errno, the utility will + * EXIT_FAILURE. If only informational messages (with zero errno) + * are posted, the utility will EXIT_SUCCESS. */ void -gctl_msg(struct gctl_req *req, const char *fmt, ...) +gctl_msg(struct gctl_req *req, int errno, const char *fmt, ...) { va_list ap; if (req == NULL) return; if (sbuf_done(req->serror)) { #ifdef DIAGNOSTIC printf("gctl_msg: buffer closed, message discarded.\n"); #endif return; } + if (req->nerror == 0) + req->nerror = errno; /* Put second and later messages indented on a new line */ if (sbuf_len(req->serror) > 0) sbuf_cat(req->serror, "\n\t"); va_start(ap, fmt); sbuf_vprintf(req->serror, fmt, ap); va_end(ap); } /* * Post the messages to the user. */ void gctl_post_messages(struct gctl_req *req) { if (sbuf_done(req->serror)) { #ifdef DIAGNOSTIC printf("gctl_post_messages: message buffer already closed.\n"); #endif return; } sbuf_finish(req->serror); if (g_debugflags & G_F_CTLDUMP) printf("gctl %p message(s) \"%s\"\n", req, sbuf_data(req->serror)); } /* * Allocate space and copyin() something. * XXX: this should really be a standard function in the kernel. */ static void * geom_alloc_copyin(struct gctl_req *req, void *uaddr, size_t len) { void *ptr; ptr = g_malloc(len, M_WAITOK); req->nerror = copyin(uaddr, ptr, len); if (!req->nerror) return (ptr); g_free(ptr); return (NULL); } static void gctl_copyin(struct gctl_req *req) { struct gctl_req_arg *ap; char *p; u_int i; if (req->narg > GEOM_CTL_ARG_MAX) { gctl_error(req, "too many arguments"); req->arg = NULL; return; } ap = geom_alloc_copyin(req, req->arg, req->narg * sizeof(*ap)); if (ap == NULL) { gctl_error(req, "bad control request"); req->arg = NULL; return; } /* Nothing have been copyin()'ed yet */ for (i = 0; i < req->narg; i++) { ap[i].flag &= ~(GCTL_PARAM_NAMEKERNEL|GCTL_PARAM_VALUEKERNEL); ap[i].flag &= ~GCTL_PARAM_CHANGED; ap[i].kvalue = NULL; } for (i = 0; i < req->narg; i++) { if (ap[i].nlen < 1 || ap[i].nlen > SPECNAMELEN) { gctl_error(req, "wrong param name length %d: %d", i, ap[i].nlen); break; } p = geom_alloc_copyin(req, ap[i].name, ap[i].nlen); if (p == NULL) break; if (p[ap[i].nlen - 1] != '\0') { gctl_error(req, "unterminated param name"); g_free(p); break; } ap[i].name = p; ap[i].flag |= GCTL_PARAM_NAMEKERNEL; if (ap[i].len <= 0) { gctl_error(req, "negative param length"); break; } if (ap[i].flag & GCTL_PARAM_RD) { p = geom_alloc_copyin(req, ap[i].value, ap[i].len); if (p == NULL) break; if ((ap[i].flag & GCTL_PARAM_ASCII) && p[ap[i].len - 1] != '\0') { gctl_error(req, "unterminated param value"); g_free(p); break; } } else { p = g_malloc(ap[i].len, M_WAITOK | M_ZERO); } ap[i].kvalue = p; ap[i].flag |= GCTL_PARAM_VALUEKERNEL; } req->arg = ap; return; } static void gctl_copyout(struct gctl_req *req) { int error, i; struct gctl_req_arg *ap; if (req->nerror) return; error = 0; ap = req->arg; for (i = 0; i < req->narg; i++, ap++) { if (!(ap->flag & GCTL_PARAM_CHANGED)) continue; error = copyout(ap->kvalue, ap->value, ap->len); if (!error) continue; req->nerror = error; return; } return; } static void gctl_free(struct gctl_req *req) { u_int i; sbuf_delete(req->serror); if (req->arg == NULL) return; for (i = 0; i < req->narg; i++) { if (req->arg[i].flag & GCTL_PARAM_NAMEKERNEL) g_free(req->arg[i].name); if ((req->arg[i].flag & GCTL_PARAM_VALUEKERNEL) && req->arg[i].len > 0) g_free(req->arg[i].kvalue); } g_free(req->arg); } static void gctl_dump(struct gctl_req *req, const char *what) { struct gctl_req_arg *ap; u_int i; int j; printf("Dump of gctl %s at %p:\n", what, req); if (req->nerror > 0) { printf(" nerror:\t%d\n", req->nerror); if (sbuf_len(req->serror) > 0) printf(" error:\t\"%s\"\n", sbuf_data(req->serror)); } if (req->arg == NULL) return; for (i = 0; i < req->narg; i++) { ap = &req->arg[i]; if (!(ap->flag & GCTL_PARAM_NAMEKERNEL)) printf(" param:\t%d@%p", ap->nlen, ap->name); else printf(" param:\t\"%s\"", ap->name); printf(" [%s%s%d] = ", ap->flag & GCTL_PARAM_RD ? "R" : "", ap->flag & GCTL_PARAM_WR ? "W" : "", ap->len); if (!(ap->flag & GCTL_PARAM_VALUEKERNEL)) { printf(" =@ %p", ap->value); } else if (ap->flag & GCTL_PARAM_ASCII) { printf("\"%s\"", (char *)ap->kvalue); } else if (ap->len > 0) { for (j = 0; j < ap->len && j < 512; j++) printf(" %02x", ((u_char *)ap->kvalue)[j]); } else { printf(" = %p", ap->kvalue); } printf("\n"); } } int gctl_set_param(struct gctl_req *req, const char *param, void const *ptr, int len) { u_int i; struct gctl_req_arg *ap; for (i = 0; i < req->narg; i++) { ap = &req->arg[i]; if (strcmp(param, ap->name)) continue; if (!(ap->flag & GCTL_PARAM_WR)) return (EPERM); ap->flag |= GCTL_PARAM_CHANGED; if (ap->len < len) { bcopy(ptr, ap->kvalue, ap->len); return (ENOSPC); } bcopy(ptr, ap->kvalue, len); return (0); } return (EINVAL); } void gctl_set_param_err(struct gctl_req *req, const char *param, void const *ptr, int len) { switch (gctl_set_param(req, param, ptr, len)) { case EPERM: gctl_error(req, "No write access %s argument", param); break; case ENOSPC: gctl_error(req, "Wrong length %s argument", param); break; case EINVAL: gctl_error(req, "Missing %s argument", param); break; } } void * gctl_get_param_flags(struct gctl_req *req, const char *param, int flags, int *len) { u_int i; void *p; struct gctl_req_arg *ap; for (i = 0; i < req->narg; i++) { ap = &req->arg[i]; if (strcmp(param, ap->name)) continue; if ((ap->flag & flags) != flags) continue; p = ap->kvalue; if (len != NULL) *len = ap->len; return (p); } return (NULL); } void * gctl_get_param(struct gctl_req *req, const char *param, int *len) { return (gctl_get_param_flags(req, param, GCTL_PARAM_RD, len)); } char const * gctl_get_asciiparam(struct gctl_req *req, const char *param) { char const *p; int len; p = gctl_get_param_flags(req, param, GCTL_PARAM_RD, &len); if (p == NULL) return (NULL); if (len < 1) { gctl_error(req, "Argument without length (%s)", param); return (NULL); } if (p[len - 1] != '\0') { gctl_error(req, "Unterminated argument (%s)", param); return (NULL); } return (p); } void * gctl_get_paraml_opt(struct gctl_req *req, const char *param, int len) { int i; void *p; p = gctl_get_param(req, param, &i); if (i != len) { p = NULL; gctl_error(req, "Wrong length %s argument", param); } return (p); } void * gctl_get_paraml(struct gctl_req *req, const char *param, int len) { void *p; p = gctl_get_paraml_opt(req, param, len); if (p == NULL) gctl_error(req, "Missing %s argument", param); return (p); } struct g_class * gctl_get_class(struct gctl_req *req, char const *arg) { char const *p; struct g_class *cp; p = gctl_get_asciiparam(req, arg); if (p == NULL) { gctl_error(req, "Missing %s argument", arg); return (NULL); } LIST_FOREACH(cp, &g_classes, class) { if (!strcmp(p, cp->name)) return (cp); } gctl_error(req, "Class not found: \"%s\"", p); return (NULL); } struct g_geom * gctl_get_geom(struct gctl_req *req, struct g_class *mp, char const *arg) { char const *p; struct g_geom *gp; MPASS(mp != NULL); p = gctl_get_asciiparam(req, arg); if (p == NULL) { gctl_error(req, "Missing %s argument", arg); return (NULL); } LIST_FOREACH(gp, &mp->geom, geom) if (!strcmp(p, gp->name)) return (gp); gctl_error(req, "Geom not found: \"%s\"", p); return (NULL); } struct g_provider * gctl_get_provider(struct gctl_req *req, char const *arg) { char const *p; struct g_provider *pp; p = gctl_get_asciiparam(req, arg); if (p == NULL) { gctl_error(req, "Missing '%s' argument", arg); return (NULL); } pp = g_provider_by_name(p); if (pp != NULL) return (pp); gctl_error(req, "Provider not found: \"%s\"", p); return (NULL); } static void g_ctl_getxml(struct gctl_req *req, struct g_class *mp) { const char *name; char *buf; struct sbuf *sb; int len, i = 0, n = 0, *parents; struct g_geom *gp, **gps; struct g_consumer *cp; parents = gctl_get_paraml(req, "parents", sizeof(*parents)); if (parents == NULL) return; name = gctl_get_asciiparam(req, "arg0"); n = 0; LIST_FOREACH(gp, &mp->geom, geom) { if (name && strcmp(gp->name, name) != 0) continue; n++; if (*parents) { LIST_FOREACH(cp, &gp->consumer, consumer) n++; } } gps = g_malloc((n + 1) * sizeof(*gps), M_WAITOK); i = 0; LIST_FOREACH(gp, &mp->geom, geom) { if (name && strcmp(gp->name, name) != 0) continue; gps[i++] = gp; if (*parents) { LIST_FOREACH(cp, &gp->consumer, consumer) { if (cp->provider != NULL) gps[i++] = cp->provider->geom; } } } KASSERT(i == n, ("different number of geoms found (%d != %d)", i, n)); gps[i] = 0; buf = gctl_get_param_flags(req, "output", GCTL_PARAM_WR, &len); if (buf == NULL) { gctl_error(req, "output parameter missing"); g_free(gps); return; } sb = sbuf_new(NULL, buf, len, SBUF_FIXEDLEN | SBUF_INCLUDENUL); g_conf_specific(sb, gps); gctl_set_param(req, "output", buf, 0); if (sbuf_error(sb)) gctl_error(req, "output buffer overflow"); sbuf_delete(sb); g_free(gps); } static void g_ctl_req(void *arg, int flag __unused) { struct g_class *mp; struct gctl_req *req; char const *verb; g_topology_assert(); req = arg; mp = gctl_get_class(req, "class"); if (mp == NULL) return; verb = gctl_get_param(req, "verb", NULL); if (verb == NULL) { gctl_error(req, "Verb missing"); return; } if (strcmp(verb, "getxml") == 0) { g_ctl_getxml(req, mp); } else if (mp->ctlreq == NULL) { gctl_error(req, "Class takes no requests"); } else { mp->ctlreq(req, mp, verb); } g_topology_assert(); } static int g_ctl_ioctl_ctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct gctl_req *req; int nerror; req = (void *)data; req->nerror = 0; /* It is an error if we cannot return an error text */ if (req->lerror < 2) return (EINVAL); if (!useracc(req->error, req->lerror, VM_PROT_WRITE)) return (EINVAL); req->serror = sbuf_new_auto(); /* Check the version */ if (req->version != GCTL_VERSION) { gctl_error(req, "kernel and libgeom version mismatch."); req->arg = NULL; } else { /* Get things on board */ gctl_copyin(req); if (g_debugflags & G_F_CTLDUMP) gctl_dump(req, "request"); if (!req->nerror) { g_waitfor_event(g_ctl_req, req, M_WAITOK, NULL); if (g_debugflags & G_F_CTLDUMP) gctl_dump(req, "result"); gctl_copyout(req); } } if (sbuf_done(req->serror)) { copyout(sbuf_data(req->serror), req->error, imin(req->lerror, sbuf_len(req->serror) + 1)); } nerror = req->nerror; gctl_free(req); return (nerror); } static int g_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { int error; switch(cmd) { case GEOM_CTL: error = g_ctl_ioctl_ctl(dev, cmd, data, fflag, td); break; default: error = ENOIOCTL; break; } return (error); } diff --git a/sys/geom/union/g_union.c b/sys/geom/union/g_union.c index ddc0acf52b78..dee541064ced 100644 --- a/sys/geom/union/g_union.c +++ b/sys/geom/union/g_union.c @@ -1,1395 +1,1400 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2022 Marshall Kirk McKusick * * 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 AUTHORS 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 AUTHORS 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include SYSCTL_DECL(_kern_geom); static SYSCTL_NODE(_kern_geom, OID_AUTO, union, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "GEOM_UNION stuff"); static u_int g_union_debug = 0; SYSCTL_UINT(_kern_geom_union, OID_AUTO, debug, CTLFLAG_RW, &g_union_debug, 0, "Debug level"); static void g_union_config(struct gctl_req *req, struct g_class *mp, const char *verb); static g_access_t g_union_access; static g_start_t g_union_start; static g_dumpconf_t g_union_dumpconf; static g_orphan_t g_union_orphan; static int g_union_destroy_geom(struct gctl_req *req, struct g_class *mp, struct g_geom *gp); static g_provgone_t g_union_providergone; static g_resize_t g_union_resize; struct g_class g_union_class = { .name = G_UNION_CLASS_NAME, .version = G_VERSION, .ctlreq = g_union_config, .access = g_union_access, .start = g_union_start, .dumpconf = g_union_dumpconf, .orphan = g_union_orphan, .destroy_geom = g_union_destroy_geom, .providergone = g_union_providergone, .resize = g_union_resize, }; static void g_union_ctl_create(struct gctl_req *req, struct g_class *mp, bool); static intmax_t g_union_fetcharg(struct gctl_req *req, const char *name); static bool g_union_verify_nprefix(const char *name); static void g_union_ctl_destroy(struct gctl_req *req, struct g_class *mp, bool); static struct g_geom *g_union_find_geom(struct g_class *mp, const char *name); static void g_union_ctl_reset(struct gctl_req *req, struct g_class *mp, bool); static void g_union_ctl_revert(struct gctl_req *req, struct g_class *mp, bool); static void g_union_revert(struct g_union_softc *sc); static void g_union_doio(struct g_union_wip *wip); static void g_union_ctl_commit(struct gctl_req *req, struct g_class *mp, bool); static void g_union_setmap(struct bio *bp, struct g_union_softc *sc); static bool g_union_getmap(struct bio *bp, struct g_union_softc *sc, off_t *len2read); static void g_union_done(struct bio *bp); static void g_union_kerneldump(struct bio *bp, struct g_union_softc *sc); static int g_union_dumper(void *, void *, off_t, size_t); static int g_union_destroy(struct gctl_req *req, struct g_geom *gp, bool force); /* * Operate on union-specific configuration commands. */ static void g_union_config(struct gctl_req *req, struct g_class *mp, const char *verb) { uint32_t *version, *verbose; g_topology_assert(); version = gctl_get_paraml(req, "version", sizeof(*version)); if (version == NULL) { gctl_error(req, "No '%s' argument.", "version"); return; } if (*version != G_UNION_VERSION) { gctl_error(req, "Userland and kernel parts are out of sync."); return; } verbose = gctl_get_paraml(req, "verbose", sizeof(*verbose)); if (verbose == NULL) { gctl_error(req, "No '%s' argument.", "verbose"); return; } if (strcmp(verb, "create") == 0) { g_union_ctl_create(req, mp, *verbose); return; } else if (strcmp(verb, "destroy") == 0) { g_union_ctl_destroy(req, mp, *verbose); return; } else if (strcmp(verb, "reset") == 0) { g_union_ctl_reset(req, mp, *verbose); return; } else if (strcmp(verb, "revert") == 0) { g_union_ctl_revert(req, mp, *verbose); return; } else if (strcmp(verb, "commit") == 0) { g_union_ctl_commit(req, mp, *verbose); return; } gctl_error(req, "Unknown verb."); } /* * Create a union device. */ static void g_union_ctl_create(struct gctl_req *req, struct g_class *mp, bool verbose) { struct g_provider *upperpp, *lowerpp, *newpp; struct g_consumer *uppercp, *lowercp; struct g_union_softc *sc; struct g_geom_alias *gap; struct g_geom *gp; intmax_t offset, secsize, size, needed; const char *gunionname; int *nargs, error, i, n; char name[64]; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs < 2) { gctl_error(req, "Missing device(s)."); return; } if (*nargs > 2) { gctl_error(req, "Extra device(s)."); return; } offset = g_union_fetcharg(req, "offset"); size = g_union_fetcharg(req, "size"); secsize = g_union_fetcharg(req, "secsize"); gunionname = gctl_get_asciiparam(req, "gunionname"); upperpp = gctl_get_provider(req, "arg0"); lowerpp = gctl_get_provider(req, "arg1"); if (upperpp == NULL || lowerpp == NULL) /* error message provided by gctl_get_provider() */ return; /* Create the union */ if (secsize == 0) secsize = lowerpp->sectorsize; else if ((secsize % lowerpp->sectorsize) != 0) { gctl_error(req, "Sector size %jd is not a multiple of lower " "provider %s's %jd sector size.", (intmax_t)secsize, lowerpp->name, (intmax_t)lowerpp->sectorsize); return; } if (secsize > maxphys) { gctl_error(req, "Too big secsize %jd for lower provider %s.", (intmax_t)secsize, lowerpp->name); return; } if (secsize % upperpp->sectorsize != 0) { gctl_error(req, "Sector size %jd is not a multiple of upper " "provider %s's %jd sector size.", (intmax_t)secsize, upperpp->name, (intmax_t)upperpp->sectorsize); return; } if ((offset % secsize) != 0) { gctl_error(req, "Offset %jd is not a multiple of lower " "provider %s's %jd sector size.", (intmax_t)offset, lowerpp->name, (intmax_t)lowerpp->sectorsize); return; } if (size == 0) size = lowerpp->mediasize - offset; else size -= offset; if ((size % secsize) != 0) { gctl_error(req, "Size %jd is not a multiple of sector size " "%jd.", (intmax_t)size, (intmax_t)secsize); return; } if (offset + size < lowerpp->mediasize) { gctl_error(req, "Size %jd is too small for lower provider %s, " "needs %jd.", (intmax_t)(offset + size), lowerpp->name, lowerpp->mediasize); return; } if (size > upperpp->mediasize) { gctl_error(req, "Upper provider %s size (%jd) is too small, " "needs %jd.", upperpp->name, (intmax_t)upperpp->mediasize, (intmax_t)size); return; } if (gunionname != NULL && !g_union_verify_nprefix(gunionname)) { gctl_error(req, "Gunion name %s must be alphanumeric.", gunionname); return; } if (gunionname != NULL) { n = snprintf(name, sizeof(name), "%s%s", gunionname, G_UNION_SUFFIX); } else { n = snprintf(name, sizeof(name), "%s-%s%s", upperpp->name, lowerpp->name, G_UNION_SUFFIX); } if (n <= 0 || n >= sizeof(name)) { gctl_error(req, "Invalid provider name."); return; } LIST_FOREACH(gp, &mp->geom, geom) { if (strcmp(gp->name, name) == 0) { gctl_error(req, "Provider %s already exists.", name); return; } } gp = g_new_geomf(mp, "%s", name); sc = g_malloc(sizeof(*sc), M_WAITOK | M_ZERO); rw_init(&sc->sc_rwlock, "gunion"); TAILQ_INIT(&sc->sc_wiplist); sc->sc_offset = offset; sc->sc_size = size; sc->sc_sectorsize = secsize; sc->sc_reads = 0; sc->sc_writes = 0; sc->sc_deletes = 0; sc->sc_getattrs = 0; sc->sc_flushes = 0; sc->sc_speedups = 0; sc->sc_cmd0s = 0; sc->sc_cmd1s = 0; sc->sc_cmd2s = 0; sc->sc_readbytes = 0; sc->sc_wrotebytes = 0; sc->sc_writemap_memory = 0; gp->softc = sc; newpp = g_new_providerf(gp, "%s", gp->name); newpp->flags |= G_PF_DIRECT_SEND | G_PF_DIRECT_RECEIVE; newpp->mediasize = size; newpp->sectorsize = secsize; LIST_FOREACH(gap, &upperpp->aliases, ga_next) g_provider_add_alias(newpp, "%s%s", gap->ga_alias, G_UNION_SUFFIX); LIST_FOREACH(gap, &lowerpp->aliases, ga_next) g_provider_add_alias(newpp, "%s%s", gap->ga_alias, G_UNION_SUFFIX); lowercp = g_new_consumer(gp); lowercp->flags |= G_CF_DIRECT_SEND | G_CF_DIRECT_RECEIVE; if ((error = g_attach(lowercp, lowerpp)) != 0) { gctl_error(req, "Error %d: cannot attach to provider %s.", error, lowerpp->name); goto fail1; } /* request read and exclusive access for lower */ if ((error = g_access(lowercp, 1, 0, 1)) != 0) { gctl_error(req, "Error %d: cannot obtain exclusive access to " "%s.\n\tMust be unmounted or mounted read-only.", error, lowerpp->name); goto fail2; } uppercp = g_new_consumer(gp); uppercp->flags |= G_CF_DIRECT_SEND | G_CF_DIRECT_RECEIVE; if ((error = g_attach(uppercp, upperpp)) != 0) { gctl_error(req, "Error %d: cannot attach to provider %s.", error, upperpp->name); goto fail3; } /* request read, write, and exclusive access for upper */ if ((error = g_access(uppercp, 1, 1, 1)) != 0) { gctl_error(req, "Error %d: cannot obtain write access to %s.", error, upperpp->name); goto fail4; } sc->sc_uppercp = uppercp; sc->sc_lowercp = lowercp; newpp->flags |= (upperpp->flags & G_PF_ACCEPT_UNMAPPED) & (lowerpp->flags & G_PF_ACCEPT_UNMAPPED); g_error_provider(newpp, 0); /* * Allocate the map that tracks the sectors that have been written * to the top layer. We use a 2-level hierarchy as that lets us * map up to 1 petabyte using allocations of less than 33 Mb * when using 4K byte sectors (or 268 Mb with 512 byte sectors). * * We totally populate the leaf nodes rather than allocating them * as they are first used because their usage occurs in the * g_union_start() routine that may be running in the g_down * thread which cannot sleep. */ sc->sc_map_size = roundup(size / secsize, BITS_PER_ENTRY); needed = sc->sc_map_size / BITS_PER_ENTRY; for (sc->sc_root_size = 1; sc->sc_root_size * sc->sc_root_size < needed; sc->sc_root_size++) continue; sc->sc_writemap_root = g_malloc(sc->sc_root_size * sizeof(uint64_t *), M_WAITOK | M_ZERO); sc->sc_leaf_size = sc->sc_root_size; sc->sc_bits_per_leaf = sc->sc_leaf_size * BITS_PER_ENTRY; sc->sc_leafused = g_malloc(roundup(sc->sc_root_size, BITS_PER_ENTRY), M_WAITOK | M_ZERO); for (i = 0; i < sc->sc_root_size; i++) sc->sc_writemap_root[i] = g_malloc(sc->sc_leaf_size * sizeof(uint64_t), M_WAITOK | M_ZERO); sc->sc_writemap_memory = (sc->sc_root_size + sc->sc_root_size * sc->sc_leaf_size) * sizeof(uint64_t) + roundup(sc->sc_root_size, BITS_PER_ENTRY); if (verbose) - gctl_error(req, "Device %s created with memory map size %jd.", + gctl_msg(req, 0, "Device %s created with memory map size %jd.", gp->name, (intmax_t)sc->sc_writemap_memory); + gctl_post_messages(req); G_UNION_DEBUG(1, "Device %s created with memory map size %jd.", gp->name, (intmax_t)sc->sc_writemap_memory); return; fail4: g_detach(uppercp); fail3: g_destroy_consumer(uppercp); g_access(lowercp, -1, 0, -1); fail2: g_detach(lowercp); fail1: g_destroy_consumer(lowercp); g_destroy_provider(newpp); g_destroy_geom(gp); } /* * Fetch named option and verify that it is positive. */ static intmax_t g_union_fetcharg(struct gctl_req *req, const char *name) { intmax_t *val; val = gctl_get_paraml_opt(req, name, sizeof(*val)); if (val == NULL) return (0); if (*val >= 0) return (*val); - gctl_error(req, "Invalid '%s': negative value, using default.", name); + gctl_msg(req, EINVAL, "Invalid '%s' (%jd): negative value, " + "using default.", name, *val); return (0); } /* * Verify that a name is alphanumeric. */ static bool g_union_verify_nprefix(const char *name) { int i; for (i = 0; i < strlen(name); i++) { if (isalpha(name[i]) == 0 && isdigit(name[i]) == 0) { return (false); } } return (true); } /* * Destroy a union device. */ static void g_union_ctl_destroy(struct gctl_req *req, struct g_class *mp, bool verbose) { int *nargs, *force, error, i; struct g_geom *gp; const char *name; char param[16]; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No 'force' argument."); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); name = gctl_get_asciiparam(req, param); if (name == NULL) { - gctl_msg(req, "No '%s' argument.", param); + gctl_msg(req, EINVAL, "No '%s' argument.", param); continue; } if (strncmp(name, _PATH_DEV, strlen(_PATH_DEV)) == 0) name += strlen(_PATH_DEV); gp = g_union_find_geom(mp, name); if (gp == NULL) { - gctl_msg(req, "Device %s is invalid.", name); + gctl_msg(req, EINVAL, "Device %s is invalid.", name); continue; } error = g_union_destroy(verbose ? req : NULL, gp, *force); if (error != 0) - gctl_msg(req, "Error %d: cannot destroy device %s.", - error, gp->name); + gctl_msg(req, error, "Error %d: " + "cannot destroy device %s.", error, gp->name); } gctl_post_messages(req); } /* * Find a union geom. */ static struct g_geom * g_union_find_geom(struct g_class *mp, const char *name) { struct g_geom *gp; LIST_FOREACH(gp, &mp->geom, geom) { if (strcmp(gp->name, name) == 0) return (gp); } return (NULL); } /* * Zero out all the statistics associated with a union device. */ static void g_union_ctl_reset(struct gctl_req *req, struct g_class *mp, bool verbose) { struct g_union_softc *sc; struct g_provider *pp; struct g_geom *gp; char param[16]; int i, *nargs; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); pp = gctl_get_provider(req, param); if (pp == NULL) { - gctl_msg(req, "No '%s' argument.", param); + gctl_msg(req, EINVAL, "No '%s' argument.", param); continue; } gp = pp->geom; if (gp->class != mp) { - gctl_msg(req, "Provider %s is invalid.", + gctl_msg(req, EINVAL, "Provider %s is invalid.", pp->name); continue; } sc = gp->softc; sc->sc_reads = 0; sc->sc_writes = 0; sc->sc_deletes = 0; sc->sc_getattrs = 0; sc->sc_flushes = 0; sc->sc_speedups = 0; sc->sc_cmd0s = 0; sc->sc_cmd1s = 0; sc->sc_cmd2s = 0; sc->sc_readbytes = 0; sc->sc_wrotebytes = 0; if (verbose) - gctl_msg(req, "Device %s has been reset.", pp->name); + gctl_msg(req, 0, "Device %s has been reset.", pp->name); G_UNION_DEBUG(1, "Device %s has been reset.", pp->name); } gctl_post_messages(req); } /* * Revert all write requests made to the top layer of the union. */ static void g_union_ctl_revert(struct gctl_req *req, struct g_class *mp, bool verbose) { struct g_union_softc *sc; struct g_provider *pp; struct g_geom *gp; char param[16]; int i, *nargs; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); pp = gctl_get_provider(req, param); if (pp == NULL) { - gctl_msg(req, "No '%s' argument.", param); + gctl_msg(req, EINVAL, "No '%s' argument.", param); continue; } gp = pp->geom; if (gp->class != mp) { - gctl_msg(req, "Provider %s is invalid.", pp->name); + gctl_msg(req, EINVAL, "Provider %s is invalid.", + pp->name); continue; } sc = gp->softc; if (g_union_get_writelock(sc) != 0) { - gctl_msg(req, "Revert already in progress for " + gctl_msg(req, EINVAL, "Revert already in progress for " "provider %s.", pp->name); continue; } /* * No mount or other use of union is allowed. */ if (pp->acr > 0 || pp->acw > 0 || pp->ace > 0) { - gctl_msg(req, "Unable to get exclusive access for " - "reverting of %s;\n\t%s cannot be mounted or " + gctl_msg(req, EPERM, "Unable to get exclusive access " + "for reverting of %s;\n\t%s cannot be mounted or " "otherwise open during a revert.", pp->name, pp->name); g_union_rel_writelock(sc); continue; } g_union_revert(sc); g_union_rel_writelock(sc); if (verbose) - gctl_msg(req, "Device %s has been reverted.", pp->name); + gctl_msg(req, 0, "Device %s has been reverted.", + pp->name); G_UNION_DEBUG(1, "Device %s has been reverted.", pp->name); } gctl_post_messages(req); } /* * Revert union writes by zero'ing out the writemap. */ static void g_union_revert(struct g_union_softc *sc) { int i; G_WLOCK(sc); for (i = 0; i < sc->sc_root_size; i++) memset(sc->sc_writemap_root[i], 0, sc->sc_leaf_size * sizeof(uint64_t)); memset(sc->sc_leafused, 0, roundup(sc->sc_root_size, BITS_PER_ENTRY)); G_WUNLOCK(sc); } /* * Commit all the writes made in the top layer to the lower layer. */ static void g_union_ctl_commit(struct gctl_req *req, struct g_class *mp, bool verbose) { struct g_union_softc *sc; struct g_provider *pp, *lowerpp; struct g_consumer *lowercp; struct g_geom *gp; struct bio *bp; char param[16]; off_t len2rd, len2wt, savelen; int i, error, error1, *nargs, *force, *reboot; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No 'force' argument."); return; } reboot = gctl_get_paraml(req, "reboot", sizeof(*reboot)); if (reboot == NULL) { gctl_error(req, "No 'reboot' argument."); return; } /* Get a bio buffer to do our I/O */ bp = g_alloc_bio(); bp->bio_data = g_malloc(MAXBSIZE, M_WAITOK); bp->bio_done = biodone; for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); pp = gctl_get_provider(req, param); if (pp == NULL) { - gctl_msg(req, "No '%s' argument.", param); + gctl_msg(req, EINVAL, "No '%s' argument.", param); continue; } gp = pp->geom; if (gp->class != mp) { - gctl_msg(req, "Provider %s is invalid.", pp->name); + gctl_msg(req, EINVAL, "Provider %s is invalid.", + pp->name); continue; } sc = gp->softc; if (g_union_get_writelock(sc) != 0) { - gctl_msg(req, "Commit already in progress for " + gctl_msg(req, EINVAL, "Commit already in progress for " "provider %s.", pp->name); continue; } /* upgrade to write access for lower */ lowercp = sc->sc_lowercp; lowerpp = lowercp->provider; /* * No mount or other use of union is allowed, unless the * -f flag is given which allows read-only mount or usage. */ if ((*force == false && pp->acr > 0) || pp->acw > 0 || pp->ace > 0) { - gctl_msg(req, "Unable to get exclusive access for " - "writing of %s.\n\tNote that %s cannot be mounted " - "or otherwise\n\topen during a commit unless the " - "-f flag is used.", pp->name, pp->name); + gctl_msg(req, EPERM, "Unable to get exclusive access " + "for writing of %s.\n\tNote that %s cannot be " + "mounted or otherwise\n\topen during a commit " + "unless the -f flag is used.", pp->name, pp->name); g_union_rel_writelock(sc); continue; } /* * No mount or other use of lower media is allowed, unless the * -f flag is given which allows read-only mount or usage. */ if ((*force == false && lowerpp->acr > lowercp->acr) || lowerpp->acw > lowercp->acw || lowerpp->ace > lowercp->ace) { - gctl_msg(req, "provider %s is unable to get " + gctl_msg(req, EPERM, "provider %s is unable to get " "exclusive access to %s\n\tfor writing. Note that " "%s cannot be mounted or otherwise open\n\tduring " "a commit unless the -f flag is used.", pp->name, lowerpp->name, lowerpp->name); g_union_rel_writelock(sc); continue; } if ((error = g_access(lowercp, 0, 1, 0)) != 0) { - gctl_msg(req, "Error %d: provider %s is unable to " - "access %s for writing.", error, pp->name, + gctl_msg(req, error, "Error %d: provider %s is unable " + "to access %s for writing.", error, pp->name, lowerpp->name); g_union_rel_writelock(sc); continue; } g_topology_unlock(); /* Loop over write map copying across written blocks */ bp->bio_offset = 0; bp->bio_length = sc->sc_map_size * sc->sc_sectorsize; G_RLOCK(sc); error = 0; while (bp->bio_length > 0) { if (!g_union_getmap(bp, sc, &len2rd)) { /* not written, so skip */ bp->bio_offset += len2rd; bp->bio_length -= len2rd; continue; } G_RUNLOCK(sc); /* need to read then write len2rd sectors */ for ( ; len2rd > 0; len2rd -= len2wt) { /* limit ourselves to MAXBSIZE size I/Os */ len2wt = len2rd; if (len2wt > MAXBSIZE) len2wt = MAXBSIZE; savelen = bp->bio_length; bp->bio_length = len2wt; bp->bio_cmd = BIO_READ; g_io_request(bp, sc->sc_uppercp); if ((error = biowait(bp, "rdunion")) != 0) { - gctl_msg(req, "Commit read error %d " - "in provider %s, commit aborted.", - error, pp->name); + gctl_msg(req, error, "Commit read " + "error %d in provider %s, commit " + "aborted.", error, pp->name); goto cleanup; } bp->bio_flags &= ~BIO_DONE; bp->bio_cmd = BIO_WRITE; g_io_request(bp, lowercp); if ((error = biowait(bp, "wtunion")) != 0) { - gctl_msg(req, "Commit write error %d " - "in provider %s, commit aborted.", - error, pp->name); + gctl_msg(req, error, "Commit write " + "error %d in provider %s, commit " + "aborted.", error, pp->name); goto cleanup; } bp->bio_flags &= ~BIO_DONE; bp->bio_offset += len2wt; bp->bio_length = savelen - len2wt; } G_RLOCK(sc); } G_RUNLOCK(sc); /* clear the write map */ g_union_revert(sc); cleanup: g_topology_lock(); /* return lower to previous access */ if ((error1 = g_access(lowercp, 0, -1, 0)) != 0) { G_UNION_DEBUG(2, "Error %d: device %s could not reset " "access to %s (r=0 w=-1 e=0).", error1, pp->name, lowerpp->name); } g_union_rel_writelock(sc); if (error == 0 && verbose) - gctl_msg(req, "Device %s has been committed.", + gctl_msg(req, 0, "Device %s has been committed.", pp->name); G_UNION_DEBUG(1, "Device %s has been committed.", pp->name); } gctl_post_messages(req); g_free(bp->bio_data); g_destroy_bio(bp); if (*reboot) kern_reboot(RB_AUTOBOOT); } /* * Generally allow access unless a commit is in progress. */ static int g_union_access(struct g_provider *pp, int r, int w, int e) { struct g_union_softc *sc; sc = pp->geom->softc; if (sc == NULL) { if (r <= 0 && w <= 0 && e <= 0) return (0); return (ENXIO); } r += pp->acr; w += pp->acw; e += pp->ace; if (g_union_get_writelock(sc) != 0) { if ((pp->acr + pp->acw + pp->ace) > 0 && (r + w + e) == 0) return (0); return (EBUSY); } g_union_rel_writelock(sc); return (0); } /* * Initiate an I/O operation on the union device. */ static void g_union_start(struct bio *bp) { struct g_union_softc *sc; struct g_union_wip *wip; struct bio *cbp; sc = bp->bio_to->geom->softc; if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) { wip = g_malloc(sizeof(*wip), M_NOWAIT); if (wip == NULL) { g_io_deliver(bp, ENOMEM); return; } TAILQ_INIT(&wip->wip_waiting); wip->wip_bp = bp; wip->wip_sc = sc; wip->wip_start = bp->bio_offset + sc->sc_offset; wip->wip_end = wip->wip_start + bp->bio_length - 1; wip->wip_numios = 1; wip->wip_error = 0; g_union_doio(wip); return; } /* * All commands other than read and write are passed through to * the upper-level device since it is writable and thus able to * respond to delete, flush, and speedup requests. */ cbp = g_clone_bio(bp); if (cbp == NULL) { g_io_deliver(bp, ENOMEM); return; } cbp->bio_offset = bp->bio_offset + sc->sc_offset; cbp->bio_done = g_std_done; switch (cbp->bio_cmd) { case BIO_DELETE: G_UNION_LOGREQ(cbp, "Delete request received."); atomic_add_long(&sc->sc_deletes, 1); break; case BIO_GETATTR: G_UNION_LOGREQ(cbp, "Getattr request received."); atomic_add_long(&sc->sc_getattrs, 1); if (strcmp(cbp->bio_attribute, "GEOM::kerneldump") != 0) /* forward the GETATTR to the lower-level device */ break; g_union_kerneldump(bp, sc); return; case BIO_FLUSH: G_UNION_LOGREQ(cbp, "Flush request received."); atomic_add_long(&sc->sc_flushes, 1); break; case BIO_SPEEDUP: G_UNION_LOGREQ(cbp, "Speedup request received."); atomic_add_long(&sc->sc_speedups, 1); break; case BIO_CMD0: G_UNION_LOGREQ(cbp, "Cmd0 request received."); atomic_add_long(&sc->sc_cmd0s, 1); break; case BIO_CMD1: G_UNION_LOGREQ(cbp, "Cmd1 request received."); atomic_add_long(&sc->sc_cmd1s, 1); break; case BIO_CMD2: G_UNION_LOGREQ(cbp, "Cmd2 request received."); atomic_add_long(&sc->sc_cmd2s, 1); break; default: G_UNION_LOGREQ(cbp, "Unknown (%d) request received.", cbp->bio_cmd); break; } g_io_request(cbp, sc->sc_uppercp); } /* * Initiate a read or write operation on the union device. */ static void g_union_doio(struct g_union_wip *wip) { struct g_union_softc *sc; struct g_consumer *cp, *firstcp; struct g_union_wip *activewip; struct bio *cbp, *firstbp; off_t rdlen, len2rd, offset; int iocnt, needstoblock; char *level; /* * To maintain consistency, we cannot allow concurrent reads * or writes to the same block. * * A work-in-progress (wip) structure is allocated for each * read or write request. All active requests are kept on the * softc sc_wiplist. As each request arrives, it is checked to * see if it overlaps any of the active entries. If it does not * overlap, then it is added to the active list and initiated. * If it does overlap an active entry, it is added to the * wip_waiting list for the active entry that it overlaps. * When an active entry completes, it restarts all the requests * on its wip_waiting list. */ sc = wip->wip_sc; G_WLOCK(sc); TAILQ_FOREACH(activewip, &sc->sc_wiplist, wip_next) { if (wip->wip_end < activewip->wip_start || wip->wip_start > activewip->wip_end) continue; needstoblock = 1; if (wip->wip_bp->bio_cmd == BIO_WRITE) if (activewip->wip_bp->bio_cmd == BIO_WRITE) sc->sc_writeblockwrite += 1; else sc->sc_readblockwrite += 1; else if (activewip->wip_bp->bio_cmd == BIO_WRITE) sc->sc_writeblockread += 1; else { sc->sc_readcurrentread += 1; needstoblock = 0; } /* Put request on a waiting list if necessary */ if (needstoblock) { TAILQ_INSERT_TAIL(&activewip->wip_waiting, wip, wip_next); G_WUNLOCK(sc); return; } } /* Put request on the active list */ TAILQ_INSERT_TAIL(&sc->sc_wiplist, wip, wip_next); /* * Process I/O requests that have been cleared to go. */ cbp = g_clone_bio(wip->wip_bp); if (cbp == NULL) { TAILQ_REMOVE(&sc->sc_wiplist, wip, wip_next); G_WUNLOCK(sc); KASSERT(TAILQ_FIRST(&wip->wip_waiting) == NULL, ("g_union_doio: non-empty work-in-progress waiting queue")); g_io_deliver(wip->wip_bp, ENOMEM); g_free(wip); return; } G_WUNLOCK(sc); cbp->bio_caller1 = wip; cbp->bio_done = g_union_done; cbp->bio_offset = wip->wip_start; /* * Writes are always done to the top level. The blocks that * are written are recorded in the bitmap when the I/O completes. */ if (cbp->bio_cmd == BIO_WRITE) { G_UNION_LOGREQ(cbp, "Sending %jd byte write request to upper " "level.", cbp->bio_length); atomic_add_long(&sc->sc_writes, 1); atomic_add_long(&sc->sc_wrotebytes, cbp->bio_length); g_io_request(cbp, sc->sc_uppercp); return; } /* * The usual read case is that we either read the top layer * if the block has been previously written or the bottom layer * if it has not been written. However, it is possible that * only part of the block has been written, For example we may * have written a UFS/FFS file fragment comprising several * sectors out of an 8-sector block. Here, if the entire * 8-sector block is read for example by a snapshot needing * to copy the full block, then we need to read the written * sectors from the upper level and the unwritten sectors from * the lower level. We do this by alternately reading from the * top and bottom layers until we complete the read. We * simplify for the common case to just do the I/O and return. */ atomic_add_long(&sc->sc_reads, 1); atomic_add_long(&sc->sc_readbytes, cbp->bio_length); rdlen = cbp->bio_length; offset = 0; for (iocnt = 0; ; iocnt++) { if (g_union_getmap(cbp, sc, &len2rd)) { /* read top */ cp = sc->sc_uppercp; level = "upper"; } else { /* read bottom */ cp = sc->sc_lowercp; level = "lower"; } /* Check if only a single read is required */ if (iocnt == 0 && rdlen == len2rd) { G_UNION_LOGREQLVL((cp == sc->sc_uppercp) ? 3 : 4, cbp, "Sending %jd byte read " "request to %s level.", len2rd, level); g_io_request(cbp, cp); return; } cbp->bio_length = len2rd; if ((cbp->bio_flags & BIO_UNMAPPED) != 0) cbp->bio_ma_offset += offset; else cbp->bio_data += offset; offset += len2rd; rdlen -= len2rd; G_UNION_LOGREQLVL(3, cbp, "Sending %jd byte read " "request to %s level.", len2rd, level); /* * To avoid prematurely notifying our consumer * that their I/O has completed, we have to delay * issuing our first I/O request until we have * issued all the additional I/O requests. */ if (iocnt > 0) { atomic_add_long(&wip->wip_numios, 1); g_io_request(cbp, cp); } else { firstbp = cbp; firstcp = cp; } if (rdlen == 0) break; /* set up for next read */ cbp = g_clone_bio(wip->wip_bp); if (cbp == NULL) { wip->wip_error = ENOMEM; atomic_add_long(&wip->wip_numios, -1); break; } cbp->bio_caller1 = wip; cbp->bio_done = g_union_done; cbp->bio_offset += offset; cbp->bio_length = rdlen; atomic_add_long(&sc->sc_reads, 1); } /* We have issued all our I/O, so start the first one */ g_io_request(firstbp, firstcp); return; } /* * Used when completing a union I/O operation. */ static void g_union_done(struct bio *bp) { struct g_union_wip *wip, *waitingwip; struct g_union_softc *sc; wip = bp->bio_caller1; if (wip->wip_error != 0 && bp->bio_error == 0) bp->bio_error = wip->wip_error; wip->wip_error = 0; if (atomic_fetchadd_long(&wip->wip_numios, -1) == 1) { sc = wip->wip_sc; G_WLOCK(sc); if (bp->bio_cmd == BIO_WRITE) g_union_setmap(bp, sc); TAILQ_REMOVE(&sc->sc_wiplist, wip, wip_next); G_WUNLOCK(sc); while ((waitingwip = TAILQ_FIRST(&wip->wip_waiting)) != NULL) { TAILQ_REMOVE(&wip->wip_waiting, waitingwip, wip_next); g_union_doio(waitingwip); } g_free(wip); } g_std_done(bp); } /* * Record blocks that have been written in the map. */ static void g_union_setmap(struct bio *bp, struct g_union_softc *sc) { size_t root_idx; uint64_t **leaf; uint64_t *wordp; off_t start, numsec; G_WLOCKOWNED(sc); KASSERT(bp->bio_offset % sc->sc_sectorsize == 0, ("g_union_setmap: offset not on sector boundry")); KASSERT(bp->bio_length % sc->sc_sectorsize == 0, ("g_union_setmap: length not a multiple of sectors")); start = bp->bio_offset / sc->sc_sectorsize; numsec = bp->bio_length / sc->sc_sectorsize; KASSERT(start + numsec <= sc->sc_map_size, ("g_union_setmap: block %jd is out of range", start + numsec)); for ( ; numsec > 0; numsec--, start++) { root_idx = start / sc->sc_bits_per_leaf; leaf = &sc->sc_writemap_root[root_idx]; wordp = &(*leaf) [(start % sc->sc_bits_per_leaf) / BITS_PER_ENTRY]; *wordp |= 1ULL << (start % BITS_PER_ENTRY); sc->sc_leafused[root_idx / BITS_PER_ENTRY] |= 1ULL << (root_idx % BITS_PER_ENTRY); } } /* * Check map to determine whether blocks have been written. * * Return true if they have been written so should be read from the top * layer. Return false if they have not been written so should be read * from the bottom layer. Return in len2read the bytes to be read. See * the comment above the BIO_READ implementation in g_union_start() for * an explantion of why len2read may be shorter than the buffer length. */ static bool g_union_getmap(struct bio *bp, struct g_union_softc *sc, off_t *len2read) { off_t start, numsec, leafresid, bitloc; bool first, maptype, retval; uint64_t *leaf, word; size_t root_idx; KASSERT(bp->bio_offset % sc->sc_sectorsize == 0, ("g_union_getmap: offset not on sector boundry")); KASSERT(bp->bio_length % sc->sc_sectorsize == 0, ("g_union_getmap: length not a multiple of sectors")); start = bp->bio_offset / sc->sc_sectorsize; numsec = bp->bio_length / sc->sc_sectorsize; G_UNION_DEBUG(4, "g_union_getmap: check %jd sectors starting at %jd\n", numsec, start); KASSERT(start + numsec <= sc->sc_map_size, ("g_union_getmap: block %jd is out of range", start + numsec)); root_idx = start / sc->sc_bits_per_leaf; first = true; maptype = false; while (numsec > 0) { /* Check first if the leaf records any written sectors */ root_idx = start / sc->sc_bits_per_leaf; leafresid = sc->sc_bits_per_leaf - (start % sc->sc_bits_per_leaf); if (((sc->sc_leafused[root_idx / BITS_PER_ENTRY]) & (1ULL << (root_idx % BITS_PER_ENTRY))) == 0) { if (first) { maptype = false; first = false; } if (maptype) break; numsec -= leafresid; start += leafresid; continue; } /* Check up to a word boundry, then check word by word */ leaf = sc->sc_writemap_root[root_idx]; word = leaf[(start % sc->sc_bits_per_leaf) / BITS_PER_ENTRY]; bitloc = start % BITS_PER_ENTRY; if (bitloc == 0 && (word == 0 || word == ~0)) { if (first) { if (word == 0) maptype = false; else maptype = true; first = false; } if ((word == 0 && maptype) || (word == ~0 && !maptype)) break; numsec -= BITS_PER_ENTRY; start += BITS_PER_ENTRY; continue; } for ( ; bitloc < BITS_PER_ENTRY; bitloc ++) { retval = (word & (1ULL << bitloc)) != 0; if (first) { maptype = retval; first = false; } if (maptype == retval) { numsec--; start++; continue; } goto out; } } out: if (numsec < 0) { start += numsec; numsec = 0; } *len2read = bp->bio_length - (numsec * sc->sc_sectorsize); G_UNION_DEBUG(maptype ? 3 : 4, "g_union_getmap: return maptype %swritten for %jd " "sectors ending at %jd\n", maptype ? "" : "NOT ", *len2read / sc->sc_sectorsize, start - 1); return (maptype); } /* * Fill in details for a BIO_GETATTR request. */ static void g_union_kerneldump(struct bio *bp, struct g_union_softc *sc) { struct g_kerneldump *gkd; struct g_geom *gp; struct g_provider *pp; gkd = (struct g_kerneldump *)bp->bio_data; gp = bp->bio_to->geom; g_trace(G_T_TOPOLOGY, "%s(%s, %jd, %jd)", __func__, gp->name, (intmax_t)gkd->offset, (intmax_t)gkd->length); pp = LIST_FIRST(&gp->provider); gkd->di.dumper = g_union_dumper; gkd->di.priv = sc; gkd->di.blocksize = pp->sectorsize; gkd->di.maxiosize = DFLTPHYS; gkd->di.mediaoffset = sc->sc_offset + gkd->offset; if (gkd->offset > sc->sc_size) { g_io_deliver(bp, ENODEV); return; } if (gkd->offset + gkd->length > sc->sc_size) gkd->length = sc->sc_size - gkd->offset; gkd->di.mediasize = gkd->length; g_io_deliver(bp, 0); } /* * Handler for g_union_kerneldump(). */ static int g_union_dumper(void *priv, void *virtual, off_t offset, size_t length) { return (0); } /* * List union statistics. */ static void g_union_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp) { struct g_union_softc *sc; if (pp != NULL || cp != NULL || gp->softc == NULL) return; sc = gp->softc; sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_reads); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_writes); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_deletes); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_getattrs); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_flushes); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_speedups); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_cmd0s); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_cmd1s); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_cmd2s); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_readcurrentread); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_readblockwrite); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_writeblockread); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_writeblockwrite); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_readbytes); sbuf_printf(sb, "%s%ju\n", indent, (uintmax_t)sc->sc_wrotebytes); sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_offset); } /* * Clean up an orphaned geom. */ static void g_union_orphan(struct g_consumer *cp) { g_topology_assert(); g_union_destroy(NULL, cp->geom, true); } /* * Clean up a union geom. */ static int g_union_destroy_geom(struct gctl_req *req, struct g_class *mp, struct g_geom *gp) { return (g_union_destroy(NULL, gp, false)); } /* * Clean up a union device. */ static int g_union_destroy(struct gctl_req *req, struct g_geom *gp, bool force) { struct g_union_softc *sc; struct g_provider *pp; int error; g_topology_assert(); sc = gp->softc; if (sc == NULL) return (ENXIO); pp = LIST_FIRST(&gp->provider); if ((sc->sc_flags & DOING_COMMIT) != 0 || (pp != NULL && (pp->acr != 0 || pp->acw != 0 || pp->ace != 0))) { if (force) { if (req != NULL) - gctl_msg(req, "Device %s is still in use, " + gctl_msg(req, 0, "Device %s is still in use, " "so is being forcibly removed.", gp->name); G_UNION_DEBUG(1, "Device %s is still in use, so " "is being forcibly removed.", gp->name); } else { if (req != NULL) - gctl_msg(req, "Device %s is still open " + gctl_msg(req, EBUSY, "Device %s is still open " "(r=%d w=%d e=%d).", gp->name, pp->acr, pp->acw, pp->ace); G_UNION_DEBUG(1, "Device %s is still open " "(r=%d w=%d e=%d).", gp->name, pp->acr, pp->acw, pp->ace); return (EBUSY); } } else { if (req != NULL) - gctl_msg(req, "Device %s removed.", gp->name); + gctl_msg(req, 0, "Device %s removed.", gp->name); G_UNION_DEBUG(1, "Device %s removed.", gp->name); } /* Close consumers */ if ((error = g_access(sc->sc_lowercp, -1, 0, -1)) != 0) G_UNION_DEBUG(2, "Error %d: device %s could not reset access " "to %s.", error, gp->name, sc->sc_lowercp->provider->name); if ((error = g_access(sc->sc_uppercp, -1, -1, -1)) != 0) G_UNION_DEBUG(2, "Error %d: device %s could not reset access " "to %s.", error, gp->name, sc->sc_uppercp->provider->name); g_wither_geom(gp, ENXIO); return (0); } /* * Clean up a union provider. */ static void g_union_providergone(struct g_provider *pp) { struct g_geom *gp; struct g_union_softc *sc; size_t i; gp = pp->geom; sc = gp->softc; gp->softc = NULL; for (i = 0; i < sc->sc_root_size; i++) g_free(sc->sc_writemap_root[i]); g_free(sc->sc_writemap_root); g_free(sc->sc_leafused); rw_destroy(&sc->sc_rwlock); g_free(sc); } /* * Respond to a resized provider. */ static void g_union_resize(struct g_consumer *cp) { struct g_union_softc *sc; struct g_geom *gp; g_topology_assert(); gp = cp->geom; sc = gp->softc; /* * If size has gotten bigger, ignore it and just keep using * the space we already had. Otherwise we are done. */ if (sc->sc_size < cp->provider->mediasize - sc->sc_offset) return; g_union_destroy(NULL, gp, true); } DECLARE_GEOM_CLASS(g_union_class, g_union); MODULE_VERSION(geom_union, 0);