2022-11-22 02:30:46

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 00/21] FUSE BPF: A Stacked Filesystem Extension for FUSE

These patches extend FUSE to be able to act as a stacked filesystem. This
allows pure passthrough, where the fuse file system simply reflects the lower
filesystem, and also allows optional pre and post filtering in BPF and/or the
userspace daemon as needed. This can dramatically reduce or even eliminate
transitions to and from userspace.

For this patch set, I have removed the code related to the bpf side of things
since that is undergoing some large reworks to get it in line with the more
recent BPF developements. This set of patches implements direct passthrough to
the lower filesystem with no alteration. Looking at the v1 code should give a
pretty good idea of what the general shape of the bpf calls will look like.
Without the bpf side, it's like a less efficient bind mount. Not very useful
on its own, but still useful to get eyes on it since the backing calls will be
larglely the same when bpf is in the mix.

This changes the format of adding a backing file/bpf slightly from v1. It's now
a bit more modular. You add a block of data at the end of a lookup response to
give the bpf fd and backing id, but there is now a type header to both blocks,
and a reserved value for future additions. In the future, we may allow for
multiple bpfs or backing files, and this will allow us to extend it without any
UAPI breaking changes. Multiple BPFs would be useful for combining fuse-bpf
implementations without needing to manually combine bpf fragments. Multiple
backing files would allow implementing things like a limited overlayfs.
In this patch set, this is only a single block, with only backing supported,
although I've left the definitions reflecting the BPF case as well.
For bpf, the plan is to have two blocks, with the bpf one coming first.
Any further extensions are currently just speculative.

You can run this without needing to set up a userspace daemon by adding these
mount options: root_dir=[fd],no_daemon where fd is an open file descriptor
pointing to the folder you'd like to use as the root directory. The fd can be
immediately closed after mounting. This is useful for running various fs tests.

The main changes for v2:
-Refactored code to remove many of the ifdefs
-Adjusted attr related code per Amir's suggestions
-Added ioctl interface for responding to fuse requests (required for backing)
-Adjusted lookup add-on block for adding backing file/bpf
-Moved bpf related patches to the end of the stack (not included currently)

TODO:
override_creds to interact with backing files in the same context the daemon
would

Implement backing calls for other FUSE operations (i.e. File Locking/tmp files)

Convert BPF over to more modern version

Alessio Balsini (1):
fs: Generic function to convert iocb to rw flags

Daniel Rosenberg (20):
fuse-bpf: Update fuse side uapi
fuse-bpf: Prepare for fuse-bpf patch
fuse: Add fuse-bpf, a stacked fs extension for FUSE
fuse-bpf: Add ioctl interface for /dev/fuse
fuse-bpf: Don't support export_operations
fuse-bpf: Add support for FUSE_ACCESS
fuse-bpf: Partially add mapping support
fuse-bpf: Add lseek support
fuse-bpf: Add support for fallocate
fuse-bpf: Support file/dir open/close
fuse-bpf: Support mknod/unlink/mkdir/rmdir
fuse-bpf: Add support for read/write iter
fuse-bpf: support FUSE_READDIR
fuse-bpf: Add support for sync operations
fuse-bpf: Add Rename support
fuse-bpf: Add attr support
fuse-bpf: Add support for FUSE_COPY_FILE_RANGE
fuse-bpf: Add xattr support
fuse-bpf: Add symlink/link support
fuse-bpf: allow mounting with no userspace daemon

fs/fuse/Kconfig | 8 +
fs/fuse/Makefile | 1 +
fs/fuse/backing.c | 3118 +++++++++++++++++++++++++++++++++++++
fs/fuse/control.c | 2 +-
fs/fuse/dev.c | 83 +-
fs/fuse/dir.c | 326 ++--
fs/fuse/file.c | 62 +-
fs/fuse/fuse_i.h | 424 ++++-
fs/fuse/inode.c | 264 +++-
fs/fuse/ioctl.c | 2 +-
fs/fuse/readdir.c | 5 +
fs/fuse/xattr.c | 18 +
fs/overlayfs/file.c | 23 +-
include/linux/fs.h | 5 +
include/uapi/linux/fuse.h | 24 +-
15 files changed, 4154 insertions(+), 211 deletions(-)
create mode 100644 fs/fuse/backing.c


base-commit: 23a60a03d9a9980d1e91190491ceea0dc58fae62
--
2.38.1.584.g0f3c55d4c2-goog


2022-11-22 02:31:17

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 05/21] fuse-bpf: Add ioctl interface for /dev/fuse

This introduces an alternative method of responding to fuse requests.
Lookups supplying a backing fd or bpf will need to call through the
ioctl to ensure there can be no attempts to fool priveledged processes
into inadvertantly performing other actions.

Signed-off-by: Daniel Rosenberg <[email protected]>
---
fs/fuse/dev.c | 56 ++++++++++++++++++++++++++++++++-------
fs/fuse/fuse_i.h | 1 +
include/uapi/linux/fuse.h | 1 +
3 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 79d2fb6adc83..fbc519c37e66 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1013,18 +1013,19 @@ static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size)

/* Copy the fuse-bpf lookup args and verify them */
#ifdef CONFIG_FUSE_BPF
-static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned size)
+static int fuse_copy_lookup(struct fuse_copy_state *cs, unsigned via_ioctl, void *val, unsigned size)
{
struct fuse_bpf_entry_out *fbeo = (struct fuse_bpf_entry_out *)val;
struct fuse_bpf_entry *feb = container_of(fbeo, struct fuse_bpf_entry, out[0]);
int num_entries = size / sizeof(*fbeo);
int err;

- if (size && size % sizeof(*fbeo) != 0)
+ if (size && (size % sizeof(*fbeo) != 0 || !via_ioctl))
return -EINVAL;

if (num_entries > FUSE_BPF_MAX_ENTRIES)
return -EINVAL;
+
err = fuse_copy_one(cs, val, size);
if (err)
return err;
@@ -1033,7 +1034,7 @@ static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned size
return err;
}
#else
-static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned size)
+static int fuse_copy_lookup(struct fuse_copy_state *cs, unsigned via_ioctl, void *val, unsigned size)
{
return fuse_copy_one(cs, val, size);
}
@@ -1042,7 +1043,7 @@ static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned size
/* Copy request arguments to/from userspace buffer */
static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs,
unsigned argpages, struct fuse_arg *args,
- int zeroing, unsigned is_lookup)
+ int zeroing, unsigned is_lookup, unsigned via_ioct)
{
int err = 0;
unsigned i;
@@ -1052,7 +1053,7 @@ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs,
if (i == numargs - 1 && argpages)
err = fuse_copy_pages(cs, arg->size, zeroing);
else if (i == numargs - 1 && is_lookup)
- err = fuse_copy_lookup(cs, arg->value, arg->size);
+ err = fuse_copy_lookup(cs, via_ioct, arg->value, arg->size);
else
err = fuse_copy_one(cs, arg->value, arg->size);
}
@@ -1330,7 +1331,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
err = fuse_copy_one(cs, &req->in.h, sizeof(req->in.h));
if (!err)
err = fuse_copy_args(cs, args->in_numargs, args->in_pages,
- (struct fuse_arg *) args->in_args, 0, 0);
+ (struct fuse_arg *) args->in_args, 0, 0, 0);
fuse_copy_finish(cs);
spin_lock(&fpq->lock);
clear_bit(FR_LOCKED, &req->flags);
@@ -1869,7 +1870,8 @@ static int copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args,
lastarg->size -= diffsize;
}
return fuse_copy_args(cs, args->out_numargs, args->out_pages,
- args->out_args, args->page_zeroing, args->is_lookup);
+ args->out_args, args->page_zeroing, args->is_lookup,
+ args->via_ioctl);
}

/*
@@ -1879,7 +1881,7 @@ static int copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args,
* it from the list and copy the rest of the buffer to the request.
* The request is finished by calling fuse_request_end().
*/
-static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
+static ssize_t fuse_dev_do_write(struct fuse_dev *fud, bool from_ioctl,
struct fuse_copy_state *cs, size_t nbytes)
{
int err;
@@ -1951,6 +1953,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
if (!req->args->page_replace)
cs->move_pages = 0;

+ req->args->via_ioctl = from_ioctl;
if (oh.error)
err = nbytes != sizeof(oh) ? -EINVAL : 0;
else
@@ -1989,7 +1992,7 @@ static ssize_t fuse_dev_write(struct kiocb *iocb, struct iov_iter *from)

fuse_copy_init(&cs, 0, from);

- return fuse_dev_do_write(fud, &cs, iov_iter_count(from));
+ return fuse_dev_do_write(fud, false, &cs, iov_iter_count(from));
}

static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
@@ -2070,7 +2073,7 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
if (flags & SPLICE_F_MOVE)
cs.move_pages = 1;

- ret = fuse_dev_do_write(fud, &cs, len);
+ ret = fuse_dev_do_write(fud, false, &cs, len);

pipe_lock(pipe);
out_free:
@@ -2283,6 +2286,33 @@ static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
return 0;
}

+// Provides an alternate means to respond to a fuse request
+static int fuse_handle_ioc_response(struct fuse_dev *dev, void *buff, uint32_t size)
+{
+ struct fuse_copy_state cs;
+ struct iovec *iov = NULL;
+ struct iov_iter iter;
+ int res;
+
+ if (size > PAGE_SIZE)
+ return -EINVAL;
+ iov = (struct iovec *) __get_free_page(GFP_KERNEL);
+ if (!iov)
+ return -ENOMEM;
+
+ iov->iov_base = buff;
+ iov->iov_len = size;
+
+ iov_iter_init(&iter, READ, iov, 1, size);
+ fuse_copy_init(&cs, 0, &iter);
+
+
+ res = fuse_dev_do_write(dev, true, &cs, size);
+ free_page((unsigned long) iov);
+
+ return res;
+}
+
static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -2316,6 +2346,12 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
}
break;
default:
+ if (_IOC_TYPE(cmd) == FUSE_DEV_IOC_MAGIC
+ && _IOC_NR(cmd) == _IOC_NR(FUSE_DEV_IOC_BPF_RESPONSE(0))
+ && _IOC_DIR(cmd) == _IOC_WRITE) {
+ res = fuse_handle_ioc_response(fuse_get_dev(file), (void *) arg, _IOC_SIZE(cmd));
+ break;
+ }
res = -ENOTTY;
break;
}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d67325af5e72..3452530aba94 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -314,6 +314,7 @@ struct fuse_args {
bool page_replace:1;
bool may_block:1;
bool is_lookup:1;
+ bool via_ioctl:1;
struct fuse_in_arg in_args[3];
struct fuse_arg out_args[2];
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 0e19076729d9..e49e5a8e044c 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -972,6 +972,7 @@ struct fuse_notify_retrieve_in {
/* Device ioctls: */
#define FUSE_DEV_IOC_MAGIC 229
#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+#define FUSE_DEV_IOC_BPF_RESPONSE(N) _IOW(FUSE_DEV_IOC_MAGIC, 125, char[N])

struct fuse_lseek_in {
uint64_t fh;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:31:28

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 01/21] fs: Generic function to convert iocb to rw flags

From: Alessio Balsini <[email protected]>

OverlayFS implements its own function to translate iocb flags into rw
flags, so that they can be passed into another vfs call.
With commit ce71bfea207b4 ("fs: align IOCB_* flags with RWF_* flags")
Jens created a 1:1 matching between the iocb flags and rw flags,
simplifying the conversion.

Reduce the OverlayFS code by making the flag conversion function generic
and reusable.

Signed-off-by: Alessio Balsini <[email protected]>
---
fs/overlayfs/file.c | 23 +++++------------------
include/linux/fs.h | 5 +++++
2 files changed, 10 insertions(+), 18 deletions(-)

diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index a1a22f58ba18..287ae968852a 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -15,6 +15,8 @@
#include <linux/fs.h>
#include "overlayfs.h"

+#define OVL_IOCB_MASK (IOCB_DSYNC | IOCB_HIPRI | IOCB_NOWAIT | IOCB_SYNC)
+
struct ovl_aio_req {
struct kiocb iocb;
refcount_t ref;
@@ -240,22 +242,6 @@ static void ovl_file_accessed(struct file *file)
touch_atime(&file->f_path);
}

-static rwf_t ovl_iocb_to_rwf(int ifl)
-{
- rwf_t flags = 0;
-
- if (ifl & IOCB_NOWAIT)
- flags |= RWF_NOWAIT;
- if (ifl & IOCB_HIPRI)
- flags |= RWF_HIPRI;
- if (ifl & IOCB_DSYNC)
- flags |= RWF_DSYNC;
- if (ifl & IOCB_SYNC)
- flags |= RWF_SYNC;
-
- return flags;
-}
-
static inline void ovl_aio_put(struct ovl_aio_req *aio_req)
{
if (refcount_dec_and_test(&aio_req->ref)) {
@@ -315,7 +301,8 @@ static ssize_t ovl_read_iter(struct kiocb *iocb, struct iov_iter *iter)
old_cred = ovl_override_creds(file_inode(file)->i_sb);
if (is_sync_kiocb(iocb)) {
ret = vfs_iter_read(real.file, iter, &iocb->ki_pos,
- ovl_iocb_to_rwf(iocb->ki_flags));
+ iocb_to_rw_flags(iocb->ki_flags,
+ OVL_IOCB_MASK));
} else {
struct ovl_aio_req *aio_req;

@@ -379,7 +366,7 @@ static ssize_t ovl_write_iter(struct kiocb *iocb, struct iov_iter *iter)
if (is_sync_kiocb(iocb)) {
file_start_write(real.file);
ret = vfs_iter_write(real.file, iter, &iocb->ki_pos,
- ovl_iocb_to_rwf(ifl));
+ iocb_to_rw_flags(ifl, OVL_IOCB_MASK));
file_end_write(real.file);
/* Update size */
ovl_copyattr(inode);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index e654435f1651..c913106fdd65 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3434,6 +3434,11 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags)
return 0;
}

+static inline rwf_t iocb_to_rw_flags(int ifl, int iocb_mask)
+{
+ return ifl & iocb_mask;
+}
+
static inline ino_t parent_ino(struct dentry *dentry)
{
ino_t res;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:32:20

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 06/21] fuse-bpf: Don't support export_operations

In the future, we may choose to support these, but it poses some
challenges. In order to create a disconnected dentry/inode, we'll need
to encode the mountpoint and bpf into the file_handle, which means we'd
need a stable representation of them. This also won't hold up to cases
where the bpf is not stateless. One possibility is registering bpf
programs and mounts in a specific order, so they can be assigned
consistent ids we can use in the file_handle. We can defer to the lower
filesystem for the lower inode's representation in the file_handle.

Signed-off-by: Daniel Rosenberg <[email protected]>
---
fs/fuse/inode.c | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 224d7dfe754d..bafb2832627d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1100,6 +1100,14 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
nodeid = get_fuse_inode(inode)->nodeid;
generation = inode->i_generation;

+#ifdef CONFIG_FUSE_BPF
+ /* TODO: Does it make sense to support this in some cases? */
+ if (!nodeid && get_fuse_inode(inode)->backing_inode) {
+ *max_len = 0;
+ return FILEID_INVALID;
+ }
+#endif
+
fh[0] = (u32)(nodeid >> 32);
fh[1] = (u32)(nodeid & 0xffffffff);
fh[2] = generation;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:32:39

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 18/21] fuse-bpf: Add support for FUSE_COPY_FILE_RANGE

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/file.c | 4 +++
fs/fuse/fuse_i.h | 10 ++++++
3 files changed, 99 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index e2fe8c3aac2d..36c8688c4463 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -792,6 +792,91 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t o
file, offset, whence);
}

+struct fuse_copy_file_range_io {
+ struct fuse_copy_file_range_in fci;
+ struct fuse_write_out fwo;
+};
+
+static int fuse_copy_file_range_initialize_in(struct fuse_args *fa,
+ struct fuse_copy_file_range_io *fcf,
+ struct file *file_in, loff_t pos_in, struct file *file_out,
+ loff_t pos_out, size_t len, unsigned int flags)
+{
+ struct fuse_file *fuse_file_in = file_in->private_data;
+ struct fuse_file *fuse_file_out = file_out->private_data;
+
+ fcf->fci = (struct fuse_copy_file_range_in) {
+ .fh_in = fuse_file_in->fh,
+ .off_in = pos_in,
+ .nodeid_out = fuse_file_out->nodeid,
+ .fh_out = fuse_file_out->fh,
+ .off_out = pos_out,
+ .len = len,
+ .flags = flags,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(file_in->f_inode),
+ .opcode = FUSE_COPY_FILE_RANGE,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(fcf->fci),
+ .in_args[0].value = &fcf->fci,
+ };
+
+ return 0;
+}
+
+static int fuse_copy_file_range_initialize_out(struct fuse_args *fa,
+ struct fuse_copy_file_range_io *fcf,
+ struct file *file_in, loff_t pos_in, struct file *file_out,
+ loff_t pos_out, size_t len, unsigned int flags)
+{
+ fa->out_numargs = 1;
+ fa->out_args[0].size = sizeof(fcf->fwo);
+ fa->out_args[0].value = &fcf->fwo;
+
+ return 0;
+}
+
+static int fuse_copy_file_range_backing(struct fuse_args *fa, ssize_t *out, struct file *file_in,
+ loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len,
+ unsigned int flags)
+{
+ const struct fuse_copy_file_range_in *fci = fa->in_args[0].value;
+ struct fuse_file *fuse_file_in = file_in->private_data;
+ struct file *backing_file_in = fuse_file_in->backing_file;
+ struct fuse_file *fuse_file_out = file_out->private_data;
+ struct file *backing_file_out = fuse_file_out->backing_file;
+
+ /* TODO: Handle changing of in/out files */
+ if (backing_file_out)
+ *out = vfs_copy_file_range(backing_file_in, fci->off_in, backing_file_out,
+ fci->off_out, fci->len, fci->flags);
+ else
+ *out = generic_copy_file_range(file_in, pos_in, file_out, pos_out, len,
+ flags);
+ return 0;
+}
+
+static int fuse_copy_file_range_finalize(struct fuse_args *fa, ssize_t *out, struct file *file_in,
+ loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len,
+ unsigned int flags)
+{
+ return 0;
+}
+
+int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct file *file_in,
+ loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len,
+ unsigned int flags)
+{
+ return fuse_bpf_backing(inode, struct fuse_copy_file_range_io, out,
+ fuse_copy_file_range_initialize_in,
+ fuse_copy_file_range_initialize_out,
+ fuse_copy_file_range_backing,
+ fuse_copy_file_range_finalize,
+ file_in, pos_in, file_out, pos_out, len, flags);
+}
+
static int fuse_fsync_initialize_in(struct fuse_args *fa, struct fuse_fsync_in *ffi,
struct file *file, loff_t start, loff_t end, int datasync)
{
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index fa9ee2740a42..8153e78ff1d6 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -3127,6 +3127,10 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
bool is_unstable = (!fc->writeback_cache) &&
((pos_out + len) > inode_out->i_size);

+ if (fuse_bpf_copy_file_range(&err, file_inode(file_in), file_in, pos_in,
+ file_out, pos_out, len, flags))
+ return err;
+
if (fc->no_copy_file_range)
return -EOPNOTSUPP;

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 8ecaf55e4632..275b649bb5ed 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1416,6 +1416,9 @@ int fuse_bpf_release(int *out, struct inode *inode, struct file *file);
int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file);
int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id);
int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence);
+int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct file *file_in, loff_t pos_in,
+ struct file *file_out, loff_t pos_out,
+ size_t len, unsigned int flags);
int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to);
@@ -1495,6 +1498,13 @@ static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *
return 0;
}

+static inline int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct file *file_in, loff_t pos_in,
+ struct file *file_out, loff_t pos_out,
+ size_t len, unsigned int flags)
+{
+ return 0;
+}
+
static inline int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:32:47

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 02/21] fuse-bpf: Update fuse side uapi

Adds structures which will be used to inform fuse about what it is being
stacked on top of. Once filters are in place, error_in will inform the
post filter if the backing call returned an error.

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
include/uapi/linux/fuse.h | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 76ee8f9e024a..0e19076729d9 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -576,6 +576,21 @@ struct fuse_entry_out {
struct fuse_attr attr;
};

+#define FUSE_BPF_MAX_ENTRIES 2
+
+enum fuse_bpf_type {
+ FUSE_ENTRY_BACKING = 1,
+ FUSE_ENTRY_BPF = 2,
+ FUSE_ENTRY_REMOVE_BACKING = 3,
+ FUSE_ENTRY_REMOVE_BPF = 4,
+};
+
+struct fuse_bpf_entry_out {
+ uint32_t entry_type;
+ uint32_t unused;
+ uint64_t fd;
+};
+
struct fuse_forget_in {
uint64_t nlookup;
};
@@ -874,7 +889,7 @@ struct fuse_in_header {
uint32_t uid;
uint32_t gid;
uint32_t pid;
- uint32_t padding;
+ uint32_t error_in;
};

struct fuse_out_header {
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:33:35

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 03/21] fuse-bpf: Prepare for fuse-bpf patch

This moves some functions and structs around to make the following patch
easier to read.

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/dir.c | 30 ------------------------------
fs/fuse/fuse_i.h | 35 +++++++++++++++++++++++++++++++++++
fs/fuse/inode.c | 44 ++++++++++++++++++++++----------------------
3 files changed, 57 insertions(+), 52 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index bb97a384dc5d..168903cadb54 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -46,10 +46,6 @@ static inline u64 fuse_dentry_time(const struct dentry *entry)
}

#else
-union fuse_dentry {
- u64 time;
- struct rcu_head rcu;
-};

static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time)
{
@@ -83,27 +79,6 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
__fuse_dentry_settime(dentry, time);
}

-/*
- * FUSE caches dentries and attributes with separate timeout. The
- * time in jiffies until the dentry/attributes are valid is stored in
- * dentry->d_fsdata and fuse_inode->i_time respectively.
- */
-
-/*
- * Calculate the time in jiffies until a dentry/attributes are valid
- */
-static u64 time_to_jiffies(u64 sec, u32 nsec)
-{
- if (sec || nsec) {
- struct timespec64 ts = {
- sec,
- min_t(u32, nsec, NSEC_PER_SEC - 1)
- };
-
- return get_jiffies_64() + timespec64_to_jiffies(&ts);
- } else
- return 0;
-}

/*
* Set dentry and possibly attribute timeouts from the lookup/mk*
@@ -115,11 +90,6 @@ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o)
time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
}

-static u64 attr_timeout(struct fuse_attr_out *o)
-{
- return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
-}
-
u64 entry_attr_timeout(struct fuse_entry_out *o)
{
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 98a9cf531873..57453296e662 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -63,6 +63,14 @@ struct fuse_forget_link {
struct fuse_forget_link *next;
};

+/** FUSE specific dentry data */
+#if BITS_PER_LONG < 64
+union fuse_dentry {
+ u64 time;
+ struct rcu_head rcu;
+};
+#endif
+
/** FUSE inode */
struct fuse_inode {
/** Inode data */
@@ -1319,4 +1327,31 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir);

+/*
+ * FUSE caches dentries and attributes with separate timeout. The
+ * time in jiffies until the dentry/attributes are valid is stored in
+ * dentry->d_fsdata and fuse_inode->i_time respectively.
+ */
+
+/*
+ * Calculate the time in jiffies until a dentry/attributes are valid
+ */
+static inline u64 time_to_jiffies(u64 sec, u32 nsec)
+{
+ if (sec || nsec) {
+ struct timespec64 ts = {
+ sec,
+ min_t(u32, nsec, NSEC_PER_SEC - 1)
+ };
+
+ return get_jiffies_64() + timespec64_to_jiffies(&ts);
+ } else
+ return 0;
+}
+
+static inline u64 attr_timeout(struct fuse_attr_out *o)
+{
+ return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
+}
+
#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 6b3beda16c1b..504336d56a7f 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -162,6 +162,28 @@ static ino_t fuse_squash_ino(u64 ino64)
return ino;
}

+static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
+ const struct fuse_inode *fi)
+{
+ *attr = (struct fuse_attr){
+ .ino = fi->inode.i_ino,
+ .size = fi->inode.i_size,
+ .blocks = fi->inode.i_blocks,
+ .atime = fi->inode.i_atime.tv_sec,
+ .mtime = fi->inode.i_mtime.tv_sec,
+ .ctime = fi->inode.i_ctime.tv_sec,
+ .atimensec = fi->inode.i_atime.tv_nsec,
+ .mtimensec = fi->inode.i_mtime.tv_nsec,
+ .ctimensec = fi->inode.i_ctime.tv_nsec,
+ .mode = fi->inode.i_mode,
+ .nlink = fi->inode.i_nlink,
+ .uid = fi->inode.i_uid.val,
+ .gid = fi->inode.i_gid.val,
+ .rdev = fi->inode.i_rdev,
+ .blksize = 1u << fi->inode.i_blkbits,
+ };
+}
+
void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
u64 attr_valid, u32 cache_mask)
{
@@ -1386,28 +1408,6 @@ void fuse_dev_free(struct fuse_dev *fud)
}
EXPORT_SYMBOL_GPL(fuse_dev_free);

-static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
- const struct fuse_inode *fi)
-{
- *attr = (struct fuse_attr){
- .ino = fi->inode.i_ino,
- .size = fi->inode.i_size,
- .blocks = fi->inode.i_blocks,
- .atime = fi->inode.i_atime.tv_sec,
- .mtime = fi->inode.i_mtime.tv_sec,
- .ctime = fi->inode.i_ctime.tv_sec,
- .atimensec = fi->inode.i_atime.tv_nsec,
- .mtimensec = fi->inode.i_mtime.tv_nsec,
- .ctimensec = fi->inode.i_ctime.tv_nsec,
- .mode = fi->inode.i_mode,
- .nlink = fi->inode.i_nlink,
- .uid = fi->inode.i_uid.val,
- .gid = fi->inode.i_gid.val,
- .rdev = fi->inode.i_rdev,
- .blksize = 1u << fi->inode.i_blkbits,
- };
-}
-
static void fuse_sb_defaults(struct super_block *sb)
{
sb->s_magic = FUSE_SUPER_MAGIC;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:33:40

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 11/21] fuse-bpf: Support file/dir open/close

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 356 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 8 ++
fs/fuse/file.c | 7 +
fs/fuse/fuse_i.h | 26 ++++
4 files changed, 397 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 51aadeb1b7dc..c8e95abc04aa 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -205,6 +205,362 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode,
attr->blksize = 1 << blkbits;
}

+struct fuse_open_io {
+ struct fuse_open_in foi;
+ struct fuse_open_out foo;
+};
+
+static int fuse_open_initialize_in(struct fuse_args *fa, struct fuse_open_io *foio,
+ struct inode *inode, struct file *file, bool isdir)
+{
+ foio->foi = (struct fuse_open_in) {
+ .flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY),
+ };
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(inode)->nodeid,
+ .opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN,
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(foio->foi),
+ .value = &foio->foi,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_open_initialize_out(struct fuse_args *fa, struct fuse_open_io *foio,
+ struct inode *inode, struct file *file, bool isdir)
+{
+ foio->foo = (struct fuse_open_out) { 0 };
+
+ fa->out_numargs = 1;
+ fa->out_args[0] = (struct fuse_arg) {
+ .size = sizeof(foio->foo),
+ .value = &foio->foo,
+ };
+
+ return 0;
+}
+
+static int fuse_open_backing(struct fuse_args *fa, int *out,
+ struct inode *inode, struct file *file, bool isdir)
+{
+ struct fuse_mount *fm = get_fuse_mount(inode);
+ const struct fuse_open_in *foi = fa->in_args[0].value;
+ struct fuse_file *ff;
+ int mask;
+ struct fuse_dentry *fd = get_fuse_dentry(file->f_path.dentry);
+ struct file *backing_file;
+
+ ff = fuse_file_alloc(fm);
+ if (!ff)
+ return -ENOMEM;
+ file->private_data = ff;
+
+ switch (foi->flags & O_ACCMODE) {
+ case O_RDONLY:
+ mask = MAY_READ;
+ break;
+
+ case O_WRONLY:
+ mask = MAY_WRITE;
+ break;
+
+ case O_RDWR:
+ mask = MAY_READ | MAY_WRITE;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ *out = inode_permission(&init_user_ns,
+ get_fuse_inode(inode)->backing_inode, mask);
+ if (*out)
+ return *out;
+
+ backing_file =
+ dentry_open(&fd->backing_path, foi->flags, current_cred());
+
+ if (IS_ERR(backing_file)) {
+ fuse_file_free(ff);
+ file->private_data = NULL;
+ return PTR_ERR(backing_file);
+ }
+ ff->backing_file = backing_file;
+
+ *out = 0;
+ return 0;
+}
+
+static int fuse_open_finalize(struct fuse_args *fa, int *out,
+ struct inode *inode, struct file *file, bool isdir)
+{
+ struct fuse_file *ff = file->private_data;
+ struct fuse_open_out *foo = fa->out_args[0].value;
+
+ if (ff) {
+ ff->fh = foo->fh;
+ ff->nodeid = get_fuse_inode(inode)->nodeid;
+ }
+ return 0;
+}
+
+int fuse_bpf_open(int *out, struct inode *inode, struct file *file, bool isdir)
+{
+ return fuse_bpf_backing(inode, struct fuse_open_io, out,
+ fuse_open_initialize_in, fuse_open_initialize_out,
+ fuse_open_backing,
+ fuse_open_finalize,
+ inode, file, isdir);
+}
+
+struct fuse_create_open_io {
+ struct fuse_create_in fci;
+ struct fuse_entry_out feo;
+ struct fuse_open_out foo;
+};
+
+static int fuse_create_open_initialize_in(struct fuse_args *fa, struct fuse_create_open_io *fcoio,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ fcoio->fci = (struct fuse_create_in) {
+ .flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY),
+ .mode = mode,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_CREATE,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(fcoio->fci),
+ .value = &fcoio->fci,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = (void *) entry->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_create_open_initialize_out(struct fuse_args *fa, struct fuse_create_open_io *fcoio,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ fcoio->feo = (struct fuse_entry_out) { 0 };
+ fcoio->foo = (struct fuse_open_out) { 0 };
+
+ fa->out_numargs = 2;
+ fa->out_args[0] = (struct fuse_arg) {
+ .size = sizeof(fcoio->feo),
+ .value = &fcoio->feo,
+ };
+ fa->out_args[1] = (struct fuse_arg) {
+ .size = sizeof(fcoio->foo),
+ .value = &fcoio->foo,
+ };
+
+ return 0;
+}
+
+static int fuse_open_file_backing(struct inode *inode, struct file *file)
+{
+ struct fuse_mount *fm = get_fuse_mount(inode);
+ struct dentry *entry = file->f_path.dentry;
+ struct fuse_dentry *fuse_dentry = get_fuse_dentry(entry);
+ struct fuse_file *fuse_file;
+ struct file *backing_file;
+
+ fuse_file = fuse_file_alloc(fm);
+ if (!fuse_file)
+ return -ENOMEM;
+ file->private_data = fuse_file;
+
+ backing_file = dentry_open(&fuse_dentry->backing_path, file->f_flags,
+ current_cred());
+ if (IS_ERR(backing_file)) {
+ fuse_file_free(fuse_file);
+ file->private_data = NULL;
+ return PTR_ERR(backing_file);
+ }
+ fuse_file->backing_file = backing_file;
+
+ return 0;
+}
+
+static int fuse_create_open_backing(struct fuse_args *fa, int *out,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ struct fuse_inode *dir_fuse_inode = get_fuse_inode(dir);
+ struct path backing_path;
+ struct inode *inode = NULL;
+ struct dentry *backing_parent;
+ struct dentry *newent;
+ const struct fuse_create_in *fci = fa->in_args[0].value;
+
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ if (IS_ERR(backing_path.dentry))
+ return PTR_ERR(backing_path.dentry);
+
+ if (d_really_is_positive(backing_path.dentry)) {
+ *out = -EIO;
+ goto out;
+ }
+
+ backing_parent = dget_parent(backing_path.dentry);
+ inode_lock_nested(dir_fuse_inode->backing_inode, I_MUTEX_PARENT);
+ *out = vfs_create(&init_user_ns, d_inode(backing_parent),
+ backing_path.dentry, fci->mode, true);
+ inode_unlock(d_inode(backing_parent));
+ dput(backing_parent);
+ if (*out)
+ goto out;
+
+ inode = fuse_iget_backing(dir->i_sb, 0, backing_path.dentry->d_inode);
+ if (IS_ERR(inode)) {
+ *out = PTR_ERR(inode);
+ goto out;
+ }
+
+ newent = d_splice_alias(inode, entry);
+ if (IS_ERR(newent)) {
+ *out = PTR_ERR(newent);
+ goto out;
+ }
+
+ entry = newent ? newent : entry;
+ *out = finish_open(file, entry, fuse_open_file_backing);
+
+out:
+ path_put(&backing_path);
+ return *out;
+}
+
+static int fuse_create_open_finalize(struct fuse_args *fa, int *out,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ struct fuse_file *ff = file->private_data;
+ struct fuse_inode *fi = get_fuse_inode(file->f_inode);
+ struct fuse_entry_out *feo = fa->out_args[0].value;
+ struct fuse_open_out *foo = fa->out_args[1].value;
+
+ if (fi)
+ fi->nodeid = feo->nodeid;
+ if (ff)
+ ff->fh = foo->fh;
+ return 0;
+}
+
+int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ return fuse_bpf_backing(dir, struct fuse_create_open_io, out,
+ fuse_create_open_initialize_in,
+ fuse_create_open_initialize_out,
+ fuse_create_open_backing,
+ fuse_create_open_finalize,
+ dir, entry, file, flags, mode);
+}
+
+static int fuse_release_initialize_in(struct fuse_args *fa, struct fuse_release_in *fri,
+ struct inode *inode, struct file *file)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ /* Always put backing file whatever bpf/userspace says */
+ fput(fuse_file->backing_file);
+
+ *fri = (struct fuse_release_in) {
+ .fh = ((struct fuse_file *)(file->private_data))->fh,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(inode)->nodeid,
+ .opcode = FUSE_RELEASE,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*fri),
+ .in_args[0].value = fri,
+ };
+
+ return 0;
+}
+
+static int fuse_release_initialize_out(struct fuse_args *fa, struct fuse_release_in *fri,
+ struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int fuse_releasedir_initialize_in(struct fuse_args *fa,
+ struct fuse_release_in *fri,
+ struct inode *inode, struct file *file)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ /* Always put backing file whatever bpf/userspace says */
+ fput(fuse_file->backing_file);
+
+ *fri = (struct fuse_release_in) {
+ .fh = ((struct fuse_file *)(file->private_data))->fh,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(inode)->nodeid,
+ .opcode = FUSE_RELEASEDIR,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*fri),
+ .in_args[0].value = fri,
+ };
+
+ return 0;
+}
+
+static int fuse_releasedir_initialize_out(struct fuse_args *fa,
+ struct fuse_release_in *fri,
+ struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int fuse_release_backing(struct fuse_args *fa, int *out,
+ struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int fuse_release_finalize(struct fuse_args *fa, int *out,
+ struct inode *inode, struct file *file)
+{
+ fuse_file_free(file->private_data);
+ *out = 0;
+ return 0;
+}
+
+int fuse_bpf_release(int *out, struct inode *inode, struct file *file)
+{
+ return fuse_bpf_backing(inode, struct fuse_release_in, out,
+ fuse_release_initialize_in, fuse_release_initialize_out,
+ fuse_release_backing, fuse_release_finalize,
+ inode, file);
+}
+
+int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file)
+{
+ return fuse_bpf_backing(inode, struct fuse_release_in, out,
+ fuse_releasedir_initialize_in, fuse_releasedir_initialize_out,
+ fuse_release_backing, fuse_release_finalize, inode, file);
+}
+
struct fuse_lseek_io {
struct fuse_lseek_in fli;
struct fuse_lseek_out flo;
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4e19320889ed..e330a6af9ee7 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -635,6 +635,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
/* Userspace expects S_IFREG in create mode */
BUG_ON((mode & S_IFMT) != S_IFREG);

+ if (fuse_bpf_create_open(&err, dir, entry, file, flags, mode))
+ return err;
+
forget = fuse_alloc_forget();
err = -ENOMEM;
if (!forget)
@@ -1554,6 +1557,11 @@ static int fuse_dir_open(struct inode *inode, struct file *file)

static int fuse_dir_release(struct inode *inode, struct file *file)
{
+ int err = 0;
+
+ if (fuse_bpf_releasedir(&err, inode, file))
+ return err;
+
fuse_release_common(file, true);

return 0;
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ab3cd43556e0..70a5bd5403ca 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -241,6 +241,9 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir)
if (err)
return err;

+ if (fuse_bpf_open(&err, inode, file, isdir))
+ return err;
+
if (is_wb_truncate || dax_truncate)
inode_lock(inode);

@@ -349,6 +352,10 @@ static int fuse_open(struct inode *inode, struct file *file)
static int fuse_release(struct inode *inode, struct file *file)
{
struct fuse_conn *fc = get_fuse_conn(inode);
+ int err;
+
+ if (fuse_bpf_release(&err, inode, file))
+ return err;

/*
* Dirty pages might remain despite write_inode_now() call from
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 4351dbc7f10d..794b1a06079c 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1400,6 +1400,11 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries);

#ifdef CONFIG_FUSE_BPF

+int fuse_bpf_open(int *err, struct inode *inode, struct file *file, bool isdir);
+int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode);
+int fuse_bpf_release(int *out, struct inode *inode, struct file *file);
+int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file);
int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence);
int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length);
int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry *entry, unsigned int flags);
@@ -1407,6 +1412,27 @@ int fuse_bpf_access(int *out, struct inode *inode, int mask);

#else

+static inline int fuse_bpf_open(int *err, struct inode *inode, struct file *file, bool isdir)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_release(int *out, struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:34:33

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 15/21] fuse-bpf: Add support for sync operations

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 3 +
fs/fuse/file.c | 6 ++
fs/fuse/fuse_i.h | 18 ++++++
4 files changed, 169 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index a15b5c107cfe..719292e03b18 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -652,6 +652,58 @@ int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file)
fuse_release_backing, fuse_release_finalize, inode, file);
}

+static int fuse_flush_initialize_in(struct fuse_args *fa, struct fuse_flush_in *ffi,
+ struct file *file, fl_owner_t id)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ *ffi = (struct fuse_flush_in) {
+ .fh = fuse_file->fh,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(file->f_inode),
+ .opcode = FUSE_FLUSH,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*ffi),
+ .in_args[0].value = ffi,
+ .force = true,
+ };
+
+ return 0;
+}
+
+static int fuse_flush_initialize_out(struct fuse_args *fa, struct fuse_flush_in *ffi,
+ struct file *file, fl_owner_t id)
+{
+ return 0;
+}
+
+static int fuse_flush_backing(struct fuse_args *fa, int *out, struct file *file, fl_owner_t id)
+{
+ struct fuse_file *fuse_file = file->private_data;
+ struct file *backing_file = fuse_file->backing_file;
+
+ *out = 0;
+ if (backing_file->f_op->flush)
+ *out = backing_file->f_op->flush(backing_file, id);
+ return *out;
+}
+
+static int fuse_flush_finalize(struct fuse_args *fa, int *out, struct file *file, fl_owner_t id)
+{
+ return 0;
+}
+
+int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id)
+{
+ return fuse_bpf_backing(inode, struct fuse_flush_in, out,
+ fuse_flush_initialize_in, fuse_flush_initialize_out,
+ fuse_flush_backing,
+ fuse_flush_finalize,
+ file, id);
+}
+
struct fuse_lseek_io {
struct fuse_lseek_in fli;
struct fuse_lseek_out flo;
@@ -740,6 +792,96 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t o
file, offset, whence);
}

+static int fuse_fsync_initialize_in(struct fuse_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ *ffi = (struct fuse_fsync_in) {
+ .fh = fuse_file->fh,
+ .fsync_flags = datasync ? FUSE_FSYNC_FDATASYNC : 0,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(file->f_inode)->nodeid,
+ .opcode = FUSE_FSYNC,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*ffi),
+ .in_args[0].value = ffi,
+ .force = true,
+ };
+
+ return 0;
+}
+
+static int fuse_fsync_initialize_out(struct fuse_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return 0;
+}
+
+static int fuse_fsync_backing(struct fuse_args *fa, int *out,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ struct fuse_file *fuse_file = file->private_data;
+ struct file *backing_file = fuse_file->backing_file;
+ const struct fuse_fsync_in *ffi = fa->in_args[0].value;
+ int new_datasync = (ffi->fsync_flags & FUSE_FSYNC_FDATASYNC) ? 1 : 0;
+
+ *out = vfs_fsync(backing_file, new_datasync);
+ return 0;
+}
+
+static int fuse_fsync_finalize(struct fuse_args *fa, int *out,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return 0;
+}
+
+int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return fuse_bpf_backing(inode, struct fuse_fsync_in, out,
+ fuse_fsync_initialize_in, fuse_fsync_initialize_out,
+ fuse_fsync_backing, fuse_fsync_finalize,
+ file, start, end, datasync);
+}
+
+static int fuse_dir_fsync_initialize_in(struct fuse_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ *ffi = (struct fuse_fsync_in) {
+ .fh = fuse_file->fh,
+ .fsync_flags = datasync ? FUSE_FSYNC_FDATASYNC : 0,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(file->f_inode)->nodeid,
+ .opcode = FUSE_FSYNCDIR,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*ffi),
+ .in_args[0].value = ffi,
+ .force = true,
+ };
+
+ return 0;
+}
+
+static int fuse_dir_fsync_initialize_out(struct fuse_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return 0;
+}
+
+int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return fuse_bpf_backing(inode, struct fuse_fsync_in, out,
+ fuse_dir_fsync_initialize_in, fuse_dir_fsync_initialize_out,
+ fuse_fsync_backing, fuse_fsync_finalize,
+ file, start, end, datasync);
+}
+
static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req)
{
if (refcount_dec_and_test(&aio_req->ref))
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 729a0348fa01..55ed3fb9d4a3 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1591,6 +1591,9 @@ static int fuse_dir_fsync(struct file *file, loff_t start, loff_t end,
if (fuse_is_bad(inode))
return -EIO;

+ if (fuse_bpf_dir_fsync(&err, inode, file, start, end, datasync))
+ return err;
+
if (fc->no_fsyncdir)
return 0;

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 59f3d85106d3..fa9ee2740a42 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -502,6 +502,9 @@ static int fuse_flush(struct file *file, fl_owner_t id)
if (fuse_is_bad(inode))
return -EIO;

+ if (fuse_bpf_flush(&err, file_inode(file), file, id))
+ return err;
+
if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache)
return 0;

@@ -577,6 +580,9 @@ static int fuse_fsync(struct file *file, loff_t start, loff_t end,
if (fuse_is_bad(inode))
return -EIO;

+ if (fuse_bpf_fsync(&err, inode, file, start, end, datasync))
+ return err;
+
inode_lock(inode);

/*
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 0ea3fb74caab..cb087364e9bb 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1409,7 +1409,10 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry);
int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry);
int fuse_bpf_release(int *out, struct inode *inode, struct file *file);
int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file);
+int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id);
int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence);
+int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
+int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to);
int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from);
int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length);
@@ -1460,11 +1463,26 @@ static inline int fuse_bpf_releasedir(int *out, struct inode *inode, struct file
return 0;
}

+static inline int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id)
+{
+ return 0;
+}
+
static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence)
{
return 0;
}

+static inline int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return 0;
+}
+
static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 02:34:51

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 16/21] fuse-bpf: Add Rename support

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 210 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 7 ++
fs/fuse/fuse_i.h | 18 ++++
3 files changed, 235 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 719292e03b18..333181d6ad73 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -1677,6 +1677,216 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry)
dir, entry);
}

+static int fuse_rename_backing_common(struct inode *olddir,
+ struct dentry *oldent,
+ struct inode *newdir,
+ struct dentry *newent, unsigned int flags)
+{
+ int err = 0;
+ struct path old_backing_path;
+ struct path new_backing_path;
+ struct dentry *old_backing_dir_dentry;
+ struct dentry *old_backing_dentry;
+ struct dentry *new_backing_dir_dentry;
+ struct dentry *new_backing_dentry;
+ struct dentry *trap = NULL;
+ struct inode *target_inode;
+ struct renamedata rd;
+
+ //TODO Actually deal with changing anything that isn't a flag
+ get_fuse_backing_path(oldent, &old_backing_path);
+ if (!old_backing_path.dentry)
+ return -EBADF;
+ get_fuse_backing_path(newent, &new_backing_path);
+ if (!new_backing_path.dentry) {
+ /*
+ * TODO A file being moved from a backing path to another
+ * backing path which is not yet instrumented with FUSE-BPF.
+ * This may be slow and should be substituted with something
+ * more clever.
+ */
+ err = -EXDEV;
+ goto put_old_path;
+ }
+ if (new_backing_path.mnt != old_backing_path.mnt) {
+ err = -EXDEV;
+ goto put_new_path;
+ }
+ old_backing_dentry = old_backing_path.dentry;
+ new_backing_dentry = new_backing_path.dentry;
+ old_backing_dir_dentry = dget_parent(old_backing_dentry);
+ new_backing_dir_dentry = dget_parent(new_backing_dentry);
+ target_inode = d_inode(newent);
+
+ trap = lock_rename(old_backing_dir_dentry, new_backing_dir_dentry);
+ if (trap == old_backing_dentry) {
+ err = -EINVAL;
+ goto put_parents;
+ }
+ if (trap == new_backing_dentry) {
+ err = -ENOTEMPTY;
+ goto put_parents;
+ }
+
+ rd = (struct renamedata) {
+ .old_mnt_userns = &init_user_ns,
+ .old_dir = d_inode(old_backing_dir_dentry),
+ .old_dentry = old_backing_dentry,
+ .new_mnt_userns = &init_user_ns,
+ .new_dir = d_inode(new_backing_dir_dentry),
+ .new_dentry = new_backing_dentry,
+ .flags = flags,
+ };
+ err = vfs_rename(&rd);
+ if (err)
+ goto unlock;
+ if (target_inode)
+ fsstack_copy_attr_all(target_inode,
+ get_fuse_inode(target_inode)->backing_inode);
+ fsstack_copy_attr_all(d_inode(oldent), d_inode(old_backing_dentry));
+unlock:
+ unlock_rename(old_backing_dir_dentry, new_backing_dir_dentry);
+put_parents:
+ dput(new_backing_dir_dentry);
+ dput(old_backing_dir_dentry);
+put_new_path:
+ path_put(&new_backing_path);
+put_old_path:
+ path_put(&old_backing_path);
+ return err;
+}
+
+static int fuse_rename2_initialize_in(struct fuse_args *fa, struct fuse_rename2_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ *fri = (struct fuse_rename2_in) {
+ .newdir = get_node_id(newdir),
+ .flags = flags,
+ };
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(olddir),
+ .opcode = FUSE_RENAME2,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(*fri),
+ .value = fri,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = oldent->d_name.len + 1,
+ .value = (void *) oldent->d_name.name,
+ },
+ .in_args[2] = (struct fuse_in_arg) {
+ .size = newent->d_name.len + 1,
+ .value = (void *) newent->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_rename2_initialize_out(struct fuse_args *fa, struct fuse_rename2_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ return 0;
+}
+
+static int fuse_rename2_backing(struct fuse_args *fa, int *out,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ const struct fuse_rename2_in *fri = fa->in_args[0].value;
+
+ /* TODO: deal with changing dirs/ents */
+ *out = fuse_rename_backing_common(olddir, oldent, newdir, newent,
+ fri->flags);
+ return *out;
+}
+
+static int fuse_rename2_finalize(struct fuse_args *fa, int *out,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ return 0;
+}
+
+int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ return fuse_bpf_backing(olddir, struct fuse_rename2_in, out,
+ fuse_rename2_initialize_in,
+ fuse_rename2_initialize_out, fuse_rename2_backing,
+ fuse_rename2_finalize,
+ olddir, oldent, newdir, newent, flags);
+}
+
+static int fuse_rename_initialize_in(struct fuse_args *fa, struct fuse_rename_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ *fri = (struct fuse_rename_in) {
+ .newdir = get_node_id(newdir),
+ };
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(olddir),
+ .opcode = FUSE_RENAME,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(*fri),
+ .value = fri,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = oldent->d_name.len + 1,
+ .value = (void *) oldent->d_name.name,
+ },
+ .in_args[2] = (struct fuse_in_arg) {
+ .size = newent->d_name.len + 1,
+ .value = (void *) newent->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_rename_initialize_out(struct fuse_args *fa, struct fuse_rename_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ return 0;
+}
+
+static int fuse_rename_backing(struct fuse_args *fa, int *out,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ /* TODO: deal with changing dirs/ents */
+ *out = fuse_rename_backing_common(olddir, oldent, newdir, newent, 0);
+ return *out;
+}
+
+static int fuse_rename_finalize(struct fuse_args *fa, int *out,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ return 0;
+}
+
+int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ return fuse_bpf_backing(olddir, struct fuse_rename_in, out,
+ fuse_rename_initialize_in,
+ fuse_rename_initialize_out, fuse_rename_backing,
+ fuse_rename_finalize,
+ olddir, oldent, newdir, newent);
+}
+
static int fuse_unlink_initialize_in(struct fuse_args *fa, struct fuse_unused_io *unused,
struct inode *dir, struct dentry *entry)
{
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 55ed3fb9d4a3..6ad0eb92de3b 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1116,6 +1116,10 @@ static int fuse_rename2(struct user_namespace *mnt_userns, struct inode *olddir,
return -EINVAL;

if (flags) {
+ if (fuse_bpf_rename2(&err, olddir, oldent, newdir, newent, flags))
+ return err;
+
+ /* TODO: how should this go with bpfs involved? */
if (fc->no_rename2 || fc->minor < 23)
return -EINVAL;

@@ -1127,6 +1131,9 @@ static int fuse_rename2(struct user_namespace *mnt_userns, struct inode *olddir,
err = -EINVAL;
}
} else {
+ if (fuse_bpf_rename(&err, olddir, oldent, newdir, newent))
+ return err;
+
err = fuse_rename_common(olddir, oldent, newdir, newent, 0,
FUSE_RENAME,
sizeof(struct fuse_rename_in));
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index cb087364e9bb..3338ac84d083 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1406,6 +1406,11 @@ int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry,
int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umode_t mode);
int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry);
+int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags);
+int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent);
int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry);
int fuse_bpf_release(int *out, struct inode *inode, struct file *file);
int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file);
@@ -1448,6 +1453,19 @@ static inline int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *ent
return 0;
}

+static inline int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ return 0;
+}
+
static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 03:01:03

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 21/21] fuse-bpf: allow mounting with no userspace daemon

When using fuse-bpf in pure passthrough mode, we don't explicitly need a
userspace daemon. This allows simple testing of the backing operations.

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/fuse_i.h | 4 ++++
fs/fuse/inode.c | 25 +++++++++++++++++++------
2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 99c9231ec98b..402d80d35958 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -564,6 +564,7 @@ struct fuse_fs_context {
bool no_control:1;
bool no_force_umount:1;
bool legacy_opts_show:1;
+ bool no_daemon:1;
enum fuse_dax_mode dax_mode;
unsigned int max_read;
unsigned int blksize;
@@ -842,6 +843,9 @@ struct fuse_conn {
/* Is tmpfile not implemented by fs? */
unsigned int no_tmpfile:1;

+ /** BPF Only, no Daemon running */
+ unsigned int no_daemon:1;
+
/** The number of requests waiting for completion */
atomic_t num_waiting;

diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 1e7d45977144..4820edcc242a 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -749,6 +749,7 @@ enum {
OPT_MAX_READ,
OPT_BLKSIZE,
OPT_ROOT_DIR,
+ OPT_NO_DAEMON,
OPT_ERR
};

@@ -764,6 +765,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
fsparam_u32 ("blksize", OPT_BLKSIZE),
fsparam_string ("subtype", OPT_SUBTYPE),
fsparam_u32 ("root_dir", OPT_ROOT_DIR),
+ fsparam_flag ("no_daemon", OPT_NO_DAEMON),
{}
};

@@ -853,6 +855,11 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
return invalfc(fsc, "Unable to open root directory");
break;

+ case OPT_NO_DAEMON:
+ ctx->no_daemon = true;
+ ctx->fd_present = true;
+ break;
+
default:
return -EINVAL;
}
@@ -1411,7 +1418,7 @@ void fuse_send_init(struct fuse_mount *fm)
ia->args.nocreds = true;
ia->args.end = process_init_reply;

- if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
+ if (unlikely(fm->fc->no_daemon) || fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
process_init_reply(fm, &ia->args, -ENOTCONN);
}
EXPORT_SYMBOL_GPL(fuse_send_init);
@@ -1693,6 +1700,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
fc->destroy = ctx->destroy;
fc->no_control = ctx->no_control;
fc->no_force_umount = ctx->no_force_umount;
+ fc->no_daemon = ctx->no_daemon;

err = -ENOMEM;
root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir);
@@ -1739,7 +1747,7 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
struct fuse_fs_context *ctx = fsc->fs_private;
int err;

- if (!ctx->file || !ctx->rootmode_present ||
+ if (!!ctx->file == ctx->no_daemon || !ctx->rootmode_present ||
!ctx->user_id_present || !ctx->group_id_present)
return -EINVAL;

@@ -1747,10 +1755,12 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
* Require mount to happen from the same user namespace which
* opened /dev/fuse to prevent potential attacks.
*/
- if ((ctx->file->f_op != &fuse_dev_operations) ||
- (ctx->file->f_cred->user_ns != sb->s_user_ns))
- return -EINVAL;
- ctx->fudptr = &ctx->file->private_data;
+ if (ctx->file) {
+ if ((ctx->file->f_op != &fuse_dev_operations) ||
+ (ctx->file->f_cred->user_ns != sb->s_user_ns))
+ return -EINVAL;
+ ctx->fudptr = &ctx->file->private_data;
+ }

err = fuse_fill_super_common(sb, ctx);
if (err)
@@ -1800,6 +1810,9 @@ static int fuse_get_tree(struct fs_context *fsc)

fsc->s_fs_info = fm;

+ if (ctx->no_daemon)
+ return get_tree_nodev(fsc, fuse_fill_super);
+
if (ctx->fd_present)
ctx->file = fget(ctx->fd);

--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 03:07:07

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 20/21] fuse-bpf: Add symlink/link support

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 11 ++
fs/fuse/fuse_i.h | 20 ++++
3 files changed, 302 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 05fb88865289..a77414e8f3df 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -2319,6 +2319,104 @@ int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry)
dir, entry);
}

+static int fuse_link_initialize_in(struct fuse_args *fa, struct fuse_link_in *fli,
+ struct dentry *entry, struct inode *dir,
+ struct dentry *newent)
+{
+ struct inode *src_inode = entry->d_inode;
+
+ *fli = (struct fuse_link_in) {
+ .oldnodeid = get_node_id(src_inode),
+ };
+
+ fa->opcode = FUSE_LINK;
+ fa->in_numargs = 2;
+ fa->in_args[0].size = sizeof(*fli);
+ fa->in_args[0].value = fli;
+ fa->in_args[1].size = newent->d_name.len + 1;
+ fa->in_args[1].value = (void *) newent->d_name.name;
+
+ return 0;
+}
+
+static int fuse_link_initialize_out(struct fuse_args *fa, struct fuse_link_in *fli,
+ struct dentry *entry, struct inode *dir,
+ struct dentry *newent)
+{
+ return 0;
+}
+
+static int fuse_link_backing(struct fuse_args *fa, int *out, struct dentry *entry,
+ struct inode *dir, struct dentry *newent)
+{
+ struct path backing_old_path;
+ struct path backing_new_path;
+ struct dentry *backing_dir_dentry;
+ struct inode *fuse_new_inode = NULL;
+ struct fuse_inode *fuse_dir_inode = get_fuse_inode(dir);
+ struct inode *backing_dir_inode = fuse_dir_inode->backing_inode;
+
+ *out = 0;
+ get_fuse_backing_path(entry, &backing_old_path);
+ if (!backing_old_path.dentry)
+ return -EBADF;
+
+ get_fuse_backing_path(newent, &backing_new_path);
+ if (!backing_new_path.dentry) {
+ *out = -EBADF;
+ goto err_dst_path;
+ }
+
+ backing_dir_dentry = dget_parent(backing_new_path.dentry);
+ backing_dir_inode = d_inode(backing_dir_dentry);
+
+ inode_lock_nested(backing_dir_inode, I_MUTEX_PARENT);
+ *out = vfs_link(backing_old_path.dentry, &init_user_ns,
+ backing_dir_inode, backing_new_path.dentry, NULL);
+ inode_unlock(backing_dir_inode);
+ if (*out)
+ goto out;
+
+ if (d_really_is_negative(backing_new_path.dentry) ||
+ unlikely(d_unhashed(backing_new_path.dentry))) {
+ *out = -EINVAL;
+ /**
+ * TODO: overlayfs responds to this situation with a
+ * lookupOneLen. Should we do that too?
+ */
+ goto out;
+ }
+
+ fuse_new_inode = fuse_iget_backing(dir->i_sb, fuse_dir_inode->nodeid, backing_dir_inode);
+ if (IS_ERR(fuse_new_inode)) {
+ *out = PTR_ERR(fuse_new_inode);
+ goto out;
+ }
+ d_instantiate(newent, fuse_new_inode);
+
+out:
+ dput(backing_dir_dentry);
+ path_put(&backing_new_path);
+err_dst_path:
+ path_put(&backing_old_path);
+ return *out;
+}
+
+static int fuse_link_finalize(struct fuse_args *fa, int *out, struct dentry *entry,
+ struct inode *dir, struct dentry *newent)
+{
+ return 0;
+}
+
+int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry,
+ struct inode *newdir, struct dentry *newent)
+{
+ return fuse_bpf_backing(inode, struct fuse_link_in, out,
+ fuse_link_initialize_in, fuse_link_initialize_out,
+ fuse_link_backing, fuse_link_finalize, entry,
+ newdir, newent);
+}
+
struct fuse_getattr_io {
struct fuse_getattr_in fgi;
struct fuse_attr_out fao;
@@ -2600,6 +2698,179 @@ int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, struct
dentry, buf);
}

+static int fuse_get_link_initialize_in(struct fuse_args *fa, struct fuse_unused_io *unused,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ /*
+ * TODO
+ * If we want to handle changing these things, we'll need to copy
+ * the lower fs's data into our own buffer, and provide our own callback
+ * to free that buffer.
+ *
+ * Pre could change the name we're looking at
+ * postfilter can change the name we return
+ *
+ * We ought to only make that buffer if it's been requested, so leaving
+ * this unimplemented for the moment
+ */
+ *fa = (struct fuse_args) {
+ .opcode = FUSE_READLINK,
+ .nodeid = get_node_id(inode),
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = dentry->d_name.len + 1,
+ .value = (void *) dentry->d_name.name,
+ },
+ /*
+ * .out_argvar = 1,
+ * .out_numargs = 1,
+ * .out_args[0].size = ,
+ * .out_args[0].value = ,
+ */
+ };
+
+ return 0;
+}
+
+static int fuse_get_link_initialize_out(struct fuse_args *fa, struct fuse_unused_io *unused,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ /*
+ * .out_argvar = 1,
+ * .out_numargs = 1,
+ * .out_args[0].size = ,
+ * .out_args[0].value = ,
+ */
+
+ return 0;
+}
+
+static int fuse_get_link_backing(struct fuse_args *fa, const char **out,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ struct path backing_path;
+
+ if (!dentry) {
+ *out = ERR_PTR(-ECHILD);
+ return PTR_ERR(*out);
+ }
+
+ get_fuse_backing_path(dentry, &backing_path);
+ if (!backing_path.dentry) {
+ *out = ERR_PTR(-ECHILD);
+ return PTR_ERR(*out);
+ }
+
+ /*
+ * TODO: If we want to do our own thing, copy the data and then call the
+ * callback
+ */
+ *out = vfs_get_link(backing_path.dentry, callback);
+
+ path_put(&backing_path);
+ return 0;
+}
+
+static int fuse_get_link_finalize(struct fuse_args *fa, const char **out,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ return 0;
+}
+
+int fuse_bpf_get_link(const char **out, struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ return fuse_bpf_backing(inode, struct fuse_unused_io, out,
+ fuse_get_link_initialize_in, fuse_get_link_initialize_out,
+ fuse_get_link_backing,
+ fuse_get_link_finalize,
+ inode, dentry, callback);
+}
+
+static int fuse_symlink_initialize_in(struct fuse_args *fa, struct fuse_unused_io *unused,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ *fa = (struct fuse_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_SYMLINK,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = (void *) entry->d_name.name,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = len,
+ .value = (void *) link,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_symlink_initialize_out(struct fuse_args *fa, struct fuse_unused_io *unused,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ return 0;
+}
+
+static int fuse_symlink_backing(struct fuse_args *fa, int *out,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ struct fuse_inode *fuse_inode = get_fuse_inode(dir);
+ struct inode *backing_inode = fuse_inode->backing_inode;
+ struct path backing_path;
+ struct inode *inode = NULL;
+
+ *out = 0;
+ //TODO Actually deal with changing the backing entry in symlink
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ inode_lock_nested(backing_inode, I_MUTEX_PARENT);
+ *out = vfs_symlink(&init_user_ns, backing_inode, backing_path.dentry,
+ link);
+ inode_unlock(backing_inode);
+ if (*out)
+ goto out;
+ if (d_really_is_negative(backing_path.dentry) ||
+ unlikely(d_unhashed(backing_path.dentry))) {
+ *out = -EINVAL;
+ /**
+ * TODO: overlayfs responds to this situation with a
+ * lookupOneLen. Should we do that too?
+ */
+ goto out;
+ }
+ inode = fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode);
+ if (IS_ERR(inode)) {
+ *out = PTR_ERR(inode);
+ goto out;
+ }
+ d_instantiate(entry, inode);
+out:
+ path_put(&backing_path);
+ return *out;
+}
+
+static int fuse_symlink_finalize(struct fuse_args *fa, int *out,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ return 0;
+}
+
+int fuse_bpf_symlink(int *out, struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ return fuse_bpf_backing(dir, struct fuse_unused_io, out,
+ fuse_symlink_initialize_in, fuse_symlink_initialize_out,
+ fuse_symlink_backing, fuse_symlink_finalize,
+ dir, entry, link, len);
+}
+
struct fuse_read_io {
struct fuse_read_in fri;
struct fuse_read_out fro;
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 899de6c84c2e..1f9105edc7e2 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -945,6 +945,10 @@ static int fuse_symlink(struct user_namespace *mnt_userns, struct inode *dir,
struct fuse_mount *fm = get_fuse_mount(dir);
unsigned len = strlen(link) + 1;
FUSE_ARGS(args);
+ int err;
+
+ if (fuse_bpf_symlink(&err, dir, entry, link, len))
+ return err;

args.opcode = FUSE_SYMLINK;
args.in_numargs = 2;
@@ -1151,6 +1155,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,
struct fuse_mount *fm = get_fuse_mount(inode);
FUSE_ARGS(args);

+ if (fuse_bpf_link(&err, inode, entry, newdir, newent))
+ return err;
+
memset(&inarg, 0, sizeof(inarg));
inarg.oldnodeid = get_node_id(inode);
args.opcode = FUSE_LINK;
@@ -1543,12 +1550,16 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
{
struct fuse_conn *fc = get_fuse_conn(inode);
struct page *page;
+ const char *out = NULL;
int err;

err = -EIO;
if (fuse_is_bad(inode))
goto out_err;

+ if (fuse_bpf_get_link(&out, inode, dentry, callback))
+ return out;
+
if (fc->cache_symlinks)
return page_get_link(dentry, inode, callback);

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 37b29a3ea330..99c9231ec98b 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1412,6 +1412,7 @@ int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent,
int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent,
struct inode *newdir, struct dentry *newent);
int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry);
+int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry, struct inode *dir, struct dentry *newent);
int fuse_bpf_release(int *out, struct inode *inode, struct file *file);
int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file);
int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id);
@@ -1436,6 +1437,9 @@ int fuse_bpf_getattr(int *out, struct inode *inode, const struct dentry *entry,
u32 request_mask, unsigned int flags);
int fuse_bpf_setattr(int *out, struct inode *inode, struct dentry *dentry, struct iattr *attr, struct file *file);
int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, struct kstatfs *buf);
+int fuse_bpf_get_link(const char **out, struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback);
+int fuse_bpf_symlink(int *out, struct inode *dir, struct dentry *entry, const char *link, int len);
int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx);
int fuse_bpf_access(int *out, struct inode *inode, int mask);

@@ -1485,6 +1489,11 @@ static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *en
return 0;
}

+static inline int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry, struct inode *dir, struct dentry *newent)
+{
+ return 0;
+}
+
static inline int fuse_bpf_release(int *out, struct inode *inode, struct file *file)
{
return 0;
@@ -1581,6 +1590,17 @@ static inline int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *
return 0;
}

+static inline int fuse_bpf_get_link(const char **out, struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_symlink(int *out, struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ return 0;
+}
+
static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 03:32:35

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 10/21] fuse-bpf: Add support for fallocate

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/file.c | 3 +++
fs/fuse/fuse_i.h | 6 +++++
3 files changed, 67 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 76f48872ed35..51aadeb1b7dc 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -330,6 +330,64 @@ ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma)
return ret;
}

+static int fuse_file_fallocate_initialize_in(struct fuse_args *fa,
+ struct fuse_fallocate_in *ffi,
+ struct file *file, int mode, loff_t offset, loff_t length)
+{
+ struct fuse_file *ff = file->private_data;
+
+ *ffi = (struct fuse_fallocate_in) {
+ .fh = ff->fh,
+ .offset = offset,
+ .length = length,
+ .mode = mode,
+ };
+
+ *fa = (struct fuse_args) {
+ .opcode = FUSE_FALLOCATE,
+ .nodeid = ff->nodeid,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*ffi),
+ .in_args[0].value = ffi,
+ };
+
+ return 0;
+}
+
+static int fuse_file_fallocate_initialize_out(struct fuse_args *fa,
+ struct fuse_fallocate_in *ffi,
+ struct file *file, int mode, loff_t offset, loff_t length)
+{
+ return 0;
+}
+
+static int fuse_file_fallocate_backing(struct fuse_args *fa, int *out,
+ struct file *file, int mode, loff_t offset, loff_t length)
+{
+ const struct fuse_fallocate_in *ffi = fa->in_args[0].value;
+ struct fuse_file *ff = file->private_data;
+
+ *out = vfs_fallocate(ff->backing_file, ffi->mode, ffi->offset,
+ ffi->length);
+ return 0;
+}
+
+static int fuse_file_fallocate_finalize(struct fuse_args *fa, int *out,
+ struct file *file, int mode, loff_t offset, loff_t length)
+{
+ return 0;
+}
+
+int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length)
+{
+ return fuse_bpf_backing(inode, struct fuse_fallocate_in, out,
+ fuse_file_fallocate_initialize_in,
+ fuse_file_fallocate_initialize_out,
+ fuse_file_fallocate_backing,
+ fuse_file_fallocate_finalize,
+ file, mode, offset, length);
+}
+
/*******************************************************************************
* Directory operations after here *
******************************************************************************/
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index e90b3e2d5452..ab3cd43556e0 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -2997,6 +2997,9 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,

bool block_faults = FUSE_IS_DAX(inode) && lock_inode;

+ if (fuse_bpf_file_fallocate(&err, inode, file, mode, offset, length))
+ return err;
+
if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
FALLOC_FL_ZERO_RANGE))
return -EOPNOTSUPP;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 108c2ea15a49..4351dbc7f10d 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1401,6 +1401,7 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries);
#ifdef CONFIG_FUSE_BPF

int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence);
+int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length);
int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry *entry, unsigned int flags);
int fuse_bpf_access(int *out, struct inode *inode, int mask);

@@ -1411,6 +1412,11 @@ static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *
return 0;
}

+static inline int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length)
+{
+ return 0;
+}
+
static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry *entry, unsigned int flags)
{
return 0;
--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 03:41:17

by Daniel Rosenberg

[permalink] [raw]
Subject: [RFC PATCH v2 19/21] fuse-bpf: Add xattr support

Signed-off-by: Daniel Rosenberg <[email protected]>
Signed-off-by: Paul Lawrence <[email protected]>
---
fs/fuse/backing.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/fuse_i.h | 30 +++++
fs/fuse/xattr.c | 18 +++
3 files changed, 333 insertions(+)

diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index 36c8688c4463..05fb88865289 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -967,6 +967,291 @@ int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t
file, start, end, datasync);
}

+struct fuse_getxattr_io {
+ struct fuse_getxattr_in fgi;
+ struct fuse_getxattr_out fgo;
+};
+
+static int fuse_getxattr_initialize_in(struct fuse_args *fa,
+ struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size)
+{
+ *fgio = (struct fuse_getxattr_io) {
+ .fgi.size = size,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_GETXATTR,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(fgio->fgi),
+ .value = &fgio->fgi,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = strlen(name) + 1,
+ .value = (void *) name,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_getxattr_initialize_out(struct fuse_args *fa,
+ struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size)
+{
+ fa->out_numargs = 1;
+ if (size) {
+ fa->out_argvar = true;
+ fa->out_args[0].size = size;
+ fa->out_args[0].value = value;
+ } else {
+ fa->out_args[0].size = sizeof(fgio->fgo);
+ fa->out_args[0].value = &fgio->fgo;
+ }
+ return 0;
+}
+
+static int fuse_getxattr_backing(struct fuse_args *fa, int *out,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size)
+{
+ ssize_t ret = vfs_getxattr(&init_user_ns,
+ get_fuse_dentry(dentry)->backing_path.dentry,
+ fa->in_args[1].value, value, size);
+
+ if (fa->out_argvar)
+ fa->out_args[0].size = ret;
+ else
+ ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = ret;
+
+ return 0;
+}
+
+static int fuse_getxattr_finalize(struct fuse_args *fa, int *out,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size)
+{
+ struct fuse_getxattr_out *fgo;
+
+ if (fa->out_argvar) {
+ *out = fa->out_args[0].size;
+ return 0;
+ }
+
+ fgo = fa->out_args[0].value;
+
+ *out = fgo->size;
+ return 0;
+}
+
+int fuse_bpf_getxattr(int *out, struct inode *inode, struct dentry *dentry, const char *name,
+ void *value, size_t size)
+{
+ return fuse_bpf_backing(inode, struct fuse_getxattr_io, out,
+ fuse_getxattr_initialize_in, fuse_getxattr_initialize_out,
+ fuse_getxattr_backing, fuse_getxattr_finalize,
+ dentry, name, value, size);
+}
+
+static int fuse_listxattr_initialize_in(struct fuse_args *fa,
+ struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, char *list, size_t size)
+{
+ *fgio = (struct fuse_getxattr_io) {
+ .fgi.size = size,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_LISTXATTR,
+ .in_numargs = 1,
+ .in_args[0] =
+ (struct fuse_in_arg) {
+ .size = sizeof(fgio->fgi),
+ .value = &fgio->fgi,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_listxattr_initialize_out(struct fuse_args *fa,
+ struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, char *list, size_t size)
+{
+ fa->out_numargs = 1;
+
+ if (size) {
+ fa->out_argvar = true;
+ fa->out_args[0].size = size;
+ fa->out_args[0].value = (void *)list;
+ } else {
+ fa->out_args[0].size = sizeof(fgio->fgo);
+ fa->out_args[0].value = &fgio->fgo;
+ }
+ return 0;
+}
+
+static int fuse_listxattr_backing(struct fuse_args *fa, ssize_t *out, struct dentry *dentry,
+ char *list, size_t size)
+{
+ *out = vfs_listxattr(get_fuse_dentry(dentry)->backing_path.dentry, list, size);
+
+ if (*out < 0)
+ return *out;
+
+ if (fa->out_argvar)
+ fa->out_args[0].size = *out;
+ else
+ ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = *out;
+
+ return 0;
+}
+
+static int fuse_listxattr_finalize(struct fuse_args *fa, ssize_t *out, struct dentry *dentry,
+ char *list, size_t size)
+{
+ struct fuse_getxattr_out *fgo;
+
+ if (fa->error_in)
+ return 0;
+
+ if (fa->out_argvar) {
+ *out = fa->out_args[0].size;
+ return 0;
+ }
+
+ fgo = fa->out_args[0].value;
+ *out = fgo->size;
+ return 0;
+}
+
+int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *dentry,
+ char *list, size_t size)
+{
+ return fuse_bpf_backing(inode, struct fuse_getxattr_io, out,
+ fuse_listxattr_initialize_in, fuse_listxattr_initialize_out,
+ fuse_listxattr_backing, fuse_listxattr_finalize,
+ dentry, list, size);
+}
+
+static int fuse_setxattr_initialize_in(struct fuse_args *fa,
+ struct fuse_setxattr_in *fsxi,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ *fsxi = (struct fuse_setxattr_in) {
+ .size = size,
+ .flags = flags,
+ };
+
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_SETXATTR,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = sizeof(*fsxi),
+ .value = fsxi,
+ },
+ .in_args[1] = (struct fuse_in_arg) {
+ .size = strlen(name) + 1,
+ .value = (void *) name,
+ },
+ .in_args[2] = (struct fuse_in_arg) {
+ .size = size,
+ .value = (void *) value,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_setxattr_initialize_out(struct fuse_args *fa,
+ struct fuse_setxattr_in *fsxi,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ return 0;
+}
+
+static int fuse_setxattr_backing(struct fuse_args *fa, int *out, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ *out = vfs_setxattr(&init_user_ns,
+ get_fuse_dentry(dentry)->backing_path.dentry, name,
+ value, size, flags);
+ return 0;
+}
+
+static int fuse_setxattr_finalize(struct fuse_args *fa, int *out, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ return 0;
+}
+
+int fuse_bpf_setxattr(int *out, struct inode *inode, struct dentry *dentry,
+ const char *name, const void *value, size_t size, int flags)
+{
+ return fuse_bpf_backing(inode, struct fuse_setxattr_in, out,
+ fuse_setxattr_initialize_in, fuse_setxattr_initialize_out,
+ fuse_setxattr_backing, fuse_setxattr_finalize,
+ dentry, name, value, size, flags);
+}
+
+static int fuse_removexattr_initialize_in(struct fuse_args *fa,
+ struct fuse_unused_io *unused,
+ struct dentry *dentry, const char *name)
+{
+ *fa = (struct fuse_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_REMOVEXATTR,
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_in_arg) {
+ .size = strlen(name) + 1,
+ .value = (void *) name,
+ },
+ };
+
+ return 0;
+}
+
+static int fuse_removexattr_initialize_out(struct fuse_args *fa,
+ struct fuse_unused_io *unused,
+ struct dentry *dentry, const char *name)
+{
+ return 0;
+}
+
+static int fuse_removexattr_backing(struct fuse_args *fa, int *out,
+ struct dentry *dentry, const char *name)
+{
+ struct path *backing_path = &get_fuse_dentry(dentry)->backing_path;
+
+ /* TODO account for changes of the name by prefilter */
+ *out = vfs_removexattr(&init_user_ns, backing_path->dentry, name);
+ return 0;
+}
+
+static int fuse_removexattr_finalize(struct fuse_args *fa, int *out,
+ struct dentry *dentry, const char *name)
+{
+ return 0;
+}
+
+int fuse_bpf_removexattr(int *out, struct inode *inode, struct dentry *dentry, const char *name)
+{
+ return fuse_bpf_backing(inode, struct fuse_unused_io, out,
+ fuse_removexattr_initialize_in, fuse_removexattr_initialize_out,
+ fuse_removexattr_backing, fuse_removexattr_finalize,
+ dentry, name);
+}
+
static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req)
{
if (refcount_dec_and_test(&aio_req->ref))
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 275b649bb5ed..37b29a3ea330 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1421,6 +1421,13 @@ int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct file *fil
size_t len, unsigned int flags);
int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync);
+int fuse_bpf_getxattr(int *out, struct inode *inode, struct dentry *dentry,
+ const char *name, void *value, size_t size);
+int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *dentry, char *list, size_t size);
+int fuse_bpf_setxattr(int *out, struct inode *inode, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags);
+int fuse_bpf_removexattr(int *out, struct inode *inode, struct dentry *dentry, const char *name);
int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to);
int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from);
int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length);
@@ -1515,6 +1522,29 @@ static inline int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file
return 0;
}

+static inline int fuse_bpf_getxattr(int *out, struct inode *inode, struct dentry *dentry,
+ const char *name, void *value, size_t size)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *dentry, char *list, size_t size)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_setxattr(int *out, struct inode *inode, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ return 0;
+}
+
+static inline int fuse_bpf_removexattr(int *out, struct inode *inode, struct dentry *dentry, const char *name)
+{
+ return 0;
+}
+
static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to)
{
return 0;
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
index 0d3e7177fce0..857e7d3a0dab 100644
--- a/fs/fuse/xattr.c
+++ b/fs/fuse/xattr.c
@@ -118,6 +118,9 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
if (fuse_is_bad(inode))
return -EIO;

+ if (fuse_bpf_listxattr(&ret, inode, entry, list, size))
+ return ret;
+
if (!fuse_allow_current_process(fm->fc))
return -EACCES;

@@ -182,9 +185,14 @@ static int fuse_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, void *value, size_t size)
{
+ int err;
+
if (fuse_is_bad(inode))
return -EIO;

+ if (fuse_bpf_getxattr(&err, inode, dentry, name, value, size))
+ return err;
+
return fuse_getxattr(inode, name, value, size);
}

@@ -194,9 +202,19 @@ static int fuse_xattr_set(const struct xattr_handler *handler,
const char *name, const void *value, size_t size,
int flags)
{
+ int err;
+ bool handled;
+
if (fuse_is_bad(inode))
return -EIO;

+ if (value)
+ handled = fuse_bpf_setxattr(&err, inode, dentry, name, value, size, flags);
+ else
+ handled = fuse_bpf_removexattr(&err, inode, dentry, name);
+ if (handled)
+ return err;
+
if (!value)
return fuse_removexattr(inode, name);

--
2.38.1.584.g0f3c55d4c2-goog

2022-11-22 11:18:40

by Amir Goldstein

[permalink] [raw]
Subject: Re: [RFC PATCH v2 00/21] FUSE BPF: A Stacked Filesystem Extension for FUSE

On Tue, Nov 22, 2022 at 4:15 AM Daniel Rosenberg <[email protected]> wrote:
>
> These patches extend FUSE to be able to act as a stacked filesystem. This
> allows pure passthrough, where the fuse file system simply reflects the lower
> filesystem, and also allows optional pre and post filtering in BPF and/or the
> userspace daemon as needed. This can dramatically reduce or even eliminate
> transitions to and from userspace.
>
> For this patch set, I have removed the code related to the bpf side of things
> since that is undergoing some large reworks to get it in line with the more
> recent BPF developements. This set of patches implements direct passthrough to
> the lower filesystem with no alteration. Looking at the v1 code should give a
> pretty good idea of what the general shape of the bpf calls will look like.
> Without the bpf side, it's like a less efficient bind mount. Not very useful
> on its own, but still useful to get eyes on it since the backing calls will be
> larglely the same when bpf is in the mix.
>
> This changes the format of adding a backing file/bpf slightly from v1. It's now
> a bit more modular. You add a block of data at the end of a lookup response to
> give the bpf fd and backing id, but there is now a type header to both blocks,
> and a reserved value for future additions. In the future, we may allow for
> multiple bpfs or backing files, and this will allow us to extend it without any
> UAPI breaking changes. Multiple BPFs would be useful for combining fuse-bpf
> implementations without needing to manually combine bpf fragments. Multiple
> backing files would allow implementing things like a limited overlayfs.
> In this patch set, this is only a single block, with only backing supported,
> although I've left the definitions reflecting the BPF case as well.
> For bpf, the plan is to have two blocks, with the bpf one coming first.
> Any further extensions are currently just speculative.
>
> You can run this without needing to set up a userspace daemon by adding these
> mount options: root_dir=[fd],no_daemon where fd is an open file descriptor
> pointing to the folder you'd like to use as the root directory. The fd can be
> immediately closed after mounting. This is useful for running various fs tests.
>

Which tests did you run?

My recommendation (if you haven't done that already):
Add a variant to libfuse test_passthrough (test_examples.py):
@pytest.mark.parametrize("name", ('passthrough', 'passthrough_plus',
'passthrough_fh', 'passthrough_ll',
'passthrough_bpf'))

and compose the no_daemon cmdline for the 'passthrough_bpf' mount.

This gives pretty good basic test coverage for FUSE passthrough operations.

I've extended test_passthrough_hp() for my libfuse_passthrough patches [1],
but it's the same principle.

Thanks,
Amir.

[1] https://github.com/amir73il/libfuse/commits/fuse_passthrough
* 'passthrough_module' uses 'libfuse_passthrough' which enables
Allesio's FUSE_DEV_IOC_PASSTHROUGH_OPEN by default.

2022-11-22 21:06:29

by Daniel Rosenberg

[permalink] [raw]
Subject: Re: [RFC PATCH v2 00/21] FUSE BPF: A Stacked Filesystem Extension for FUSE

I've been running the generic xfstests against it, with some
modifications to do things like mount/unmount the lower and upper fs
at once. Most of the failures I see there are related to missing
opcodes, like FUSE_SETLK, FUSE_GETLK, and FUSE_IOCTL. The main failure
I have been seeing is generic/126, which is happening due to some
additional checks we're doing in fuse_open_backing. I figured at some
point we'd add some tests into libfuse, and that sounds like a good
place to start.

On Tue, Nov 22, 2022 at 3:13 AM Amir Goldstein <[email protected]> wrote:
>
> On Tue, Nov 22, 2022 at 4:15 AM Daniel Rosenberg <[email protected]> wrote:
> >
> > These patches extend FUSE to be able to act as a stacked filesystem. This
> > allows pure passthrough, where the fuse file system simply reflects the lower
> > filesystem, and also allows optional pre and post filtering in BPF and/or the
> > userspace daemon as needed. This can dramatically reduce or even eliminate
> > transitions to and from userspace.
> >
> > For this patch set, I have removed the code related to the bpf side of things
> > since that is undergoing some large reworks to get it in line with the more
> > recent BPF developements. This set of patches implements direct passthrough to
> > the lower filesystem with no alteration. Looking at the v1 code should give a
> > pretty good idea of what the general shape of the bpf calls will look like.
> > Without the bpf side, it's like a less efficient bind mount. Not very useful
> > on its own, but still useful to get eyes on it since the backing calls will be
> > larglely the same when bpf is in the mix.
> >
> > This changes the format of adding a backing file/bpf slightly from v1. It's now
> > a bit more modular. You add a block of data at the end of a lookup response to
> > give the bpf fd and backing id, but there is now a type header to both blocks,
> > and a reserved value for future additions. In the future, we may allow for
> > multiple bpfs or backing files, and this will allow us to extend it without any
> > UAPI breaking changes. Multiple BPFs would be useful for combining fuse-bpf
> > implementations without needing to manually combine bpf fragments. Multiple
> > backing files would allow implementing things like a limited overlayfs.
> > In this patch set, this is only a single block, with only backing supported,
> > although I've left the definitions reflecting the BPF case as well.
> > For bpf, the plan is to have two blocks, with the bpf one coming first.
> > Any further extensions are currently just speculative.
> >
> > You can run this without needing to set up a userspace daemon by adding these
> > mount options: root_dir=[fd],no_daemon where fd is an open file descriptor
> > pointing to the folder you'd like to use as the root directory. The fd can be
> > immediately closed after mounting. This is useful for running various fs tests.
> >
>
> Which tests did you run?
>
> My recommendation (if you haven't done that already):
> Add a variant to libfuse test_passthrough (test_examples.py):
> @pytest.mark.parametrize("name", ('passthrough', 'passthrough_plus',
> 'passthrough_fh', 'passthrough_ll',
> 'passthrough_bpf'))
>
> and compose the no_daemon cmdline for the 'passthrough_bpf' mount.
>
> This gives pretty good basic test coverage for FUSE passthrough operations.
>
> I've extended test_passthrough_hp() for my libfuse_passthrough patches [1],
> but it's the same principle.
>
> Thanks,
> Amir.
>
> [1] https://github.com/amir73il/libfuse/commits/fuse_passthrough
> * 'passthrough_module' uses 'libfuse_passthrough' which enables
> Allesio's FUSE_DEV_IOC_PASSTHROUGH_OPEN by default.

2022-11-22 21:32:13

by Bernd Schubert

[permalink] [raw]
Subject: Re: [RFC PATCH v2 00/21] FUSE BPF: A Stacked Filesystem Extension for FUSE



On 11/22/22 21:56, Daniel Rosenberg wrote:
> I've been running the generic xfstests against it, with some
> modifications to do things like mount/unmount the lower and upper fs
> at once. Most of the failures I see there are related to missing
> opcodes, like FUSE_SETLK, FUSE_GETLK, and FUSE_IOCTL. The main failure
> I have been seeing is generic/126, which is happening due to some
> additional checks we're doing in fuse_open_backing. I figured at some
> point we'd add some tests into libfuse, and that sounds like a good
> place to start.


Here is a branch of xfstests that should work with fuse and should not
run "rm -fr /" (we are going to give it more testing this week).

https://github.com/hbirth/xfstests


Bernd