From 8daaa21b3949e5e327586a49a63dfcacd5600aba Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 3 Jun 2019 22:20:01 -0700 Subject: fs: Add a driver to access U-Boot environment variables Add a driver working on top of ubootvar device and exposing U-Boot environment variable data as files. Signed-off-by: Andrey Smirnov Signed-off-by: Cory Tusar Signed-off-by: Sascha Hauer --- fs/Kconfig | 8 + fs/Makefile | 1 + fs/ubootvarfs.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+) create mode 100644 fs/ubootvarfs.c (limited to 'fs') diff --git a/fs/Kconfig b/fs/Kconfig index e3a95321c7..adf281a5b8 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -118,4 +118,12 @@ config FS_RATP This enables support for transferring files over RATP. A host can export a directory which can then be mounted under barebox. +config FS_UBOOTVARFS + bool + depends on UBOOTVAR + prompt "U-Boot environment variable filesystem support" + help + This filesystem driver provides access to U-Boot environment + variables. + endmenu diff --git a/fs/Makefile b/fs/Makefile index ac3e6a03aa..9889a6507c 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_FS_SMHFS) += smhfs.o obj-$(CONFIG_FS_PSTORE) += pstore/ obj-$(CONFIG_FS_SQUASHFS) += squashfs/ obj-$(CONFIG_FS_RATP) += ratpfs.o +obj-$(CONFIG_FS_UBOOTVARFS) += ubootvarfs.o diff --git a/fs/ubootvarfs.c b/fs/ubootvarfs.c new file mode 100644 index 0000000000..81ec05d5ef --- /dev/null +++ b/fs/ubootvarfs.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Zodiac Inflight Innovations + */ + +#define pr_fmt(fmt) "ubootvarfs: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Some theory of operation: + * + * U-Boot environment variable data is expected to be presented as a + * single blob containing an arbitrary number "=\0" pairs + * without any other auxiliary information (accomplished by ubootvar + * driver) + * + * Filesystem driver code in this file parses above data an creates a + * linked list of all of the "variables" found (see @ubootvarfs_var to + * what information is recorded). + * + * With that in place reading or writing file data becomes as trivial + * as looking up a variable in the linked list by name and then + * memcpy()-ing bytes from its value region. + * + * The only moderately tricky part is re-sizing a given file/variable + * since, given the underlying data format, it requires us to move all + * of the key/value data that comes after the given file/variable as + * well as to adjust all of the cached offsets stored in variable + * linked list. See ubootvarfs_adjust() for the implementation + * details. + */ + +/** + * struct ubootvarfs_var - U-Boot environment key-value pair + * + * @list: Linked list head + * @name: Pointer to memory containing key string (variable name) + * @name_len: Variable name's (above) length + * @start: Start of value in memory + * @end: End of value in memory + */ +struct ubootvarfs_var { + struct list_head list; + char *name; + size_t name_len; + char *start; + char *end; +}; + +/** + * struct ubootvarfs_data - U-Boot environment data + * + * @var_list: Linked list of all of the parsed variables + * @fd: File descriptor of underlying ubootvar device + * @end: End of U-boot environment + * @limit: U-boot environment limit (can't grow to go past the limit) + */ +struct ubootvarfs_data { + struct list_head var_list; + int fd; + char *end; + const char *limit; +}; + +struct ubootvarfs_inode { + struct inode inode; + struct ubootvarfs_var *var; + struct ubootvarfs_data *data; +}; + +static struct ubootvarfs_inode *inode_to_node(struct inode *inode) +{ + return container_of(inode, struct ubootvarfs_inode, inode); +} + +static const struct inode_operations ubootvarfs_file_inode_operations; +static const struct file_operations ubootvarfs_dir_operations; +static const struct inode_operations ubootvarfs_dir_inode_operations; +static const struct file_operations ubootvarfs_file_operations; + +static struct inode *ubootvarfs_get_inode(struct super_block *sb, + const struct inode *dir, + umode_t mode, + struct ubootvarfs_var *var) +{ + struct inode *inode = new_inode(sb); + struct ubootvarfs_inode *node; + + if (!inode) + return NULL; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + if (var) + inode->i_size = var->end - var->start; + + node = inode_to_node(inode); + node->var = var; + + switch (mode & S_IFMT) { + default: + return NULL; + case S_IFREG: + inode->i_op = &ubootvarfs_file_inode_operations; + inode->i_fop = &ubootvarfs_file_operations; + break; + case S_IFDIR: + inode->i_op = &ubootvarfs_dir_inode_operations; + inode->i_fop = &ubootvarfs_dir_operations; + inc_nlink(inode); + break; + } + + return inode; +} + +static struct ubootvarfs_var * +ubootvarfs_var_by_name(struct ubootvarfs_data *data, const char *name) +{ + struct ubootvarfs_var *var; + const size_t len = strlen(name); + + list_for_each_entry(var, &data->var_list, list) { + if (len == var->name_len && + !memcmp(name, var->name, var->name_len)) + return var; + } + + return NULL; +} + +static struct dentry *ubootvarfs_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + struct super_block *sb = dir->i_sb; + struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb); + struct ubootvarfs_data *data = fsdev->dev.priv; + struct ubootvarfs_var *var; + struct inode *inode; + + var = ubootvarfs_var_by_name(data, dentry->name); + if (!var) + return NULL; + + inode = ubootvarfs_get_inode(dir->i_sb, dir, S_IFREG | 0777, var); + if (!inode) + return ERR_PTR(-ENOMEM); + + d_add(dentry, inode); + + return NULL; +} + +static int ubootvarfs_iterate(struct file *file, struct dir_context *ctx) +{ + struct dentry *dentry = file->f_path.dentry; + struct inode *inode = d_inode(dentry); + struct ubootvarfs_inode *node = inode_to_node(inode); + struct ubootvarfs_data *data = node->data; + struct ubootvarfs_var *var; + + dir_emit_dots(file, ctx); + + list_for_each_entry(var, &data->var_list, list) + dir_emit(ctx, var->name, var->name_len, 0, DT_REG); + + return 0; +} + +static const struct file_operations ubootvarfs_dir_operations = { + .iterate = ubootvarfs_iterate, +}; + +/** + * ubootvarfs_relocate_tail() - Move all of the data after given inode by delta + * + * @node: Inode marking the start of the data + * @delta: Offset to move the data by + * + * This function move all of the environment data that starts after + * the given @node by @delta bytes. In case the data is moved towards + * the start of the environment data blob trailing leftover data is + * zeroed out + */ +static void ubootvarfs_relocate_tail(struct ubootvarfs_inode *node, + int delta) +{ + struct ubootvarfs_var *var = node->var; + struct ubootvarfs_data *data = node->data; + const size_t n = data->end - var->start; + void *src = var->end + 1; + + memmove(src + delta, src, n); + + data->end += delta; + + if (delta < 0) { + /* + * Remove all of the trailing leftovers + */ + memset(data->end, '\0', -delta); + } +} + +/** + * ubootvarfs_adjust() - Adjust the size of a variable blob + * + * @node: Inode marking where to start adjustement from + * @delta: Offset to adjust by + * + * This function move all of the environment data that starts after + * the given @node by @delta bytes and updates all of the affected + * ubootvarfs_var's in varaible linked list + */ +static void ubootvarfs_adjust(struct ubootvarfs_inode *node, + int delta) +{ + struct ubootvarfs_var *var = node->var; + struct ubootvarfs_data *data = node->data; + + ubootvarfs_relocate_tail(node, delta); + + list_for_each_entry_continue(var, &data->var_list, list) { + var->name += delta; + var->start += delta; + var->end += delta; + } +} + +static int ubootvarfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + + if (inode) { + struct ubootvarfs_inode *node = inode_to_node(inode); + struct ubootvarfs_var *var = node->var; + /* + * -1 at the end is to account for '\0' at the end + * that needs to be removed as well + */ + const int delta = var->name - var->end - 1; + + ubootvarfs_adjust(node, delta); + + list_del(&var->list); + free(var); + } + + return simple_unlink(dir, dentry); +} + +static int ubootvarfs_create(struct inode *dir, struct dentry *dentry, + umode_t mode) +{ + struct super_block *sb = dir->i_sb; + struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb); + struct ubootvarfs_data *data = fsdev->dev.priv; + struct inode *inode; + struct ubootvarfs_var *var; + size_t len = strlen(dentry->name); + /* + * We'll be adding =\0\0 to the end of our data, so + * we need to make sure there's enough room for it. Note that + * + 3 is to accoutn for '=', and two '\0' from above + */ + if (data->end + len + 3 > data->limit) + return -ENOSPC; + + var = xmalloc(sizeof(*var)); + + var->name = data->end; + memcpy(var->name, dentry->name, len); + var->name_len = len; + var->start = var->name + len; + *var->start++ = '='; + *var->start = '\0'; + var->end = var->start; + data->end = var->end + 1; + *data->end = '\0'; + + list_add_tail(&var->list, &data->var_list); + + inode = ubootvarfs_get_inode(sb, dir, mode, var); + d_instantiate(dentry, inode); + + return 0; +} + +static const struct inode_operations ubootvarfs_dir_inode_operations = { + .lookup = ubootvarfs_lookup, + .unlink = ubootvarfs_unlink, + .create = ubootvarfs_create, +}; + +static struct inode *ubootvarfs_alloc_inode(struct super_block *sb) +{ + struct ubootvarfs_inode *node; + struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb); + struct ubootvarfs_data *data = fsdev->dev.priv; + + node = xzalloc(sizeof(*node)); + node->data = data; + + return &node->inode; +} + +static void ubootvarfs_destroy_inode(struct inode *inode) +{ + struct ubootvarfs_inode *node = inode_to_node(inode); + + free(node->var); + free(node); +} + +static const struct super_operations ubootvarfs_ops = { + .alloc_inode = ubootvarfs_alloc_inode, + .destroy_inode = ubootvarfs_destroy_inode, +}; + +static int ubootvarfs_io(struct device_d *dev, FILE *f, void *buf, + size_t insize, bool read) +{ + struct inode *inode = f->f_inode; + struct ubootvarfs_inode *node = inode_to_node(inode); + void *ptr = node->var->start + f->pos; + + if (read) + memcpy(buf, ptr, insize); + else + memcpy(ptr, buf, insize); + + return insize; +} + +static int ubootvarfs_read(struct device_d *dev, FILE *f, void *buf, + size_t insize) +{ + return ubootvarfs_io(dev, f, buf, insize, true); +} + +static int ubootvarfs_write(struct device_d *dev, FILE *f, const void *buf, + size_t insize) +{ + return ubootvarfs_io(dev, f, (void *)buf, insize, false); +} + +static int ubootvarfs_truncate(struct device_d *dev, FILE *f, loff_t size) +{ + struct inode *inode = f->f_inode; + struct ubootvarfs_inode *node = inode_to_node(inode); + struct ubootvarfs_data *data = node->data; + struct ubootvarfs_var *var = node->var; + const int delta = size - inode->i_size; + + if (size == inode->i_size) + return 0; + + if (data->end + delta >= data->limit) + return -ENOSPC; + + ubootvarfs_adjust(node, delta); + + if (delta > 0) + memset(var->end, '\0', delta); + + var->end += delta; + *var->end = '\0'; + + return 0; +} + +static void ubootvarfs_parse(struct ubootvarfs_data *data, char *blob, + size_t size) +{ + struct ubootvarfs_var *var; + const char *start = blob; + size_t len; + char *sep; + + data->limit = blob + size; + INIT_LIST_HEAD(&data->var_list); + + while (*blob) { + var = xmalloc(sizeof(*var)); + len = strnlen(blob, size); + + var->name = blob; + var->end = blob + len; + + sep = strchr(blob, '='); + if (sep) { + var->start = sep + 1; + var->name_len = sep - blob; + + list_add_tail(&var->list, &data->var_list); + } else { + pr_err("No separator in data @ 0x%08x. Skipped.", + blob - start); + free(var); + } + + len++; /* account for '\0' */ + size -= len; + blob += len; + }; + + data->end = blob; +} + +static int ubootvarfs_probe(struct device_d *dev) +{ + struct inode *inode; + struct ubootvarfs_data *data = xzalloc(sizeof(*data)); + struct fs_device_d *fsdev = dev_to_fs_device(dev); + struct super_block *sb = &fsdev->sb; + struct stat s; + void *map; + int ret; + + dev->priv = data; + + data->fd = open(fsdev->backingstore, O_RDWR); + if (data->fd < 0) { + ret = -errno; + goto free_data; + } + + if (fstat(data->fd, &s) < 0) { + ret = -errno; + goto exit; + } + + map = memmap(data->fd, PROT_READ | PROT_WRITE); + if (map == MAP_FAILED) { + ret = -errno; + goto exit; + } + + ubootvarfs_parse(data, map, s.st_size); + + sb->s_op = &ubootvarfs_ops; + inode = ubootvarfs_get_inode(sb, NULL, S_IFDIR, NULL); + sb->s_root = d_make_root(inode); + + /* + * We don't use cdev * directly, but this is needed for + * cdev_get_mount_path() to work right + */ + fsdev->cdev = cdev_by_name(devpath_to_name(fsdev->backingstore)); + + return 0; +exit: + close(data->fd); +free_data: + free(data); + return ret; +} + +static void ubootvarfs_remove(struct device_d *dev) +{ + struct ubootvarfs_data *data = dev->priv; + + flush(data->fd); + close(data->fd); + free(data); +} + +static struct fs_driver_d ubootvarfs_driver = { + .truncate = ubootvarfs_truncate, + .read = ubootvarfs_read, + .write = ubootvarfs_write, + .type = filetype_ubootvar, + .drv = { + .probe = ubootvarfs_probe, + .remove = ubootvarfs_remove, + .name = "ubootvarfs", + } +}; + +static int ubootvarfs_init(void) +{ + return register_fs_driver(&ubootvarfs_driver); +} +coredevice_initcall(ubootvarfs_init); -- cgit v1.2.3