[PATCH v2 5/7] fs: Add a driver to access U-Boot environment variables

Andrey Smirnov andrew.smirnov at gmail.com
Mon Jun 3 22:20:01 PDT 2019


Add a driver working on top of ubootvar device and exposing U-Boot
environment variable data as files.

Signed-off-by: Andrey Smirnov <andrew.smirnov at gmail.com>
Signed-off-by: Cory Tusar <cory.tusar at zii.aero>
---
 Documentation/filesystems/ubootvarfs.rst |  28 ++
 fs/Kconfig                               |   8 +
 fs/Makefile                              |   1 +
 fs/ubootvarfs.c                          | 499 +++++++++++++++++++++++
 4 files changed, 536 insertions(+)
 create mode 100644 Documentation/filesystems/ubootvarfs.rst
 create mode 100644 fs/ubootvarfs.c

diff --git a/Documentation/filesystems/ubootvarfs.rst b/Documentation/filesystems/ubootvarfs.rst
new file mode 100644
index 000000000..0433b1294
--- /dev/null
+++ b/Documentation/filesystems/ubootvarfs.rst
@@ -0,0 +1,28 @@
+.. index:: ubootvarfs (filesystem)
+
+.. _filesystems_ubootvarfs:
+
+U-Boot environment filesystem
+=============================
+
+barebox supports accessing U-Boot environment contents as a regular
+filesystems in both read and write modes.  U-Boot environment data
+(ubootvar) device supports automount, so no explicit mount command
+should be necessary and accessing the environment should be as easy
+as:
+
+.. code-block:: console
+
+  barebox:/ ls -l /mnt/ubootvar0
+
+However the filesystem can be explicitly mounted with the following
+command:
+
+.. code-block:: console
+
+  barebox:/ mount -t ubootvarfs /dev/device /mnt/path
+
+**NOTE** Current implementation of the filesystem driver uses lazy
+synchronization, any changes made to the environment will not be
+written to the medium until the filesystem is unmounted (will happen
+automatically on Barebox shutdown)
diff --git a/fs/Kconfig b/fs/Kconfig
index e3a95321c..adf281a5b 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 ac3e6a03a..9889a6507 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 000000000..81ec05d5e
--- /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 <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <fs.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/stat.h>
+#include <xfuncs.h>
+#include <fcntl.h>
+#include <efi.h>
+#include <wchar.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+
+/**
+ * Some theory of operation:
+ *
+ * U-Boot environment variable data is expected to be presented as a
+ * single blob containing an arbitrary number "<key>=<value>\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 <varname>=\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);
-- 
2.21.0




More information about the barebox mailing list