* [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O)
@ 2025-06-06 8:58 Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 01/10] tftp: centralize 2 sec d_revalidate optimization to new netfs lib Ahmad Fatoum
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox
In preparation for adding usb9pfs support to barebox, this series adds
9PFS over Virt I/O, which is nifty for passing files to (and from, FS
is writable) barebox running in Qemu.
Ahmad Fatoum (10):
tftp: centralize 2 sec d_revalidate optimization to new netfs lib
Port Linux __cleanup() based guard infrastructure
lib: idr: implement Linux idr_alloc/_u32 API
lib: add iov_iter I/O vector iterator support
lib: add parser code for mount options
include: add definitions for UID/GID/DEV
net: add support for 9P protocol
fs: add new 9P2000.l (Plan 9) File system support
fs: 9p: enable 9P over Virt I/O transport in defconfigs
test: add support for --fs option in QEMU
Documentation/user/virtio.rst | 32 +-
Makefile | 3 -
arch/arm/configs/multi_v7_defconfig | 4 +
arch/arm/configs/multi_v8_defconfig | 4 +
arch/riscv/configs/rv64i_defconfig | 4 +
arch/riscv/configs/virt32_defconfig | 4 +
common/startup.c | 3 +
conftest.py | 15 +
drivers/firmware/arm_scmi/bus.c | 5 +-
drivers/firmware/arm_scmi/driver.c | 10 +-
fs/9p/Kconfig | 18 +
fs/9p/Makefile | 12 +
fs/9p/fid.c | 295 ++++
fs/9p/fid.h | 37 +
fs/9p/v9fs.c | 373 ++++++
fs/9p/v9fs.h | 183 +++
fs/9p/v9fs_vfs.h | 74 +
fs/9p/vfs_addr.c | 111 ++
fs/9p/vfs_dir.c | 172 +++
fs/9p/vfs_file.c | 67 +
fs/9p/vfs_inode.c | 279 ++++
fs/9p/vfs_inode_dotl.c | 579 ++++++++
fs/9p/vfs_super.c | 243 ++++
fs/Kconfig | 6 +
fs/Makefile | 2 +
fs/fs.c | 9 +
fs/netfs.c | 25 +
fs/tftp.c | 35 +-
include/linux/cleanup.h | 401 ++++++
include/linux/compiler-clang.h | 11 +
include/linux/compiler_types.h | 6 +
include/linux/completion.h | 3 +
include/linux/fs.h | 47 +
include/linux/gfp.h | 2 +
include/linux/idr.h | 8 +-
include/linux/kdev_t.h | 22 +
include/linux/limits.h | 1 +
include/linux/module.h | 1 +
include/linux/mutex.h | 8 +-
include/linux/netfs.h | 78 ++
include/linux/parser.h | 42 +
include/linux/spinlock.h | 17 +-
include/linux/stat.h | 4 +
include/linux/uidgid.h | 59 +
include/linux/uidgid_types.h | 15 +
include/linux/uio.h | 305 +++++
include/net/9p/9p.h | 559 ++++++++
include/net/9p/client.h | 297 ++++
include/net/9p/transport.h | 73 +
include/printf.h | 11 +
include/uapi/linux/uio.h | 23 +
include/uapi/linux/virtio_9p.h | 44 +
lib/Makefile | 2 +
lib/idr.c | 116 +-
lib/iov_iter.c | 245 ++++
lib/parser.c | 363 +++++
net/9p/Kconfig | 32 +
net/9p/Makefile | 11 +
net/9p/client.c | 1932 +++++++++++++++++++++++++++
net/9p/mod.c | 180 +++
net/9p/protocol.c | 802 +++++++++++
net/9p/protocol.h | 19 +
net/9p/trans_virtio.c | 426 ++++++
net/Kconfig | 2 +
net/Makefile | 1 +
test/self/idr.c | 5 +
66 files changed, 8715 insertions(+), 62 deletions(-)
create mode 100644 fs/9p/Kconfig
create mode 100644 fs/9p/Makefile
create mode 100644 fs/9p/fid.c
create mode 100644 fs/9p/fid.h
create mode 100644 fs/9p/v9fs.c
create mode 100644 fs/9p/v9fs.h
create mode 100644 fs/9p/v9fs_vfs.h
create mode 100644 fs/9p/vfs_addr.c
create mode 100644 fs/9p/vfs_dir.c
create mode 100644 fs/9p/vfs_file.c
create mode 100644 fs/9p/vfs_inode.c
create mode 100644 fs/9p/vfs_inode_dotl.c
create mode 100644 fs/9p/vfs_super.c
create mode 100644 fs/netfs.c
create mode 100644 include/linux/cleanup.h
create mode 100644 include/linux/kdev_t.h
create mode 100644 include/linux/netfs.h
create mode 100644 include/linux/parser.h
create mode 100644 include/linux/uidgid.h
create mode 100644 include/linux/uidgid_types.h
create mode 100644 include/linux/uio.h
create mode 100644 include/net/9p/9p.h
create mode 100644 include/net/9p/client.h
create mode 100644 include/net/9p/transport.h
create mode 100644 include/uapi/linux/uio.h
create mode 100644 include/uapi/linux/virtio_9p.h
create mode 100644 lib/iov_iter.c
create mode 100644 lib/parser.c
create mode 100644 net/9p/Kconfig
create mode 100644 net/9p/Makefile
create mode 100644 net/9p/client.c
create mode 100644 net/9p/mod.c
create mode 100644 net/9p/protocol.c
create mode 100644 net/9p/protocol.h
create mode 100644 net/9p/trans_virtio.c
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 01/10] tftp: centralize 2 sec d_revalidate optimization to new netfs lib
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 02/10] Port Linux __cleanup() based guard infrastructure Ahmad Fatoum
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The logic of dentries expiring after 2 seconds can be useful elsewhere
as well, so factor this out as seed for a new netfs lib that is going to
be used for 9pfs as well.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
fs/Kconfig | 4 +++
fs/Makefile | 1 +
fs/netfs.c | 25 ++++++++++++++
fs/tftp.c | 35 +++++--------------
include/linux/netfs.h | 78 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 116 insertions(+), 27 deletions(-)
create mode 100644 fs/netfs.c
create mode 100644 include/linux/netfs.h
diff --git a/fs/Kconfig b/fs/Kconfig
index 01ac3040438d..f94488e643bf 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -19,6 +19,9 @@ config FS_WRITABLE
invisible option selected by filesystem drivers that can
write and are not fully read-only.
+config NETFS_SUPPORT
+ bool
+
if FS_LEGACY
comment "Some selected filesystems still use the legacy FS API."
comment "Consider updating them."
@@ -51,6 +54,7 @@ config FS_TFTP
prompt "tftp support"
depends on NET
select FS_WRITABLE
+ select NETFS_SUPPORT
config FS_TFTP_MAX_WINDOW_SIZE
int
diff --git a/fs/Makefile b/fs/Makefile
index 20a26a16c5ab..b6a44ff82a38 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_FS_LEGACY) += legacy.o
obj-$(CONFIG_FS_DEVFS) += devfs.o
obj-pbl-$(CONFIG_FS_FAT) += fat/
obj-y += fs.o libfs.o
+obj-$(CONFIG_NETFS_SUPPORT) += netfs.o
obj-$(CONFIG_FS_JFFS2) += jffs2/
obj-$(CONFIG_FS_UBIFS) += ubifs/
obj-$(CONFIG_FS_TFTP) += tftp.o
diff --git a/fs/netfs.c b/fs/netfs.c
new file mode 100644
index 000000000000..446377130d61
--- /dev/null
+++ b/fs/netfs.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/dcache.h>
+#include <clock.h>
+#include <linux/netfs.h>
+
+
+static int netfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
+{
+ struct netfs_inode *node;
+
+ if (!dentry->d_inode)
+ return 0;
+
+ node = netfs_inode(dentry->d_inode);
+
+ if (is_timeout(node->time, NETFS_INODE_VALID_TIME))
+ return 0;
+
+ return 1;
+}
+
+const struct dentry_operations netfs_dentry_operations_timed = {
+ .d_revalidate = netfs_lookup_revalidate,
+};
diff --git a/fs/tftp.c b/fs/tftp.c
index cf91f66ed88b..5cb71431eb49 100644
--- a/fs/tftp.c
+++ b/fs/tftp.c
@@ -35,6 +35,7 @@
#include <kfifo.h>
#include <parseopt.h>
#include <linux/sizes.h>
+#include <linux/netfs.h>
#include "tftp-selftest.h"
@@ -131,13 +132,12 @@ struct tftp_priv {
};
struct tftp_inode {
- struct inode inode;
- u64 time;
+ struct netfs_inode netfs_node;
};
static struct tftp_inode *to_tftp_inode(struct inode *inode)
{
- return container_of(inode, struct tftp_inode, inode);
+ return container_of(inode, struct tftp_inode, netfs_node.inode);
}
static inline bool is_block_before(uint16_t a, uint16_t b)
@@ -957,12 +957,12 @@ static struct inode *tftp_get_inode(struct super_block *sb, const struct inode *
if (!inode)
return NULL;
- node = to_tftp_inode(inode);
- node->time = get_time_ns();
-
inode->i_ino = get_next_ino();
inode->i_mode = mode;
+ node = to_tftp_inode(inode);
+ netfs_inode_init(&node->netfs_node);
+
switch (mode & S_IFMT) {
default:
return NULL;
@@ -1040,7 +1040,7 @@ static struct inode *tftp_alloc_inode(struct super_block *sb)
if (!node)
return NULL;
- return &node->inode;
+ return &node->netfs_node.inode;
}
static void tftp_destroy_inode(struct inode *inode)
@@ -1055,25 +1055,6 @@ static const struct super_operations tftp_ops = {
.destroy_inode = tftp_destroy_inode,
};
-static int tftp_lookup_revalidate(struct dentry *dentry, unsigned int flags)
-{
- struct tftp_inode *node;
-
- if (!dentry->d_inode)
- return 0;
-
- node = to_tftp_inode(dentry->d_inode);
-
- if (is_timeout(node->time, 2 * SECOND))
- return 0;
-
- return 1;
-}
-
-static const struct dentry_operations tftp_dentry_operations = {
- .d_revalidate = tftp_lookup_revalidate,
-};
-
static int tftp_probe(struct device *dev)
{
struct fs_device *fsdev = dev_to_fs_device(dev);
@@ -1091,7 +1072,7 @@ static int tftp_probe(struct device *dev)
}
sb->s_op = &tftp_ops;
- sb->s_d_op = &tftp_dentry_operations;
+ sb->s_d_op = &netfs_dentry_operations_timed;
inode = tftp_get_inode(sb, NULL, S_IFDIR);
sb->s_root = d_make_root(inode);
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
new file mode 100644
index 000000000000..4aa2e2223d4a
--- /dev/null
+++ b/include/linux/netfs.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Network filesystem support services.
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * See:
+ *
+ * Documentation/filesystems/netfs_library.rst
+ *
+ * for a description of the network filesystem interface declared here.
+ */
+
+#ifndef _LINUX_NETFS_H
+#define _LINUX_NETFS_H
+
+#include <linux/fs.h>
+#include <linux/container_of.h>
+#include <clock.h>
+
+/*
+ * Per-inode context. This wraps the VFS inode.
+ */
+struct netfs_inode {
+ struct inode inode; /* The VFS inode */
+ u64 time; /* Time the inode was allocated */
+};
+
+enum netfs_io_origin {
+ NETFS_READPAGE, /* This read is a synchronous read */
+ NETFS_WRITETHROUGH, /* This write was made by netfs_perform_write() */
+} __mode(byte);
+
+/*
+ * Descriptor for an I/O helper request. This is used to make multiple I/O
+ * operations to a variety of data stores and then stitch the result together.
+ */
+struct netfs_io_request {
+ struct inode *inode; /* The file being accessed */
+ void *netfs_priv; /* Private data for the netfs */
+ enum netfs_io_origin origin; /* Origin of the request */
+};
+
+/**
+ * netfs_inode - Get the netfs inode context from the inode
+ * @inode: The inode to query
+ *
+ * Get the netfs lib inode context from the network filesystem's inode. The
+ * context struct is expected to directly follow on from the VFS inode struct.
+ */
+static inline struct netfs_inode *netfs_inode(struct inode *inode)
+{
+ return container_of(inode, struct netfs_inode, inode);
+}
+
+/**
+ * netfs_inode_init - Initialise a netfslib inode context
+ * @ctx: The netfs inode to initialise
+ *
+ * Initialise the netfs library context struct. This is expected to follow on
+ * directly from the VFS inode struct.
+ */
+static inline void netfs_inode_init(struct netfs_inode *ctx)
+{
+ ctx->time = get_time_ns();
+}
+
+#define NETFS_INODE_VALID_TIME (2 * NSEC_PER_SEC)
+
+static inline void netfs_invalidate_inode_attr(struct netfs_inode *ctx)
+{
+ /* ensure that we always detect the inode to be stale */
+ ctx->time = -NETFS_INODE_VALID_TIME;
+}
+
+extern const struct dentry_operations netfs_dentry_operations_timed;
+
+#endif /* _LINUX_NETFS_H */
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 02/10] Port Linux __cleanup() based guard infrastructure
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 01/10] tftp: centralize 2 sec d_revalidate optimization to new netfs lib Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 03/10] lib: idr: implement Linux idr_alloc/_u32 API Ahmad Fatoum
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Kernel code is increasingly switching to make use of the new cleanup
mechanism. This will also be useful for us in barebox to avoid leaks
that are now biting us when fuzzing.
As first step, let's import the infrastructure along with dummy guards
for mutexes and spinlocks, which can be built upon in future.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
Makefile | 3 -
include/linux/cleanup.h | 401 +++++++++++++++++++++++++++++++++
include/linux/compiler-clang.h | 11 +
include/linux/compiler_types.h | 6 +
include/linux/mutex.h | 8 +-
include/linux/spinlock.h | 17 +-
6 files changed, 439 insertions(+), 7 deletions(-)
create mode 100644 include/linux/cleanup.h
diff --git a/Makefile b/Makefile
index 031d5bdd3e20..8ab361ed63ed 100644
--- a/Makefile
+++ b/Makefile
@@ -783,9 +783,6 @@ KBUILD_USERLDFLAGS += $(filter -m32 -m64, $(KBUILD_CFLAGS))
NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
CHECKFLAGS += $(NOSTDINC_FLAGS)
-# warn about C99 declaration after statement
-KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
-
# warn about e.g. (unsigned)x < 0
KBUILD_CFLAGS += $(call cc-option,-Wtype-limits)
diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h
new file mode 100644
index 000000000000..d796f1b1c8fc
--- /dev/null
+++ b/include/linux/cleanup.h
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_CLEANUP_H
+#define _LINUX_CLEANUP_H
+
+#include <linux/compiler.h>
+
+/**
+ * DOC: scope-based cleanup helpers
+ *
+ * The "goto error" pattern is notorious for introducing subtle resource
+ * leaks. It is tedious and error prone to add new resource acquisition
+ * constraints into code paths that already have several unwind
+ * conditions. The "cleanup" helpers enable the compiler to help with
+ * this tedium and can aid in maintaining LIFO (last in first out)
+ * unwind ordering to avoid unintentional leaks.
+ *
+ * As drivers make up the majority of the kernel code base, here is an
+ * example of using these helpers to clean up PCI drivers. The target of
+ * the cleanups are occasions where a goto is used to unwind a device
+ * reference (pci_dev_put()), or unlock the device (pci_dev_unlock())
+ * before returning.
+ *
+ * The DEFINE_FREE() macro can arrange for PCI device references to be
+ * dropped when the associated variable goes out of scope::
+ *
+ * DEFINE_FREE(pci_dev_put, struct pci_dev *, if (_T) pci_dev_put(_T))
+ * ...
+ * struct pci_dev *dev __free(pci_dev_put) =
+ * pci_get_slot(parent, PCI_DEVFN(0, 0));
+ *
+ * The above will automatically call pci_dev_put() if @dev is non-NULL
+ * when @dev goes out of scope (automatic variable scope). If a function
+ * wants to invoke pci_dev_put() on error, but return @dev (i.e. without
+ * freeing it) on success, it can do::
+ *
+ * return no_free_ptr(dev);
+ *
+ * ...or::
+ *
+ * return_ptr(dev);
+ *
+ * The DEFINE_GUARD() macro can arrange for the PCI device lock to be
+ * dropped when the scope where guard() is invoked ends::
+ *
+ * DEFINE_GUARD(pci_dev, struct pci_dev *, pci_dev_lock(_T), pci_dev_unlock(_T))
+ * ...
+ * guard(pci_dev)(dev);
+ *
+ * The lifetime of the lock obtained by the guard() helper follows the
+ * scope of automatic variable declaration. Take the following example::
+ *
+ * func(...)
+ * {
+ * if (...) {
+ * ...
+ * guard(pci_dev)(dev); // pci_dev_lock() invoked here
+ * ...
+ * } // <- implied pci_dev_unlock() triggered here
+ * }
+ *
+ * Observe the lock is held for the remainder of the "if ()" block not
+ * the remainder of "func()".
+ *
+ * Now, when a function uses both __free() and guard(), or multiple
+ * instances of __free(), the LIFO order of variable definition order
+ * matters. GCC documentation says:
+ *
+ * "When multiple variables in the same scope have cleanup attributes,
+ * at exit from the scope their associated cleanup functions are run in
+ * reverse order of definition (last defined, first cleanup)."
+ *
+ * When the unwind order matters it requires that variables be defined
+ * mid-function scope rather than at the top of the file. Take the
+ * following example and notice the bug highlighted by "!!"::
+ *
+ * LIST_HEAD(list);
+ * DEFINE_MUTEX(lock);
+ *
+ * struct object {
+ * struct list_head node;
+ * };
+ *
+ * static struct object *alloc_add(void)
+ * {
+ * struct object *obj;
+ *
+ * lockdep_assert_held(&lock);
+ * obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ * if (obj) {
+ * LIST_HEAD_INIT(&obj->node);
+ * list_add(obj->node, &list):
+ * }
+ * return obj;
+ * }
+ *
+ * static void remove_free(struct object *obj)
+ * {
+ * lockdep_assert_held(&lock);
+ * list_del(&obj->node);
+ * kfree(obj);
+ * }
+ *
+ * DEFINE_FREE(remove_free, struct object *, if (_T) remove_free(_T))
+ * static int init(void)
+ * {
+ * struct object *obj __free(remove_free) = NULL;
+ * int err;
+ *
+ * guard(mutex)(&lock);
+ * obj = alloc_add();
+ *
+ * if (!obj)
+ * return -ENOMEM;
+ *
+ * err = other_init(obj);
+ * if (err)
+ * return err; // remove_free() called without the lock!!
+ *
+ * no_free_ptr(obj);
+ * return 0;
+ * }
+ *
+ * That bug is fixed by changing init() to call guard() and define +
+ * initialize @obj in this order::
+ *
+ * guard(mutex)(&lock);
+ * struct object *obj __free(remove_free) = alloc_add();
+ *
+ * Given that the "__free(...) = NULL" pattern for variables defined at
+ * the top of the function poses this potential interdependency problem
+ * the recommendation is to always define and assign variables in one
+ * statement and not group variable definitions at the top of the
+ * function when __free() is used.
+ *
+ * Lastly, given that the benefit of cleanup helpers is removal of
+ * "goto", and that the "goto" statement can jump between scopes, the
+ * expectation is that usage of "goto" and cleanup helpers is never
+ * mixed in the same function. I.e. for a given routine, convert all
+ * resources that need a "goto" cleanup to scope-based cleanup, or
+ * convert none of them.
+ */
+
+/*
+ * DEFINE_FREE(name, type, free):
+ * simple helper macro that defines the required wrapper for a __free()
+ * based cleanup function. @free is an expression using '_T' to access the
+ * variable. @free should typically include a NULL test before calling a
+ * function, see the example below.
+ *
+ * __free(name):
+ * variable attribute to add a scoped based cleanup to the variable.
+ *
+ * no_free_ptr(var):
+ * like a non-atomic xchg(var, NULL), such that the cleanup function will
+ * be inhibited -- provided it sanely deals with a NULL value.
+ *
+ * NOTE: this has __must_check semantics so that it is harder to accidentally
+ * leak the resource.
+ *
+ * return_ptr(p):
+ * returns p while inhibiting the __free().
+ *
+ * Ex.
+ *
+ * DEFINE_FREE(kfree, void *, if (_T) kfree(_T))
+ *
+ * void *alloc_obj(...)
+ * {
+ * struct obj *p __free(kfree) = kmalloc(...);
+ * if (!p)
+ * return NULL;
+ *
+ * if (!init_obj(p))
+ * return NULL;
+ *
+ * return_ptr(p);
+ * }
+ *
+ * NOTE: the DEFINE_FREE()'s @free expression includes a NULL test even though
+ * kfree() is fine to be called with a NULL value. This is on purpose. This way
+ * the compiler sees the end of our alloc_obj() function as:
+ *
+ * tmp = p;
+ * p = NULL;
+ * if (p)
+ * kfree(p);
+ * return tmp;
+ *
+ * And through the magic of value-propagation and dead-code-elimination, it
+ * eliminates the actual cleanup call and compiles into:
+ *
+ * return p;
+ *
+ * Without the NULL test it turns into a mess and the compiler can't help us.
+ */
+
+#define DEFINE_FREE(_name, _type, _free) \
+ static inline void __free_##_name(void *p) { _type _T = *(_type *)p; _free; }
+
+#define __free(_name) __cleanup(__free_##_name)
+
+#define __get_and_null(p, nullvalue) \
+ ({ \
+ __auto_type __ptr = &(p); \
+ __auto_type __val = *__ptr; \
+ *__ptr = nullvalue; \
+ __val; \
+ })
+
+static inline __must_check
+const volatile void * __must_check_fn(const volatile void *val)
+{ return val; }
+
+#define no_free_ptr(p) \
+ ((typeof(p)) __must_check_fn(__get_and_null(p, NULL)))
+
+#define return_ptr(p) return no_free_ptr(p)
+
+
+/*
+ * DEFINE_CLASS(name, type, exit, init, init_args...):
+ * helper to define the destructor and constructor for a type.
+ * @exit is an expression using '_T' -- similar to FREE above.
+ * @init is an expression in @init_args resulting in @type
+ *
+ * EXTEND_CLASS(name, ext, init, init_args...):
+ * extends class @name to @name@ext with the new constructor
+ *
+ * CLASS(name, var)(args...):
+ * declare the variable @var as an instance of the named class
+ *
+ * Ex.
+ *
+ * DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd)
+ *
+ * CLASS(fdget, f)(fd);
+ * if (!fd_file(f))
+ * return -EBADF;
+ *
+ * // use 'f' without concern
+ */
+
+#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
+typedef _type class_##_name##_t; \
+static inline void class_##_name##_destructor(_type *p) \
+{ _type _T = *p; _exit; } \
+static inline _type class_##_name##_constructor(_init_args) \
+{ _type t = _init; return t; }
+
+#define EXTEND_CLASS(_name, ext, _init, _init_args...) \
+typedef class_##_name##_t class_##_name##ext##_t; \
+static inline void class_##_name##ext##_destructor(class_##_name##_t *p)\
+{ class_##_name##_destructor(p); } \
+static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
+{ class_##_name##_t t = _init; return t; }
+
+#define CLASS(_name, var) \
+ class_##_name##_t var __cleanup(class_##_name##_destructor) = \
+ class_##_name##_constructor
+
+
+/*
+ * DEFINE_GUARD(name, type, lock, unlock):
+ * trivial wrapper around DEFINE_CLASS() above specifically
+ * for locks.
+ *
+ * DEFINE_GUARD_COND(name, ext, condlock)
+ * wrapper around EXTEND_CLASS above to add conditional lock
+ * variants to a base class, eg. mutex_trylock() or
+ * mutex_lock_interruptible().
+ *
+ * guard(name):
+ * an anonymous instance of the (guard) class, not recommended for
+ * conditional locks.
+ *
+ * scoped_guard (name, args...) { }:
+ * similar to CLASS(name, scope)(args), except the variable (with the
+ * explicit name 'scope') is declard in a for-loop such that its scope is
+ * bound to the next (compound) statement.
+ *
+ * for conditional locks the loop body is skipped when the lock is not
+ * acquired.
+ *
+ * scoped_cond_guard (name, fail, args...) { }:
+ * similar to scoped_guard(), except it does fail when the lock
+ * acquire fails.
+ *
+ */
+
+#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
+ DEFINE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \
+ static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \
+ { return *_T; }
+
+#define DEFINE_GUARD_COND(_name, _ext, _condlock) \
+ EXTEND_CLASS(_name, _ext, \
+ ({ void *_t = _T; if (_T && !(_condlock)) _t = NULL; _t; }), \
+ class_##_name##_t _T) \
+ static inline void * class_##_name##_ext##_lock_ptr(class_##_name##_t *_T) \
+ { return class_##_name##_lock_ptr(_T); }
+
+#define guard(_name) \
+ CLASS(_name, __UNIQUE_ID(guard))
+
+#define __guard_ptr(_name) class_##_name##_lock_ptr
+
+#define scoped_guard(_name, args...) \
+ for (CLASS(_name, scope)(args), \
+ *done = NULL; __guard_ptr(_name)(&scope) && !done; done = (void *)1)
+
+#define scoped_cond_guard(_name, _fail, args...) \
+ for (CLASS(_name, scope)(args), \
+ *done = NULL; !done; done = (void *)1) \
+ if (!__guard_ptr(_name)(&scope)) _fail; \
+ else
+
+/*
+ * Additional helper macros for generating lock guards with types, either for
+ * locks that don't have a native type (eg. RCU, preempt) or those that need a
+ * 'fat' pointer (eg. spin_lock_irqsave).
+ *
+ * DEFINE_LOCK_GUARD_0(name, lock, unlock, ...)
+ * DEFINE_LOCK_GUARD_1(name, type, lock, unlock, ...)
+ * DEFINE_LOCK_GUARD_1_COND(name, ext, condlock)
+ *
+ * will result in the following type:
+ *
+ * typedef struct {
+ * type *lock; // 'type := void' for the _0 variant
+ * __VA_ARGS__;
+ * } class_##name##_t;
+ *
+ * As above, both _lock and _unlock are statements, except this time '_T' will
+ * be a pointer to the above struct.
+ */
+
+#define __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, ...) \
+typedef struct { \
+ _type *lock; \
+ __VA_ARGS__; \
+} class_##_name##_t; \
+ \
+static inline void class_##_name##_destructor(class_##_name##_t *_T) \
+{ \
+ if (_T->lock) { _unlock; } \
+} \
+ \
+static inline void *class_##_name##_lock_ptr(class_##_name##_t *_T) \
+{ \
+ return _T->lock; \
+}
+
+
+#define __DEFINE_LOCK_GUARD_1(_name, _type, _lock) \
+static inline class_##_name##_t class_##_name##_constructor(_type *l) \
+{ \
+ class_##_name##_t _t = { .lock = l }, *_T = &_t; \
+ _lock; \
+ return _t; \
+}
+
+#define __DEFINE_LOCK_GUARD_0(_name, _lock) \
+static inline class_##_name##_t class_##_name##_constructor(void) \
+{ \
+ class_##_name##_t _t = { .lock = (void*)1 }, \
+ *_T __maybe_unused = &_t; \
+ _lock; \
+ return _t; \
+}
+
+#define DEFINE_LOCK_GUARD_1(_name, _type, _lock, _unlock, ...) \
+__DEFINE_UNLOCK_GUARD(_name, _type, _unlock, __VA_ARGS__) \
+__DEFINE_LOCK_GUARD_1(_name, _type, _lock)
+
+#define DEFINE_LOCK_GUARD_0(_name, _lock, _unlock, ...) \
+__DEFINE_UNLOCK_GUARD(_name, void, _unlock, __VA_ARGS__) \
+__DEFINE_LOCK_GUARD_0(_name, _lock)
+
+#define DEFINE_LOCK_GUARD_1_COND(_name, _ext, _condlock) \
+ EXTEND_CLASS(_name, _ext, \
+ ({ class_##_name##_t _t = { .lock = l }, *_T = &_t;\
+ if (_T->lock && !(_condlock)) _T->lock = NULL; \
+ _t; }), \
+ typeof_member(class_##_name##_t, lock) l) \
+ static inline void * class_##_name##_ext##_lock_ptr(class_##_name##_t *_T) \
+ { return class_##_name##_lock_ptr(_T); }
+
+
+#define dummy_do(arg, ...) ((void)arg)
+#define dummy_undo(arg, ...) ((void)arg)
+
+#define DEFINE_DUMMY_GUARD(_name, _type) \
+ DEFINE_GUARD(_name, _type, dummy_do(_T), dummy_undo(_T))
+
+#define DEFINE_DUMMY_GUARD_1(_name, _type, ...) \
+ __DEFINE_UNLOCK_GUARD(_name, _type, dummy_undo(_T)) \
+ __DEFINE_LOCK_GUARD_1(_name, _type, dummy_do(_T))
+
+
+
+#endif /* _LINUX_CLEANUP_H */
diff --git a/include/linux/compiler-clang.h b/include/linux/compiler-clang.h
index b1ce500fe8b3..681e347283b2 100644
--- a/include/linux/compiler-clang.h
+++ b/include/linux/compiler-clang.h
@@ -3,6 +3,17 @@
#error "Please don't include <linux/compiler-clang.h> directly, include <linux/compiler.h> instead."
#endif
+/* Compiler specific definitions for Clang compiler */
+
+/*
+ * Clang prior to 17 is being silly and considers many __cleanup() variables
+ * as unused (because they are, their sole purpose is to go out of scope).
+ *
+ * https://github.com/llvm/llvm-project/commit/877210faa447f4cc7db87812f8ed80e398fedd61
+ */
+#undef __cleanup
+#define __cleanup(func) __maybe_unused __attribute__((__cleanup__(func)))
+
/* Some compiler specific definitions are overwritten here
* for Clang compiler
*/
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 87ab117aca13..9aac751af7ab 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -54,6 +54,12 @@ extern void __chk_io_ptr(const volatile void __iomem *);
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
+/*
+ * gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute
+ * clang: https://clang.llvm.org/docs/AttributeReference.html#cleanup
+ */
+#define __cleanup(func) __attribute__((__cleanup__(func)))
+
#ifdef __KERNEL__
/*
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index f511c45814e7..5b3ac89f9bab 100644
--- a/include/linux/mutex.h
+++ b/include/linux/mutex.h
@@ -12,9 +12,11 @@
#ifndef __LINUX_MUTEX_H
#define __LINUX_MUTEX_H
+#include <linux/cleanup.h>
+
#define mutex_init(...)
-#define mutex_lock(...)
-#define mutex_unlock(...)
+#define mutex_lock(lock) ((void)0)
+#define mutex_unlock(lock) ((void)0)
#define mutex_lock_nested(...)
#define mutex_unlock_nested(...)
#define mutex_is_locked(...) 0
@@ -22,4 +24,6 @@ struct mutex { int i; };
#define DEFINE_MUTEX(obj) struct mutex __always_unused obj
+DEFINE_DUMMY_GUARD(mutex, struct mutex *)
+
#endif /* __LINUX_MUTEX_H */
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index 6f90fb1cad43..c2d09eaf9064 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -3,15 +3,28 @@
#ifndef __LINUX_SPINLOCK_H
#define __LINUX_SPINLOCK_H
+#include <linux/cleanup.h>
+
typedef int spinlock_t;
#define spin_lock_init(...)
-#define spin_lock(...)
+#define spin_lock(lock)
#define spin_lock_bh spin_lock
-#define spin_unlock(...)
+#define spin_lock_irq spin_lock
+#define spin_unlock(lock)
#define spin_unlock_bh spin_unlock
+#define spin_unlock_irq spin_unlock
#define spin_lock_irqsave(lock, flags) do { flags = 0; } while (0)
#define spin_unlock_irqrestore(lock, flags) do { flags = flags; } while (0)
#define DEFINE_SPINLOCK(lock) spinlock_t __always_unused lock
+DEFINE_DUMMY_GUARD_1(spinlock, spinlock_t,
+ spin_lock(_T->lock),
+ spin_unlock(_T->lock))
+
+DEFINE_DUMMY_GUARD_1(spinlock_irqsave, spinlock_t,
+ spin_lock_irqsave(_T->lock, _T->flags),
+ spin_unlock_irqrestore(_T->lock, _T->flags),
+ unsigned long flags)
+
#endif /* __LINUX_SPINLOCK_H */
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 03/10] lib: idr: implement Linux idr_alloc/_u32 API
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 01/10] tftp: centralize 2 sec d_revalidate optimization to new netfs lib Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 02/10] Port Linux __cleanup() based guard infrastructure Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 04/10] lib: add iov_iter I/O vector iterator support Ahmad Fatoum
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
idr_alloc_one is a barebox invention that was adequate for the use case
in the ARM SCMI driver, but is not enough for incoming 9PFS support.
Reimplement it int terms of idr_alloc like in Linux.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/firmware/arm_scmi/bus.c | 5 +-
drivers/firmware/arm_scmi/driver.c | 10 ++-
include/linux/idr.h | 5 +-
lib/idr.c | 116 ++++++++++++++++++++++++-----
test/self/idr.c | 5 ++
5 files changed, 115 insertions(+), 26 deletions(-)
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c
index 0d7c404ae5ed..cf58b7088281 100644
--- a/drivers/firmware/arm_scmi/bus.c
+++ b/drivers/firmware/arm_scmi/bus.c
@@ -114,8 +114,9 @@ static int scmi_protocol_device_request(const struct scmi_device_id *id_table)
}
INIT_LIST_HEAD(phead);
- ret = idr_alloc_one(&scmi_requested_devices, (void *)phead,
- id_table->protocol_id);
+ ret = idr_alloc(&scmi_requested_devices, (void *)phead,
+ id_table->protocol_id,
+ id_table->protocol_id + 1, GFP_KERNEL);
if (ret != id_table->protocol_id) {
pr_err("Failed to save SCMI device - ret:%d\n", ret);
kfree(rdev);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index e602f7a4404a..0048cc012223 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -173,7 +173,8 @@ int scmi_protocol_register(const struct scmi_protocol *proto)
}
spin_lock(&protocol_lock);
- ret = idr_alloc_one(&scmi_protocols, (void *)proto, proto->id);
+ ret = idr_alloc(&scmi_protocols, (void *)proto, proto->id, proto->id + 1,
+ GFP_KERNEL);
spin_unlock(&protocol_lock);
if (ret != proto->id) {
pr_err("unable to allocate SCMI idr slot for 0x%x - err %d\n",
@@ -1090,7 +1091,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
if (ret)
goto clean;
- ret = idr_alloc_one(&info->protocols, pi, proto->id);
+ ret = idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, GFP_KERNEL);
if (ret != proto->id)
goto clean;
@@ -1444,7 +1445,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
}
idr_alloc:
- ret = idr_alloc_one(idr, cinfo, prot_id);
+ ret = idr_alloc(idr, cinfo, prot_id, prot_id + 1, GFP_KERNEL);
if (ret != prot_id) {
dev_err(info->dev,
"unable to allocate SCMI idr slot err %d\n", ret);
@@ -1693,7 +1694,8 @@ static int scmi_probe(struct device *dev)
* Save this valid DT protocol descriptor amongst
* @active_protocols for this SCMI instance/
*/
- ret = idr_alloc_one(&info->active_protocols, child, prot_id);
+ ret = idr_alloc(&info->active_protocols, child,
+ prot_id, prot_id + 1, GFP_KERNEL);
if (ret != prot_id) {
dev_err(dev, "SCMI protocol %d already activated. Skip\n",
prot_id);
diff --git a/include/linux/idr.h b/include/linux/idr.h
index 9939085d0e8d..8573a2b05ee6 100644
--- a/include/linux/idr.h
+++ b/include/linux/idr.h
@@ -14,6 +14,7 @@
#include <errno.h>
#include <linux/list.h>
+#include <linux/gfp.h>
struct idr {
int id;
@@ -58,7 +59,9 @@ static inline void *idr_find(struct idr *head, int id)
return idr ? idr->ptr : NULL;
}
-int idr_alloc_one(struct idr *head, void *ptr, int start);
+int idr_alloc(struct idr *, void *ptr, int start, int end, gfp_t);
+int __must_check idr_alloc_u32(struct idr *, void *ptr, u32 *id,
+ unsigned long max, gfp_t);
static inline void idr_init(struct idr *idr)
{
diff --git a/lib/idr.c b/lib/idr.c
index a25e46b17b95..03bb6eadd533 100644
--- a/lib/idr.c
+++ b/lib/idr.c
@@ -6,8 +6,10 @@
#include <errno.h>
#include <linux/idr.h>
-#include <malloc.h>
+#include <linux/bug.h>
+#include <linux/limits.h>
#include <linux/minmax.h>
+#include <xfuncs.h>
struct idr *__idr_find(struct idr *head, int lookup_id)
{
@@ -48,31 +50,107 @@ int idr_for_each(const struct idr *idr,
return 0;
}
-static int idr_compare(struct list_head *a, struct list_head *b)
-{
- int id_a = list_entry(a, struct idr, list)->id;
- int id_b = list_entry(b, struct idr, list)->id;
-
- return __compare3(id_a, id_b);
-}
-
-int idr_alloc_one(struct idr *head, void *ptr, int start)
+static struct idr *__idr_alloc(void *ptr, u32 start)
{
struct idr *idr;
- if (__idr_find(head, start))
- return -EBUSY;
-
- idr = malloc(sizeof(*idr));
- if (!idr)
- return -ENOMEM;
-
+ idr = xmalloc(sizeof(*idr));
idr->id = start;
idr->ptr = ptr;
- list_add_sort(&idr->list, &head->list, idr_compare);
+ return idr;
+}
- return start;
+/**
+ * idr_alloc_u32() - Allocate an ID.
+ * @head: IDR handle.
+ * @ptr: Pointer to be associated with the new ID.
+ * @nextid: Pointer to an ID.
+ * @max: The maximum ID to allocate (inclusive).
+ * @gfp: Memory allocation flags.
+ *
+ * Allocates an unused ID in the range specified by @nextid and @max.
+ * Note that @max is inclusive whereas the @end parameter to idr_alloc()
+ * is exclusive. The new ID is assigned to @nextid before the pointer
+ * is inserted into the IDR.
+ *
+ * Return: 0 if an ID was allocated, -ENOMEM if memory allocation failed,
+ * or -ENOSPC if no free IDs could be found. If an error occurred,
+ * @nextid is unchanged.
+ */
+int idr_alloc_u32(struct idr *head, void *ptr, u32 *nextid,
+ unsigned long max, gfp_t gfp)
+{
+ struct idr *idr, *prev, *next;
+
+ if (list_empty(&head->list)) {
+ idr = __idr_alloc(ptr, *nextid);
+ list_add_tail(&idr->list, &head->list);
+ return 0;
+ }
+
+ next = list_first_entry(&head->list, struct idr, list);
+ if (*nextid < next->id) {
+ idr = __idr_alloc(ptr, *nextid);
+ list_add(&idr->list, &head->list);
+ return 0;
+ }
+
+ prev = list_last_entry(&head->list, struct idr, list);
+ if (prev->id < max) {
+ *nextid = max_t(u32, prev->id + 1, *nextid);
+ idr = __idr_alloc(ptr, *nextid);
+ list_add_tail(&idr->list, &head->list);
+ return 0;
+ }
+
+ list_for_each_entry_safe(prev, next, &head->list, list) {
+ /* at the end of the list */
+ if (&next->list == &head->list)
+ break;
+
+ if (prev->id > max)
+ break;
+ if (next->id < *nextid)
+ continue;
+
+ *nextid = max_t(u32, prev->id + 1, *nextid);
+ idr = __idr_alloc(ptr, *nextid);
+ list_add(&idr->list, &prev->list);
+ return 0;
+ }
+
+ return -ENOSPC;
+}
+
+/**
+ * idr_alloc() - Allocate an ID.
+ * @head: IDR handle.
+ * @ptr: Pointer to be associated with the new ID.
+ * @start: The minimum ID (inclusive).
+ * @end: The maximum ID (exclusive).
+ * @gfp: Memory allocation flags.
+ *
+ * Allocates an unused ID in the range specified by @start and @end. If
+ * @end is <= 0, it is treated as one larger than %INT_MAX. This allows
+ * callers to use @start + N as @end as long as N is within integer range.
+ *
+ * Return: The newly allocated ID, -ENOMEM if memory allocation failed,
+ * or -ENOSPC if no free IDs could be found.
+ */
+int idr_alloc(struct idr *head, void *ptr, int start, int end, gfp_t gfp)
+{
+ u32 id = start;
+ int ret;
+
+ if (WARN_ON_ONCE(start < 0))
+ return -EINVAL;
+
+ ret = idr_alloc_u32(head, ptr, &id, end > 0 ? end - 1 : INT_MAX, gfp);
+ if (ret)
+ return ret;
+
+ return id;
}
static void __idr_remove(struct idr *idr)
diff --git a/test/self/idr.c b/test/self/idr.c
index bb902b041bb4..cc3217bb06e4 100644
--- a/test/self/idr.c
+++ b/test/self/idr.c
@@ -42,6 +42,11 @@ static int count_idr(int id, void *p, void *data)
return 0;
}
+static inline int idr_alloc_one(struct idr *idr, void *ptr, int id)
+{
+ return idr_alloc_u32(idr, ptr, &id, id + 1, GFP_KERNEL) ?: id;
+}
+
static void test_idr(void)
{
void *ptr;
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 04/10] lib: add iov_iter I/O vector iterator support
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (2 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 03/10] lib: idr: implement Linux idr_alloc/_u32 API Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 05/10] lib: add parser code for mount options Ahmad Fatoum
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
I/O vector iterators are a frequent sight in Linux file system drivers
and are also used in the incoming 9PFS support, so provide the
definitions in barebox.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
include/linux/uio.h | 305 +++++++++++++++++++++++++++++++++++++++
include/uapi/linux/uio.h | 23 +++
lib/Makefile | 1 +
lib/iov_iter.c | 245 +++++++++++++++++++++++++++++++
4 files changed, 574 insertions(+)
create mode 100644 include/linux/uio.h
create mode 100644 include/uapi/linux/uio.h
create mode 100644 lib/iov_iter.c
diff --git a/include/linux/uio.h b/include/linux/uio.h
new file mode 100644
index 000000000000..8724c04a4acb
--- /dev/null
+++ b/include/linux/uio.h
@@ -0,0 +1,305 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Berkeley style UIO structures - Alan Cox 1994.
+ */
+#ifndef __LINUX_UIO_H
+#define __LINUX_UIO_H
+
+#include <linux/kernel.h>
+#include <uapi/linux/uio.h>
+
+struct page;
+
+typedef unsigned int __bitwise iov_iter_extraction_t;
+
+struct kvec {
+ void *iov_base; /* and that should *never* hold a userland pointer */
+ size_t iov_len;
+};
+
+enum iter_type {
+ /* iter types */
+ ITER_KVEC,
+};
+
+#define ITER_SOURCE 1 // == WRITE
+#define ITER_DEST 0 // == READ
+
+struct iov_iter_state {
+ size_t iov_offset;
+ size_t count;
+ unsigned long nr_segs;
+};
+
+struct iov_iter {
+ u8 iter_type;
+ bool nofault;
+ bool data_source;
+ size_t iov_offset;
+ struct {
+ union {
+ /* use iter_iov() to get the current vec */
+ const struct iovec *__iov;
+ const struct kvec *kvec;
+ };
+ size_t count;
+ };
+ unsigned long nr_segs;
+};
+
+static inline const struct iovec *iter_iov(const struct iov_iter *iter)
+{
+ return iter->__iov;
+}
+
+static inline enum iter_type iov_iter_type(const struct iov_iter *i)
+{
+ return i->iter_type;
+}
+
+static inline void iov_iter_save_state(struct iov_iter *iter,
+ struct iov_iter_state *state)
+{
+ state->iov_offset = iter->iov_offset;
+ state->count = iter->count;
+ state->nr_segs = iter->nr_segs;
+}
+
+static inline bool iter_is_iovec(const struct iov_iter *i)
+{
+ return false;
+}
+
+static inline bool iov_iter_is_kvec(const struct iov_iter *i)
+{
+ return iov_iter_type(i) == ITER_KVEC;
+}
+
+/*
+ * Total number of bytes covered by an iovec.
+ *
+ * NOTE that it is not safe to use this function until all the iovec's
+ * segment lengths have been validated. Because the individual lengths can
+ * overflow a size_t when added together.
+ */
+static inline size_t iov_length(const struct iovec *iov, unsigned long nr_segs)
+{
+ unsigned long seg;
+ size_t ret = 0;
+
+ for (seg = 0; seg < nr_segs; seg++)
+ ret += iov[seg].iov_len;
+ return ret;
+}
+
+void iov_iter_advance(struct iov_iter *i, size_t bytes);
+void iov_iter_revert(struct iov_iter *i, size_t bytes);
+size_t fault_in_iov_iter_readable(const struct iov_iter *i, size_t bytes);
+size_t fault_in_iov_iter_writeable(const struct iov_iter *i, size_t bytes);
+size_t iov_iter_single_seg_count(const struct iov_iter *i);
+
+__must_check size_t copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
+__must_check size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
+__must_check size_t copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i);
+
+static __always_inline __must_check
+bool copy_to_iter_full(const void *addr, size_t bytes, struct iov_iter *i)
+{
+ size_t copied = copy_to_iter(addr, bytes, i);
+ if (likely(copied == bytes))
+ return true;
+ iov_iter_revert(i, copied);
+ return false;
+}
+
+static __always_inline __must_check
+bool copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i)
+{
+ size_t copied = copy_from_iter(addr, bytes, i);
+ if (likely(copied == bytes))
+ return true;
+ iov_iter_revert(i, copied);
+ return false;
+}
+
+static __always_inline __must_check
+bool copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i)
+{
+ size_t copied = copy_from_iter_nocache(addr, bytes, i);
+ if (likely(copied == bytes))
+ return true;
+ iov_iter_revert(i, copied);
+ return false;
+}
+
+size_t iov_iter_zero(size_t bytes, struct iov_iter *);
+bool iov_iter_is_aligned(const struct iov_iter *i, unsigned addr_mask,
+ unsigned len_mask);
+unsigned long iov_iter_alignment(const struct iov_iter *i);
+unsigned long iov_iter_gap_alignment(const struct iov_iter *i);
+void iov_iter_kvec(struct iov_iter *i, unsigned int direction, const struct kvec *kvec,
+ unsigned long nr_segs, size_t count);
+int iov_iter_npages(const struct iov_iter *i, int maxpages);
+
+const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags);
+
+static inline size_t iov_iter_count(const struct iov_iter *i)
+{
+ return i->count;
+}
+
+/*
+ * Cap the iov_iter by given limit; note that the second argument is
+ * *not* the new size - it's upper limit for such. Passing it a value
+ * greater than the amount of data in iov_iter is fine - it'll just do
+ * nothing in that case.
+ */
+static inline void iov_iter_truncate(struct iov_iter *i, u64 count)
+{
+ /*
+ * count doesn't have to fit in size_t - comparison extends both
+ * operands to u64 here and any value that would be truncated by
+ * conversion in assignement is by definition greater than all
+ * values of size_t, including old i->count.
+ */
+ if (i->count > count)
+ i->count = count;
+}
+
+/*
+ * reexpand a previously truncated iterator; count must be no more than how much
+ * we had shrunk it.
+ */
+static inline void iov_iter_reexpand(struct iov_iter *i, size_t count)
+{
+ i->count = count;
+}
+
+static inline int
+iov_iter_npages_cap(struct iov_iter *i, int maxpages, size_t max_bytes)
+{
+ size_t shorted = 0;
+ int npages;
+
+ if (iov_iter_count(i) > max_bytes) {
+ shorted = iov_iter_count(i) - max_bytes;
+ iov_iter_truncate(i, max_bytes);
+ }
+ npages = iov_iter_npages(i, maxpages);
+ if (shorted)
+ iov_iter_reexpand(i, iov_iter_count(i) + shorted);
+
+ return npages;
+}
+
+struct iovec *iovec_from_user(const struct iovec __user *uvector,
+ unsigned long nr_segs, unsigned long fast_segs,
+ struct iovec *fast_iov, bool compat);
+ssize_t import_iovec(int type, const struct iovec __user *uvec,
+ unsigned nr_segs, unsigned fast_segs, struct iovec **iovp,
+ struct iov_iter *i);
+ssize_t __import_iovec(int type, const struct iovec __user *uvec,
+ unsigned nr_segs, unsigned fast_segs, struct iovec **iovp,
+ struct iov_iter *i, bool compat);
+
+struct sg_table;
+ssize_t extract_iter_to_sg(struct iov_iter *iter, size_t len,
+ struct sg_table *sgtable, unsigned int sg_max,
+ iov_iter_extraction_t extraction_flags);
+
+typedef size_t (*iov_step_f)(void *iter_base, size_t progress, size_t len,
+ void *priv, void *priv2);
+typedef size_t (*iov_ustep_f)(void __user *iter_base, size_t progress, size_t len,
+ void *priv, void *priv2);
+
+/*
+ * Handle ITER_KVEC.
+ */
+static __always_inline
+size_t iterate_kvec(struct iov_iter *iter, size_t len, void *priv, void *priv2,
+ iov_step_f step)
+{
+ const struct kvec *p = iter->kvec;
+ size_t progress = 0, skip = iter->iov_offset;
+
+ do {
+ size_t remain, consumed;
+ size_t part = min(len, p->iov_len - skip);
+
+ if (likely(part)) {
+ remain = step(p->iov_base + skip, progress, part, priv, priv2);
+ consumed = part - remain;
+ progress += consumed;
+ skip += consumed;
+ len -= consumed;
+ if (skip < p->iov_len)
+ break;
+ }
+ p++;
+ skip = 0;
+ } while (len);
+
+ iter->nr_segs -= p - iter->kvec;
+ iter->kvec = p;
+ iter->iov_offset = skip;
+ iter->count -= progress;
+ return progress;
+}
+
+/**
+ * iterate_and_advance2 - Iterate over an iterator
+ * @iter: The iterator to iterate over.
+ * @len: The amount to iterate over.
+ * @priv: Data for the step functions.
+ * @priv2: More data for the step functions.
+ * @ustep: Function for UBUF/IOVEC iterators; given __user addresses.
+ * @step: Function for other iterators; given kernel addresses.
+ *
+ * Iterate over the next part of an iterator, up to the specified length. The
+ * buffer is presented in segments, which for kernel iteration are broken up by
+ * physical pages and mapped, with the mapped address being presented.
+ *
+ * Two step functions, @step and @ustep, must be provided, one for handling
+ * mapped kernel addresses and the other is given user addresses which have the
+ * potential to fault since no pinning is performed.
+ *
+ * The step functions are passed the address and length of the segment, @priv,
+ * @priv2 and the amount of data so far iterated over (which can, for example,
+ * be added to @priv to point to the right part of a second buffer). The step
+ * functions should return the amount of the segment they didn't process (ie. 0
+ * indicates complete processsing).
+ *
+ * This function returns the amount of data processed (ie. 0 means nothing was
+ * processed and the value of @len means processes to completion).
+ */
+static __always_inline
+size_t iterate_and_advance2(struct iov_iter *iter, size_t len, void *priv,
+ void *priv2, iov_ustep_f ustep, iov_step_f step)
+{
+ if (unlikely(iter->count < len))
+ len = iter->count;
+ if (unlikely(!len))
+ return 0;
+
+ return iterate_kvec(iter, len, priv, priv2, step);
+}
+
+
+/**
+ * iterate_and_advance - Iterate over an iterator
+ * @iter: The iterator to iterate over.
+ * @len: The amount to iterate over.
+ * @priv: Data for the step functions.
+ * @ustep: Function for UBUF/IOVEC iterators; given __user addresses.
+ * @step: Function for other iterators; given kernel addresses.
+ *
+ * As iterate_and_advance2(), but priv2 is always NULL.
+ */
+static __always_inline
+size_t iterate_and_advance(struct iov_iter *iter, size_t len, void *priv,
+ iov_ustep_f ustep, iov_step_f step)
+{
+ return iterate_and_advance2(iter, len, priv, NULL, ustep, step);
+}
+
+#endif
diff --git a/include/uapi/linux/uio.h b/include/uapi/linux/uio.h
new file mode 100644
index 000000000000..1edb1422a786
--- /dev/null
+++ b/include/uapi/linux/uio.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Berkeley style UIO structures - Alan Cox 1994.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#ifndef _UAPI__LINUX_UIO_H
+#define _UAPI__LINUX_UIO_H
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+
+struct iovec
+{
+ void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
+ __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
+};
+
+#endif /* _UAPI__LINUX_UIO_H */
diff --git a/lib/Makefile b/lib/Makefile
index 0d1d22c0845b..a1ca2803a095 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -32,6 +32,7 @@ obj-y += recursive_action.o
obj-y += make_directory.o
obj-y += arith.o
obj-$(CONFIG_IDR) += idr.o
+obj-y += iov_iter.o
obj-y += math/
obj-y += uuid.o
obj-$(CONFIG_XXHASH) += xxhash.o
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
new file mode 100644
index 000000000000..d8b6efb73920
--- /dev/null
+++ b/lib/iov_iter.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/export.h>
+#include <linux/uio.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/uaccess.h>
+
+#define MAX_RW_COUNT (INT_MAX & PAGE_MASK)
+
+static __always_inline
+size_t copy_to_user_iter(void __user *iter_to, size_t progress,
+ size_t len, void *from, void *priv2)
+{
+ if (access_ok(iter_to, len)) {
+ from += progress;
+ len = raw_copy_to_user(iter_to, from, len);
+ }
+ return len;
+}
+
+static __always_inline
+size_t copy_from_user_iter(void __user *iter_from, size_t progress,
+ size_t len, void *to, void *priv2)
+{
+ size_t res = len;
+
+ if (access_ok(iter_from, len)) {
+ to += progress;
+ res = raw_copy_from_user(to, iter_from, len);
+ }
+ return res;
+}
+
+static __always_inline
+size_t memcpy_to_iter(void *iter_to, size_t progress,
+ size_t len, void *from, void *priv2)
+{
+ memcpy(iter_to, from + progress, len);
+ return 0;
+}
+
+static __always_inline
+size_t memcpy_from_iter(void *iter_from, size_t progress,
+ size_t len, void *to, void *priv2)
+{
+ memcpy(to + progress, iter_from, len);
+ return 0;
+}
+
+static __always_inline
+size_t zero_to_user_iter(void __user *iter_to, size_t progress,
+ size_t len, void *priv, void *priv2)
+{
+ return clear_user(iter_to, len);
+}
+
+static __always_inline
+size_t zero_to_iter(void *iter_to, size_t progress,
+ size_t len, void *priv, void *priv2)
+{
+ memset(iter_to, 0, len);
+ return 0;
+}
+
+static void iov_iter_iovec_advance(struct iov_iter *i, size_t size)
+{
+ const struct iovec *iov, *end;
+
+ if (!i->count)
+ return;
+ i->count -= size;
+
+ size += i->iov_offset; // from beginning of current segment
+ for (iov = iter_iov(i), end = iov + i->nr_segs; iov < end; iov++) {
+ if (likely(size < iov->iov_len))
+ break;
+ size -= iov->iov_len;
+ }
+ i->iov_offset = size;
+ i->nr_segs -= iov - iter_iov(i);
+ i->__iov = iov;
+}
+
+void iov_iter_advance(struct iov_iter *i, size_t size)
+{
+ if (unlikely(i->count < size))
+ size = i->count;
+ if (likely(iter_is_iovec(i) || iov_iter_is_kvec(i))) {
+ /* iovec and kvec have identical layouts */
+ iov_iter_iovec_advance(i, size);
+ }
+}
+EXPORT_SYMBOL(iov_iter_advance);
+
+void iov_iter_revert(struct iov_iter *i, size_t unroll)
+{
+ if (!unroll)
+ return;
+ if (WARN_ON(unroll > MAX_RW_COUNT))
+ return;
+ i->count += unroll;
+ if (unroll <= i->iov_offset) {
+ i->iov_offset -= unroll;
+ return;
+ }
+ unroll -= i->iov_offset;
+ /* same logics for iovec and kvec */
+ const struct iovec *iov = iter_iov(i);
+ while (1) {
+ size_t n = (--iov)->iov_len;
+ i->nr_segs++;
+ if (unroll <= n) {
+ i->__iov = iov;
+ i->iov_offset = n - unroll;
+ return;
+ }
+ unroll -= n;
+ }
+}
+EXPORT_SYMBOL(iov_iter_revert);
+
+/*
+ * Return the count of just the current iov_iter segment.
+ */
+size_t iov_iter_single_seg_count(const struct iov_iter *i)
+{
+ if (i->nr_segs > 1) {
+ if (likely(iter_is_iovec(i) || iov_iter_is_kvec(i)))
+ return min(i->count, iter_iov(i)->iov_len - i->iov_offset);
+ }
+ return i->count;
+}
+EXPORT_SYMBOL(iov_iter_single_seg_count);
+
+void iov_iter_kvec(struct iov_iter *i, unsigned int direction,
+ const struct kvec *kvec, unsigned long nr_segs,
+ size_t count)
+{
+ *i = (struct iov_iter){
+ .iter_type = ITER_KVEC,
+ .data_source = direction,
+ .kvec = kvec,
+ .nr_segs = nr_segs,
+ .iov_offset = 0,
+ .count = count
+ };
+}
+EXPORT_SYMBOL(iov_iter_kvec);
+
+static bool iov_iter_aligned_iovec(const struct iov_iter *i, unsigned addr_mask,
+ unsigned len_mask)
+{
+ const struct iovec *iov = iter_iov(i);
+ size_t size = i->count;
+ size_t skip = i->iov_offset;
+
+ do {
+ size_t len = iov->iov_len - skip;
+
+ if (len > size)
+ len = size;
+ if (len & len_mask)
+ return false;
+ if ((unsigned long)(iov->iov_base + skip) & addr_mask)
+ return false;
+
+ iov++;
+ size -= len;
+ skip = 0;
+ } while (size);
+
+ return true;
+}
+
+/**
+ * iov_iter_is_aligned() - Check if the addresses and lengths of each segments
+ * are aligned to the parameters.
+ *
+ * @i: &struct iov_iter to restore
+ * @addr_mask: bit mask to check against the iov element's addresses
+ * @len_mask: bit mask to check against the iov element's lengths
+ *
+ * Return: false if any addresses or lengths intersect with the provided masks
+ */
+bool iov_iter_is_aligned(const struct iov_iter *i, unsigned addr_mask,
+ unsigned len_mask)
+{
+ if (likely(iter_is_iovec(i) || iov_iter_is_kvec(i)))
+ return iov_iter_aligned_iovec(i, addr_mask, len_mask);
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(iov_iter_is_aligned);
+
+static unsigned long iov_iter_alignment_iovec(const struct iov_iter *i)
+{
+ const struct iovec *iov = iter_iov(i);
+ unsigned long res = 0;
+ size_t size = i->count;
+ size_t skip = i->iov_offset;
+
+ do {
+ size_t len = iov->iov_len - skip;
+ if (len) {
+ res |= (unsigned long)iov->iov_base + skip;
+ if (len > size)
+ len = size;
+ res |= len;
+ size -= len;
+ }
+ iov++;
+ skip = 0;
+ } while (size);
+ return res;
+}
+
+unsigned long iov_iter_alignment(const struct iov_iter *i)
+{
+ /* iovec and kvec have identical layouts */
+ if (likely(iter_is_iovec(i) || iov_iter_is_kvec(i)))
+ return iov_iter_alignment_iovec(i);
+
+ return 0;
+}
+EXPORT_SYMBOL(iov_iter_alignment);
+
+size_t copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i)
+{
+ if (WARN_ON_ONCE(i->data_source))
+ return 0;
+ return iterate_and_advance(i, bytes, (void *)addr,
+ copy_to_user_iter, memcpy_to_iter);
+}
+EXPORT_SYMBOL(copy_to_iter);
+
+size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i)
+{
+ if (WARN_ON_ONCE(!i->data_source))
+ return 0;
+
+ return iterate_and_advance(i, bytes, addr,
+ copy_from_user_iter, memcpy_from_iter);
+}
+EXPORT_SYMBOL(copy_from_iter);
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 05/10] lib: add parser code for mount options
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (3 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 04/10] lib: add iov_iter I/O vector iterator support Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 06/10] include: add definitions for UID/GID/DEV Ahmad Fatoum
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This is going to be used in the 9PFS code, but could be retrofitted to
other file systems as well in future.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
include/linux/parser.h | 42 +++++
lib/Makefile | 1 +
lib/parser.c | 363 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 406 insertions(+)
create mode 100644 include/linux/parser.h
create mode 100644 lib/parser.c
diff --git a/include/linux/parser.h b/include/linux/parser.h
new file mode 100644
index 000000000000..3c3185ddbad6
--- /dev/null
+++ b/include/linux/parser.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/include/linux/parser.h
+ *
+ * Header for lib/parser.c
+ * Intended use of these functions is parsing filesystem argument lists,
+ * but could potentially be used anywhere else that simple option=arg
+ * parsing is required.
+ */
+#ifndef _LINUX_PARSER_H
+#define _LINUX_PARSER_H
+
+#include <linux/types.h>
+
+/* associates an integer enumerator with a pattern string. */
+struct match_token {
+ int token;
+ const char *pattern;
+};
+
+typedef struct match_token match_table_t[];
+
+/* Maximum number of arguments that match_token will find in a pattern */
+enum {MAX_OPT_ARGS = 3};
+
+/* Describe the location within a string of a substring */
+typedef struct {
+ char *from;
+ char *to;
+} substring_t;
+
+int match_token(char *, const match_table_t table, substring_t args[]);
+int match_int(substring_t *, int *result);
+int match_uint(substring_t *s, unsigned int *result);
+int match_u64(substring_t *, u64 *result);
+int match_octal(substring_t *, int *result);
+int match_hex(substring_t *, int *result);
+bool match_wildcard(const char *pattern, const char *str);
+size_t match_strlcpy(char *, const substring_t *, size_t);
+char *match_strdup(const substring_t *);
+
+#endif /* _LINUX_PARSER_H */
diff --git a/lib/Makefile b/lib/Makefile
index a1ca2803a095..468383508f17 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -32,6 +32,7 @@ obj-y += recursive_action.o
obj-y += make_directory.o
obj-y += arith.o
obj-$(CONFIG_IDR) += idr.o
+obj-y += parser.o
obj-y += iov_iter.o
obj-y += math/
obj-y += uuid.o
diff --git a/lib/parser.c b/lib/parser.c
new file mode 100644
index 000000000000..83c49352667c
--- /dev/null
+++ b/lib/parser.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * lib/parser.c - simple parser for mount, etc. options.
+ */
+
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/kstrtox.h>
+#include <linux/parser.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+/*
+ * max size needed by different bases to express U64
+ * HEX: "0xFFFFFFFFFFFFFFFF" --> 18
+ * DEC: "18446744073709551615" --> 20
+ * OCT: "01777777777777777777777" --> 23
+ * pick the max one to define NUMBER_BUF_LEN
+ */
+#define NUMBER_BUF_LEN 24
+
+/**
+ * match_one - Determines if a string matches a simple pattern
+ * @s: the string to examine for presence of the pattern
+ * @p: the string containing the pattern
+ * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match
+ * locations.
+ *
+ * Description: Determines if the pattern @p is present in string @s. Can only
+ * match extremely simple token=arg style patterns. If the pattern is found,
+ * the location(s) of the arguments will be returned in the @args array.
+ */
+static int match_one(char *s, const char *p, substring_t args[])
+{
+ const char *meta;
+ int argc = 0;
+
+ if (!p)
+ return 1;
+
+ while(1) {
+ int len = -1;
+ meta = strchr(p, '%');
+ if (!meta)
+ return strcmp(p, s) == 0;
+
+ if (strncmp(p, s, meta-p))
+ return 0;
+
+ s += meta - p;
+ p = meta + 1;
+
+ if (isdigit(*p))
+ len = simple_strtoul(p, (char **) &p, 10);
+ else if (*p == '%') {
+ if (*s++ != '%')
+ return 0;
+ p++;
+ continue;
+ }
+
+ if (argc >= MAX_OPT_ARGS)
+ return 0;
+
+ args[argc].from = s;
+ switch (*p++) {
+ case 's': {
+ size_t str_len = strlen(s);
+
+ if (str_len == 0)
+ return 0;
+ if (len == -1 || len > str_len)
+ len = str_len;
+ args[argc].to = s + len;
+ break;
+ }
+ case 'd':
+ simple_strtol(s, &args[argc].to, 0);
+ goto num;
+ case 'u':
+ simple_strtoul(s, &args[argc].to, 0);
+ goto num;
+ case 'o':
+ simple_strtoul(s, &args[argc].to, 8);
+ goto num;
+ case 'x':
+ simple_strtoul(s, &args[argc].to, 16);
+ num:
+ if (args[argc].to == args[argc].from)
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+ s = args[argc].to;
+ argc++;
+ }
+}
+
+/**
+ * match_token - Find a token (and optional args) in a string
+ * @s: the string to examine for token/argument pairs
+ * @table: match_table_t describing the set of allowed option tokens and the
+ * arguments that may be associated with them. Must be terminated with a
+ * &struct match_token whose pattern is set to the NULL pointer.
+ * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match
+ * locations.
+ *
+ * Description: Detects which if any of a set of token strings has been passed
+ * to it. Tokens can include up to %MAX_OPT_ARGS instances of basic c-style
+ * format identifiers which will be taken into account when matching the
+ * tokens, and whose locations will be returned in the @args array.
+ */
+int match_token(char *s, const match_table_t table, substring_t args[])
+{
+ const struct match_token *p;
+
+ for (p = table; !match_one(s, p->pattern, args) ; p++)
+ ;
+
+ return p->token;
+}
+EXPORT_SYMBOL(match_token);
+
+/**
+ * match_number - scan a number in the given base from a substring_t
+ * @s: substring to be scanned
+ * @result: resulting integer on success
+ * @base: base to use when converting string
+ *
+ * Description: Given a &substring_t and a base, attempts to parse the substring
+ * as a number in that base.
+ *
+ * Return: On success, sets @result to the integer represented by the
+ * string and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+static int match_number(substring_t *s, int *result, int base)
+{
+ char *endp;
+ char buf[NUMBER_BUF_LEN];
+ int ret;
+ long val;
+
+ if (match_strlcpy(buf, s, NUMBER_BUF_LEN) >= NUMBER_BUF_LEN)
+ return -ERANGE;
+ ret = 0;
+ val = simple_strtol(buf, &endp, base);
+ if (endp == buf)
+ ret = -EINVAL;
+ else if (val < (long)INT_MIN || val > (long)INT_MAX)
+ ret = -ERANGE;
+ else
+ *result = (int) val;
+ return ret;
+}
+
+/**
+ * match_u64int - scan a number in the given base from a substring_t
+ * @s: substring to be scanned
+ * @result: resulting u64 on success
+ * @base: base to use when converting string
+ *
+ * Description: Given a &substring_t and a base, attempts to parse the substring
+ * as a number in that base.
+ *
+ * Return: On success, sets @result to the integer represented by the
+ * string and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+static int match_u64int(substring_t *s, u64 *result, int base)
+{
+ char buf[NUMBER_BUF_LEN];
+ int ret;
+ u64 val;
+
+ if (match_strlcpy(buf, s, NUMBER_BUF_LEN) >= NUMBER_BUF_LEN)
+ return -ERANGE;
+ ret = kstrtoull(buf, base, &val);
+ if (!ret)
+ *result = val;
+ return ret;
+}
+
+/**
+ * match_int - scan a decimal representation of an integer from a substring_t
+ * @s: substring_t to be scanned
+ * @result: resulting integer on success
+ *
+ * Description: Attempts to parse the &substring_t @s as a decimal integer.
+ *
+ * Return: On success, sets @result to the integer represented by the string
+ * and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+int match_int(substring_t *s, int *result)
+{
+ return match_number(s, result, 0);
+}
+EXPORT_SYMBOL(match_int);
+
+/**
+ * match_uint - scan a decimal representation of an integer from a substring_t
+ * @s: substring_t to be scanned
+ * @result: resulting integer on success
+ *
+ * Description: Attempts to parse the &substring_t @s as a decimal integer.
+ *
+ * Return: On success, sets @result to the integer represented by the string
+ * and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+int match_uint(substring_t *s, unsigned int *result)
+{
+ char buf[NUMBER_BUF_LEN];
+
+ if (match_strlcpy(buf, s, NUMBER_BUF_LEN) >= NUMBER_BUF_LEN)
+ return -ERANGE;
+
+ return kstrtouint(buf, 10, result);
+}
+EXPORT_SYMBOL(match_uint);
+
+/**
+ * match_u64 - scan a decimal representation of a u64 from
+ * a substring_t
+ * @s: substring_t to be scanned
+ * @result: resulting unsigned long long on success
+ *
+ * Description: Attempts to parse the &substring_t @s as a long decimal
+ * integer.
+ *
+ * Return: On success, sets @result to the integer represented by the string
+ * and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+int match_u64(substring_t *s, u64 *result)
+{
+ return match_u64int(s, result, 0);
+}
+EXPORT_SYMBOL(match_u64);
+
+/**
+ * match_octal - scan an octal representation of an integer from a substring_t
+ * @s: substring_t to be scanned
+ * @result: resulting integer on success
+ *
+ * Description: Attempts to parse the &substring_t @s as an octal integer.
+ *
+ * Return: On success, sets @result to the integer represented by the string
+ * and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+int match_octal(substring_t *s, int *result)
+{
+ return match_number(s, result, 8);
+}
+EXPORT_SYMBOL(match_octal);
+
+/**
+ * match_hex - scan a hex representation of an integer from a substring_t
+ * @s: substring_t to be scanned
+ * @result: resulting integer on success
+ *
+ * Description: Attempts to parse the &substring_t @s as a hexadecimal integer.
+ *
+ * Return: On success, sets @result to the integer represented by the string
+ * and returns 0. Returns -EINVAL or -ERANGE on failure.
+ */
+int match_hex(substring_t *s, int *result)
+{
+ return match_number(s, result, 16);
+}
+EXPORT_SYMBOL(match_hex);
+
+/**
+ * match_wildcard - parse if a string matches given wildcard pattern
+ * @pattern: wildcard pattern
+ * @str: the string to be parsed
+ *
+ * Description: Parse the string @str to check if matches wildcard
+ * pattern @pattern. The pattern may contain two types of wildcards:
+ * '*' - matches zero or more characters
+ * '?' - matches one character
+ *
+ * Return: If the @str matches the @pattern, return true, else return false.
+ */
+bool match_wildcard(const char *pattern, const char *str)
+{
+ const char *s = str;
+ const char *p = pattern;
+ bool star = false;
+
+ while (*s) {
+ switch (*p) {
+ case '?':
+ s++;
+ p++;
+ break;
+ case '*':
+ star = true;
+ str = s;
+ if (!*++p)
+ return true;
+ pattern = p;
+ break;
+ default:
+ if (*s == *p) {
+ s++;
+ p++;
+ } else {
+ if (!star)
+ return false;
+ str++;
+ s = str;
+ p = pattern;
+ }
+ break;
+ }
+ }
+
+ if (*p == '*')
+ ++p;
+ return !*p;
+}
+EXPORT_SYMBOL(match_wildcard);
+
+/**
+ * match_strlcpy - Copy the characters from a substring_t to a sized buffer
+ * @dest: where to copy to
+ * @src: &substring_t to copy
+ * @size: size of destination buffer
+ *
+ * Description: Copy the characters in &substring_t @src to the
+ * c-style string @dest. Copy no more than @size - 1 characters, plus
+ * the terminating NUL.
+ *
+ * Return: length of @src.
+ */
+size_t match_strlcpy(char *dest, const substring_t *src, size_t size)
+{
+ size_t ret = src->to - src->from;
+
+ if (size) {
+ size_t len = ret >= size ? size - 1 : ret;
+ memcpy(dest, src->from, len);
+ dest[len] = '\0';
+ }
+ return ret;
+}
+EXPORT_SYMBOL(match_strlcpy);
+
+/**
+ * match_strdup - allocate a new string with the contents of a substring_t
+ * @s: &substring_t to copy
+ *
+ * Description: Allocates and returns a string filled with the contents of
+ * the &substring_t @s. The caller is responsible for freeing the returned
+ * string with kfree().
+ *
+ * Return: the address of the newly allocated NUL-terminated string or
+ * %NULL on error.
+ */
+char *match_strdup(const substring_t *s)
+{
+ return memdup_nul(s->from, s->to - s->from);
+}
+EXPORT_SYMBOL(match_strdup);
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 06/10] include: add definitions for UID/GID/DEV
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (4 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 05/10] lib: add parser code for mount options Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 07/10] net: add support for 9P protocol Ahmad Fatoum
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
We do not care for these fields, but they simplify porting kernel file
systems, so add helpers for them.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
include/linux/kdev_t.h | 22 ++++++++++++++
include/linux/uidgid.h | 59 ++++++++++++++++++++++++++++++++++++
include/linux/uidgid_types.h | 15 +++++++++
3 files changed, 96 insertions(+)
create mode 100644 include/linux/kdev_t.h
create mode 100644 include/linux/uidgid.h
create mode 100644 include/linux/uidgid_types.h
diff --git a/include/linux/kdev_t.h b/include/linux/kdev_t.h
new file mode 100644
index 000000000000..2919eb6bde82
--- /dev/null
+++ b/include/linux/kdev_t.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_KDEV_T_H
+#define _LINUX_KDEV_T_H
+
+#include <linux/compiler.h>
+
+#define MINORBITS 20
+#define MINORMASK ((1U << MINORBITS) - 1)
+
+#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
+#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
+#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
+
+static __always_inline dev_t new_decode_dev(u32 dev)
+{
+ unsigned major = (dev & 0xfff00) >> 8;
+ unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
+ return MKDEV(major, minor);
+}
+
+
+#endif
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
new file mode 100644
index 000000000000..7589e77b325f
--- /dev/null
+++ b/include/linux/uidgid.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_UIDGID_H
+#define _LINUX_UIDGID_H
+
+/*
+ * A set of types for the internal kernel types representing uids and gids.
+ *
+ * The types defined in this header allow distinguishing which uids and gids in
+ * the kernel are values used by userspace and which uid and gid values are
+ * the internal kernel values. With the addition of user namespaces the values
+ * can be different. Using the type system makes it possible for the compiler
+ * to detect when we overlook these differences.
+ *
+ */
+#include <linux/uidgid_types.h>
+
+#define KUIDT_INIT(value) (kuid_t){ value }
+#define KGIDT_INIT(value) (kgid_t){ value }
+
+#define make_kuid(uid) KUIDT_INIT(uid)
+#define make_kgid(gid) KGIDT_INIT(gid)
+
+#define GLOBAL_ROOT_UID KUIDT_INIT(0)
+#define GLOBAL_ROOT_GID KGIDT_INIT(0)
+
+#define INVALID_UID KUIDT_INIT(-1)
+#define INVALID_GID KGIDT_INIT(-1)
+
+static inline uid_t __kuid_val(kuid_t uid)
+{
+ return 0;
+}
+
+static inline gid_t __kgid_val(kgid_t gid)
+{
+ return 0;
+}
+
+static inline uid_t from_kuid(kuid_t kuid)
+{
+ return __kuid_val(kuid);
+}
+
+static inline gid_t from_kgid(kgid_t kgid)
+{
+ return __kgid_val(kgid);
+}
+
+static inline bool uid_eq(kuid_t left, kuid_t right)
+{
+ return __kuid_val(left) == __kuid_val(right);
+}
+
+static inline bool gid_eq(kgid_t left, kgid_t right)
+{
+ return __kgid_val(left) == __kgid_val(right);
+}
+
+#endif /* _LINUX_UIDGID_H */
diff --git a/include/linux/uidgid_types.h b/include/linux/uidgid_types.h
new file mode 100644
index 000000000000..b35ac4955a33
--- /dev/null
+++ b/include/linux/uidgid_types.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_UIDGID_TYPES_H
+#define _LINUX_UIDGID_TYPES_H
+
+#include <linux/types.h>
+
+typedef struct {
+ uid_t val;
+} kuid_t;
+
+typedef struct {
+ gid_t val;
+} kgid_t;
+
+#endif /* _LINUX_UIDGID_TYPES_H */
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 07/10] net: add support for 9P protocol
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (5 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 06/10] include: add definitions for UID/GID/DEV Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 08/10] fs: add new 9P2000.l (Plan 9) File system support Ahmad Fatoum
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
The Plan 9 Filesystem Protocol is a network protocol developed for the
Plan 9 from Bell Labs distributed operating system.
It's most common transports are via TCP and VirtFS. Linux recently
gained support for it over USB gadget as well.
As we have no TCP support in barebox yet, add here support for the
file system and Virt I/O transport with usb9pfs to follow.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
include/linux/completion.h | 3 +
include/linux/gfp.h | 2 +
include/linux/idr.h | 3 +
include/net/9p/9p.h | 559 +++++++++
include/net/9p/client.h | 297 +++++
include/net/9p/transport.h | 73 ++
include/printf.h | 11 +
include/uapi/linux/virtio_9p.h | 44 +
net/9p/Kconfig | 32 +
net/9p/Makefile | 11 +
net/9p/client.c | 1932 ++++++++++++++++++++++++++++++++
net/9p/mod.c | 180 +++
net/9p/protocol.c | 802 +++++++++++++
net/9p/protocol.h | 19 +
net/9p/trans_virtio.c | 426 +++++++
net/Kconfig | 2 +
net/Makefile | 1 +
17 files changed, 4397 insertions(+)
create mode 100644 include/net/9p/9p.h
create mode 100644 include/net/9p/client.h
create mode 100644 include/net/9p/transport.h
create mode 100644 include/uapi/linux/virtio_9p.h
create mode 100644 net/9p/Kconfig
create mode 100644 net/9p/Makefile
create mode 100644 net/9p/client.c
create mode 100644 net/9p/mod.c
create mode 100644 net/9p/protocol.c
create mode 100644 net/9p/protocol.h
create mode 100644 net/9p/trans_virtio.c
diff --git a/include/linux/completion.h b/include/linux/completion.h
index e897e4f65b8b..a7b5c064e411 100644
--- a/include/linux/completion.h
+++ b/include/linux/completion.h
@@ -42,6 +42,9 @@ static inline int wait_for_completion_interruptible(struct completion *x)
return 0;
}
+#define wait_for_completion_killable \
+ wait_for_completion_interruptible
+
static inline bool completion_done(struct completion *x)
{
return x->done;
diff --git a/include/linux/gfp.h b/include/linux/gfp.h
index 799c2e461278..b2f0f48ee8f6 100644
--- a/include/linux/gfp.h
+++ b/include/linux/gfp.h
@@ -7,6 +7,8 @@
#define GFP_KERNEL 0
#define GFP_NOFS 0
#define GFP_USER 0
+#define GFP_ATOMIC 0
+#define GFP_NOWAIT 0
#define __GFP_NOWARN 0
#endif
diff --git a/include/linux/idr.h b/include/linux/idr.h
index 8573a2b05ee6..07adde713477 100644
--- a/include/linux/idr.h
+++ b/include/linux/idr.h
@@ -59,6 +59,9 @@ static inline void *idr_find(struct idr *head, int id)
return idr ? idr->ptr : NULL;
}
+static inline void idr_preload(gfp_t gfp_mask) {}
+static inline void idr_preload_end(void) {}
+
int idr_alloc(struct idr *, void *ptr, int start, int end, gfp_t);
int __must_check idr_alloc_u32(struct idr *, void *ptr, u32 *id,
unsigned long max, gfp_t);
diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h
new file mode 100644
index 000000000000..edf61c4117a4
--- /dev/null
+++ b/include/net/9p/9p.h
@@ -0,0 +1,559 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * 9P protocol definitions.
+ *
+ * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net>
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#ifndef NET_9P_H
+#define NET_9P_H
+
+#include <linux/types.h>
+#include <linux/uidgid_types.h>
+
+/**
+ * enum p9_debug_flags - bits for mount time debug parameter
+ * @P9_DEBUG_ERROR: more verbose error messages including original error string
+ * @P9_DEBUG_9P: 9P protocol tracing
+ * @P9_DEBUG_VFS: VFS API tracing
+ * @P9_DEBUG_CONV: protocol conversion tracing
+ * @P9_DEBUG_MUX: trace management of concurrent transactions
+ * @P9_DEBUG_TRANS: transport tracing
+ * @P9_DEBUG_SLABS: memory management tracing
+ * @P9_DEBUG_FCALL: verbose dump of protocol messages
+ * @P9_DEBUG_FID: fid allocation/deallocation tracking
+ * @P9_DEBUG_PKT: packet marshalling/unmarshalling
+ * @P9_DEBUG_FSC: FS-cache tracing
+ * @P9_DEBUG_VPKT: Verbose packet debugging (full packet dump)
+ *
+ * These flags are passed at mount time to turn on various levels of
+ * verbosity and tracing which will be output to the system logs.
+ */
+
+enum p9_debug_flags {
+ P9_DEBUG_ERROR = (1<<0),
+ P9_DEBUG_9P = (1<<2),
+ P9_DEBUG_VFS = (1<<3),
+ P9_DEBUG_CONV = (1<<4),
+ P9_DEBUG_MUX = (1<<5),
+ P9_DEBUG_TRANS = (1<<6),
+ P9_DEBUG_SLABS = (1<<7),
+ P9_DEBUG_FCALL = (1<<8),
+ P9_DEBUG_FID = (1<<9),
+ P9_DEBUG_PKT = (1<<10),
+ P9_DEBUG_FSC = (1<<11),
+ P9_DEBUG_VPKT = (1<<12),
+ P9_DEBUG_CACHE = (1<<13),
+ P9_DEBUG_MMAP = (1<<14),
+};
+
+#ifdef CONFIG_NET_9P_DEBUG
+extern unsigned int p9_debug_level;
+__printf(3, 4)
+void _p9_debug(enum p9_debug_flags level, const char *func,
+ const char *fmt, ...);
+#define p9_debug(level, fmt, ...) \
+ _p9_debug(level, __func__, fmt, ##__VA_ARGS__)
+#else
+#define p9_debug(level, fmt, ...) \
+ no_printk(fmt, ##__VA_ARGS__)
+#endif
+
+/**
+ * enum p9_msg_t - 9P message types
+ * @P9_TLERROR: not used
+ * @P9_RLERROR: response for any failed request for 9P2000.L
+ * @P9_TSTATFS: file system status request
+ * @P9_RSTATFS: file system status response
+ * @P9_TSYMLINK: make symlink request
+ * @P9_RSYMLINK: make symlink response
+ * @P9_TMKNOD: create a special file object request
+ * @P9_RMKNOD: create a special file object response
+ * @P9_TLCREATE: prepare a handle for I/O on an new file for 9P2000.L
+ * @P9_RLCREATE: response with file access information for 9P2000.L
+ * @P9_TRENAME: rename request
+ * @P9_RRENAME: rename response
+ * @P9_TMKDIR: create a directory request
+ * @P9_RMKDIR: create a directory response
+ * @P9_TVERSION: version handshake request
+ * @P9_RVERSION: version handshake response
+ * @P9_TAUTH: request to establish authentication channel
+ * @P9_RAUTH: response with authentication information
+ * @P9_TATTACH: establish user access to file service
+ * @P9_RATTACH: response with top level handle to file hierarchy
+ * @P9_TERROR: not used
+ * @P9_RERROR: response for any failed request
+ * @P9_TFLUSH: request to abort a previous request
+ * @P9_RFLUSH: response when previous request has been cancelled
+ * @P9_TWALK: descend a directory hierarchy
+ * @P9_RWALK: response with new handle for position within hierarchy
+ * @P9_TOPEN: prepare a handle for I/O on an existing file
+ * @P9_ROPEN: response with file access information
+ * @P9_TCREATE: prepare a handle for I/O on a new file
+ * @P9_RCREATE: response with file access information
+ * @P9_TREAD: request to transfer data from a file or directory
+ * @P9_RREAD: response with data requested
+ * @P9_TWRITE: reuqest to transfer data to a file
+ * @P9_RWRITE: response with out much data was transferred to file
+ * @P9_TCLUNK: forget about a handle to an entity within the file system
+ * @P9_RCLUNK: response when server has forgotten about the handle
+ * @P9_TREMOVE: request to remove an entity from the hierarchy
+ * @P9_RREMOVE: response when server has removed the entity
+ * @P9_TSTAT: request file entity attributes
+ * @P9_RSTAT: response with file entity attributes
+ * @P9_TWSTAT: request to update file entity attributes
+ * @P9_RWSTAT: response when file entity attributes are updated
+ *
+ * There are 14 basic operations in 9P2000, paired as
+ * requests and responses. The one special case is ERROR
+ * as there is no @P9_TERROR request for clients to transmit to
+ * the server, but the server may respond to any other request
+ * with an @P9_RERROR.
+ *
+ * See Also: http://plan9.bell-labs.com/sys/man/5/INDEX.html
+ */
+
+enum p9_msg_t {
+ P9_TLERROR = 6,
+ P9_RLERROR,
+ P9_TSTATFS = 8,
+ P9_RSTATFS,
+ P9_TLOPEN = 12,
+ P9_RLOPEN,
+ P9_TLCREATE = 14,
+ P9_RLCREATE,
+ P9_TSYMLINK = 16,
+ P9_RSYMLINK,
+ P9_TMKNOD = 18,
+ P9_RMKNOD,
+ P9_TRENAME = 20,
+ P9_RRENAME,
+ P9_TREADLINK = 22,
+ P9_RREADLINK,
+ P9_TGETATTR = 24,
+ P9_RGETATTR,
+ P9_TSETATTR = 26,
+ P9_RSETATTR,
+ P9_TXATTRWALK = 30,
+ P9_RXATTRWALK,
+ P9_TXATTRCREATE = 32,
+ P9_RXATTRCREATE,
+ P9_TREADDIR = 40,
+ P9_RREADDIR,
+ P9_TFSYNC = 50,
+ P9_RFSYNC,
+ P9_TLOCK = 52,
+ P9_RLOCK,
+ P9_TGETLOCK = 54,
+ P9_RGETLOCK,
+ P9_TLINK = 70,
+ P9_RLINK,
+ P9_TMKDIR = 72,
+ P9_RMKDIR,
+ P9_TRENAMEAT = 74,
+ P9_RRENAMEAT,
+ P9_TUNLINKAT = 76,
+ P9_RUNLINKAT,
+ P9_TVERSION = 100,
+ P9_RVERSION,
+ P9_TAUTH = 102,
+ P9_RAUTH,
+ P9_TATTACH = 104,
+ P9_RATTACH,
+ P9_TERROR = 106,
+ P9_RERROR,
+ P9_TFLUSH = 108,
+ P9_RFLUSH,
+ P9_TWALK = 110,
+ P9_RWALK,
+ P9_TOPEN = 112,
+ P9_ROPEN,
+ P9_TCREATE = 114,
+ P9_RCREATE,
+ P9_TREAD = 116,
+ P9_RREAD,
+ P9_TWRITE = 118,
+ P9_RWRITE,
+ P9_TCLUNK = 120,
+ P9_RCLUNK,
+ P9_TREMOVE = 122,
+ P9_RREMOVE,
+ P9_TSTAT = 124,
+ P9_RSTAT,
+ P9_TWSTAT = 126,
+ P9_RWSTAT,
+};
+
+/**
+ * enum p9_open_mode_t - 9P open modes
+ * @P9_OREAD: open file for reading only
+ * @P9_OWRITE: open file for writing only
+ * @P9_ORDWR: open file for reading or writing
+ * @P9_OEXEC: open file for execution
+ * @P9_OTRUNC: truncate file to zero-length before opening it
+ * @P9_OREXEC: close the file when an exec(2) system call is made
+ * @P9_ORCLOSE: remove the file when the file is closed
+ * @P9_OAPPEND: open the file and seek to the end
+ * @P9_OEXCL: only create a file, do not open it
+ *
+ * 9P open modes differ slightly from Posix standard modes.
+ * In particular, there are extra modes which specify different
+ * semantic behaviors than may be available on standard Posix
+ * systems. For example, @P9_OREXEC and @P9_ORCLOSE are modes that
+ * most likely will not be issued from the Linux VFS client, but may
+ * be supported by servers.
+ *
+ * See Also: http://plan9.bell-labs.com/magic/man2html/2/open
+ */
+
+enum p9_open_mode_t {
+ P9_OREAD = 0x00,
+ P9_OWRITE = 0x01,
+ P9_ORDWR = 0x02,
+ P9_OEXEC = 0x03,
+ P9_OTRUNC = 0x10,
+ P9_OREXEC = 0x20,
+ P9_ORCLOSE = 0x40,
+ P9_OAPPEND = 0x80,
+ P9_OEXCL = 0x1000,
+ P9L_MODE_MASK = 0x1FFF, /* don't send anything under this to server */
+};
+
+/**
+ * enum p9_perm_t - 9P permissions
+ * @P9_DMDIR: mode bit for directories
+ * @P9_DMAPPEND: mode bit for is append-only
+ * @P9_DMEXCL: mode bit for excluse use (only one open handle allowed)
+ * @P9_DMMOUNT: mode bit for mount points
+ * @P9_DMAUTH: mode bit for authentication file
+ * @P9_DMTMP: mode bit for non-backed-up files
+ * @P9_DMSYMLINK: mode bit for symbolic links (9P2000.u)
+ * @P9_DMLINK: mode bit for hard-link (9P2000.u)
+ * @P9_DMDEVICE: mode bit for device files (9P2000.u)
+ * @P9_DMNAMEDPIPE: mode bit for named pipe (9P2000.u)
+ * @P9_DMSOCKET: mode bit for socket (9P2000.u)
+ * @P9_DMSETUID: mode bit for setuid (9P2000.u)
+ * @P9_DMSETGID: mode bit for setgid (9P2000.u)
+ * @P9_DMSETVTX: mode bit for sticky bit (9P2000.u)
+ *
+ * 9P permissions differ slightly from Posix standard modes.
+ *
+ * See Also: http://plan9.bell-labs.com/magic/man2html/2/stat
+ */
+enum p9_perm_t {
+ P9_DMDIR = 0x80000000,
+ P9_DMAPPEND = 0x40000000,
+ P9_DMEXCL = 0x20000000,
+ P9_DMMOUNT = 0x10000000,
+ P9_DMAUTH = 0x08000000,
+ P9_DMTMP = 0x04000000,
+/* 9P2000.u extensions */
+ P9_DMSYMLINK = 0x02000000,
+ P9_DMLINK = 0x01000000,
+ P9_DMDEVICE = 0x00800000,
+ P9_DMNAMEDPIPE = 0x00200000,
+ P9_DMSOCKET = 0x00100000,
+ P9_DMSETUID = 0x00080000,
+ P9_DMSETGID = 0x00040000,
+ P9_DMSETVTX = 0x00010000,
+};
+
+/* 9p2000.L open flags */
+#define P9_DOTL_RDONLY 00000000
+#define P9_DOTL_WRONLY 00000001
+#define P9_DOTL_RDWR 00000002
+#define P9_DOTL_NOACCESS 00000003
+#define P9_DOTL_CREATE 00000100
+#define P9_DOTL_EXCL 00000200
+#define P9_DOTL_NOCTTY 00000400
+#define P9_DOTL_TRUNC 00001000
+#define P9_DOTL_APPEND 00002000
+#define P9_DOTL_NONBLOCK 00004000
+#define P9_DOTL_DSYNC 00010000
+#define P9_DOTL_FASYNC 00020000
+#define P9_DOTL_DIRECT 00040000
+#define P9_DOTL_LARGEFILE 00100000
+#define P9_DOTL_DIRECTORY 00200000
+#define P9_DOTL_NOFOLLOW 00400000
+#define P9_DOTL_NOATIME 01000000
+#define P9_DOTL_CLOEXEC 02000000
+#define P9_DOTL_SYNC 04000000
+
+/* 9p2000.L at flags */
+#define P9_DOTL_AT_REMOVEDIR 0x200
+
+/* 9p2000.L lock type */
+#define P9_LOCK_TYPE_RDLCK 0
+#define P9_LOCK_TYPE_WRLCK 1
+#define P9_LOCK_TYPE_UNLCK 2
+
+/**
+ * enum p9_qid_t - QID types
+ * @P9_QTDIR: directory
+ * @P9_QTAPPEND: append-only
+ * @P9_QTEXCL: excluse use (only one open handle allowed)
+ * @P9_QTMOUNT: mount points
+ * @P9_QTAUTH: authentication file
+ * @P9_QTTMP: non-backed-up files
+ * @P9_QTSYMLINK: symbolic links (9P2000.u)
+ * @P9_QTLINK: hard-link (9P2000.u)
+ * @P9_QTFILE: normal files
+ *
+ * QID types are a subset of permissions - they are primarily
+ * used to differentiate semantics for a file system entity via
+ * a jump-table. Their value is also the most significant 16 bits
+ * of the permission_t
+ *
+ * See Also: http://plan9.bell-labs.com/magic/man2html/2/stat
+ */
+enum p9_qid_t {
+ P9_QTDIR = 0x80,
+ P9_QTAPPEND = 0x40,
+ P9_QTEXCL = 0x20,
+ P9_QTMOUNT = 0x10,
+ P9_QTAUTH = 0x08,
+ P9_QTTMP = 0x04,
+ P9_QTSYMLINK = 0x02,
+ P9_QTLINK = 0x01,
+ P9_QTFILE = 0x00,
+};
+
+/* 9P Magic Numbers */
+#define P9_NOTAG ((u16)(~0))
+#define P9_NOFID ((u32)(~0))
+#define P9_MAXWELEM 16
+
+/* Minimal header size: size[4] type[1] tag[2] */
+#define P9_HDRSZ 7
+
+/* ample room for Twrite/Rread header */
+#define P9_IOHDRSZ 24
+
+/* Room for readdir header */
+#define P9_READDIRHDRSZ 24
+
+/* size of header for zero copy read/write */
+#define P9_ZC_HDR_SZ 4096
+
+/* maximum length of an error string */
+#define P9_ERRMAX 128
+
+/**
+ * struct p9_qid - file system entity information
+ * @type: 8-bit type &p9_qid_t
+ * @version: 16-bit monotonically incrementing version number
+ * @path: 64-bit per-server-unique ID for a file system element
+ *
+ * qids are identifiers used by 9P servers to track file system
+ * entities. The type is used to differentiate semantics for operations
+ * on the entity (ie. read means something different on a directory than
+ * on a file). The path provides a server unique index for an entity
+ * (roughly analogous to an inode number), while the version is updated
+ * every time a file is modified and can be used to maintain cache
+ * coherency between clients and serves.
+ * Servers will often differentiate purely synthetic entities by setting
+ * their version to 0, signaling that they should never be cached and
+ * should be accessed synchronously.
+ *
+ * See Also://plan9.bell-labs.com/magic/man2html/2/stat
+ */
+
+struct p9_qid {
+ u8 type;
+ u32 version;
+ u64 path;
+};
+
+/**
+ * struct p9_wstat - file system metadata information
+ * @size: length prefix for this stat structure instance
+ * @type: the type of the server (equivalent to a major number)
+ * @dev: the sub-type of the server (equivalent to a minor number)
+ * @qid: unique id from the server of type &p9_qid
+ * @mode: Plan 9 format permissions of type &p9_perm_t
+ * @atime: Last access/read time
+ * @mtime: Last modify/write time
+ * @length: file length
+ * @name: last element of path (aka filename)
+ * @uid: owner name
+ * @gid: group owner
+ * @muid: last modifier
+ * @extension: area used to encode extended UNIX support
+ * @n_uid: numeric user id of owner (part of 9p2000.u extension)
+ * @n_gid: numeric group id (part of 9p2000.u extension)
+ * @n_muid: numeric user id of laster modifier (part of 9p2000.u extension)
+ *
+ * See Also: http://plan9.bell-labs.com/magic/man2html/2/stat
+ */
+
+struct p9_wstat {
+ u16 size;
+ u16 type;
+ u32 dev;
+ struct p9_qid qid;
+ u32 mode;
+ u32 atime;
+ u32 mtime;
+ u64 length;
+ const char *name;
+ const char *uid;
+ const char *gid;
+ const char *muid;
+ char *extension; /* 9p2000.u extensions */
+ kuid_t n_uid; /* 9p2000.u extensions */
+ kgid_t n_gid; /* 9p2000.u extensions */
+ kuid_t n_muid; /* 9p2000.u extensions */
+};
+
+struct p9_stat_dotl {
+ u64 st_result_mask;
+ struct p9_qid qid;
+ u32 st_mode;
+ kuid_t st_uid;
+ kgid_t st_gid;
+ u64 st_nlink;
+ u64 st_rdev;
+ u64 st_size;
+ u64 st_blksize;
+ u64 st_blocks;
+ u64 st_atime_sec;
+ u64 st_atime_nsec;
+ u64 st_mtime_sec;
+ u64 st_mtime_nsec;
+ u64 st_ctime_sec;
+ u64 st_ctime_nsec;
+ u64 st_btime_sec;
+ u64 st_btime_nsec;
+ u64 st_gen;
+ u64 st_data_version;
+};
+
+#define P9_STATS_MODE 0x00000001ULL
+#define P9_STATS_NLINK 0x00000002ULL
+#define P9_STATS_UID 0x00000004ULL
+#define P9_STATS_GID 0x00000008ULL
+#define P9_STATS_RDEV 0x00000010ULL
+#define P9_STATS_ATIME 0x00000020ULL
+#define P9_STATS_MTIME 0x00000040ULL
+#define P9_STATS_CTIME 0x00000080ULL
+#define P9_STATS_INO 0x00000100ULL
+#define P9_STATS_SIZE 0x00000200ULL
+#define P9_STATS_BLOCKS 0x00000400ULL
+
+#define P9_STATS_BTIME 0x00000800ULL
+#define P9_STATS_GEN 0x00001000ULL
+#define P9_STATS_DATA_VERSION 0x00002000ULL
+
+#define P9_STATS_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */
+#define P9_STATS_ALL 0x00003fffULL /* Mask for All fields above */
+
+/**
+ * struct p9_iattr_dotl - P9 inode attribute for setattr
+ * @valid: bitfield specifying which fields are valid
+ * same as in struct iattr
+ * @mode: File permission bits
+ * @uid: user id of owner
+ * @gid: group id
+ * @size: File size
+ * @atime_sec: Last access time, seconds
+ * @atime_nsec: Last access time, nanoseconds
+ * @mtime_sec: Last modification time, seconds
+ * @mtime_nsec: Last modification time, nanoseconds
+ */
+
+struct p9_iattr_dotl {
+ u32 valid;
+ u32 mode;
+ kuid_t uid;
+ kgid_t gid;
+ u64 size;
+ u64 atime_sec;
+ u64 atime_nsec;
+ u64 mtime_sec;
+ u64 mtime_nsec;
+};
+
+#define P9_LOCK_SUCCESS 0
+#define P9_LOCK_BLOCKED 1
+#define P9_LOCK_ERROR 2
+#define P9_LOCK_GRACE 3
+
+#define P9_LOCK_FLAGS_BLOCK 1
+#define P9_LOCK_FLAGS_RECLAIM 2
+
+/* struct p9_flock: POSIX lock structure
+ * @type - type of lock
+ * @flags - lock flags
+ * @start - starting offset of the lock
+ * @length - number of bytes
+ * @proc_id - process id which wants to take lock
+ * @client_id - client id
+ */
+
+struct p9_flock {
+ u8 type;
+ u32 flags;
+ u64 start;
+ u64 length;
+ u32 proc_id;
+ char *client_id;
+};
+
+/* struct p9_getlock: getlock structure
+ * @type - type of lock
+ * @start - starting offset of the lock
+ * @length - number of bytes
+ * @proc_id - process id which wants to take lock
+ * @client_id - client id
+ */
+
+struct p9_getlock {
+ u8 type;
+ u64 start;
+ u64 length;
+ u32 proc_id;
+ char *client_id;
+};
+
+struct p9_rstatfs {
+ u32 type;
+ u32 bsize;
+ u64 blocks;
+ u64 bfree;
+ u64 bavail;
+ u64 files;
+ u64 ffree;
+ u64 fsid;
+ u32 namelen;
+};
+
+/**
+ * struct p9_fcall - primary packet structure
+ * @size: prefixed length of the structure
+ * @id: protocol operating identifier of type &p9_msg_t
+ * @tag: transaction id of the request
+ * @offset: used by marshalling routines to track current position in buffer
+ * @capacity: used by marshalling routines to track total malloc'd capacity
+ * @sdata: payload
+ *
+ * &p9_fcall represents the structure for all 9P RPC
+ * transactions. Requests are packaged into fcalls, and reponses
+ * must be extracted from them.
+ *
+ * See Also: http://plan9.bell-labs.com/magic/man2html/2/fcall
+ */
+
+struct p9_fcall {
+ u32 size;
+ u8 id;
+ u16 tag;
+
+ size_t offset;
+ size_t capacity;
+
+ struct kmem_cache *cache;
+ u8 *sdata;
+};
+#endif /* NET_9P_H */
diff --git a/include/net/9p/client.h b/include/net/9p/client.h
new file mode 100644
index 000000000000..aac21bdac07a
--- /dev/null
+++ b/include/net/9p/client.h
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * 9P Client Definitions
+ *
+ * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
+ */
+
+#ifndef NET_9P_CLIENT_H
+#define NET_9P_CLIENT_H
+
+#include <linux/completion.h>
+#include <linux/refcount.h>
+#include <linux/spinlock.h>
+#include <net/9p/9p.h>
+#include <linux/idr.h>
+
+/* Number of requests per row */
+#define P9_ROW_MAXTAG 255
+
+/** enum p9_proto_versions - 9P protocol versions
+ * @p9_proto_legacy: 9P Legacy mode, pre-9P2000.u
+ * @p9_proto_2000u: 9P2000.u extension
+ * @p9_proto_2000L: 9P2000.L extension
+ */
+
+enum p9_proto_versions {
+ p9_proto_legacy,
+ p9_proto_2000u,
+ p9_proto_2000L,
+};
+
+
+/**
+ * enum p9_trans_status - different states of underlying transports
+ * @Connected: transport is connected and healthy
+ * @Disconnected: transport has been disconnected
+ * @Hung: transport is connected by wedged
+ *
+ * This enumeration details the various states a transport
+ * instatiation can be in.
+ */
+
+enum p9_trans_status {
+ Connected,
+ BeginDisconnect,
+ Disconnected,
+ Hung,
+};
+
+/**
+ * enum p9_req_status_t - status of a request
+ * @REQ_STATUS_ALLOC: request has been allocated but not sent
+ * @REQ_STATUS_UNSENT: request waiting to be sent
+ * @REQ_STATUS_SENT: request sent to server
+ * @REQ_STATUS_RCVD: response received from server
+ * @REQ_STATUS_FLSHD: request has been flushed
+ * @REQ_STATUS_ERROR: request encountered an error on the client side
+ */
+
+enum p9_req_status_t {
+ REQ_STATUS_ALLOC,
+ REQ_STATUS_UNSENT,
+ REQ_STATUS_SENT,
+ REQ_STATUS_RCVD,
+ REQ_STATUS_FLSHD,
+ REQ_STATUS_ERROR,
+};
+
+/**
+ * struct p9_req_t - request slots
+ * @status: status of this request slot
+ * @t_err: transport error
+ * @wq: wait_queue for the client to block on for this request
+ * @tc: the request fcall structure
+ * @rc: the response fcall structure
+ * @req_list: link for higher level objects to chain requests
+ */
+struct p9_req_t {
+ int status;
+ int t_err;
+ refcount_t refcount;
+ struct completion completion;
+ struct p9_fcall tc;
+ struct p9_fcall rc;
+ struct list_head req_list;
+};
+
+/**
+ * struct p9_client - per client instance state
+ * @lock: protect @fids and @reqs
+ * @msize: maximum data size negotiated by protocol
+ * @proto_version: 9P protocol version to use
+ * @trans_mod: module API instantiated with this client
+ * @status: connection state
+ * @trans: tranport instance state and API
+ * @fids: All active FID handles
+ * @reqs: All active requests.
+ * @name: node name used as client id
+ *
+ * The client structure is used to keep track of various per-client
+ * state that has been instantiated.
+ */
+struct p9_client {
+ spinlock_t lock;
+ unsigned int msize;
+ unsigned char proto_version;
+ struct p9_trans_module *trans_mod;
+ const char *trans_tag;
+ enum p9_trans_status status;
+ void *trans;
+ struct kmem_cache *fcall_cache;
+
+ union {
+ struct {
+ int rfd;
+ int wfd;
+ } fd;
+ struct {
+ u16 port;
+ bool privport;
+
+ } tcp;
+ } trans_opts;
+
+ struct idr fids;
+ struct idr reqs;
+
+ char name[64 + 1];
+};
+
+/**
+ * struct p9_fid - file system entity handle
+ * @clnt: back pointer to instantiating &p9_client
+ * @fid: numeric identifier for this handle
+ * @mode: current mode of this fid (enum?)
+ * @qid: the &p9_qid server identifier this handle points to
+ * @iounit: the server reported maximum transaction size for this file
+ * @uid: the numeric uid of the local user who owns this handle
+ * @rdir: readdir accounting structure (allocated on demand)
+ * @dlist: per-dentry fid tracking
+ *
+ * TODO: This needs lots of explanation.
+ */
+enum fid_source {
+ FID_FROM_OTHER,
+ FID_FROM_INODE,
+ FID_FROM_DENTRY,
+};
+
+struct p9_fid {
+ struct p9_client *clnt;
+ u32 fid;
+ refcount_t count;
+ int mode;
+ struct p9_qid qid;
+ u32 iounit;
+ kuid_t uid;
+
+ void *rdir;
+
+ struct hlist_node dlist; /* list of all fids attached to a dentry */
+ struct hlist_node ilist;
+};
+
+/**
+ * struct p9_dirent - directory entry structure
+ * @qid: The p9 server qid for this dirent
+ * @d_off: offset to the next dirent
+ * @d_type: type of file
+ * @d_name: file name
+ */
+
+struct p9_dirent {
+ struct p9_qid qid;
+ u64 d_off;
+ unsigned char d_type;
+ char d_name[256];
+};
+
+struct iov_iter;
+
+int p9_show_client_options(struct p9_client *clnt);
+int p9_client_statfs(struct p9_fid *fid, struct p9_rstatfs *sb);
+int p9_client_rename(struct p9_fid *fid, struct p9_fid *newdirfid,
+ const char *name);
+int p9_client_renameat(struct p9_fid *olddirfid, const char *old_name,
+ struct p9_fid *newdirfid, const char *new_name);
+struct p9_client *p9_client_create(const char *dev_name, char *options);
+void p9_client_destroy(struct p9_client *clnt);
+void p9_client_disconnect(struct p9_client *clnt);
+void p9_client_begin_disconnect(struct p9_client *clnt);
+struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid,
+ const char *uname, kuid_t n_uname, const char *aname);
+struct p9_fid *p9_client_walk(struct p9_fid *oldfid, uint16_t nwname,
+ const unsigned char * const *wnames, int clone);
+int p9_client_open(struct p9_fid *fid, int mode);
+int p9_client_fcreate(struct p9_fid *fid, const char *name, u32 perm, int mode,
+ char *extension);
+int p9_client_link(struct p9_fid *fid, struct p9_fid *oldfid, const char *newname);
+int p9_client_symlink(struct p9_fid *fid, const char *name, const char *symname,
+ kgid_t gid, struct p9_qid *qid);
+int p9_client_create_dotl(struct p9_fid *ofid, const char *name, u32 flags, u32 mode,
+ kgid_t gid, struct p9_qid *qid);
+int p9_client_clunk(struct p9_fid *fid);
+int p9_client_fsync(struct p9_fid *fid, int datasync);
+int p9_client_remove(struct p9_fid *fid);
+int p9_client_unlinkat(struct p9_fid *dfid, const char *name, int flags);
+int p9_client_read(struct p9_fid *fid, u64 offset, struct iov_iter *to, int *err);
+int p9_client_read_once(struct p9_fid *fid, u64 offset, struct iov_iter *to,
+ int *err);
+int p9_client_write(struct p9_fid *fid, u64 offset, struct iov_iter *from, int *err);
+int p9_client_readdir(struct p9_fid *fid, char *data, u32 count, u64 offset);
+int p9dirent_read(struct p9_client *clnt, char *buf, int len,
+ struct p9_dirent *dirent);
+struct p9_wstat *p9_client_stat(struct p9_fid *fid);
+int p9_client_wstat(struct p9_fid *fid, struct p9_wstat *wst);
+int p9_client_setattr(struct p9_fid *fid, struct p9_iattr_dotl *attr);
+
+struct p9_stat_dotl *p9_client_getattr_dotl(struct p9_fid *fid,
+ u64 request_mask);
+
+int p9_client_mknod_dotl(struct p9_fid *oldfid, const char *name, int mode,
+ dev_t rdev, kgid_t gid, struct p9_qid *qid);
+int p9_client_mkdir_dotl(struct p9_fid *fid, const char *name, int mode,
+ kgid_t gid, struct p9_qid *qid);
+int p9_client_lock_dotl(struct p9_fid *fid, struct p9_flock *flock, u8 *status);
+int p9_client_getlock_dotl(struct p9_fid *fid, struct p9_getlock *fl);
+void p9_fcall_fini(struct p9_fcall *fc);
+struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag);
+
+static inline void p9_req_get(struct p9_req_t *r)
+{
+ refcount_inc(&r->refcount);
+}
+
+static inline int p9_req_try_get(struct p9_req_t *r)
+{
+ return refcount_inc_not_zero(&r->refcount);
+}
+
+int p9_req_put(struct p9_client *c, struct p9_req_t *r);
+
+/* fid reference counting helpers:
+ * - fids used for any length of time should always be referenced through
+ * p9_fid_get(), and released with p9_fid_put()
+ * - v9fs_fid_lookup() or similar will automatically call get for you
+ * and also require a put
+ * - the *_fid_add() helpers will stash the fid in the inode,
+ * at which point it is the responsibility of evict_inode()
+ * to call the put
+ * - the last put will automatically send a clunk to the server
+ */
+static inline struct p9_fid *p9_fid_get(struct p9_fid *fid)
+{
+ refcount_inc(&fid->count);
+
+ return fid;
+}
+
+static inline int p9_fid_put(struct p9_fid *fid)
+{
+ if (!fid || IS_ERR(fid))
+ return 0;
+
+ if (!refcount_dec_and_test(&fid->count))
+ return 0;
+
+ return p9_client_clunk(fid);
+}
+
+void p9_client_cb(struct p9_client *c, struct p9_req_t *req, int status);
+
+int p9_parse_header(struct p9_fcall *pdu, int32_t *size, int8_t *type,
+ int16_t *tag, int rewind);
+int p9stat_read(struct p9_client *clnt, char *buf, int len,
+ struct p9_wstat *st);
+void p9stat_free(struct p9_wstat *stbuf);
+
+static inline int p9_is_proto_dotu(struct p9_client *clnt)
+{
+ return false;
+}
+static inline int p9_is_proto_dotl(struct p9_client *clnt)
+{
+ return true;
+}
+struct p9_fid *p9_client_xattrwalk(struct p9_fid *file_fid,
+ const char *attr_name, u64 *attr_size);
+int p9_client_xattrcreate(struct p9_fid *fid, const char *name,
+ u64 attr_size, int flags);
+int p9_client_readlink(struct p9_fid *fid, char **target);
+
+int p9_client_init(void);
+void p9_client_exit(void);
+
+#endif /* NET_9P_CLIENT_H */
diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h
new file mode 100644
index 000000000000..9f5f9345f3f5
--- /dev/null
+++ b/include/net/9p/transport.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Transport Definition
+ *
+ * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net>
+ * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ */
+
+#ifndef NET_9P_TRANSPORT_H
+#define NET_9P_TRANSPORT_H
+
+#include <linux/module.h>
+#include <linux/list.h>
+
+#define P9_DEF_MIN_RESVPORT (665U)
+#define P9_DEF_MAX_RESVPORT (1023U)
+
+struct p9_client;
+struct p9_req_t;
+struct iov_iter;
+
+/**
+ * struct p9_trans_module - transport module interface
+ * @list: used to maintain a list of currently available transports
+ * @name: the human-readable name of the transport
+ * @maxsize: transport provided maximum packet size
+ * @pooled_rbuffers: currently only set for RDMA transport which pulls the
+ * response buffers from a shared pool, and accordingly
+ * we're less flexible when choosing the response message
+ * size in this case
+ * @def: set if this transport should be considered the default
+ * @create: member function to create a new connection on this transport
+ * @close: member function to discard a connection on this transport
+ * @request: member function to issue a request to the transport
+ * @poll: member function to poll for completed requests
+ * @cancel: member function to cancel a request (if it hasn't been sent)
+ * @cancelled: member function to notify that a cancelled request will not
+ * receive a reply
+ *
+ * This is the basic API for a transport module which is registered by the
+ * transport module with the 9P core network module and used by the client
+ * to instantiate a new connection on a transport.
+ *
+ * The transport module list is protected by v9fs_trans_lock.
+ */
+
+struct p9_trans_module {
+ struct list_head list;
+ char *name; /* name of transport */
+ int maxsize; /* max message size of transport */
+ bool pooled_rbuffers;
+ int def; /* this transport should be default */
+ struct module *owner;
+ int (*create)(struct p9_client *client,
+ const char *devname, char *args);
+ void (*close)(struct p9_client *client);
+ int (*request)(struct p9_client *client, struct p9_req_t *req);
+ int (*poll)(struct p9_client *client);
+ int (*cancel)(struct p9_client *client, struct p9_req_t *req);
+ int (*cancelled)(struct p9_client *client, struct p9_req_t *req);
+ int (*show_options)(struct p9_client *client);
+};
+
+void v9fs_register_trans(struct p9_trans_module *m);
+void v9fs_unregister_trans(struct p9_trans_module *m);
+struct p9_trans_module *v9fs_get_trans_by_name(const char *s);
+struct p9_trans_module *v9fs_get_default_trans(void);
+static inline void v9fs_put_trans(struct p9_trans_module *m) {}
+
+#define MODULE_ALIAS_9P(transport) \
+ MODULE_ALIAS("9p-" transport)
+
+#endif /* NET_9P_TRANSPORT_H */
diff --git a/include/printf.h b/include/printf.h
index 83da06201a3a..d111afacb63e 100644
--- a/include/printf.h
+++ b/include/printf.h
@@ -32,6 +32,17 @@ void __noreturn panic_no_stacktrace(const char *fmt, ...) __printf(1, 2);
#define printk printf
+/*
+ * Dummy printk for disabled debugging statements to use whilst maintaining
+ * gcc's format checking.
+ */
+#define no_printk(fmt, ...) \
+({ \
+ if (0) \
+ printk(fmt, ##__VA_ARGS__); \
+ 0; \
+})
+
enum {
DUMP_PREFIX_NONE,
DUMP_PREFIX_ADDRESS,
diff --git a/include/uapi/linux/virtio_9p.h b/include/uapi/linux/virtio_9p.h
new file mode 100644
index 000000000000..374b68f8ac6e
--- /dev/null
+++ b/include/uapi/linux/virtio_9p.h
@@ -0,0 +1,44 @@
+#ifndef _LINUX_VIRTIO_9P_H
+#define _LINUX_VIRTIO_9P_H
+/* This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * 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. Neither the name of IBM nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 IBM 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 <linux/virtio_types.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+
+/* The feature bitmap for virtio 9P */
+
+/* The mount point is specified in a config variable */
+#define VIRTIO_9P_MOUNT_TAG 0
+
+struct virtio_9p_config {
+ /* length of the tag name */
+ __virtio16 tag_len;
+ /* non-NULL terminated tag name */
+ __u8 tag[];
+} __attribute__((packed));
+
+#endif /* _LINUX_VIRTIO_9P_H */
diff --git a/net/9p/Kconfig b/net/9p/Kconfig
new file mode 100644
index 000000000000..ebb9ff91b723
--- /dev/null
+++ b/net/9p/Kconfig
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# 9P protocol configuration
+#
+
+menuconfig NET_9P
+ tristate "Plan 9 Resource Sharing Support (9P2000)"
+ select NETFS_SUPPORT
+ select IDR
+ help
+ If you say Y here, you will get experimental support for
+ Plan 9 resource sharing via the 9P2000 protocol.
+
+ See <http://v9fs.sf.net> for more information.
+
+ If unsure, say N.
+
+if NET_9P
+
+config NET_9P_VIRTIO
+ depends on VIRTIO
+ tristate "9P Virtio Transport"
+ help
+ This builds support for a transports between
+ guest partitions and a host partition.
+
+config NET_9P_DEBUG
+ bool "Debug information"
+ help
+ Say Y if you want the 9P subsystem to log debug information.
+
+endif
diff --git a/net/9p/Makefile b/net/9p/Makefile
new file mode 100644
index 000000000000..10bf41d773be
--- /dev/null
+++ b/net/9p/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_NET_9P) := 9pnet.o
+obj-$(CONFIG_NET_9P_VIRTIO) += 9pnet_virtio.o
+
+9pnet-objs := \
+ mod.o \
+ client.o \
+ protocol.o
+
+9pnet_virtio-objs := \
+ trans_virtio.o \
diff --git a/net/9p/client.c b/net/9p/client.c
new file mode 100644
index 000000000000..1fae8092f97e
--- /dev/null
+++ b/net/9p/client.c
@@ -0,0 +1,1932 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 9P Client
+ *
+ * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/kdev_t.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/uio.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <net/9p/9p.h>
+#include <linux/parser.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+#include <barebox-info.h>
+#include <stdio.h>
+#include "protocol.h"
+
+/* DEFAULT MSIZE = 32 pages worth of payload + P9_HDRSZ +
+ * room for write (16 extra) or read (11 extra) operands.
+ */
+
+#define DEFAULT_MSIZE ((128 * 1024) + P9_IOHDRSZ)
+
+/* Client Option Parsing (code inspired by NFS code)
+ * - a little lazy - parse all client options
+ */
+
+enum {
+ Opt_msize,
+ Opt_trans,
+ Opt_err,
+};
+
+static const match_table_t tokens = {
+ {Opt_msize, "msize=%u"},
+ {Opt_trans, "trans=%s"},
+ {Opt_err, NULL},
+};
+
+int p9_show_client_options(struct p9_client *clnt)
+{
+ if (clnt->msize != DEFAULT_MSIZE)
+ printf(",msize=%u", clnt->msize);
+ printf(",trans=%s", clnt->trans_mod->name);
+
+ if (clnt->trans_mod->show_options)
+ return clnt->trans_mod->show_options(clnt);
+ return 0;
+}
+EXPORT_SYMBOL(p9_show_client_options);
+
+/* Some error codes are taken directly from the server replies,
+ * make sure they are valid.
+ */
+static int safe_errno(int err)
+{
+ if (err > 0 || err < -MAX_ERRNO) {
+ p9_debug(P9_DEBUG_ERROR, "Invalid error code %d\n", err);
+ return -EPROTO;
+ }
+ return err;
+}
+
+/**
+ * parse_opts - parse mount options into client structure
+ * @opts: options string passed from mount
+ * @clnt: existing v9fs client information
+ *
+ * Return 0 upon success, -ERRNO upon failure
+ */
+
+static int parse_opts(char *opts, struct p9_client *clnt)
+{
+ char *options, *tmp_options;
+ char *p;
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ char *s;
+ int ret = 0;
+
+ clnt->proto_version = p9_proto_2000L;
+ clnt->msize = DEFAULT_MSIZE;
+
+ if (!opts)
+ return 0;
+
+ tmp_options = kstrdup(opts, GFP_KERNEL);
+ if (!tmp_options)
+ return -ENOMEM;
+ options = tmp_options;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ int token, r;
+
+ if (!*p)
+ continue;
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_msize:
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ continue;
+ }
+ if (option < 4096) {
+ p9_debug(P9_DEBUG_ERROR,
+ "msize should be at least 4k\n");
+ ret = -EINVAL;
+ continue;
+ }
+ clnt->msize = option;
+ break;
+ case Opt_trans:
+ s = match_strdup(&args[0]);
+ if (!s) {
+ ret = -ENOMEM;
+ p9_debug(P9_DEBUG_ERROR,
+ "problem allocating copy of trans arg\n");
+ goto free_and_return;
+ }
+
+ v9fs_put_trans(clnt->trans_mod);
+ clnt->trans_mod = v9fs_get_trans_by_name(s);
+ if (!clnt->trans_mod) {
+ pr_info("Could not find request transport: %s\n",
+ s);
+ ret = -EINVAL;
+ }
+ kfree(s);
+ break;
+ default:
+ continue;
+ }
+ }
+
+free_and_return:
+ if (ret)
+ v9fs_put_trans(clnt->trans_mod);
+ kfree(tmp_options);
+ return ret;
+}
+
+static int p9_fcall_init(struct p9_client *c, struct p9_fcall *fc,
+ int alloc_msize)
+{
+ fc->sdata = kmalloc(alloc_msize, GFP_NOFS);
+ if (!fc->sdata)
+ return -ENOMEM;
+ fc->capacity = alloc_msize;
+ fc->id = 0;
+ fc->tag = P9_NOTAG;
+ return 0;
+}
+
+void p9_fcall_fini(struct p9_fcall *fc)
+{
+ kfree(fc->sdata);
+}
+EXPORT_SYMBOL(p9_fcall_fini);
+
+/**
+ * p9_tag_alloc - Allocate a new request.
+ * @c: Client session.
+ * @type: Transaction type.
+ * @t_size: Buffer size for holding this request
+ * (automatic calculation by format template if 0).
+ * @r_size: Buffer size for holding server's reply on this request
+ * (automatic calculation by format template if 0).
+ * @fmt: Format template for assembling 9p request message
+ * (see p9pdu_vwritef).
+ * @ap: Variable arguments to be fed to passed format template
+ * (see p9pdu_vwritef).
+ *
+ * Context: Process context.
+ * Return: Pointer to new request.
+ */
+static struct p9_req_t *
+p9_tag_alloc(struct p9_client *c, int8_t type, uint t_size, uint r_size,
+ const char *fmt, va_list ap)
+{
+ struct p9_req_t *req = malloc(sizeof(*req));
+ int alloc_tsize;
+ int alloc_rsize;
+ int tag;
+ va_list apc;
+
+ va_copy(apc, ap);
+ alloc_tsize = min_t(size_t, c->msize,
+ t_size ?: p9_msg_buf_size(c, type, fmt, apc));
+ va_end(apc);
+
+ alloc_rsize = min_t(size_t, c->msize,
+ r_size ?: p9_msg_buf_size(c, type + 1, fmt, ap));
+
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ if (p9_fcall_init(c, &req->tc, alloc_tsize))
+ goto free_req;
+ if (p9_fcall_init(c, &req->rc, alloc_rsize))
+ goto free;
+
+ p9pdu_reset(&req->tc);
+ p9pdu_reset(&req->rc);
+ req->t_err = 0;
+ req->status = REQ_STATUS_ALLOC;
+ /* refcount needs to be set to 0 before inserting into the idr
+ * so p9_tag_lookup does not accept a request that is not fully
+ * initialized. refcount_set to 2 below will mark request ready.
+ */
+ refcount_set(&req->refcount, 0);
+ init_completion(&req->completion);
+ INIT_LIST_HEAD(&req->req_list);
+
+ idr_preload(GFP_NOFS);
+ spin_lock_irq(&c->lock);
+ if (type == P9_TVERSION)
+ tag = idr_alloc(&c->reqs, req, P9_NOTAG, P9_NOTAG + 1,
+ GFP_NOWAIT);
+ else
+ tag = idr_alloc(&c->reqs, req, 0, P9_NOTAG, GFP_NOWAIT);
+ req->tc.tag = tag;
+ spin_unlock_irq(&c->lock);
+ idr_preload_end();
+ if (tag < 0)
+ goto free;
+
+ /* Init ref to two because in the general case there is one ref
+ * that is put asynchronously by a writer thread, one ref
+ * temporarily given by p9_tag_lookup and put by p9_client_cb
+ * in the recv thread, and one ref put by p9_req_put in the
+ * main thread. The only exception is virtio that does not use
+ * p9_tag_lookup but does not have a writer thread either
+ * (the write happens synchronously in the request
+ * callback), so p9_client_cb eats the second ref there
+ * as the pointer is duplicated directly by virtqueue_add_sgs()
+ */
+ refcount_set(&req->refcount, 2);
+
+ return req;
+
+free:
+ p9_fcall_fini(&req->tc);
+ p9_fcall_fini(&req->rc);
+free_req:
+ free(req);
+ return ERR_PTR(-ENOMEM);
+}
+
+/**
+ * p9_tag_lookup - Look up a request by tag.
+ * @c: Client session.
+ * @tag: Transaction ID.
+ *
+ * Context: Any context.
+ * Return: A request, or %NULL if there is no request with that tag.
+ */
+struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag)
+{
+ struct p9_req_t *req;
+
+again:
+ req = idr_find(&c->reqs, tag);
+ if (req) {
+ if (!p9_req_try_get(req))
+ goto again;
+ if (req->tc.tag != tag) {
+ p9_req_put(c, req);
+ goto again;
+ }
+ }
+
+ return req;
+}
+EXPORT_SYMBOL(p9_tag_lookup);
+
+/**
+ * p9_tag_remove - Remove a tag.
+ * @c: Client session.
+ * @r: Request of reference.
+ *
+ * Context: Any context.
+ */
+static void p9_tag_remove(struct p9_client *c, struct p9_req_t *r)
+{
+ unsigned long flags;
+ u16 tag = r->tc.tag;
+
+ p9_debug(P9_DEBUG_MUX, "freeing clnt %p req %p tag: %d\n", c, r, tag);
+ spin_lock_irqsave(&c->lock, flags);
+ idr_remove(&c->reqs, tag);
+ spin_unlock_irqrestore(&c->lock, flags);
+}
+
+int p9_req_put(struct p9_client *c, struct p9_req_t *r)
+{
+ if (refcount_dec_and_test(&r->refcount)) {
+ p9_tag_remove(c, r);
+
+ p9_fcall_fini(&r->tc);
+ p9_fcall_fini(&r->rc);
+ free(r);
+ return 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(p9_req_put);
+
+/**
+ * p9_tag_cleanup - cleans up tags structure and reclaims resources
+ * @c: v9fs client struct
+ *
+ * This frees resources associated with the tags structure
+ *
+ */
+static void p9_tag_cleanup(struct p9_client *c)
+{
+ struct p9_req_t *req;
+ int id;
+
+ idr_for_each_entry(&c->reqs, req, id) {
+ pr_info("Tag %d still in use\n", id);
+ if (p9_req_put(c, req) == 0)
+ pr_warn("Packet with tag %d has still references",
+ req->tc.tag);
+ }
+}
+
+/**
+ * p9_client_cb - call back from transport to client
+ * @c: client state
+ * @req: request received
+ * @status: request status, one of REQ_STATUS_*
+ *
+ */
+void p9_client_cb(struct p9_client *c, struct p9_req_t *req, int status)
+{
+ p9_debug(P9_DEBUG_MUX, " tag %d\n", req->tc.tag);
+
+ WRITE_ONCE(req->status, status);
+
+ if (READ_ONCE(req->status) >= REQ_STATUS_RCVD)
+ complete(&req->completion);
+ p9_debug(P9_DEBUG_MUX, "wakeup: %d\n", req->tc.tag);
+ p9_req_put(c, req);
+}
+EXPORT_SYMBOL(p9_client_cb);
+
+/**
+ * p9_parse_header - parse header arguments out of a packet
+ * @pdu: packet to parse
+ * @size: size of packet
+ * @type: type of request
+ * @tag: tag of packet
+ * @rewind: set if we need to rewind offset afterwards
+ */
+
+int
+p9_parse_header(struct p9_fcall *pdu, int32_t *size, int8_t *type,
+ int16_t *tag, int rewind)
+{
+ s8 r_type;
+ s16 r_tag;
+ s32 r_size;
+ int offset = pdu->offset;
+ int err;
+
+ pdu->offset = 0;
+
+ err = p9pdu_readf(pdu, 0, "dbw", &r_size, &r_type, &r_tag);
+ if (err)
+ goto rewind_and_exit;
+
+ if (type)
+ *type = r_type;
+ if (tag)
+ *tag = r_tag;
+ if (size)
+ *size = r_size;
+
+ if (pdu->size != r_size || r_size < 7) {
+ err = -EINVAL;
+ goto rewind_and_exit;
+ }
+
+ pdu->id = r_type;
+ pdu->tag = r_tag;
+
+ p9_debug(P9_DEBUG_9P, "<<< size=%d type: %d tag: %d\n",
+ pdu->size, pdu->id, pdu->tag);
+
+rewind_and_exit:
+ if (rewind)
+ pdu->offset = offset;
+ return err;
+}
+EXPORT_SYMBOL(p9_parse_header);
+
+/**
+ * p9_check_errors - check 9p packet for error return and process it
+ * @c: current client instance
+ * @req: request to parse and check for error conditions
+ *
+ * returns error code if one is discovered, otherwise returns 0
+ *
+ * this will have to be more complicated if we have multiple
+ * error packet types
+ */
+
+static int p9_check_errors(struct p9_client *c, struct p9_req_t *req)
+{
+ s8 type;
+ int err;
+ int ecode;
+
+ err = p9_parse_header(&req->rc, NULL, &type, NULL, 0);
+ if (req->rc.size > req->rc.capacity) {
+ pr_err("requested packet size too big: %d does not fit %zu (type=%d)\n",
+ req->rc.size, req->rc.capacity, req->rc.id);
+ return -EIO;
+ }
+ /* dump the response from server
+ * This should be after check errors which poplulate pdu_fcall.
+ */
+ if (err) {
+ p9_debug(P9_DEBUG_ERROR, "couldn't parse header %d\n", err);
+ return err;
+ }
+ if (type != P9_RERROR && type != P9_RLERROR)
+ return 0;
+
+ err = p9pdu_readf(&req->rc, c->proto_version, "d", &ecode);
+ if (err)
+ goto out_err;
+ err = -ecode;
+
+ p9_debug(P9_DEBUG_9P, "<<< RLERROR (%d)\n", -ecode);
+
+ return err;
+
+out_err:
+ p9_debug(P9_DEBUG_ERROR, "couldn't parse error%d\n", err);
+
+ return err;
+}
+
+static struct p9_req_t *
+p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...);
+
+/**
+ * p9_client_flush - flush (cancel) a request
+ * @c: client state
+ * @oldreq: request to cancel
+ *
+ * This sents a flush for a particular request and links
+ * the flush request to the original request. The current
+ * code only supports a single flush request although the protocol
+ * allows for multiple flush requests to be sent for a single request.
+ *
+ */
+
+static int p9_client_flush(struct p9_client *c, struct p9_req_t *oldreq)
+{
+ struct p9_req_t *req;
+ s16 oldtag;
+ int err;
+
+ err = p9_parse_header(&oldreq->tc, NULL, NULL, &oldtag, 1);
+ if (err)
+ return err;
+
+ p9_debug(P9_DEBUG_9P, ">>> TFLUSH tag %d\n", oldtag);
+
+ req = p9_client_rpc(c, P9_TFLUSH, "w", oldtag);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ /* if we haven't received a response for oldreq,
+ * remove it from the list
+ */
+ if (READ_ONCE(oldreq->status) == REQ_STATUS_SENT) {
+ if (c->trans_mod->cancelled)
+ c->trans_mod->cancelled(c, oldreq);
+ }
+
+ p9_req_put(c, req);
+ return 0;
+}
+
+static struct p9_req_t *p9_client_prepare_req(struct p9_client *c,
+ int8_t type, uint t_size, uint r_size,
+ const char *fmt, va_list ap)
+{
+ int err;
+ struct p9_req_t *req;
+ va_list apc;
+
+ p9_debug(P9_DEBUG_MUX, "client %p op %d\n", c, type);
+
+ /* we allow for any status other than disconnected */
+ if (c->status == Disconnected)
+ return ERR_PTR(-EIO);
+
+ /* if status is begin_disconnected we allow only clunk request */
+ if (c->status == BeginDisconnect && type != P9_TCLUNK)
+ return ERR_PTR(-EIO);
+
+ va_copy(apc, ap);
+ req = p9_tag_alloc(c, type, t_size, r_size, fmt, apc);
+ va_end(apc);
+ if (IS_ERR(req))
+ return req;
+
+ /* marshall the data */
+ p9pdu_prepare(&req->tc, req->tc.tag, type);
+ err = p9pdu_vwritef(&req->tc, c->proto_version, fmt, ap);
+ if (err)
+ goto reterr;
+ p9pdu_finalize(c, &req->tc);
+ return req;
+reterr:
+ p9_req_put(c, req);
+ /* We have to put also the 2nd reference as it won't be used */
+ p9_req_put(c, req);
+ return ERR_PTR(err);
+}
+
+/**
+ * p9_client_rpc - issue a request and wait for a response
+ * @c: client session
+ * @type: type of request
+ * @fmt: protocol format string (see protocol.c)
+ *
+ * Returns request structure (which client must free using p9_req_put)
+ */
+
+static struct p9_req_t *
+p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+ struct p9_req_t *req;
+ /* Passing zero for tsize/rsize to p9_client_prepare_req() tells it to
+ * auto determine an appropriate (small) request/response size
+ * according to actual message data being sent. Currently RDMA
+ * transport is excluded from this response message size optimization,
+ * as it would not cope with it, due to its pooled response buffers
+ * (using an optimized request size for RDMA as well though).
+ */
+ const uint tsize = 0;
+ const uint rsize = c->trans_mod->pooled_rbuffers ? c->msize : 0;
+
+ va_start(ap, fmt);
+ req = p9_client_prepare_req(c, type, tsize, rsize, fmt, ap);
+ va_end(ap);
+ if (IS_ERR(req))
+ return req;
+
+
+ err = c->trans_mod->request(c, req);
+ if (err < 0) {
+ /* write won't happen */
+ p9_req_put(c, req);
+ if (err != -ERESTARTSYS && err != -EFAULT)
+ c->status = Disconnected;
+ goto recalc_sigpending;
+ }
+again:
+ do {
+ c->trans_mod->poll(c);
+ } while (!completion_done(&req->completion) && !(err = ctrlc()));
+
+ if (err)
+ err = -ERESTARTSYS;
+
+ if (err == -ERESTARTSYS && c->status == Connected &&
+ type == P9_TFLUSH) {
+ goto again;
+ }
+
+ if (READ_ONCE(req->status) == REQ_STATUS_ERROR) {
+ p9_debug(P9_DEBUG_ERROR, "req_status error %d\n", req->t_err);
+ err = req->t_err;
+ }
+ if (err == -ERESTARTSYS && c->status == Connected) {
+ p9_debug(P9_DEBUG_MUX, "flushing\n");
+
+ if (c->trans_mod->cancel(c, req))
+ p9_client_flush(c, req);
+
+ /* if we received the response anyway, don't signal error */
+ if (READ_ONCE(req->status) == REQ_STATUS_RCVD)
+ err = 0;
+ }
+recalc_sigpending:
+ if (err < 0)
+ goto reterr;
+
+ err = p9_check_errors(c, req);
+ if (!err)
+ return req;
+reterr:
+ p9_req_put(c, req);
+ return ERR_PTR(safe_errno(err));
+}
+
+static struct p9_fid *p9_fid_create(struct p9_client *clnt)
+{
+ int ret;
+ struct p9_fid *fid;
+
+ p9_debug(P9_DEBUG_FID, "clnt %p\n", clnt);
+ fid = kzalloc(sizeof(*fid), GFP_KERNEL);
+ if (!fid)
+ return NULL;
+
+ fid->mode = -1;
+ fid->clnt = clnt;
+ refcount_set(&fid->count, 1);
+
+ idr_preload(GFP_KERNEL);
+ spin_lock_irq(&clnt->lock);
+ ret = idr_alloc_u32(&clnt->fids, fid, &fid->fid, P9_NOFID - 1,
+ GFP_NOWAIT);
+ spin_unlock_irq(&clnt->lock);
+ idr_preload_end();
+ if (!ret)
+ return fid;
+
+ kfree(fid);
+ return NULL;
+}
+
+static void p9_fid_destroy(struct p9_fid *fid)
+{
+ struct p9_client *clnt;
+ unsigned long flags;
+
+ p9_debug(P9_DEBUG_FID, "fid %d\n", fid->fid);
+ clnt = fid->clnt;
+ spin_lock_irqsave(&clnt->lock, flags);
+ idr_remove(&clnt->fids, fid->fid);
+ spin_unlock_irqrestore(&clnt->lock, flags);
+ kfree(fid->rdir);
+ kfree(fid);
+}
+
+static int p9_client_version(struct p9_client *c)
+{
+ int err;
+ struct p9_req_t *req;
+ char *version = NULL;
+ int msize;
+
+ p9_debug(P9_DEBUG_9P, ">>> TVERSION msize %d protocol %d\n",
+ c->msize, c->proto_version);
+
+ switch (c->proto_version) {
+ case p9_proto_2000L:
+ req = p9_client_rpc(c, P9_TVERSION, "ds",
+ c->msize, "9P2000.L");
+ break;
+ case p9_proto_2000u:
+ req = p9_client_rpc(c, P9_TVERSION, "ds",
+ c->msize, "9P2000.u");
+ break;
+ case p9_proto_legacy:
+ req = p9_client_rpc(c, P9_TVERSION, "ds",
+ c->msize, "9P2000");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, c->proto_version, "ds", &msize, &version);
+ if (err) {
+ p9_debug(P9_DEBUG_9P, "version error %d\n", err);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RVERSION msize %d %s\n", msize, version);
+ if (strncmp(version, "9P2000.L", 8)) {
+ p9_debug(P9_DEBUG_ERROR,
+ "server returned an unknown/unsupported version: %s\n", version);
+ err = -EREMOTEIO;
+ goto error;
+ }
+
+ if (msize < 4096) {
+ p9_debug(P9_DEBUG_ERROR,
+ "server returned a msize < 4096: %d\n", msize);
+ err = -EREMOTEIO;
+ goto error;
+ }
+ if (msize < c->msize)
+ c->msize = msize;
+
+error:
+ kfree(version);
+ p9_req_put(c, req);
+
+ return err;
+}
+
+struct p9_client *p9_client_create(const char *dev_name, char *options)
+{
+ int err;
+ struct p9_client *clnt;
+
+ clnt = kmalloc(sizeof(*clnt), GFP_KERNEL);
+ if (!clnt)
+ return ERR_PTR(-ENOMEM);
+
+ clnt->trans_mod = NULL;
+ clnt->trans = NULL;
+
+ memcpy(clnt->name, release_string, strlen(release_string) + 1);
+
+ spin_lock_init(&clnt->lock);
+ idr_init(&clnt->fids);
+ idr_init(&clnt->reqs);
+
+ err = parse_opts(options, clnt);
+ if (err < 0)
+ goto free_client;
+
+ if (!clnt->trans_mod)
+ clnt->trans_mod = v9fs_get_default_trans();
+
+ if (!clnt->trans_mod) {
+ err = -EPROTONOSUPPORT;
+ p9_debug(P9_DEBUG_ERROR,
+ "No transport defined or default transport\n");
+ goto free_client;
+ }
+
+ p9_debug(P9_DEBUG_MUX, "clnt %p trans %p msize %d protocol %d\n",
+ clnt, clnt->trans_mod, clnt->msize, clnt->proto_version);
+
+ err = clnt->trans_mod->create(clnt, dev_name, options);
+ if (err)
+ goto put_trans;
+
+ if (clnt->msize > clnt->trans_mod->maxsize) {
+ clnt->msize = clnt->trans_mod->maxsize;
+ pr_info("Limiting 'msize' to %d as this is the maximum "
+ "supported by transport %s\n",
+ clnt->msize, clnt->trans_mod->name
+ );
+ }
+
+ if (clnt->msize < 4096) {
+ p9_debug(P9_DEBUG_ERROR,
+ "Please specify a msize of at least 4k\n");
+ err = -EINVAL;
+ goto close_trans;
+ }
+
+ err = p9_client_version(clnt);
+ if (err)
+ goto close_trans;
+
+ return clnt;
+
+close_trans:
+ clnt->trans_mod->close(clnt);
+put_trans:
+ v9fs_put_trans(clnt->trans_mod);
+free_client:
+ kfree(clnt);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p9_client_create);
+
+void p9_client_destroy(struct p9_client *clnt)
+{
+ struct p9_fid *fid;
+ int id;
+
+ p9_debug(P9_DEBUG_MUX, "clnt %p\n", clnt);
+
+ if (clnt->trans_mod)
+ clnt->trans_mod->close(clnt);
+
+ v9fs_put_trans(clnt->trans_mod);
+
+ idr_for_each_entry(&clnt->fids, fid, id) {
+ pr_info("Found fid %d not clunked\n", fid->fid);
+ p9_fid_destroy(fid);
+ }
+
+ p9_tag_cleanup(clnt);
+
+ kfree(clnt);
+}
+EXPORT_SYMBOL(p9_client_destroy);
+
+void p9_client_disconnect(struct p9_client *clnt)
+{
+ p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt);
+ clnt->status = Disconnected;
+}
+EXPORT_SYMBOL(p9_client_disconnect);
+
+void p9_client_begin_disconnect(struct p9_client *clnt)
+{
+ p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt);
+ clnt->status = BeginDisconnect;
+}
+EXPORT_SYMBOL(p9_client_begin_disconnect);
+
+struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid,
+ const char *uname, kuid_t n_uname,
+ const char *aname)
+{
+ int err;
+ struct p9_req_t *req;
+ struct p9_fid *fid;
+ struct p9_qid qid;
+
+ p9_debug(P9_DEBUG_9P, ">>> TATTACH afid %d uname %s aname %s\n",
+ afid ? afid->fid : -1, uname, aname);
+ fid = p9_fid_create(clnt);
+ if (!fid) {
+ err = -ENOMEM;
+ goto error;
+ }
+ fid->uid = n_uname;
+
+ req = p9_client_rpc(clnt, P9_TATTACH, "ddss?u", fid->fid,
+ afid ? afid->fid : P9_NOFID, uname, aname, n_uname);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Q", &qid);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RATTACH qid %x.%llx.%x\n",
+ qid.type, qid.path, qid.version);
+
+ memmove(&fid->qid, &qid, sizeof(struct p9_qid));
+
+ p9_req_put(clnt, req);
+ return fid;
+
+error:
+ if (fid)
+ p9_fid_destroy(fid);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p9_client_attach);
+
+struct p9_fid *p9_client_walk(struct p9_fid *oldfid, uint16_t nwname,
+ const unsigned char * const *wnames, int clone)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_fid *fid;
+ struct p9_qid *wqids;
+ struct p9_req_t *req;
+ u16 nwqids, count;
+
+ wqids = NULL;
+ clnt = oldfid->clnt;
+ if (clone) {
+ fid = p9_fid_create(clnt);
+ if (!fid) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ fid->uid = oldfid->uid;
+ } else {
+ fid = oldfid;
+ }
+
+ p9_debug(P9_DEBUG_9P, ">>> TWALK fids %d,%d nwname %ud wname[0] %s\n",
+ oldfid->fid, fid->fid, nwname, wnames ? wnames[0] : NULL);
+ req = p9_client_rpc(clnt, P9_TWALK, "ddT", oldfid->fid, fid->fid,
+ nwname, wnames);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "R", &nwqids, &wqids);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto clunk_fid;
+ }
+ p9_req_put(clnt, req);
+
+ p9_debug(P9_DEBUG_9P, "<<< RWALK nwqid %d:\n", nwqids);
+
+ if (nwqids != nwname) {
+ err = -ENOENT;
+ goto clunk_fid;
+ }
+
+ for (count = 0; count < nwqids; count++)
+ p9_debug(P9_DEBUG_9P, "<<< [%d] %x.%llx.%x\n",
+ count, wqids[count].type,
+ wqids[count].path,
+ wqids[count].version);
+
+ if (nwname)
+ memmove(&fid->qid, &wqids[nwqids - 1], sizeof(struct p9_qid));
+ else
+ memmove(&fid->qid, &oldfid->qid, sizeof(struct p9_qid));
+
+ kfree(wqids);
+ return fid;
+
+clunk_fid:
+ kfree(wqids);
+ p9_fid_put(fid);
+ fid = NULL;
+
+error:
+ if (fid && fid != oldfid)
+ p9_fid_destroy(fid);
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p9_client_walk);
+
+int p9_client_open(struct p9_fid *fid, int mode)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+ struct p9_qid qid;
+ int iounit;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P, ">>> %s fid %d mode %d\n",
+ p9_is_proto_dotl(clnt) ? "TLOPEN" : "TOPEN", fid->fid, mode);
+
+ if (fid->mode != -1)
+ return -EINVAL;
+
+ if (p9_is_proto_dotl(clnt))
+ req = p9_client_rpc(clnt, P9_TLOPEN, "dd", fid->fid, mode & P9L_MODE_MASK);
+ else
+ req = p9_client_rpc(clnt, P9_TOPEN, "db", fid->fid, mode & P9L_MODE_MASK);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Qd", &qid, &iounit);
+ if (err)
+ goto free_and_error;
+
+ p9_debug(P9_DEBUG_9P, "<<< %s qid %x.%llx.%x iounit %x\n",
+ p9_is_proto_dotl(clnt) ? "RLOPEN" : "ROPEN", qid.type,
+ qid.path, qid.version, iounit);
+
+ memmove(&fid->qid, &qid, sizeof(struct p9_qid));
+ fid->mode = mode;
+ fid->iounit = iounit;
+
+free_and_error:
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_open);
+
+int p9_client_create_dotl(struct p9_fid *ofid, const char *name, u32 flags,
+ u32 mode, kgid_t gid, struct p9_qid *qid)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+ int iounit;
+
+ p9_debug(P9_DEBUG_9P,
+ ">>> TLCREATE fid %d name %s flags %d mode %d gid %d\n",
+ ofid->fid, name, flags, mode,
+ from_kgid(gid));
+ clnt = ofid->clnt;
+
+ if (ofid->mode != -1)
+ return -EINVAL;
+
+ req = p9_client_rpc(clnt, P9_TLCREATE, "dsddg", ofid->fid, name, flags,
+ mode & P9L_MODE_MASK, gid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Qd", qid, &iounit);
+ if (err)
+ goto free_and_error;
+
+ p9_debug(P9_DEBUG_9P, "<<< RLCREATE qid %x.%llx.%x iounit %x\n",
+ qid->type, qid->path, qid->version, iounit);
+
+ memmove(&ofid->qid, qid, sizeof(struct p9_qid));
+ ofid->mode = flags;
+ ofid->iounit = iounit;
+
+free_and_error:
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_create_dotl);
+
+int p9_client_fcreate(struct p9_fid *fid, const char *name, u32 perm, int mode,
+ char *extension)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+ struct p9_qid qid;
+ int iounit;
+
+ p9_debug(P9_DEBUG_9P, ">>> TCREATE fid %d name %s perm %d mode %d\n",
+ fid->fid, name, perm, mode);
+ clnt = fid->clnt;
+
+ if (fid->mode != -1)
+ return -EINVAL;
+
+ req = p9_client_rpc(clnt, P9_TCREATE, "dsdb?s", fid->fid, name, perm,
+ mode & P9L_MODE_MASK, extension);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Qd", &qid, &iounit);
+ if (err)
+ goto free_and_error;
+
+ p9_debug(P9_DEBUG_9P, "<<< RCREATE qid %x.%llx.%x iounit %x\n",
+ qid.type, qid.path, qid.version, iounit);
+
+ memmove(&fid->qid, &qid, sizeof(struct p9_qid));
+ fid->mode = mode;
+ fid->iounit = iounit;
+
+free_and_error:
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_fcreate);
+
+int p9_client_symlink(struct p9_fid *dfid, const char *name,
+ const char *symtgt, kgid_t gid, struct p9_qid *qid)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ p9_debug(P9_DEBUG_9P, ">>> TSYMLINK dfid %d name %s symtgt %s\n",
+ dfid->fid, name, symtgt);
+ clnt = dfid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TSYMLINK, "dssg", dfid->fid, name, symtgt,
+ gid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Q", qid);
+ if (err)
+ goto free_and_error;
+
+ p9_debug(P9_DEBUG_9P, "<<< RSYMLINK qid %x.%llx.%x\n",
+ qid->type, qid->path, qid->version);
+
+free_and_error:
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_symlink);
+
+int p9_client_link(struct p9_fid *dfid, struct p9_fid *oldfid, const char *newname)
+{
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ p9_debug(P9_DEBUG_9P, ">>> TLINK dfid %d oldfid %d newname %s\n",
+ dfid->fid, oldfid->fid, newname);
+ clnt = dfid->clnt;
+ req = p9_client_rpc(clnt, P9_TLINK, "dds", dfid->fid, oldfid->fid,
+ newname);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ p9_debug(P9_DEBUG_9P, "<<< RLINK\n");
+ p9_req_put(clnt, req);
+ return 0;
+}
+EXPORT_SYMBOL(p9_client_link);
+
+int p9_client_fsync(struct p9_fid *fid, int datasync)
+{
+ int err = 0;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ p9_debug(P9_DEBUG_9P, ">>> TFSYNC fid %d datasync:%d\n",
+ fid->fid, datasync);
+ clnt = fid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TFSYNC, "dd", fid->fid, datasync);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RFSYNC fid %d\n", fid->fid);
+
+ p9_req_put(clnt, req);
+
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_fsync);
+
+int p9_client_clunk(struct p9_fid *fid)
+{
+ int err = 0;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+ int retries = 0;
+
+again:
+ p9_debug(P9_DEBUG_9P, ">>> TCLUNK fid %d (try %d)\n",
+ fid->fid, retries);
+ clnt = fid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TCLUNK, "d", fid->fid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RCLUNK fid %d\n", fid->fid);
+
+ p9_req_put(clnt, req);
+error:
+ /* Fid is not valid even after a failed clunk
+ * If interrupted, retry once then give up and
+ * leak fid until umount.
+ */
+ if (err == -ERESTARTSYS) {
+ if (retries++ == 0)
+ goto again;
+ } else {
+ p9_fid_destroy(fid);
+ }
+ return err;
+}
+EXPORT_SYMBOL(p9_client_clunk);
+
+int p9_client_remove(struct p9_fid *fid)
+{
+ int err = 0;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ p9_debug(P9_DEBUG_9P, ">>> TREMOVE fid %d\n", fid->fid);
+ clnt = fid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TREMOVE, "d", fid->fid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RREMOVE fid %d\n", fid->fid);
+
+ p9_req_put(clnt, req);
+error:
+ if (err == -ERESTARTSYS)
+ p9_fid_put(fid);
+ else
+ p9_fid_destroy(fid);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_remove);
+
+int p9_client_unlinkat(struct p9_fid *dfid, const char *name, int flags)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ p9_debug(P9_DEBUG_9P, ">>> TUNLINKAT fid %d %s %d\n",
+ dfid->fid, name, flags);
+
+ clnt = dfid->clnt;
+ req = p9_client_rpc(clnt, P9_TUNLINKAT, "dsd", dfid->fid, name, flags);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+ p9_debug(P9_DEBUG_9P, "<<< RUNLINKAT fid %d %s\n", dfid->fid, name);
+
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_unlinkat);
+
+int
+p9_client_read(struct p9_fid *fid, u64 offset, struct iov_iter *to, int *err)
+{
+ int total = 0;
+ *err = 0;
+
+ while (iov_iter_count(to)) {
+ int count;
+
+ count = p9_client_read_once(fid, offset, to, err);
+ if (!count || *err)
+ break;
+ offset += count;
+ total += count;
+ }
+ return total;
+}
+EXPORT_SYMBOL(p9_client_read);
+
+int
+p9_client_read_once(struct p9_fid *fid, u64 offset, struct iov_iter *to,
+ int *err)
+{
+ struct p9_client *clnt = fid->clnt;
+ struct p9_req_t *req;
+ int count = iov_iter_count(to);
+ int rsize, received;
+ char *dataptr;
+
+ *err = 0;
+ p9_debug(P9_DEBUG_9P, ">>> TREAD fid %d offset %llu %zu\n",
+ fid->fid, offset, iov_iter_count(to));
+
+ rsize = fid->iounit;
+ if (!rsize || rsize > clnt->msize - P9_IOHDRSZ)
+ rsize = clnt->msize - P9_IOHDRSZ;
+
+ if (count < rsize)
+ rsize = count;
+
+ req = p9_client_rpc(clnt, P9_TREAD, "dqd", fid->fid, offset,
+ rsize);
+
+ if (IS_ERR(req)) {
+ *err = PTR_ERR(req);
+ return 0;
+ }
+
+ *err = p9pdu_readf(&req->rc, clnt->proto_version,
+ "D", &received, &dataptr);
+ if (*err) {
+ p9_req_put(clnt, req);
+ return 0;
+ }
+ if (rsize < received) {
+ pr_err("bogus RREAD count (%d > %d)\n", received, rsize);
+ received = rsize;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RREAD count %d\n", received);
+
+ int n = copy_to_iter(dataptr, received, to);
+
+ if (n != received) {
+ *err = -EFAULT;
+ p9_req_put(clnt, req);
+ return n;
+ }
+
+ p9_req_put(clnt, req);
+ return received;
+}
+EXPORT_SYMBOL(p9_client_read_once);
+
+int
+p9_client_write(struct p9_fid *fid, u64 offset, struct iov_iter *from, int *err)
+{
+ struct p9_client *clnt = fid->clnt;
+ struct p9_req_t *req;
+ int total = 0;
+ *err = 0;
+
+ while (iov_iter_count(from)) {
+ int count = iov_iter_count(from);
+ int rsize = fid->iounit;
+ int written;
+
+ if (!rsize || rsize > clnt->msize - P9_IOHDRSZ)
+ rsize = clnt->msize - P9_IOHDRSZ;
+
+ if (count < rsize)
+ rsize = count;
+
+ p9_debug(P9_DEBUG_9P, ">>> TWRITE fid %d offset %llu count %d (/%d)\n",
+ fid->fid, offset, rsize, count);
+
+ req = p9_client_rpc(clnt, P9_TWRITE, "dqV", fid->fid,
+ offset, rsize, from);
+ if (IS_ERR(req)) {
+ iov_iter_revert(from, count - iov_iter_count(from));
+ *err = PTR_ERR(req);
+ break;
+ }
+
+ *err = p9pdu_readf(&req->rc, clnt->proto_version, "d", &written);
+ if (*err) {
+ iov_iter_revert(from, count - iov_iter_count(from));
+ p9_req_put(clnt, req);
+ break;
+ }
+ if (rsize < written) {
+ pr_err("bogus RWRITE count (%d > %d)\n", written, rsize);
+ written = rsize;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RWRITE count %d\n", written);
+
+ p9_req_put(clnt, req);
+ iov_iter_revert(from, count - written - iov_iter_count(from));
+ total += written;
+ offset += written;
+ }
+ return total;
+}
+EXPORT_SYMBOL(p9_client_write);
+
+struct p9_wstat *p9_client_stat(struct p9_fid *fid)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_wstat *ret;
+ struct p9_req_t *req;
+ u16 ignored;
+
+ p9_debug(P9_DEBUG_9P, ">>> TSTAT fid %d\n", fid->fid);
+
+ ret = kmalloc(sizeof(*ret), GFP_KERNEL);
+ if (!ret)
+ return ERR_PTR(-ENOMEM);
+
+ clnt = fid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TSTAT, "d", fid->fid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "wS", &ignored, ret);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P,
+ "<<< RSTAT sz=%x type=%x dev=%x qid=%x.%llx.%x\n"
+ "<<< mode=%8.8x atime=%8.8x mtime=%8.8x length=%llx\n"
+ "<<< name=%s uid=%s gid=%s muid=%s extension=(%s)\n"
+ "<<< uid=%d gid=%d n_muid=%d\n",
+ ret->size, ret->type, ret->dev, ret->qid.type, ret->qid.path,
+ ret->qid.version, ret->mode,
+ ret->atime, ret->mtime, ret->length,
+ ret->name, ret->uid, ret->gid, ret->muid, ret->extension,
+ from_kuid(ret->n_uid),
+ from_kgid(ret->n_gid),
+ from_kuid(ret->n_muid));
+
+ p9_req_put(clnt, req);
+ return ret;
+
+error:
+ kfree(ret);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p9_client_stat);
+
+struct p9_stat_dotl *p9_client_getattr_dotl(struct p9_fid *fid,
+ u64 request_mask)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_stat_dotl *ret;
+ struct p9_req_t *req;
+
+ p9_debug(P9_DEBUG_9P, ">>> TGETATTR fid %d, request_mask %lld\n",
+ fid->fid, request_mask);
+
+ ret = kmalloc(sizeof(*ret), GFP_KERNEL);
+ if (!ret)
+ return ERR_PTR(-ENOMEM);
+
+ clnt = fid->clnt;
+
+ req = p9_client_rpc(clnt, P9_TGETATTR, "dq", fid->fid, request_mask);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "A", ret);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RGETATTR st_result_mask=%lld\n"
+ "<<< qid=%x.%llx.%x\n"
+ "<<< st_mode=%8.8x st_nlink=%llu\n"
+ "<<< st_uid=%d st_gid=%d\n"
+ "<<< st_rdev=%llx st_size=%llx st_blksize=%llu st_blocks=%llu\n"
+ "<<< st_atime_sec=%lld st_atime_nsec=%lld\n"
+ "<<< st_mtime_sec=%lld st_mtime_nsec=%lld\n"
+ "<<< st_ctime_sec=%lld st_ctime_nsec=%lld\n"
+ "<<< st_btime_sec=%lld st_btime_nsec=%lld\n"
+ "<<< st_gen=%lld st_data_version=%lld\n",
+ ret->st_result_mask,
+ ret->qid.type, ret->qid.path, ret->qid.version,
+ ret->st_mode, ret->st_nlink,
+ from_kuid(ret->st_uid),
+ from_kgid(ret->st_gid),
+ ret->st_rdev, ret->st_size, ret->st_blksize, ret->st_blocks,
+ ret->st_atime_sec, ret->st_atime_nsec,
+ ret->st_mtime_sec, ret->st_mtime_nsec,
+ ret->st_ctime_sec, ret->st_ctime_nsec,
+ ret->st_btime_sec, ret->st_btime_nsec,
+ ret->st_gen, ret->st_data_version);
+
+ p9_req_put(clnt, req);
+ return ret;
+
+error:
+ kfree(ret);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p9_client_getattr_dotl);
+
+static int p9_client_statsize(struct p9_wstat *wst, int proto_version)
+{
+ int ret;
+
+ /* NOTE: size shouldn't include its own length */
+ /* size[2] type[2] dev[4] qid[13] */
+ /* mode[4] atime[4] mtime[4] length[8]*/
+ /* name[s] uid[s] gid[s] muid[s] */
+ ret = 2 + 4 + 13 + 4 + 4 + 4 + 8 + 2 + 2 + 2 + 2;
+
+ if (wst->name)
+ ret += strlen(wst->name);
+ if (wst->uid)
+ ret += strlen(wst->uid);
+ if (wst->gid)
+ ret += strlen(wst->gid);
+ if (wst->muid)
+ ret += strlen(wst->muid);
+
+ if (proto_version == p9_proto_2000u ||
+ proto_version == p9_proto_2000L) {
+ /* extension[s] n_uid[4] n_gid[4] n_muid[4] */
+ ret += 2 + 4 + 4 + 4;
+ if (wst->extension)
+ ret += strlen(wst->extension);
+ }
+
+ return ret;
+}
+
+int p9_client_wstat(struct p9_fid *fid, struct p9_wstat *wst)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ clnt = fid->clnt;
+ wst->size = p9_client_statsize(wst, clnt->proto_version);
+ p9_debug(P9_DEBUG_9P, ">>> TWSTAT fid %d\n",
+ fid->fid);
+ p9_debug(P9_DEBUG_9P,
+ " sz=%x type=%x dev=%x qid=%x.%llx.%x\n"
+ " mode=%8.8x atime=%8.8x mtime=%8.8x length=%llx\n"
+ " name=%s uid=%s gid=%s muid=%s extension=(%s)\n"
+ " uid=%d gid=%d n_muid=%d\n",
+ wst->size, wst->type, wst->dev, wst->qid.type,
+ wst->qid.path, wst->qid.version,
+ wst->mode, wst->atime, wst->mtime, wst->length,
+ wst->name, wst->uid, wst->gid, wst->muid, wst->extension,
+ from_kuid(wst->n_uid),
+ from_kgid(wst->n_gid),
+ from_kuid(wst->n_muid));
+
+ req = p9_client_rpc(clnt, P9_TWSTAT, "dwS",
+ fid->fid, wst->size + 2, wst);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RWSTAT fid %d\n", fid->fid);
+
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_wstat);
+
+int p9_client_setattr(struct p9_fid *fid, struct p9_iattr_dotl *p9attr)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P, ">>> TSETATTR fid %d\n", fid->fid);
+ p9_debug(P9_DEBUG_9P, " valid=%x mode=%x uid=%d gid=%d size=%lld\n",
+ p9attr->valid, p9attr->mode,
+ from_kuid(p9attr->uid),
+ from_kgid(p9attr->gid),
+ p9attr->size);
+ p9_debug(P9_DEBUG_9P, " atime_sec=%lld atime_nsec=%lld\n",
+ p9attr->atime_sec, p9attr->atime_nsec);
+ p9_debug(P9_DEBUG_9P, " mtime_sec=%lld mtime_nsec=%lld\n",
+ p9attr->mtime_sec, p9attr->mtime_nsec);
+
+ req = p9_client_rpc(clnt, P9_TSETATTR, "dI", fid->fid, p9attr);
+
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+ p9_debug(P9_DEBUG_9P, "<<< RSETATTR fid %d\n", fid->fid);
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_setattr);
+
+int p9_client_statfs(struct p9_fid *fid, struct p9_rstatfs *sb)
+{
+ int err;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ clnt = fid->clnt;
+
+ p9_debug(P9_DEBUG_9P, ">>> TSTATFS fid %d\n", fid->fid);
+
+ req = p9_client_rpc(clnt, P9_TSTATFS, "d", fid->fid);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "ddqqqqqqd", &sb->type,
+ &sb->bsize, &sb->blocks, &sb->bfree, &sb->bavail,
+ &sb->files, &sb->ffree, &sb->fsid, &sb->namelen);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P,
+ "<<< RSTATFS fid %d type 0x%x bsize %u blocks %llu bfree %llu bavail %llu files %llu ffree %llu fsid %llu namelen %u\n",
+ fid->fid, sb->type, sb->bsize, sb->blocks, sb->bfree,
+ sb->bavail, sb->files, sb->ffree, sb->fsid, sb->namelen);
+
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_statfs);
+
+int p9_client_rename(struct p9_fid *fid,
+ struct p9_fid *newdirfid, const char *name)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ clnt = fid->clnt;
+
+ p9_debug(P9_DEBUG_9P, ">>> TRENAME fid %d newdirfid %d name %s\n",
+ fid->fid, newdirfid->fid, name);
+
+ req = p9_client_rpc(clnt, P9_TRENAME, "dds", fid->fid,
+ newdirfid->fid, name);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RRENAME fid %d\n", fid->fid);
+
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_rename);
+
+int p9_client_renameat(struct p9_fid *olddirfid, const char *old_name,
+ struct p9_fid *newdirfid, const char *new_name)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ clnt = olddirfid->clnt;
+
+ p9_debug(P9_DEBUG_9P,
+ ">>> TRENAMEAT olddirfid %d old name %s newdirfid %d new name %s\n",
+ olddirfid->fid, old_name, newdirfid->fid, new_name);
+
+ req = p9_client_rpc(clnt, P9_TRENAMEAT, "dsds", olddirfid->fid,
+ old_name, newdirfid->fid, new_name);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RRENAMEAT newdirfid %d new name %s\n",
+ newdirfid->fid, new_name);
+
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_renameat);
+
+/* An xattrwalk without @attr_name gives the fid for the lisxattr namespace
+ */
+struct p9_fid *p9_client_xattrwalk(struct p9_fid *file_fid,
+ const char *attr_name, u64 *attr_size)
+{
+ int err;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+ struct p9_fid *attr_fid;
+
+ clnt = file_fid->clnt;
+ attr_fid = p9_fid_create(clnt);
+ if (!attr_fid) {
+ err = -ENOMEM;
+ goto error;
+ }
+ p9_debug(P9_DEBUG_9P,
+ ">>> TXATTRWALK file_fid %d, attr_fid %d name '%s'\n",
+ file_fid->fid, attr_fid->fid, attr_name);
+
+ req = p9_client_rpc(clnt, P9_TXATTRWALK, "dds",
+ file_fid->fid, attr_fid->fid, attr_name);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "q", attr_size);
+ if (err) {
+ p9_req_put(clnt, req);
+ goto clunk_fid;
+ }
+ p9_req_put(clnt, req);
+ p9_debug(P9_DEBUG_9P, "<<< RXATTRWALK fid %d size %llu\n",
+ attr_fid->fid, *attr_size);
+ return attr_fid;
+clunk_fid:
+ p9_fid_put(attr_fid);
+ attr_fid = NULL;
+error:
+ if (attr_fid && attr_fid != file_fid)
+ p9_fid_destroy(attr_fid);
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(p9_client_xattrwalk);
+
+int p9_client_xattrcreate(struct p9_fid *fid, const char *name,
+ u64 attr_size, int flags)
+{
+ int err = 0;
+ struct p9_req_t *req;
+ struct p9_client *clnt;
+
+ p9_debug(P9_DEBUG_9P,
+ ">>> TXATTRCREATE fid %d name %s size %llu flag %d\n",
+ fid->fid, name, attr_size, flags);
+ clnt = fid->clnt;
+ req = p9_client_rpc(clnt, P9_TXATTRCREATE, "dsqd",
+ fid->fid, name, attr_size, flags);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+ p9_debug(P9_DEBUG_9P, "<<< RXATTRCREATE fid %d\n", fid->fid);
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL_GPL(p9_client_xattrcreate);
+
+int p9_client_readdir(struct p9_fid *fid, char *data, u32 count, u64 offset)
+{
+ int err, rsize;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+ char *dataptr;
+
+ p9_debug(P9_DEBUG_9P, ">>> TREADDIR fid %d offset %llu count %d\n",
+ fid->fid, offset, count);
+
+ clnt = fid->clnt;
+
+ rsize = fid->iounit;
+ if (!rsize || rsize > clnt->msize - P9_READDIRHDRSZ)
+ rsize = clnt->msize - P9_READDIRHDRSZ;
+
+ if (count < rsize)
+ rsize = count;
+
+ /* Don't bother zerocopy for small IO (< 1024) */
+ req = p9_client_rpc(clnt, P9_TREADDIR, "dqd", fid->fid,
+ offset, rsize);
+ if (IS_ERR(req)) {
+ err = PTR_ERR(req);
+ goto error;
+ }
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "D", &count, &dataptr);
+ if (err)
+ goto free_and_error;
+ if (rsize < count) {
+ pr_err("bogus RREADDIR count (%d > %d)\n", count, rsize);
+ count = rsize;
+ }
+
+ p9_debug(P9_DEBUG_9P, "<<< RREADDIR count %d\n", count);
+
+ memmove(data, dataptr, count);
+
+ p9_req_put(clnt, req);
+ return count;
+
+free_and_error:
+ p9_req_put(clnt, req);
+error:
+ return err;
+}
+EXPORT_SYMBOL(p9_client_readdir);
+
+int p9_client_mknod_dotl(struct p9_fid *fid, const char *name, int mode,
+ dev_t rdev, kgid_t gid, struct p9_qid *qid)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P,
+ ">>> TMKNOD fid %d name %s mode %d major %d minor %d\n",
+ fid->fid, name, mode, MAJOR(rdev), MINOR(rdev));
+ req = p9_client_rpc(clnt, P9_TMKNOD, "dsdddg", fid->fid, name, mode,
+ MAJOR(rdev), MINOR(rdev), gid);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Q", qid);
+ if (err)
+ goto error;
+ p9_debug(P9_DEBUG_9P, "<<< RMKNOD qid %x.%llx.%x\n",
+ qid->type, qid->path, qid->version);
+
+error:
+ p9_req_put(clnt, req);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_mknod_dotl);
+
+int p9_client_mkdir_dotl(struct p9_fid *fid, const char *name, int mode,
+ kgid_t gid, struct p9_qid *qid)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P, ">>> TMKDIR fid %d name %s mode %d gid %d\n",
+ fid->fid, name, mode, from_kgid(gid));
+ req = p9_client_rpc(clnt, P9_TMKDIR, "dsdg",
+ fid->fid, name, mode, gid);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "Q", qid);
+ if (err)
+ goto error;
+ p9_debug(P9_DEBUG_9P, "<<< RMKDIR qid %x.%llx.%x\n", qid->type,
+ qid->path, qid->version);
+
+error:
+ p9_req_put(clnt, req);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_mkdir_dotl);
+
+int p9_client_lock_dotl(struct p9_fid *fid, struct p9_flock *flock, u8 *status)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P,
+ ">>> TLOCK fid %d type %i flags %d start %lld length %lld proc_id %d client_id %s\n",
+ fid->fid, flock->type, flock->flags, flock->start,
+ flock->length, flock->proc_id, flock->client_id);
+
+ req = p9_client_rpc(clnt, P9_TLOCK, "dbdqqds", fid->fid, flock->type,
+ flock->flags, flock->start, flock->length,
+ flock->proc_id, flock->client_id);
+
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "b", status);
+ if (err)
+ goto error;
+ p9_debug(P9_DEBUG_9P, "<<< RLOCK status %i\n", *status);
+error:
+ p9_req_put(clnt, req);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_lock_dotl);
+
+int p9_client_getlock_dotl(struct p9_fid *fid, struct p9_getlock *glock)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P,
+ ">>> TGETLOCK fid %d, type %i start %lld length %lld proc_id %d client_id %s\n",
+ fid->fid, glock->type, glock->start, glock->length,
+ glock->proc_id, glock->client_id);
+
+ req = p9_client_rpc(clnt, P9_TGETLOCK, "dbqqds", fid->fid,
+ glock->type, glock->start, glock->length,
+ glock->proc_id, glock->client_id);
+
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "bqqds", &glock->type,
+ &glock->start, &glock->length, &glock->proc_id,
+ &glock->client_id);
+ if (err)
+ goto error;
+ p9_debug(P9_DEBUG_9P,
+ "<<< RGETLOCK type %i start %lld length %lld proc_id %d client_id %s\n",
+ glock->type, glock->start, glock->length,
+ glock->proc_id, glock->client_id);
+error:
+ p9_req_put(clnt, req);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_getlock_dotl);
+
+int p9_client_readlink(struct p9_fid *fid, char **target)
+{
+ int err;
+ struct p9_client *clnt;
+ struct p9_req_t *req;
+
+ clnt = fid->clnt;
+ p9_debug(P9_DEBUG_9P, ">>> TREADLINK fid %d\n", fid->fid);
+
+ req = p9_client_rpc(clnt, P9_TREADLINK, "d", fid->fid);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ err = p9pdu_readf(&req->rc, clnt->proto_version, "s", target);
+ if (err)
+ goto error;
+ p9_debug(P9_DEBUG_9P, "<<< RREADLINK target %s\n", *target);
+error:
+ p9_req_put(clnt, req);
+ return err;
+}
+EXPORT_SYMBOL(p9_client_readlink);
+
+int __init p9_client_init(void)
+{
+ return 0;
+}
+
+void __exit p9_client_exit(void)
+{
+}
diff --git a/net/9p/mod.c b/net/9p/mod.c
new file mode 100644
index 000000000000..ef071a1b490d
--- /dev/null
+++ b/net/9p/mod.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 9P entry point
+ *
+ * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <globalvar.h>
+#include <magicvar.h>
+#include <net/9p/9p.h>
+#include <linux/fs.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/parser.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#ifdef CONFIG_NET_9P_DEBUG
+unsigned int p9_debug_level; /* feature-rific global debug level */
+BAREBOX_MAGICVAR(plan9debug, "9P debugging level");
+
+void _p9_debug(enum p9_debug_flags level, const char *func,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ if ((p9_debug_level & level) != level)
+ return;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ if (level == P9_DEBUG_9P)
+ pr_notice("%pV", &vaf);
+ else
+ pr_notice("-- %s %pV", func, &vaf);
+
+ va_end(args);
+}
+EXPORT_SYMBOL(_p9_debug);
+
+static inline void p9_add_debug_globalvar(void)
+{
+ globalvar_add_simple_int("9p.debug", &p9_debug_level, "%d");
+}
+#else
+static inline void p9_add_debug_globalvar(void)
+{
+}
+#endif
+
+/* Dynamic Transport Registration Routines */
+
+static DEFINE_SPINLOCK(v9fs_trans_lock);
+static LIST_HEAD(v9fs_trans_list);
+
+/**
+ * v9fs_register_trans - register a new transport with 9p
+ * @m: structure describing the transport module and entry points
+ *
+ */
+void v9fs_register_trans(struct p9_trans_module *m)
+{
+ spin_lock(&v9fs_trans_lock);
+ list_add_tail(&m->list, &v9fs_trans_list);
+ spin_unlock(&v9fs_trans_lock);
+}
+EXPORT_SYMBOL(v9fs_register_trans);
+
+/**
+ * v9fs_unregister_trans - unregister a 9p transport
+ * @m: the transport to remove
+ *
+ */
+void v9fs_unregister_trans(struct p9_trans_module *m)
+{
+ spin_lock(&v9fs_trans_lock);
+ list_del_init(&m->list);
+ spin_unlock(&v9fs_trans_lock);
+}
+EXPORT_SYMBOL(v9fs_unregister_trans);
+
+/**
+ * v9fs_get_trans_by_name - get transport with the matching name
+ * @s: string identifying transport
+ *
+ */
+struct p9_trans_module *v9fs_get_trans_by_name(const char *s)
+{
+ struct p9_trans_module *t, *found = NULL;
+
+ spin_lock(&v9fs_trans_lock);
+
+ list_for_each_entry(t, &v9fs_trans_list, list)
+ if (strcmp(t->name, s) == 0) {
+ found = t;
+ break;
+ }
+
+ spin_unlock(&v9fs_trans_lock);
+
+ return found;
+}
+EXPORT_SYMBOL(v9fs_get_trans_by_name);
+
+static const char * const v9fs_default_transports[] = {
+ "virtio", "tcp", "fd", "unix", "xen", "rdma",
+};
+
+/**
+ * v9fs_get_default_trans - get the default transport
+ *
+ */
+
+struct p9_trans_module *v9fs_get_default_trans(void)
+{
+ struct p9_trans_module *t, *found = NULL;
+ int i;
+
+ spin_lock(&v9fs_trans_lock);
+
+ list_for_each_entry(t, &v9fs_trans_list, list)
+ if (t->def) {
+ found = t;
+ break;
+ }
+
+ if (!found)
+ list_for_each_entry(t, &v9fs_trans_list, list) {
+ found = t;
+ break;
+ }
+
+ spin_unlock(&v9fs_trans_lock);
+
+ for (i = 0; !found && i < ARRAY_SIZE(v9fs_default_transports); i++)
+ found = v9fs_get_trans_by_name(v9fs_default_transports[i]);
+
+ return found;
+}
+EXPORT_SYMBOL(v9fs_get_default_trans);
+
+/**
+ * init_p9 - Initialize module
+ *
+ */
+static int __init init_p9(void)
+{
+ int ret;
+
+ p9_add_debug_globalvar();
+
+ ret = p9_client_init();
+ if (ret)
+ return ret;
+
+ pr_info("Installing 9P2000 support\n");
+
+ return ret;
+}
+
+fs_initcall(init_p9)
+
+MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
+MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
+MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Plan 9 Resource Sharing Support (9P2000)");
diff --git a/net/9p/protocol.c b/net/9p/protocol.c
new file mode 100644
index 000000000000..3ba3c19e64ec
--- /dev/null
+++ b/net/9p/protocol.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 9P Protocol Support Code
+ *
+ * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ *
+ * Base on code from Anthony Liguori <aliguori@us.ibm.com>
+ * Copyright (C) 2008 by IBM, Corp.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/uidgid.h>
+#include <linux/uio.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+#include "protocol.h"
+
+/* len[2] text[len] */
+#define P9_STRLEN(s) \
+ (2 + min_t(size_t, s ? strlen(s) : 0, USHRT_MAX))
+
+static inline void trace_9p_protocol_dump(struct p9_client *clnt,
+ struct p9_fcall *pdu)
+{
+}
+
+/**
+ * p9_msg_buf_size - Returns a buffer size sufficiently large to hold the
+ * intended 9p message.
+ * @c: client
+ * @type: message type
+ * @fmt: format template for assembling request message
+ * (see p9pdu_vwritef)
+ * @ap: variable arguments to be fed to passed format template
+ * (see p9pdu_vwritef)
+ *
+ * Note: Even for response types (P9_R*) the format template and variable
+ * arguments must always be for the originating request type (P9_T*).
+ */
+size_t p9_msg_buf_size(struct p9_client *c, enum p9_msg_t type,
+ const char *fmt, va_list ap)
+{
+ /* size[4] type[1] tag[2] */
+ const int hdr = 4 + 1 + 2;
+ /* ename[s] errno[4] */
+ const int rerror_size = hdr + P9_ERRMAX + 4;
+ /* ecode[4] */
+ const int rlerror_size = hdr + 4;
+ const int err_size =
+ c->proto_version == p9_proto_2000L ? rlerror_size : rerror_size;
+
+ static_assert(NAME_MAX <= 4*1024, "p9_msg_buf_size() currently assumes "
+ "a max. allowed directory entry name length of 4k");
+
+ switch (type) {
+
+ /* message types not used at all */
+ case P9_TERROR:
+ case P9_TLERROR:
+ case P9_TAUTH:
+ case P9_RAUTH:
+ BUG();
+
+ /* variable length & potentially large message types */
+ case P9_TATTACH:
+ BUG_ON(strcmp("ddss?u", fmt));
+ va_arg(ap, int32_t);
+ va_arg(ap, int32_t);
+ {
+ const char *uname = va_arg(ap, const char *);
+ const char *aname = va_arg(ap, const char *);
+ /* fid[4] afid[4] uname[s] aname[s] n_uname[4] */
+ return hdr + 4 + 4 + P9_STRLEN(uname) + P9_STRLEN(aname) + 4;
+ }
+ case P9_TWALK:
+ BUG_ON(strcmp("ddT", fmt));
+ va_arg(ap, int32_t);
+ va_arg(ap, int32_t);
+ {
+ uint i, nwname = va_arg(ap, int);
+ size_t wname_all;
+ const char **wnames = va_arg(ap, const char **);
+ for (i = 0, wname_all = 0; i < nwname; ++i) {
+ wname_all += P9_STRLEN(wnames[i]);
+ }
+ /* fid[4] newfid[4] nwname[2] nwname*(wname[s]) */
+ return hdr + 4 + 4 + 2 + wname_all;
+ }
+ case P9_RWALK:
+ BUG_ON(strcmp("ddT", fmt));
+ va_arg(ap, int32_t);
+ va_arg(ap, int32_t);
+ {
+ uint nwname = va_arg(ap, int);
+ /* nwqid[2] nwqid*(wqid[13]) */
+ return max_t(size_t, hdr + 2 + nwname * 13, err_size);
+ }
+ case P9_TCREATE:
+ BUG_ON(strcmp("dsdb?s", fmt));
+ va_arg(ap, int32_t);
+ {
+ const char *name = va_arg(ap, const char *);
+ if (c->proto_version == p9_proto_legacy) {
+ /* fid[4] name[s] perm[4] mode[1] */
+ return hdr + 4 + P9_STRLEN(name) + 4 + 1;
+ } else {
+ va_arg(ap, int32_t);
+ va_arg(ap, int);
+ {
+ const char *ext = va_arg(ap, const char *);
+ /* fid[4] name[s] perm[4] mode[1] extension[s] */
+ return hdr + 4 + P9_STRLEN(name) + 4 + 1 + P9_STRLEN(ext);
+ }
+ }
+ }
+ case P9_TLCREATE:
+ BUG_ON(strcmp("dsddg", fmt));
+ va_arg(ap, int32_t);
+ {
+ const char *name = va_arg(ap, const char *);
+ /* fid[4] name[s] flags[4] mode[4] gid[4] */
+ return hdr + 4 + P9_STRLEN(name) + 4 + 4 + 4;
+ }
+ case P9_RREAD:
+ case P9_RREADDIR:
+ BUG_ON(strcmp("dqd", fmt));
+ va_arg(ap, int32_t);
+ va_arg(ap, int64_t);
+ {
+ const int32_t count = va_arg(ap, int32_t);
+ /* count[4] data[count] */
+ return max_t(size_t, hdr + 4 + count, err_size);
+ }
+ case P9_TWRITE:
+ BUG_ON(strcmp("dqV", fmt));
+ va_arg(ap, int32_t);
+ va_arg(ap, int64_t);
+ {
+ const int32_t count = va_arg(ap, int32_t);
+ /* fid[4] offset[8] count[4] data[count] */
+ return hdr + 4 + 8 + 4 + count;
+ }
+ case P9_TRENAMEAT:
+ BUG_ON(strcmp("dsds", fmt));
+ va_arg(ap, int32_t);
+ {
+ const char *oldname, *newname;
+ oldname = va_arg(ap, const char *);
+ va_arg(ap, int32_t);
+ newname = va_arg(ap, const char *);
+ /* olddirfid[4] oldname[s] newdirfid[4] newname[s] */
+ return hdr + 4 + P9_STRLEN(oldname) + 4 + P9_STRLEN(newname);
+ }
+ case P9_TSYMLINK:
+ BUG_ON(strcmp("dssg", fmt));
+ va_arg(ap, int32_t);
+ {
+ const char *name = va_arg(ap, const char *);
+ const char *symtgt = va_arg(ap, const char *);
+ /* fid[4] name[s] symtgt[s] gid[4] */
+ return hdr + 4 + P9_STRLEN(name) + P9_STRLEN(symtgt) + 4;
+ }
+
+ case P9_RERROR:
+ return rerror_size;
+ case P9_RLERROR:
+ return rlerror_size;
+
+ /* small message types */
+ case P9_TWSTAT:
+ case P9_RSTAT:
+ case P9_RREADLINK:
+ case P9_TXATTRWALK:
+ case P9_TXATTRCREATE:
+ case P9_TLINK:
+ case P9_TMKDIR:
+ case P9_TMKNOD:
+ case P9_TRENAME:
+ case P9_TUNLINKAT:
+ case P9_TLOCK:
+ return 8 * 1024;
+
+ /* tiny message types */
+ default:
+ return 4 * 1024;
+
+ }
+}
+
+static int
+p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
+
+void p9stat_free(struct p9_wstat *stbuf)
+{
+ kfree(stbuf->name);
+ stbuf->name = NULL;
+ kfree(stbuf->uid);
+ stbuf->uid = NULL;
+ kfree(stbuf->gid);
+ stbuf->gid = NULL;
+ kfree(stbuf->muid);
+ stbuf->muid = NULL;
+ kfree(stbuf->extension);
+ stbuf->extension = NULL;
+}
+EXPORT_SYMBOL(p9stat_free);
+
+size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size)
+{
+ size_t len = min(pdu->size - pdu->offset, size);
+
+ memcpy(data, &pdu->sdata[pdu->offset], len);
+ pdu->offset += len;
+ return size - len;
+}
+
+static size_t pdu_write(struct p9_fcall *pdu, const void *data, size_t size)
+{
+ size_t len = min(pdu->capacity - pdu->size, size);
+
+ memcpy(&pdu->sdata[pdu->size], data, len);
+ pdu->size += len;
+ return size - len;
+}
+
+static size_t
+pdu_write_u(struct p9_fcall *pdu, struct iov_iter *from, size_t size)
+{
+ size_t len = min(pdu->capacity - pdu->size, size);
+
+ if (!copy_from_iter_full(&pdu->sdata[pdu->size], len, from))
+ len = 0;
+
+ pdu->size += len;
+ return size - len;
+}
+
+/* b - int8_t
+ * w - int16_t
+ * d - int32_t
+ * q - int64_t
+ * s - string
+ * u - numeric uid
+ * g - numeric gid
+ * S - stat
+ * Q - qid
+ * D - data blob (int32_t size followed by void *, results are not freed)
+ * T - array of strings (int16_t count, followed by strings)
+ * R - array of qids (int16_t count, followed by qids)
+ * A - stat for 9p2000.L (p9_stat_dotl)
+ * ? - if optional = 1, continue parsing
+ */
+
+static int
+p9pdu_vreadf(struct p9_fcall *pdu, int proto_version, const char *fmt,
+ va_list ap)
+{
+ const char *ptr;
+ int errcode = 0;
+
+ for (ptr = fmt; *ptr; ptr++) {
+ switch (*ptr) {
+ case 'b':{
+ int8_t *val = va_arg(ap, int8_t *);
+ if (pdu_read(pdu, val, sizeof(*val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ }
+ break;
+ case 'w':{
+ int16_t *val = va_arg(ap, int16_t *);
+ __le16 le_val;
+ if (pdu_read(pdu, &le_val, sizeof(le_val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ *val = le16_to_cpu(le_val);
+ }
+ break;
+ case 'd':{
+ int32_t *val = va_arg(ap, int32_t *);
+ __le32 le_val;
+ if (pdu_read(pdu, &le_val, sizeof(le_val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ *val = le32_to_cpu(le_val);
+ }
+ break;
+ case 'q':{
+ int64_t *val = va_arg(ap, int64_t *);
+ __le64 le_val;
+ if (pdu_read(pdu, &le_val, sizeof(le_val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ *val = le64_to_cpu(le_val);
+ }
+ break;
+ case 's':{
+ char **sptr = va_arg(ap, char **);
+ uint16_t len;
+
+ errcode = p9pdu_readf(pdu, proto_version,
+ "w", &len);
+ if (errcode)
+ break;
+
+ *sptr = kmalloc(len + 1, GFP_NOFS);
+ if (*sptr == NULL) {
+ errcode = -ENOMEM;
+ break;
+ }
+ if (pdu_read(pdu, *sptr, len)) {
+ errcode = -EFAULT;
+ kfree(*sptr);
+ *sptr = NULL;
+ } else
+ (*sptr)[len] = 0;
+ }
+ break;
+ case 'u': {
+ kuid_t *uid = va_arg(ap, kuid_t *);
+ __le32 le_val;
+ if (pdu_read(pdu, &le_val, sizeof(le_val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ *uid = make_kuid(le32_to_cpu(le_val));
+ } break;
+ case 'g': {
+ kgid_t *gid = va_arg(ap, kgid_t *);
+ __le32 le_val;
+ if (pdu_read(pdu, &le_val, sizeof(le_val))) {
+ errcode = -EFAULT;
+ break;
+ }
+ *gid = make_kgid(le32_to_cpu(le_val));
+ } break;
+ case 'Q':{
+ struct p9_qid *qid =
+ va_arg(ap, struct p9_qid *);
+
+ errcode = p9pdu_readf(pdu, proto_version, "bdq",
+ &qid->type, &qid->version,
+ &qid->path);
+ }
+ break;
+ case 'S':{
+ struct p9_wstat *stbuf =
+ va_arg(ap, struct p9_wstat *);
+
+ memset(stbuf, 0, sizeof(struct p9_wstat));
+ stbuf->n_uid = stbuf->n_muid = INVALID_UID;
+ stbuf->n_gid = INVALID_GID;
+
+ errcode =
+ p9pdu_readf(pdu, proto_version,
+ "wwdQdddqssss?sugu",
+ &stbuf->size, &stbuf->type,
+ &stbuf->dev, &stbuf->qid,
+ &stbuf->mode, &stbuf->atime,
+ &stbuf->mtime, &stbuf->length,
+ &stbuf->name, &stbuf->uid,
+ &stbuf->gid, &stbuf->muid,
+ &stbuf->extension,
+ &stbuf->n_uid, &stbuf->n_gid,
+ &stbuf->n_muid);
+ if (errcode)
+ p9stat_free(stbuf);
+ }
+ break;
+ case 'D':{
+ uint32_t *count = va_arg(ap, uint32_t *);
+ void **data = va_arg(ap, void **);
+
+ errcode =
+ p9pdu_readf(pdu, proto_version, "d", count);
+ if (!errcode) {
+ *count =
+ min_t(uint32_t, *count,
+ pdu->size - pdu->offset);
+ *data = &pdu->sdata[pdu->offset];
+ }
+ }
+ break;
+ case 'T':{
+ uint16_t *nwname = va_arg(ap, uint16_t *);
+ char ***wnames = va_arg(ap, char ***);
+
+ *wnames = NULL;
+
+ errcode = p9pdu_readf(pdu, proto_version,
+ "w", nwname);
+ if (!errcode) {
+ *wnames =
+ kmalloc_array(*nwname,
+ sizeof(char *),
+ GFP_NOFS);
+ if (!*wnames)
+ errcode = -ENOMEM;
+ else
+ (*wnames)[0] = NULL;
+ }
+
+ if (!errcode) {
+ int i;
+
+ for (i = 0; i < *nwname; i++) {
+ errcode =
+ p9pdu_readf(pdu,
+ proto_version,
+ "s",
+ &(*wnames)[i]);
+ if (errcode) {
+ (*wnames)[i] = NULL;
+ break;
+ }
+ }
+ }
+
+ if (errcode) {
+ if (*wnames) {
+ int i;
+
+ for (i = 0; i < *nwname; i++) {
+ if (!(*wnames)[i])
+ break;
+ kfree((*wnames)[i]);
+ }
+ kfree(*wnames);
+ *wnames = NULL;
+ }
+ }
+ }
+ break;
+ case 'R':{
+ uint16_t *nwqid = va_arg(ap, uint16_t *);
+ struct p9_qid **wqids =
+ va_arg(ap, struct p9_qid **);
+
+ *wqids = NULL;
+
+ errcode =
+ p9pdu_readf(pdu, proto_version, "w", nwqid);
+ if (!errcode) {
+ *wqids =
+ kmalloc_array(*nwqid,
+ sizeof(struct p9_qid),
+ GFP_NOFS);
+ if (*wqids == NULL)
+ errcode = -ENOMEM;
+ }
+
+ if (!errcode) {
+ int i;
+
+ for (i = 0; i < *nwqid; i++) {
+ errcode =
+ p9pdu_readf(pdu,
+ proto_version,
+ "Q",
+ &(*wqids)[i]);
+ if (errcode)
+ break;
+ }
+ }
+
+ if (errcode) {
+ kfree(*wqids);
+ *wqids = NULL;
+ }
+ }
+ break;
+ case 'A': {
+ struct p9_stat_dotl *stbuf =
+ va_arg(ap, struct p9_stat_dotl *);
+
+ memset(stbuf, 0, sizeof(struct p9_stat_dotl));
+ errcode =
+ p9pdu_readf(pdu, proto_version,
+ "qQdugqqqqqqqqqqqqqqq",
+ &stbuf->st_result_mask,
+ &stbuf->qid,
+ &stbuf->st_mode,
+ &stbuf->st_uid, &stbuf->st_gid,
+ &stbuf->st_nlink,
+ &stbuf->st_rdev, &stbuf->st_size,
+ &stbuf->st_blksize, &stbuf->st_blocks,
+ &stbuf->st_atime_sec,
+ &stbuf->st_atime_nsec,
+ &stbuf->st_mtime_sec,
+ &stbuf->st_mtime_nsec,
+ &stbuf->st_ctime_sec,
+ &stbuf->st_ctime_nsec,
+ &stbuf->st_btime_sec,
+ &stbuf->st_btime_nsec,
+ &stbuf->st_gen,
+ &stbuf->st_data_version);
+ }
+ break;
+ case '?':
+ if ((proto_version != p9_proto_2000u) &&
+ (proto_version != p9_proto_2000L))
+ return 0;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ if (errcode)
+ break;
+ }
+
+ return errcode;
+}
+
+int
+p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
+ va_list ap)
+{
+ const char *ptr;
+ int errcode = 0;
+
+ for (ptr = fmt; *ptr; ptr++) {
+ switch (*ptr) {
+ case 'b':{
+ int8_t val = va_arg(ap, int);
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ }
+ break;
+ case 'w':{
+ __le16 val = cpu_to_le16(va_arg(ap, int));
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ }
+ break;
+ case 'd':{
+ __le32 val = cpu_to_le32(va_arg(ap, int32_t));
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ }
+ break;
+ case 'q':{
+ __le64 val = cpu_to_le64(va_arg(ap, int64_t));
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ }
+ break;
+ case 's':{
+ const char *sptr = va_arg(ap, const char *);
+ uint16_t len = 0;
+ if (sptr)
+ len = min_t(size_t, strlen(sptr),
+ USHRT_MAX);
+
+ errcode = p9pdu_writef(pdu, proto_version,
+ "w", len);
+ if (!errcode && pdu_write(pdu, sptr, len))
+ errcode = -EFAULT;
+ }
+ break;
+ case 'u': {
+ kuid_t uid = va_arg(ap, kuid_t);
+ __le32 val = cpu_to_le32(
+ from_kuid(uid));
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ } break;
+ case 'g': {
+ kgid_t gid = va_arg(ap, kgid_t);
+ __le32 val = cpu_to_le32(
+ from_kgid(gid));
+ if (pdu_write(pdu, &val, sizeof(val)))
+ errcode = -EFAULT;
+ } break;
+ case 'Q':{
+ const struct p9_qid *qid =
+ va_arg(ap, const struct p9_qid *);
+ errcode =
+ p9pdu_writef(pdu, proto_version, "bdq",
+ qid->type, qid->version,
+ qid->path);
+ } break;
+ case 'S':{
+ const struct p9_wstat *stbuf =
+ va_arg(ap, const struct p9_wstat *);
+ errcode =
+ p9pdu_writef(pdu, proto_version,
+ "wwdQdddqssss?sugu",
+ stbuf->size, stbuf->type,
+ stbuf->dev, &stbuf->qid,
+ stbuf->mode, stbuf->atime,
+ stbuf->mtime, stbuf->length,
+ stbuf->name, stbuf->uid,
+ stbuf->gid, stbuf->muid,
+ stbuf->extension, stbuf->n_uid,
+ stbuf->n_gid, stbuf->n_muid);
+ } break;
+ case 'V':{
+ uint32_t count = va_arg(ap, uint32_t);
+ struct iov_iter *from =
+ va_arg(ap, struct iov_iter *);
+ errcode = p9pdu_writef(pdu, proto_version, "d",
+ count);
+ if (!errcode && pdu_write_u(pdu, from, count))
+ errcode = -EFAULT;
+ }
+ break;
+ case 'T':{
+ uint16_t nwname = va_arg(ap, int);
+ const char **wnames = va_arg(ap, const char **);
+
+ errcode = p9pdu_writef(pdu, proto_version, "w",
+ nwname);
+ if (!errcode) {
+ int i;
+
+ for (i = 0; i < nwname; i++) {
+ errcode =
+ p9pdu_writef(pdu,
+ proto_version,
+ "s",
+ wnames[i]);
+ if (errcode)
+ break;
+ }
+ }
+ }
+ break;
+ case 'R':{
+ uint16_t nwqid = va_arg(ap, int);
+ struct p9_qid *wqids =
+ va_arg(ap, struct p9_qid *);
+
+ errcode = p9pdu_writef(pdu, proto_version, "w",
+ nwqid);
+ if (!errcode) {
+ int i;
+
+ for (i = 0; i < nwqid; i++) {
+ errcode =
+ p9pdu_writef(pdu,
+ proto_version,
+ "Q",
+ &wqids[i]);
+ if (errcode)
+ break;
+ }
+ }
+ }
+ break;
+ case 'I':{
+ struct p9_iattr_dotl *p9attr = va_arg(ap,
+ struct p9_iattr_dotl *);
+
+ errcode = p9pdu_writef(pdu, proto_version,
+ "ddugqqqqq",
+ p9attr->valid,
+ p9attr->mode,
+ p9attr->uid,
+ p9attr->gid,
+ p9attr->size,
+ p9attr->atime_sec,
+ p9attr->atime_nsec,
+ p9attr->mtime_sec,
+ p9attr->mtime_nsec);
+ }
+ break;
+ case '?':
+ if ((proto_version != p9_proto_2000u) &&
+ (proto_version != p9_proto_2000L))
+ return 0;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ if (errcode)
+ break;
+ }
+
+ return errcode;
+}
+
+int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = p9pdu_vreadf(pdu, proto_version, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+static int
+p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = p9pdu_vwritef(pdu, proto_version, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+int p9stat_read(struct p9_client *clnt, char *buf, int len, struct p9_wstat *st)
+{
+ struct p9_fcall fake_pdu;
+ int ret;
+
+ fake_pdu.size = len;
+ fake_pdu.capacity = len;
+ fake_pdu.sdata = buf;
+ fake_pdu.offset = 0;
+
+ ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "S", st);
+ if (ret) {
+ p9_debug(P9_DEBUG_9P, "<<< p9stat_read failed: %d\n", ret);
+ trace_9p_protocol_dump(clnt, &fake_pdu);
+ return ret;
+ }
+
+ return fake_pdu.offset;
+}
+EXPORT_SYMBOL(p9stat_read);
+
+int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type)
+{
+ pdu->id = type;
+ return p9pdu_writef(pdu, 0, "dbw", 0, type, tag);
+}
+
+int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu)
+{
+ int size = pdu->size;
+ int err;
+
+ pdu->size = 0;
+ err = p9pdu_writef(pdu, 0, "d", size);
+ pdu->size = size;
+
+ trace_9p_protocol_dump(clnt, pdu);
+ p9_debug(P9_DEBUG_9P, ">>> size=%d type: %d tag: %d\n",
+ pdu->size, pdu->id, pdu->tag);
+
+ return err;
+}
+
+void p9pdu_reset(struct p9_fcall *pdu)
+{
+ pdu->offset = 0;
+ pdu->size = 0;
+}
+
+int p9dirent_read(struct p9_client *clnt, char *buf, int len,
+ struct p9_dirent *dirent)
+{
+ struct p9_fcall fake_pdu;
+ int ret;
+ char *nameptr;
+
+ fake_pdu.size = len;
+ fake_pdu.capacity = len;
+ fake_pdu.sdata = buf;
+ fake_pdu.offset = 0;
+
+ ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "Qqbs", &dirent->qid,
+ &dirent->d_off, &dirent->d_type, &nameptr);
+ if (ret) {
+ p9_debug(P9_DEBUG_9P, "<<< p9dirent_read failed: %d\n", ret);
+ trace_9p_protocol_dump(clnt, &fake_pdu);
+ return ret;
+ }
+
+ ret = strscpy(dirent->d_name, nameptr, sizeof(dirent->d_name));
+ if (ret < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "On the wire dirent name too long: %s\n",
+ nameptr);
+ kfree(nameptr);
+ return ret;
+ }
+ kfree(nameptr);
+
+ return fake_pdu.offset;
+}
+EXPORT_SYMBOL(p9dirent_read);
diff --git a/net/9p/protocol.h b/net/9p/protocol.h
new file mode 100644
index 000000000000..ad2283d1f96b
--- /dev/null
+++ b/net/9p/protocol.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * 9P Protocol Support Code
+ *
+ * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ *
+ * Base on code from Anthony Liguori <aliguori@us.ibm.com>
+ * Copyright (C) 2008 by IBM, Corp.
+ */
+
+size_t p9_msg_buf_size(struct p9_client *c, enum p9_msg_t type,
+ const char *fmt, va_list ap);
+int p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
+ va_list ap);
+int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
+int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type);
+int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu);
+void p9pdu_reset(struct p9_fcall *pdu);
+size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size);
diff --git a/net/9p/trans_virtio.c b/net/9p/trans_virtio.c
new file mode 100644
index 000000000000..1cbbca4a27a2
--- /dev/null
+++ b/net/9p/trans_virtio.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * The Virtio 9p transport driver
+ *
+ * This is a block based transport driver based on the lguest block driver
+ * code.
+ *
+ * Copyright (C) 2007, 2008 Eric Van Hensbergen, IBM Corporation
+ *
+ * Based on virtio console driver
+ * Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/pagemap.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/virtio_ring.h>
+#include <net/9p/9p.h>
+#include <linux/parser.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+#include <linux/virtio.h>
+#include <linux/virtio_9p.h>
+
+#include <sys/stat.h>
+#include <fs.h>
+
+#define VIRTQUEUE_NUM 128
+
+/* a single mutex to manage channel initialization and attachment */
+static DEFINE_MUTEX(virtio_9p_lock);
+
+/**
+ * struct virtio_chan - per-instance transport information
+ * @inuse: whether the channel is in use
+ * @lock: protects multiple elements within this structure
+ * @client: client instance
+ * @vdev: virtio dev associated with this channel
+ * @vq: virtio queue associated with this channel
+ * @ring_bufs_avail: flag to indicate there is some available in the ring buf
+ * @sg: scatter gather list which is used to pack a request (protected?)
+ * @chan_list: linked list of channels
+ *
+ * We keep all per-channel information in a structure.
+ * This structure is allocated within the devices dev->mem space.
+ * A pointer to the structure will get put in the transport private.
+ *
+ */
+
+struct virtio_chan {
+ bool inuse;
+
+ spinlock_t lock;
+
+ struct p9_client *client;
+ struct virtio_device *vdev;
+ struct virtqueue *vq;
+ int ring_bufs_avail;
+ /* Scatterlist: can be too big for stack. */
+ struct scatterlist sg[VIRTQUEUE_NUM];
+ /**
+ * @tag: name to identify a mount null terminated
+ */
+ char *tag;
+
+ struct list_head chan_list;
+};
+
+static struct list_head virtio_chan_list;
+
+/**
+ * p9_virtio_close - reclaim resources of a channel
+ * @client: client instance
+ *
+ * This reclaims a channel by freeing its resources and
+ * resetting its inuse flag.
+ *
+ */
+
+static void p9_virtio_close(struct p9_client *client)
+{
+ struct virtio_chan *chan = client->trans;
+
+ mutex_lock(&virtio_9p_lock);
+ if (chan)
+ chan->inuse = false;
+ mutex_unlock(&virtio_9p_lock);
+}
+/* We don't currently allow canceling of virtio requests */
+static int p9_virtio_cancel(struct p9_client *client, struct p9_req_t *req)
+{
+ return 1;
+}
+
+/* Reply won't come, so drop req ref */
+static int p9_virtio_cancelled(struct p9_client *client, struct p9_req_t *req)
+{
+ p9_req_put(client, req);
+ return 0;
+}
+
+/**
+ * p9_virtio_request - issue a request
+ * @client: client instance issuing the request
+ * @req: request to be issued
+ *
+ */
+
+static int
+p9_virtio_request(struct p9_client *client, struct p9_req_t *req)
+{
+
+ int err;
+ int out_sgs, in_sgs;
+ unsigned long flags;
+ struct virtio_chan *chan = client->trans;
+ struct scatterlist *sgs[2];
+
+ p9_debug(P9_DEBUG_TRANS, "9p debug: virtio request\n");
+
+ WRITE_ONCE(req->status, REQ_STATUS_SENT);
+ spin_lock_irqsave(&chan->lock, flags);
+
+ out_sgs = in_sgs = 0;
+
+ /* Handle out VirtIO ring buffers */
+ if (req->tc.size) {
+ sg_init_one(chan->sg, req->tc.sdata, req->tc.size);
+ sgs[out_sgs++] = chan->sg;
+ }
+
+ if (req->rc.capacity) {
+ sg_init_one(&chan->sg[1], req->rc.sdata, req->rc.capacity);
+ sgs[out_sgs + in_sgs++] = &chan->sg[1];
+ }
+
+ err = virtqueue_add_sgs(chan->vq, sgs, out_sgs, in_sgs, req);
+ if (err < 0) {
+ spin_unlock_irqrestore(&chan->lock, flags);
+ p9_debug(P9_DEBUG_TRANS,
+ "virtio rpc add_sgs returned failure\n");
+ return err == -ENOSPC ? -ENOSPC : -EIO;
+ }
+ virtqueue_kick(chan->vq);
+ spin_unlock_irqrestore(&chan->lock, flags);
+
+ p9_debug(P9_DEBUG_TRANS, "virtio request kicked\n");
+
+ return 0;
+}
+
+/**
+ * p9_virtio_poll - poll for request completion
+ * @client: client instance issuing the request
+ */
+
+static int
+p9_virtio_poll(struct p9_client *client)
+{
+ struct virtio_chan *chan = client->trans;
+ unsigned int len;
+ struct p9_req_t *req;
+
+ while ((req = virtqueue_get_buf(chan->vq, &len)) != NULL) {
+ p9_debug(P9_DEBUG_TRANS, ": request done\n");
+
+ if (len) {
+ req->rc.size = len;
+ p9_client_cb(chan->client, req, REQ_STATUS_RCVD);
+ }
+ }
+
+ return 0;
+}
+
+static void p9_mount_tag_show(struct device *dev)
+{
+ struct virtio_chan *chan;
+ struct virtio_device *vdev;
+ int tag_len;
+
+ vdev = dev_to_virtio(dev);
+ chan = vdev->priv;
+ tag_len = strlen(chan->tag);
+
+ printf("%.*s\n", tag_len + 1, chan->tag);
+}
+
+/**
+ * p9_virtio_probe - probe for existence of 9P virtio channels
+ * @vdev: virtio device to probe
+ *
+ * This probes for existing virtio channels.
+ *
+ */
+
+static int p9_virtio_probe(struct virtio_device *vdev)
+{
+ __u16 tag_len;
+ char *tag;
+ int err;
+ struct virtio_chan *chan;
+ char *path, *cmd;
+
+ if (!vdev->config->get_config) {
+ dev_err(&vdev->dev, "%s failure: config access disabled\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ chan = kmalloc(sizeof(struct virtio_chan), GFP_KERNEL);
+ if (!chan) {
+ pr_err("Failed to allocate virtio 9P channel\n");
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ chan->vdev = vdev;
+
+ /* We expect one virtqueue, for requests. */
+ err = virtio_find_vqs(vdev, 1, &chan->vq);
+ if (err)
+ goto out_free_chan;
+
+ chan->vq->vdev->priv = chan;
+ spin_lock_init(&chan->lock);
+
+ sg_init_table(chan->sg, VIRTQUEUE_NUM);
+
+ chan->inuse = false;
+ if (virtio_has_feature(vdev, VIRTIO_9P_MOUNT_TAG)) {
+ virtio_cread(vdev, struct virtio_9p_config, tag_len, &tag_len);
+ } else {
+ err = -EINVAL;
+ goto out_free_vq;
+ }
+ tag = kzalloc(tag_len + 1, GFP_KERNEL);
+ if (!tag) {
+ err = -ENOMEM;
+ goto out_free_vq;
+ }
+
+ virtio_cread_bytes(vdev, offsetof(struct virtio_9p_config, tag),
+ tag, tag_len);
+ chan->tag = tag;
+
+ chan->ring_bufs_avail = 1;
+
+ virtio_device_ready(vdev);
+
+ mutex_lock(&virtio_9p_lock);
+ list_add_tail(&chan->chan_list, &virtio_chan_list);
+ mutex_unlock(&virtio_9p_lock);
+
+ devinfo_add(&vdev->dev, p9_mount_tag_show);
+
+ path = xasprintf("/mnt/9p/%s", chan->tag);
+ cmd = xasprintf("mount -t 9p -o trans=virtio %s %s", chan->tag, path);
+
+ if (mkdir(path, 0755) == 0) {
+ err = automount_add(path, cmd);
+ if (err)
+ dev_warn(&vdev->dev,
+ "adding automountpoint at %s failed: %pe\n",
+ path, ERR_PTR(err));
+ }
+
+ free(cmd);
+ free(path);
+
+ return 0;
+
+ kfree(tag);
+out_free_vq:
+ vdev->config->del_vqs(vdev);
+out_free_chan:
+ kfree(chan);
+fail:
+ return err;
+}
+
+
+/**
+ * p9_virtio_create - allocate a new virtio channel
+ * @client: client instance invoking this transport
+ * @devname: string identifying the channel to connect to (unused)
+ * @args: args passed from sys_mount() for per-transport options (unused)
+ *
+ * This sets up a transport channel for 9p communication. Right now
+ * we only match the first available channel, but eventually we could look up
+ * alternate channels by matching devname versus a virtio_config entry.
+ * We use a simple reference count mechanism to ensure that only a single
+ * mount has a channel open at a time.
+ *
+ */
+
+static int
+p9_virtio_create(struct p9_client *client, const char *devname, char *args)
+{
+ struct virtio_chan *chan;
+ int ret = -ENOENT;
+ int found = 0;
+
+ if (devname == NULL)
+ return -EINVAL;
+
+ mutex_lock(&virtio_9p_lock);
+ list_for_each_entry(chan, &virtio_chan_list, chan_list) {
+ if (!strcmp(devname, chan->tag)) {
+ if (!chan->inuse) {
+ chan->inuse = true;
+ found = 1;
+ break;
+ }
+ ret = -EBUSY;
+ }
+ }
+ mutex_unlock(&virtio_9p_lock);
+
+ if (!found) {
+ pr_err("no channels available for device %s\n", devname);
+ return ret;
+ }
+
+ client->trans = (void *)chan;
+ client->status = Connected;
+ client->trans_tag = chan->tag;
+ chan->client = client;
+
+ return 0;
+}
+
+/**
+ * p9_virtio_remove - clean up resources associated with a virtio device
+ * @vdev: virtio device to remove
+ *
+ */
+
+static void p9_virtio_remove(struct virtio_device *vdev)
+{
+ struct virtio_chan *chan = vdev->priv;
+
+ mutex_lock(&virtio_9p_lock);
+
+ /* Remove self from list so we don't get new users. */
+ list_del(&chan->chan_list);
+
+ /* Wait for existing users to close. */
+ if (chan->inuse)
+ dev_emerg(&vdev->dev,
+ "p9_virtio_remove: device in use.\n");
+
+ vdev->config->reset(vdev);
+ vdev->config->del_vqs(vdev);
+
+ kfree(chan->tag);
+ kfree(chan);
+}
+
+static struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_9P, VIRTIO_DEV_ANY_ID },
+ { 0 },
+};
+
+static unsigned int features[] = {
+ VIRTIO_9P_MOUNT_TAG,
+};
+
+/* The standard "struct lguest_driver": */
+static struct virtio_driver p9_virtio_drv = {
+ .feature_table = features,
+ .feature_table_size = ARRAY_SIZE(features),
+ .driver.name = KBUILD_MODNAME,
+ .id_table = id_table,
+ .probe = p9_virtio_probe,
+ .remove = p9_virtio_remove,
+};
+
+static struct p9_trans_module p9_virtio_trans = {
+ .name = "virtio",
+ .create = p9_virtio_create,
+ .close = p9_virtio_close,
+ .request = p9_virtio_request,
+ .poll = p9_virtio_poll,
+ .cancel = p9_virtio_cancel,
+ .cancelled = p9_virtio_cancelled,
+ /*
+ * We leave one entry for input and one entry for response
+ * headers. We also skip one more entry to accommodate, address
+ * that are not at page boundary, that can result in an extra
+ * page in zero copy.
+ */
+ .maxsize = PAGE_SIZE * (VIRTQUEUE_NUM - 3),
+ .pooled_rbuffers = false,
+ .def = 1,
+};
+
+/* The standard init function */
+static int __init p9_virtio_init(void)
+{
+ int rc;
+
+ INIT_LIST_HEAD(&virtio_chan_list);
+
+ v9fs_register_trans(&p9_virtio_trans);
+ rc = virtio_driver_register(&p9_virtio_drv);
+ if (rc)
+ v9fs_unregister_trans(&p9_virtio_trans);
+
+ return rc;
+}
+device_initcall(p9_virtio_init);
+MODULE_ALIAS_9P("virtio");
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
+MODULE_DESCRIPTION("Virtio 9p Transport");
+MODULE_LICENSE("GPL");
diff --git a/net/Kconfig b/net/Kconfig
index b089806457d8..a37eff60a12f 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -59,4 +59,6 @@ config NET_FASTBOOT
This option adds support for the UDP variant of the Fastboot
protocol.
+source "net/9p/Kconfig"
+
endif
diff --git a/net/Makefile b/net/Makefile
index 2837dc25682e..8994e08447f5 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_NET_RESOLV)+= dns.o
obj-$(CONFIG_NET_NETCONSOLE) += netconsole.o
obj-$(CONFIG_NET_IFUP) += ifup.o
obj-$(CONFIG_NET_FASTBOOT) += fastboot.o
+obj-$(CONFIG_NET_9P) += 9p/
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 08/10] fs: add new 9P2000.l (Plan 9) File system support
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (6 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 07/10] net: add support for 9P protocol Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 09/10] fs: 9p: enable 9P over Virt I/O transport in defconfigs Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 10/10] test: add support for --fs option in QEMU Ahmad Fatoum
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
Building on top of the networking implementation added in the previous
commit, write it to the barebox file system support.
The new file system is both readable and writable and has been tested
against Qemu and NFS Ganesha.
Note that symlink usage in Qemu results in ELOOP, but doesn't in
Ganesha.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
Documentation/user/virtio.rst | 32 +-
common/startup.c | 3 +
fs/9p/Kconfig | 18 ++
fs/9p/Makefile | 12 +
fs/9p/fid.c | 295 +++++++++++++++++
fs/9p/fid.h | 37 +++
fs/9p/v9fs.c | 373 ++++++++++++++++++++++
fs/9p/v9fs.h | 183 +++++++++++
fs/9p/v9fs_vfs.h | 74 +++++
fs/9p/vfs_addr.c | 111 +++++++
fs/9p/vfs_dir.c | 172 ++++++++++
fs/9p/vfs_file.c | 67 ++++
fs/9p/vfs_inode.c | 279 ++++++++++++++++
fs/9p/vfs_inode_dotl.c | 579 ++++++++++++++++++++++++++++++++++
fs/9p/vfs_super.c | 243 ++++++++++++++
fs/Kconfig | 2 +
fs/Makefile | 1 +
fs/fs.c | 9 +
include/linux/fs.h | 47 +++
include/linux/limits.h | 1 +
include/linux/module.h | 1 +
include/linux/stat.h | 4 +
22 files changed, 2541 insertions(+), 2 deletions(-)
create mode 100644 fs/9p/Kconfig
create mode 100644 fs/9p/Makefile
create mode 100644 fs/9p/fid.c
create mode 100644 fs/9p/fid.h
create mode 100644 fs/9p/v9fs.c
create mode 100644 fs/9p/v9fs.h
create mode 100644 fs/9p/v9fs_vfs.h
create mode 100644 fs/9p/vfs_addr.c
create mode 100644 fs/9p/vfs_dir.c
create mode 100644 fs/9p/vfs_file.c
create mode 100644 fs/9p/vfs_inode.c
create mode 100644 fs/9p/vfs_inode_dotl.c
create mode 100644 fs/9p/vfs_super.c
diff --git a/Documentation/user/virtio.rst b/Documentation/user/virtio.rst
index 046e5dac8225..4a46713f66aa 100644
--- a/Documentation/user/virtio.rst
+++ b/Documentation/user/virtio.rst
@@ -34,8 +34,15 @@ specification. Therefore most operations including device initialization,
queues configuration and buffer transfers are nearly identical. Both MMIO
and non-legacy PCI are supported in barebox.
-The VirtIO spec defines a lots of VirtIO device types, however at present only
-block, network, console, input and RNG devices are supported.
+The VirtIO spec defines a lots of VirtIO device types. barebox currently
+supports the following:
+
+ * Block
+ * Network
+ * Console
+ * Random Number Generator (RNG)
+ * Input
+ * File System (virtfs/9p)
Build Instructions
------------------
@@ -86,3 +93,24 @@ Depending on QEMU version used, it may be required to add
.. _VirtIO: http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.pdf
.. _qemu: https://www.qemu.org
+
+VirtFS
+~~~~~~
+
+The current working directory can be passed to the guest via the ``virtio-9p``
+device::
+
+ qemu-system-aarch64 -kernel barebox-dt-2nd.img -machine virt,highmem=off \
+ -fsdev local,security_model=mapped,id=fsdev0,path=. \
+ -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare
+ -cpu cortex-a57 -m 1024M -nographic \
+ -serial mon:stdio -trace file=/dev/null
+
+The file system can then be mounted in bareboxvia::
+
+ mkdir -p /mnt/9p/hostshare
+ mount -t 9p -o trans=virtio hostshare /mnt/9p/hostshare
+
+For ease of use, automounts units will automatically be created in ``/mnt/9p/``,
+so for a given ``mount_tag``, the file system wil automatically be mounted
+on first access to ``/mnt/9p/$mount_tag`` in barebox.
diff --git a/common/startup.c b/common/startup.c
index c9e99d47fbd7..49eaa4edc6d4 100644
--- a/common/startup.c
+++ b/common/startup.c
@@ -75,6 +75,9 @@ static int mount_root(void)
automount_add("/mnt/smhfs", "mount -t smhfs /dev/null /mnt/smhfs");
}
+ if (IS_ENABLED(CONFIG_9P_FS))
+ mkdir("/mnt/9p", 0);
+
return 0;
}
fs_initcall(mount_root);
diff --git a/fs/9p/Kconfig b/fs/9p/Kconfig
new file mode 100644
index 000000000000..0e43e2a4ac10
--- /dev/null
+++ b/fs/9p/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config 9P_FS
+ tristate "Plan 9 Resource Sharing Support (9P2000)"
+ depends on NET_9P
+ help
+ If you say Y here, you will get experimental support for
+ Plan 9 resource sharing via the 9P2000 protocol.
+
+ See <http://v9fs.sf.net> for more information.
+
+ If unsure, say N.
+
+config 9P_FS_WRITE
+ tristate "9P2000 Write Support"
+ depends on 9P_FS
+ select FS_WRITABLE
+ help
+ Enable support for writing in 9P filesystems.
diff --git a/fs/9p/Makefile b/fs/9p/Makefile
new file mode 100644
index 000000000000..820e253f3f9d
--- /dev/null
+++ b/fs/9p/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_9P_FS) := 9p.o
+
+9p-objs := \
+ vfs_super.o \
+ vfs_inode.o \
+ vfs_inode_dotl.o \
+ vfs_addr.o \
+ vfs_file.o \
+ vfs_dir.o \
+ v9fs.o \
+ fid.o
diff --git a/fs/9p/fid.c b/fs/9p/fid.c
new file mode 100644
index 000000000000..de754ca5fdc9
--- /dev/null
+++ b/fs/9p/fid.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * V9FS FID Management
+ *
+ * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
+ * Copyright (C) 2005, 2006 by Eric Van Hensbergen <ericvh@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/minmax.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid)
+{
+ hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata);
+}
+
+/**
+ * v9fs_fid_add - add a fid to a dentry
+ * @dentry: dentry that the fid is being added to
+ * @pfid: fid to add, NULLed out
+ *
+ */
+void v9fs_fid_add(struct dentry *dentry, struct p9_fid **pfid)
+{
+ struct p9_fid *fid = *pfid;
+
+ spin_lock(&dentry->d_lock);
+ __add_fid(dentry, fid);
+ spin_unlock(&dentry->d_lock);
+
+ *pfid = NULL;
+}
+
+/**
+ * v9fs_fid_find_inode - search for an open fid off of the inode list
+ * @inode: return a fid pointing to a specific inode
+ * @want_writeable: only consider fids which are writeable
+ * @uid: return a fid belonging to the specified user
+ * @any: ignore uid as a selection criteria
+ *
+ */
+struct p9_fid *v9fs_fid_find_inode(struct inode *inode, bool want_writeable,
+ kuid_t uid, bool any)
+{
+ struct hlist_head *h;
+ struct p9_fid *fid, *ret = NULL;
+
+ p9_debug(P9_DEBUG_VFS, " inode: %p\n", inode);
+
+ spin_lock(&inode->i_lock);
+ h = (struct hlist_head *)&inode->i_private;
+ hlist_for_each_entry(fid, h, ilist) {
+ if (want_writeable) {
+ p9_debug(P9_DEBUG_VFS, " mode: %x not writeable?\n",
+ fid->mode);
+ continue;
+ }
+ p9_fid_get(fid);
+ ret = fid;
+ break;
+ }
+ spin_unlock(&inode->i_lock);
+ return ret;
+}
+
+/**
+ * v9fs_open_fid_add - add an open fid to an inode
+ * @inode: inode that the fid is being added to
+ * @pfid: fid to add, NULLed out
+ *
+ */
+
+void v9fs_open_fid_add(struct inode *inode, struct p9_fid **pfid)
+{
+ struct p9_fid *fid = *pfid;
+
+ spin_lock(&inode->i_lock);
+ hlist_add_head(&fid->ilist, (struct hlist_head *)&inode->i_private);
+ spin_unlock(&inode->i_lock);
+
+ *pfid = NULL;
+}
+
+
+/**
+ * v9fs_fid_find - retrieve a fid that belongs to the specified uid
+ * @dentry: dentry to look for fid in
+ * @uid: return fid that belongs to the specified user
+ * @any: if non-zero, return any fid associated with the dentry
+ *
+ */
+
+static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any)
+{
+ struct p9_fid *fid, *ret;
+
+ p9_debug(P9_DEBUG_VFS, " dentry: %s (%p) uid %d any %d\n",
+ dentry->d_name.name, dentry, from_kuid(uid),
+ any);
+ ret = NULL;
+ /* we'll recheck under lock if there's anything to look in */
+ if (dentry->d_fsdata) {
+ struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata;
+
+ spin_lock(&dentry->d_lock);
+ hlist_for_each_entry(fid, h, dlist) {
+ if (any || uid_eq(fid->uid, uid)) {
+ ret = fid;
+ p9_fid_get(ret);
+ break;
+ }
+ }
+ spin_unlock(&dentry->d_lock);
+ } else {
+ if (dentry->d_inode)
+ ret = v9fs_fid_find_inode(dentry->d_inode, false, uid, any);
+ }
+
+ return ret;
+}
+
+/*
+ * We need to hold v9ses->rename_sem as long as we hold references
+ * to returned path array. Array element contain pointers to
+ * dentry names.
+ */
+static int build_path_from_dentry(struct v9fs_session_info *v9ses,
+ struct dentry *dentry, const unsigned char ***names)
+{
+ int n = 0, i;
+ const unsigned char **wnames;
+ struct dentry *ds;
+
+ for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent)
+ n++;
+
+ wnames = kmalloc_array(n, sizeof(char *), GFP_KERNEL);
+ if (!wnames)
+ goto err_out;
+
+ for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent)
+ wnames[i] = ds->d_name.name;
+
+ *names = wnames;
+ return n;
+err_out:
+ return -ENOMEM;
+}
+
+static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry,
+ kuid_t uid, int any)
+{
+ struct dentry *ds;
+ const unsigned char **wnames, *uname;
+ int i, n, l, access;
+ struct v9fs_session_info *v9ses;
+ struct p9_fid *fid, *root_fid, *old_fid;
+
+ v9ses = v9fs_dentry2v9ses(dentry);
+ access = v9ses->flags & V9FS_ACCESS_MASK;
+ fid = v9fs_fid_find(dentry, uid, any);
+ if (fid)
+ return fid;
+ /*
+ * we don't have a matching fid. To do a TWALK we need
+ * parent fid. We need to prevent rename when we want to
+ * look at the parent.
+ */
+ down_read(&v9ses->rename_sem);
+ ds = dentry->d_parent;
+ fid = v9fs_fid_find(ds, uid, any);
+ if (fid) {
+ /* Found the parent fid do a lookup with that */
+ old_fid = fid;
+
+ fid = p9_client_walk(old_fid, 1, &dentry->d_name.name, 1);
+ p9_fid_put(old_fid);
+ goto fid_out;
+ }
+ up_read(&v9ses->rename_sem);
+
+ /* start from the root and try to do a lookup */
+ root_fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any);
+ if (!root_fid) {
+ /* the user is not attached to the fs yet */
+ if (access == V9FS_ACCESS_SINGLE)
+ return ERR_PTR(-EPERM);
+
+ uname = NULL;
+
+ fid = p9_client_attach(v9ses->clnt, NULL, uname, uid,
+ v9ses->aname);
+ if (IS_ERR(fid))
+ return fid;
+
+ root_fid = p9_fid_get(fid);
+ v9fs_fid_add(dentry->d_sb->s_root, &fid);
+ }
+ /* If we are root ourself just return that */
+ if (dentry->d_sb->s_root == dentry)
+ return root_fid;
+
+ /*
+ * Do a multipath walk with attached root.
+ * When walking parent we need to make sure we
+ * don't have a parallel rename happening
+ */
+ down_read(&v9ses->rename_sem);
+ n = build_path_from_dentry(v9ses, dentry, &wnames);
+ if (n < 0) {
+ fid = ERR_PTR(n);
+ goto err_out;
+ }
+ fid = root_fid;
+ old_fid = root_fid;
+ i = 0;
+ while (i < n) {
+ l = min(n - i, P9_MAXWELEM);
+ /*
+ * We need to hold rename lock when doing a multipath
+ * walk to ensure none of the path components change
+ */
+ fid = p9_client_walk(old_fid, l, &wnames[i],
+ old_fid == root_fid /* clone */);
+ /* non-cloning walk will return the same fid */
+ if (fid != old_fid) {
+ p9_fid_put(old_fid);
+ old_fid = fid;
+ }
+ if (IS_ERR(fid)) {
+ kfree(wnames);
+ goto err_out;
+ }
+ i += l;
+ }
+ kfree(wnames);
+fid_out:
+ if (!IS_ERR(fid)) {
+ __add_fid(dentry, fid);
+ p9_fid_get(fid);
+ }
+err_out:
+ up_read(&v9ses->rename_sem);
+ return fid;
+}
+
+/**
+ * v9fs_fid_lookup - lookup for a fid, try to walk if not found
+ * @dentry: dentry to look for fid in
+ *
+ * Look for a fid in the specified dentry for the current user.
+ * If no fid is found, try to create one walking from a fid from the parent
+ * dentry (if it has one), or the root dentry. If the user haven't accessed
+ * the fs yet, attach now and walk from the root.
+ */
+
+struct p9_fid *v9fs_fid_lookup(struct dentry *dentry)
+{
+ kuid_t uid;
+ int any, access;
+ struct v9fs_session_info *v9ses;
+
+ v9ses = v9fs_dentry2v9ses(dentry);
+ access = v9ses->flags & V9FS_ACCESS_MASK;
+ switch (access) {
+ case V9FS_ACCESS_SINGLE:
+ case V9FS_ACCESS_USER:
+ case V9FS_ACCESS_CLIENT:
+ uid = make_kuid(0);
+ any = 0;
+ break;
+
+ case V9FS_ACCESS_ANY:
+ uid = v9ses->uid;
+ any = 1;
+ break;
+
+ default:
+ uid = INVALID_UID;
+ any = 0;
+ break;
+ }
+ return v9fs_fid_lookup_with_uid(dentry, uid, any);
+}
+
diff --git a/fs/9p/fid.h b/fs/9p/fid.h
new file mode 100644
index 000000000000..b537ee8deb8f
--- /dev/null
+++ b/fs/9p/fid.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * V9FS FID Management
+ *
+ * Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
+ */
+#ifndef FS_9P_FID_H
+#define FS_9P_FID_H
+#include <linux/list.h>
+#include "v9fs.h"
+
+struct p9_fid *v9fs_fid_find_inode(struct inode *inode, bool want_writeable,
+ kuid_t uid, bool any);
+struct p9_fid *v9fs_fid_lookup(struct dentry *dentry);
+static inline struct p9_fid *v9fs_parent_fid(struct dentry *dentry)
+{
+ return v9fs_fid_lookup(dentry->d_parent);
+}
+void v9fs_fid_add(struct dentry *dentry, struct p9_fid **fid);
+void v9fs_open_fid_add(struct inode *inode, struct p9_fid **fid);
+static inline struct p9_fid *clone_fid(struct p9_fid *fid)
+{
+ return IS_ERR(fid) ? fid : p9_client_walk(fid, 0, NULL, 1);
+}
+static inline struct p9_fid *v9fs_fid_clone(struct dentry *dentry)
+{
+ struct p9_fid *fid, *nfid;
+
+ fid = v9fs_fid_lookup(dentry);
+ if (!fid || IS_ERR(fid))
+ return fid;
+
+ nfid = clone_fid(fid);
+ p9_fid_put(fid);
+ return nfid;
+}
+#endif
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
new file mode 100644
index 000000000000..568c4f4bc946
--- /dev/null
+++ b/fs/9p/v9fs.c
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains functions assisting in mapping VFS to 9P2000
+ *
+ * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/uidgid.h>
+#include <linux/kstrtox.h>
+#include <linux/parser.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+
+static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
+static LIST_HEAD(v9fs_sessionlist);
+
+/*
+ * Option Parsing (code inspired by NFS code)
+ * NOTE: each transport will parse its own options
+ */
+
+enum {
+ /* Options that take integer arguments */
+ Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
+ /* String options */
+ Opt_uname, Opt_remotename,
+ /* Access options */
+ Opt_access,
+ /* Error token */
+ Opt_err
+};
+
+static const match_table_t tokens = {
+ {Opt_debug, "debug=%x"},
+ {Opt_dfltuid, "dfltuid=%u"},
+ {Opt_dfltgid, "dfltgid=%u"},
+ {Opt_afid, "afid=%u"},
+ {Opt_uname, "uname=%s"},
+ {Opt_remotename, "aname=%s"},
+ {Opt_access, "access=%s"},
+ {Opt_err, NULL}
+};
+
+/*
+ * Display the mount options in /proc/mounts.
+ */
+int v9fs_show_options(struct dentry *root)
+{
+ struct v9fs_session_info *v9ses = root->d_sb->s_fs_info;
+
+ /* TODO: generate Linux bootarg */
+
+ if (v9ses->debug)
+ printf(",debug=%x", v9ses->debug);
+ printf(",dfltuid=%u",
+ from_kuid(v9ses->dfltuid));
+ printf(",dfltgid=%u",
+ from_kgid(v9ses->dfltgid));
+ if (v9ses->afid != ~0)
+ printf(",afid=%u", v9ses->afid);
+ if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0)
+ printf(",uname=%s", v9ses->uname);
+ if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0)
+ printf(",aname=%s", v9ses->aname);
+
+ switch (v9ses->flags & V9FS_ACCESS_MASK) {
+ case V9FS_ACCESS_USER:
+ printf(",access=user");
+ break;
+ case V9FS_ACCESS_ANY:
+ printf(",access=any");
+ break;
+ case V9FS_ACCESS_CLIENT:
+ printf(",access=client");
+ break;
+ case V9FS_ACCESS_SINGLE:
+ printf(",access=%u",
+ from_kuid(v9ses->uid));
+ break;
+ }
+
+ return p9_show_client_options(v9ses->clnt);
+}
+
+/**
+ * v9fs_parse_options - parse mount options into session structure
+ * @v9ses: existing v9fs session information
+ * @opts: The mount option string
+ *
+ * Return 0 upon success, -ERRNO upon failure.
+ */
+
+static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
+{
+ char *options, *tmp_options;
+ substring_t args[MAX_OPT_ARGS];
+ char *p;
+ int option = 0;
+ char *s;
+ int ret = 0;
+
+ /* setup defaults */
+ v9ses->afid = ~0;
+ v9ses->debug = 0;
+
+ if (!opts)
+ return 0;
+
+ tmp_options = kstrdup(opts, GFP_KERNEL);
+ if (!tmp_options) {
+ ret = -ENOMEM;
+ goto fail_option_alloc;
+ }
+ options = tmp_options;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ int token, r;
+
+ if (!*p)
+ continue;
+
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_debug:
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ } else {
+ v9ses->debug = option;
+#ifdef CONFIG_NET_9P_DEBUG
+ p9_debug_level = option;
+#endif
+ }
+ break;
+
+ case Opt_dfltuid:
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ continue;
+ }
+ v9ses->dfltuid = make_kuid(option);
+ break;
+ case Opt_dfltgid:
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ continue;
+ }
+ v9ses->dfltgid = make_kgid(option);
+ break;
+ case Opt_afid:
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ p9_debug(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ } else {
+ v9ses->afid = option;
+ }
+ break;
+ case Opt_uname:
+ kfree(v9ses->uname);
+ v9ses->uname = match_strdup(&args[0]);
+ if (!v9ses->uname) {
+ ret = -ENOMEM;
+ goto free_and_return;
+ }
+ break;
+ case Opt_remotename:
+ kfree(v9ses->aname);
+ v9ses->aname = match_strdup(&args[0]);
+ if (!v9ses->aname) {
+ ret = -ENOMEM;
+ goto free_and_return;
+ }
+ break;
+ case Opt_access:
+ s = match_strdup(&args[0]);
+ if (!s) {
+ ret = -ENOMEM;
+ p9_debug(P9_DEBUG_ERROR,
+ "problem allocating copy of access arg\n");
+ goto free_and_return;
+ }
+
+ v9ses->flags &= ~V9FS_ACCESS_MASK;
+ if (strcmp(s, "user") == 0)
+ v9ses->flags |= V9FS_ACCESS_USER;
+ else if (strcmp(s, "any") == 0)
+ v9ses->flags |= V9FS_ACCESS_ANY;
+ else if (strcmp(s, "client") == 0) {
+ v9ses->flags |= V9FS_ACCESS_CLIENT;
+ } else {
+ uid_t uid;
+
+ v9ses->flags |= V9FS_ACCESS_SINGLE;
+ r = kstrtouint(s, 10, &uid);
+ if (r) {
+ ret = r;
+ pr_info("Unknown access argument %s: %d\n",
+ s, r);
+ kfree(s);
+ continue;
+ }
+ v9ses->uid = make_kuid(uid);
+ }
+
+ kfree(s);
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+free_and_return:
+ kfree(tmp_options);
+fail_option_alloc:
+ return ret;
+}
+
+/**
+ * v9fs_session_init - initialize session
+ * @v9ses: session information structure
+ * @dev_name: device being mounted
+ * @data: options
+ *
+ */
+
+struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
+ const char *dev_name, char *data)
+{
+ struct p9_fid *fid;
+ int rc = -ENOMEM;
+
+ v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL);
+ if (!v9ses->uname)
+ goto err_names;
+
+ v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL);
+ if (!v9ses->aname)
+ goto err_names;
+ init_rwsem(&v9ses->rename_sem);
+
+ v9ses->uid = INVALID_UID;
+ v9ses->dfltuid = V9FS_DEFUID;
+ v9ses->dfltgid = V9FS_DEFGID;
+
+ v9ses->clnt = p9_client_create(dev_name, data);
+ if (IS_ERR(v9ses->clnt)) {
+ rc = PTR_ERR(v9ses->clnt);
+ p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
+ goto err_names;
+ }
+
+ v9ses->flags = V9FS_ACCESS_USER;
+
+ v9ses->flags = V9FS_ACCESS_CLIENT;
+ v9ses->flags |= V9FS_PROTO_2000L;
+
+ rc = v9fs_parse_options(v9ses, data);
+ if (rc < 0)
+ goto err_clnt;
+
+ v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;
+
+ fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
+ v9ses->aname);
+ if (IS_ERR(fid)) {
+ rc = PTR_ERR(fid);
+ p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
+ goto err_clnt;
+ }
+
+ if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
+ fid->uid = v9ses->uid;
+ else
+ fid->uid = INVALID_UID;
+
+ spin_lock(&v9fs_sessionlist_lock);
+ list_add(&v9ses->slist, &v9fs_sessionlist);
+ spin_unlock(&v9fs_sessionlist_lock);
+
+ return fid;
+
+err_clnt:
+ p9_client_destroy(v9ses->clnt);
+err_names:
+ kfree(v9ses->uname);
+ kfree(v9ses->aname);
+ return ERR_PTR(rc);
+}
+
+/**
+ * v9fs_session_close - shutdown a session
+ * @v9ses: session information structure
+ *
+ */
+
+void v9fs_session_close(struct v9fs_session_info *v9ses)
+{
+ if (v9ses->clnt) {
+ p9_client_destroy(v9ses->clnt);
+ v9ses->clnt = NULL;
+ }
+
+ kfree(v9ses->uname);
+ kfree(v9ses->aname);
+
+ spin_lock(&v9fs_sessionlist_lock);
+ list_del(&v9ses->slist);
+ spin_unlock(&v9fs_sessionlist_lock);
+}
+
+/**
+ * v9fs_session_cancel - terminate a session
+ * @v9ses: session to terminate
+ *
+ * mark transport as disconnected and cancel all pending requests.
+ */
+
+void v9fs_session_cancel(struct v9fs_session_info *v9ses)
+{
+ p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
+ p9_client_disconnect(v9ses->clnt);
+}
+
+/**
+ * v9fs_session_begin_cancel - Begin terminate of a session
+ * @v9ses: session to terminate
+ *
+ * After this call we don't allow any request other than clunk.
+ */
+
+void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
+{
+ p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
+ p9_client_begin_disconnect(v9ses->clnt);
+}
+
+static int init_v9fs(void)
+{
+ pr_info("Installing v9fs 9p2000 file system support\n");
+ /* TODO: Setup list of registered transport modules */
+
+ return register_fs_driver(&v9fs_driver);
+}
+coredevice_initcall(init_v9fs);
+
+MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
+MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
+MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
+MODULE_DESCRIPTION("9P Client File System");
+MODULE_LICENSE("GPL");
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
new file mode 100644
index 000000000000..2100fbcfa5de
--- /dev/null
+++ b/fs/9p/v9fs.h
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * V9FS definitions.
+ *
+ * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+#ifndef FS_9P_V9FS_H
+#define FS_9P_V9FS_H
+
+#include <linux/types.h>
+#include <linux/uidgid.h>
+#include <linux/netfs.h>
+#include <fs.h>
+
+/**
+ * enum p9_session_flags - option flags for each 9P session
+ * @V9FS_PROTO_2000U: whether or not to use 9P2000.u extensions
+ * @V9FS_PROTO_2000L: whether or not to use 9P2000.l extensions
+ * @V9FS_ACCESS_SINGLE: only the mounting user can access the hierarchy
+ * @V9FS_ACCESS_USER: a new attach will be issued for every user (default)
+ * @V9FS_ACCESS_CLIENT: Just like user, but access check is performed on client.
+ * @V9FS_ACCESS_ANY: use a single attach for all users
+ * @V9FS_ACCESS_MASK: bit mask of different ACCESS options
+ *
+ * Session flags reflect options selected by users at mount time
+ */
+#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \
+ V9FS_ACCESS_USER | \
+ V9FS_ACCESS_CLIENT)
+#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY
+
+enum p9_session_flags {
+ V9FS_PROTO_2000U = 0x01,
+ V9FS_PROTO_2000L = 0x02,
+ V9FS_ACCESS_SINGLE = 0x04,
+ V9FS_ACCESS_USER = 0x08,
+ V9FS_ACCESS_CLIENT = 0x10,
+ V9FS_SYNC = 0x200
+};
+
+/**
+ * enum p9_cache_shortcuts - human readable cache preferences
+ * @CACHE_SC_NONE: disable all caches
+ *
+ */
+
+enum p9_cache_shortcuts {
+ CACHE_SC_NONE = 0b00000000,
+};
+
+/**
+ * enum p9_cache_bits - possible values of ->cache
+ * @CACHE_NONE: caches disabled
+ *
+ */
+
+enum p9_cache_bits {
+ CACHE_NONE = 0b00000000,
+};
+
+/**
+ * struct v9fs_session_info - per-instance session information
+ * @flags: session options of type &p9_session_flags
+ * @debug: debug level
+ * @afid: authentication handle
+ * @cache: cache mode of type &p9_cache_bits
+ * @cachetag: the tag of the cache associated with this session
+ * @fscache: session cookie associated with FS-Cache
+ * @uname: string user name to mount hierarchy as
+ * @aname: mount specifier for remote hierarchy
+ * @maxdata: maximum data to be sent/recvd per protocol message
+ * @dfltuid: default numeric userid to mount hierarchy as
+ * @dfltgid: default numeric groupid to mount hierarchy as
+ * @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy
+ * @clnt: reference to 9P network client instantiated for this session
+ * @slist: reference to list of registered 9p sessions
+ *
+ * This structure holds state for each session instance established during
+ * a sys_mount() .
+ *
+ * Bugs: there seems to be a lot of state which could be condensed and/or
+ * removed.
+ */
+
+struct v9fs_session_info {
+ /* options */
+ unsigned int flags;
+ unsigned short debug;
+ unsigned int afid;
+
+ char *uname; /* user name to mount as */
+ char *aname; /* name of remote hierarchy being mounted */
+ unsigned int maxdata; /* max data for client interface */
+ kuid_t dfltuid; /* default uid/muid for legacy support */
+ kgid_t dfltgid; /* default gid for legacy support */
+ kuid_t uid; /* if ACCESS_SINGLE, the uid that has access */
+ struct p9_client *clnt; /* 9p client */
+ struct list_head slist; /* list of sessions registered with v9fs */
+ struct rw_semaphore rename_sem;
+ long session_lock_timeout; /* retry interval for blocking locks */
+};
+
+struct v9fs_inode {
+ struct netfs_inode netfs; /* Netfslib context and vfs inode */
+ struct p9_qid qid;
+};
+
+static inline struct v9fs_inode *V9FS_I(const struct inode *inode)
+{
+ return container_of(inode, struct v9fs_inode, netfs.inode);
+}
+
+struct dentry;
+struct inode;
+struct super_block;
+
+extern int v9fs_show_options(struct dentry *root);
+
+struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
+ const char *dev_name, char *data);
+extern void v9fs_session_close(struct v9fs_session_info *v9ses);
+extern void v9fs_session_cancel(struct v9fs_session_info *v9ses);
+extern void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses);
+extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d);
+extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d);
+extern const struct inode_operations v9fs_dir_inode_operations_dotl;
+extern const struct inode_operations v9fs_file_inode_operations_dotl;
+extern const struct inode_operations v9fs_symlink_inode_operations_dotl;
+extern struct inode *v9fs_fid_iget_dotl(struct super_block *sb,
+ struct p9_fid *fid, bool new);
+
+int v9fs_read(struct device *dev, struct file *f, void *buf,
+ size_t insize);
+
+int v9fs_write(struct device *dev, struct file *f, const void *buf,
+ size_t insize);
+
+/* other default globals */
+#define V9FS_PORT 564
+#define V9FS_DEFUSER "nobody"
+#define V9FS_DEFANAME ""
+#define V9FS_DEFUID KUIDT_INIT(-2)
+#define V9FS_DEFGID KGIDT_INIT(-2)
+
+static inline struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode)
+{
+ return inode->i_sb->s_fs_info;
+}
+
+static inline struct v9fs_session_info *v9fs_dentry2v9ses(struct dentry *dentry)
+{
+ return dentry->d_sb->s_fs_info;
+}
+
+static inline int v9fs_proto_dotu(struct v9fs_session_info *v9ses)
+{
+ return false;
+}
+
+static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses)
+{
+ return true;
+}
+
+/**
+ * v9fs_get_inode_from_fid - Helper routine to populate an inode by
+ * issuing a attribute request
+ * @v9ses: session information
+ * @fid: fid to issue attribute request for
+ * @sb: superblock on which to create inode
+ *
+ */
+static inline struct inode *
+v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
+ struct super_block *sb, bool new)
+{
+ return v9fs_fid_iget_dotl(sb, fid, new);
+}
+
+#endif
diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h
new file mode 100644
index 000000000000..aa97032521b8
--- /dev/null
+++ b/fs/9p/v9fs_vfs.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * V9FS VFS extensions.
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+#ifndef FS_9P_V9FS_VFS_H
+#define FS_9P_V9FS_VFS_H
+
+#include <fs.h>
+#include <linux/netfs.h>
+
+/* plan9 semantics are that created files are implicitly opened.
+ * But linux semantics are that you call create, then open.
+ * the plan9 approach is superior as it provides an atomic
+ * open.
+ * we track the create fid here. When the file is opened, if fidopen is
+ * non-zero, we use the fid and can skip some steps.
+ * there may be a better way to do this, but I don't know it.
+ * one BAD way is to clunk the fid on create, then open it again:
+ * you lose the atomicity of file open
+ */
+
+extern struct fs_driver v9fs_driver;
+struct v9fs_session_info;
+struct p9_qid;
+struct p9_fid;
+struct p9_stat_dotl;
+struct p9_wstat;
+
+extern const struct file_operations v9fs_file_operations_dotl;
+extern const struct file_operations v9fs_dir_operations_dotl;
+
+struct super_block;
+struct inode *v9fs_alloc_inode(struct super_block *sb);
+void v9fs_free_inode(struct inode *inode);
+void v9fs_set_netfs_context(struct inode *inode);
+int v9fs_mount(struct device *dev);
+void v9fs_umount(struct device *dev);
+int v9fs_init_inode(struct v9fs_session_info *v9ses,
+ struct inode *inode, struct p9_qid *qid, umode_t mode, dev_t rdev);
+#if (BITS_PER_LONG == 32)
+#define QID2INO(q) ((ino_t) (((q)->path+2) ^ (((q)->path) >> 32)))
+#else
+#define QID2INO(q) ((ino_t) ((q)->path+2))
+#endif
+
+void v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode);
+int v9fs_dir_release(struct inode *inode, struct file *file);
+int v9fs_file_open(struct inode *inode, struct file *file);
+
+int v9fs_file_fsync_dotl(struct device *dev, struct file *filp);
+static inline void v9fs_invalidate_inode_attr(struct inode *inode)
+{
+ netfs_invalidate_inode_attr(netfs_inode(inode));
+}
+
+int v9fs_open_to_dotl_flags(int flags);
+
+static inline void v9fs_i_size_write(struct inode *inode, loff_t i_size)
+{
+ /*
+ * 32-bit need the lock, concurrent updates could break the
+ * sequences and make i_size_read() loop forever.
+ * 64-bit updates are atomic and can skip the locking.
+ */
+ if (sizeof(i_size) > sizeof(long))
+ spin_lock(&inode->i_lock);
+ i_size_write(inode, i_size);
+ if (sizeof(i_size) > sizeof(long))
+ spin_unlock(&inode->i_lock);
+}
+#endif
diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c
new file mode 100644
index 000000000000..70bb1a04f83c
--- /dev/null
+++ b/fs/9p/vfs_addr.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contians vfs address (mmap) ops for 9P2000.
+ *
+ * Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/pagemap.h>
+#include <linux/sched.h>
+#include <linux/uio.h>
+#include <linux/netfs.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+/**
+ * v9fs_init_request - Initialise a request
+ * @rreq: The read request
+ * @file: The file being read from
+ */
+static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file)
+{
+ struct p9_fid *fid;
+ bool writing = rreq->origin == NETFS_WRITETHROUGH;
+
+ if (file) {
+ fid = file->private_data;
+ if (!fid)
+ goto no_fid;
+ p9_fid_get(fid);
+ } else {
+ fid = v9fs_fid_find_inode(rreq->inode, writing, INVALID_UID, true);
+ if (!fid)
+ goto no_fid;
+ }
+
+ rreq->netfs_priv = fid;
+ return 0;
+
+no_fid:
+ WARN_ONCE(1, "folio expected an open fid inode->i_ino=%lx\n",
+ rreq->inode->i_ino);
+ return -EINVAL;
+}
+
+/**
+ * v9fs_free_request - Cleanup request initialized by v9fs_init_rreq
+ * @rreq: The I/O request to clean up
+ */
+static void v9fs_free_request(struct netfs_io_request *rreq)
+{
+ struct p9_fid *fid = rreq->netfs_priv;
+
+ p9_fid_put(fid);
+}
+
+int v9fs_read(struct device *dev, struct file *f, void *buf,
+ size_t insize)
+{
+ struct netfs_io_request rreq = {
+ .origin = NETFS_READPAGE,
+ .inode = f->f_inode,
+ };
+ struct kvec kv = {.iov_base = buf, .iov_len = insize};
+ struct iov_iter to;
+ int len, err;
+
+ err = v9fs_init_request(&rreq, f);
+ if (err)
+ return err;
+
+ iov_iter_kvec(&to, ITER_DEST, &kv, 1, insize);
+
+ len = p9_client_read(rreq.netfs_priv, f->f_pos, &to, &err);
+
+ v9fs_free_request(&rreq);
+ return len ?: err;
+}
+
+int v9fs_write(struct device *dev, struct file *f, const void *buf,
+ size_t insize)
+{
+ struct netfs_io_request rreq = {
+ .origin = NETFS_WRITETHROUGH,
+ .inode = f->f_inode,
+ };
+ struct kvec kv = {.iov_base = (void *)buf, .iov_len = insize};
+ struct iov_iter from;
+ int len, err;
+
+ err = v9fs_init_request(&rreq, f);
+ if (err)
+ return err;
+
+ iov_iter_kvec(&from, ITER_SOURCE, &kv, 1, insize);
+
+ len = p9_client_write(rreq.netfs_priv, f->f_pos, &from, &err);
+
+ v9fs_free_request(&rreq);
+ return len ?: err;
+}
+
diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c
new file mode 100644
index 000000000000..179d83620c22
--- /dev/null
+++ b/fs/9p/vfs_dir.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains vfs directory ops for the 9P2000 protocol.
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uio.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+/**
+ * struct p9_rdir - readdir accounting
+ * @head: start offset of current dirread buffer
+ * @tail: end offset of current dirread buffer
+ * @buf: dirread buffer
+ *
+ * private structure for keeping track of readdir
+ * allocated on demand
+ */
+
+struct p9_rdir {
+ int head;
+ int tail;
+ uint8_t buf[];
+};
+
+/**
+ * dt_type - return file type
+ * @mistat: mistat structure
+ *
+ */
+
+static inline int dt_type(struct p9_wstat *mistat)
+{
+ unsigned long perm = mistat->mode;
+ int rettype = DT_REG;
+
+ if (perm & P9_DMDIR)
+ rettype = DT_DIR;
+ if (perm & P9_DMSYMLINK)
+ rettype = DT_LNK;
+
+ return rettype;
+}
+
+/**
+ * v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir
+ * @filp: opened file structure
+ * @buflen: Length in bytes of buffer to allocate
+ *
+ */
+
+static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen)
+{
+ struct p9_fid *fid = filp->private_data;
+
+ if (!fid->rdir)
+ fid->rdir = kzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL);
+ return fid->rdir;
+}
+
+/**
+ * v9fs_dir_readdir_dotl - iterate through a directory
+ * @file: opened file structure
+ * @ctx: actor we feed the entries to
+ *
+ */
+static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
+{
+ int err = 0;
+ struct p9_fid *fid;
+ int buflen;
+ struct p9_rdir *rdir;
+ struct p9_dirent curdirent;
+
+ p9_debug(P9_DEBUG_VFS, "name %s\n", file->path);
+ fid = file->private_data;
+
+ buflen = fid->clnt->msize - P9_READDIRHDRSZ;
+
+ rdir = v9fs_alloc_rdir_buf(file, buflen);
+ if (!rdir)
+ return -ENOMEM;
+
+ while (1) {
+ if (rdir->tail == rdir->head) {
+ err = p9_client_readdir(fid, rdir->buf, buflen,
+ ctx->pos);
+ if (err <= 0)
+ return err;
+
+ rdir->head = 0;
+ rdir->tail = err;
+ }
+
+ while (rdir->head < rdir->tail) {
+ err = p9dirent_read(fid->clnt, rdir->buf + rdir->head,
+ rdir->tail - rdir->head,
+ &curdirent);
+ if (err < 0) {
+ p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
+ return -EIO;
+ }
+
+ dir_emit(ctx, curdirent.d_name,
+ strlen(curdirent.d_name),
+ QID2INO(&curdirent.qid),
+ curdirent.d_type);
+
+ ctx->pos = curdirent.d_off;
+ rdir->head += err;
+ }
+ }
+}
+
+
+/**
+ * v9fs_dir_release - close a directory or a file
+ * @inode: inode of the directory or file
+ * @filp: file pointer to a directory or file
+ *
+ */
+
+int v9fs_dir_release(struct inode *inode, struct file *filp)
+{
+ struct p9_fid *fid;
+ int retval = 0, put_err;
+
+ fid = filp->private_data;
+ p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n",
+ inode, filp, fid ? fid->fid : -1);
+
+ if (fid) {
+ spin_lock(&inode->i_lock);
+ hlist_del(&fid->ilist);
+ spin_unlock(&inode->i_lock);
+ put_err = p9_fid_put(fid);
+ retval = retval < 0 ? retval : put_err;
+ }
+
+ return retval;
+}
+
+int v9fs_file_fsync_dotl(struct device *dev, struct file *filp)
+{
+ struct p9_fid *fid;
+
+ p9_debug(P9_DEBUG_VFS, "filp %p datasync\n", filp);
+
+ fid = filp->private_data;
+
+ return p9_client_fsync(fid, true);
+}
+
+const struct file_operations v9fs_dir_operations_dotl = {
+ .iterate = v9fs_dir_readdir_dotl,
+ .open = v9fs_file_open,
+ .release = v9fs_dir_release,
+};
diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c
new file mode 100644
index 000000000000..2a44a773263c
--- /dev/null
+++ b/fs/9p/vfs_file.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contians vfs file ops for 9P2000.
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/pagemap.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/slab.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+/**
+ * v9fs_file_open - open a file (or directory)
+ * @inode: inode to be opened
+ * @file: file being opened
+ *
+ */
+
+int v9fs_file_open(struct inode *inode, struct file *file)
+{
+ int err;
+ struct v9fs_session_info *v9ses;
+ struct p9_fid *fid;
+ int omode;
+
+ p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file);
+ v9ses = v9fs_inode2v9ses(inode);
+ omode = v9fs_open_to_dotl_flags(file->f_flags);
+ fid = file->private_data;
+
+ if (!fid) {
+ fid = v9fs_fid_clone(file->f_dentry);
+ if (IS_ERR(fid))
+ return PTR_ERR(fid);
+
+ err = p9_client_open(fid, omode);
+ if (err < 0) {
+ p9_fid_put(fid);
+ return err;
+ }
+
+ file->private_data = fid;
+ }
+
+ v9fs_open_fid_add(inode, &fid);
+ return 0;
+}
+
+const struct file_operations v9fs_file_operations_dotl = {
+ .open = v9fs_file_open,
+ .release = v9fs_dir_release,
+};
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
new file mode 100644
index 000000000000..458442c4692b
--- /dev/null
+++ b/fs/9p/vfs_inode.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains vfs inode ops for the 9P2000 protocol.
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/netfs.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+/**
+ * v9fs_alloc_inode - helper function to allocate an inode
+ * @sb: The superblock to allocate the inode from
+ */
+struct inode *v9fs_alloc_inode(struct super_block *sb)
+{
+ struct v9fs_inode *v9inode;
+
+ v9inode = xzalloc(sizeof(*v9inode));
+
+ mutex_init(&v9inode->v_mutex);
+ return &v9inode->netfs.inode;
+}
+
+/**
+ * v9fs_free_inode - destroy an inode
+ * @inode: The inode to be freed
+ */
+
+void v9fs_free_inode(struct inode *inode)
+{
+ struct v9fs_inode *v9inode = V9FS_I(inode);
+
+ free(v9inode);
+}
+
+/*
+ * Set parameters for the netfs library
+ */
+void v9fs_set_netfs_context(struct inode *inode)
+{
+ struct v9fs_inode *v9inode = V9FS_I(inode);
+ netfs_inode_init(&v9inode->netfs);
+}
+
+int v9fs_init_inode(struct v9fs_session_info *v9ses,
+ struct inode *inode, struct p9_qid *qid, umode_t mode, dev_t rdev)
+{
+ int err = 0;
+ struct v9fs_inode *v9inode = V9FS_I(inode);
+
+ v9inode->qid = *qid;
+
+ inode_init_owner(inode, NULL, mode);
+ inode->i_blocks = 0;
+ inode->i_private = NULL;
+
+ switch (mode & S_IFMT) {
+ case S_IFIFO:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFSOCK:
+ case S_IFREG:
+ inode->i_op = &v9fs_file_inode_operations_dotl;
+ inode->i_fop = &v9fs_file_operations_dotl;
+
+ break;
+ case S_IFLNK:
+ inode->i_op = &v9fs_symlink_inode_operations_dotl;
+
+ break;
+ case S_IFDIR:
+ inc_nlink(inode);
+ inode->i_op = &v9fs_dir_inode_operations_dotl;
+ inode->i_fop = &v9fs_dir_operations_dotl;
+
+ break;
+ default:
+ p9_debug(P9_DEBUG_ERROR, "BAD mode 0x%hx S_IFMT 0x%x\n",
+ mode, mode & S_IFMT);
+ err = -EINVAL;
+ goto error;
+ }
+error:
+ return err;
+
+}
+
+/**
+ * v9fs_at_to_dotl_flags- convert Linux specific AT flags to
+ * plan 9 AT flag.
+ * @flags: flags to convert
+ */
+static int v9fs_at_to_dotl_flags(int flags)
+{
+ int rflags = 0;
+
+ if (flags & AT_REMOVEDIR)
+ rflags |= P9_DOTL_AT_REMOVEDIR;
+
+ return rflags;
+}
+
+/**
+ * v9fs_dec_count - helper functon to drop i_nlink.
+ *
+ * If a directory had nlink <= 2 (including . and ..), then we should not drop
+ * the link count, which indicates the underlying exported fs doesn't maintain
+ * nlink accurately. e.g.
+ * - overlayfs sets nlink to 1 for merged dir
+ * - ext4 (with dir_nlink feature enabled) sets nlink to 1 if a dir has more
+ * than EXT4_LINK_MAX (65000) links.
+ *
+ * @inode: inode whose nlink is being dropped
+ */
+static void v9fs_dec_count(struct inode *inode)
+{
+ if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2) {
+ if (inode->i_nlink) {
+ drop_nlink(inode);
+ } else {
+ p9_debug(P9_DEBUG_VFS,
+ "WARNING: unexpected i_nlink zero %d inode %ld\n",
+ inode->i_nlink, inode->i_ino);
+ }
+ }
+}
+
+/**
+ * v9fs_remove - helper function to remove files and directories
+ * @dir: directory inode that is being deleted
+ * @dentry: dentry that is being deleted
+ * @flags: removing a directory
+ *
+ */
+
+static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags)
+{
+ struct inode *inode;
+ int retval = -EOPNOTSUPP;
+ struct p9_fid *v9fid, *dfid;
+ struct v9fs_session_info *v9ses;
+
+ p9_debug(P9_DEBUG_VFS, "inode: %p dentry: %p rmdir: %x\n",
+ dir, dentry, flags);
+
+ v9ses = v9fs_inode2v9ses(dir);
+ inode = d_inode(dentry);
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid)) {
+ retval = PTR_ERR(dfid);
+ p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", retval);
+ return retval;
+ }
+ retval = p9_client_unlinkat(dfid, dentry->d_name.name,
+ v9fs_at_to_dotl_flags(flags));
+ p9_fid_put(dfid);
+ if (retval == -EOPNOTSUPP) {
+ /* Try the one based on path */
+ v9fid = v9fs_fid_clone(dentry);
+ if (IS_ERR(v9fid))
+ return PTR_ERR(v9fid);
+ retval = p9_client_remove(v9fid);
+ }
+ if (!retval) {
+ /*
+ * directories on unlink should have zero
+ * link count
+ */
+ if (flags & AT_REMOVEDIR) {
+ clear_nlink(inode);
+ v9fs_dec_count(dir);
+ } else
+ v9fs_dec_count(inode);
+
+ v9fs_invalidate_inode_attr(inode);
+ v9fs_invalidate_inode_attr(dir);
+ }
+ return retval;
+}
+
+/**
+ * v9fs_vfs_lookup - VFS lookup hook to "walk" to a new inode
+ * @dir: inode that is being walked from
+ * @dentry: dentry that is being walked to?
+ * @flags: lookup flags (unused)
+ *
+ */
+
+struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct v9fs_session_info *v9ses;
+ struct p9_fid *dfid, *fid;
+ struct inode *inode;
+ const unsigned char *name;
+
+ p9_debug(P9_DEBUG_VFS, "dir: %p dentry: (%s) %p flags: %x\n",
+ dir, dentry->d_name.name, dentry, flags);
+
+ if (dentry->d_name.len > NAME_MAX)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ v9ses = v9fs_inode2v9ses(dir);
+ /* We can walk d_parent because we hold the dir->i_mutex */
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid))
+ return ERR_CAST(dfid);
+
+ /*
+ * Make sure we don't use a wrong inode due to parallel
+ * unlink. For cached mode create calls request for new
+ * inode. But with cache disabled, lookup should do this.
+ */
+ name = dentry->d_name.name;
+ fid = p9_client_walk(dfid, 1, &name, 1);
+ p9_fid_put(dfid);
+ if (fid == ERR_PTR(-ENOENT))
+ inode = NULL;
+ else if (IS_ERR(fid))
+ inode = ERR_CAST(fid);
+ else
+ inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb, false);
+ /*
+ * If we had a rename on the server and a parallel lookup
+ * for the new name, then make sure we instantiate with
+ * the new name. ie look up for a/b, while on server somebody
+ * moved b under k and client parallely did a lookup for
+ * k/b.
+ */
+ if (!IS_ERR(inode)) {
+ d_add(dentry, inode);
+ if (!IS_ERR(fid))
+ v9fs_fid_add(dentry, &fid);
+ }
+
+ return NULL;
+}
+
+/**
+ * v9fs_vfs_unlink - VFS unlink hook to delete an inode
+ * @i: inode that is being unlinked
+ * @d: dentry that is being unlinked
+ *
+ */
+
+int v9fs_vfs_unlink(struct inode *i, struct dentry *d)
+{
+ return v9fs_remove(i, d, 0);
+}
+
+/**
+ * v9fs_vfs_rmdir - VFS unlink hook to delete a directory
+ * @i: inode that is being unlinked
+ * @d: dentry that is being unlinked
+ *
+ */
+
+int v9fs_vfs_rmdir(struct inode *i, struct dentry *d)
+{
+ return v9fs_remove(i, d, AT_REMOVEDIR);
+}
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
new file mode 100644
index 000000000000..2eb3cc9274bc
--- /dev/null
+++ b/fs/9p/vfs_inode_dotl.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains vfs inode ops for the 9P2000.L protocol.
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/uidgid.h>
+#include <linux/pagemap.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/kdev_t.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+static int
+v9fs_vfs_mknod_dotl(struct inode *dir,
+ struct dentry *dentry, umode_t omode, dev_t rdev);
+
+/**
+ * v9fs_get_fsgid_for_create - Helper function to get the gid for a new object
+ * @dir_inode: The directory inode
+ *
+ * Helper function to get the gid for creating a
+ * new file system object. This checks the S_ISGID to determine the owning
+ * group of the new file system object.
+ */
+
+static kgid_t v9fs_get_fsgid_for_create(struct inode *dir_inode)
+{
+ BUG_ON(dir_inode == NULL);
+
+ if (dir_inode->i_mode & S_ISGID) {
+ /* set_gid bit is set.*/
+ return make_kgid(dir_inode->i_gid);
+ }
+ return make_kgid(0);
+}
+
+struct inode *
+v9fs_fid_iget_dotl(struct super_block *sb, struct p9_fid *fid, bool new)
+{
+ int retval;
+ struct inode *inode;
+ struct p9_stat_dotl *st;
+ struct v9fs_session_info *v9ses = sb->s_fs_info;
+
+ inode = iget_locked(sb, QID2INO(&fid->qid));
+ if (unlikely(!inode))
+ return ERR_PTR(-ENOMEM);
+ if (!(inode->i_state & I_NEW)) {
+ if (!new)
+ goto done;
+ else /* deal with race condition in inode number reuse */
+ return ERR_PTR(-EBUSY);
+ }
+
+ /*
+ * initialize the inode with the stat info
+ * FIXME!! we may need support for stale inodes
+ * later.
+ */
+ st = p9_client_getattr_dotl(fid, P9_STATS_BASIC | P9_STATS_GEN);
+ if (IS_ERR(st)) {
+ retval = PTR_ERR(st);
+ goto error;
+ }
+
+ retval = v9fs_init_inode(v9ses, inode, &fid->qid,
+ st->st_mode, new_decode_dev(st->st_rdev));
+ v9fs_stat2inode_dotl(st, inode);
+ kfree(st);
+ if (retval)
+ goto error;
+
+ v9fs_set_netfs_context(inode);
+
+done:
+ return inode;
+error:
+ iget_failed(inode);
+ return ERR_PTR(retval);
+}
+
+struct dotl_openflag_map {
+ int open_flag;
+ int dotl_flag;
+};
+
+static int v9fs_mapped_dotl_flags(int flags)
+{
+ int i;
+ int rflags = 0;
+ struct dotl_openflag_map dotl_oflag_map[] = {
+ { O_CREAT, P9_DOTL_CREATE },
+ { O_EXCL, P9_DOTL_EXCL },
+ /*
+ * fs.c __write emulates O_APPEND by calling ->truncate first,
+ * so repeating append operation here, means we set an offset
+ * twice as big as intended.
+ * TODO: find out how Linux handles it, but for now skipping
+ * it seems t do the job.
+ *
+ * { O_APPEND, P9_DOTL_APPEND },
+ */
+ { O_DIRECTORY, P9_DOTL_DIRECTORY },
+ { O_NOFOLLOW, P9_DOTL_NOFOLLOW },
+ };
+ for (i = 0; i < ARRAY_SIZE(dotl_oflag_map); i++) {
+ if (flags & dotl_oflag_map[i].open_flag)
+ rflags |= dotl_oflag_map[i].dotl_flag;
+ }
+ return rflags;
+}
+
+/**
+ * v9fs_open_to_dotl_flags- convert Linux specific open flags to
+ * plan 9 open flag.
+ * @flags: flags to convert
+ */
+int v9fs_open_to_dotl_flags(int flags)
+{
+ int rflags = 0;
+
+ /*
+ * We have same bits for P9_DOTL_READONLY, P9_DOTL_WRONLY
+ * and P9_DOTL_NOACCESS
+ */
+ rflags |= flags & O_ACCMODE;
+ rflags |= v9fs_mapped_dotl_flags(flags);
+
+ return rflags;
+}
+
+/**
+ * v9fs_vfs_create_dotl - VFS hook to create files for 9P2000.L protocol.
+ * @dir: directory inode that is being created
+ * @dentry: dentry that is being deleted
+ * @omode: create permissions
+ * @excl: True if the file must not yet exist
+ *
+ */
+static __maybe_unused int
+v9fs_vfs_create_dotl(struct inode *dir,
+ struct dentry *dentry, umode_t omode)
+{
+ return v9fs_vfs_mknod_dotl(dir, dentry, omode, 0);
+}
+
+/**
+ * v9fs_vfs_mkdir_dotl - VFS mkdir hook to create a directory
+ * @dir: inode that is being unlinked
+ * @dentry: dentry that is being unlinked
+ * @omode: mode for new directory
+ *
+ */
+
+static __maybe_unused
+int v9fs_vfs_mkdir_dotl(struct inode *dir, struct dentry *dentry,
+ umode_t omode)
+{
+ int err;
+ struct p9_fid *fid = NULL, *dfid = NULL;
+ kgid_t gid;
+ const unsigned char *name;
+ umode_t mode;
+ struct inode *inode;
+ struct p9_qid qid;
+
+ p9_debug(P9_DEBUG_VFS, "name %s\n", dentry->d_name.name);
+
+ omode |= S_IFDIR;
+ if (dir->i_mode & S_ISGID)
+ omode |= S_ISGID;
+
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid)) {
+ err = PTR_ERR(dfid);
+ p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
+ goto error;
+ }
+
+ gid = v9fs_get_fsgid_for_create(dir);
+ mode = omode;
+ name = dentry->d_name.name;
+ err = p9_client_mkdir_dotl(dfid, name, mode, gid, &qid);
+ if (err < 0)
+ goto error;
+ fid = p9_client_walk(dfid, 1, &name, 1);
+ if (IS_ERR(fid)) {
+ err = PTR_ERR(fid);
+ p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
+ err);
+ goto error;
+ }
+
+ /* instantiate inode and assign the unopened fid to the dentry */
+ inode = v9fs_fid_iget_dotl(dir->i_sb, fid, true);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
+ err);
+ goto error;
+ }
+ v9fs_fid_add(dentry, &fid);
+ d_instantiate(dentry, inode);
+ err = 0;
+ inc_nlink(dir);
+ v9fs_invalidate_inode_attr(dir);
+error:
+ p9_fid_put(fid);
+ p9_fid_put(dfid);
+ return err;
+}
+
+/*
+ * Attribute flags.
+ */
+#define P9_ATTR_MODE (1 << 0)
+#define P9_ATTR_UID (1 << 1)
+#define P9_ATTR_GID (1 << 2)
+#define P9_ATTR_SIZE (1 << 3)
+#define P9_ATTR_ATIME (1 << 4)
+#define P9_ATTR_MTIME (1 << 5)
+#define P9_ATTR_CTIME (1 << 6)
+#define P9_ATTR_ATIME_SET (1 << 7)
+#define P9_ATTR_MTIME_SET (1 << 8)
+
+struct dotl_iattr_map {
+ int iattr_valid;
+ int p9_iattr_valid;
+};
+
+static int v9fs_mapped_iattr_valid(int iattr_valid)
+{
+ int i;
+ int p9_iattr_valid = 0;
+ struct dotl_iattr_map dotl_iattr_map[] = {
+ { ATTR_SIZE, P9_ATTR_SIZE },
+ };
+ for (i = 0; i < ARRAY_SIZE(dotl_iattr_map); i++) {
+ if (iattr_valid & dotl_iattr_map[i].iattr_valid)
+ p9_iattr_valid |= dotl_iattr_map[i].p9_iattr_valid;
+ }
+ return p9_iattr_valid;
+}
+
+/**
+ * v9fs_vfs_setattr_dotl - set file metadata
+ * @dentry: file whose metadata to set
+ * @iattr: metadata assignment structure
+ *
+ */
+
+static int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr)
+{
+ int retval, use_dentry = 0;
+ struct inode *inode = d_inode(dentry);
+ struct p9_fid *fid = NULL;
+ struct p9_iattr_dotl p9attr = {
+ .uid = INVALID_UID,
+ .gid = INVALID_GID,
+ };
+
+ p9_debug(P9_DEBUG_VFS, "\n");
+
+ p9attr.valid = v9fs_mapped_iattr_valid(iattr->ia_valid);
+ if (iattr->ia_valid & ATTR_SIZE)
+ p9attr.size = iattr->ia_size;
+
+ if (iattr->ia_valid & ATTR_FILE) {
+ fid = iattr->ia_file->private_data;
+ WARN_ON(!fid);
+ }
+ if (!fid) {
+ fid = v9fs_fid_lookup(dentry);
+ use_dentry = 1;
+ }
+ if (IS_ERR(fid))
+ return PTR_ERR(fid);
+
+ retval = p9_client_setattr(fid, &p9attr);
+ if (retval < 0) {
+ if (use_dentry)
+ p9_fid_put(fid);
+ return retval;
+ }
+
+ if ((iattr->ia_valid & ATTR_SIZE) && iattr->ia_size !=
+ i_size_read(inode)) {
+ truncate_setsize(inode, iattr->ia_size);
+ }
+
+ v9fs_invalidate_inode_attr(inode);
+ mark_inode_dirty(inode);
+ if (use_dentry)
+ p9_fid_put(fid);
+
+ return 0;
+}
+
+static __maybe_unused int
+v9fs_truncate(struct device *dev, struct file *f, loff_t size)
+{
+ struct iattr iattr = {
+ .ia_valid = ATTR_SIZE | ATTR_FILE,
+ .ia_size = size,
+ .ia_file = f,
+ };
+
+ return v9fs_vfs_setattr_dotl(f->f_dentry, &iattr);
+}
+
+/**
+ * v9fs_stat2inode_dotl - populate an inode structure with stat info
+ * @stat: stat structure
+ * @inode: inode to populate
+ *
+ */
+
+void
+v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode)
+{
+ umode_t mode;
+
+ if ((stat->st_result_mask & P9_STATS_BASIC) == P9_STATS_BASIC) {
+ inode->i_uid = from_kuid(stat->st_uid);
+ inode->i_gid = from_kgid(stat->st_gid);
+ set_nlink(inode, stat->st_nlink);
+
+ mode = stat->st_mode & S_IALLUGO;
+ mode |= inode->i_mode & ~S_IALLUGO;
+ inode->i_mode = mode;
+
+ v9fs_i_size_write(inode, stat->st_size);
+ inode->i_blocks = stat->st_blocks;
+ } else {
+ if (stat->st_result_mask & P9_STATS_UID)
+ inode->i_uid = from_kuid(stat->st_uid);
+ if (stat->st_result_mask & P9_STATS_GID)
+ inode->i_gid = from_kgid(stat->st_gid);
+ if (stat->st_result_mask & P9_STATS_NLINK)
+ set_nlink(inode, stat->st_nlink);
+ if (stat->st_result_mask & P9_STATS_MODE) {
+ mode = stat->st_mode & S_IALLUGO;
+ mode |= inode->i_mode & ~S_IALLUGO;
+ inode->i_mode = mode;
+ }
+ if (stat->st_result_mask & P9_STATS_SIZE) {
+ v9fs_i_size_write(inode, stat->st_size);
+ }
+ if (stat->st_result_mask & P9_STATS_BLOCKS)
+ inode->i_blocks = stat->st_blocks;
+ }
+ if (stat->st_result_mask & P9_STATS_GEN)
+ inode->i_generation = stat->st_gen;
+}
+
+static __maybe_unused int
+v9fs_vfs_symlink_dotl(struct inode *dir,
+ struct dentry *dentry, const char *symname)
+{
+ int err;
+ kgid_t gid;
+ const unsigned char *name;
+ struct p9_qid qid;
+ struct p9_fid *dfid;
+ struct p9_fid *fid = NULL;
+
+ name = dentry->d_name.name;
+ p9_debug(P9_DEBUG_VFS, "%lu,%s,%s\n", dir->i_ino, name, symname);
+
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid)) {
+ err = PTR_ERR(dfid);
+ p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
+ return err;
+ }
+
+ gid = v9fs_get_fsgid_for_create(dir);
+
+ /* Server doesn't alter fid on TSYMLINK. Hence no need to clone it. */
+ err = p9_client_symlink(dfid, name, symname, gid, &qid);
+
+ if (err < 0) {
+ p9_debug(P9_DEBUG_VFS, "p9_client_symlink failed %d\n", err);
+ goto error;
+ }
+
+ v9fs_invalidate_inode_attr(dir);
+
+error:
+ p9_fid_put(fid);
+ p9_fid_put(dfid);
+ return err;
+}
+
+/**
+ * v9fs_vfs_link_dotl - create a hardlink for dotl
+ * @old_dentry: dentry for file to link to
+ * @dir: inode destination for new link
+ * @dentry: dentry for link
+ *
+ */
+
+static __maybe_unused int
+v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir,
+ struct dentry *dentry)
+{
+ int err;
+ struct p9_fid *dfid, *oldfid;
+ struct v9fs_session_info *v9ses;
+
+ p9_debug(P9_DEBUG_VFS, "dir ino: %lu, old_name: %s, new_name: %s\n",
+ dir->i_ino, old_dentry->d_name.name, dentry->d_name.name);
+
+ v9ses = v9fs_inode2v9ses(dir);
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid))
+ return PTR_ERR(dfid);
+
+ oldfid = v9fs_fid_lookup(old_dentry);
+ if (IS_ERR(oldfid)) {
+ p9_fid_put(dfid);
+ return PTR_ERR(oldfid);
+ }
+
+ err = p9_client_link(dfid, oldfid, dentry->d_name.name);
+
+ p9_fid_put(dfid);
+ p9_fid_put(oldfid);
+ if (err < 0) {
+ p9_debug(P9_DEBUG_VFS, "p9_client_link failed %d\n", err);
+ return err;
+ }
+
+ v9fs_invalidate_inode_attr(dir);
+ ihold(d_inode(old_dentry));
+ d_instantiate(dentry, d_inode(old_dentry));
+
+ return err;
+}
+
+/**
+ * v9fs_vfs_mknod_dotl - create a special file
+ * @dir: inode destination for new link
+ * @dentry: dentry for file
+ * @omode: mode for creation
+ * @rdev: device associated with special file
+ *
+ */
+static int
+v9fs_vfs_mknod_dotl(struct inode *dir,
+ struct dentry *dentry, umode_t omode, dev_t rdev)
+{
+ int err;
+ kgid_t gid;
+ const unsigned char *name;
+ umode_t mode;
+ struct p9_fid *fid = NULL, *dfid = NULL;
+ struct inode *inode;
+ struct p9_qid qid;
+
+ p9_debug(P9_DEBUG_VFS, " %lu,%s mode: %x MAJOR: %u MINOR: %u\n",
+ dir->i_ino, dentry->d_name.name, omode,
+ MAJOR(rdev), MINOR(rdev));
+
+ dfid = v9fs_parent_fid(dentry);
+ if (IS_ERR(dfid)) {
+ err = PTR_ERR(dfid);
+ p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
+ goto error;
+ }
+
+ gid = v9fs_get_fsgid_for_create(dir);
+ mode = omode;
+ name = dentry->d_name.name;
+
+ err = p9_client_mknod_dotl(dfid, name, mode, rdev, gid, &qid);
+ if (err < 0)
+ goto error;
+
+ v9fs_invalidate_inode_attr(dir);
+ fid = p9_client_walk(dfid, 1, &name, 1);
+ if (IS_ERR(fid)) {
+ err = PTR_ERR(fid);
+ p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
+ err);
+ goto error;
+ }
+ inode = v9fs_fid_iget_dotl(dir->i_sb, fid, true);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
+ err);
+ goto error;
+ }
+ v9fs_fid_add(dentry, &fid);
+ d_instantiate(dentry, inode);
+ err = 0;
+error:
+ p9_fid_put(fid);
+ p9_fid_put(dfid);
+
+ return err;
+}
+
+/**
+ * v9fs_vfs_get_link_dotl - follow a symlink path
+ * @dentry: dentry for symlink
+ * @inode: inode for symlink
+ * @done: destructor for return value
+ */
+
+static const char *
+v9fs_vfs_get_link_dotl(struct dentry *dentry,
+ struct inode *inode)
+{
+ struct p9_fid *fid;
+ char *target;
+ int retval;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+
+ p9_debug(P9_DEBUG_VFS, "%s\n", dentry->d_name.name);
+
+ fid = v9fs_fid_lookup(dentry);
+ if (IS_ERR(fid))
+ return ERR_CAST(fid);
+ retval = p9_client_readlink(fid, &target);
+ p9_fid_put(fid);
+ if (retval)
+ return ERR_PTR(retval);
+ return target;
+}
+
+const struct inode_operations v9fs_dir_inode_operations_dotl = {
+ .lookup = v9fs_vfs_lookup,
+#ifdef CONFIG_9P_FS_WRITE
+ .create = v9fs_vfs_create_dotl,
+ .link = v9fs_vfs_link_dotl,
+ .symlink = v9fs_vfs_symlink_dotl,
+ .unlink = v9fs_vfs_unlink,
+ .mkdir = v9fs_vfs_mkdir_dotl,
+ .rmdir = v9fs_vfs_rmdir,
+#endif
+};
+
+const struct inode_operations v9fs_file_inode_operations_dotl;
+
+const struct inode_operations v9fs_symlink_inode_operations_dotl = {
+ .get_link = v9fs_vfs_get_link_dotl,
+};
+
+struct fs_driver v9fs_driver = {
+ .read = v9fs_read,
+#ifdef CONFIG_9P_FS_WRITE
+ .write = v9fs_write,
+ .truncate = v9fs_truncate,
+ .flush = v9fs_file_fsync_dotl,
+#endif
+ .drv = {
+ .probe = v9fs_mount,
+ .remove = v9fs_umount,
+ .name = "9p",
+ }
+};
diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c
new file mode 100644
index 000000000000..6a451d9ef514
--- /dev/null
+++ b/fs/9p/vfs_super.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
+ * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/pagemap.h>
+#include <linux/mount.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/magic.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+
+#include "v9fs.h"
+#include "v9fs_vfs.h"
+#include "fid.h"
+
+static const struct super_operations v9fs_super_ops_dotl;
+
+static void v9fs_devinfo(struct device *dev);
+
+/**
+ * v9fs_set_super - set the superblock
+ * @s: super block
+ * @data: file system specific data
+ *
+ */
+
+static void v9fs_set_super(struct super_block *s, void *data)
+{
+ s->s_fs_info = data;
+}
+
+/**
+ * v9fs_fill_super - populate superblock with info
+ * @sb: superblock
+ * @v9ses: session information
+ *
+ */
+
+static int
+v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses)
+{
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ sb->s_blocksize_bits = fls(v9ses->maxdata - 1);
+ sb->s_blocksize = 1 << sb->s_blocksize_bits;
+ sb->s_magic = V9FS_MAGIC;
+ sb->s_op = &v9fs_super_ops_dotl;
+
+ return 0;
+}
+
+static void v9fs_set_rootarg(struct v9fs_session_info *v9ses,
+ struct fs_device *fsdev)
+{
+ const char *tag, *trans, *path;
+ char *str;
+
+ tag = v9ses->clnt->trans_tag;
+ trans = v9ses->clnt->trans_mod->name;
+ path = v9ses->aname;
+
+ str = basprintf("root=%s rootfstype=9p rootflags=trans=%s,msize=%d,"
+ "cache=loose,uname=%s,dfltuid=0,dfltgid=0,aname=%s",
+ tag, trans, v9ses->clnt->msize, tag, path);
+
+ fsdev_set_linux_rootarg(fsdev, str);
+
+ free(str);
+}
+
+/**
+ * v9fs_mount - mount a superblock
+ * @flags: mount flags
+ * @dev_name: device name that was mounted
+ * @data: mount options
+ *
+ */
+
+int v9fs_mount(struct device *dev)
+{
+ struct fs_device *fsdev = dev_to_fs_device(dev);
+ void *data = fsdev->options;
+ struct super_block *sb = &fsdev->sb;
+ struct inode *inode = NULL;
+ struct dentry *root = NULL;
+ struct v9fs_session_info *v9ses = NULL;
+ struct p9_fid *fid;
+ int retval = 0;
+
+ p9_debug(P9_DEBUG_VFS, "\n");
+
+ v9ses = kzalloc(sizeof(struct v9fs_session_info), GFP_KERNEL);
+ if (!v9ses)
+ return -ENOMEM;
+
+ fid = v9fs_session_init(v9ses, fsdev->backingstore, data);
+ if (IS_ERR(fid)) {
+ retval = PTR_ERR(fid);
+ goto free_session;
+ }
+
+ v9fs_set_super(sb, v9ses);
+
+ retval = v9fs_fill_super(sb, v9ses);
+ if (retval)
+ goto release_sb;
+
+ sb->s_d_op = &netfs_dentry_operations_timed;
+
+ inode = v9fs_get_inode_from_fid(v9ses, fid, sb, true);
+ if (IS_ERR(inode)) {
+ retval = PTR_ERR(inode);
+ goto release_sb;
+ }
+
+ root = d_make_root(inode);
+ if (!root) {
+ retval = -ENOMEM;
+ goto release_sb;
+ }
+ sb->s_root = root;
+ v9fs_fid_add(root, &fid);
+
+ p9_debug(P9_DEBUG_VFS, " simple set mount, return 0\n");
+
+ dev->priv = sb;
+ devinfo_add(dev, v9fs_devinfo);
+
+ v9fs_set_rootarg(v9ses, fsdev);
+
+ return 0;
+free_session:
+ kfree(v9ses);
+ return retval;
+
+release_sb:
+ /*
+ * we will do the session_close and root dentry release
+ * in the below call. But we need to clunk fid, because we haven't
+ * attached the fid to dentry so it won't get clunked
+ * automatically.
+ */
+ p9_fid_put(fid);
+ return retval;
+}
+
+/**
+ * v9fs_kill_super - Kill Superblock
+ * @s: superblock
+ *
+ */
+
+static void v9fs_kill_super(struct super_block *s)
+{
+ struct v9fs_session_info *v9ses = s->s_fs_info;
+
+ p9_debug(P9_DEBUG_VFS, " %p\n", s);
+
+ v9fs_session_cancel(v9ses);
+ v9fs_session_close(v9ses);
+ kfree(v9ses);
+ s->s_fs_info = NULL;
+ p9_debug(P9_DEBUG_VFS, "exiting kill_super\n");
+}
+
+static void
+v9fs_umount_begin(struct super_block *sb)
+{
+ struct v9fs_session_info *v9ses;
+
+ v9ses = sb->s_fs_info;
+ v9fs_session_begin_cancel(v9ses);
+}
+
+void v9fs_umount(struct device *dev)
+{
+ struct super_block *sb = dev->priv;
+
+ devinfo_del(dev, v9fs_devinfo);
+ v9fs_umount_begin(sb);
+ v9fs_kill_super(sb);
+}
+
+static void v9fs_statfs(struct device *dev, struct dentry *dentry)
+{
+ struct v9fs_session_info *v9ses;
+ struct p9_fid *fid;
+ struct p9_rstatfs rs;
+ int res;
+
+ fid = v9fs_fid_lookup(dentry);
+ if (IS_ERR(fid)) {
+ res = PTR_ERR(fid);
+ goto done;
+ }
+
+ v9ses = v9fs_dentry2v9ses(dentry);
+ res = p9_client_statfs(fid, &rs);
+ if (res == 0) {
+ printf("File system stats:\n");
+ printf(" type = %u\n", rs.type);
+ printf(" bsize = %u\n", rs.bsize);
+ printf(" blocks = %llu\n", rs.blocks);
+ printf(" bfree = %llu\n", rs.bfree);
+ printf(" bavail = %llu\n", rs.bavail);
+ printf(" files = %llu\n", rs.files);
+ printf(" ffree = %llu\n", rs.ffree);
+ printf(" fsid = %llu\n", rs.fsid);
+ printf(" namelen = %u\n", rs.namelen);
+ }
+ if (res != -ENOSYS)
+ goto done;
+done:
+ p9_fid_put(fid);
+ if (res)
+ dev_warn(dev, "statfs failed; %pe\n", ERR_PTR(res));
+}
+
+static void v9fs_devinfo(struct device *dev)
+{
+ struct super_block *sb = dev->priv;
+ struct dentry *root = sb->s_root;
+
+ v9fs_statfs(dev, root);
+}
+
+static const struct super_operations v9fs_super_ops_dotl = {
+ .alloc_inode = v9fs_alloc_inode,
+ .destroy_inode = v9fs_free_inode,
+};
+
+MODULE_ALIAS_FS("9p");
diff --git a/fs/Kconfig b/fs/Kconfig
index f94488e643bf..e86420f9c259 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -81,6 +81,8 @@ config FS_NFS
bool
prompt "nfs support"
+source "fs/9p/Kconfig"
+
config FS_EFI
depends on EFI_PAYLOAD
select FS_LEGACY
diff --git a/fs/Makefile b/fs/Makefile
index b6a44ff82a38..de6e356d1897 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_FS_UBIFS) += ubifs/
obj-$(CONFIG_FS_TFTP) += tftp.o
obj-$(CONFIG_FS_OMAP4_USBBOOT) += omap4_usbbootfs.o
obj-$(CONFIG_FS_NFS) += nfs.o
+obj-$(CONFIG_9P_FS) += 9p/
obj-$(CONFIG_FS_BPKFS) += bpkfs.o
obj-$(CONFIG_FS_UIMAGEFS) += uimagefs.o
obj-$(CONFIG_FS_EFI) += efi.o
diff --git a/fs/fs.c b/fs/fs.c
index 465fd617c2ba..b6fc8557d054 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -1382,6 +1382,15 @@ struct inode *iget(struct inode *inode)
return inode;
}
+/*
+ * get additional reference to inode; caller must already hold one.
+ */
+void ihold(struct inode *inode)
+{
+ WARN_ON(++inode->i_count < 2);
+}
+EXPORT_SYMBOL(ihold);
+
/* dcache.c */
/*
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 7461eb367d07..7ae52d476d26 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -5,6 +5,7 @@
#include <linux/list.h>
#include <linux/time.h>
+#include <linux/stat.h>
#include <linux/mount.h>
#include <linux/path.h>
#include <linux/spinlock.h>
@@ -239,6 +240,25 @@ struct file {
void *private_data;
};
+/*
+ * Attribute flags. These should be or-ed together to figure out what
+ * has been changed!
+ */
+#define ATTR_SIZE (1 << 3)
+#define ATTR_FILE (1 << 13)
+
+struct iattr {
+ unsigned int ia_valid;
+ loff_t ia_size;
+
+ /*
+ * Not an attribute, but an auxiliary info for filesystems wanting to
+ * implement an ftruncate() like method. NOTE: filesystem should
+ * check for (ia_valid & ATTR_FILE), and not for (ia_file != NULL).
+ */
+ struct file *ia_file;
+};
+
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
@@ -393,10 +413,21 @@ static inline loff_t i_size_read(const struct inode *inode)
return inode->i_size;
}
+static inline void i_size_write(struct inode *inode, loff_t i_size)
+{
+ inode->i_size = i_size;
+}
+
+static inline void truncate_setsize(struct inode *inode, loff_t i_size)
+{
+ i_size_write(inode, i_size);
+}
+
struct inode *new_inode(struct super_block *sb);
unsigned int get_next_ino(void);
void iput(struct inode *);
struct inode *iget(struct inode *);
+void ihold(struct inode *inode);
void inc_nlink(struct inode *inode);
void clear_nlink(struct inode *inode);
void set_nlink(struct inode *inode, unsigned int nlink);
@@ -476,4 +507,20 @@ const char *simple_get_link(struct dentry *dentry, struct inode *inode);
struct inode *iget_locked(struct super_block *, unsigned long);
void iget_failed(struct inode *inode);
+static inline void inode_init_owner(struct inode *inode,
+ const struct inode *dir, umode_t mode)
+{
+ if (dir && dir->i_mode & S_ISGID) {
+ inode->i_gid = dir->i_gid;
+
+ /* Directories are special, and always inherit S_ISGID */
+ if (S_ISDIR(mode))
+ mode |= S_ISGID;
+ }
+
+ inode->i_mode = mode;
+}
+
+static inline void mark_inode_dirty(struct inode *inode) {}
+
#endif /* _LINUX_FS_H */
diff --git a/include/linux/limits.h b/include/linux/limits.h
index 2318e4f606d5..8c6f2d398b23 100644
--- a/include/linux/limits.h
+++ b/include/linux/limits.h
@@ -38,5 +38,6 @@
#define S64_MIN ((s64)(-S64_MAX - 1))
#define PATH_MAX 1024
+#define NAME_MAX 255 /* # chars in a file name */
#endif /* _LINUX_LIMITS_H */
diff --git a/include/linux/module.h b/include/linux/module.h
index 6a8ef6d6eccc..77bcc57a271a 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -13,5 +13,6 @@
#define MODULE_DEVICE_TABLE(bus, table)
#define MODULE_ALIAS_DSA_TAG_DRIVER(drv)
#define MODULE_ALIAS_CRYPTO(alias)
+#define MODULE_ALIAS_FS(alias)
#endif
diff --git a/include/linux/stat.h b/include/linux/stat.h
index fc3dd222a673..6ee05b52873f 100644
--- a/include/linux/stat.h
+++ b/include/linux/stat.h
@@ -45,6 +45,10 @@ extern "C" {
#define S_IXOTH 00001 /* execute/search permission for other */
#define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO)
+#define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
+#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
+#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
+#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
struct stat {
unsigned long st_ino;
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 09/10] fs: 9p: enable 9P over Virt I/O transport in defconfigs
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (7 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 08/10] fs: add new 9P2000.l (Plan 9) File system support Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 10/10] test: add support for --fs option in QEMU Ahmad Fatoum
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
All configs that currently enable VIRTIO_BLK are also likely candidates
to benefit from CONFIG_NET_9P_VIRTIO, so enable it too.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
arch/arm/configs/multi_v7_defconfig | 4 ++++
arch/arm/configs/multi_v8_defconfig | 4 ++++
arch/riscv/configs/rv64i_defconfig | 4 ++++
arch/riscv/configs/virt32_defconfig | 4 ++++
4 files changed, 16 insertions(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 7fb804c90707..de2d41e633fa 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -202,6 +202,8 @@ CONFIG_CMD_TIME=y
CONFIG_NET=y
CONFIG_NET_NETCONSOLE=y
CONFIG_NET_FASTBOOT=y
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
CONFIG_DEEP_PROBE_DEFAULT=y
CONFIG_OF_BAREBOX_DRIVERS=y
CONFIG_AIODEV=y
@@ -346,6 +348,8 @@ CONFIG_FS_EXT4=y
CONFIG_FS_TFTP=y
CONFIG_FS_TFTP_MAX_WINDOW_SIZE=1
CONFIG_FS_NFS=y
+CONFIG_9P_FS=y
+CONFIG_9P_FS_WRITE=y
CONFIG_FS_FAT=y
CONFIG_FS_FAT_WRITE=y
CONFIG_FS_UBIFS=y
diff --git a/arch/arm/configs/multi_v8_defconfig b/arch/arm/configs/multi_v8_defconfig
index 78e7adda0dee..ca3c891b4679 100644
--- a/arch/arm/configs/multi_v8_defconfig
+++ b/arch/arm/configs/multi_v8_defconfig
@@ -150,6 +150,8 @@ CONFIG_NET=y
CONFIG_NET_NETCONSOLE=y
CONFIG_NET_SNTP=y
CONFIG_NET_FASTBOOT=y
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
CONFIG_DEEP_PROBE_DEFAULT=y
CONFIG_OF_BAREBOX_DRIVERS=y
CONFIG_OF_BAREBOX_ENV_IN_FS=y
@@ -274,6 +276,8 @@ CONFIG_FS_CRAMFS=y
CONFIG_FS_EXT4=y
CONFIG_FS_TFTP=y
CONFIG_FS_NFS=y
+CONFIG_9P_FS=y
+CONFIG_9P_FS_WRITE=y
CONFIG_FS_FAT=y
CONFIG_FS_FAT_WRITE=y
CONFIG_FS_BPKFS=y
diff --git a/arch/riscv/configs/rv64i_defconfig b/arch/riscv/configs/rv64i_defconfig
index 32b681d17c61..bedb2727a552 100644
--- a/arch/riscv/configs/rv64i_defconfig
+++ b/arch/riscv/configs/rv64i_defconfig
@@ -93,6 +93,8 @@ CONFIG_CMD_DHRYSTONE=y
CONFIG_NET=y
CONFIG_NET_NETCONSOLE=y
CONFIG_NET_FASTBOOT=y
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
CONFIG_OF_BAREBOX_DRIVERS=y
CONFIG_OF_BAREBOX_ENV_IN_FS=y
CONFIG_DRIVER_SERIAL_NS16550=y
@@ -163,6 +165,8 @@ CONFIG_VIRTIO_MMIO=y
CONFIG_FS_EXT4=y
CONFIG_FS_TFTP=y
CONFIG_FS_NFS=y
+CONFIG_9P_FS=y
+CONFIG_9P_FS_WRITE=y
CONFIG_FS_FAT=y
CONFIG_FS_FAT_WRITE=y
CONFIG_FS_FAT_LFN=y
diff --git a/arch/riscv/configs/virt32_defconfig b/arch/riscv/configs/virt32_defconfig
index c56f9053fb3c..8020604ada9d 100644
--- a/arch/riscv/configs/virt32_defconfig
+++ b/arch/riscv/configs/virt32_defconfig
@@ -80,6 +80,8 @@ CONFIG_CMD_DHRYSTONE=y
CONFIG_NET=y
CONFIG_NET_NETCONSOLE=y
CONFIG_NET_FASTBOOT=y
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
CONFIG_OF_BAREBOX_DRIVERS=y
CONFIG_OF_BAREBOX_ENV_IN_FS=y
CONFIG_DRIVER_SERIAL_NS16550=y
@@ -120,6 +122,8 @@ CONFIG_VIRTIO_MMIO=y
CONFIG_FS_EXT4=y
CONFIG_FS_TFTP=y
CONFIG_FS_NFS=y
+CONFIG_9P_FS=y
+CONFIG_9P_FS_WRITE=y
CONFIG_FS_FAT=y
CONFIG_FS_FAT_WRITE=y
CONFIG_FS_FAT_LFN=y
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 10/10] test: add support for --fs option in QEMU
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
` (8 preceding siblings ...)
2025-06-06 8:58 ` [PATCH 09/10] fs: 9p: enable 9P over Virt I/O transport in defconfigs Ahmad Fatoum
@ 2025-06-06 8:58 ` Ahmad Fatoum
9 siblings, 0 replies; 11+ messages in thread
From: Ahmad Fatoum @ 2025-06-06 8:58 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
This is a convenient way to pass files from and to barebox running
in QEMU.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
conftest.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/conftest.py b/conftest.py
index 5dfefef24638..feccbcfbf52e 100644
--- a/conftest.py
+++ b/conftest.py
@@ -58,6 +58,9 @@ def pytest_addoption(parser):
parser.addoption('--blk', action='append', dest='qemu_block',
default=[], metavar="FILE",
help=('Pass block device to emulated barebox. Can be specified more than once'))
+ parser.addoption('--fs', action='append', dest='qemu_fs',
+ default=[], metavar="[tag=]DIR", type=assignment,
+ help=('Pass directory trees to emulated barebox. Can be specified more than once'))
parser.addoption('--env', action='append', dest='qemu_fw_cfg',
default=[], metavar="[envpath=]content | [envpath=]@filepath", type=assignment,
help=('Pass barebox environment files to barebox. Can be specified more than once'))
@@ -115,6 +118,18 @@ def strategy(request, target, pytestconfig):
else:
pytest.exit("--blk unsupported for target\n", 1)
+ for i, fs in enumerate(pytestconfig.option.qemu_fs):
+ if virtio:
+ path = fs.pop()
+ tag = fs.pop() if fs else f"fs{i}"
+
+ strategy.append_qemu_args(
+ "-fsdev", f"local,security_model=mapped,id=fs{i},path={path}",
+ "-device", f"virtio-9p-{virtio},id=fs{i},fsdev=fs{i},mount_tag={tag}"
+ )
+ else:
+ pytest.exit("--fs unsupported for target\n", 1)
+
for i, fw_cfg in enumerate(pytestconfig.option.qemu_fw_cfg):
if virtio:
value = fw_cfg.pop()
--
2.39.5
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-06-06 8:59 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-06 8:58 [PATCH 00/10] fs: add virtfs (Plan 9 ove Virt I/O) Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 01/10] tftp: centralize 2 sec d_revalidate optimization to new netfs lib Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 02/10] Port Linux __cleanup() based guard infrastructure Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 03/10] lib: idr: implement Linux idr_alloc/_u32 API Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 04/10] lib: add iov_iter I/O vector iterator support Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 05/10] lib: add parser code for mount options Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 06/10] include: add definitions for UID/GID/DEV Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 07/10] net: add support for 9P protocol Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 08/10] fs: add new 9P2000.l (Plan 9) File system support Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 09/10] fs: 9p: enable 9P over Virt I/O transport in defconfigs Ahmad Fatoum
2025-06-06 8:58 ` [PATCH 10/10] test: add support for --fs option in QEMU Ahmad Fatoum
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox