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.
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 <
br...@cs.berkeley.edu>
+ * 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");
+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