/*- * 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, 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); }