With #vars, you can specify certain variables, such as num_cores, to be exposed to userspace. If you want, you can:
$ bind -a \#vars /dev $ cat /dev/num_cores!dw For debugging, you can add entries with the DEVVARS_ENTRY(name, format) macro. 'format' is two chars, the data_format and the data_size, using Qemu's notation. Privileged users (i.e. anyone) can add new entries to #vars, which internally will do a lookup in the symbol table and trust the format string. This is a little dangerous. Signed-off-by: Barret Rhoden <[email protected]> --- kern/drivers/dev/Kbuild | 1 + kern/drivers/dev/Kconfig | 6 + kern/drivers/dev/vars.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++ kern/include/ns.h | 7 + 4 files changed, 455 insertions(+) create mode 100644 kern/drivers/dev/vars.c diff --git a/kern/drivers/dev/Kbuild b/kern/drivers/dev/Kbuild index 57fd9368fb60..400a9b7d4c7f 100644 --- a/kern/drivers/dev/Kbuild +++ b/kern/drivers/dev/Kbuild @@ -12,5 +12,6 @@ obj-y += proc.o obj-$(CONFIG_REGRESS) += regress.o obj-y += root.o obj-y += srv.o +obj-$(CONFIG_DEVVARS) += vars.o obj-$(CONFIG_NIX) += nix.o diff --git a/kern/drivers/dev/Kconfig b/kern/drivers/dev/Kconfig index eb737b8b11cb..d01c6dd563f6 100644 --- a/kern/drivers/dev/Kconfig +++ b/kern/drivers/dev/Kconfig @@ -4,3 +4,9 @@ config REGRESS help The regression test device allows you to push commands to monitor() for testing. Defaults to 'y' for now. + +config DEVVARS + bool "#vars kernel variable exporter" + default y + help + The #vars device exports read access to select kernel variables. diff --git a/kern/drivers/dev/vars.c b/kern/drivers/dev/vars.c new file mode 100644 index 000000000000..bf9d68553212 --- /dev/null +++ b/kern/drivers/dev/vars.c @@ -0,0 +1,441 @@ +/* Copyright (c) 2015 Google Inc + * Barret Rhoden <[email protected]> + * See LICENSE for details. + * + * #vars device, exports read access to select kernel variables. These + * variables are statically set. + * + * To add a variable, add a DEVVARS_ENTRY(name, format) somewhere in the kernel. + * The format is a string consisting of two characters, using a modified version + * of QEMU's formatting rules (ignoring count): [data_format][size] + * + * data_format is: + * x (hex) + * d (decimal) + * u (unsigned) + * o (octal) + * c (char) does not need a size + * s (string) does not need a size + * size is: + * b (8 bits) + * h (16 bits) + * w (32 bits) + * g (64 bits) + * + * e.g. DEVVARS_ENTRY(num_cores, "dw"); + * + * Another thing we can consider doing is implementing create() to add variables + * on the fly. We can easily get the address (symbol table), but not the type, + * unless we get debugging info. We could consider a CTL command to allow the + * user to change the type, though that might overload write() if we also allow + * setting variables. */ + +#include <ns.h> +#include <kmalloc.h> +#include <kref.h> +#include <atomic.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <error.h> +#include <sys/queue.h> +#include <fdtap.h> +#include <syscall.h> + +struct dev vars_devtab; + +static char *devname(void) +{ + return vars_devtab.name; +} + +static struct dirtab *vars_dir; +static size_t nr_vars; +static qlock_t vars_lock; + +struct dirtab __attribute__((__section__("devvars"))) + __devvars_dot = {".", {0, 0, QTDIR}, 0, DMDIR | 0555}; + +DEVVARS_ENTRY(num_cores, "dw"); + +static bool var_is_valid(struct dirtab *dir) +{ + return dir->qid.vers != -1; +} + +/* Careful with this. c->name->s is the full path, at least sometimes. */ +static struct dirtab *find_var_by_name(const char *name) +{ + for (size_t i = 0; i < nr_vars; i++) + if (!strcmp(vars_dir[i].name, name)) + return &vars_dir[i]; + return 0; +} + +static void vars_init(void) +{ + /* If you name a section without a '.', GCC will create start and stop + * symbols, e.g. __start_SECTION */ + extern struct dirtab __start_devvars; + extern struct dirtab __stop_devvars; + struct dirtab *dot, temp; + + nr_vars = &__stop_devvars - &__start_devvars; + vars_dir = kmalloc_array(nr_vars, sizeof(struct dirtab), KMALLOC_WAIT); + if (!vars_dir) + error(ENOMEM, "kmalloc_array failed, nr_vars was %p", nr_vars); + memcpy(vars_dir, &__start_devvars, nr_vars * sizeof(struct dirtab)); + /* "." needs to be the first entry in a devtable. It might already be + * first, but we can do the swap regardless. */ + temp = vars_dir[0]; + dot = find_var_by_name("."); + assert(dot); + vars_dir[0] = *dot; + *dot = temp; + qlock_init(&vars_lock); +} + +static struct chan *vars_attach(char *spec) +{ + struct chan *c; + + c = devattach(devname(), spec); + mkqid(&c->qid, 0, 0, QTDIR); + return c; +} + +static struct walkqid *vars_walk(struct chan *c, struct chan *nc, char **name, + int nname) +{ + ERRSTACK(1); + struct walkqid *ret; + + qlock(&vars_lock); + if (waserror()) { + qunlock(&vars_lock); + nexterror(); + } + ret = devwalk(c, nc, name, nname, vars_dir, nr_vars, devgen); + poperror(); + qunlock(&vars_lock); + return ret; +} + +static int vars_stat(struct chan *c, uint8_t *db, int n) +{ + ERRSTACK(1); + int ret; + + qlock(&vars_lock); + if (waserror()) { + qunlock(&vars_lock); + nexterror(); + } + ret = devstat(c, db, n, vars_dir, nr_vars, devgen); + poperror(); + qunlock(&vars_lock); + return ret; +} + +static struct chan *vars_open(struct chan *c, int omode) +{ + ERRSTACK(1); + struct chan *ret; + + qlock(&vars_lock); + if (waserror()) { + qunlock(&vars_lock); + nexterror(); + } + ret = devopen(c, omode, vars_dir, nr_vars, devgen); + poperror(); + qunlock(&vars_lock); + return ret; +} + +static void vars_close(struct chan *c) +{ +} + +static struct dirtab *find_free_var(void) +{ + for (size_t i = 0; i < nr_vars; i++) + if (!var_is_valid(&vars_dir[i])) + return &vars_dir[i]; + return 0; +} + +/* We ignore the perm - they are all hard-coded in the dirtab */ +static void vars_create(struct chan *c, char *name, int omode, uint32_t perm) +{ + struct dirtab *new_slot; + uintptr_t addr; + char *bang; + size_t size; + + /* TODO: check that the user is privileged */ + bang = strchr(name, '!'); + if (!bang) + error(EINVAL, "Var %s has no ! in its format string", name); + *bang = 0; + addr = get_symbol_addr(name); + *bang = '!'; + if (!addr) + error(EINVAL, "Could not find symbol for %s", name); + bang++; + /* Note that we don't check the symbol type against the format. We're + * trusting the user here. o/w we'd need dwarf support. */ + switch (*bang) { + case 'c': + size = sizeof(char); + break; + case 's': + size = sizeof(char*); + break; + case 'd': + case 'x': + case 'u': + case 'o': + bang++; + switch (*bang) { + case 'b': + size = sizeof(uint8_t); + break; + case 'h': + size = sizeof(uint16_t); + break; + case 'w': + size = sizeof(uint32_t); + break; + case 'g': + size = sizeof(uint64_t); + break; + default: + error(EINVAL, "Bad var size '%c'", *bang); + } + break; + default: + error(EINVAL, "Unknown var data_format '%c'", *bang); + } + bang++; + if (*bang) + error(EINVAL, "Extra chars for var %s", name); + + qlock(&vars_lock); + new_slot = find_free_var(); + if (!new_slot) { + vars_dir = kreallocarray(vars_dir, nr_vars * 2, sizeof(struct dirtab), + KMALLOC_WAIT); + if (!vars_dir) + error(ENOMEM, "krealloc_array failed, nr_vars was %p", nr_vars); + memset(vars_dir + nr_vars, 0, nr_vars * sizeof(struct dirtab)); + for (size_t i = nr_vars; i < nr_vars * 2; i++) + vars_dir[i].qid.vers = -1; + new_slot = vars_dir + nr_vars; + nr_vars *= 2; + } + strncpy(new_slot->name, name, sizeof(new_slot->name)); + new_slot->qid.path = addr; + new_slot->qid.vers = 0; + new_slot->qid.type = QTFILE; + new_slot->length = size; + new_slot->perm = 0444; + c->qid = new_slot->qid; /* need to update c with its new qid */ + qunlock(&vars_lock); + c->mode = openmode(omode); +} + +static const char *get_integer_fmt(char data_fmt, char data_size) +{ + switch (data_fmt) { + case 'x': + switch (data_size) { + case 'b': + case 'h': + case 'w': + return "0x%x"; + case 'g': + return "0x%lx"; + } + case 'd': + switch (data_size) { + case 'b': + case 'h': + case 'w': + return "%d"; + case 'g': + return "%ld"; + } + case 'u': + switch (data_size) { + case 'b': + case 'h': + case 'w': + return "%u"; + case 'g': + return "%lu"; + } + case 'o': + switch (data_size) { + case 'b': + case 'h': + case 'w': + return "0%o"; + case 'g': + return "0%lo"; + } + } + return 0; +} + +static long vars_read(struct chan *c, void *ubuf, long n, int64_t offset) +{ + ERRSTACK(1); + char tmp[128]; /* big enough for any number and most strings */ + size_t size = sizeof(tmp); + char data_size, data_fmt, *fmt; + const char *fmt_int; + bool is_signed = FALSE; + long ret; + + qlock(&vars_lock); + if (waserror()) { + qunlock(&vars_lock); + nexterror(); + } + + if (c->qid.type == QTDIR) { + ret = devdirread(c, ubuf, n, vars_dir, nr_vars, devgen); + poperror(); + qunlock(&vars_lock); + return ret; + } + + /* These checks are mostly for the static variables. They are a + * double-check for the user-provided vars. */ + fmt = strchr(c->name->s, '!'); + if (!fmt) + error(EINVAL, "var %s has no ! in its format string", c->name->s); + fmt++; + data_fmt = *fmt; + if (!data_fmt) + error(EINVAL, "var %s has no data_format", c->name->s); + + switch (data_fmt) { + case 'c': + size = snprintf(tmp, size, "%c", *(char*)c->qid.path); + break; + case 's': + size = snprintf(tmp, size, "%s", *(char**)c->qid.path); + break; + case 'd': + is_signed = TRUE; + /* fall through */ + case 'x': + case 'u': + case 'o': + fmt++; + data_size = *fmt; + if (!data_size) + error(EINVAL, "var %s has no size", c->name->s); + fmt_int = get_integer_fmt(data_fmt, data_size); + if (!fmt_int) + error(EINVAL, "#%s was unable to get an int fmt for %s", + devname(), c->name->s); + switch (data_size) { + case 'b': + if (is_signed) + size = snprintf(tmp, size, fmt_int, *(int8_t*)c->qid.path); + else + size = snprintf(tmp, size, fmt_int, *(uint8_t*)c->qid.path); + break; + case 'h': + if (is_signed) + size = snprintf(tmp, size, fmt_int, *(int16_t*)c->qid.path); + else + size = snprintf(tmp, size, fmt_int, *(uint16_t*)c->qid.path); + break; + case 'w': + if (is_signed) + size = snprintf(tmp, size, fmt_int, *(int32_t*)c->qid.path); + else + size = snprintf(tmp, size, fmt_int, *(uint32_t*)c->qid.path); + break; + case 'g': + if (is_signed) + size = snprintf(tmp, size, fmt_int, *(int64_t*)c->qid.path); + else + size = snprintf(tmp, size, fmt_int, *(uint64_t*)c->qid.path); + break; + default: + error(EINVAL, "Bad #%s size %c", devname(), data_size); + } + break; + default: + error(EINVAL, "Unknown #%s data_format %c", devname(), data_fmt); + } + fmt++; + if (*fmt) + error(EINVAL, "Extra characters after var %s", c->name->s); + ret = readmem(offset, ubuf, n, tmp, size + 1); + poperror(); + qunlock(&vars_lock); + return ret; +} + +static long vars_write(struct chan *c, void *ubuf, long n, int64_t offset) +{ + error(EFAIL, "Can't write to a #%s file", devname()); +} + +/* remove is interesting. we mark the qid in the dirtab as -1, which is a + * signal to devgen that it is an invalid entry. someone could already have + * done a walk (before we qlocked) and grabbed the qid before it was -1. as far + * as they are concerned, they have a valid entry, since "the qid is the file" + * for devvars. (i.e. the chan gets a copy of the entire file, which fits into + * the qid). */ +static void vars_remove(struct chan *c) +{ + ERRSTACK(1); + struct dirtab *dir; + char *dir_name; + + /* chan's name may have multiple elements in the path; get the last one. */ + dir_name = strrchr(c->name->s, '/'); + dir_name = dir_name ? dir_name + 1 : c->name->s; + + qlock(&vars_lock); + if (waserror()) { + qunlock(&vars_lock); + nexterror(); + } + dir = find_var_by_name(dir_name); + if (!dir || dir->qid.vers == -1) + error(ENOENT, "Failed to remove %s, was it already removed?", + c->name->s); + dir->qid.vers = -1; + poperror(); + qunlock(&vars_lock); +} + +struct dev vars_devtab __devtab = { + .name = "vars", + .reset = devreset, + .init = vars_init, + .shutdown = devshutdown, + .attach = vars_attach, + .walk = vars_walk, + .stat = vars_stat, + .open = vars_open, + .create = vars_create, + .close = vars_close, + .read = vars_read, + .bread = devbread, + .write = vars_write, + .bwrite = devbwrite, + .remove = vars_remove, + .wstat = devwstat, + .power = devpower, + .chaninfo = devchaninfo, + .tapfd = 0, +}; diff --git a/kern/include/ns.h b/kern/include/ns.h index 166158472114..c24f4c87a934 100644 --- a/kern/include/ns.h +++ b/kern/include/ns.h @@ -1000,3 +1000,10 @@ extern unsigned int qiomaxatomic; /* special sections */ #define __devtab __attribute__((__section__(".devtab"))) + +#define DEVVARS_ENTRY(name, fmt) \ +struct dirtab __attribute__((__section__("devvars"))) __devvars_##name = \ + {#name "!" fmt, \ + {(uint64_t)&(name), 0, QTFILE}, \ + sizeof((name)), \ + 0444} -- 2.6.0.rc2.230.g3dd15c0 -- You received this message because you are subscribed to the Google Groups "Akaros" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. For more options, visit https://groups.google.com/d/optout.
