ANDROID: fuse-bpf v1.1
This is a squash of these changes cherry-picked from common-android13-5.10
ANDROID: fuse-bpf: Make compile and pass test
ANDROID: fuse-bpf: set error_in to ENOENT in negative lookup
ANDROID: fuse-bpf: Add ability to run ranges of tests to fuse_test
ANDROID: fuse-bpf: Add test for lookup postfilter
ANDROID: fuse-bpf: readddir postfilter fixes
ANDROID: fix kernelci error in fs/fuse/dir.c
ANDROID: fuse-bpf: Fix RCU/reference issue
ANDROID: fuse-bpf: Always call revalidate for backing
ANDROID: fuse-bpf: Adjust backing handle funcs
ANDROID: fuse-bpf: Fix revalidate error path and backing handling
ANDROID: fuse-bpf: Fix use of get_fuse_inode
ANDROID: fuse: Don't use readdirplus w/ nodeid 0
ANDROID: fuse-bpf: Introduce readdirplus test case for fuse bpf
ANDROID: fuse-bpf: Make sure force_again flag is false by default
ANDROID: fuse-bpf: Make inodes with backing_fd reachable for regular FUSE fuse_iget
Revert "ANDROID: fuse-bpf: use target instead of parent inode to execute backing revalidate"
ANDROID: fuse-bpf: use target instead of parent inode to execute backing revalidate
ANDROID: fuse-bpf: Fix misuse of args.out_args
ANDROID: fuse-bpf: Fix non-fusebpf build
ANDROID: fuse-bpf: Use fuse_bpf_args in uapi
ANDROID: fuse-bpf: Fix read_iter
ANDROID: fuse-bpf: Use cache and refcount
ANDROID: fuse-bpf: Rename iocb_fuse to iocb_orig
ANDROID: fuse-bpf: Fix fixattr in rename
ANDROID: fuse-bpf: Fix readdir
ANDROID: fuse-bpf: Fix lseek return value for offset 0
ANDROID: fuse-bpf: fix read_iter and write_iter
ANDROID: fuse-bpf: fix special devices
ANDROID: fuse-bpf: support FUSE_LSEEK
ANDROID: fuse-bpf: Add support for FUSE_COPY_FILE_RANGE
ANDROID: fuse-bpf: Report errors to finalize
ANDROID: fuse-bpf: Avoid reusing uint64_t for file
ANDROID: fuse-bpf: Fix CONFIG_FUSE_BPF typo in FUSE_FSYNCDIR
ANDROID: fuse-bpf: Move fd operations to be synchronous
ANDROID: fuse-bpf: Invalidate if lower is unhashed
ANDROID: fuse-bpf: Move bpf earlier in fuse_permission
ANDROID: fuse-bpf: Update attributes on file write
ANDROID: fuse: allow mounting with no userspace daemon
ANDROID: fuse-bpf: Support FUSE_STATFS
ANDROID: fuse-bpf: Fix filldir
ANDROID: fuse-bpf: fix fuse_create_open_finalize
ANDROID: fuse: add bpf support for removexattr
ANDROID: fuse-bpf: Fix truncate
ANDROID: fuse-bpf: Support inotify
ANDROID: fuse-bpf: Make compile with CONFIG_FUSE but no CONFIG_FUSE_BPF
ANDROID: fuse-bpf: Fix perms on readdir
ANDROID: fuse: Fix umasking in backing
ANDROID: fs/fuse: Backing move returns EXDEV if TO not backed
ANDROID: bpf-fuse: Fix Setattr
ANDROID: fuse-bpf: Check if mkdir dentry setup
ANDROID: fuse-bpf: Close backing fds in fuse_dentry_revalidate
ANDROID: fuse-bpf: Close backing-fd on both paths
ANDROID: fuse-bpf: Partial fix for mmap'd files
ANDROID: fuse-bpf: Restore a missing const
ANDROID: Add fuse-bpf self tests
ANDROID: Add FUSE_BPF to gki_defconfig
ANDROID: fuse-bpf v1
ANDROID: fuse: Move functions in preparation for fuse-bpf
Bug: 202785178
Bug: 265206112
Test: test_fuse passes on linux.
On cuttlefish,
atest android.scopedstorage.cts.host.ScopedStorageHostTest
passes with fuse-bpf enabled and disabled
Change-Id: Idb099c281f9b39ff2c46fa3ebc63e508758416ee
Signed-off-by: Paul Lawrence <paullawrence@google.com>
Signed-off-by: Daniel Rosenberg <drosen@google.com>
diff --git a/arch/arm64/configs/gki_defconfig b/arch/arm64/configs/gki_defconfig
index 1004c61..b733a1da 100644
--- a/arch/arm64/configs/gki_defconfig
+++ b/arch/arm64/configs/gki_defconfig
@@ -569,6 +569,7 @@
CONFIG_QFMT_V2=y
CONFIG_FUSE_FS=y
CONFIG_VIRTIO_FS=y
+CONFIG_FUSE_BPF=y
CONFIG_OVERLAY_FS=y
CONFIG_INCREMENTAL_FS=y
CONFIG_MSDOS_FS=y
diff --git a/arch/x86/configs/gki_defconfig b/arch/x86/configs/gki_defconfig
index a4ccc6d..0978b0a 100644
--- a/arch/x86/configs/gki_defconfig
+++ b/arch/x86/configs/gki_defconfig
@@ -509,6 +509,7 @@
CONFIG_QFMT_V2=y
CONFIG_FUSE_FS=y
CONFIG_VIRTIO_FS=y
+CONFIG_FUSE_BPF=y
CONFIG_OVERLAY_FS=y
CONFIG_INCREMENTAL_FS=y
CONFIG_MSDOS_FS=y
diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig
index 038ed0b..3a64fa7 100644
--- a/fs/fuse/Kconfig
+++ b/fs/fuse/Kconfig
@@ -52,3 +52,11 @@
If you want to allow mounting a Virtio Filesystem with the "dax"
option, answer Y.
+
+config FUSE_BPF
+ bool "Adds BPF to fuse"
+ depends on FUSE_FS
+ depends on BPF
+ help
+ Extends FUSE by adding BPF to prefilter calls and potentially pass to a
+ backing file system
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index d9e1b47..096bd78 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -10,5 +10,6 @@
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
fuse-y += passthrough.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
+fuse-$(CONFIG_FUSE_BPF) += backing.o
virtiofs-y := virtio_fs.o
diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
new file mode 100644
index 0000000..22656a0
--- /dev/null
+++ b/fs/fuse/backing.c
@@ -0,0 +1,2468 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE-BPF: Filesystem in Userspace with BPF
+ * Copyright (c) 2021 Google LLC
+ */
+
+#include "fuse_i.h"
+
+#include <linux/fdtable.h>
+#include <linux/filter.h>
+#include <linux/fs_stack.h>
+#include <linux/namei.h>
+
+#include "../internal.h"
+
+#define FUSE_BPF_IOCB_MASK (IOCB_APPEND | IOCB_DSYNC | IOCB_HIPRI | IOCB_NOWAIT | IOCB_SYNC)
+
+struct fuse_bpf_aio_req {
+ struct kiocb iocb;
+ refcount_t ref;
+ struct kiocb *iocb_orig;
+};
+
+static struct kmem_cache *fuse_bpf_aio_request_cachep;
+
+static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode,
+ struct kstat *stat, struct fuse_attr *attr);
+
+static void fuse_file_accessed(struct file *dst_file, struct file *src_file)
+{
+ struct inode *dst_inode;
+ struct inode *src_inode;
+
+ if (dst_file->f_flags & O_NOATIME)
+ return;
+
+ dst_inode = file_inode(dst_file);
+ src_inode = file_inode(src_file);
+
+ if ((!timespec64_equal(&dst_inode->i_mtime, &src_inode->i_mtime) ||
+ !timespec64_equal(&dst_inode->i_ctime, &src_inode->i_ctime))) {
+ dst_inode->i_mtime = src_inode->i_mtime;
+ dst_inode->i_ctime = src_inode->i_ctime;
+ }
+
+ touch_atime(&dst_file->f_path);
+}
+
+int fuse_open_initialize(struct fuse_bpf_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),
+ };
+
+ foio->foo = (struct fuse_open_out) {0};
+
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_fuse_inode(inode)->nodeid,
+ .opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN,
+ .in_numargs = 1,
+ .out_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(foio->foi),
+ .value = &foio->foi,
+ },
+ .out_args[0] = (struct fuse_bpf_arg) {
+ .size = sizeof(foio->foo),
+ .value = &foio->foo,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_open_backing(struct fuse_bpf_args *fa,
+ 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 retval;
+ 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;
+ }
+
+ retval = inode_permission(&init_user_ns,
+ get_fuse_inode(inode)->backing_inode, mask);
+ if (retval)
+ return retval;
+
+ 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;
+
+ return 0;
+}
+
+void *fuse_open_finalize(struct fuse_bpf_args *fa,
+ 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_create_open_initialize(
+ struct fuse_bpf_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,
+ };
+
+ fcoio->feo = (struct fuse_entry_out) {0};
+ fcoio->foo = (struct fuse_open_out) {0};
+
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_CREATE,
+ .in_numargs = 2,
+ .out_numargs = 2,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(fcoio->fci),
+ .value = &fcoio->fci,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ .out_args[0] = (struct fuse_bpf_arg) {
+ .size = sizeof(fcoio->feo),
+ .value = &fcoio->feo,
+ },
+ .out_args[1] = (struct fuse_bpf_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;
+}
+
+int fuse_create_open_backing(
+ struct fuse_bpf_args *fa,
+ 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 fuse_dentry *dir_fuse_dentry = get_fuse_dentry(entry->d_parent);
+ struct dentry *backing_dentry = NULL;
+ struct inode *inode = NULL;
+ struct dentry *newent;
+ int err = 0;
+ const struct fuse_create_in *fci = fa->in_args[0].value;
+ struct inode *d_inode = entry->d_inode;
+ u64 target_nodeid = 0;
+
+ if (!dir_fuse_inode || !dir_fuse_dentry)
+ return -EIO;
+
+ inode_lock_nested(dir_fuse_inode->backing_inode, I_MUTEX_PARENT);
+ backing_dentry = lookup_one_len(fa->in_args[1].value,
+ dir_fuse_dentry->backing_path.dentry,
+ strlen(fa->in_args[1].value));
+ inode_unlock(dir_fuse_inode->backing_inode);
+
+ if (IS_ERR(backing_dentry))
+ return PTR_ERR(backing_dentry);
+
+ if (d_really_is_positive(backing_dentry)) {
+ err = -EIO;
+ goto out;
+ }
+
+ err = vfs_create(&init_user_ns, dir_fuse_inode->backing_inode,
+ backing_dentry, fci->mode, true);
+ if (err)
+ goto out;
+
+ if (get_fuse_dentry(entry)->backing_path.dentry)
+ path_put(&get_fuse_dentry(entry)->backing_path);
+ get_fuse_dentry(entry)->backing_path = (struct path) {
+ .mnt = dir_fuse_dentry->backing_path.mnt,
+ .dentry = backing_dentry,
+ };
+ path_get(&get_fuse_dentry(entry)->backing_path);
+
+ if (d_inode)
+ target_nodeid = get_fuse_inode(d_inode)->nodeid;
+
+ inode = fuse_iget_backing(dir->i_sb, target_nodeid,
+ get_fuse_dentry(entry)->backing_path.dentry->d_inode);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ goto out;
+ }
+
+ if (get_fuse_inode(inode)->bpf)
+ bpf_prog_put(get_fuse_inode(inode)->bpf);
+ get_fuse_inode(inode)->bpf = dir_fuse_inode->bpf;
+ if (get_fuse_inode(inode)->bpf)
+ bpf_prog_inc(dir_fuse_inode->bpf);
+
+ newent = d_splice_alias(inode, entry);
+ if (IS_ERR(newent)) {
+ err = PTR_ERR(newent);
+ goto out;
+ }
+
+ entry = newent ? newent : entry;
+ err = finish_open(file, entry, fuse_open_file_backing);
+
+out:
+ dput(backing_dentry);
+ return err;
+}
+
+void *fuse_create_open_finalize(
+ struct fuse_bpf_args *fa,
+ 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_release_initialize(struct fuse_bpf_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_bpf_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;
+}
+
+int fuse_releasedir_initialize(struct fuse_bpf_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_bpf_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;
+}
+
+int fuse_release_backing(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+void *fuse_release_finalize(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file)
+{
+ fuse_file_free(file->private_data);
+ return NULL;
+}
+
+int fuse_flush_initialize(struct fuse_bpf_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_bpf_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,
+ .flags = FUSE_BPF_FORCE,
+ };
+
+ return 0;
+}
+
+int fuse_flush_backing(struct fuse_bpf_args *fa, struct file *file, fl_owner_t id)
+{
+ struct fuse_file *fuse_file = file->private_data;
+ struct file *backing_file = fuse_file->backing_file;
+
+ if (backing_file->f_op->flush)
+ return backing_file->f_op->flush(backing_file, id);
+ return 0;
+}
+
+void *fuse_flush_finalize(struct fuse_bpf_args *fa, struct file *file, fl_owner_t id)
+{
+ return NULL;
+}
+
+int fuse_lseek_initialize(struct fuse_bpf_args *fa, struct fuse_lseek_io *flio,
+ struct file *file, loff_t offset, int whence)
+{
+ struct fuse_file *fuse_file = file->private_data;
+
+ flio->fli = (struct fuse_lseek_in) {
+ .fh = fuse_file->fh,
+ .offset = offset,
+ .whence = whence,
+ };
+
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(file->f_inode),
+ .opcode = FUSE_LSEEK,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(flio->fli),
+ .in_args[0].value = &flio->fli,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(flio->flo),
+ .out_args[0].value = &flio->flo,
+ };
+
+ return 0;
+}
+
+int fuse_lseek_backing(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence)
+{
+ const struct fuse_lseek_in *fli = fa->in_args[0].value;
+ struct fuse_lseek_out *flo = fa->out_args[0].value;
+ struct fuse_file *fuse_file = file->private_data;
+ struct file *backing_file = fuse_file->backing_file;
+ loff_t ret;
+
+ /* TODO: Handle changing of the file handle */
+ if (offset == 0) {
+ if (whence == SEEK_CUR) {
+ flo->offset = file->f_pos;
+ return flo->offset;
+ }
+
+ if (whence == SEEK_SET) {
+ flo->offset = vfs_setpos(file, 0, 0);
+ return flo->offset;
+ }
+ }
+
+ inode_lock(file->f_inode);
+ backing_file->f_pos = file->f_pos;
+ ret = vfs_llseek(backing_file, fli->offset, fli->whence);
+ flo->offset = ret;
+ inode_unlock(file->f_inode);
+ return ret;
+}
+
+void *fuse_lseek_finalize(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence)
+{
+ struct fuse_lseek_out *flo = fa->out_args[0].value;
+
+ if (!fa->error_in)
+ file->f_pos = flo->offset;
+ return ERR_PTR(flo->offset);
+}
+
+int fuse_copy_file_range_initialize(struct fuse_bpf_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_bpf_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,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(fcf->fwo),
+ .out_args[0].value = &fcf->fwo,
+ };
+
+ return 0;
+}
+
+int fuse_copy_file_range_backing(struct fuse_bpf_args *fa, 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)
+ return vfs_copy_file_range(backing_file_in, fci->off_in, backing_file_out,
+ fci->off_out, fci->len, fci->flags);
+ else
+ return generic_copy_file_range(file_in, pos_in, file_out, pos_out, len,
+ flags);
+}
+
+void *fuse_copy_file_range_finalize(struct fuse_bpf_args *fa, struct file *file_in, loff_t pos_in,
+ struct file *file_out, loff_t pos_out, size_t len,
+ unsigned int flags)
+{
+ return NULL;
+}
+
+int fuse_fsync_initialize(struct fuse_bpf_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_bpf_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,
+ .flags = FUSE_BPF_FORCE,
+ };
+
+ return 0;
+}
+
+int fuse_fsync_backing(struct fuse_bpf_args *fa,
+ 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;
+
+ return vfs_fsync(backing_file, new_datasync);
+}
+
+void *fuse_fsync_finalize(struct fuse_bpf_args *fa,
+ struct file *file, loff_t start, loff_t end, int datasync)
+{
+ return NULL;
+}
+
+int fuse_dir_fsync_initialize(struct fuse_bpf_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_bpf_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,
+ .flags = FUSE_BPF_FORCE,
+ };
+
+ return 0;
+}
+
+int fuse_getxattr_initialize(struct fuse_bpf_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_bpf_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_GETXATTR,
+ .in_numargs = 2,
+ .out_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(fgio->fgi),
+ .value = &fgio->fgi,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = strlen(name) + 1,
+ .value = name,
+ },
+ .flags = size ? FUSE_BPF_OUT_ARGVAR : 0,
+ .out_args[0].size = size ? size : sizeof(fgio->fgo),
+ .out_args[0].value = size ? value : &fgio->fgo,
+ };
+ return 0;
+}
+
+int fuse_getxattr_backing(struct fuse_bpf_args *fa,
+ 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->flags & FUSE_BPF_OUT_ARGVAR)
+ fa->out_args[0].size = ret;
+ else
+ ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = ret;
+
+ return 0;
+}
+
+void *fuse_getxattr_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size)
+{
+ struct fuse_getxattr_out *fgo;
+
+ if (fa->flags & FUSE_BPF_OUT_ARGVAR)
+ return ERR_PTR(fa->out_args[0].size);
+
+ fgo = fa->out_args[0].value;
+
+ return ERR_PTR(fgo->size);
+
+}
+
+int fuse_listxattr_initialize(struct fuse_bpf_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_bpf_args){
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_LISTXATTR,
+ .in_numargs = 1,
+ .out_numargs = 1,
+ .in_args[0] =
+ (struct fuse_bpf_in_arg){
+ .size = sizeof(fgio->fgi),
+ .value = &fgio->fgi,
+ },
+ .flags = size ? FUSE_BPF_OUT_ARGVAR : 0,
+ .out_args[0].size = size ? size : sizeof(fgio->fgo),
+ .out_args[0].value = size ? (void *)list : &fgio->fgo,
+ };
+
+ return 0;
+}
+
+int fuse_listxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
+ char *list, size_t size)
+{
+ ssize_t ret =
+ vfs_listxattr(get_fuse_dentry(dentry)->backing_path.dentry,
+ list, size);
+
+ if (ret < 0)
+ return ret;
+
+ if (fa->flags & FUSE_BPF_OUT_ARGVAR)
+ fa->out_args[0].size = ret;
+ else
+ ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = ret;
+
+ return ret;
+}
+
+void *fuse_listxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
+ char *list, size_t size)
+{
+ struct fuse_getxattr_out *fgo;
+
+ if (fa->error_in)
+ return NULL;
+
+ if (fa->flags & FUSE_BPF_OUT_ARGVAR)
+ return ERR_PTR(fa->out_args[0].size);
+
+ fgo = fa->out_args[0].value;
+ return ERR_PTR(fgo->size);
+}
+
+int fuse_setxattr_initialize(struct fuse_bpf_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_bpf_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_SETXATTR,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(*fsxi),
+ .value = fsxi,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = strlen(name) + 1,
+ .value = name,
+ },
+ .in_args[2] = (struct fuse_bpf_in_arg) {
+ .size = size,
+ .value = value,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_setxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ return vfs_setxattr(&init_user_ns,
+ get_fuse_dentry(dentry)->backing_path.dentry, name,
+ value, size, flags);
+}
+
+void *fuse_setxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ return NULL;
+}
+
+int fuse_removexattr_initialize(struct fuse_bpf_args *fa,
+ struct fuse_dummy_io *unused,
+ struct dentry *dentry, const char *name)
+{
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_fuse_inode(dentry->d_inode)->nodeid,
+ .opcode = FUSE_REMOVEXATTR,
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = strlen(name) + 1,
+ .value = name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_removexattr_backing(struct fuse_bpf_args *fa,
+ 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 */
+ return vfs_removexattr(&init_user_ns, backing_path->dentry, name);
+}
+
+void *fuse_removexattr_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name)
+{
+ return NULL;
+}
+
+static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req)
+{
+ if (refcount_dec_and_test(&aio_req->ref))
+ kmem_cache_free(fuse_bpf_aio_request_cachep, aio_req);
+}
+
+static void fuse_bpf_aio_cleanup_handler(struct fuse_bpf_aio_req *aio_req)
+{
+ struct kiocb *iocb = &aio_req->iocb;
+ struct kiocb *iocb_orig = aio_req->iocb_orig;
+
+ if (iocb->ki_flags & IOCB_WRITE) {
+ __sb_writers_acquired(file_inode(iocb->ki_filp)->i_sb,
+ SB_FREEZE_WRITE);
+ file_end_write(iocb->ki_filp);
+ fuse_copyattr(iocb_orig->ki_filp, iocb->ki_filp);
+ }
+ iocb_orig->ki_pos = iocb->ki_pos;
+ fuse_bpf_aio_put(aio_req);
+}
+
+static void fuse_bpf_aio_rw_complete(struct kiocb *iocb, long res)
+{
+ struct fuse_bpf_aio_req *aio_req =
+ container_of(iocb, struct fuse_bpf_aio_req, iocb);
+ struct kiocb *iocb_orig = aio_req->iocb_orig;
+
+ fuse_bpf_aio_cleanup_handler(aio_req);
+ iocb_orig->ki_complete(iocb_orig, res);
+}
+
+
+int fuse_file_read_iter_initialize(
+ struct fuse_bpf_args *fa, struct fuse_file_read_iter_io *fri,
+ struct kiocb *iocb, struct iov_iter *to)
+{
+ struct file *file = iocb->ki_filp;
+ struct fuse_file *ff = file->private_data;
+
+ fri->fri = (struct fuse_read_in) {
+ .fh = ff->fh,
+ .offset = iocb->ki_pos,
+ .size = to->count,
+ };
+
+ fri->frio = (struct fuse_read_iter_out) {
+ .ret = fri->fri.size,
+ };
+
+ /* TODO we can't assume 'to' is a kvec */
+ /* TODO we also can't assume the vector has only one component */
+ *fa = (struct fuse_bpf_args) {
+ .opcode = FUSE_READ,
+ .nodeid = ff->nodeid,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(fri->fri),
+ .in_args[0].value = &fri->fri,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(fri->frio),
+ .out_args[0].value = &fri->frio,
+ /*
+ * TODO Design this properly.
+ * Possible approach: do not pass buf to bpf
+ * If going to userland, do a deep copy
+ * For extra credit, do that to/from the vector, rather than
+ * making an extra copy in the kernel
+ */
+ };
+
+ return 0;
+}
+
+int fuse_file_read_iter_backing(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *to)
+{
+ struct fuse_read_iter_out *frio = fa->out_args[0].value;
+ struct file *file = iocb->ki_filp;
+ struct fuse_file *ff = file->private_data;
+ ssize_t ret;
+
+ if (!iov_iter_count(to))
+ return 0;
+
+ if ((iocb->ki_flags & IOCB_DIRECT) &&
+ (!ff->backing_file->f_mapping->a_ops ||
+ !ff->backing_file->f_mapping->a_ops->direct_IO))
+ return -EINVAL;
+
+ /* TODO This just plain ignores any change to fuse_read_in */
+ if (is_sync_kiocb(iocb)) {
+ ret = vfs_iter_read(ff->backing_file, to, &iocb->ki_pos,
+ iocb_to_rw_flags(iocb->ki_flags, FUSE_BPF_IOCB_MASK));
+ } else {
+ struct fuse_bpf_aio_req *aio_req;
+
+ ret = -ENOMEM;
+ aio_req = kmem_cache_zalloc(fuse_bpf_aio_request_cachep, GFP_KERNEL);
+ if (!aio_req)
+ goto out;
+
+ aio_req->iocb_orig = iocb;
+ kiocb_clone(&aio_req->iocb, iocb, ff->backing_file);
+ aio_req->iocb.ki_complete = fuse_bpf_aio_rw_complete;
+ refcount_set(&aio_req->ref, 2);
+ ret = vfs_iocb_iter_read(ff->backing_file, &aio_req->iocb, to);
+ fuse_bpf_aio_put(aio_req);
+ if (ret != -EIOCBQUEUED)
+ fuse_bpf_aio_cleanup_handler(aio_req);
+ }
+
+ frio->ret = ret;
+
+ /* TODO Need to point value at the buffer for post-modification */
+
+out:
+ fuse_file_accessed(file, ff->backing_file);
+
+ return ret;
+}
+
+void *fuse_file_read_iter_finalize(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *to)
+{
+ struct fuse_read_iter_out *frio = fa->out_args[0].value;
+
+ return ERR_PTR(frio->ret);
+}
+
+int fuse_file_write_iter_initialize(
+ struct fuse_bpf_args *fa, struct fuse_file_write_iter_io *fwio,
+ struct kiocb *iocb, struct iov_iter *from)
+{
+ struct file *file = iocb->ki_filp;
+ struct fuse_file *ff = file->private_data;
+
+ *fwio = (struct fuse_file_write_iter_io) {
+ .fwi.fh = ff->fh,
+ .fwi.offset = iocb->ki_pos,
+ .fwi.size = from->count,
+ };
+
+ /* TODO we can't assume 'from' is a kvec */
+ *fa = (struct fuse_bpf_args) {
+ .opcode = FUSE_WRITE,
+ .nodeid = ff->nodeid,
+ .in_numargs = 2,
+ .in_args[0].size = sizeof(fwio->fwi),
+ .in_args[0].value = &fwio->fwi,
+ .in_args[1].size = fwio->fwi.size,
+ .in_args[1].value = from->kvec->iov_base,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(fwio->fwio),
+ .out_args[0].value = &fwio->fwio,
+ };
+
+ return 0;
+}
+
+int fuse_file_write_iter_backing(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *from)
+{
+ struct file *file = iocb->ki_filp;
+ struct fuse_file *ff = file->private_data;
+ struct fuse_write_iter_out *fwio = fa->out_args[0].value;
+ ssize_t ret;
+
+ if (!iov_iter_count(from))
+ return 0;
+
+ /* TODO This just plain ignores any change to fuse_write_in */
+ /* TODO uint32_t seems smaller than ssize_t.... right? */
+ inode_lock(file_inode(file));
+
+ fuse_copyattr(file, ff->backing_file);
+
+ if (is_sync_kiocb(iocb)) {
+ file_start_write(ff->backing_file);
+ ret = vfs_iter_write(ff->backing_file, from, &iocb->ki_pos,
+ iocb_to_rw_flags(iocb->ki_flags, FUSE_BPF_IOCB_MASK));
+ file_end_write(ff->backing_file);
+
+ /* Must reflect change in size of backing file to upper file */
+ if (ret > 0)
+ fuse_copyattr(file, ff->backing_file);
+ } else {
+ struct fuse_bpf_aio_req *aio_req;
+
+ ret = -ENOMEM;
+ aio_req = kmem_cache_zalloc(fuse_bpf_aio_request_cachep, GFP_KERNEL);
+ if (!aio_req)
+ goto out;
+
+ file_start_write(ff->backing_file);
+ __sb_writers_release(file_inode(ff->backing_file)->i_sb, SB_FREEZE_WRITE);
+ aio_req->iocb_orig = iocb;
+ kiocb_clone(&aio_req->iocb, iocb, ff->backing_file);
+ aio_req->iocb.ki_complete = fuse_bpf_aio_rw_complete;
+ refcount_set(&aio_req->ref, 2);
+ ret = vfs_iocb_iter_write(ff->backing_file, &aio_req->iocb, from);
+ fuse_bpf_aio_put(aio_req);
+ if (ret != -EIOCBQUEUED)
+ fuse_bpf_aio_cleanup_handler(aio_req);
+ }
+
+out:
+ inode_unlock(file_inode(file));
+ fwio->ret = ret;
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+void *fuse_file_write_iter_finalize(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *from)
+{
+ struct fuse_write_iter_out *fwio = fa->out_args[0].value;
+
+ return ERR_PTR(fwio->ret);
+}
+
+ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int ret;
+ struct fuse_file *ff = file->private_data;
+ struct inode *fuse_inode = file_inode(file);
+ struct file *backing_file = ff->backing_file;
+ struct inode *backing_inode = file_inode(backing_file);
+
+ if (!backing_file->f_op->mmap)
+ return -ENODEV;
+
+ if (WARN_ON(file != vma->vm_file))
+ return -EIO;
+
+ vma->vm_file = get_file(backing_file);
+
+ ret = call_mmap(vma->vm_file, vma);
+
+ if (ret)
+ fput(backing_file);
+ else
+ fput(file);
+
+ if (file->f_flags & O_NOATIME)
+ return ret;
+
+ if ((!timespec64_equal(&fuse_inode->i_mtime,
+ &backing_inode->i_mtime) ||
+ !timespec64_equal(&fuse_inode->i_ctime,
+ &backing_inode->i_ctime))) {
+ fuse_inode->i_mtime = backing_inode->i_mtime;
+ fuse_inode->i_ctime = backing_inode->i_ctime;
+ }
+ touch_atime(&file->f_path);
+
+ return ret;
+}
+
+int fuse_file_fallocate_initialize(struct fuse_bpf_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_bpf_args) {
+ .opcode = FUSE_FALLOCATE,
+ .nodeid = ff->nodeid,
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*ffi),
+ .in_args[0].value = ffi,
+ };
+
+ return 0;
+}
+
+int fuse_file_fallocate_backing(struct fuse_bpf_args *fa,
+ 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;
+
+ return vfs_fallocate(ff->backing_file, ffi->mode, ffi->offset,
+ ffi->length);
+}
+
+void *fuse_file_fallocate_finalize(struct fuse_bpf_args *fa,
+ struct file *file, int mode, loff_t offset, loff_t length)
+{
+ return NULL;
+}
+
+/*******************************************************************************
+ * Directory operations after here *
+ ******************************************************************************/
+
+int fuse_lookup_initialize(struct fuse_bpf_args *fa, struct fuse_lookup_io *fli,
+ struct inode *dir, struct dentry *entry, unsigned int flags)
+{
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_fuse_inode(dir)->nodeid,
+ .opcode = FUSE_LOOKUP,
+ .in_numargs = 1,
+ .out_numargs = 2,
+ .flags = FUSE_BPF_OUT_ARGVAR,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ .out_args[0] = (struct fuse_bpf_arg) {
+ .size = sizeof(fli->feo),
+ .value = &fli->feo,
+ },
+ .out_args[1] = (struct fuse_bpf_arg) {
+ .size = sizeof(fli->feb.out),
+ .value = &fli->feb.out,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_lookup_backing(struct fuse_bpf_args *fa, struct inode *dir,
+ struct dentry *entry, unsigned int flags)
+{
+ struct fuse_dentry *fuse_entry = get_fuse_dentry(entry);
+ struct fuse_dentry *dir_fuse_entry = get_fuse_dentry(entry->d_parent);
+ struct dentry *dir_backing_entry = dir_fuse_entry->backing_path.dentry;
+ struct inode *dir_backing_inode = dir_backing_entry->d_inode;
+ struct dentry *backing_entry;
+ struct fuse_entry_out *feo = (void *)fa->out_args[0].value;
+ struct kstat stat;
+ int err;
+
+ /* TODO this will not handle lookups over mount points */
+ inode_lock_nested(dir_backing_inode, I_MUTEX_PARENT);
+ backing_entry = lookup_one_len(entry->d_name.name, dir_backing_entry,
+ strlen(entry->d_name.name));
+ inode_unlock(dir_backing_inode);
+
+ if (IS_ERR(backing_entry))
+ return PTR_ERR(backing_entry);
+
+ fuse_entry->backing_path = (struct path) {
+ .dentry = backing_entry,
+ .mnt = mntget(dir_fuse_entry->backing_path.mnt),
+ };
+
+ if (d_is_negative(backing_entry)) {
+ fa->error_in = -ENOENT;
+ return 0;
+ }
+
+ err = vfs_getattr(&fuse_entry->backing_path, &stat,
+ STATX_BASIC_STATS, 0);
+ if (err) {
+ path_put_init(&fuse_entry->backing_path);
+ return err;
+ }
+
+ fuse_stat_to_attr(get_fuse_conn(dir),
+ backing_entry->d_inode, &stat, &feo->attr);
+ return 0;
+}
+
+int fuse_handle_backing(struct fuse_entry_bpf *feb, struct inode **backing_inode,
+ struct path *backing_path)
+{
+ switch (feb->out.backing_action) {
+ case FUSE_ACTION_KEEP:
+ /* backing inode/path are added in fuse_lookup_backing */
+ break;
+
+ case FUSE_ACTION_REMOVE:
+ iput(*backing_inode);
+ *backing_inode = NULL;
+ path_put_init(backing_path);
+ break;
+
+ case FUSE_ACTION_REPLACE: {
+ struct file *backing_file = feb->backing_file;
+
+ if (!backing_file)
+ return -EINVAL;
+ if (IS_ERR(backing_file))
+ return PTR_ERR(backing_file);
+
+ if (backing_inode)
+ iput(*backing_inode);
+ *backing_inode = backing_file->f_inode;
+ ihold(*backing_inode);
+
+ path_put(backing_path);
+ *backing_path = backing_file->f_path;
+ path_get(backing_path);
+
+ fput(backing_file);
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int fuse_handle_bpf_prog(struct fuse_entry_bpf *feb, struct inode *parent,
+ struct bpf_prog **bpf)
+{
+ struct fuse_inode *pi;
+
+ // Parent isn't presented, but we want to keep
+ // Don't touch bpf program at all in this case
+ if (feb->out.bpf_action == FUSE_ACTION_KEEP && !parent)
+ goto out;
+
+ if (*bpf) {
+ bpf_prog_put(*bpf);
+ *bpf = NULL;
+ }
+
+ switch (feb->out.bpf_action) {
+ case FUSE_ACTION_KEEP:
+ pi = get_fuse_inode(parent);
+ *bpf = pi->bpf;
+ if (*bpf)
+ bpf_prog_inc(*bpf);
+ break;
+
+ case FUSE_ACTION_REMOVE:
+ break;
+
+ case FUSE_ACTION_REPLACE: {
+ struct file *bpf_file = feb->bpf_file;
+ struct bpf_prog *bpf_prog = ERR_PTR(-EINVAL);
+
+ if (bpf_file && !IS_ERR(bpf_file))
+ bpf_prog = fuse_get_bpf_prog(bpf_file);
+
+ if (IS_ERR(bpf_prog))
+ return PTR_ERR(bpf_prog);
+
+ *bpf = bpf_prog;
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+out:
+ return 0;
+}
+
+struct dentry *fuse_lookup_finalize(struct fuse_bpf_args *fa, struct inode *dir,
+ struct dentry *entry, unsigned int flags)
+{
+ struct fuse_dentry *fd;
+ struct dentry *bd;
+ struct inode *inode, *backing_inode;
+ struct inode *d_inode = entry->d_inode;
+ struct fuse_entry_out *feo = fa->out_args[0].value;
+ struct fuse_entry_bpf_out *febo = fa->out_args[1].value;
+ struct fuse_entry_bpf *feb = container_of(febo, struct fuse_entry_bpf, out);
+ int error = -1;
+ u64 target_nodeid = 0;
+
+ fd = get_fuse_dentry(entry);
+ if (!fd)
+ return ERR_PTR(-EIO);
+ bd = fd->backing_path.dentry;
+ if (!bd)
+ return ERR_PTR(-ENOENT);
+ backing_inode = bd->d_inode;
+ if (!backing_inode)
+ return 0;
+
+ if (d_inode)
+ target_nodeid = get_fuse_inode(d_inode)->nodeid;
+
+ inode = fuse_iget_backing(dir->i_sb, target_nodeid, backing_inode);
+
+ if (IS_ERR(inode))
+ return ERR_PTR(PTR_ERR(inode));
+
+ error = fuse_handle_bpf_prog(feb, dir, &get_fuse_inode(inode)->bpf);
+ if (error)
+ return ERR_PTR(error);
+
+ error = fuse_handle_backing(feb, &get_fuse_inode(inode)->backing_inode, &fd->backing_path);
+ if (error)
+ return ERR_PTR(error);
+
+ get_fuse_inode(inode)->nodeid = feo->nodeid;
+
+ return d_splice_alias(inode, entry);
+}
+
+int fuse_revalidate_backing(struct dentry *entry, unsigned int flags)
+{
+ struct fuse_dentry *fuse_dentry = get_fuse_dentry(entry);
+ struct dentry *backing_entry = fuse_dentry->backing_path.dentry;
+
+ spin_lock(&backing_entry->d_lock);
+ if (d_unhashed(backing_entry)) {
+ spin_unlock(&backing_entry->d_lock);
+ return 0;
+ }
+ spin_unlock(&backing_entry->d_lock);
+
+ if (unlikely(backing_entry->d_flags & DCACHE_OP_REVALIDATE))
+ return backing_entry->d_op->d_revalidate(backing_entry, flags);
+ return 1;
+}
+
+int fuse_canonical_path_initialize(struct fuse_bpf_args *fa,
+ struct fuse_dummy_io *fdi,
+ const struct path *path,
+ struct path *canonical_path)
+{
+ fa->opcode = FUSE_CANONICAL_PATH;
+ return 0;
+}
+
+int fuse_canonical_path_backing(struct fuse_bpf_args *fa, const struct path *path,
+ struct path *canonical_path)
+{
+ get_fuse_backing_path(path->dentry, canonical_path);
+ return 0;
+}
+
+void *fuse_canonical_path_finalize(struct fuse_bpf_args *fa,
+ const struct path *path,
+ struct path *canonical_path)
+{
+ return NULL;
+}
+
+int fuse_mknod_initialize(
+ struct fuse_bpf_args *fa, struct fuse_mknod_in *fmi,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev)
+{
+ *fmi = (struct fuse_mknod_in) {
+ .mode = mode,
+ .rdev = new_encode_dev(rdev),
+ .umask = current_umask(),
+ };
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_MKNOD,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(*fmi),
+ .value = fmi,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_mknod_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev)
+{
+ int err = 0;
+ const struct fuse_mknod_in *fmi = fa->in_args[0].value;
+ 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;
+
+ //TODO Actually deal with changing the backing entry in mknod
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ inode_lock_nested(backing_inode, I_MUTEX_PARENT);
+ mode = fmi->mode;
+ if (!IS_POSIXACL(backing_inode))
+ mode &= ~fmi->umask;
+ err = vfs_mknod(&init_user_ns, backing_inode, backing_path.dentry,
+ mode, new_decode_dev(fmi->rdev));
+ inode_unlock(backing_inode);
+ if (err)
+ goto out;
+ if (d_really_is_negative(backing_path.dentry) ||
+ unlikely(d_unhashed(backing_path.dentry))) {
+ err = -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)) {
+ err = PTR_ERR(inode);
+ goto out;
+ }
+ d_instantiate(entry, inode);
+out:
+ path_put(&backing_path);
+ return err;
+}
+
+void *fuse_mknod_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev)
+{
+ return NULL;
+}
+
+int fuse_mkdir_initialize(
+ struct fuse_bpf_args *fa, struct fuse_mkdir_in *fmi,
+ struct inode *dir, struct dentry *entry, umode_t mode)
+{
+ *fmi = (struct fuse_mkdir_in) {
+ .mode = mode,
+ .umask = current_umask(),
+ };
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_MKDIR,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(*fmi),
+ .value = fmi,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_mkdir_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode)
+{
+ int err = 0;
+ const struct fuse_mkdir_in *fmi = fa->in_args[0].value;
+ 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;
+ struct dentry *d;
+
+ //TODO Actually deal with changing the backing entry in mkdir
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ inode_lock_nested(backing_inode, I_MUTEX_PARENT);
+ mode = fmi->mode;
+ if (!IS_POSIXACL(backing_inode))
+ mode &= ~fmi->umask;
+ err = vfs_mkdir(&init_user_ns, backing_inode, backing_path.dentry, mode);
+ if (err)
+ goto out;
+ if (d_really_is_negative(backing_path.dentry) ||
+ unlikely(d_unhashed(backing_path.dentry))) {
+ d = lookup_one_len(entry->d_name.name, backing_path.dentry->d_parent,
+ entry->d_name.len);
+ if (IS_ERR(d)) {
+ err = PTR_ERR(d);
+ goto out;
+ }
+ dput(backing_path.dentry);
+ backing_path.dentry = d;
+ }
+ inode = fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ goto out;
+ }
+ d_instantiate(entry, inode);
+out:
+ inode_unlock(backing_inode);
+ path_put(&backing_path);
+ return err;
+}
+
+void *fuse_mkdir_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode)
+{
+ return NULL;
+}
+
+int fuse_rmdir_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *dummy,
+ struct inode *dir, struct dentry *entry)
+{
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_RMDIR,
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_rmdir_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry)
+{
+ int err = 0;
+ struct path backing_path = {};
+ struct dentry *backing_parent_dentry;
+ struct inode *backing_inode;
+
+ /* TODO Actually deal with changing the backing entry in rmdir */
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ /* TODO Not sure if we should reverify like overlayfs, or get inode from d_parent */
+ backing_parent_dentry = dget_parent(backing_path.dentry);
+ backing_inode = d_inode(backing_parent_dentry);
+
+ inode_lock_nested(backing_inode, I_MUTEX_PARENT);
+ err = vfs_rmdir(&init_user_ns, backing_inode, backing_path.dentry);
+ inode_unlock(backing_inode);
+
+ dput(backing_parent_dentry);
+ if (!err)
+ d_drop(entry);
+ path_put(&backing_path);
+ return err;
+}
+
+void *fuse_rmdir_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry)
+{
+ return NULL;
+}
+
+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;
+}
+
+int fuse_rename2_initialize(struct fuse_bpf_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_bpf_args) {
+ .nodeid = get_node_id(olddir),
+ .opcode = FUSE_RENAME2,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(*fri),
+ .value = fri,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = oldent->d_name.len + 1,
+ .value = oldent->d_name.name,
+ },
+ .in_args[2] = (struct fuse_bpf_in_arg) {
+ .size = newent->d_name.len + 1,
+ .value = newent->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_rename2_backing(struct fuse_bpf_args *fa,
+ 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 */
+ return fuse_rename_backing_common(olddir, oldent, newdir, newent, fri->flags);
+}
+
+void *fuse_rename2_finalize(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
+{
+ return NULL;
+}
+
+int fuse_rename_initialize(struct fuse_bpf_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_bpf_args) {
+ .nodeid = get_node_id(olddir),
+ .opcode = FUSE_RENAME,
+ .in_numargs = 3,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(*fri),
+ .value = fri,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = oldent->d_name.len + 1,
+ .value = oldent->d_name.name,
+ },
+ .in_args[2] = (struct fuse_bpf_in_arg) {
+ .size = newent->d_name.len + 1,
+ .value = newent->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_rename_backing(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ /* TODO: deal with changing dirs/ents */
+ return fuse_rename_backing_common(olddir, oldent, newdir, newent, 0);
+}
+
+void *fuse_rename_finalize(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ return NULL;
+}
+
+int fuse_unlink_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *dummy,
+ struct inode *dir, struct dentry *entry)
+{
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_UNLINK,
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_unlink_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry)
+{
+ int err = 0;
+ struct path backing_path = {};
+ struct dentry *backing_parent_dentry;
+ struct inode *backing_inode;
+
+ /* TODO Actually deal with changing the backing entry in unlink */
+ get_fuse_backing_path(entry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+
+ /* TODO Not sure if we should reverify like overlayfs, or get inode from d_parent */
+ backing_parent_dentry = dget_parent(backing_path.dentry);
+ backing_inode = d_inode(backing_parent_dentry);
+
+ inode_lock_nested(backing_inode, I_MUTEX_PARENT);
+ err = vfs_unlink(&init_user_ns, backing_inode, backing_path.dentry, NULL);
+ inode_unlock(backing_inode);
+
+ dput(backing_parent_dentry);
+ if (!err)
+ d_drop(entry);
+ path_put(&backing_path);
+ return err;
+}
+
+void *fuse_unlink_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry)
+{
+ return NULL;
+}
+
+int fuse_link_initialize(struct fuse_bpf_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 = newent->d_name.name;
+
+ return 0;
+}
+
+int fuse_link_backing(struct fuse_bpf_args *fa, struct dentry *entry,
+ struct inode *dir, struct dentry *newent)
+{
+ int err = 0;
+ 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;
+
+ 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) {
+ err = -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);
+ err = vfs_link(backing_old_path.dentry, &init_user_ns,
+ backing_dir_inode, backing_new_path.dentry, NULL);
+ inode_unlock(backing_dir_inode);
+ if (err)
+ goto out;
+
+ if (d_really_is_negative(backing_new_path.dentry) ||
+ unlikely(d_unhashed(backing_new_path.dentry))) {
+ err = -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)) {
+ err = 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 err;
+}
+
+void *fuse_link_finalize(struct fuse_bpf_args *fa, struct dentry *entry,
+ struct inode *dir, struct dentry *newent)
+{
+ return NULL;
+}
+
+int fuse_getattr_initialize(struct fuse_bpf_args *fa, struct fuse_getattr_io *fgio,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags)
+{
+ fgio->fgi = (struct fuse_getattr_in) {
+ .getattr_flags = flags,
+ .fh = -1, /* TODO is this OK? */
+ };
+
+ fgio->fao = (struct fuse_attr_out) {0};
+
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(entry->d_inode),
+ .opcode = FUSE_GETATTR,
+ .in_numargs = 1,
+ .out_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(fgio->fgi),
+ .value = &fgio->fgi,
+ },
+ .out_args[0] = (struct fuse_bpf_arg) {
+ .size = sizeof(fgio->fao),
+ .value = &fgio->fao,
+ },
+ };
+
+ return 0;
+}
+
+static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode,
+ struct kstat *stat, struct fuse_attr *attr)
+{
+ unsigned int blkbits;
+
+ /* see the comment in fuse_change_attributes() */
+ if (fc->writeback_cache && S_ISREG(inode->i_mode)) {
+ stat->size = i_size_read(inode);
+ stat->mtime.tv_sec = inode->i_mtime.tv_sec;
+ stat->mtime.tv_nsec = inode->i_mtime.tv_nsec;
+ stat->ctime.tv_sec = inode->i_ctime.tv_sec;
+ stat->ctime.tv_nsec = inode->i_ctime.tv_nsec;
+ }
+
+ attr->ino = stat->ino;
+ attr->mode = (inode->i_mode & S_IFMT) | (stat->mode & 07777);
+ attr->nlink = stat->nlink;
+ attr->uid = from_kuid(fc->user_ns, stat->uid);
+ attr->gid = from_kgid(fc->user_ns, stat->gid);
+ attr->atime = stat->atime.tv_sec;
+ attr->atimensec = stat->atime.tv_nsec;
+ attr->mtime = stat->mtime.tv_sec;
+ attr->mtimensec = stat->mtime.tv_nsec;
+ attr->ctime = stat->ctime.tv_sec;
+ attr->ctimensec = stat->ctime.tv_nsec;
+ attr->size = stat->size;
+ attr->blocks = stat->blocks;
+
+ if (stat->blksize != 0)
+ blkbits = ilog2(stat->blksize);
+ else
+ blkbits = inode->i_sb->s_blocksize_bits;
+
+ attr->blksize = 1 << blkbits;
+}
+
+int fuse_getattr_backing(struct fuse_bpf_args *fa,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags)
+{
+ struct path *backing_path =
+ &get_fuse_dentry(entry)->backing_path;
+ struct inode *backing_inode = backing_path->dentry->d_inode;
+ struct fuse_attr_out *fao = fa->out_args[0].value;
+ struct kstat tmp;
+ int err;
+
+ if (!stat)
+ stat = &tmp;
+
+ err = vfs_getattr(backing_path, stat, request_mask, flags);
+
+ if (!err)
+ fuse_stat_to_attr(get_fuse_conn(entry->d_inode),
+ backing_inode, stat, &fao->attr);
+
+ return err;
+}
+
+void *fuse_getattr_finalize(struct fuse_bpf_args *fa,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags)
+{
+ struct fuse_attr_out *outarg = fa->out_args[0].value;
+ struct inode *inode = entry->d_inode;
+ u64 attr_version = fuse_get_attr_version(get_fuse_mount(inode)->fc);
+ int err = 0;
+
+ /* TODO: Ensure this doesn't happen if we had an error getting attrs in
+ * backing.
+ */
+ err = finalize_attr(inode, outarg, attr_version, stat);
+ return ERR_PTR(err);
+}
+
+static void fattr_to_iattr(struct fuse_conn *fc,
+ const struct fuse_setattr_in *arg,
+ struct iattr *iattr)
+{
+ unsigned int fvalid = arg->valid;
+
+ if (fvalid & FATTR_MODE)
+ iattr->ia_valid |= ATTR_MODE, iattr->ia_mode = arg->mode;
+ if (fvalid & FATTR_UID) {
+ iattr->ia_valid |= ATTR_UID;
+ iattr->ia_uid = make_kuid(fc->user_ns, arg->uid);
+ }
+ if (fvalid & FATTR_GID) {
+ iattr->ia_valid |= ATTR_GID;
+ iattr->ia_gid = make_kgid(fc->user_ns, arg->gid);
+ }
+ if (fvalid & FATTR_SIZE)
+ iattr->ia_valid |= ATTR_SIZE, iattr->ia_size = arg->size;
+ if (fvalid & FATTR_ATIME) {
+ iattr->ia_valid |= ATTR_ATIME;
+ iattr->ia_atime.tv_sec = arg->atime;
+ iattr->ia_atime.tv_nsec = arg->atimensec;
+ if (!(fvalid & FATTR_ATIME_NOW))
+ iattr->ia_valid |= ATTR_ATIME_SET;
+ }
+ if (fvalid & FATTR_MTIME) {
+ iattr->ia_valid |= ATTR_MTIME;
+ iattr->ia_mtime.tv_sec = arg->mtime;
+ iattr->ia_mtime.tv_nsec = arg->mtimensec;
+ if (!(fvalid & FATTR_MTIME_NOW))
+ iattr->ia_valid |= ATTR_MTIME_SET;
+ }
+ if (fvalid & FATTR_CTIME) {
+ iattr->ia_valid |= ATTR_CTIME;
+ iattr->ia_ctime.tv_sec = arg->ctime;
+ iattr->ia_ctime.tv_nsec = arg->ctimensec;
+ }
+}
+
+int fuse_setattr_initialize(struct fuse_bpf_args *fa, struct fuse_setattr_io *fsio,
+ struct dentry *dentry, struct iattr *attr, struct file *file)
+{
+ struct fuse_conn *fc = get_fuse_conn(dentry->d_inode);
+
+ *fsio = (struct fuse_setattr_io) {0};
+ iattr_to_fattr(fc, attr, &fsio->fsi, true);
+
+ *fa = (struct fuse_bpf_args) {
+ .opcode = FUSE_SETATTR,
+ .nodeid = get_node_id(dentry->d_inode),
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(fsio->fsi),
+ .in_args[0].value = &fsio->fsi,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(fsio->fao),
+ .out_args[0].value = &fsio->fao,
+ };
+
+ return 0;
+}
+
+int fuse_setattr_backing(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct iattr *attr, struct file *file)
+{
+ struct fuse_conn *fc = get_fuse_conn(dentry->d_inode);
+ const struct fuse_setattr_in *fsi = fa->in_args[0].value;
+ struct iattr new_attr = {0};
+ struct path *backing_path = &get_fuse_dentry(dentry)->backing_path;
+ int res;
+
+ fattr_to_iattr(fc, fsi, &new_attr);
+ /* TODO: Some info doesn't get saved by the attr->fattr->attr transition
+ * When we actually allow the bpf to change these, we may have to consider
+ * the extra flags more, or pass more info into the bpf. Until then we can
+ * keep everything except for ATTR_FILE, since we'd need a file on the
+ * lower fs. For what it's worth, neither f2fs nor ext4 make use of that
+ * even if it is present.
+ */
+ new_attr.ia_valid = attr->ia_valid & ~ATTR_FILE;
+ inode_lock(d_inode(backing_path->dentry));
+ res = notify_change(&init_user_ns, backing_path->dentry, &new_attr,
+ NULL);
+ inode_unlock(d_inode(backing_path->dentry));
+
+ if (res == 0 && (new_attr.ia_valid & ATTR_SIZE))
+ i_size_write(dentry->d_inode, new_attr.ia_size);
+ return res;
+}
+
+void *fuse_setattr_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct iattr *attr, struct file *file)
+{
+ return NULL;
+}
+
+int fuse_statfs_initialize(
+ struct fuse_bpf_args *fa, struct fuse_statfs_out *fso,
+ struct dentry *dentry, struct kstatfs *buf)
+{
+ *fso = (struct fuse_statfs_out) {0};
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(d_inode(dentry)),
+ .opcode = FUSE_STATFS,
+ .out_numargs = 1,
+ .out_numargs = 1,
+ .out_args[0].size = sizeof(fso),
+ .out_args[0].value = fso,
+ };
+
+ return 0;
+}
+
+int fuse_statfs_backing(
+ struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct kstatfs *buf)
+{
+ int err = 0;
+ struct path backing_path;
+ struct fuse_statfs_out *fso = fa->out_args[0].value;
+
+ get_fuse_backing_path(dentry, &backing_path);
+ if (!backing_path.dentry)
+ return -EBADF;
+ err = vfs_statfs(&backing_path, buf);
+ path_put(&backing_path);
+ buf->f_type = FUSE_SUPER_MAGIC;
+
+ //TODO Provide postfilter opportunity to modify
+ if (!err)
+ convert_statfs_to_fuse(&fso->st, buf);
+
+ return err;
+}
+
+void *fuse_statfs_finalize(
+ struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct kstatfs *buf)
+{
+ struct fuse_statfs_out *fso = fa->out_args[0].value;
+
+ if (!fa->error_in)
+ convert_fuse_statfs(buf, &fso->st);
+ return NULL;
+}
+
+int fuse_get_link_initialize(struct fuse_bpf_args *fa, struct fuse_dummy_io *unused,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out)
+{
+ /*
+ * 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_bpf_args) {
+ .opcode = FUSE_READLINK,
+ .nodeid = get_node_id(inode),
+ .in_numargs = 1,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = dentry->d_name.len + 1,
+ .value = dentry->d_name.name,
+ },
+ /*
+ * .out_argvar = 1,
+ * .out_numargs = 1,
+ * .out_args[0].size = ,
+ * .out_args[0].value = ,
+ */
+ };
+
+ return 0;
+}
+
+int fuse_get_link_backing(struct fuse_bpf_args *fa,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out)
+{
+ 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;
+}
+
+void *fuse_get_link_finalize(struct fuse_bpf_args *fa,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out)
+{
+ return NULL;
+}
+
+int fuse_symlink_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *unused,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = get_node_id(dir),
+ .opcode = FUSE_SYMLINK,
+ .in_numargs = 2,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = entry->d_name.len + 1,
+ .value = entry->d_name.name,
+ },
+ .in_args[1] = (struct fuse_bpf_in_arg) {
+ .size = len,
+ .value = link,
+ },
+ };
+
+ return 0;
+}
+
+int fuse_symlink_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ int err = 0;
+ 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;
+
+ //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);
+ err = vfs_symlink(&init_user_ns, backing_inode, backing_path.dentry,
+ link);
+ inode_unlock(backing_inode);
+ if (err)
+ goto out;
+ if (d_really_is_negative(backing_path.dentry) ||
+ unlikely(d_unhashed(backing_path.dentry))) {
+ err = -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)) {
+ err = PTR_ERR(inode);
+ goto out;
+ }
+ d_instantiate(entry, inode);
+out:
+ path_put(&backing_path);
+ return err;
+}
+
+void *fuse_symlink_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, const char *link, int len)
+{
+ return NULL;
+}
+
+int fuse_readdir_initialize(struct fuse_bpf_args *fa, struct fuse_read_io *frio,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued)
+{
+ struct fuse_file *ff = file->private_data;
+ u8 *page = (u8 *)__get_free_page(GFP_KERNEL);
+
+ if (!page)
+ return -ENOMEM;
+
+ *fa = (struct fuse_bpf_args) {
+ .nodeid = ff->nodeid,
+ .opcode = FUSE_READDIR,
+ .in_numargs = 1,
+ .flags = FUSE_BPF_OUT_ARGVAR,
+ .out_numargs = 2,
+ .in_args[0] = (struct fuse_bpf_in_arg) {
+ .size = sizeof(frio->fri),
+ .value = &frio->fri,
+ },
+ .out_args[0] = (struct fuse_bpf_arg) {
+ .size = sizeof(frio->fro),
+ .value = &frio->fro,
+ },
+ .out_args[1] = (struct fuse_bpf_arg) {
+ .size = PAGE_SIZE,
+ .value = page,
+ },
+ };
+
+ frio->fri = (struct fuse_read_in) {
+ .fh = ff->fh,
+ .offset = ctx->pos,
+ .size = PAGE_SIZE,
+ };
+ frio->fro = (struct fuse_read_out) {
+ .again = 0,
+ .offset = 0,
+ };
+ *force_again = false;
+ *allow_force = true;
+ return 0;
+}
+
+struct extfuse_ctx {
+ struct dir_context ctx;
+ u8 *addr;
+ size_t offset;
+};
+
+static bool filldir(struct dir_context *ctx, const char *name, int namelen,
+ loff_t offset, u64 ino, unsigned int d_type)
+{
+ struct extfuse_ctx *ec = container_of(ctx, struct extfuse_ctx, ctx);
+ struct fuse_dirent *fd = (struct fuse_dirent *) (ec->addr + ec->offset);
+
+ if (ec->offset + sizeof(struct fuse_dirent) + namelen > PAGE_SIZE)
+ return false;
+
+ *fd = (struct fuse_dirent) {
+ .ino = ino,
+ .off = offset,
+ .namelen = namelen,
+ .type = d_type,
+ };
+
+ memcpy(fd->name, name, namelen);
+ ec->offset += FUSE_DIRENT_SIZE(fd);
+
+ return true;
+}
+
+static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx)
+{
+ while (nbytes >= FUSE_NAME_OFFSET) {
+ struct fuse_dirent *dirent = (struct fuse_dirent *) buf;
+ size_t reclen = FUSE_DIRENT_SIZE(dirent);
+
+ if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX)
+ return -EIO;
+ if (reclen > nbytes)
+ break;
+ if (memchr(dirent->name, '/', dirent->namelen) != NULL)
+ return -EIO;
+
+ ctx->pos = dirent->off;
+ if (!dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino,
+ dirent->type))
+ break;
+
+ buf += reclen;
+ nbytes -= reclen;
+ }
+
+ return 0;
+}
+
+
+int fuse_readdir_backing(struct fuse_bpf_args *fa,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued)
+{
+ struct fuse_file *ff = file->private_data;
+ struct file *backing_dir = ff->backing_file;
+ struct fuse_read_out *fro = fa->out_args[0].value;
+ struct extfuse_ctx ec;
+ int err;
+
+ ec = (struct extfuse_ctx) {
+ .ctx.actor = filldir,
+ .ctx.pos = ctx->pos,
+ .addr = fa->out_args[1].value,
+ };
+
+ if (!ec.addr)
+ return -ENOMEM;
+
+ if (!is_continued)
+ backing_dir->f_pos = file->f_pos;
+
+ err = iterate_dir(backing_dir, &ec.ctx);
+ if (ec.offset == 0)
+ *allow_force = false;
+ fa->out_args[1].size = ec.offset;
+
+ fro->offset = ec.ctx.pos;
+ fro->again = false;
+ return err;
+}
+
+void *fuse_readdir_finalize(struct fuse_bpf_args *fa,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued)
+{
+ struct fuse_read_out *fro = fa->out_args[0].value;
+ struct fuse_file *ff = file->private_data;
+ struct file *backing_dir = ff->backing_file;
+ int err = 0;
+
+ err = parse_dirfile(fa->out_args[1].value, fa->out_args[1].size, ctx);
+ *force_again = !!fro->again;
+ if (*force_again && !*allow_force)
+ err = -EINVAL;
+
+ ctx->pos = fro->offset;
+ backing_dir->f_pos = fro->offset;
+
+ free_page((unsigned long) fa->out_args[1].value);
+ return ERR_PTR(err);
+}
+
+int fuse_access_initialize(struct fuse_bpf_args *fa, struct fuse_access_in *fai,
+ struct inode *inode, int mask)
+{
+ *fai = (struct fuse_access_in) {
+ .mask = mask,
+ };
+
+ *fa = (struct fuse_bpf_args) {
+ .opcode = FUSE_ACCESS,
+ .nodeid = get_node_id(inode),
+ .in_numargs = 1,
+ .in_args[0].size = sizeof(*fai),
+ .in_args[0].value = fai,
+ };
+
+ return 0;
+}
+
+int fuse_access_backing(struct fuse_bpf_args *fa, struct inode *inode, int mask)
+{
+ struct fuse_inode *fi = get_fuse_inode(inode);
+ const struct fuse_access_in *fai = fa->in_args[0].value;
+
+ return inode_permission(&init_user_ns, fi->backing_inode, fai->mask);
+}
+
+void *fuse_access_finalize(struct fuse_bpf_args *fa, struct inode *inode, int mask)
+{
+ return NULL;
+}
+
+int __init fuse_bpf_init(void)
+{
+ fuse_bpf_aio_request_cachep = kmem_cache_create("fuse_bpf_aio_req",
+ sizeof(struct fuse_bpf_aio_req),
+ 0, SLAB_HWCACHE_ALIGN, NULL);
+ if (!fuse_bpf_aio_request_cachep)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void __exit fuse_bpf_cleanup(void)
+{
+ kmem_cache_destroy(fuse_bpf_aio_request_cachep);
+}
+
+ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct fuse_bpf_args *bpf_args)
+{
+ int i;
+ ssize_t res;
+ struct fuse_args args = {
+ .nodeid = bpf_args->nodeid,
+ .opcode = bpf_args->opcode,
+ .error_in = bpf_args->error_in,
+ .in_numargs = bpf_args->in_numargs,
+ .out_numargs = bpf_args->out_numargs,
+ .force = !!(bpf_args->flags & FUSE_BPF_FORCE),
+ .out_argvar = !!(bpf_args->flags & FUSE_BPF_OUT_ARGVAR),
+ };
+
+ for (i = 0; i < args.in_numargs; ++i)
+ args.in_args[i] = (struct fuse_in_arg) {
+ .size = bpf_args->in_args[i].size,
+ .value = bpf_args->in_args[i].value,
+ };
+ for (i = 0; i < args.out_numargs; ++i)
+ args.out_args[i] = (struct fuse_arg) {
+ .size = bpf_args->out_args[i].size,
+ .value = bpf_args->out_args[i].value,
+ };
+
+ res = fuse_simple_request(fm, &args);
+
+ *bpf_args = (struct fuse_bpf_args) {
+ .nodeid = args.nodeid,
+ .opcode = args.opcode,
+ .error_in = args.error_in,
+ .in_numargs = args.in_numargs,
+ .out_numargs = args.out_numargs,
+ };
+ if (args.force)
+ bpf_args->flags |= FUSE_BPF_FORCE;
+ if (args.out_argvar)
+ bpf_args->flags |= FUSE_BPF_OUT_ARGVAR;
+ for (i = 0; i < args.in_numargs; ++i)
+ bpf_args->in_args[i] = (struct fuse_bpf_in_arg) {
+ .size = args.in_args[i].size,
+ .value = args.in_args[i].value,
+ };
+ for (i = 0; i < args.out_numargs; ++i)
+ bpf_args->out_args[i] = (struct fuse_bpf_arg) {
+ .size = args.out_args[i].size,
+ .value = args.out_args[i].value,
+ };
+ return res;
+}
diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index 247ef4f..6855524 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -378,7 +378,7 @@ int __init fuse_ctl_init(void)
return register_filesystem(&fuse_ctl_fs_type);
}
-void __exit fuse_ctl_cleanup(void)
+void fuse_ctl_cleanup(void)
{
unregister_filesystem(&fuse_ctl_fs_type);
}
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index aac9860..155e073 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -242,6 +242,11 @@ void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget,
{
struct fuse_iqueue *fiq = &fc->iq;
+ if (nodeid == 0) {
+ kfree(forget);
+ return;
+ }
+
forget->forget_one.nodeid = nodeid;
forget->forget_one.nlookup = nlookup;
@@ -479,6 +484,7 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args)
{
req->in.h.opcode = args->opcode;
req->in.h.nodeid = args->nodeid;
+ req->in.h.padding = args->error_in;
req->args = args;
if (args->end)
__set_bit(FR_ASYNC, &req->flags);
@@ -1934,6 +1940,19 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
kern_path(path, 0, req->args->canonical_path);
}
+ if (!err && (req->in.h.opcode == FUSE_LOOKUP ||
+ req->in.h.opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) &&
+ req->args->out_args[1].size == sizeof(struct fuse_entry_bpf_out)) {
+ struct fuse_entry_bpf_out *febo = (struct fuse_entry_bpf_out *)
+ req->args->out_args[1].value;
+ struct fuse_entry_bpf *feb = container_of(febo, struct fuse_entry_bpf, out);
+
+ if (febo->backing_action == FUSE_ACTION_REPLACE)
+ feb->backing_file = fget(febo->backing_fd);
+ if (febo->bpf_action == FUSE_ACTION_REPLACE)
+ feb->bpf_file = fget(febo->bpf_fd);
+ }
+
spin_lock(&fpq->lock);
clear_bit(FR_LOCKED, &req->flags);
if (!fpq->connected)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index fe49b2c..d7c90d7 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -8,8 +8,10 @@
#include "fuse_i.h"
+#include <linux/fdtable.h>
#include <linux/pagemap.h>
#include <linux/file.h>
+#include <linux/filter.h>
#include <linux/fs_context.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
@@ -27,6 +29,8 @@ module_param(allow_sys_admin_access, bool, 0644);
MODULE_PARM_DESC(allow_sys_admin_access,
"Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check");
+#include "../internal.h"
+
static void fuse_advise_use_readdirplus(struct inode *dir)
{
struct fuse_inode *fi = get_fuse_inode(dir);
@@ -34,7 +38,7 @@ static void fuse_advise_use_readdirplus(struct inode *dir)
set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);
}
-#if BITS_PER_LONG >= 64
+#if BITS_PER_LONG >= 64 && !defined(CONFIG_FUSE_BPF)
static inline void __fuse_dentry_settime(struct dentry *entry, u64 time)
{
entry->d_fsdata = (void *) time;
@@ -46,19 +50,15 @@ 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)
{
- ((union fuse_dentry *) dentry->d_fsdata)->time = time;
+ ((struct fuse_dentry *) dentry->d_fsdata)->time = time;
}
static inline u64 fuse_dentry_time(const struct dentry *entry)
{
- return ((union fuse_dentry *) entry->d_fsdata)->time;
+ return ((struct fuse_dentry *) entry->d_fsdata)->time;
}
#endif
@@ -83,26 +83,16 @@ 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)
+void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir)
{
- if (sec || nsec) {
- struct timespec64 ts = {
- sec,
- min_t(u32, nsec, NSEC_PER_SEC - 1)
- };
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_dentry *fuse_dentry = root->d_fsdata;
- return get_jiffies_64() + timespec64_to_jiffies(&ts);
- } else
- return 0;
+ if (backing_dir) {
+ fuse_dentry->backing_path = backing_dir->f_path;
+ path_get(&fuse_dentry->backing_path);
+ }
+#endif
}
/*
@@ -115,11 +105,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);
@@ -180,7 +165,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
u64 nodeid, const struct qstr *name,
- struct fuse_entry_out *outarg)
+ struct fuse_entry_out *outarg,
+ struct fuse_entry_bpf_out *bpf_outarg)
{
memset(outarg, 0, sizeof(struct fuse_entry_out));
args->opcode = FUSE_LOOKUP;
@@ -188,11 +174,52 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
args->in_numargs = 1;
args->in_args[0].size = name->len + 1;
args->in_args[0].value = name->name;
- args->out_numargs = 1;
+ args->out_argvar = true;
+ args->out_numargs = 2;
args->out_args[0].size = sizeof(struct fuse_entry_out);
args->out_args[0].value = outarg;
+ args->out_args[1].size = sizeof(struct fuse_entry_bpf_out);
+ args->out_args[1].value = bpf_outarg;
}
+#ifdef CONFIG_FUSE_BPF
+static bool backing_data_changed(struct fuse_inode *fi, struct dentry *entry,
+ struct fuse_entry_bpf *bpf_arg)
+{
+ struct path new_backing_path;
+ struct inode *new_backing_inode;
+ struct bpf_prog *bpf = NULL;
+ int err;
+ bool ret = true;
+
+ if (!entry)
+ return false;
+
+ get_fuse_backing_path(entry, &new_backing_path);
+ new_backing_inode = fi->backing_inode;
+ ihold(new_backing_inode);
+
+ err = fuse_handle_backing(bpf_arg, &new_backing_inode, &new_backing_path);
+
+ if (err)
+ goto put_inode;
+
+ err = fuse_handle_bpf_prog(bpf_arg, entry->d_parent->d_inode, &bpf);
+ if (err)
+ goto put_bpf;
+
+ ret = (bpf != fi->bpf || fi->backing_inode != new_backing_inode ||
+ !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path));
+put_bpf:
+ if (bpf)
+ bpf_prog_put(bpf);
+put_inode:
+ iput(new_backing_inode);
+ path_put(&new_backing_path);
+ return ret;
+}
+#endif
+
/*
* Check whether the dentry is still valid
*
@@ -213,9 +240,23 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
inode = d_inode_rcu(entry);
if (inode && fuse_is_bad(inode))
goto invalid;
- else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
+
+#ifdef CONFIG_FUSE_BPF
+ /* TODO: Do we need bpf support for revalidate?
+ * If the lower filesystem says the entry is invalid, FUSE probably shouldn't
+ * try to fix that without going through the normal lookup path...
+ */
+ if (get_fuse_dentry(entry)->backing_path.dentry) {
+ ret = fuse_revalidate_backing(entry, flags);
+ if (ret <= 0) {
+ goto out;
+ }
+ }
+#endif
+ if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
(flags & (LOOKUP_EXCL | LOOKUP_REVAL))) {
struct fuse_entry_out outarg;
+ struct fuse_entry_bpf bpf_arg;
FUSE_ARGS(args);
struct fuse_forget_link *forget;
u64 attr_version;
@@ -227,27 +268,44 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
ret = -ECHILD;
if (flags & LOOKUP_RCU)
goto out;
-
fm = get_fuse_mount(inode);
+ parent = dget_parent(entry);
+
+#ifdef CONFIG_FUSE_BPF
+ /* TODO: Once we're handling timeouts for backing inodes, do a
+ * bpf based lookup_revalidate here.
+ */
+ if (get_fuse_inode(parent->d_inode)->backing_inode) {
+ dput(parent);
+ ret = 1;
+ goto out;
+ }
+#endif
forget = fuse_alloc_forget();
ret = -ENOMEM;
- if (!forget)
+ if (!forget) {
+ dput(parent);
goto out;
+ }
attr_version = fuse_get_attr_version(fm->fc);
- parent = dget_parent(entry);
fuse_lookup_init(fm->fc, &args, get_node_id(d_inode(parent)),
- &entry->d_name, &outarg);
+ &entry->d_name, &outarg, &bpf_arg.out);
ret = fuse_simple_request(fm, &args);
dput(parent);
+
/* Zero nodeid is same as -ENOENT */
if (!ret && !outarg.nodeid)
ret = -ENOENT;
- if (!ret) {
+ if (!ret || ret == sizeof(bpf_arg.out)) {
fi = get_fuse_inode(inode);
if (outarg.nodeid != get_node_id(inode) ||
+#ifdef CONFIG_FUSE_BPF
+ (ret == sizeof(bpf_arg.out) &&
+ backing_data_changed(fi, entry, &bpf_arg)) ||
+#endif
(bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
fuse_queue_forget(fm->fc, forget,
outarg.nodeid, 1);
@@ -289,17 +347,20 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
goto out;
}
-#if BITS_PER_LONG < 64
+#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
static int fuse_dentry_init(struct dentry *dentry)
{
- dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry),
+ dentry->d_fsdata = kzalloc(sizeof(struct fuse_dentry),
GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);
return dentry->d_fsdata ? 0 : -ENOMEM;
}
static void fuse_dentry_release(struct dentry *dentry)
{
- union fuse_dentry *fd = dentry->d_fsdata;
+ struct fuse_dentry *fd = dentry->d_fsdata;
+
+ if (fd && fd->backing_path.dentry)
+ path_put(&fd->backing_path);
kfree_rcu(fd, rcu);
}
@@ -353,6 +414,18 @@ static void fuse_dentry_canonical_path(const struct path *path,
char *path_name;
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_dummy_io,
+ fuse_canonical_path_initialize,
+ fuse_canonical_path_backing,
+ fuse_canonical_path_finalize, path,
+ canonical_path);
+ if (fer.ret)
+ return;
+#endif
+
path_name = (char *)get_zeroed_page(GFP_KERNEL);
if (!path_name)
goto default_path;
@@ -379,7 +452,7 @@ static void fuse_dentry_canonical_path(const struct path *path,
const struct dentry_operations fuse_dentry_operations = {
.d_revalidate = fuse_dentry_revalidate,
.d_delete = fuse_dentry_delete,
-#if BITS_PER_LONG < 64
+#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
.d_init = fuse_dentry_init,
.d_release = fuse_dentry_release,
#endif
@@ -388,7 +461,7 @@ const struct dentry_operations fuse_dentry_operations = {
};
const struct dentry_operations fuse_root_dentry_operations = {
-#if BITS_PER_LONG < 64
+#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
.d_init = fuse_dentry_init,
.d_release = fuse_dentry_release,
#endif
@@ -407,10 +480,13 @@ bool fuse_invalid_attr(struct fuse_attr *attr)
}
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
- struct fuse_entry_out *outarg, struct inode **inode)
+ struct fuse_entry_out *outarg,
+ struct dentry *entry,
+ struct inode **inode)
{
struct fuse_mount *fm = get_fuse_mount_super(sb);
FUSE_ARGS(args);
+ struct fuse_entry_bpf bpf_arg = {0};
struct fuse_forget_link *forget;
u64 attr_version;
int err;
@@ -428,23 +504,68 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
attr_version = fuse_get_attr_version(fm->fc);
- fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
+ fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, &bpf_arg.out);
err = fuse_simple_request(fm, &args);
- /* Zero nodeid is same as -ENOENT, but with valid timeout */
- if (err || !outarg->nodeid)
- goto out_put_forget;
- err = -EIO;
- if (!outarg->nodeid)
- goto out_put_forget;
- if (fuse_invalid_attr(&outarg->attr))
- goto out_put_forget;
+#ifdef CONFIG_FUSE_BPF
+ if (err == sizeof(bpf_arg.out)) {
+ /* TODO Make sure this handles invalid handles */
+ struct file *backing_file;
+ struct inode *backing_inode;
- *inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
- &outarg->attr, entry_attr_timeout(outarg),
- attr_version);
+ err = -ENOENT;
+ if (!entry)
+ goto out_queue_forget;
+
+ err = -EINVAL;
+ backing_file = bpf_arg.backing_file;
+ if (!backing_file)
+ goto out_queue_forget;
+
+ if (IS_ERR(backing_file)) {
+ err = PTR_ERR(backing_file);
+ goto out_queue_forget;
+ }
+
+ backing_inode = backing_file->f_inode;
+ *inode = fuse_iget_backing(sb, outarg->nodeid, backing_inode);
+ if (!*inode)
+ goto bpf_arg_out;
+
+ err = fuse_handle_backing(&bpf_arg,
+ &get_fuse_inode(*inode)->backing_inode,
+ &get_fuse_dentry(entry)->backing_path);
+ if (err)
+ goto out;
+
+ err = fuse_handle_bpf_prog(&bpf_arg, NULL, &get_fuse_inode(*inode)->bpf);
+ if (err)
+ goto out;
+bpf_arg_out:
+ fput(backing_file);
+ } else
+#endif
+ {
+ /* Zero nodeid is same as -ENOENT, but with valid timeout */
+ if (err || !outarg->nodeid)
+ goto out_put_forget;
+
+ err = -EIO;
+ if (!outarg->nodeid)
+ goto out_put_forget;
+ if (fuse_invalid_attr(&outarg->attr))
+ goto out_put_forget;
+
+ *inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
+ &outarg->attr, entry_attr_timeout(outarg),
+ attr_version);
+ }
+
err = -ENOMEM;
- if (!*inode) {
+#ifdef CONFIG_FUSE_BPF
+out_queue_forget:
+#endif
+ if (!*inode && outarg->nodeid) {
fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
goto out;
}
@@ -466,12 +587,23 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
bool outarg_valid = true;
bool locked;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_lookup_io,
+ fuse_lookup_initialize, fuse_lookup_backing,
+ fuse_lookup_finalize,
+ dir, entry, flags);
+ if (fer.ret)
+ return fer.result;
+#endif
+
if (fuse_is_bad(dir))
return ERR_PTR(-EIO);
locked = fuse_lock_inode(dir);
err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
- &outarg, &inode);
+ &outarg, entry, &inode);
fuse_unlock_inode(dir, locked);
if (err == -ENOENT) {
outarg_valid = false;
@@ -589,6 +721,20 @@ 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);
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_create_open_io,
+ fuse_create_open_initialize,
+ fuse_create_open_backing,
+ fuse_create_open_finalize,
+ dir, entry, file, flags, mode);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
forget = fuse_alloc_forget();
err = -ENOMEM;
if (!forget)
@@ -822,6 +968,17 @@ static int fuse_mknod(struct user_namespace *mnt_userns, struct inode *dir,
struct fuse_mount *fm = get_fuse_mount(dir);
FUSE_ARGS(args);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_mknod_in,
+ fuse_mknod_initialize, fuse_mknod_backing,
+ fuse_mknod_finalize,
+ dir, entry, mode, rdev);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (!fm->fc->dont_mask)
mode &= ~current_umask();
@@ -868,6 +1025,17 @@ static int fuse_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
struct fuse_mount *fm = get_fuse_mount(dir);
FUSE_ARGS(args);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_mkdir_in,
+ fuse_mkdir_initialize, fuse_mkdir_backing,
+ fuse_mkdir_finalize,
+ dir, entry, mode);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (!fm->fc->dont_mask)
mode &= ~current_umask();
@@ -890,6 +1058,17 @@ static int fuse_symlink(struct user_namespace *mnt_userns, struct inode *dir,
unsigned len = strlen(link) + 1;
FUSE_ARGS(args);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_dummy_io,
+ fuse_symlink_initialize, fuse_symlink_backing,
+ fuse_symlink_finalize,
+ dir, entry, link, len);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
args.opcode = FUSE_SYMLINK;
args.in_numargs = 2;
args.in_args[0].size = entry->d_name.len + 1;
@@ -953,6 +1132,20 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry)
if (fuse_is_bad(dir))
return -EIO;
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_dummy_io,
+ fuse_unlink_initialize,
+ fuse_unlink_backing,
+ fuse_unlink_finalize,
+ dir, entry);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
args.opcode = FUSE_UNLINK;
args.nodeid = get_node_id(dir);
args.in_numargs = 1;
@@ -976,6 +1169,20 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry)
if (fuse_is_bad(dir))
return -EIO;
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(dir, struct fuse_dummy_io,
+ fuse_rmdir_initialize,
+ fuse_rmdir_backing,
+ fuse_rmdir_finalize,
+ dir, entry);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
args.opcode = FUSE_RMDIR;
args.nodeid = get_node_id(dir);
args.in_numargs = 1;
@@ -1054,6 +1261,18 @@ static int fuse_rename2(struct user_namespace *mnt_userns, struct inode *olddir,
return -EINVAL;
if (flags) {
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(olddir, struct fuse_rename2_in,
+ fuse_rename2_initialize, fuse_rename2_backing,
+ fuse_rename2_finalize,
+ olddir, oldent, newdir, newent, flags);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
+ /* TODO: how should this go with bpfs involved? */
if (fc->no_rename2 || fc->minor < 23)
return -EINVAL;
@@ -1065,6 +1284,17 @@ static int fuse_rename2(struct user_namespace *mnt_userns, struct inode *olddir,
err = -EINVAL;
}
} else {
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(olddir, struct fuse_rename_in,
+ fuse_rename_initialize, fuse_rename_backing,
+ fuse_rename_finalize,
+ olddir, oldent, newdir, newent);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
err = fuse_rename_common(olddir, oldent, newdir, newent, 0,
FUSE_RENAME,
sizeof(struct fuse_rename_in));
@@ -1082,6 +1312,16 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,
struct fuse_mount *fm = get_fuse_mount(inode);
FUSE_ARGS(args);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_link_in, fuse_link_initialize,
+ fuse_link_backing, fuse_link_finalize, entry,
+ newdir, newent);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
memset(&inarg, 0, sizeof(inarg));
inarg.oldnodeid = get_node_id(inode);
args.opcode = FUSE_LINK;
@@ -1099,7 +1339,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,
return err;
}
-static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
+void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
struct kstat *stat)
{
unsigned int blkbits;
@@ -1159,23 +1399,13 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
args.out_args[0].size = sizeof(outarg);
args.out_args[0].value = &outarg;
err = fuse_simple_request(fm, &args);
- if (!err) {
- if (fuse_invalid_attr(&outarg.attr) ||
- inode_wrong_type(inode, outarg.attr.mode)) {
- fuse_make_bad(inode);
- err = -EIO;
- } else {
- fuse_change_attributes(inode, &outarg.attr,
- attr_timeout(&outarg),
- attr_version);
- if (stat)
- fuse_fillattr(inode, &outarg.attr, stat);
- }
- }
+ if (!err)
+ err = finalize_attr(inode, &outarg, attr_version, stat);
return err;
}
static int fuse_update_get_attr(struct inode *inode, struct file *file,
+ const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int flags)
{
@@ -1185,6 +1415,17 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
u32 inval_mask = READ_ONCE(fi->inval_mask);
u32 cache_mask = fuse_get_cache_mask(inode);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_getattr_io,
+ fuse_getattr_initialize, fuse_getattr_backing,
+ fuse_getattr_finalize,
+ path->dentry, stat, request_mask, flags);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (flags & AT_STATX_FORCE_SYNC)
sync = true;
else if (flags & AT_STATX_DONT_SYNC)
@@ -1208,7 +1449,9 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
int fuse_update_attributes(struct inode *inode, struct file *file, u32 mask)
{
- return fuse_update_get_attr(inode, file, NULL, mask, 0);
+ /* Do *not* need to get atime for internal purposes */
+ return fuse_update_get_attr(inode, file, &file->f_path, NULL,
+ mask & ~STATX_ATIME, 0);
}
int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
@@ -1319,6 +1562,16 @@ static int fuse_access(struct inode *inode, int mask)
struct fuse_access_in inarg;
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_access_in,
+ fuse_access_initialize, fuse_access_backing,
+ fuse_access_finalize, inode, mask);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
BUG_ON(mask & MAY_NOT_BLOCK);
if (fm->fc->no_access)
@@ -1367,6 +1620,10 @@ static int fuse_permission(struct user_namespace *mnt_userns,
struct fuse_conn *fc = get_fuse_conn(inode);
bool refreshed = false;
int err = 0;
+ struct fuse_inode *fi = get_fuse_inode(inode);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+#endif
if (fuse_is_bad(inode))
return -EIO;
@@ -1374,12 +1631,19 @@ static int fuse_permission(struct user_namespace *mnt_userns,
if (!fuse_allow_current_process(fc))
return -EACCES;
+#ifdef CONFIG_FUSE_BPF
+ fer = fuse_bpf_backing(inode, struct fuse_access_in,
+ fuse_access_initialize, fuse_access_backing,
+ fuse_access_finalize, inode, mask);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
/*
* If attributes are needed, refresh them before proceeding
*/
if (fc->default_permissions ||
((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) {
- struct fuse_inode *fi = get_fuse_inode(inode);
u32 perm_mask = STATX_MODE | STATX_UID | STATX_GID;
if (perm_mask & READ_ONCE(fi->inval_mask) ||
@@ -1470,6 +1734,21 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
if (fuse_is_bad(inode))
goto out_err;
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+ const char *out = NULL;
+
+ fer = fuse_bpf_backing(inode, struct fuse_dummy_io,
+ fuse_get_link_initialize,
+ fuse_get_link_backing,
+ fuse_get_link_finalize,
+ inode, dentry, callback, &out);
+ if (fer.ret)
+ return fer.result ?: out;
+ }
+#endif
+
if (fc->cache_symlinks)
return page_get_link(dentry, inode, callback);
@@ -1503,8 +1782,18 @@ static int fuse_dir_open(struct inode *inode, struct file *file)
static int fuse_dir_release(struct inode *inode, struct file *file)
{
- fuse_release_common(file, true);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+ fer = fuse_bpf_backing(inode, struct fuse_release_in,
+ fuse_releasedir_initialize, fuse_release_backing,
+ fuse_release_finalize,
+ inode, file);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
+ fuse_release_common(file, true);
return 0;
}
@@ -1518,6 +1807,19 @@ static int fuse_dir_fsync(struct file *file, loff_t start, loff_t end,
if (fuse_is_bad(inode))
return -EIO;
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_fsync_in,
+ fuse_dir_fsync_initialize, fuse_fsync_backing,
+ fuse_fsync_finalize,
+ file, start, end, datasync);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
if (fc->no_fsyncdir)
return 0;
@@ -1556,58 +1858,6 @@ static long fuse_dir_compat_ioctl(struct file *file, unsigned int cmd,
FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR);
}
-static bool update_mtime(unsigned ivalid, bool trust_local_mtime)
-{
- /* Always update if mtime is explicitly set */
- if (ivalid & ATTR_MTIME_SET)
- return true;
-
- /* Or if kernel i_mtime is the official one */
- if (trust_local_mtime)
- return true;
-
- /* If it's an open(O_TRUNC) or an ftruncate(), don't update */
- if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
- return false;
-
- /* In all other cases update */
- return true;
-}
-
-static void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr,
- struct fuse_setattr_in *arg, bool trust_local_cmtime)
-{
- unsigned ivalid = iattr->ia_valid;
-
- if (ivalid & ATTR_MODE)
- arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode;
- if (ivalid & ATTR_UID)
- arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid);
- if (ivalid & ATTR_GID)
- arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid);
- if (ivalid & ATTR_SIZE)
- arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size;
- if (ivalid & ATTR_ATIME) {
- arg->valid |= FATTR_ATIME;
- arg->atime = iattr->ia_atime.tv_sec;
- arg->atimensec = iattr->ia_atime.tv_nsec;
- if (!(ivalid & ATTR_ATIME_SET))
- arg->valid |= FATTR_ATIME_NOW;
- }
- if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) {
- arg->valid |= FATTR_MTIME;
- arg->mtime = iattr->ia_mtime.tv_sec;
- arg->mtimensec = iattr->ia_mtime.tv_nsec;
- if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime)
- arg->valid |= FATTR_MTIME_NOW;
- }
- if ((ivalid & ATTR_CTIME) && trust_local_cmtime) {
- arg->valid |= FATTR_CTIME;
- arg->ctime = iattr->ia_ctime.tv_sec;
- arg->ctimensec = iattr->ia_ctime.tv_nsec;
- }
-}
-
/*
* Prevent concurrent writepages on inode
*
@@ -1722,6 +1972,16 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
bool trust_local_cmtime = is_wb;
bool fault_blocked = false;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_setattr_io,
+ fuse_setattr_initialize, fuse_setattr_backing,
+ fuse_setattr_finalize, dentry, attr, file);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (!fc->default_permissions)
attr->ia_valid |= ATTR_FORCE;
@@ -1897,11 +2157,22 @@ static int fuse_setattr(struct user_namespace *mnt_userns, struct dentry *entry,
* This should be done on write(), truncate() and chown().
*/
if (!fc->handle_killpriv && !fc->handle_killpriv_v2) {
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
/*
* ia_mode calculation may have used stale i_mode.
* Refresh and recalculate.
*/
- ret = fuse_do_getattr(inode, NULL, file);
+ fer = fuse_bpf_backing(inode, struct fuse_getattr_io,
+ fuse_getattr_initialize, fuse_getattr_backing,
+ fuse_getattr_finalize,
+ entry, NULL, 0, 0);
+ if (fer.ret)
+ ret = PTR_ERR(fer.result);
+ else
+#endif
+ ret = fuse_do_getattr(inode, NULL, file);
if (ret)
return ret;
@@ -1958,7 +2229,8 @@ static int fuse_getattr(struct user_namespace *mnt_userns,
return -EACCES;
}
- return fuse_update_get_attr(inode, NULL, stat, request_mask, flags);
+ return fuse_update_get_attr(inode, NULL, path, stat, request_mask,
+ flags);
}
static const struct inode_operations fuse_dir_inode_operations = {
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index edceaa0..bccf5cf 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -8,6 +8,7 @@
#include "fuse_i.h"
+#include <linux/filter.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/kernel.h>
@@ -235,6 +236,20 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir)
if (err)
return err;
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_open_io,
+ fuse_open_initialize,
+ fuse_open_backing,
+ fuse_open_finalize,
+ inode, file, isdir);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
if (is_wb_truncate || dax_truncate)
inode_lock(inode);
@@ -346,6 +361,17 @@ static int fuse_release(struct inode *inode, struct file *file)
{
struct fuse_conn *fc = get_fuse_conn(inode);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_release_in,
+ fuse_release_initialize, fuse_release_backing,
+ fuse_release_finalize,
+ inode, file);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
/*
* Dirty pages might remain despite write_inode_now() call from
* fuse_flush() due to writes racing with the close.
@@ -488,6 +514,17 @@ static int fuse_flush(struct file *file, fl_owner_t id)
FUSE_ARGS(args);
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(file->f_inode, struct fuse_flush_in,
+ fuse_flush_initialize, fuse_flush_backing,
+ fuse_flush_finalize,
+ file, id);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
@@ -563,6 +600,17 @@ static int fuse_fsync(struct file *file, loff_t start, loff_t end,
struct fuse_conn *fc = get_fuse_conn(inode);
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_fsync_in,
+ fuse_fsync_initialize, fuse_fsync_backing,
+ fuse_fsync_finalize,
+ file, start, end, datasync);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
@@ -1600,6 +1648,20 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
if (FUSE_IS_DAX(inode))
return fuse_dax_read_iter(iocb, to);
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_file_read_iter_io,
+ fuse_file_read_iter_initialize,
+ fuse_file_read_iter_backing,
+ fuse_file_read_iter_finalize,
+ iocb, to);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
if (ff->passthrough.filp)
return fuse_passthrough_read_iter(iocb, to);
else if (!(ff->open_flags & FOPEN_DIRECT_IO))
@@ -1620,6 +1682,20 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (FUSE_IS_DAX(inode))
return fuse_dax_write_iter(iocb, from);
+#ifdef CONFIG_FUSE_BPF
+ {
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_file_write_iter_io,
+ fuse_file_write_iter_initialize,
+ fuse_file_write_iter_backing,
+ fuse_file_write_iter_finalize,
+ iocb, from);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+ }
+#endif
+
if (ff->passthrough.filp)
return fuse_passthrough_write_iter(iocb, from);
else if (!(ff->open_flags & FOPEN_DIRECT_IO))
@@ -1868,6 +1944,19 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc)
struct fuse_file *ff;
int err;
+ /**
+ * TODO - fully understand why this is necessary
+ *
+ * With fuse-bpf, fsstress fails if rename is enabled without this
+ *
+ * We are getting writes here on directory inodes, which do not have an
+ * initialized file list so crash.
+ *
+ * The question is why we are getting those writes
+ */
+ if (!S_ISREG(inode->i_mode))
+ return 0;
+
/*
* Inode is always written before the last reference is dropped and
* hence this should not be reached from reclaim.
@@ -2439,6 +2528,12 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
if (FUSE_IS_DAX(file_inode(file)))
return fuse_dax_mmap(file, vma);
+#ifdef CONFIG_FUSE_BPF
+ /* TODO - this is simply passthrough, not a proper BPF filter */
+ if (ff->backing_file)
+ return fuse_backing_mmap(file, vma);
+#endif
+
if (ff->passthrough.filp)
return fuse_passthrough_mmap(file, vma);
@@ -2687,6 +2782,17 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
{
loff_t retval;
struct inode *inode = file_inode(file);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_lseek_io,
+ fuse_lseek_initialize,
+ fuse_lseek_backing,
+ fuse_lseek_finalize,
+ file, offset, whence);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
switch (whence) {
case SEEK_SET:
@@ -2976,6 +3082,18 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
(!(mode & FALLOC_FL_KEEP_SIZE) ||
(mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE)));
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_fallocate_in,
+ fuse_file_fallocate_initialize,
+ fuse_file_fallocate_backing,
+ fuse_file_fallocate_finalize,
+ file, mode, offset, length);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
FALLOC_FL_ZERO_RANGE))
return -EOPNOTSUPP;
@@ -3079,6 +3197,18 @@ 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);
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(file_in->f_inode, struct fuse_copy_file_range_io,
+ fuse_copy_file_range_initialize,
+ fuse_copy_file_range_backing,
+ fuse_copy_file_range_finalize,
+ file_in, pos_in, file_out, pos_out, len, flags);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fc->no_copy_file_range)
return -EOPNOTSUPP;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 4d8a986..637a30e 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -13,6 +13,9 @@
# define pr_fmt(fmt) "fuse: " fmt
#endif
+#include <linux/android_fuse.h>
+#include <linux/filter.h>
+#include <linux/pagemap.h>
#include <linux/fuse.h>
#include <linux/fs.h>
#include <linux/mount.h>
@@ -31,6 +34,9 @@
#include <linux/pid_namespace.h>
#include <linux/refcount.h>
#include <linux/user_namespace.h>
+#include <linux/statfs.h>
+
+#define FUSE_SUPER_MAGIC 0x65735546
/** Default max number of pages that can be used in a single read request */
#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
@@ -63,11 +69,57 @@ struct fuse_forget_link {
struct fuse_forget_link *next;
};
+/** FUSE specific dentry data */
+#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
+struct fuse_dentry {
+ union {
+ u64 time;
+ struct rcu_head rcu;
+ };
+ struct path backing_path;
+};
+
+static inline struct fuse_dentry *get_fuse_dentry(const struct dentry *entry)
+{
+ return entry->d_fsdata;
+}
+#endif
+
+#ifdef CONFIG_FUSE_BPF
+static inline void get_fuse_backing_path(const struct dentry *d,
+ struct path *path)
+{
+ struct fuse_dentry *di = get_fuse_dentry(d);
+
+ if (!di) {
+ *path = (struct path) {};
+ return;
+ }
+
+ *path = di->backing_path;
+ path_get(path);
+}
+#endif
+
/** FUSE inode */
struct fuse_inode {
/** Inode data */
struct inode inode;
+#ifdef CONFIG_FUSE_BPF
+ /**
+ * Backing inode, if this inode is from a backing file system.
+ * If this is set, nodeid is 0.
+ */
+ struct inode *backing_inode;
+
+ /**
+ * bpf_prog, run on all operations to determine whether to pass through
+ * or handle in place
+ */
+ struct bpf_prog *bpf;
+#endif
+
/** Unique ID, which identifies the inode between userspace
* and kernel */
u64 nodeid;
@@ -232,6 +284,14 @@ struct fuse_file {
/** Container for data related to the passthrough functionality */
struct fuse_passthrough passthrough;
+#ifdef CONFIG_FUSE_BPF
+ /**
+ * TODO: Reconcile with passthrough file
+ * backing file when in bpf mode
+ */
+ struct file *backing_file;
+#endif
+
/** RB node to be linked on fuse_conn->polled_files */
struct rb_node polled_node;
@@ -263,6 +323,7 @@ struct fuse_page_desc {
struct fuse_args {
uint64_t nodeid;
uint32_t opcode;
+ uint32_t error_in;
unsigned short in_numargs;
unsigned short out_numargs;
bool force:1;
@@ -275,8 +336,8 @@ struct fuse_args {
bool page_zeroing:1;
bool page_replace:1;
bool may_block:1;
- struct fuse_in_arg in_args[3];
- struct fuse_arg out_args[2];
+ struct fuse_in_arg in_args[FUSE_MAX_IN_ARGS];
+ struct fuse_arg out_args[FUSE_MAX_OUT_ARGS];
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
/* Path used for completing d_canonical_path */
@@ -526,9 +587,12 @@ struct fuse_fs_context {
bool no_force_umount:1;
bool legacy_opts_show:1;
enum fuse_dax_mode dax_mode;
+ bool no_daemon:1;
unsigned int max_read;
unsigned int blksize;
const char *subtype;
+ struct bpf_prog *root_bpf;
+ struct file *root_dir;
/* DAX device, may be NULL */
struct dax_device *dax_dev;
@@ -805,6 +869,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;
@@ -979,14 +1046,18 @@ extern const struct dentry_operations fuse_dentry_operations;
extern const struct dentry_operations fuse_root_dentry_operations;
/**
- * Get a filled in inode
+ * Get a filled-in inode
*/
+struct inode *fuse_iget_backing(struct super_block *sb,
+ u64 nodeid,
+ struct inode *backing_inode);
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
int generation, struct fuse_attr *attr,
u64 attr_valid, u64 attr_version);
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
- struct fuse_entry_out *outarg, struct inode **inode);
+ struct fuse_entry_out *outarg,
+ struct dentry *entry, struct inode **inode);
/**
* Send FORGET command
@@ -1023,7 +1094,6 @@ struct fuse_io_args {
void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
size_t count, int opcode);
-
/**
* Send OPEN or OPENDIR request
*/
@@ -1095,7 +1165,7 @@ int fuse_dev_init(void);
void fuse_dev_cleanup(void);
int fuse_ctl_init(void);
-void __exit fuse_ctl_cleanup(void);
+void fuse_ctl_cleanup(void);
/**
* Simple request sending that does request allocation and freeing
@@ -1131,6 +1201,7 @@ void fuse_invalidate_entry_cache(struct dentry *entry);
void fuse_invalidate_atime(struct inode *inode);
u64 entry_attr_timeout(struct fuse_entry_out *o);
+void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir);
void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o);
/**
@@ -1344,6 +1415,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir);
/* passthrough.c */
+void fuse_copyattr(struct file *dst_file, struct file *src_file);
int fuse_passthrough_open(struct fuse_dev *fud, u32 lower_fd);
int fuse_passthrough_setup(struct fuse_conn *fc, struct fuse_file *ff,
struct fuse_open_out *openarg);
@@ -1352,4 +1424,640 @@ ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *to);
ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *from);
ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma);
+/* backing.c */
+
+struct bpf_prog *fuse_get_bpf_prog(struct file *file);
+
+/*
+ * Dummy io passed to fuse_bpf_backing when io operation needs no scratch space
+ */
+struct fuse_dummy_io {
+ int unused;
+};
+
+struct fuse_open_io {
+ struct fuse_open_in foi;
+ struct fuse_open_out foo;
+};
+
+int fuse_open_initialize(struct fuse_bpf_args *fa, struct fuse_open_io *foi,
+ struct inode *inode, struct file *file, bool isdir);
+int fuse_open_backing(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file, bool isdir);
+void *fuse_open_finalize(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file, bool isdir);
+
+struct fuse_create_open_io {
+ struct fuse_create_in fci;
+ struct fuse_entry_out feo;
+ struct fuse_open_out foo;
+};
+
+int fuse_create_open_initialize(
+ struct fuse_bpf_args *fa, struct fuse_create_open_io *fcoi,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode);
+int fuse_create_open_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode);
+void *fuse_create_open_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry,
+ struct file *file, unsigned int flags, umode_t mode);
+
+int fuse_mknod_initialize(
+ struct fuse_bpf_args *fa, struct fuse_mknod_in *fmi,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
+int fuse_mknod_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
+void *fuse_mknod_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
+
+int fuse_mkdir_initialize(
+ struct fuse_bpf_args *fa, struct fuse_mkdir_in *fmi,
+ struct inode *dir, struct dentry *entry, umode_t mode);
+int fuse_mkdir_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode);
+void *fuse_mkdir_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, umode_t mode);
+
+int fuse_rmdir_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *fmi,
+ struct inode *dir, struct dentry *entry);
+int fuse_rmdir_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry);
+void *fuse_rmdir_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry);
+
+int fuse_rename2_initialize(struct fuse_bpf_args *fa, struct fuse_rename2_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags);
+int fuse_rename2_backing(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags);
+void *fuse_rename2_finalize(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags);
+
+int fuse_rename_initialize(struct fuse_bpf_args *fa, struct fuse_rename_in *fri,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent);
+int fuse_rename_backing(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent);
+void *fuse_rename_finalize(struct fuse_bpf_args *fa,
+ struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent);
+
+int fuse_unlink_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *fmi,
+ struct inode *dir, struct dentry *entry);
+int fuse_unlink_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry);
+void *fuse_unlink_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry);
+
+int fuse_link_initialize(struct fuse_bpf_args *fa, struct fuse_link_in *fli,
+ struct dentry *entry, struct inode *dir,
+ struct dentry *newent);
+int fuse_link_backing(struct fuse_bpf_args *fa, struct dentry *entry,
+ struct inode *dir, struct dentry *newent);
+void *fuse_link_finalize(struct fuse_bpf_args *fa, struct dentry *entry,
+ struct inode *dir, struct dentry *newent);
+
+int fuse_release_initialize(struct fuse_bpf_args *fa, struct fuse_release_in *fri,
+ struct inode *inode, struct file *file);
+int fuse_releasedir_initialize(struct fuse_bpf_args *fa,
+ struct fuse_release_in *fri,
+ struct inode *inode, struct file *file);
+int fuse_release_backing(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file);
+void *fuse_release_finalize(struct fuse_bpf_args *fa,
+ struct inode *inode, struct file *file);
+
+int fuse_flush_initialize(struct fuse_bpf_args *fa, struct fuse_flush_in *ffi,
+ struct file *file, fl_owner_t id);
+int fuse_flush_backing(struct fuse_bpf_args *fa, struct file *file, fl_owner_t id);
+void *fuse_flush_finalize(struct fuse_bpf_args *fa,
+ struct file *file, fl_owner_t id);
+
+struct fuse_lseek_io {
+ struct fuse_lseek_in fli;
+ struct fuse_lseek_out flo;
+};
+
+int fuse_lseek_initialize(struct fuse_bpf_args *fa, struct fuse_lseek_io *fli,
+ struct file *file, loff_t offset, int whence);
+int fuse_lseek_backing(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence);
+void *fuse_lseek_finalize(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence);
+
+struct fuse_copy_file_range_io {
+ struct fuse_copy_file_range_in fci;
+ struct fuse_write_out fwo;
+};
+
+int fuse_copy_file_range_initialize(struct fuse_bpf_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);
+int fuse_copy_file_range_backing(struct fuse_bpf_args *fa,
+ struct file *file_in, loff_t pos_in,
+ struct file *file_out, loff_t pos_out,
+ size_t len, unsigned int flags);
+void *fuse_copy_file_range_finalize(struct fuse_bpf_args *fa,
+ struct file *file_in, loff_t pos_in,
+ struct file *file_out, loff_t pos_out,
+ size_t len, unsigned int flags);
+
+int fuse_fsync_initialize(struct fuse_bpf_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync);
+int fuse_fsync_backing(struct fuse_bpf_args *fa,
+ struct file *file, loff_t start, loff_t end, int datasync);
+void *fuse_fsync_finalize(struct fuse_bpf_args *fa,
+ struct file *file, loff_t start, loff_t end, int datasync);
+int fuse_dir_fsync_initialize(struct fuse_bpf_args *fa, struct fuse_fsync_in *ffi,
+ struct file *file, loff_t start, loff_t end, int datasync);
+
+struct fuse_getxattr_io {
+ struct fuse_getxattr_in fgi;
+ struct fuse_getxattr_out fgo;
+};
+
+int fuse_getxattr_initialize(
+ struct fuse_bpf_args *fa, struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size);
+int fuse_getxattr_backing(
+ struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size);
+void *fuse_getxattr_finalize(
+ struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name, void *value,
+ size_t size);
+
+int fuse_listxattr_initialize(struct fuse_bpf_args *fa,
+ struct fuse_getxattr_io *fgio,
+ struct dentry *dentry, char *list, size_t size);
+int fuse_listxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
+ char *list, size_t size);
+void *fuse_listxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
+ char *list, size_t size);
+
+int fuse_setxattr_initialize(struct fuse_bpf_args *fa,
+ struct fuse_setxattr_in *fsxi,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags);
+int fuse_setxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags);
+void *fuse_setxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
+ const char *name, const void *value, size_t size,
+ int flags);
+
+int fuse_removexattr_initialize(struct fuse_bpf_args *fa,
+ struct fuse_dummy_io *unused,
+ struct dentry *dentry, const char *name);
+int fuse_removexattr_backing(struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name);
+void *fuse_removexattr_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, const char *name);
+
+struct fuse_read_iter_out {
+ uint64_t ret;
+};
+struct fuse_file_read_iter_io {
+ struct fuse_read_in fri;
+ struct fuse_read_iter_out frio;
+};
+
+int fuse_file_read_iter_initialize(
+ struct fuse_bpf_args *fa, struct fuse_file_read_iter_io *fri,
+ struct kiocb *iocb, struct iov_iter *to);
+int fuse_file_read_iter_backing(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *to);
+void *fuse_file_read_iter_finalize(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *to);
+
+struct fuse_write_iter_out {
+ uint64_t ret;
+};
+struct fuse_file_write_iter_io {
+ struct fuse_write_in fwi;
+ struct fuse_write_out fwo;
+ struct fuse_write_iter_out fwio;
+};
+
+int fuse_file_write_iter_initialize(
+ struct fuse_bpf_args *fa, struct fuse_file_write_iter_io *fwio,
+ struct kiocb *iocb, struct iov_iter *from);
+int fuse_file_write_iter_backing(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *from);
+void *fuse_file_write_iter_finalize(struct fuse_bpf_args *fa,
+ struct kiocb *iocb, struct iov_iter *from);
+
+ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma);
+
+int fuse_file_fallocate_initialize(struct fuse_bpf_args *fa,
+ struct fuse_fallocate_in *ffi,
+ struct file *file, int mode, loff_t offset, loff_t length);
+int fuse_file_fallocate_backing(struct fuse_bpf_args *fa,
+ struct file *file, int mode, loff_t offset, loff_t length);
+void *fuse_file_fallocate_finalize(struct fuse_bpf_args *fa,
+ struct file *file, int mode, loff_t offset, loff_t length);
+
+struct fuse_lookup_io {
+ struct fuse_entry_out feo;
+ struct fuse_entry_bpf feb;
+};
+
+int fuse_handle_backing(struct fuse_entry_bpf *feb, struct inode **backing_inode,
+ struct path *backing_path);
+int fuse_handle_bpf_prog(struct fuse_entry_bpf *feb, struct inode *parent,
+ struct bpf_prog **bpf);
+
+int fuse_lookup_initialize(struct fuse_bpf_args *fa, struct fuse_lookup_io *feo,
+ struct inode *dir, struct dentry *entry, unsigned int flags);
+int fuse_lookup_backing(struct fuse_bpf_args *fa, struct inode *dir,
+ struct dentry *entry, unsigned int flags);
+struct dentry *fuse_lookup_finalize(struct fuse_bpf_args *fa, struct inode *dir,
+ struct dentry *entry, unsigned int flags);
+int fuse_revalidate_backing(struct dentry *entry, unsigned int flags);
+
+int fuse_canonical_path_initialize(struct fuse_bpf_args *fa,
+ struct fuse_dummy_io *fdi,
+ const struct path *path,
+ struct path *canonical_path);
+int fuse_canonical_path_backing(struct fuse_bpf_args *fa, const struct path *path,
+ struct path *canonical_path);
+void *fuse_canonical_path_finalize(struct fuse_bpf_args *fa,
+ const struct path *path,
+ struct path *canonical_path);
+
+struct fuse_getattr_io {
+ struct fuse_getattr_in fgi;
+ struct fuse_attr_out fao;
+};
+int fuse_getattr_initialize(struct fuse_bpf_args *fa, struct fuse_getattr_io *fgio,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags);
+int fuse_getattr_backing(struct fuse_bpf_args *fa,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags);
+void *fuse_getattr_finalize(struct fuse_bpf_args *fa,
+ const struct dentry *entry, struct kstat *stat,
+ u32 request_mask, unsigned int flags);
+
+struct fuse_setattr_io {
+ struct fuse_setattr_in fsi;
+ struct fuse_attr_out fao;
+};
+
+int fuse_setattr_initialize(struct fuse_bpf_args *fa, struct fuse_setattr_io *fsi,
+ struct dentry *dentry, struct iattr *attr, struct file *file);
+int fuse_setattr_backing(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct iattr *attr, struct file *file);
+void *fuse_setattr_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct iattr *attr, struct file *file);
+
+int fuse_statfs_initialize(struct fuse_bpf_args *fa, struct fuse_statfs_out *fso,
+ struct dentry *dentry, struct kstatfs *buf);
+int fuse_statfs_backing(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct kstatfs *buf);
+void *fuse_statfs_finalize(struct fuse_bpf_args *fa,
+ struct dentry *dentry, struct kstatfs *buf);
+
+int fuse_get_link_initialize(struct fuse_bpf_args *fa, struct fuse_dummy_io *dummy,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out);
+int fuse_get_link_backing(struct fuse_bpf_args *fa,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out);
+void *fuse_get_link_finalize(struct fuse_bpf_args *fa,
+ struct inode *inode, struct dentry *dentry,
+ struct delayed_call *callback, const char **out);
+
+int fuse_symlink_initialize(
+ struct fuse_bpf_args *fa, struct fuse_dummy_io *unused,
+ struct inode *dir, struct dentry *entry, const char *link, int len);
+int fuse_symlink_backing(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, const char *link, int len);
+void *fuse_symlink_finalize(
+ struct fuse_bpf_args *fa,
+ struct inode *dir, struct dentry *entry, const char *link, int len);
+
+struct fuse_read_io {
+ struct fuse_read_in fri;
+ struct fuse_read_out fro;
+};
+
+int fuse_readdir_initialize(struct fuse_bpf_args *fa, struct fuse_read_io *frio,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued);
+int fuse_readdir_backing(struct fuse_bpf_args *fa,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued);
+void *fuse_readdir_finalize(struct fuse_bpf_args *fa,
+ struct file *file, struct dir_context *ctx,
+ bool *force_again, bool *allow_force, bool is_continued);
+
+int fuse_access_initialize(struct fuse_bpf_args *fa, struct fuse_access_in *fai,
+ struct inode *inode, int mask);
+int fuse_access_backing(struct fuse_bpf_args *fa, struct inode *inode, int mask);
+void *fuse_access_finalize(struct fuse_bpf_args *fa, struct inode *inode, int mask);
+
+/*
+ * 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);
+}
+
+static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtime)
+{
+ /* Always update if mtime is explicitly set */
+ if (ivalid & ATTR_MTIME_SET)
+ return true;
+
+ /* Or if kernel i_mtime is the official one */
+ if (trust_local_mtime)
+ return true;
+
+ /* If it's an open(O_TRUNC) or an ftruncate(), don't update */
+ if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
+ return false;
+
+ /* In all other cases update */
+ return true;
+}
+
+void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
+ struct kstat *stat);
+
+static inline void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr,
+ struct fuse_setattr_in *arg, bool trust_local_cmtime)
+{
+ unsigned int ivalid = iattr->ia_valid;
+
+ if (ivalid & ATTR_MODE)
+ arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode;
+ if (ivalid & ATTR_UID)
+ arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid);
+ if (ivalid & ATTR_GID)
+ arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid);
+ if (ivalid & ATTR_SIZE)
+ arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size;
+ if (ivalid & ATTR_ATIME) {
+ arg->valid |= FATTR_ATIME;
+ arg->atime = iattr->ia_atime.tv_sec;
+ arg->atimensec = iattr->ia_atime.tv_nsec;
+ if (!(ivalid & ATTR_ATIME_SET))
+ arg->valid |= FATTR_ATIME_NOW;
+ }
+ if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) {
+ arg->valid |= FATTR_MTIME;
+ arg->mtime = iattr->ia_mtime.tv_sec;
+ arg->mtimensec = iattr->ia_mtime.tv_nsec;
+ if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime)
+ arg->valid |= FATTR_MTIME_NOW;
+ }
+ if ((ivalid & ATTR_CTIME) && trust_local_cmtime) {
+ arg->valid |= FATTR_CTIME;
+ arg->ctime = iattr->ia_ctime.tv_sec;
+ arg->ctimensec = iattr->ia_ctime.tv_nsec;
+ }
+}
+
+static inline int finalize_attr(struct inode *inode, struct fuse_attr_out *outarg,
+ u64 attr_version, struct kstat *stat)
+{
+ int err = 0;
+
+ if (fuse_invalid_attr(&outarg->attr) ||
+ ((inode->i_mode ^ outarg->attr.mode) & S_IFMT)) {
+ fuse_make_bad(inode);
+ err = -EIO;
+ } else {
+ fuse_change_attributes(inode, &outarg->attr,
+ attr_timeout(outarg),
+ attr_version);
+ if (stat)
+ fuse_fillattr(inode, &outarg->attr, stat);
+ }
+ return err;
+}
+
+static inline void convert_statfs_to_fuse(struct fuse_kstatfs *attr, struct kstatfs *stbuf)
+{
+ attr->bsize = stbuf->f_bsize;
+ attr->frsize = stbuf->f_frsize;
+ attr->blocks = stbuf->f_blocks;
+ attr->bfree = stbuf->f_bfree;
+ attr->bavail = stbuf->f_bavail;
+ attr->files = stbuf->f_files;
+ attr->ffree = stbuf->f_ffree;
+ attr->namelen = stbuf->f_namelen;
+ /* fsid is left zero */
+}
+
+static inline void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
+{
+ stbuf->f_type = FUSE_SUPER_MAGIC;
+ stbuf->f_bsize = attr->bsize;
+ stbuf->f_frsize = attr->frsize;
+ stbuf->f_blocks = attr->blocks;
+ stbuf->f_bfree = attr->bfree;
+ stbuf->f_bavail = attr->bavail;
+ stbuf->f_files = attr->files;
+ stbuf->f_ffree = attr->ffree;
+ stbuf->f_namelen = attr->namelen;
+ /* fsid is left zero */
+}
+
+#ifdef CONFIG_FUSE_BPF
+struct fuse_err_ret {
+ void *result;
+ bool ret;
+};
+
+int __init fuse_bpf_init(void);
+void __exit fuse_bpf_cleanup(void);
+
+ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct fuse_bpf_args *args);
+
+/*
+ * expression statement to wrap the backing filter logic
+ * struct inode *inode: inode with bpf and backing inode
+ * typedef io: (typically complex) type whose components fuse_args can point to.
+ * An instance of this type is created locally and passed to initialize
+ * void initialize(struct fuse_bpf_args *fa, io *in_out, args...): function that sets
+ * up fa and io based on args
+ * int backing(struct fuse_bpf_args *fa, args...): function that actually performs
+ * the backing io operation
+ * void *finalize(struct fuse_bpf_args *, args...): function that performs any final
+ * work needed to commit the backing io
+ */
+#define fuse_bpf_backing(inode, io, initialize, backing, finalize, \
+ args...) \
+({ \
+ struct fuse_err_ret fer = {0}; \
+ int ext_flags; \
+ struct fuse_inode *fuse_inode = get_fuse_inode(inode); \
+ struct fuse_mount *fm = get_fuse_mount(inode); \
+ io feo = {0}; \
+ struct fuse_bpf_args fa = {0}, fa_backup = {0}; \
+ bool locked; \
+ ssize_t res; \
+ void *err; \
+ int i; \
+ bool initialized = false; \
+ \
+ do { \
+ if (!fuse_inode || !fuse_inode->backing_inode) \
+ break; \
+ \
+ err = ERR_PTR(initialize(&fa, &feo, args)); \
+ if (err) { \
+ fer = (struct fuse_err_ret) { \
+ err, \
+ true, \
+ }; \
+ break; \
+ } \
+ initialized = true; \
+ \
+ fa_backup = fa; \
+ fa.opcode |= FUSE_PREFILTER; \
+ for (i = 0; i < fa.in_numargs; ++i) \
+ fa.out_args[i] = (struct fuse_bpf_arg) { \
+ .size = fa.in_args[i].size, \
+ .value = (void *)fa.in_args[i].value, \
+ }; \
+ fa.out_numargs = fa.in_numargs; \
+ \
+ ext_flags = fuse_inode->bpf ? \
+ bpf_prog_run(fuse_inode->bpf, &fa) : \
+ FUSE_BPF_BACKING; \
+ if (ext_flags < 0) { \
+ fer = (struct fuse_err_ret) { \
+ ERR_PTR(ext_flags), \
+ true, \
+ }; \
+ break; \
+ } \
+ \
+ if (ext_flags & FUSE_BPF_USER_FILTER) { \
+ locked = fuse_lock_inode(inode); \
+ res = fuse_bpf_simple_request(fm, &fa); \
+ fuse_unlock_inode(inode, locked); \
+ if (res < 0) { \
+ fer = (struct fuse_err_ret) { \
+ ERR_PTR(res), \
+ true, \
+ }; \
+ break; \
+ } \
+ } \
+ \
+ if (!(ext_flags & FUSE_BPF_BACKING)) \
+ break; \
+ \
+ fa.opcode &= ~FUSE_PREFILTER; \
+ for (i = 0; i < fa.in_numargs; ++i) \
+ fa.in_args[i] = (struct fuse_bpf_in_arg) { \
+ .size = fa.out_args[i].size, \
+ .value = fa.out_args[i].value, \
+ }; \
+ for (i = 0; i < fa_backup.out_numargs; ++i) \
+ fa.out_args[i] = (struct fuse_bpf_arg) { \
+ .size = fa_backup.out_args[i].size, \
+ .value = fa_backup.out_args[i].value, \
+ }; \
+ fa.out_numargs = fa_backup.out_numargs; \
+ \
+ fer = (struct fuse_err_ret) { \
+ ERR_PTR(backing(&fa, args)), \
+ true, \
+ }; \
+ if (IS_ERR(fer.result)) \
+ fa.error_in = PTR_ERR(fer.result); \
+ if (!(ext_flags & FUSE_BPF_POST_FILTER)) \
+ break; \
+ \
+ fa.opcode |= FUSE_POSTFILTER; \
+ for (i = 0; i < fa.out_numargs; ++i) \
+ fa.in_args[fa.in_numargs++] = \
+ (struct fuse_bpf_in_arg) { \
+ .size = fa.out_args[i].size, \
+ .value = fa.out_args[i].value, \
+ }; \
+ ext_flags = bpf_prog_run(fuse_inode->bpf, &fa); \
+ if (ext_flags < 0) { \
+ fer = (struct fuse_err_ret) { \
+ ERR_PTR(ext_flags), \
+ true, \
+ }; \
+ break; \
+ } \
+ if (!(ext_flags & FUSE_BPF_USER_FILTER)) \
+ break; \
+ \
+ fa.out_args[0].size = fa_backup.out_args[0].size; \
+ fa.out_args[1].size = fa_backup.out_args[1].size; \
+ fa.out_numargs = fa_backup.out_numargs; \
+ locked = fuse_lock_inode(inode); \
+ res = fuse_bpf_simple_request(fm, &fa); \
+ fuse_unlock_inode(inode, locked); \
+ if (res < 0) { \
+ fer.result = ERR_PTR(res); \
+ break; \
+ } \
+ } while (false); \
+ \
+ if (initialized && fer.ret) { \
+ err = finalize(&fa, args); \
+ if (err) \
+ fer.result = err; \
+ } \
+ \
+ fer; \
+})
+
+struct bpf_prog *fuse_get_bpf_prog(struct file *file);
+#endif /* CONFIG_FUSE_BPF */
+
#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 632f94b..6ccb7fc 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -78,6 +78,10 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
fi->i_time = 0;
fi->inval_mask = 0;
+#ifdef CONFIG_FUSE_BPF
+ fi->backing_inode = NULL;
+ fi->bpf = NULL;
+#endif
fi->nodeid = 0;
fi->nlookup = 0;
fi->attr_version = 0;
@@ -120,6 +124,12 @@ static void fuse_evict_inode(struct inode *inode)
/* Will write inode on close/munmap and in all other dirtiers */
WARN_ON(inode->i_state & I_DIRTY_INODE);
+#ifdef CONFIG_FUSE_BPF
+ iput(fi->backing_inode);
+ if (fi->bpf)
+ bpf_prog_put(fi->bpf);
+ fi->bpf = NULL;
+#endif
truncate_inode_pages_final(&inode->i_data);
clear_inode(inode);
if (inode->i_sb->s_flags & SB_ACTIVE) {
@@ -162,6 +172,28 @@ static ino_t fuse_squash_ino(u64 ino64)
return ino;
}
+static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
+ const struct inode *inode)
+{
+ *attr = (struct fuse_attr){
+ .ino = inode->i_ino,
+ .size = inode->i_size,
+ .blocks = inode->i_blocks,
+ .atime = inode->i_atime.tv_sec,
+ .mtime = inode->i_mtime.tv_sec,
+ .ctime = inode->i_ctime.tv_sec,
+ .atimensec = inode->i_atime.tv_nsec,
+ .mtimensec = inode->i_mtime.tv_nsec,
+ .ctimensec = inode->i_ctime.tv_nsec,
+ .mode = inode->i_mode,
+ .nlink = inode->i_nlink,
+ .uid = inode->i_uid.val,
+ .gid = inode->i_gid.val,
+ .rdev = inode->i_rdev,
+ .blksize = 1u << inode->i_blkbits,
+ };
+}
+
void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
u64 attr_valid, u32 cache_mask)
{
@@ -329,28 +361,104 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
fuse_init_common(inode);
- init_special_inode(inode, inode->i_mode,
- new_decode_dev(attr->rdev));
+ init_special_inode(inode, inode->i_mode, attr->rdev);
} else
BUG();
}
+struct fuse_inode_identifier {
+ u64 nodeid;
+ struct inode *backing_inode;
+};
+
static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
{
- u64 nodeid = *(u64 *) _nodeidp;
- if (get_node_id(inode) == nodeid)
- return 1;
- else
- return 0;
+ struct fuse_inode_identifier *fii =
+ (struct fuse_inode_identifier *) _nodeidp;
+ struct fuse_inode *fi = get_fuse_inode(inode);
+
+ return fii->nodeid == fi->nodeid;
+}
+
+static int fuse_inode_backing_eq(struct inode *inode, void *_nodeidp)
+{
+ struct fuse_inode_identifier *fii =
+ (struct fuse_inode_identifier *) _nodeidp;
+ struct fuse_inode *fi = get_fuse_inode(inode);
+
+ return fii->nodeid == fi->nodeid
+#ifdef CONFIG_FUSE_BPF
+ && fii->backing_inode == fi->backing_inode
+#endif
+ ;
}
static int fuse_inode_set(struct inode *inode, void *_nodeidp)
{
- u64 nodeid = *(u64 *) _nodeidp;
- get_fuse_inode(inode)->nodeid = nodeid;
+ struct fuse_inode_identifier *fii =
+ (struct fuse_inode_identifier *) _nodeidp;
+ struct fuse_inode *fi = get_fuse_inode(inode);
+
+ fi->nodeid = fii->nodeid;
+
return 0;
}
+static int fuse_inode_backing_set(struct inode *inode, void *_nodeidp)
+{
+ struct fuse_inode_identifier *fii =
+ (struct fuse_inode_identifier *) _nodeidp;
+ struct fuse_inode *fi = get_fuse_inode(inode);
+
+ fi->nodeid = fii->nodeid;
+#ifdef CONFIG_FUSE_BPF
+ fi->backing_inode = fii->backing_inode;
+ if (fi->backing_inode)
+ ihold(fi->backing_inode);
+#endif
+
+ return 0;
+}
+
+struct inode *fuse_iget_backing(struct super_block *sb, u64 nodeid,
+ struct inode *backing_inode)
+{
+ struct inode *inode;
+ struct fuse_inode *fi;
+ struct fuse_conn *fc = get_fuse_conn_super(sb);
+ struct fuse_inode_identifier fii = {
+ .nodeid = nodeid,
+ .backing_inode = backing_inode,
+ };
+ struct fuse_attr attr;
+ unsigned long hash = (unsigned long) backing_inode;
+
+ if (nodeid)
+ hash = nodeid;
+
+ fuse_fill_attr_from_inode(&attr, backing_inode);
+ inode = iget5_locked(sb, hash, fuse_inode_backing_eq,
+ fuse_inode_backing_set, &fii);
+ if (!inode)
+ return NULL;
+
+ if ((inode->i_state & I_NEW)) {
+ inode->i_flags |= S_NOATIME;
+ if (!fc->writeback_cache)
+ inode->i_flags |= S_NOCMTIME;
+ fuse_init_common(inode);
+ unlock_new_inode(inode);
+ }
+
+ fi = get_fuse_inode(inode);
+ fuse_init_inode(inode, &attr);
+ spin_lock(&fi->lock);
+ fi->nlookup++;
+ spin_unlock(&fi->lock);
+
+ return inode;
+}
+
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
int generation, struct fuse_attr *attr,
u64 attr_valid, u64 attr_version)
@@ -358,6 +466,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
struct inode *inode;
struct fuse_inode *fi;
struct fuse_conn *fc = get_fuse_conn_super(sb);
+ struct fuse_inode_identifier fii = {
+ .nodeid = nodeid,
+ };
/*
* Auto mount points get their node id from the submount root, which is
@@ -379,7 +490,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
}
retry:
- inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
+ inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &fii);
if (!inode)
return NULL;
@@ -411,13 +522,16 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 nodeid,
{
struct fuse_mount *fm_iter;
struct inode *inode;
+ struct fuse_inode_identifier fii = {
+ .nodeid = nodeid,
+ };
WARN_ON(!rwsem_is_locked(&fc->killsb));
list_for_each_entry(fm_iter, &fc->mounts, fc_entry) {
if (!fm_iter->sb)
continue;
- inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &nodeid);
+ inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &fii);
if (inode) {
if (fm)
*fm = fm_iter;
@@ -504,20 +618,6 @@ static void fuse_send_destroy(struct fuse_mount *fm)
}
}
-static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
-{
- stbuf->f_type = FUSE_SUPER_MAGIC;
- stbuf->f_bsize = attr->bsize;
- stbuf->f_frsize = attr->frsize;
- stbuf->f_blocks = attr->blocks;
- stbuf->f_bfree = attr->bfree;
- stbuf->f_bavail = attr->bavail;
- stbuf->f_files = attr->files;
- stbuf->f_ffree = attr->ffree;
- stbuf->f_namelen = attr->namelen;
- /* fsid is left zero */
-}
-
static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct super_block *sb = dentry->d_sb;
@@ -525,12 +625,24 @@ static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
FUSE_ARGS(args);
struct fuse_statfs_out outarg;
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+#endif
if (!fuse_allow_current_process(fm->fc)) {
buf->f_type = FUSE_SUPER_MAGIC;
return 0;
}
+#ifdef CONFIG_FUSE_BPF
+ fer = fuse_bpf_backing(dentry->d_inode, struct fuse_statfs_out,
+ fuse_statfs_initialize, fuse_statfs_backing,
+ fuse_statfs_finalize,
+ dentry, buf);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
memset(&outarg, 0, sizeof(outarg));
args.in_numargs = 0;
args.opcode = FUSE_STATFS;
@@ -647,6 +759,9 @@ enum {
OPT_ALLOW_OTHER,
OPT_MAX_READ,
OPT_BLKSIZE,
+ OPT_ROOT_BPF,
+ OPT_ROOT_DIR,
+ OPT_NO_DAEMON,
OPT_ERR
};
@@ -661,6 +776,9 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
fsparam_u32 ("max_read", OPT_MAX_READ),
fsparam_u32 ("blksize", OPT_BLKSIZE),
fsparam_string ("subtype", OPT_SUBTYPE),
+ fsparam_u32 ("root_bpf", OPT_ROOT_BPF),
+ fsparam_u32 ("root_dir", OPT_ROOT_DIR),
+ fsparam_flag ("no_daemon", OPT_NO_DAEMON),
{}
};
@@ -744,6 +862,26 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
ctx->blksize = result.uint_32;
break;
+ case OPT_ROOT_BPF:
+ ctx->root_bpf = bpf_prog_get_type_dev(result.uint_32,
+ BPF_PROG_TYPE_FUSE, false);
+ if (IS_ERR(ctx->root_bpf)) {
+ ctx->root_bpf = NULL;
+ return invalfc(fsc, "Unable to open bpf program");
+ }
+ break;
+
+ case OPT_ROOT_DIR:
+ ctx->root_dir = fget(result.uint_32);
+ if (!ctx->root_dir)
+ 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;
}
@@ -756,6 +894,10 @@ static void fuse_free_fsc(struct fs_context *fsc)
struct fuse_fs_context *ctx = fsc->fs_private;
if (ctx) {
+ if (ctx->root_dir)
+ fput(ctx->root_dir);
+ if (ctx->root_bpf)
+ bpf_prog_put(ctx->root_bpf);
kfree(ctx->subtype);
kfree(ctx);
}
@@ -885,15 +1027,34 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)
}
EXPORT_SYMBOL_GPL(fuse_conn_get);
-static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode)
+static struct inode *fuse_get_root_inode(struct super_block *sb,
+ unsigned int mode,
+ struct bpf_prog *root_bpf,
+ struct file *backing_fd)
{
struct fuse_attr attr;
- memset(&attr, 0, sizeof(attr));
+ struct inode *inode;
+ memset(&attr, 0, sizeof(attr));
attr.mode = mode;
attr.ino = FUSE_ROOT_ID;
attr.nlink = 1;
- return fuse_iget(sb, 1, 0, &attr, 0, 0);
+ inode = fuse_iget(sb, 1, 0, &attr, 0, 0);
+ if (!inode)
+ return NULL;
+
+#ifdef CONFIG_FUSE_BPF
+ get_fuse_inode(inode)->bpf = root_bpf;
+ if (root_bpf)
+ bpf_prog_inc(root_bpf);
+
+ if (backing_fd) {
+ get_fuse_inode(inode)->backing_inode = backing_fd->f_inode;
+ ihold(backing_fd->f_inode);
+ }
+#endif
+
+ return inode;
}
struct fuse_inode_handle {
@@ -908,11 +1069,14 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
struct inode *inode;
struct dentry *entry;
int err = -ESTALE;
+ struct fuse_inode_identifier fii = {
+ .nodeid = handle->nodeid,
+ };
if (handle->nodeid == 0)
goto out_err;
- inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid);
+ inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &fii);
if (!inode) {
struct fuse_entry_out outarg;
const struct qstr name = QSTR_INIT(".", 1);
@@ -921,7 +1085,7 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
goto out_err;
err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg,
- &inode);
+ NULL, &inode);
if (err && err != -ENOENT)
goto out_err;
if (err || !inode) {
@@ -1015,13 +1179,14 @@ static struct dentry *fuse_get_parent(struct dentry *child)
struct inode *inode;
struct dentry *parent;
struct fuse_entry_out outarg;
+ const struct qstr name = QSTR_INIT("..", 2);
int err;
if (!fc->export_support)
return ERR_PTR(-ESTALE);
err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
- &dotdot_name, &outarg, &inode);
+ &name, &outarg, NULL, &inode);
if (err) {
if (err == -ENOENT)
return ERR_PTR(-ESTALE);
@@ -1284,7 +1449,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);
@@ -1408,28 +1573,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;
@@ -1473,7 +1616,7 @@ static int fuse_fill_super_submount(struct super_block *sb,
if (parent_sb->s_subtype && !sb->s_subtype)
return -ENOMEM;
- fuse_fill_attr_from_inode(&root_attr, parent_fi);
+ fuse_fill_attr_from_inode(&root_attr, &parent_fi->inode);
root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0);
/*
* This inode is just a duplicate, so it is not looked up and
@@ -1600,13 +1743,16 @@ 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);
+ root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_bpf,
+ ctx->root_dir);
sb->s_d_op = &fuse_root_dentry_operations;
root_dentry = d_make_root(root);
if (!root_dentry)
goto err_dev_free;
+ fuse_init_dentry_root(root_dentry, ctx->root_dir);
/* Root dentry doesn't have .d_revalidate */
sb->s_d_op = &fuse_dentry_operations;
@@ -1645,18 +1791,20 @@ 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 ||
- !ctx->user_id_present || !ctx->group_id_present)
- return -EINVAL;
+ if (!ctx->no_daemon) {
+ if (!ctx->file || !ctx->rootmode_present ||
+ !ctx->user_id_present || !ctx->group_id_present)
+ return -EINVAL;
- /*
- * 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;
+ /*
+ * 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;
+ }
err = fuse_fill_super_common(sb, ctx);
if (err)
@@ -1937,6 +2085,26 @@ static void fuse_fs_cleanup(void)
static struct kobject *fuse_kobj;
+/* TODO Remove this once BPF_PROG_TYPE_FUSE is upstreamed */
+static ssize_t bpf_prog_type_fuse_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buff)
+{
+ return sysfs_emit(buff, "%d\n", BPF_PROG_TYPE_FUSE);
+}
+
+static struct kobj_attribute bpf_prog_type_fuse_attr =
+ __ATTR_RO(bpf_prog_type_fuse);
+
+static struct attribute *bpf_attributes[] = {
+ &bpf_prog_type_fuse_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group bpf_attr_group = {
+ .attrs = bpf_attributes,
+};
+/* TODO remove to here */
+
static int fuse_sysfs_init(void)
{
int err;
@@ -1951,8 +2119,15 @@ static int fuse_sysfs_init(void)
if (err)
goto out_fuse_unregister;
+ /* TODO Remove when BPF_PROG_TYPE_FUSE is upstreamed */
+ err = sysfs_create_group(fuse_kobj, &bpf_attr_group);
+ if (err)
+ goto out_fuse_remove_mount_point;
+
return 0;
+ out_fuse_remove_mount_point:
+ sysfs_remove_mount_point(fuse_kobj, "connections");
out_fuse_unregister:
kobject_put(fuse_kobj);
out_err:
@@ -1989,11 +2164,21 @@ static int __init fuse_init(void)
if (res)
goto err_sysfs_cleanup;
+#ifdef CONFIG_FUSE_BPF
+ res = fuse_bpf_init();
+ if (res)
+ goto err_ctl_cleanup;
+#endif
+
sanitize_global_limit(&max_user_bgreq);
sanitize_global_limit(&max_user_congthresh);
return 0;
+#ifdef CONFIG_FUSE_BPF
+ err_ctl_cleanup:
+ fuse_ctl_cleanup();
+#endif
err_sysfs_cleanup:
fuse_sysfs_cleanup();
err_dev_cleanup:
@@ -2011,6 +2196,9 @@ static void __exit fuse_exit(void)
fuse_ctl_cleanup();
fuse_sysfs_cleanup();
fuse_fs_cleanup();
+#ifdef CONFIG_FUSE_BPF
+ fuse_bpf_cleanup();
+#endif
fuse_dev_cleanup();
}
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 630fa28..c0ae306 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -35,7 +35,7 @@ static void fuse_file_accessed(struct file *dst_file, struct file *src_file)
touch_atime(&dst_file->f_path);
}
-static void fuse_copyattr(struct file *dst_file, struct file *src_file)
+void fuse_copyattr(struct file *dst_file, struct file *src_file)
{
struct inode *dst = file_inode(dst_file);
struct inode *src = file_inode(src_file);
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index e8deaac..4d97cdd 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -20,6 +20,8 @@ static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
if (!fc->do_readdirplus)
return false;
+ if (fi->nodeid == 0)
+ return false;
if (!fc->readdirplus_auto)
return true;
if (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state))
@@ -579,6 +581,26 @@ int fuse_readdir(struct file *file, struct dir_context *ctx)
struct inode *inode = file_inode(file);
int err;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+ bool allow_force;
+ bool force_again = false;
+ bool is_continued = false;
+
+again:
+ fer = fuse_bpf_backing(inode, struct fuse_read_io,
+ fuse_readdir_initialize, fuse_readdir_backing,
+ fuse_readdir_finalize,
+ file, ctx, &force_again, &allow_force, is_continued);
+ if (force_again && !IS_ERR(fer.result)) {
+ is_continued = true;
+ goto again;
+ }
+
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
index 0d3e717..3029d1e 100644
--- a/fs/fuse/xattr.c
+++ b/fs/fuse/xattr.c
@@ -115,6 +115,17 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
struct fuse_getxattr_out outarg;
ssize_t ret;
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_getxattr_io,
+ fuse_listxattr_initialize,
+ fuse_listxattr_backing, fuse_listxattr_finalize,
+ entry, list, size);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
@@ -182,6 +193,17 @@ static int fuse_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, void *value, size_t size)
{
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ fer = fuse_bpf_backing(inode, struct fuse_getxattr_io,
+ fuse_getxattr_initialize, fuse_getxattr_backing,
+ fuse_getxattr_finalize,
+ dentry, name, value, size);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
@@ -194,6 +216,24 @@ static int fuse_xattr_set(const struct xattr_handler *handler,
const char *name, const void *value, size_t size,
int flags)
{
+#ifdef CONFIG_FUSE_BPF
+ struct fuse_err_ret fer;
+
+ if (value)
+ fer = fuse_bpf_backing(inode, struct fuse_setxattr_in,
+ fuse_setxattr_initialize, fuse_setxattr_backing,
+ fuse_setxattr_finalize, dentry, name, value,
+ size, flags);
+ else
+ fer = fuse_bpf_backing(inode, struct fuse_dummy_io,
+ fuse_removexattr_initialize,
+ fuse_removexattr_backing,
+ fuse_removexattr_finalize,
+ dentry, name);
+ if (fer.ret)
+ return PTR_ERR(fer.result);
+#endif
+
if (fuse_is_bad(inode))
return -EIO;
diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h
index 2c6a4f25..1c61204 100644
--- a/include/linux/bpf_types.h
+++ b/include/linux/bpf_types.h
@@ -79,6 +79,9 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LSM, lsm,
#endif
BPF_PROG_TYPE(BPF_PROG_TYPE_SYSCALL, bpf_syscall,
void *, void *)
+#ifdef CONFIG_FUSE_BPF
+BPF_PROG_TYPE(BPF_PROG_TYPE_FUSE, fuse, struct fuse_bpf_args, struct fuse_bpf_args)
+#endif
BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
diff --git a/include/uapi/linux/android_fuse.h b/include/uapi/linux/android_fuse.h
new file mode 100644
index 0000000..630b752
--- /dev/null
+++ b/include/uapi/linux/android_fuse.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause WITH Linux-syscall-note */
+/* Copyright (c) 2022 Google LLC */
+
+#ifndef _LINUX_ANDROID_FUSE_H
+#define _LINUX_ANDROID_FUSE_H
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#else
+#include <stdint.h>
+#endif
+
+#define FUSE_ACTION_KEEP 0
+#define FUSE_ACTION_REMOVE 1
+#define FUSE_ACTION_REPLACE 2
+
+struct fuse_entry_bpf_out {
+ uint64_t backing_action;
+ uint64_t backing_fd;
+ uint64_t bpf_action;
+ uint64_t bpf_fd;
+};
+
+struct fuse_entry_bpf {
+ struct fuse_entry_bpf_out out;
+ struct file *backing_file;
+ struct file *bpf_file;
+};
+
+struct fuse_read_out {
+ uint64_t offset;
+ uint32_t again;
+ uint32_t padding;
+};
+
+struct fuse_in_postfilter_header {
+ uint32_t len;
+ uint32_t opcode;
+ uint64_t unique;
+ uint64_t nodeid;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t pid;
+ uint32_t error_in;
+};
+
+/*
+ * Fuse BPF Args
+ *
+ * Used to communicate with bpf programs to allow checking or altering certain values.
+ * The end_offset allows the bpf verifier to check boundaries statically. This reflects
+ * the ends of the buffer. size shows the length that was actually used.
+ *
+ */
+
+/** One input argument of a request */
+struct fuse_bpf_in_arg {
+ uint32_t size;
+ const void *value;
+ const void *end_offset;
+};
+
+/** One output argument of a request */
+struct fuse_bpf_arg {
+ uint32_t size;
+ void *value;
+ void *end_offset;
+};
+
+#define FUSE_MAX_IN_ARGS 5
+#define FUSE_MAX_OUT_ARGS 3
+
+#define FUSE_BPF_FORCE (1 << 0)
+#define FUSE_BPF_OUT_ARGVAR (1 << 6)
+
+struct fuse_bpf_args {
+ uint64_t nodeid;
+ uint32_t opcode;
+ uint32_t error_in;
+ uint32_t in_numargs;
+ uint32_t out_numargs;
+ uint32_t flags;
+ struct fuse_bpf_in_arg in_args[FUSE_MAX_IN_ARGS];
+ struct fuse_bpf_arg out_args[FUSE_MAX_OUT_ARGS];
+};
+
+#define FUSE_BPF_USER_FILTER 1
+#define FUSE_BPF_BACKING 2
+#define FUSE_BPF_POST_FILTER 4
+
+#define FUSE_OPCODE_FILTER 0x0ffff
+#define FUSE_PREFILTER 0x10000
+#define FUSE_POSTFILTER 0x20000
+
+#endif // _LINUX_ANDROID_FUSE_H
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 51b9aa6..a8b5bf3 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -978,6 +978,16 @@ enum bpf_prog_type {
BPF_PROG_TYPE_LSM,
BPF_PROG_TYPE_SK_LOOKUP,
BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
+
+ /*
+ * Until fuse-bpf is upstreamed, this value must be at the end to allow for
+ * other recently-added upstreamed values to be correct.
+ * This works because no one should use this value directly, rather they must
+ * read the value from /sys/fs/fuse/bpf_prog_type_fuse
+ * Please maintain this value at the end of the list until fuse-bpf is
+ * upstreamed.
+ */
+ BPF_PROG_TYPE_FUSE,
};
enum bpf_attach_type {
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 341c94f..0f0b283 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -43,3 +43,6 @@
obj-$(CONFIG_BPF_SYSCALL) += relo_core.o
$(obj)/relo_core.o: $(srctree)/tools/lib/bpf/relo_core.c FORCE
$(call if_changed_rule,cc_o_c)
+ifeq ($(CONFIG_FUSE_BPF),y)
+obj-$(CONFIG_BPF_SYSCALL) += bpf_fuse.o
+endif
diff --git a/kernel/bpf/bpf_fuse.c b/kernel/bpf/bpf_fuse.c
new file mode 100644
index 0000000..c6aa670b
--- /dev/null
+++ b/kernel/bpf/bpf_fuse.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021 Google LLC
+
+#include <linux/filter.h>
+#include <linux/android_fuse.h>
+
+static const struct bpf_func_proto *
+fuse_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
+{
+ switch (func_id) {
+ case BPF_FUNC_trace_printk:
+ return bpf_get_trace_printk_proto();
+
+ case BPF_FUNC_get_current_uid_gid:
+ return &bpf_get_current_uid_gid_proto;
+
+ case BPF_FUNC_get_current_pid_tgid:
+ return &bpf_get_current_pid_tgid_proto;
+
+ case BPF_FUNC_map_lookup_elem:
+ return &bpf_map_lookup_elem_proto;
+
+ case BPF_FUNC_map_update_elem:
+ return &bpf_map_update_elem_proto;
+
+ default:
+ pr_debug("Invalid fuse bpf func %d\n", func_id);
+ return NULL;
+ }
+}
+
+static bool fuse_prog_is_valid_access(int off, int size,
+ enum bpf_access_type type,
+ const struct bpf_prog *prog,
+ struct bpf_insn_access_aux *info)
+{
+ int i;
+
+ if (off < 0 || off > offsetofend(struct fuse_bpf_args, out_args))
+ return false;
+
+ /* TODO This is garbage. Do it properly */
+ for (i = 0; i < 5; i++) {
+ if (off == offsetof(struct fuse_bpf_args, in_args[i].value)) {
+ info->reg_type = PTR_TO_BUF;
+ info->ctx_field_size = 256;
+ if (type != BPF_READ)
+ return false;
+ return true;
+ }
+ }
+ for (i = 0; i < 3; i++) {
+ if (off == offsetof(struct fuse_bpf_args, out_args[i].value)) {
+ info->reg_type = PTR_TO_BUF;
+ info->ctx_field_size = 256;
+ return true;
+ }
+ }
+ if (type != BPF_READ)
+ return false;
+
+ return true;
+}
+
+const struct bpf_verifier_ops fuse_verifier_ops = {
+ .get_func_proto = fuse_prog_func_proto,
+ .is_valid_access = fuse_prog_is_valid_access,
+};
+
+const struct bpf_prog_ops fuse_prog_ops = {
+};
+
+struct bpf_prog *fuse_get_bpf_prog(struct file *file)
+{
+ struct bpf_prog *bpf_prog = ERR_PTR(-EINVAL);
+
+ if (!file || IS_ERR(file))
+ return bpf_prog;
+ /**
+ * Two ways of getting a bpf prog from another task's fd, since
+ * bpf_prog_get_type_dev only works with an fd
+ *
+ * 1) Duplicate a little of the needed code. Requires access to
+ * bpf_prog_fops for validation, which is not exported for modules
+ * 2) Insert the bpf_file object into a fd from the current task
+ * Stupidly complex, but I think OK, as security checks are not run
+ * during the existence of the handle
+ *
+ * Best would be to upstream 1) into kernel/bpf/syscall.c and export it
+ * for use here. Failing that, we have to use 2, since fuse must be
+ * compilable as a module.
+ */
+#if 1
+ if (file->f_op != &bpf_prog_fops)
+ goto out;
+
+ bpf_prog = file->private_data;
+ if (bpf_prog->type == BPF_PROG_TYPE_FUSE)
+ bpf_prog_inc(bpf_prog);
+ else
+ bpf_prog = ERR_PTR(-EINVAL);
+
+#else
+ {
+ int task_fd = get_unused_fd_flags(file->f_flags);
+
+ if (task_fd < 0)
+ goto out;
+
+ fd_install(task_fd, file);
+
+ bpf_prog = bpf_prog_get_type_dev(task_fd, BPF_PROG_TYPE_FUSE,
+ false);
+
+ /* Close the fd, which also closes the file */
+ __close_fd(current->files, task_fd);
+ file = NULL;
+ }
+#endif
+
+out:
+ if (file)
+ fput(file);
+ return bpf_prog;
+}
+EXPORT_SYMBOL(fuse_get_bpf_prog);
+
+
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 35c07af..092c285 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -3,6 +3,7 @@
#include <uapi/linux/btf.h>
#include <uapi/linux/bpf.h>
+#include <uapi/linux/android_fuse.h>
#include <uapi/linux/bpf_perf_event.h>
#include <uapi/linux/types.h>
#include <linux/seq_file.h>
diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
new file mode 100644
index 0000000..3ee9a27
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -0,0 +1,2 @@
+fuse_test
+*.raw
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
new file mode 100644
index 0000000..261d760
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -D_FILE_OFFSET_BITS=64 -Wall -Werror -I../.. -I../../../../.. -I../../../../include
+LDLIBS := -lpthread -lelf
+TEST_GEN_PROGS := fuse_test fuse_daemon
+TEST_GEN_FILES := \
+ test_bpf.bpf \
+ fd_bpf.bpf \
+ fd.sh \
+
+EXTRA_CLEAN := *.bpf
+BPF_FLAGS = -Wall -Werror -O2 -g -emit-llvm \
+ -I ../../../../../include \
+ -idirafter /usr/lib/gcc/x86_64-linux-gnu/10/include \
+ -idirafter /usr/local/include \
+ -idirafter /usr/include/x86_64-linux-gnu \
+ -idirafter /usr/include \
+
+include ../../lib.mk
+
+# Put after include ../../lib.mk since that changes $(TEST_GEN_PROGS)
+# Otherwise you get multiple targets, this becomes the default, and it's a mess
+EXTRA_SOURCES := bpf_loader.c
+$(TEST_GEN_PROGS) : $(EXTRA_SOURCES)
+
+$(OUTPUT)/%.ir: %.c
+ clang $(BPF_FLAGS) -c $< -o $@
+
+$(OUTPUT)/%.bpf: $(OUTPUT)/%.ir
+ llc -march=bpf -filetype=obj -o $@ $<
+
+$(OUTPUT)/fd.sh: fd.txt
+ cp $< $@
+ chmod 755 $@
+
diff --git a/tools/testing/selftests/filesystems/fuse/OWNERS b/tools/testing/selftests/filesystems/fuse/OWNERS
new file mode 100644
index 0000000..5eb371e
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/OWNERS
@@ -0,0 +1,2 @@
+# include OWNERS from the authoritative android-mainline branch
+include kernel/common:android-mainline:/tools/testing/selftests/filesystems/incfs/OWNERS
diff --git a/tools/testing/selftests/filesystems/fuse/bpf_loader.c b/tools/testing/selftests/filesystems/fuse/bpf_loader.c
new file mode 100644
index 0000000..5bf26ea
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/bpf_loader.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 Google LLC
+ */
+
+#include "test_fuse.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/xattr.h>
+
+#include <linux/unistd.h>
+
+#include <include/uapi/linux/fuse.h>
+#include <include/uapi/linux/bpf.h>
+
+struct _test_options test_options;
+
+struct s s(const char *s1)
+{
+ struct s s = {0};
+
+ if (!s1)
+ return s;
+
+ s.s = malloc(strlen(s1) + 1);
+ if (!s.s)
+ return s;
+
+ strcpy(s.s, s1);
+ return s;
+}
+
+struct s sn(const char *s1, const char *s2)
+{
+ struct s s = {0};
+
+ if (!s1)
+ return s;
+
+ s.s = malloc(s2 - s1 + 1);
+ if (!s.s)
+ return s;
+
+ strncpy(s.s, s1, s2 - s1);
+ s.s[s2 - s1] = 0;
+ return s;
+}
+
+int s_cmp(struct s s1, struct s s2)
+{
+ int result = -1;
+
+ if (!s1.s || !s2.s)
+ goto out;
+ result = strcmp(s1.s, s2.s);
+out:
+ free(s1.s);
+ free(s2.s);
+ return result;
+}
+
+struct s s_cat(struct s s1, struct s s2)
+{
+ struct s s = {0};
+
+ if (!s1.s || !s2.s)
+ goto out;
+
+ s.s = malloc(strlen(s1.s) + strlen(s2.s) + 1);
+ if (!s.s)
+ goto out;
+
+ strcpy(s.s, s1.s);
+ strcat(s.s, s2.s);
+out:
+ free(s1.s);
+ free(s2.s);
+ return s;
+}
+
+struct s s_splitleft(struct s s1, char c)
+{
+ struct s s = {0};
+ char *split;
+
+ if (!s1.s)
+ return s;
+
+ split = strchr(s1.s, c);
+ if (split)
+ s = sn(s1.s, split);
+
+ free(s1.s);
+ return s;
+}
+
+struct s s_splitright(struct s s1, char c)
+{
+ struct s s2 = {0};
+ char *split;
+
+ if (!s1.s)
+ return s2;
+
+ split = strchr(s1.s, c);
+ if (split)
+ s2 = s(split + 1);
+
+ free(s1.s);
+ return s2;
+}
+
+struct s s_word(struct s s1, char c, size_t n)
+{
+ while (n--)
+ s1 = s_splitright(s1, c);
+ return s_splitleft(s1, c);
+}
+
+struct s s_path(struct s s1, struct s s2)
+{
+ return s_cat(s_cat(s1, s("/")), s2);
+}
+
+struct s s_pathn(size_t n, struct s s1, ...)
+{
+ va_list argp;
+
+ va_start(argp, s1);
+ while (--n)
+ s1 = s_path(s1, va_arg(argp, struct s));
+ va_end(argp);
+ return s1;
+}
+
+int s_link(struct s src_pathname, struct s dst_pathname)
+{
+ int res;
+
+ if (src_pathname.s && dst_pathname.s) {
+ res = link(src_pathname.s, dst_pathname.s);
+ } else {
+ res = -1;
+ errno = ENOMEM;
+ }
+
+ free(src_pathname.s);
+ free(dst_pathname.s);
+ return res;
+}
+
+int s_symlink(struct s src_pathname, struct s dst_pathname)
+{
+ int res;
+
+ if (src_pathname.s && dst_pathname.s) {
+ res = symlink(src_pathname.s, dst_pathname.s);
+ } else {
+ res = -1;
+ errno = ENOMEM;
+ }
+
+ free(src_pathname.s);
+ free(dst_pathname.s);
+ return res;
+}
+
+
+int s_mkdir(struct s pathname, mode_t mode)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = mkdir(pathname.s, mode);
+ free(pathname.s);
+ return res;
+}
+
+int s_rmdir(struct s pathname)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = rmdir(pathname.s);
+ free(pathname.s);
+ return res;
+}
+
+int s_unlink(struct s pathname)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = unlink(pathname.s);
+ free(pathname.s);
+ return res;
+}
+
+int s_open(struct s pathname, int flags, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, flags);
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (flags & (O_CREAT | O_TMPFILE))
+ res = open(pathname.s, flags, va_arg(ap, mode_t));
+ else
+ res = open(pathname.s, flags);
+
+ free(pathname.s);
+ va_end(ap);
+ return res;
+}
+
+int s_openat(int dirfd, struct s pathname, int flags, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, flags);
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (flags & (O_CREAT | O_TMPFILE))
+ res = openat(dirfd, pathname.s, flags, va_arg(ap, mode_t));
+ else
+ res = openat(dirfd, pathname.s, flags);
+
+ free(pathname.s);
+ va_end(ap);
+ return res;
+}
+
+int s_creat(struct s pathname, mode_t mode)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = open(pathname.s, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
+ free(pathname.s);
+ return res;
+}
+
+int s_mkfifo(struct s pathname, mode_t mode)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = mknod(pathname.s, S_IFIFO | mode, 0);
+ free(pathname.s);
+ return res;
+}
+
+int s_stat(struct s pathname, struct stat *st)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = stat(pathname.s, st);
+ free(pathname.s);
+ return res;
+}
+
+int s_statfs(struct s pathname, struct statfs *st)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = statfs(pathname.s, st);
+ free(pathname.s);
+ return res;
+}
+
+DIR *s_opendir(struct s pathname)
+{
+ DIR *res;
+
+ res = opendir(pathname.s);
+ free(pathname.s);
+ return res;
+}
+
+int s_getxattr(struct s pathname, const char name[], void *value, size_t size,
+ ssize_t *ret_size)
+{
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ *ret_size = getxattr(pathname.s, name, value, size);
+ free(pathname.s);
+ return *ret_size >= 0 ? 0 : -1;
+}
+
+int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size)
+{
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ *ret_size = listxattr(pathname.s, list, size);
+ free(pathname.s);
+ return *ret_size >= 0 ? 0 : -1;
+}
+
+int s_setxattr(struct s pathname, const char name[], const void *value, size_t size, int flags)
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = setxattr(pathname.s, name, value, size, flags);
+ free(pathname.s);
+ return res;
+}
+
+int s_removexattr(struct s pathname, const char name[])
+{
+ int res;
+
+ if (!pathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = removexattr(pathname.s, name);
+ free(pathname.s);
+ return res;
+}
+
+int s_rename(struct s oldpathname, struct s newpathname)
+{
+ int res;
+
+ if (!oldpathname.s || !newpathname.s) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ res = rename(oldpathname.s, newpathname.s);
+ free(oldpathname.s);
+ free(newpathname.s);
+ return res;
+}
+
+int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out)
+{
+
+ struct stat st;
+ int result = TEST_FAILURE;
+
+ TESTSYSCALL(s_stat(pathname, &st));
+
+ fuse_attr_out->ino = st.st_ino;
+ fuse_attr_out->mode = st.st_mode;
+ fuse_attr_out->nlink = st.st_nlink;
+ fuse_attr_out->uid = st.st_uid;
+ fuse_attr_out->gid = st.st_gid;
+ fuse_attr_out->rdev = st.st_rdev;
+ fuse_attr_out->size = st.st_size;
+ fuse_attr_out->blksize = st.st_blksize;
+ fuse_attr_out->blocks = st.st_blocks;
+ fuse_attr_out->atime = st.st_atime;
+ fuse_attr_out->mtime = st.st_mtime;
+ fuse_attr_out->ctime = st.st_ctime;
+ fuse_attr_out->atimensec = UINT32_MAX;
+ fuse_attr_out->mtimensec = UINT32_MAX;
+ fuse_attr_out->ctimensec = UINT32_MAX;
+
+ result = TEST_SUCCESS;
+out:
+ return result;
+}
+
+struct s tracing_folder(void)
+{
+ struct s trace = {0};
+ FILE *mounts = NULL;
+ char *line = NULL;
+ size_t size = 0;
+
+ TEST(mounts = fopen("/proc/mounts", "re"), mounts);
+ while (getline(&line, &size, mounts) != -1) {
+ if (!s_cmp(s_word(sn(line, line + size), ' ', 2),
+ s("tracefs"))) {
+ trace = s_word(sn(line, line + size), ' ', 1);
+ break;
+ }
+
+ if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs")))
+ trace = s_path(s_word(sn(line, line + size), ' ', 1),
+ s("tracing"));
+ }
+
+out:
+ free(line);
+ fclose(mounts);
+ return trace;
+}
+
+int tracing_on(void)
+{
+ int result = TEST_FAILURE;
+ int tracing_on = -1;
+
+ TEST(tracing_on = s_open(s_path(tracing_folder(), s("tracing_on")),
+ O_WRONLY | O_CLOEXEC),
+ tracing_on != -1);
+ TESTEQUAL(write(tracing_on, "1", 1), 1);
+ result = TEST_SUCCESS;
+out:
+ close(tracing_on);
+ return result;
+}
+
+char *concat_file_name(const char *dir, const char *file)
+{
+ char full_name[FILENAME_MAX] = "";
+
+ if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0)
+ return NULL;
+ return strdup(full_name);
+}
+
+char *setup_mount_dir(const char *name)
+{
+ struct stat st;
+ char *current_dir = getcwd(NULL, 0);
+ char *mount_dir = concat_file_name(current_dir, name);
+
+ free(current_dir);
+ if (stat(mount_dir, &st) == 0) {
+ if (S_ISDIR(st.st_mode))
+ return mount_dir;
+
+ ksft_print_msg("%s is a file, not a dir.\n", mount_dir);
+ return NULL;
+ }
+
+ if (mkdir(mount_dir, 0777)) {
+ ksft_print_msg("Can't create mount dir.");
+ return NULL;
+ }
+
+ return mount_dir;
+}
+
+int delete_dir_tree(const char *dir_path, bool remove_root)
+{
+ DIR *dir = NULL;
+ struct dirent *dp;
+ int result = 0;
+
+ dir = opendir(dir_path);
+ if (!dir) {
+ result = -errno;
+ goto out;
+ }
+
+ while ((dp = readdir(dir))) {
+ char *full_path;
+
+ if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+ continue;
+
+ full_path = concat_file_name(dir_path, dp->d_name);
+ if (dp->d_type == DT_DIR)
+ result = delete_dir_tree(full_path, true);
+ else
+ result = unlink(full_path);
+ free(full_path);
+ if (result)
+ goto out;
+ }
+
+out:
+ if (dir)
+ closedir(dir);
+ if (!result && remove_root)
+ rmdir(dir_path);
+ return result;
+}
+
+static int mount_fuse_maybe_init(const char *mount_dir, int bpf_fd, int dir_fd,
+ int *fuse_dev_ptr, bool init)
+{
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ char options[FILENAME_MAX];
+ uint8_t bytes_in[FUSE_MIN_READ_BUFFER];
+ uint8_t bytes_out[FUSE_MIN_READ_BUFFER];
+
+ DECL_FUSE_IN(init);
+
+ TEST(fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev != -1);
+ snprintf(options, FILENAME_MAX, "fd=%d,user_id=0,group_id=0,rootmode=0040000",
+ fuse_dev);
+ if (bpf_fd != -1)
+ snprintf(options + strlen(options),
+ sizeof(options) - strlen(options),
+ ",root_bpf=%d", bpf_fd);
+ if (dir_fd != -1)
+ snprintf(options + strlen(options),
+ sizeof(options) - strlen(options),
+ ",root_dir=%d", dir_fd);
+ TESTSYSCALL(mount("ABC", mount_dir, "fuse", 0, options));
+
+ if (init) {
+ TESTFUSEIN(FUSE_INIT, init_in);
+ TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION);
+ TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION);
+ TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) {
+ .major = FUSE_KERNEL_VERSION,
+ .minor = FUSE_KERNEL_MINOR_VERSION,
+ .max_readahead = 4096,
+ .flags = 0,
+ .max_background = 0,
+ .congestion_threshold = 0,
+ .max_write = 4096,
+ .time_gran = 1000,
+ .max_pages = 12,
+ .map_alignment = 4096,
+ }));
+ }
+
+ *fuse_dev_ptr = fuse_dev;
+ fuse_dev = -1;
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ return result;
+}
+
+int mount_fuse(const char *mount_dir, int bpf_fd, int dir_fd, int *fuse_dev_ptr)
+{
+ return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr,
+ true);
+}
+
+int mount_fuse_no_init(const char *mount_dir, int bpf_fd, int dir_fd,
+ int *fuse_dev_ptr)
+{
+ return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr,
+ false);
+}
+
+struct fuse_bpf_map {
+ unsigned int map_type;
+ size_t key_size;
+ size_t value_size;
+ unsigned int max_entries;
+};
+
+static int install_maps(Elf_Data *maps, int maps_index, Elf *elf,
+ Elf_Data *symbols, int symbol_index,
+ struct map_relocation **mr, size_t *map_count)
+{
+ int result = TEST_FAILURE;
+ int i;
+ GElf_Sym symbol;
+
+ TESTNE((void *)symbols, NULL);
+
+ for (i = 0; i < symbols->d_size / sizeof(symbol); ++i) {
+ TESTNE((void *)gelf_getsym(symbols, i, &symbol), 0);
+ if (symbol.st_shndx == maps_index) {
+ struct fuse_bpf_map *map;
+ union bpf_attr attr;
+ int map_fd;
+
+ map = (struct fuse_bpf_map *)
+ ((char *)maps->d_buf + symbol.st_value);
+
+ attr = (union bpf_attr) {
+ .map_type = map->map_type,
+ .key_size = map->key_size,
+ .value_size = map->value_size,
+ .max_entries = map->max_entries,
+ };
+
+ TEST(*mr = realloc(*mr, ++*map_count *
+ sizeof(struct fuse_bpf_map)),
+ *mr);
+ TEST(map_fd = syscall(__NR_bpf, BPF_MAP_CREATE,
+ &attr, sizeof(attr)),
+ map_fd != -1);
+ (*mr)[*map_count - 1] = (struct map_relocation) {
+ .name = strdup(elf_strptr(elf, symbol_index,
+ symbol.st_name)),
+ .fd = map_fd,
+ .value = symbol.st_value,
+ };
+ }
+ }
+
+ result = TEST_SUCCESS;
+out:
+ return result;
+}
+
+static inline int relocate_maps(GElf_Shdr *rel_header, Elf_Data *rel_data,
+ Elf_Data *prog_data, Elf_Data *symbol_data,
+ struct map_relocation *map_relocations,
+ size_t map_count)
+{
+ int result = TEST_FAILURE;
+ int i;
+ struct bpf_insn *insns = (struct bpf_insn *) prog_data->d_buf;
+
+ for (i = 0; i < rel_header->sh_size / rel_header->sh_entsize; ++i) {
+ GElf_Sym sym;
+ GElf_Rel rel;
+ unsigned int insn_idx;
+ int map_idx;
+
+ gelf_getrel(rel_data, i, &rel);
+ insn_idx = rel.r_offset / sizeof(struct bpf_insn);
+ insns[insn_idx].src_reg = BPF_PSEUDO_MAP_FD;
+
+ gelf_getsym(symbol_data, GELF_R_SYM(rel.r_info), &sym);
+ for (map_idx = 0; map_idx < map_count; map_idx++) {
+ if (map_relocations[map_idx].value == sym.st_value) {
+ insns[insn_idx].imm =
+ map_relocations[map_idx].fd;
+ break;
+ }
+ }
+ TESTNE(map_idx, map_count);
+ }
+
+ result = TEST_SUCCESS;
+out:
+ return result;
+}
+
+int install_elf_bpf(const char *file, const char *section, int *fd,
+ struct map_relocation **map_relocations, size_t *map_count)
+{
+ int result = TEST_FAILURE;
+ char path[PATH_MAX] = {};
+ char *last_slash;
+ int filter_fd = -1;
+ union bpf_attr bpf_attr;
+ static char log[1 << 20];
+ Elf *elf = NULL;
+ GElf_Ehdr ehdr;
+ Elf_Data *data_prog = NULL, *data_maps = NULL, *data_symbols = NULL;
+ int maps_index, symbol_index, prog_index;
+ int i;
+ int bpf_prog_type_fuse_fd = -1;
+ char buffer[10] = {0};
+ int bpf_prog_type_fuse;
+
+ TESTNE(readlink("/proc/self/exe", path, PATH_MAX), -1);
+ TEST(last_slash = strrchr(path, '/'), last_slash);
+ strcpy(last_slash + 1, file);
+ TEST(filter_fd = open(path, O_RDONLY | O_CLOEXEC), filter_fd != -1);
+ TESTNE(elf_version(EV_CURRENT), EV_NONE);
+ TEST(elf = elf_begin(filter_fd, ELF_C_READ, NULL), elf);
+ TESTEQUAL((void *) gelf_getehdr(elf, &ehdr), &ehdr);
+ for (i = 1; i < ehdr.e_shnum; i++) {
+ char *shname;
+ GElf_Shdr shdr;
+ Elf_Scn *scn;
+
+ TEST(scn = elf_getscn(elf, i), scn);
+ TESTEQUAL((void *)gelf_getshdr(scn, &shdr), &shdr);
+ TEST(shname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name),
+ shname);
+
+ if (!strcmp(shname, "maps")) {
+ TEST(data_maps = elf_getdata(scn, 0), data_maps);
+ maps_index = i;
+ } else if (shdr.sh_type == SHT_SYMTAB) {
+ TEST(data_symbols = elf_getdata(scn, 0), data_symbols);
+ symbol_index = shdr.sh_link;
+ } else if (!strcmp(shname, section)) {
+ TEST(data_prog = elf_getdata(scn, 0), data_prog);
+ prog_index = i;
+ }
+ }
+ TESTNE((void *) data_prog, NULL);
+
+ if (data_maps)
+ TESTEQUAL(install_maps(data_maps, maps_index, elf,
+ data_symbols, symbol_index,
+ map_relocations, map_count), 0);
+
+ /* Now relocate maps */
+ for (i = 1; i < ehdr.e_shnum; i++) {
+ GElf_Shdr rel_header;
+ Elf_Scn *scn;
+ Elf_Data *rel_data;
+
+ TEST(scn = elf_getscn(elf, i), scn);
+ TESTEQUAL((void *)gelf_getshdr(scn, &rel_header),
+ &rel_header);
+ if (rel_header.sh_type != SHT_REL)
+ continue;
+ TEST(rel_data = elf_getdata(scn, 0), rel_data);
+
+ if (rel_header.sh_info != prog_index)
+ continue;
+ TESTEQUAL(relocate_maps(&rel_header, rel_data,
+ data_prog, data_symbols,
+ *map_relocations, *map_count),
+ 0);
+ }
+
+ TEST(bpf_prog_type_fuse_fd = open("/sys/fs/fuse/bpf_prog_type_fuse",
+ O_RDONLY | O_CLOEXEC),
+ bpf_prog_type_fuse_fd != -1);
+ TESTGE(read(bpf_prog_type_fuse_fd, buffer, sizeof(buffer)), 1);
+ TEST(bpf_prog_type_fuse = strtol(buffer, NULL, 10),
+ bpf_prog_type_fuse != 0);
+
+ bpf_attr = (union bpf_attr) {
+ .prog_type = bpf_prog_type_fuse,
+ .insn_cnt = data_prog->d_size / 8,
+ .insns = ptr_to_u64(data_prog->d_buf),
+ .license = ptr_to_u64("GPL"),
+ .log_buf = test_options.verbose ? ptr_to_u64(log) : 0,
+ .log_size = test_options.verbose ? sizeof(log) : 0,
+ .log_level = test_options.verbose ? 2 : 0,
+ };
+ *fd = syscall(__NR_bpf, BPF_PROG_LOAD, &bpf_attr, sizeof(bpf_attr));
+ if (test_options.verbose)
+ ksft_print_msg("%s\n", log);
+ if (*fd == -1 && errno == ENOSPC)
+ ksft_print_msg("bpf log size too small!\n");
+ TESTNE(*fd, -1);
+
+ result = TEST_SUCCESS;
+out:
+ close(filter_fd);
+ close(bpf_prog_type_fuse_fd);
+ return result;
+}
+
+
diff --git a/tools/testing/selftests/filesystems/fuse/fd.txt b/tools/testing/selftests/filesystems/fuse/fd.txt
new file mode 100644
index 0000000..15ce771
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fd.txt
@@ -0,0 +1,21 @@
+fuse_daemon $*
+cd fd-dst
+ls
+cd show
+ls
+fsstress -s 123 -d . -p 4 -n 100 -l5
+echo test > wibble
+ls
+cat wibble
+fallocate -l 1000 wobble
+mkdir testdir
+mkdir tmpdir
+rmdir tmpdir
+touch tmp
+mv tmp tmp2
+rm tmp2
+
+# FUSE_LINK
+echo "ln_src contents" > ln_src
+ln ln_src ln_link
+cat ln_link
diff --git a/tools/testing/selftests/filesystems/fuse/fd_bpf.c b/tools/testing/selftests/filesystems/fuse/fd_bpf.c
new file mode 100644
index 0000000..3cd82d6
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fd_bpf.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+// Copyright (c) 2021 Google LLC
+
+#include "test_fuse_bpf.h"
+
+SEC("maps") struct fuse_bpf_map test_map = {
+ BPF_MAP_TYPE_ARRAY,
+ sizeof(uint32_t),
+ sizeof(uint32_t),
+ 1000,
+};
+
+SEC("maps") struct fuse_bpf_map test_map2 = {
+ BPF_MAP_TYPE_HASH,
+ sizeof(uint32_t),
+ sizeof(uint64_t),
+ 76,
+};
+
+SEC("test_daemon") int trace_daemon(struct fuse_bpf_args *fa)
+{
+ uint64_t uid_gid = bpf_get_current_uid_gid();
+ uint32_t uid = uid_gid & 0xffffffff;
+ uint64_t pid_tgid = bpf_get_current_pid_tgid();
+ uint32_t pid = pid_tgid & 0xffffffff;
+ uint32_t key = 23;
+ uint32_t *pvalue;
+
+ pvalue = bpf_map_lookup_elem(&test_map, &key);
+ if (pvalue) {
+ uint32_t value = *pvalue;
+
+ bpf_printk("pid %u uid %u value %u", pid, uid, value);
+ value++;
+ bpf_map_update_elem(&test_map, &key, &value, BPF_ANY);
+ }
+
+ switch (fa->opcode) {
+ case FUSE_ACCESS | FUSE_PREFILTER: {
+ bpf_printk("Access: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_GETATTR | FUSE_PREFILTER: {
+ const struct fuse_getattr_in *fgi = fa->in_args[0].value;
+
+ bpf_printk("Get Attr %d", fgi->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_SETATTR | FUSE_PREFILTER: {
+ const struct fuse_setattr_in *fsi = fa->in_args[0].value;
+
+ bpf_printk("Set Attr %d", fsi->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_OPENDIR | FUSE_PREFILTER: {
+ bpf_printk("Open Dir: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_READDIR | FUSE_PREFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+
+ bpf_printk("Read Dir: fh: %lu", fri->fh, fri->offset);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("Lookup: %lx %s", fa->nodeid, name);
+ if (fa->nodeid == 1)
+ return FUSE_BPF_USER_FILTER | FUSE_BPF_BACKING;
+ else
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_MKNOD | FUSE_PREFILTER: {
+ const struct fuse_mknod_in *fmi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("mknod %s %x %x", name, fmi->rdev | fmi->mode, fmi->umask);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_MKDIR | FUSE_PREFILTER: {
+ const struct fuse_mkdir_in *fmi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("mkdir: %s %x %x", name, fmi->mode, fmi->umask);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RMDIR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("rmdir: %s", name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RENAME | FUSE_PREFILTER: {
+ const char *oldname = fa->in_args[1].value;
+ const char *newname = fa->in_args[2].value;
+
+ bpf_printk("rename from %s", oldname);
+ bpf_printk("rename to %s", newname);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RENAME2 | FUSE_PREFILTER: {
+ const struct fuse_rename2_in *fri = fa->in_args[0].value;
+ uint32_t flags = fri->flags;
+ const char *oldname = fa->in_args[1].value;
+ const char *newname = fa->in_args[2].value;
+
+ bpf_printk("rename(%x) from %s", flags, oldname);
+ bpf_printk("rename to %s", newname);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_UNLINK | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("unlink: %s", name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LINK | FUSE_PREFILTER: {
+ const struct fuse_link_in *fli = fa->in_args[0].value;
+ const char *dst_name = fa->in_args[1].value;
+
+ bpf_printk("Link: %d %s", fli->oldnodeid, dst_name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_SYMLINK | FUSE_PREFILTER: {
+ const char *link_name = fa->in_args[0].value;
+ const char *link_dest = fa->in_args[1].value;
+
+ bpf_printk("symlink from %s", link_name);
+ bpf_printk("symlink to %s", link_dest);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_READLINK | FUSE_PREFILTER: {
+ const char *link_name = fa->in_args[0].value;
+
+ bpf_printk("readlink from %s", link_name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RELEASE | FUSE_PREFILTER: {
+ const struct fuse_release_in *fri = fa->in_args[0].value;
+
+ bpf_printk("Release: %d", fri->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RELEASEDIR | FUSE_PREFILTER: {
+ const struct fuse_release_in *fri = fa->in_args[0].value;
+
+ bpf_printk("Release Dir: %d", fri->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_CREATE | FUSE_PREFILTER: {
+ bpf_printk("Create %s", fa->in_args[1].value);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_OPEN | FUSE_PREFILTER: {
+ bpf_printk("Open: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_READ | FUSE_PREFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+
+ bpf_printk("Read: fh: %lu, offset %lu, size %lu",
+ fri->fh, fri->offset, fri->size);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_WRITE | FUSE_PREFILTER: {
+ const struct fuse_write_in *fwi = fa->in_args[0].value;
+
+ bpf_printk("Write: fh: %lu, offset %lu, size %lu",
+ fwi->fh, fwi->offset, fwi->size);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_FLUSH | FUSE_PREFILTER: {
+ const struct fuse_flush_in *ffi = fa->in_args[0].value;
+
+ bpf_printk("Flush %d", ffi->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_FALLOCATE | FUSE_PREFILTER: {
+ const struct fuse_fallocate_in *ffa = fa->in_args[0].value;
+
+ bpf_printk("Fallocate %d %lu", ffa->fh, ffa->length);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_GETXATTR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("Getxattr %d %s", fa->nodeid, name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LISTXATTR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("Listxattr %d %s", fa->nodeid, name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_SETXATTR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("Setxattr %d %s", fa->nodeid, name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_STATFS | FUSE_PREFILTER: {
+ bpf_printk("statfs %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LSEEK | FUSE_PREFILTER: {
+ const struct fuse_lseek_in *fli = fa->in_args[0].value;
+
+ bpf_printk("lseek type:%d, offset:%lld", fli->whence, fli->offset);
+ return FUSE_BPF_BACKING;
+ }
+
+ default:
+ if (fa->opcode & FUSE_PREFILTER)
+ bpf_printk("prefilter *** UNKNOWN *** opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else if (fa->opcode & FUSE_POSTFILTER)
+ bpf_printk("postfilter *** UNKNOWN *** opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else
+ bpf_printk("*** UNKNOWN *** opcode: %d", fa->opcode);
+ return FUSE_BPF_BACKING;
+ }
+}
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_daemon.c b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c
new file mode 100644
index 0000000..1b6f8c2a
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 Google LLC
+ */
+
+#include "test_fuse.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <linux/unistd.h>
+
+#include <include/uapi/linux/fuse.h>
+#include <include/uapi/linux/bpf.h>
+
+bool user_messages;
+bool kernel_messages;
+
+static int display_trace(void)
+{
+ int pid = -1;
+ int tp = -1;
+ char c;
+ ssize_t bytes_read;
+ static char line[256] = {0};
+
+ if (!kernel_messages)
+ return TEST_SUCCESS;
+
+ TEST(pid = fork(), pid != -1);
+ if (pid != 0)
+ return pid;
+
+ TESTEQUAL(tracing_on(), 0);
+ TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")),
+ O_RDONLY | O_CLOEXEC), tp != -1);
+ for (;;) {
+ TEST(bytes_read = read(tp, &c, sizeof(c)),
+ bytes_read == 1);
+ if (c == '\n') {
+ printf("%s\n", line);
+ line[0] = 0;
+ } else
+ sprintf(line + strlen(line), "%c", c);
+ }
+out:
+ if (pid == 0) {
+ close(tp);
+ exit(TEST_FAILURE);
+ }
+ return pid;
+}
+
+static const char *fuse_opcode_to_string(int opcode)
+{
+ switch (opcode & FUSE_OPCODE_FILTER) {
+ case FUSE_LOOKUP:
+ return "FUSE_LOOKUP";
+ case FUSE_FORGET:
+ return "FUSE_FORGET";
+ case FUSE_GETATTR:
+ return "FUSE_GETATTR";
+ case FUSE_SETATTR:
+ return "FUSE_SETATTR";
+ case FUSE_READLINK:
+ return "FUSE_READLINK";
+ case FUSE_SYMLINK:
+ return "FUSE_SYMLINK";
+ case FUSE_MKNOD:
+ return "FUSE_MKNOD";
+ case FUSE_MKDIR:
+ return "FUSE_MKDIR";
+ case FUSE_UNLINK:
+ return "FUSE_UNLINK";
+ case FUSE_RMDIR:
+ return "FUSE_RMDIR";
+ case FUSE_RENAME:
+ return "FUSE_RENAME";
+ case FUSE_LINK:
+ return "FUSE_LINK";
+ case FUSE_OPEN:
+ return "FUSE_OPEN";
+ case FUSE_READ:
+ return "FUSE_READ";
+ case FUSE_WRITE:
+ return "FUSE_WRITE";
+ case FUSE_STATFS:
+ return "FUSE_STATFS";
+ case FUSE_RELEASE:
+ return "FUSE_RELEASE";
+ case FUSE_FSYNC:
+ return "FUSE_FSYNC";
+ case FUSE_SETXATTR:
+ return "FUSE_SETXATTR";
+ case FUSE_GETXATTR:
+ return "FUSE_GETXATTR";
+ case FUSE_LISTXATTR:
+ return "FUSE_LISTXATTR";
+ case FUSE_REMOVEXATTR:
+ return "FUSE_REMOVEXATTR";
+ case FUSE_FLUSH:
+ return "FUSE_FLUSH";
+ case FUSE_INIT:
+ return "FUSE_INIT";
+ case FUSE_OPENDIR:
+ return "FUSE_OPENDIR";
+ case FUSE_READDIR:
+ return "FUSE_READDIR";
+ case FUSE_RELEASEDIR:
+ return "FUSE_RELEASEDIR";
+ case FUSE_FSYNCDIR:
+ return "FUSE_FSYNCDIR";
+ case FUSE_GETLK:
+ return "FUSE_GETLK";
+ case FUSE_SETLK:
+ return "FUSE_SETLK";
+ case FUSE_SETLKW:
+ return "FUSE_SETLKW";
+ case FUSE_ACCESS:
+ return "FUSE_ACCESS";
+ case FUSE_CREATE:
+ return "FUSE_CREATE";
+ case FUSE_INTERRUPT:
+ return "FUSE_INTERRUPT";
+ case FUSE_BMAP:
+ return "FUSE_BMAP";
+ case FUSE_DESTROY:
+ return "FUSE_DESTROY";
+ case FUSE_IOCTL:
+ return "FUSE_IOCTL";
+ case FUSE_POLL:
+ return "FUSE_POLL";
+ case FUSE_NOTIFY_REPLY:
+ return "FUSE_NOTIFY_REPLY";
+ case FUSE_BATCH_FORGET:
+ return "FUSE_BATCH_FORGET";
+ case FUSE_FALLOCATE:
+ return "FUSE_FALLOCATE";
+ case FUSE_READDIRPLUS:
+ return "FUSE_READDIRPLUS";
+ case FUSE_RENAME2:
+ return "FUSE_RENAME2";
+ case FUSE_LSEEK:
+ return "FUSE_LSEEK";
+ case FUSE_COPY_FILE_RANGE:
+ return "FUSE_COPY_FILE_RANGE";
+ case FUSE_SETUPMAPPING:
+ return "FUSE_SETUPMAPPING";
+ case FUSE_REMOVEMAPPING:
+ return "FUSE_REMOVEMAPPING";
+ //case FUSE_SYNCFS:
+ // return "FUSE_SYNCFS";
+ case CUSE_INIT:
+ return "CUSE_INIT";
+ case CUSE_INIT_BSWAP_RESERVED:
+ return "CUSE_INIT_BSWAP_RESERVED";
+ case FUSE_INIT_BSWAP_RESERVED:
+ return "FUSE_INIT_BSWAP_RESERVED";
+ }
+ return "?";
+}
+
+static int parse_options(int argc, char *const *argv)
+{
+ signed char c;
+
+ while ((c = getopt(argc, argv, "kuv")) != -1)
+ switch (c) {
+ case 'v':
+ test_options.verbose = true;
+ break;
+
+ case 'u':
+ user_messages = true;
+ break;
+
+ case 'k':
+ kernel_messages = true;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int result = TEST_FAILURE;
+ int trace_pid = -1;
+ char *mount_dir = NULL;
+ char *src_dir = NULL;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ struct map_relocation *map_relocations = NULL;
+ size_t map_count = 0;
+ int i;
+
+ if (geteuid() != 0)
+ ksft_print_msg("Not a root, might fail to mount.\n");
+ TESTEQUAL(parse_options(argc, argv), 0);
+
+ TEST(trace_pid = display_trace(), trace_pid != -1);
+
+ delete_dir_tree("fd-src", true);
+ TEST(src_dir = setup_mount_dir("fd-src"), src_dir);
+ delete_dir_tree("fd-dst", true);
+ TEST(mount_dir = setup_mount_dir("fd-dst"), mount_dir);
+
+ TESTEQUAL(install_elf_bpf("fd_bpf.bpf", "test_daemon", &bpf_fd,
+ &map_relocations, &map_count), 0);
+
+ TEST(src_fd = open("fd-src", O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTSYSCALL(mkdirat(src_fd, "show", 0777));
+ TESTSYSCALL(mkdirat(src_fd, "hide", 0777));
+
+ for (i = 0; i < map_count; ++i)
+ if (!strcmp(map_relocations[i].name, "test_map")) {
+ uint32_t key = 23;
+ uint32_t value = 1234;
+ union bpf_attr attr = {
+ .map_fd = map_relocations[i].fd,
+ .key = ptr_to_u64(&key),
+ .value = ptr_to_u64(&value),
+ .flags = BPF_ANY,
+ };
+ TESTSYSCALL(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM,
+ &attr, sizeof(attr)));
+ }
+
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ if (fork())
+ return 0;
+
+ for (;;) {
+ uint8_t bytes_in[FUSE_MIN_READ_BUFFER];
+ uint8_t bytes_out[FUSE_MIN_READ_BUFFER] __maybe_unused;
+ struct fuse_in_header *in_header =
+ (struct fuse_in_header *)bytes_in;
+ ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in));
+
+ if (res == -1)
+ break;
+
+ switch (in_header->opcode) {
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ char *name = (char *)(bytes_in + sizeof(*in_header));
+
+ if (user_messages)
+ printf("Lookup %s\n", name);
+ if (!strcmp(name, "hide"))
+ TESTFUSEOUTERROR(-ENOENT);
+ else
+ TESTFUSEOUTREAD(name, strlen(name) + 1);
+ break;
+ }
+ default:
+ if (user_messages) {
+ printf("opcode is %d (%s)\n", in_header->opcode,
+ fuse_opcode_to_string(
+ in_header->opcode));
+ }
+ break;
+ }
+ }
+
+ result = TEST_SUCCESS;
+
+out:
+ for (i = 0; i < map_count; ++i) {
+ free(map_relocations[i].name);
+ close(map_relocations[i].fd);
+ }
+ free(map_relocations);
+ umount2(mount_dir, MNT_FORCE);
+ delete_dir_tree(mount_dir, true);
+ free(mount_dir);
+ delete_dir_tree(src_dir, true);
+ free(src_dir);
+ if (trace_pid != -1)
+ kill(trace_pid, SIGKILL);
+ return result;
+}
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c
new file mode 100644
index 0000000..c23f75b
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c
@@ -0,0 +1,2142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 Google LLC
+ */
+#define _GNU_SOURCE
+
+#include "test_fuse.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/inotify.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include <linux/capability.h>
+#include <linux/random.h>
+
+#include <include/uapi/linux/fuse.h>
+#include <include/uapi/linux/bpf.h>
+
+static const char *ft_src = "ft-src";
+static const char *ft_dst = "ft-dst";
+
+static void fill_buffer(uint8_t *data, size_t len, int file, int block)
+{
+ int i;
+ int seed = 7919 * file + block;
+
+ for (i = 0; i < len; i++) {
+ seed = 1103515245 * seed + 12345;
+ data[i] = (uint8_t)(seed >> (i % 13));
+ }
+}
+
+static bool test_buffer(uint8_t *data, size_t len, int file, int block)
+{
+ int i;
+ int seed = 7919 * file + block;
+
+ for (i = 0; i < len; i++) {
+ seed = 1103515245 * seed + 12345;
+ if (data[i] != (uint8_t)(seed >> (i % 13)))
+ return false;
+ }
+
+ return true;
+}
+
+static int create_file(int dir, struct s name, int index, size_t blocks)
+{
+ int result = TEST_FAILURE;
+ int fd = -1;
+ int i;
+ uint8_t data[PAGE_SIZE];
+
+ TEST(fd = s_openat(dir, name, O_CREAT | O_WRONLY, 0777), fd != -1);
+ for (i = 0; i < blocks; ++i) {
+ fill_buffer(data, PAGE_SIZE, index, i);
+ TESTEQUAL(write(fd, data, sizeof(data)), PAGE_SIZE);
+ }
+ TESTSYSCALL(close(fd));
+ result = TEST_SUCCESS;
+
+out:
+ close(fd);
+ return result;
+}
+
+static int bpf_clear_trace(void)
+{
+ int result = TEST_FAILURE;
+ int tp = -1;
+
+ TEST(tp = s_open(s_path(tracing_folder(), s("trace")),
+ O_WRONLY | O_TRUNC | O_CLOEXEC), tp != -1);
+
+ result = TEST_SUCCESS;
+out:
+ close(tp);
+ return result;
+}
+
+static int bpf_test_trace_maybe(const char *substr, bool present)
+{
+ int result = TEST_FAILURE;
+ int tp = -1;
+ char trace_buffer[4096] = {};
+ ssize_t bytes_read;
+
+ TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")),
+ O_RDONLY | O_CLOEXEC),
+ tp != -1);
+ fcntl(tp, F_SETFL, O_NONBLOCK);
+
+ for (;;) {
+ bytes_read = read(tp, trace_buffer, sizeof(trace_buffer));
+ if (present)
+ TESTCOND(bytes_read > 0);
+ else if (bytes_read <= 0) {
+ result = TEST_SUCCESS;
+ break;
+ }
+
+ if (test_options.verbose)
+ ksft_print_msg("%s\n", trace_buffer);
+
+ if (strstr(trace_buffer, substr)) {
+ if (present)
+ result = TEST_SUCCESS;
+ break;
+ }
+ }
+out:
+ close(tp);
+ return result;
+}
+
+static int bpf_test_trace(const char *substr)
+{
+ return bpf_test_trace_maybe(substr, true);
+}
+
+static int bpf_test_no_trace(const char *substr)
+{
+ return bpf_test_trace_maybe(substr, false);
+}
+
+static int basic_test(const char *mount_dir)
+{
+ const char *test_name = "test";
+ const char *test_data = "data";
+
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ char *filename = NULL;
+ int fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTEQUAL(mount_fuse(mount_dir, -1, -1, &fuse_dev), 0);
+ FUSE_START_DAEMON();
+ if (action) {
+ char data[256];
+
+ filename = concat_file_name(mount_dir, test_name);
+ TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+ TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data));
+ TESTCOND(!strcmp(data, test_data));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ } else {
+ DECL_FUSE_IN(open);
+ DECL_FUSE_IN(read);
+ DECL_FUSE_IN(flush);
+ DECL_FUSE_IN(release);
+
+ TESTFUSELOOKUP(test_name, 0);
+ TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = 2,
+ .generation = 1,
+ .attr.ino = 100,
+ .attr.size = 4,
+ .attr.blksize = 512,
+ .attr.mode = S_IFREG | 0777,
+ }));
+
+ TESTFUSEIN(FUSE_OPEN, open_in);
+ TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
+ .fh = 1,
+ .open_flags = open_in->flags,
+ }));
+
+ //TESTFUSEINNULL(FUSE_CANONICAL_PATH);
+ //TESTFUSEOUTREAD("ignored", 7);
+
+ TESTFUSEIN(FUSE_READ, read_in);
+ TESTFUSEOUTREAD(test_data, strlen(test_data));
+
+ TESTFUSEIN(FUSE_FLUSH, flush_in);
+ TESTFUSEOUTEMPTY();
+
+ TESTFUSEIN(FUSE_RELEASE, release_in);
+ TESTFUSEOUTEMPTY();
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(fd);
+ free(filename);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_real(const char *mount_dir)
+{
+ const char *test_name = "real";
+ const char *test_data = "Weebles wobble but they don't fall down";
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ char *filename = NULL;
+ int fd = -1;
+ char read_buffer[256] = {};
+ ssize_t bytes_read;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777),
+ fd != -1);
+ TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ filename = concat_file_name(mount_dir, test_name);
+ TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+ bytes_read = read(fd, read_buffer, strlen(test_data));
+ TESTEQUAL(bytes_read, strlen(test_data));
+ TESTEQUAL(strcmp(test_data, read_buffer), 0);
+ TESTEQUAL(bpf_test_trace("read"), 0);
+
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ close(fd);
+ free(filename);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+
+static int bpf_test_partial(const char *mount_dir)
+{
+ const char *test_name = "partial";
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ char *filename = NULL;
+ int fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ uint8_t data[PAGE_SIZE];
+
+ TEST(filename = concat_file_name(mount_dir, test_name),
+ filename);
+ TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+ TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE);
+ TESTEQUAL(bpf_test_trace("read"), 0);
+ TESTCOND(test_buffer(data, PAGE_SIZE, 2, 0));
+ TESTCOND(!test_buffer(data, PAGE_SIZE, 1, 0));
+ TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE);
+ TESTCOND(test_buffer(data, PAGE_SIZE, 1, 1));
+ TESTCOND(!test_buffer(data, PAGE_SIZE, 2, 1));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ } else {
+ DECL_FUSE(open);
+ DECL_FUSE(read);
+ DECL_FUSE(release);
+ uint8_t data[PAGE_SIZE];
+
+ TESTFUSEIN2(FUSE_OPEN | FUSE_POSTFILTER, open_in, open_out);
+ TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
+ .fh = 1,
+ .open_flags = open_in->flags,
+ }));
+
+ TESTFUSEIN(FUSE_READ, read_in);
+ fill_buffer(data, PAGE_SIZE, 2, 0);
+ TESTFUSEOUTREAD(data, PAGE_SIZE);
+
+ TESTFUSEIN(FUSE_RELEASE, release_in);
+ TESTFUSEOUTEMPTY();
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(fd);
+ free(filename);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+static int bpf_test_attrs(const char *mount_dir)
+{
+ const char *test_name = "partial";
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ char *filename = NULL;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(filename = concat_file_name(mount_dir, test_name), filename);
+ TESTSYSCALL(stat(filename, &st));
+ TESTSYSCALL(chmod(filename, 0111));
+ TESTSYSCALL(stat(filename, &st));
+ TESTEQUAL(st.st_mode & 0777, 0111);
+ TESTSYSCALL(chmod(filename, 0777));
+ TESTSYSCALL(stat(filename, &st));
+ TESTEQUAL(st.st_mode & 0777, 0777);
+ TESTSYSCALL(chown(filename, 5, 6));
+ TESTSYSCALL(stat(filename, &st));
+ TESTEQUAL(st.st_uid, 5);
+ TESTEQUAL(st.st_gid, 6);
+
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ free(filename);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+static int bpf_test_readdir(const char *mount_dir)
+{
+ static const char * const names[] = {
+ "real", "partial", "fake", ".", ".."
+ };
+ bool used[ARRAY_SIZE(names)] = { false };
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ DIR *dir = NULL;
+ struct dirent *dirent;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(create_file(src_fd, s(names[0]), 1, 2), 0);
+ TESTEQUAL(create_file(src_fd, s(names[1]), 1, 2), 0);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ int i, j;
+
+ TEST(dir = s_opendir(s(mount_dir)), dir);
+ TESTEQUAL(bpf_test_trace("opendir"), 0);
+
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ TEST(dirent = readdir(dir), dirent);
+
+ for (j = 0; j < ARRAY_SIZE(names); ++j)
+ if (!used[j] &&
+ strcmp(names[j], dirent->d_name) == 0) {
+ used[j] = true;
+ break;
+ }
+ TESTNE(j, ARRAY_SIZE(names));
+ }
+ TEST(dirent = readdir(dir), dirent == NULL);
+ TESTSYSCALL(closedir(dir));
+ dir = NULL;
+ TESTEQUAL(bpf_test_trace("readdir"), 0);
+ } else {
+ struct fuse_in_header *in_header =
+ (struct fuse_in_header *)bytes_in;
+ ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in));
+ struct fuse_read_out *read_out =
+ (struct fuse_read_out *) (bytes_in +
+ sizeof(*in_header) +
+ sizeof(struct fuse_read_in));
+ struct fuse_dirent *fuse_dirent =
+ (struct fuse_dirent *) (bytes_in + res);
+
+ TESTGE(res, sizeof(*in_header) + sizeof(struct fuse_read_in));
+ TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER);
+ *fuse_dirent = (struct fuse_dirent) {
+ .ino = 100,
+ .off = 5,
+ .namelen = strlen("fake"),
+ .type = DT_REG,
+ };
+ strcpy((char *)(bytes_in + res + sizeof(*fuse_dirent)), "fake");
+ res += FUSE_DIRENT_ALIGN(sizeof(*fuse_dirent) + strlen("fake") +
+ 1);
+ TESTFUSEDIROUTREAD(read_out,
+ bytes_in +
+ sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_read_in) +
+ sizeof(struct fuse_read_out),
+ res - sizeof(struct fuse_in_header) -
+ sizeof(struct fuse_read_in) -
+ sizeof(struct fuse_read_out));
+ res = read(fuse_dev, bytes_in, sizeof(bytes_in));
+ TESTEQUAL(res, sizeof(*in_header) +
+ sizeof(struct fuse_read_in) +
+ sizeof(struct fuse_read_out));
+ TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER);
+ TESTFUSEDIROUTREAD(read_out, bytes_in, 0);
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ closedir(dir);
+ close(fuse_dev);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+static int bpf_test_redact_readdir(const char *mount_dir)
+{
+ static const char * const names[] = {
+ "f1", "f2", "f3", "f4", "f5", "f6", ".", ".."
+ };
+ bool used[ARRAY_SIZE(names)] = { false };
+ int num_shown = (ARRAY_SIZE(names) - 2) / 2 + 2;
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ DIR *dir = NULL;
+ struct dirent *dirent;
+ int i;
+ int count = 0;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ for (i = 0; i < ARRAY_SIZE(names) - 2; i++)
+ TESTEQUAL(create_file(src_fd, s(names[i]), 1, 2), 0);
+
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_readdir_redact",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ int j;
+
+ TEST(dir = s_opendir(s(mount_dir)), dir);
+ while ((dirent = readdir(dir))) {
+ errno = 0;
+ TESTEQUAL(errno, 0);
+
+ for (j = 0; j < ARRAY_SIZE(names); ++j)
+ if (!used[j] &&
+ strcmp(names[j], dirent->d_name) == 0) {
+ used[j] = true;
+ count++;
+ break;
+ }
+ TESTNE(j, ARRAY_SIZE(names));
+ TESTGE(num_shown, count);
+ }
+ TESTEQUAL(count, num_shown);
+ TESTSYSCALL(closedir(dir));
+ dir = NULL;
+ } else {
+ bool skip = true;
+
+ for (int i = 0; i < ARRAY_SIZE(names) + 1; i++) {
+ uint8_t bytes_in[FUSE_MIN_READ_BUFFER];
+ uint8_t bytes_out[FUSE_MIN_READ_BUFFER];
+ struct fuse_in_header *in_header =
+ (struct fuse_in_header *)bytes_in;
+ ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in));
+ int length_out = 0;
+ uint8_t *pos;
+ uint8_t *dirs_in;
+ uint8_t *dirs_out;
+ struct fuse_read_in *fuse_read_in;
+ struct fuse_read_out *fuse_read_out_in;
+ struct fuse_read_out *fuse_read_out_out;
+ struct fuse_dirent *fuse_dirent_in = NULL;
+ struct fuse_dirent *next = NULL;
+ bool again = false;
+ int dir_ent_len = 0;
+
+ TESTGE(res, sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_read_in) +
+ sizeof(struct fuse_read_out));
+
+ pos = bytes_in + sizeof(struct fuse_in_header);
+ fuse_read_in = (struct fuse_read_in *) pos;
+ pos += sizeof(*fuse_read_in);
+ fuse_read_out_in = (struct fuse_read_out *) pos;
+ pos += sizeof(*fuse_read_out_in);
+ dirs_in = pos;
+
+ pos = bytes_out + sizeof(struct fuse_out_header);
+ fuse_read_out_out = (struct fuse_read_out *) pos;
+ pos += sizeof(*fuse_read_out_out);
+ dirs_out = pos;
+
+ if (dirs_in < bytes_in + res) {
+ bool is_dot;
+
+ fuse_dirent_in = (struct fuse_dirent *) dirs_in;
+ is_dot = (fuse_dirent_in->namelen == 1 &&
+ !strncmp(fuse_dirent_in->name, ".", 1)) ||
+ (fuse_dirent_in->namelen == 2 &&
+ !strncmp(fuse_dirent_in->name, "..", 2));
+
+ dir_ent_len = FUSE_DIRENT_ALIGN(
+ sizeof(*fuse_dirent_in) +
+ fuse_dirent_in->namelen);
+
+ if (dirs_in + dir_ent_len < bytes_in + res)
+ next = (struct fuse_dirent *)
+ (dirs_in + dir_ent_len);
+
+ if (!skip || is_dot) {
+ memcpy(dirs_out, fuse_dirent_in,
+ sizeof(struct fuse_dirent) +
+ fuse_dirent_in->namelen);
+ length_out += dir_ent_len;
+ }
+ again = ((skip && !is_dot) && next);
+
+ if (!is_dot)
+ skip = !skip;
+ }
+
+ fuse_read_out_out->offset = next ? next->off :
+ fuse_read_out_in->offset;
+ fuse_read_out_out->again = again;
+
+ {
+ struct fuse_out_header *out_header =
+ (struct fuse_out_header *)bytes_out;
+
+ *out_header = (struct fuse_out_header) {
+ .len = sizeof(*out_header) +
+ sizeof(*fuse_read_out_out) + length_out,
+ .unique = in_header->unique,
+ };
+ TESTEQUAL(write(fuse_dev, bytes_out, out_header->len),
+ out_header->len);
+ }
+ }
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ closedir(dir);
+ close(fuse_dev);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+/*
+ * This test is more to show what classic fuse does with a creat in a subdir
+ * than a test of any new functionality
+ */
+static int bpf_test_creat(const char *mount_dir)
+{
+ const char *dir_name = "show";
+ const char *file_name = "file";
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTEQUAL(mount_fuse(mount_dir, -1, -1, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_name)),
+ s(file_name)),
+ 0777),
+ fd != -1);
+ TESTSYSCALL(close(fd));
+ } else {
+ DECL_FUSE_IN(create);
+ DECL_FUSE_IN(release);
+ DECL_FUSE_IN(flush);
+
+ TESTFUSELOOKUP(dir_name, 0);
+ TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = 3,
+ .generation = 1,
+ .attr.ino = 100,
+ .attr.size = 4,
+ .attr.blksize = 512,
+ .attr.mode = S_IFDIR | 0777,
+ }));
+
+ TESTFUSELOOKUP(file_name, 0);
+ TESTFUSEOUTERROR(-ENOENT);
+
+ TESTFUSEINEXT(FUSE_CREATE, create_in, strlen(file_name) + 1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = 2,
+ .generation = 1,
+ .attr.ino = 200,
+ .attr.size = 4,
+ .attr.blksize = 512,
+ .attr.mode = S_IFREG,
+ }),
+ fuse_open_out, ((struct fuse_open_out) {
+ .fh = 1,
+ .open_flags = create_in->flags,
+ }));
+
+ //TESTFUSEINNULL(FUSE_CANONICAL_PATH);
+ //TESTFUSEOUTREAD("ignored", 7);
+
+ TESTFUSEIN(FUSE_FLUSH, flush_in);
+ TESTFUSEOUTEMPTY();
+
+ TESTFUSEIN(FUSE_RELEASE, release_in);
+ TESTFUSEOUTEMPTY();
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_hidden_entries(const char *mount_dir)
+{
+ static const char * const dir_names[] = {
+ "show",
+ "hide",
+ };
+ const char *file_name = "file";
+ const char *data = "The quick brown fox jumps over the lazy dog\n";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTSYSCALL(mkdirat(src_fd, dir_names[0], 0777));
+ TESTSYSCALL(mkdirat(src_fd, dir_names[1], 0777));
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_hidden",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_names[0])),
+ s(file_name)),
+ 0777),
+ fd != -1);
+ TESTSYSCALL(fallocate(fd, 0, 0, 4096));
+ TEST(write(fd, data, strlen(data)), strlen(data));
+ TESTSYSCALL(close(fd));
+ TESTEQUAL(bpf_test_trace("Create"), 0);
+
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_dir(const char *mount_dir)
+{
+ const char *dir_name = "dir";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777));
+ TESTEQUAL(bpf_test_trace("mkdir"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st));
+ TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name))));
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_file(const char *mount_dir, bool close_first)
+{
+ const char *file_name = "real";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)),
+ 0777),
+ fd != -1);
+ TESTEQUAL(bpf_test_trace("Create"), 0);
+ if (close_first) {
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ }
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st));
+ TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name))));
+ TESTEQUAL(bpf_test_trace("unlink"), 0);
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+ if (!close_first) {
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ }
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_file_early_close(const char *mount_dir)
+{
+ return bpf_test_file(mount_dir, true);
+}
+
+static int bpf_test_file_late_close(const char *mount_dir)
+{
+ return bpf_test_file(mount_dir, false);
+}
+
+static int bpf_test_alter_errcode_bpf(const char *mount_dir)
+{
+ const char *dir_name = "dir";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_error",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777));
+ //TESTEQUAL(bpf_test_trace("mkdir"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st));
+ TESTEQUAL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777), -EPERM);
+ TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name))));
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_alter_errcode_userspace(const char *mount_dir)
+{
+ const char *dir_name = "doesnotexist";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_error",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ TESTEQUAL(s_unlink(s_path(s(mount_dir), s(dir_name))),
+ -1);
+ TESTEQUAL(errno, ENOMEM);
+ } else {
+ TESTFUSELOOKUP("doesnotexist", FUSE_POSTFILTER);
+ TESTFUSEOUTERROR(-ENOMEM);
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_mknod(const char *mount_dir)
+{
+ const char *file_name = "real";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkfifo(s_path(s(mount_dir), s(file_name)), 0777));
+ TESTEQUAL(bpf_test_trace("mknod"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st));
+ TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name))));
+ TESTEQUAL(bpf_test_trace("unlink"), 0);
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_largedir(const char *mount_dir)
+{
+ const char *show = "show";
+ const int files = 1000;
+
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct map_relocation *map_relocations = NULL;
+ size_t map_count = 0;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("fd_bpf.bpf", "test_daemon",
+ &bpf_fd, &map_relocations, &map_count), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ int i;
+ int fd;
+ DIR *dir = NULL;
+ struct dirent *dirent;
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(show)), 0777));
+ for (i = 0; i < files; ++i) {
+ char filename[NAME_MAX];
+
+ sprintf(filename, "%d", i);
+ TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(show)),
+ s(filename)), 0777), fd != -1);
+ TESTSYSCALL(close(fd));
+ }
+
+ TEST(dir = s_opendir(s_path(s(mount_dir), s(show))), dir);
+ for (dirent = readdir(dir); dirent; dirent = readdir(dir))
+ ;
+ closedir(dir);
+ } else {
+ int i;
+
+ for (i = 0; i < files + 2; ++i) {
+ TESTFUSELOOKUP(show, FUSE_PREFILTER);
+ TESTFUSEOUTREAD(show, 5);
+ }
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_link(const char *mount_dir)
+{
+ const char *file_name = "real";
+ const char *link_name = "partial";
+ int result = TEST_FAILURE;
+ int fd = -1;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL,
+ NULL),
+ 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1);
+ TESTEQUAL(bpf_test_trace("Create"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st));
+
+ TESTSYSCALL(s_link(s_path(s(mount_dir), s(file_name)),
+ s_path(s(mount_dir), s(link_name))));
+
+ TESTEQUAL(bpf_test_trace("link"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(link_name)), &st));
+
+ TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(link_name))));
+ TESTEQUAL(bpf_test_trace("unlink"), 0);
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(link_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+
+ TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name))));
+ TESTEQUAL(bpf_test_trace("unlink"), 0);
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_symlink(const char *mount_dir)
+{
+ const char *test_name = "real";
+ const char *symlink_name = "partial";
+ const char *test_data = "Weebles wobble but they don't fall down";
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+ char read_buffer[256] = {};
+ ssize_t bytes_read;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777),
+ fd != -1);
+ TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_symlink(s_path(s(mount_dir), s(test_name)),
+ s_path(s(mount_dir), s(symlink_name))));
+ TESTEQUAL(bpf_test_trace("symlink"), 0);
+
+ TESTERR(fd = s_open(s_path(s(mount_dir), s(symlink_name)), O_RDONLY | O_CLOEXEC), fd != -1);
+ bytes_read = read(fd, read_buffer, strlen(test_data));
+ TESTEQUAL(bpf_test_trace("readlink"), 0);
+ TESTEQUAL(bytes_read, strlen(test_data));
+ TESTEQUAL(strcmp(test_data, read_buffer), 0);
+
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ close(fd);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+static int bpf_test_xattr(const char *mount_dir)
+{
+ static const char file_name[] = "real";
+ static const char xattr_name[] = "user.xattr_test_name";
+ static const char xattr_value[] = "this_is_a_test";
+ const size_t xattr_size = sizeof(xattr_value);
+ char xattr_value_ret[256];
+ ssize_t xattr_size_ret;
+ int result = TEST_FAILURE;
+ int fd = -1;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ memset(xattr_value_ret, '\0', sizeof(xattr_value_ret));
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL,
+ NULL),
+ 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1);
+ TESTEQUAL(bpf_test_trace("Create"), 0);
+ TESTSYSCALL(close(fd));
+
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st));
+ TEST(result = s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name,
+ xattr_value_ret, sizeof(xattr_value_ret),
+ &xattr_size_ret),
+ result == -1);
+ TESTEQUAL(errno, ENODATA);
+ TESTEQUAL(bpf_test_trace("getxattr"), 0);
+
+ TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)),
+ xattr_value_ret, sizeof(xattr_value_ret),
+ &xattr_size_ret));
+ TESTEQUAL(bpf_test_trace("listxattr"), 0);
+ TESTEQUAL(xattr_size_ret, 0);
+
+ TESTSYSCALL(s_setxattr(s_path(s(mount_dir), s(file_name)), xattr_name,
+ xattr_value, xattr_size, 0));
+ TESTEQUAL(bpf_test_trace("setxattr"), 0);
+
+ TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)),
+ xattr_value_ret, sizeof(xattr_value_ret),
+ &xattr_size_ret));
+ TESTEQUAL(bpf_test_trace("listxattr"), 0);
+ TESTEQUAL(xattr_size_ret, sizeof(xattr_name));
+ TESTEQUAL(strcmp(xattr_name, xattr_value_ret), 0);
+
+ TESTSYSCALL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name,
+ xattr_value_ret, sizeof(xattr_value_ret),
+ &xattr_size_ret));
+ TESTEQUAL(bpf_test_trace("getxattr"), 0);
+ TESTEQUAL(xattr_size, xattr_size_ret);
+ TESTEQUAL(strcmp(xattr_value, xattr_value_ret), 0);
+
+ TESTSYSCALL(s_removexattr(s_path(s(mount_dir), s(file_name)), xattr_name));
+ TESTEQUAL(bpf_test_trace("removexattr"), 0);
+
+ TESTEQUAL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name,
+ xattr_value_ret, sizeof(xattr_value_ret),
+ &xattr_size_ret), -1);
+ TESTEQUAL(errno, ENODATA);
+
+ TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name))));
+ TESTEQUAL(bpf_test_trace("unlink"), 0);
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_set_backing(const char *mount_dir)
+{
+ const char *backing_name = "backing";
+ const char *test_data = "data";
+ const char *test_name = "test";
+
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0);
+ FUSE_START_DAEMON();
+ if (action) {
+ char data[256] = {0};
+
+ TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)),
+ O_RDONLY | O_CLOEXEC), fd != -1);
+ TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data));
+ TESTCOND(!strcmp(data, test_data));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ TESTSYSCALL(umount(mount_dir));
+ } else {
+ int bpf_fd = -1;
+ int backing_fd = -1;
+
+ TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777),
+ backing_fd != -1);
+ TESTEQUAL(write(backing_fd, test_data, strlen(test_data)),
+ strlen(test_data));
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_simple",
+ &bpf_fd, NULL, NULL), 0);
+
+ TESTFUSEINIT();
+ TESTFUSELOOKUP(test_name, 0);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {0}),
+ fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ .bpf_action = FUSE_ACTION_REPLACE,
+ .bpf_fd = bpf_fd,
+ }));
+ read(fuse_dev, bytes_in, sizeof(bytes_in));
+ TESTSYSCALL(close(bpf_fd));
+ TESTSYSCALL(close(backing_fd));
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(fd);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_remove_backing(const char *mount_dir)
+{
+ const char *folder1 = "folder1";
+ const char *folder2 = "folder2";
+ const char *file = "file1";
+ const char *contents1 = "contents1";
+ const char *contents2 = "contents2";
+
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int fd = -1;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ char data[256] = {0};
+ FUSE_DECLARE_DAEMON;
+
+ /*
+ * Create folder1/file
+ * folder2/file
+ *
+ * test will install bpf into mount.
+ * bpf will postfilter root lookup to daemon.
+ * daemon will remove bpf and redirect opens on folder1 to folder2.
+ * test will open folder1/file which will be redirected to folder2.
+ * test will check no traces for file, and contents are folder2/file.
+ */
+ TESTEQUAL(bpf_clear_trace(), 0);
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777));
+ TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(file)), 0777),
+ fd != -1);
+ TESTEQUAL(write(fd, contents1, strlen(contents1)), strlen(contents1));
+ TESTSYSCALL(close(fd));
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777));
+ TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder2), s(file)), 0777),
+ fd != -1);
+ TESTEQUAL(write(fd, contents2, strlen(contents2)), strlen(contents2));
+ TESTSYSCALL(close(fd));
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_passthrough", &bpf_fd,
+ NULL, NULL), 0);
+ TESTEQUAL(mount_fuse_no_init(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ TESTERR(fd = s_open(s_pathn(3, s(mount_dir), s(folder1),
+ s(file)),
+ O_RDONLY | O_CLOEXEC), fd != -1);
+ TESTEQUAL(read(fd, data, sizeof(data)), strlen(contents2));
+ TESTCOND(!strcmp(data, contents2));
+ TESTEQUAL(bpf_test_no_trace("file"), 0);
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ TESTSYSCALL(umount(mount_dir));
+ } else {
+ struct {
+ char name[8];
+ struct fuse_entry_out feo;
+ struct fuse_entry_bpf_out febo;
+ } __packed in;
+ int backing_fd = -1;
+
+ TESTFUSEINIT();
+ TESTFUSEIN(FUSE_LOOKUP | FUSE_POSTFILTER, &in);
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {0}),
+ fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .bpf_action = FUSE_ACTION_REMOVE,
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ }));
+
+ while (read(fuse_dev, bytes_in, sizeof(bytes_in)) != -1)
+ ;
+ TESTSYSCALL(close(backing_fd));
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(fd);
+ close(src_fd);
+ close(bpf_fd);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_dir_rename(const char *mount_dir)
+{
+ const char *dir_name = "dir";
+ const char *dir_name2 = "dir2";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ struct stat st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777));
+ TESTEQUAL(bpf_test_trace("mkdir"), 0);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st));
+ TESTSYSCALL(s_rename(s_path(s(mount_dir), s(dir_name)),
+ s_path(s(mount_dir), s(dir_name2))));
+ TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1);
+ TESTEQUAL(errno, ENOENT);
+ TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name2)), &st));
+ result = TEST_SUCCESS;
+out:
+ close(fuse_dev);
+ umount(mount_dir);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_file_rename(const char *mount_dir)
+{
+ const char *dir = "dir";
+ const char *file1 = "file1";
+ const char *file2 = "file2";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir)), 0777));
+ TEST(fd = s_creat(s_pathn(3, s(mount_dir), s(dir), s(file1)), 0777),
+ fd != -1);
+ TESTSYSCALL(s_rename(s_pathn(3, s(mount_dir), s(dir), s(file1)),
+ s_pathn(3, s(mount_dir), s(dir), s(file2))));
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ umount(mount_dir);
+ close(fuse_dev);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int mmap_test(const char *mount_dir)
+{
+ const char *file = "file";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+ char *addr = NULL;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(mount_fuse(mount_dir, -1, src_fd, &fuse_dev), 0);
+ TEST(fd = s_open(s_path(s(mount_dir), s(file)),
+ O_CREAT | O_RDWR | O_CLOEXEC, 0777),
+ fd != -1);
+ TESTSYSCALL(fallocate(fd, 0, 4096, SEEK_CUR));
+ TEST(addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0),
+ addr != (void *) -1);
+ memset(addr, 'a', 4096);
+
+ result = TEST_SUCCESS;
+out:
+ munmap(addr, 4096);
+ close(fd);
+ umount(mount_dir);
+ close(fuse_dev);
+ close(src_fd);
+ return result;
+}
+
+static int readdir_perms_test(const char *mount_dir)
+{
+ int result = TEST_FAILURE;
+ struct __user_cap_header_struct uchs = { _LINUX_CAPABILITY_VERSION_3 };
+ struct __user_cap_data_struct ucds[2];
+ int src_fd = -1;
+ int fuse_dev = -1;
+ DIR *dir = NULL;
+
+ /* Must remove capabilities for this test. */
+ TESTSYSCALL(syscall(SYS_capget, &uchs, ucds));
+ ucds[0].effective &= ~(1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH);
+ TESTSYSCALL(syscall(SYS_capset, &uchs, ucds));
+
+ /* This is what we are testing in fuseland. First test without fuse, */
+ TESTSYSCALL(mkdir("test", 0111));
+ TEST(dir = opendir("test"), dir == NULL);
+ closedir(dir);
+ dir = NULL;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(mount_fuse(mount_dir, -1, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s("test")), 0111));
+ TEST(dir = s_opendir(s_path(s(mount_dir), s("test"))), dir == NULL);
+
+ result = TEST_SUCCESS;
+out:
+ ucds[0].effective |= 1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH;
+ syscall(SYS_capset, &uchs, ucds);
+
+ closedir(dir);
+ s_rmdir(s_path(s(mount_dir), s("test")));
+ umount(mount_dir);
+ close(fuse_dev);
+ close(src_fd);
+ rmdir("test");
+ return result;
+}
+
+static int inotify_test(const char *mount_dir)
+{
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ struct s dir;
+ int inotify_fd = -1;
+ int watch;
+ int fd = -1;
+ char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(mount_fuse(mount_dir, -1, src_fd, &fuse_dev), 0);
+
+ TEST(inotify_fd = inotify_init1(IN_CLOEXEC), inotify_fd != -1);
+ dir = s_path(s(mount_dir), s("dir"));
+ TESTSYSCALL(mkdir(dir.s, 0777));
+ TEST(watch = inotify_add_watch(inotify_fd, dir.s, IN_CREATE), watch);
+ TEST(fd = s_creat(s_path(s(ft_src), s("dir/file")), 0777), fd != -1);
+ // buffer will be two struct lengths, as "file" gets rounded up to the
+ // next multiple of struct inotify_event
+ TESTEQUAL(read(inotify_fd, &buffer, sizeof(buffer)),
+ sizeof(struct inotify_event) * 2);
+
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ s_unlink(s_path(s(ft_src), s("dir/file")));
+ close(inotify_fd);
+ rmdir(dir.s);
+ free(dir.s);
+ umount(mount_dir);
+ close(fuse_dev);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_statfs(const char *mount_dir)
+{
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+ struct statfs st;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TESTSYSCALL(s_statfs(s(mount_dir), &st));
+ TESTEQUAL(bpf_test_trace("statfs"), 0);
+ TESTEQUAL(st.f_type, 0x65735546);
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ umount(mount_dir);
+ close(fuse_dev);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+static int bpf_test_lseek(const char *mount_dir)
+{
+ const char *file = "real";
+ const char *test_data = "data";
+ int result = TEST_FAILURE;
+ int src_fd = -1;
+ int bpf_fd = -1;
+ int fuse_dev = -1;
+ int fd = -1;
+
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TEST(fd = openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777),
+ fd != -1);
+ TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data));
+ TESTSYSCALL(close(fd));
+ fd = -1;
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace",
+ &bpf_fd, NULL, NULL), 0);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+
+ TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC),
+ fd != -1);
+ TESTEQUAL(lseek(fd, 3, SEEK_SET), 3);
+ TESTEQUAL(bpf_test_trace("lseek"), 0);
+ TESTEQUAL(lseek(fd, 5, SEEK_END), 9);
+ TESTEQUAL(bpf_test_trace("lseek"), 0);
+ TESTEQUAL(lseek(fd, 1, SEEK_CUR), 10);
+ TESTEQUAL(bpf_test_trace("lseek"), 0);
+ TESTEQUAL(lseek(fd, 1, SEEK_DATA), 1);
+ TESTEQUAL(bpf_test_trace("lseek"), 0);
+ result = TEST_SUCCESS;
+out:
+ close(fd);
+ umount(mount_dir);
+ close(fuse_dev);
+ close(bpf_fd);
+ close(src_fd);
+ return result;
+}
+
+/*
+ * State:
+ * Original: dst/folder1/content.txt
+ * ^
+ * |
+ * |
+ * Backing: src/folder1/content.txt
+ *
+ * Step 1: open(folder1) - set backing to src/folder1
+ * Check 1: cat(content.txt) - check not receiving call on the fuse daemon
+ * and content is the same
+ * Step 2: readdirplus(dst)
+ * Check 2: cat(content.txt) - check not receiving call on the fuse daemon
+ * and content is the same
+ */
+static int bpf_test_readdirplus_not_overriding_backing(const char *mount_dir)
+{
+ const char *folder1 = "folder1";
+ const char *content_file = "content.txt";
+ const char *content = "hello world";
+
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int src_fd = -1;
+ int content_fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777));
+ TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777),
+ content_fd != -1);
+ TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content));
+ TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0);
+
+ FUSE_START_DAEMON();
+ if (action) {
+ DIR *open_mount_dir = NULL;
+ struct dirent *mount_dirent;
+ int dst_folder1_fd = -1;
+ int dst_content_fd = -1;
+ char content_buffer[12];
+
+ // Step 1: Lookup folder1
+ TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)),
+ O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1);
+
+ // Check 1: Read content file (backed)
+ TESTERR(dst_content_fd =
+ s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)),
+ O_RDONLY | O_CLOEXEC), dst_content_fd != -1);
+
+ TESTEQUAL(read(dst_content_fd, content_buffer, strlen(content)),
+ strlen(content));
+ TESTEQUAL(strncmp(content, content_buffer, strlen(content)), 0);
+
+ TESTSYSCALL(close(dst_content_fd));
+ dst_content_fd = -1;
+ TESTSYSCALL(close(dst_folder1_fd));
+ dst_folder1_fd = -1;
+ memset(content_buffer, 0, strlen(content));
+
+ // Step 2: readdir folder 1
+ TEST(open_mount_dir = s_opendir(s(mount_dir)),
+ open_mount_dir != NULL);
+ TEST(mount_dirent = readdir(open_mount_dir), mount_dirent != NULL);
+ TESTSYSCALL(closedir(open_mount_dir));
+ open_mount_dir = NULL;
+
+ // Check 2: Read content file again (must be backed)
+ TESTERR(dst_content_fd =
+ s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)),
+ O_RDONLY | O_CLOEXEC), dst_content_fd != -1);
+
+ TESTEQUAL(read(dst_content_fd, content_buffer, strlen(content)),
+ strlen(content));
+ TESTEQUAL(strncmp(content, content_buffer, strlen(content)), 0);
+
+ TESTSYSCALL(close(dst_content_fd));
+ dst_content_fd = -1;
+ } else {
+ size_t read_size = 0;
+ struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in;
+ struct fuse_read_out *read_out = NULL;
+ struct fuse_attr attr = {};
+ int backing_fd = -1;
+ DECL_FUSE_IN(open);
+ DECL_FUSE_IN(getattr);
+
+ TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO);
+
+ // Step 1: Lookup folder 1 with backing
+ TESTFUSELOOKUP(folder1, 0);
+ TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = attr.ino,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+
+ // Step 2: Open root dir
+ TESTFUSEIN(FUSE_OPENDIR, open_in);
+ TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
+ .fh = 100,
+ .open_flags = open_in->flags
+ }));
+
+ // Step 2: Handle getattr
+ TESTFUSEIN(FUSE_GETATTR, getattr_in);
+ TESTSYSCALL(s_fuse_attr(s(ft_src), &attr));
+ TESTFUSEOUT1(fuse_attr_out, ((struct fuse_attr_out) {
+ .attr_valid = UINT64_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr
+ }));
+
+ // Step 2: Handle readdirplus
+ read_size = read(fuse_dev, bytes_in, sizeof(bytes_in));
+ TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS);
+
+ struct fuse_direntplus *dirent_plus =
+ (struct fuse_direntplus *) (bytes_in + read_size);
+ struct fuse_dirent dirent;
+ struct fuse_entry_out entry_out;
+
+ read_out = (struct fuse_read_out *) (bytes_in +
+ sizeof(*in_header) +
+ sizeof(struct fuse_read_in));
+
+ TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
+
+ dirent = (struct fuse_dirent) {
+ .ino = attr.ino,
+ .off = 1,
+ .namelen = strlen(folder1),
+ .type = DT_REG
+ };
+ entry_out = (struct fuse_entry_out) {
+ .nodeid = attr.ino,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr
+ };
+ *dirent_plus = (struct fuse_direntplus) {
+ .dirent = dirent,
+ .entry_out = entry_out
+ };
+
+ strcpy((char *)(bytes_in + read_size + sizeof(*dirent_plus)), folder1);
+ read_size += FUSE_DIRENT_ALIGN(sizeof(*dirent_plus) + strlen(folder1) +
+ 1);
+ TESTFUSEDIROUTREAD(read_out,
+ bytes_in +
+ sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_read_in) +
+ sizeof(struct fuse_read_out),
+ read_size - sizeof(struct fuse_in_header) -
+ sizeof(struct fuse_read_in) -
+ sizeof(struct fuse_read_out));
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(content_fd);
+ close(src_fd);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_no_readdirplus_without_nodeid(const char *mount_dir)
+{
+ const char *folder1 = "folder1";
+ const char *folder2 = "folder2";
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int src_fd = -1;
+ int content_fd = -1;
+ int bpf_fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_readdirplus",
+ &bpf_fd, NULL, NULL), 0);
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777));
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777));
+ TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0);
+ FUSE_START_DAEMON();
+ if (action) {
+ DIR *open_dir = NULL;
+ struct dirent *dirent;
+
+ // Folder 1: Readdir with no nodeid
+ TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder1))),
+ open_dir != NULL);
+ TEST(dirent = readdir(open_dir), dirent == NULL);
+ TESTCOND(errno == EINVAL);
+ TESTSYSCALL(closedir(open_dir));
+ open_dir = NULL;
+
+ // Folder 2: Readdir with a nodeid
+ TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder2))),
+ open_dir != NULL);
+ TEST(dirent = readdir(open_dir), dirent == NULL);
+ TESTCOND(errno == EINVAL);
+ TESTSYSCALL(closedir(open_dir));
+ open_dir = NULL;
+ } else {
+ size_t read_size;
+ struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in;
+ struct fuse_attr attr = {};
+ int backing_fd = -1;
+
+ TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO);
+
+ // folder 1: Set 0 as nodeid, Expect READDIR
+ TESTFUSELOOKUP(folder1, 0);
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = 0,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ .bpf_action = FUSE_ACTION_REPLACE,
+ .bpf_fd = bpf_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+ TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0);
+ TESTEQUAL(in_header->opcode, FUSE_READDIR);
+ TESTFUSEOUTERROR(-EINVAL);
+
+ // folder 2: Set 10 as nodeid, Expect READDIRPLUS
+ TESTFUSELOOKUP(folder2, 0);
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = 10,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ .bpf_action = FUSE_ACTION_REPLACE,
+ .bpf_fd = bpf_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+ TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0);
+ TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS);
+ TESTFUSEOUTERROR(-EINVAL);
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(content_fd);
+ close(src_fd);
+ close(bpf_fd);
+ umount(mount_dir);
+ return result;
+}
+
+/*
+ * State:
+ * Original: dst/folder1/content.txt
+ * ^
+ * |
+ * |
+ * Backing: src/folder1/content.txt
+ *
+ * Step 1: open(folder1) - lookup folder1 with entry_timeout set to 0
+ * Step 2: open(folder1) - lookup folder1 again to trigger revalidate wich will
+ * set backing fd
+ *
+ * Check 1: cat(content.txt) - check not receiving call on the fuse daemon
+ * and content is the same
+ */
+static int bpf_test_revalidate_handle_backing_fd(const char *mount_dir)
+{
+ const char *folder1 = "folder1";
+ const char *content_file = "content.txt";
+ const char *content = "hello world";
+ int result = TEST_FAILURE;
+ int fuse_dev = -1;
+ int src_fd = -1;
+ int content_fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777));
+ TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777),
+ content_fd != -1);
+ TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content));
+ TESTSYSCALL(close(content_fd));
+ content_fd = -1;
+ TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0);
+ FUSE_START_DAEMON();
+ if (action) {
+ int dst_folder1_fd = -1;
+ int dst_content_fd = -1;
+ char content_buffer[9] = {0};
+ // Step 1: Lookup folder1
+ TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)),
+ O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1);
+ TESTSYSCALL(close(dst_folder1_fd));
+ dst_folder1_fd = -1;
+ // Step 2: Lookup folder1 again
+ TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)),
+ O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1);
+ TESTSYSCALL(close(dst_folder1_fd));
+ dst_folder1_fd = -1;
+ // Check 1: Read content file (must be backed)
+ TESTERR(dst_content_fd =
+ s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)),
+ O_RDONLY | O_CLOEXEC), dst_content_fd != -1);
+ TESTEQUAL(read(dst_content_fd, content_buffer, strlen(content)),
+ strlen(content));
+ TESTEQUAL(strncmp(content, content_buffer, strlen(content)), 0);
+ TESTSYSCALL(close(dst_content_fd));
+ dst_content_fd = -1;
+ } else {
+ struct fuse_attr attr = {};
+ int backing_fd = -1;
+
+ TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO);
+ // Step 1: Lookup folder1 set entry_timeout to 0 to trigger
+ // revalidate later
+ TESTFUSELOOKUP(folder1, 0);
+ TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = attr.ino,
+ .generation = 0,
+ .entry_valid = 0,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = 0,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+ // Step 1: Lookup folder1 as a reaction to revalidate call
+ // This attempts to change the backing node, which is not allowed on revalidate
+ TESTFUSELOOKUP(folder1, 0);
+ TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = attr.ino,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+
+ // Lookup folder1 as a reaction to failed revalidate
+ TESTFUSELOOKUP(folder1, 0);
+ TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
+ TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ backing_fd != -1);
+ TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
+ .nodeid = attr.ino,
+ .generation = 0,
+ .entry_valid = UINT64_MAX,
+ .attr_valid = UINT64_MAX,
+ .entry_valid_nsec = UINT32_MAX,
+ .attr_valid_nsec = UINT32_MAX,
+ .attr = attr,
+ }), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
+ .backing_action = FUSE_ACTION_REPLACE,
+ .backing_fd = backing_fd,
+ }));
+ TESTSYSCALL(close(backing_fd));
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(fuse_dev);
+ close(content_fd);
+ close(src_fd);
+ umount(mount_dir);
+ return result;
+}
+
+static int bpf_test_lookup_postfilter(const char *mount_dir)
+{
+ const char *file1_name = "file1";
+ const char *file2_name = "file2";
+ const char *file3_name = "file3";
+ int result = TEST_FAILURE;
+ int bpf_fd = -1;
+ int src_fd = -1;
+ int fuse_dev = -1;
+ int file_fd = -1;
+ FUSE_DECLARE_DAEMON;
+
+ TEST(file_fd = s_creat(s_path(s(ft_src), s(file1_name)), 0777),
+ file_fd != -1);
+ TESTSYSCALL(close(file_fd));
+ TEST(file_fd = s_creat(s_path(s(ft_src), s(file2_name)), 0777),
+ file_fd != -1);
+ TESTSYSCALL(close(file_fd));
+ file_fd = -1;
+ TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_lookup_postfilter",
+ &bpf_fd, NULL, NULL), 0);
+ TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+ src_fd != -1);
+ TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
+ FUSE_START_DAEMON();
+ if (action) {
+ int fd = -1;
+
+ TESTEQUAL(s_open(s_path(s(mount_dir), s(file1_name)), O_RDONLY),
+ -1);
+ TESTEQUAL(errno, ENOENT);
+ TEST(fd = s_open(s_path(s(mount_dir), s(file2_name)), O_RDONLY),
+ fd != -1);
+ TESTSYSCALL(close(fd));
+ TESTEQUAL(s_open(s_path(s(mount_dir), s(file3_name)), O_RDONLY),
+ -1);
+ } else {
+ struct fuse_in_postfilter_header *in_header =
+ (struct fuse_in_postfilter_header *)bytes_in;
+ struct fuse_entry_out *feo;
+ struct fuse_entry_bpf_out *febo;
+
+ TESTFUSELOOKUP(file1_name, FUSE_POSTFILTER);
+ TESTFUSEOUTERROR(-ENOENT);
+
+ TESTFUSELOOKUP(file2_name, FUSE_POSTFILTER);
+ feo = (struct fuse_entry_out *) (bytes_in +
+ sizeof(struct fuse_in_header) + strlen(file2_name) + 1);
+ febo = (struct fuse_entry_bpf_out *) ((char *)feo +
+ sizeof(*feo));
+ TESTFUSEOUT2(fuse_entry_out, *feo, fuse_entry_bpf_out, *febo);
+
+ TESTFUSELOOKUP(file3_name, FUSE_POSTFILTER);
+ TESTEQUAL(in_header->error_in, -ENOENT);
+ TESTFUSEOUTERROR(-ENOENT);
+ exit(TEST_SUCCESS);
+ }
+ FUSE_END_DAEMON();
+ close(file_fd);
+ close(fuse_dev);
+ umount(mount_dir);
+ close(src_fd);
+ close(bpf_fd);
+ return result;
+}
+
+static void parse_range(const char *ranges, bool *run_test, size_t tests)
+{
+ size_t i;
+ char *range;
+
+ for (i = 0; i < tests; ++i)
+ run_test[i] = false;
+
+ range = strtok(optarg, ",");
+ while (range) {
+ char *dash = strchr(range, '-');
+
+ if (dash) {
+ size_t start = 1, end = tests;
+ char *end_ptr;
+
+ if (dash > range) {
+ start = strtol(range, &end_ptr, 10);
+ if (*end_ptr != '-' || start <= 0 || start > tests)
+ ksft_exit_fail_msg("Bad range\n");
+ }
+
+ if (dash[1]) {
+ end = strtol(dash + 1, &end_ptr, 10);
+ if (*end_ptr || end <= start || end > tests)
+ ksft_exit_fail_msg("Bad range\n");
+ }
+
+ for (i = start; i <= end; ++i)
+ run_test[i - 1] = true;
+ } else {
+ char *end;
+ long value = strtol(range, &end, 10);
+
+ if (*end || value <= 0 || value > tests)
+ ksft_exit_fail_msg("Bad range\n");
+ run_test[value - 1] = true;
+ }
+ range = strtok(NULL, ",");
+ }
+}
+
+static int parse_options(int argc, char *const *argv, bool *run_test,
+ size_t tests)
+{
+ signed char c;
+
+ while ((c = getopt(argc, argv, "f:t:v")) != -1)
+ switch (c) {
+ case 'f':
+ test_options.file = strtol(optarg, NULL, 10);
+ break;
+
+ case 't':
+ parse_range(optarg, run_test, tests);
+ break;
+
+ case 'v':
+ test_options.verbose = true;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct test_case {
+ int (*pfunc)(const char *dir);
+ const char *name;
+};
+
+static void run_one_test(const char *mount_dir,
+ const struct test_case *test_case)
+{
+ ksft_print_msg("Running %s\n", test_case->name);
+ if (test_case->pfunc(mount_dir) == TEST_SUCCESS)
+ ksft_test_result_pass("%s\n", test_case->name);
+ else
+ ksft_test_result_fail("%s\n", test_case->name);
+}
+
+int main(int argc, char *argv[])
+{
+ char *mount_dir = NULL;
+ char *src_dir = NULL;
+ int i;
+ int fd, count;
+
+#define MAKE_TEST(test) \
+ { \
+ test, #test \
+ }
+ const struct test_case cases[] = {
+ MAKE_TEST(basic_test),
+ MAKE_TEST(bpf_test_real),
+ MAKE_TEST(bpf_test_partial),
+ MAKE_TEST(bpf_test_attrs),
+ MAKE_TEST(bpf_test_readdir),
+ MAKE_TEST(bpf_test_creat),
+ MAKE_TEST(bpf_test_hidden_entries),
+ MAKE_TEST(bpf_test_dir),
+ MAKE_TEST(bpf_test_file_early_close),
+ MAKE_TEST(bpf_test_file_late_close),
+ MAKE_TEST(bpf_test_mknod),
+ MAKE_TEST(bpf_test_largedir),
+ MAKE_TEST(bpf_test_link),
+ MAKE_TEST(bpf_test_symlink),
+ MAKE_TEST(bpf_test_xattr),
+ MAKE_TEST(bpf_test_redact_readdir),
+ MAKE_TEST(bpf_test_set_backing),
+ MAKE_TEST(bpf_test_remove_backing),
+ MAKE_TEST(bpf_test_dir_rename),
+ MAKE_TEST(bpf_test_file_rename),
+ MAKE_TEST(bpf_test_alter_errcode_bpf),
+ MAKE_TEST(bpf_test_alter_errcode_userspace),
+ MAKE_TEST(mmap_test),
+ MAKE_TEST(readdir_perms_test),
+ MAKE_TEST(inotify_test),
+ MAKE_TEST(bpf_test_statfs),
+ MAKE_TEST(bpf_test_lseek),
+ MAKE_TEST(bpf_test_readdirplus_not_overriding_backing),
+ MAKE_TEST(bpf_test_no_readdirplus_without_nodeid),
+ MAKE_TEST(bpf_test_revalidate_handle_backing_fd),
+ MAKE_TEST(bpf_test_lookup_postfilter),
+ };
+#undef MAKE_TEST
+
+ bool run_test[ARRAY_SIZE(cases)];
+
+ for (int i = 0; i < ARRAY_SIZE(cases); ++i)
+ run_test[i] = true;
+
+ if (parse_options(argc, argv, run_test, ARRAY_SIZE(cases)))
+ ksft_exit_fail_msg("Bad options\n");
+
+ // Seed randomness pool for testing on QEMU
+ // NOTE - this abuses the concept of randomness - do *not* ever do this
+ // on a machine for production use - the device will think it has good
+ // randomness when it does not.
+ fd = open("/dev/urandom", O_WRONLY | O_CLOEXEC);
+ count = 4096;
+ for (int i = 0; i < 128; ++i)
+ ioctl(fd, RNDADDTOENTCNT, &count);
+ close(fd);
+
+ ksft_print_header();
+
+ if (geteuid() != 0)
+ ksft_print_msg("Not a root, might fail to mount.\n");
+
+ if (tracing_on() != TEST_SUCCESS)
+ ksft_exit_fail_msg("Can't turn on tracing\n");
+
+ src_dir = setup_mount_dir(ft_src);
+ mount_dir = setup_mount_dir(ft_dst);
+ if (src_dir == NULL || mount_dir == NULL)
+ ksft_exit_fail_msg("Can't create a mount dir\n");
+
+ ksft_set_plan(ARRAY_SIZE(run_test));
+
+ for (i = 0; i < ARRAY_SIZE(run_test); ++i)
+ if (run_test[i]) {
+ delete_dir_tree(mount_dir, false);
+ delete_dir_tree(src_dir, false);
+ run_one_test(mount_dir, &cases[i]);
+ } else
+ ksft_cnt.ksft_xskip++;
+
+ umount2(mount_dir, MNT_FORCE);
+ delete_dir_tree(mount_dir, true);
+ delete_dir_tree(src_dir, true);
+ return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail();
+}
diff --git a/tools/testing/selftests/filesystems/fuse/test_bpf.c b/tools/testing/selftests/filesystems/fuse/test_bpf.c
new file mode 100644
index 0000000..032cb11
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/test_bpf.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+// Copyright (c) 2022 Google LLC
+
+#include "test_fuse_bpf.h"
+
+SEC("test_readdir_redact")
+/* return FUSE_BPF_BACKING to use backing fs, 0 to pass to usermode */
+int readdir_test(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_READDIR | FUSE_PREFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+
+ bpf_printk("readdir %d", fri->fh);
+ return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ }
+
+ case FUSE_READDIR | FUSE_POSTFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+
+ bpf_printk("readdir postfilter %x", fri->fh);
+ return FUSE_BPF_USER_FILTER;
+ }
+
+ default:
+ bpf_printk("opcode %d", fa->opcode);
+ return FUSE_BPF_BACKING;
+ }
+}
+
+SEC("test_trace")
+/* return FUSE_BPF_BACKING to use backing fs, 0 to pass to usermode */
+int trace_test(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ /* real and partial use backing file */
+ const char *name = fa->in_args[0].value;
+ bool backing = false;
+
+ if (strcmp(name, "real") == 0 || strcmp(name, "partial") == 0)
+ backing = true;
+
+ if (strcmp(name, "dir") == 0)
+ backing = true;
+ if (strcmp(name, "dir2") == 0)
+ backing = true;
+
+ if (strcmp(name, "file1") == 0)
+ backing = true;
+ if (strcmp(name, "file2") == 0)
+ backing = true;
+
+ bpf_printk("lookup %s %d", name, backing);
+ return backing ? FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER : 0;
+ }
+
+ case FUSE_LOOKUP | FUSE_POSTFILTER: {
+ const char *name = fa->in_args[0].value;
+ struct fuse_entry_out *feo = fa->out_args[0].value;
+
+ if (strcmp(name, "real") == 0)
+ feo->nodeid = 5;
+ else if (strcmp(name, "partial") == 0)
+ feo->nodeid = 6;
+
+ bpf_printk("post-lookup %s %d", name, feo->nodeid);
+ return 0;
+ }
+
+ case FUSE_ACCESS | FUSE_PREFILTER: {
+ bpf_printk("Access: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_CREATE | FUSE_PREFILTER:
+ bpf_printk("Create: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+
+ case FUSE_MKNOD | FUSE_PREFILTER: {
+ const struct fuse_mknod_in *fmi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("mknod %s %x %x", name, fmi->rdev | fmi->mode, fmi->umask);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_MKDIR | FUSE_PREFILTER: {
+ const struct fuse_mkdir_in *fmi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("mkdir %s %x %x", name, fmi->mode, fmi->umask);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RMDIR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("rmdir %s", name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RENAME | FUSE_PREFILTER: {
+ const char *oldname = fa->in_args[1].value;
+ const char *newname = fa->in_args[2].value;
+
+ bpf_printk("rename from %s", oldname);
+ bpf_printk("rename to %s", newname);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RENAME2 | FUSE_PREFILTER: {
+ const struct fuse_rename2_in *fri = fa->in_args[0].value;
+ uint32_t flags = fri->flags;
+ const char *oldname = fa->in_args[1].value;
+ const char *newname = fa->in_args[2].value;
+
+ bpf_printk("rename(%x) from %s", flags, oldname);
+ bpf_printk("rename to %s", newname);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_UNLINK | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("unlink %s", name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LINK | FUSE_PREFILTER: {
+ const struct fuse_link_in *fli = fa->in_args[0].value;
+ const char *link_name = fa->in_args[1].value;
+
+ bpf_printk("link %d %s", fli->oldnodeid, link_name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_SYMLINK | FUSE_PREFILTER: {
+ const char *link_name = fa->in_args[0].value;
+ const char *link_dest = fa->in_args[1].value;
+
+ bpf_printk("symlink from %s", link_name);
+ bpf_printk("symlink to %s", link_dest);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_READLINK | FUSE_PREFILTER: {
+ const char *link_name = fa->in_args[0].value;
+
+ bpf_printk("readlink from", link_name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_OPEN | FUSE_PREFILTER: {
+ int backing = 0;
+
+ switch (fa->nodeid) {
+ case 5:
+ backing = FUSE_BPF_BACKING;
+ break;
+
+ case 6:
+ backing = FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ break;
+
+ default:
+ break;
+ }
+
+ bpf_printk("open %d %d", fa->nodeid, backing);
+ return backing;
+ }
+
+ case FUSE_OPEN | FUSE_POSTFILTER:
+ bpf_printk("open postfilter");
+ return FUSE_BPF_USER_FILTER;
+
+ case FUSE_READ | FUSE_PREFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+
+ bpf_printk("read %llu %llu", fri->fh, fri->offset);
+ if (fri->fh == 1 && fri->offset == 0)
+ return 0;
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_GETATTR | FUSE_PREFILTER: {
+ /* real and partial use backing file */
+ int backing = 0;
+
+ switch (fa->nodeid) {
+ case 1:
+ case 5:
+ case 6:
+ /*
+ * TODO: Find better solution
+ * Add 100 to stop clang compiling to jump table which bpf hates
+ */
+ case 100:
+ backing = FUSE_BPF_BACKING;
+ break;
+ }
+
+ bpf_printk("getattr %d %d", fa->nodeid, backing);
+ return backing;
+ }
+
+ case FUSE_SETATTR | FUSE_PREFILTER: {
+ /* real and partial use backing file */
+ int backing = 0;
+
+ switch (fa->nodeid) {
+ case 1:
+ case 5:
+ case 6:
+ /* TODO See above */
+ case 100:
+ backing = FUSE_BPF_BACKING;
+ break;
+ }
+
+ bpf_printk("setattr %d %d", fa->nodeid, backing);
+ return backing;
+ }
+
+ case FUSE_OPENDIR | FUSE_PREFILTER: {
+ int backing = 0;
+
+ switch (fa->nodeid) {
+ case 1:
+ backing = FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ break;
+ }
+
+ bpf_printk("opendir %d %d", fa->nodeid, backing);
+ return backing;
+ }
+
+ case FUSE_OPENDIR | FUSE_POSTFILTER: {
+ struct fuse_open_out *foo = fa->out_args[0].value;
+
+ foo->fh = 2;
+ bpf_printk("opendir postfilter");
+ return 0;
+ }
+
+ case FUSE_READDIR | FUSE_PREFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+ int backing = 0;
+
+ if (fri->fh == 2)
+ backing = FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+
+ bpf_printk("readdir %d %d", fri->fh, backing);
+ return backing;
+ }
+
+ case FUSE_READDIR | FUSE_POSTFILTER: {
+ const struct fuse_read_in *fri = fa->in_args[0].value;
+ int backing = 0;
+
+ if (fri->fh == 2)
+ backing = FUSE_BPF_USER_FILTER | FUSE_BPF_BACKING |
+ FUSE_BPF_POST_FILTER;
+
+ bpf_printk("readdir postfilter %d %d", fri->fh, backing);
+ return backing;
+ }
+
+ case FUSE_FLUSH | FUSE_PREFILTER: {
+ const struct fuse_flush_in *ffi = fa->in_args[0].value;
+
+ bpf_printk("Flush %d", ffi->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_GETXATTR | FUSE_PREFILTER: {
+ const struct fuse_flush_in *ffi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("getxattr %d %s", ffi->fh, name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LISTXATTR | FUSE_PREFILTER: {
+ const struct fuse_flush_in *ffi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+
+ bpf_printk("listxattr %d %s", ffi->fh, name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_SETXATTR | FUSE_PREFILTER: {
+ const struct fuse_flush_in *ffi = fa->in_args[0].value;
+ const char *name = fa->in_args[1].value;
+ unsigned int size = fa->in_args[2].size;
+
+ bpf_printk("setxattr %d %s %u", ffi->fh, name, size);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_REMOVEXATTR | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("removexattr %s", name);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_CANONICAL_PATH | FUSE_PREFILTER: {
+ bpf_printk("canonical_path");
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_STATFS | FUSE_PREFILTER: {
+ bpf_printk("statfs");
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_LSEEK | FUSE_PREFILTER: {
+ const struct fuse_lseek_in *fli = fa->in_args[0].value;
+
+ bpf_printk("lseek type:%d, offset:%lld", fli->whence, fli->offset);
+ return FUSE_BPF_BACKING;
+ }
+
+ default:
+ bpf_printk("Unknown opcode %d", fa->opcode);
+ return 0;
+ }
+}
+
+SEC("test_hidden")
+int trace_hidden(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("Lookup: %s", name);
+ if (!strcmp(name, "show"))
+ return FUSE_BPF_BACKING;
+ if (!strcmp(name, "hide"))
+ return -ENOENT;
+
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_ACCESS | FUSE_PREFILTER: {
+ bpf_printk("Access: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_CREATE | FUSE_PREFILTER:
+ bpf_printk("Create: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+
+ case FUSE_WRITE | FUSE_PREFILTER:
+ // TODO: Clang combines similar printk calls, causing BPF to complain
+ // bpf_printk("Write: %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+
+ case FUSE_FLUSH | FUSE_PREFILTER: {
+ // const struct fuse_flush_in *ffi = fa->in_args[0].value;
+
+ // bpf_printk("Flush %d", ffi->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_RELEASE | FUSE_PREFILTER: {
+ // const struct fuse_release_in *fri = fa->in_args[0].value;
+
+ // bpf_printk("Release %d", fri->fh);
+ return FUSE_BPF_BACKING;
+ }
+
+ case FUSE_FALLOCATE | FUSE_PREFILTER:
+ // bpf_printk("fallocate %d", fa->nodeid);
+ return FUSE_BPF_BACKING;
+
+ case FUSE_CANONICAL_PATH | FUSE_PREFILTER: {
+ return FUSE_BPF_BACKING;
+ }
+ default:
+ bpf_printk("Unknown opcode: %d", fa->opcode);
+ return 0;
+ }
+}
+
+SEC("test_simple")
+int trace_simple(struct fuse_bpf_args *fa)
+{
+ if (fa->opcode & FUSE_PREFILTER)
+ bpf_printk("prefilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else if (fa->opcode & FUSE_POSTFILTER)
+ bpf_printk("postfilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else
+ bpf_printk("*** UNKNOWN *** opcode: %d", fa->opcode);
+ return FUSE_BPF_BACKING;
+}
+
+SEC("test_passthrough")
+int trace_daemon(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("Lookup prefilter: %lx %s", fa->nodeid, name);
+ return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ }
+
+ case FUSE_LOOKUP | FUSE_POSTFILTER: {
+ const char *name = fa->in_args[0].value;
+ struct fuse_entry_bpf_out *febo = fa->out_args[1].value;
+
+ bpf_printk("Lookup postfilter: %lx %s %lu", fa->nodeid, name);
+ febo->bpf_action = FUSE_ACTION_REMOVE;
+
+ return FUSE_BPF_USER_FILTER;
+ }
+
+ default:
+ if (fa->opcode & FUSE_PREFILTER)
+ bpf_printk("prefilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else if (fa->opcode & FUSE_POSTFILTER)
+ bpf_printk("postfilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else
+ bpf_printk("*** UNKNOWN *** opcode: %d", fa->opcode);
+ return FUSE_BPF_BACKING;
+ }
+}
+
+SEC("test_error")
+/* return FUSE_BPF_BACKING to use backing fs, 0 to pass to usermode */
+int error_test(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_MKDIR | FUSE_PREFILTER: {
+ bpf_printk("mkdir");
+ return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ }
+ case FUSE_MKDIR | FUSE_POSTFILTER: {
+ bpf_printk("mkdir postfilter");
+ if (fa->error_in == -EEXIST)
+ return -EPERM;
+
+ return 0;
+ }
+
+ case FUSE_LOOKUP | FUSE_PREFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("lookup prefilter %s", name);
+ return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ }
+ case FUSE_LOOKUP | FUSE_POSTFILTER: {
+ const char *name = fa->in_args[0].value;
+
+ bpf_printk("lookup postfilter %s %d", name, fa->error_in);
+ if (strcmp(name, "doesnotexist") == 0/* && fa->error_in == -EEXIST*/) {
+ bpf_printk("lookup postfilter doesnotexist");
+ return FUSE_BPF_USER_FILTER;
+ }
+ bpf_printk("meh");
+ return 0;
+ }
+
+ default:
+ if (fa->opcode & FUSE_PREFILTER)
+ bpf_printk("prefilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else if (fa->opcode & FUSE_POSTFILTER)
+ bpf_printk("postfilter opcode: %d",
+ fa->opcode & FUSE_OPCODE_FILTER);
+ else
+ bpf_printk("*** UNKNOWN *** opcode: %d", fa->opcode);
+ return FUSE_BPF_BACKING;
+ }
+}
+
+SEC("test_readdirplus")
+int readdirplus_test(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_READDIR | FUSE_PREFILTER: {
+ return 0;
+ }
+ }
+ return FUSE_BPF_BACKING;
+}
+
+SEC("test_lookup_postfilter")
+int lookuppostfilter_test(struct fuse_bpf_args *fa)
+{
+ switch (fa->opcode) {
+ case FUSE_LOOKUP | FUSE_PREFILTER:
+ return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
+ case FUSE_LOOKUP | FUSE_POSTFILTER:
+ return FUSE_BPF_USER_FILTER;
+ default:
+ return FUSE_BPF_BACKING;
+ }
+}
diff --git a/tools/testing/selftests/filesystems/fuse/test_framework.h b/tools/testing/selftests/filesystems/fuse/test_framework.h
new file mode 100644
index 0000000..efc6f53
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/test_framework.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Google LLC
+ */
+
+#ifndef _TEST_FRAMEWORK_H
+#define _TEST_FRAMEWORK_H
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <linux/compiler.h>
+
+#ifdef __ANDROID__
+static int test_case_pass;
+static int test_case_fail;
+#define ksft_print_msg printf
+#define ksft_test_result_pass(...) ({test_case_pass++; printf(__VA_ARGS__); })
+#define ksft_test_result_fail(...) ({test_case_fail++; printf(__VA_ARGS__); })
+#define ksft_exit_fail_msg(...) printf(__VA_ARGS__)
+#define ksft_print_header()
+#define ksft_set_plan(cnt)
+#define ksft_get_fail_cnt() test_case_fail
+#define ksft_exit_pass() 0
+#define ksft_exit_fail() 1
+#else
+#include <kselftest.h>
+#endif
+
+#define TEST_FAILURE 1
+#define TEST_SUCCESS 0
+
+#define ptr_to_u64(p) ((__u64)p)
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define le16_to_cpu(x) (x)
+#define le32_to_cpu(x) (x)
+#define le64_to_cpu(x) (x)
+#else
+#error Big endian not supported!
+#endif
+
+struct _test_options {
+ int file;
+ bool verbose;
+};
+
+extern struct _test_options test_options;
+
+#define TESTCOND(condition) \
+ do { \
+ if (!(condition)) { \
+ ksft_print_msg("%s failed %d\n", \
+ __func__, __LINE__); \
+ goto out; \
+ } else if (test_options.verbose) \
+ ksft_print_msg("%s succeeded %d\n", \
+ __func__, __LINE__); \
+ } while (false)
+
+#define TESTCONDERR(condition) \
+ do { \
+ if (!(condition)) { \
+ ksft_print_msg("%s failed %d\n", \
+ __func__, __LINE__); \
+ ksft_print_msg("Error %d (\"%s\")\n", \
+ errno, strerror(errno)); \
+ goto out; \
+ } else if (test_options.verbose) \
+ ksft_print_msg("%s succeeded %d\n", \
+ __func__, __LINE__); \
+ } while (false)
+
+#define TEST(statement, condition) \
+ do { \
+ statement; \
+ TESTCOND(condition); \
+ } while (false)
+
+#define TESTERR(statement, condition) \
+ do { \
+ statement; \
+ TESTCONDERR(condition); \
+ } while (false)
+
+enum _operator {
+ _eq,
+ _ne,
+ _ge,
+};
+
+static const char * const _operator_name[] = {
+ "==",
+ "!=",
+ ">=",
+};
+
+#define _TEST_OPERATOR(name, _type, format_specifier) \
+static inline int _test_operator_##name(const char *func, int line, \
+ _type a, _type b, enum _operator o) \
+{ \
+ bool pass; \
+ switch (o) { \
+ case _eq: \
+ pass = a == b; \
+ break; \
+ case _ne: \
+ pass = a != b; \
+ break; \
+ case _ge: \
+ pass = a >= b; \
+ break; \
+ } \
+ \
+ if (!pass) \
+ ksft_print_msg("Failed: %s at line %d, " \
+ format_specifier " %s " \
+ format_specifier "\n", \
+ func, line, a, _operator_name[o], b); \
+ else if (test_options.verbose) \
+ ksft_print_msg("Passed: %s at line %d, " \
+ format_specifier " %s " \
+ format_specifier "\n", \
+ func, line, a, _operator_name[o], b); \
+ \
+ return pass ? TEST_SUCCESS : TEST_FAILURE; \
+}
+
+_TEST_OPERATOR(i, int, "%d")
+_TEST_OPERATOR(ui, unsigned int, "%u")
+_TEST_OPERATOR(lui, unsigned long, "%lu")
+_TEST_OPERATOR(ss, ssize_t, "%zd")
+_TEST_OPERATOR(vp, void *, "%px")
+_TEST_OPERATOR(cp, char *, "%px")
+
+#define _CALL_TO(_type, name, a, b, o) \
+ _test_operator_##name(__func__, __LINE__, \
+ (_type) (long long) (a), \
+ (_type) (long long) (b), o)
+
+#define TESTOPERATOR(a, b, o) \
+ do { \
+ if (_Generic((a), \
+ int : _CALL_TO(int, i, a, b, o), \
+ unsigned int : _CALL_TO(unsigned int, ui, a, b, o), \
+ unsigned long : _CALL_TO(unsigned long, lui, a, b, o), \
+ ssize_t : _CALL_TO(ssize_t, ss, a, b, o), \
+ void * : _CALL_TO(void *, vp, a, b, o), \
+ char * : _CALL_TO(char *, cp, a, b, o) \
+ )) \
+ goto out; \
+ } while (false)
+
+#define TESTEQUAL(a, b) TESTOPERATOR(a, b, _eq)
+#define TESTNE(a, b) TESTOPERATOR(a, b, _ne)
+#define TESTGE(a, b) TESTOPERATOR(a, b, _ge)
+
+/* For testing a syscall that returns 0 on success and sets errno otherwise */
+#define TESTSYSCALL(statement) TESTCONDERR((statement) == 0)
+
+static inline void print_bytes(const void *data, size_t size)
+{
+ const char *bytes = data;
+ int i;
+
+ for (i = 0; i < size; ++i) {
+ if (i % 0x10 == 0)
+ printf("%08x:", i);
+ printf("%02x ", (unsigned int) (unsigned char) bytes[i]);
+ if (i % 0x10 == 0x0f)
+ printf("\n");
+ }
+
+ if (i % 0x10 != 0)
+ printf("\n");
+}
+
+
+
+#endif
diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse.h b/tools/testing/selftests/filesystems/fuse/test_fuse.h
new file mode 100644
index 0000000..69dadc9
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/test_fuse.h
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Google LLC
+ */
+
+#ifndef TEST_FUSE__H
+#define TEST_FUSE__H
+
+#define _GNU_SOURCE
+
+#include "test_framework.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+
+#include <include/uapi/linux/android_fuse.h>
+#include <include/uapi/linux/fuse.h>
+
+#define PAGE_SIZE 4096
+#define FUSE_POSTFILTER 0x20000
+
+extern struct _test_options test_options;
+
+/* Slow but semantically easy string functions */
+
+/*
+ * struct s just wraps a char pointer
+ * It is a pointer to a malloc'd string, or null
+ * All consumers handle null input correctly
+ * All consumers free the string
+ */
+struct s {
+ char *s;
+};
+
+struct s s(const char *s1);
+struct s sn(const char *s1, const char *s2);
+int s_cmp(struct s s1, struct s s2);
+struct s s_cat(struct s s1, struct s s2);
+struct s s_splitleft(struct s s1, char c);
+struct s s_splitright(struct s s1, char c);
+struct s s_word(struct s s1, char c, size_t n);
+struct s s_path(struct s s1, struct s s2);
+struct s s_pathn(size_t n, struct s s1, ...);
+int s_link(struct s src_pathname, struct s dst_pathname);
+int s_symlink(struct s src_pathname, struct s dst_pathname);
+int s_mkdir(struct s pathname, mode_t mode);
+int s_rmdir(struct s pathname);
+int s_unlink(struct s pathname);
+int s_open(struct s pathname, int flags, ...);
+int s_openat(int dirfd, struct s pathname, int flags, ...);
+int s_creat(struct s pathname, mode_t mode);
+int s_mkfifo(struct s pathname, mode_t mode);
+int s_stat(struct s pathname, struct stat *st);
+int s_statfs(struct s pathname, struct statfs *st);
+int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out);
+DIR *s_opendir(struct s pathname);
+int s_getxattr(struct s pathname, const char name[], void *value, size_t size,
+ ssize_t *ret_size);
+int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size);
+int s_setxattr(struct s pathname, const char name[], const void *value,
+ size_t size, int flags);
+int s_removexattr(struct s pathname, const char name[]);
+int s_rename(struct s oldpathname, struct s newpathname);
+
+struct s tracing_folder(void);
+int tracing_on(void);
+
+char *concat_file_name(const char *dir, const char *file);
+char *setup_mount_dir(const char *name);
+int delete_dir_tree(const char *dir_path, bool remove_root);
+
+#define TESTFUSEINNULL(_opcode) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ ssize_t res = read(fuse_dev, &bytes_in, \
+ sizeof(bytes_in)); \
+ \
+ TESTEQUAL(in_header->opcode, _opcode); \
+ TESTEQUAL(res, sizeof(*in_header)); \
+ } while (false)
+
+#define TESTFUSEIN(_opcode, in_struct) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ ssize_t res = read(fuse_dev, &bytes_in, \
+ sizeof(bytes_in)); \
+ \
+ TESTEQUAL(in_header->opcode, _opcode); \
+ TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct));\
+ } while (false)
+
+#define TESTFUSEIN2(_opcode, in_struct1, in_struct2) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ ssize_t res = read(fuse_dev, &bytes_in, \
+ sizeof(bytes_in)); \
+ \
+ TESTEQUAL(in_header->opcode, _opcode); \
+ TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \
+ + sizeof(*in_struct2)); \
+ in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \
+ in_struct2 = (void *)(bytes_in + sizeof(*in_header) \
+ + sizeof(*in_struct1)); \
+ } while (false)
+
+#define TESTFUSEINEXT(_opcode, in_struct, extra) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ ssize_t res = read(fuse_dev, &bytes_in, \
+ sizeof(bytes_in)); \
+ \
+ TESTEQUAL(in_header->opcode, _opcode); \
+ TESTEQUAL(res, \
+ sizeof(*in_header) + sizeof(*in_struct) + extra);\
+ } while (false)
+
+#define TESTFUSEINUNKNOWN() \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ ssize_t res = read(fuse_dev, &bytes_in, \
+ sizeof(bytes_in)); \
+ \
+ TESTGE(res, sizeof(*in_header)); \
+ TESTEQUAL(in_header->opcode, -1); \
+ } while (false)
+
+/* Special case lookup since it is asymmetric */
+#define TESTFUSELOOKUP(expected, filter) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ char *name = (char *) (bytes_in + sizeof(*in_header)); \
+ ssize_t res; \
+ \
+ TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \
+ res != -1); \
+ /* TODO once we handle forgets properly, remove */ \
+ if (in_header->opcode == FUSE_FORGET) \
+ continue; \
+ if (in_header->opcode == FUSE_BATCH_FORGET) \
+ continue; \
+ TESTGE(res, sizeof(*in_header)); \
+ TESTEQUAL(in_header->opcode, \
+ FUSE_LOOKUP | filter); \
+ TESTEQUAL(res, \
+ sizeof(*in_header) + strlen(expected) + 1 + \
+ (filter == FUSE_POSTFILTER ? \
+ sizeof(struct fuse_entry_out) + \
+ sizeof(struct fuse_entry_bpf_out) : 0));\
+ TESTCOND(!strcmp(name, expected)); \
+ break; \
+ } while (true)
+
+#define TESTFUSEOUTEMPTY() \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ struct fuse_out_header *out_header = \
+ (struct fuse_out_header *)bytes_out; \
+ \
+ *out_header = (struct fuse_out_header) { \
+ .len = sizeof(*out_header), \
+ .unique = in_header->unique, \
+ }; \
+ TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
+ out_header->len); \
+ } while (false)
+
+#define TESTFUSEOUTERROR(errno) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ struct fuse_out_header *out_header = \
+ (struct fuse_out_header *)bytes_out; \
+ \
+ *out_header = (struct fuse_out_header) { \
+ .len = sizeof(*out_header), \
+ .error = errno, \
+ .unique = in_header->unique, \
+ }; \
+ TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
+ out_header->len); \
+ } while (false)
+
+#define TESTFUSEOUTREAD(data, length) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ struct fuse_out_header *out_header = \
+ (struct fuse_out_header *)bytes_out; \
+ \
+ *out_header = (struct fuse_out_header) { \
+ .len = sizeof(*out_header) + length, \
+ .unique = in_header->unique, \
+ }; \
+ memcpy(bytes_out + sizeof(*out_header), data, length); \
+ TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
+ out_header->len); \
+ } while (false)
+
+#define TESTFUSEDIROUTREAD(read_out, data, length) \
+ do { \
+ struct fuse_in_header *in_header = \
+ (struct fuse_in_header *)bytes_in; \
+ struct fuse_out_header *out_header = \
+ (struct fuse_out_header *)bytes_out; \
+ \
+ *out_header = (struct fuse_out_header) { \
+ .len = sizeof(*out_header) + \
+ sizeof(*read_out) + length, \
+ .unique = in_header->unique, \
+ }; \
+ memcpy(bytes_out + sizeof(*out_header) + \
+ sizeof(*read_out), data, length); \
+ memcpy(bytes_out + sizeof(*out_header), \
+ read_out, sizeof(*read_out)); \
+ TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
+ out_header->len); \
+ } while (false)
+
+#define TESTFUSEOUT1(type1, obj1) \
+ do { \
+ *(struct fuse_out_header *) bytes_out \
+ = (struct fuse_out_header) { \
+ .len = sizeof(struct fuse_out_header) \
+ + sizeof(struct type1), \
+ .unique = ((struct fuse_in_header *) \
+ bytes_in)->unique, \
+ }; \
+ *(struct type1 *) (bytes_out \
+ + sizeof(struct fuse_out_header)) \
+ = obj1; \
+ TESTEQUAL(write(fuse_dev, bytes_out, \
+ ((struct fuse_out_header *)bytes_out)->len), \
+ ((struct fuse_out_header *)bytes_out)->len); \
+ } while (false)
+
+#define TESTFUSEOUT2(type1, obj1, type2, obj2) \
+ do { \
+ *(struct fuse_out_header *) bytes_out \
+ = (struct fuse_out_header) { \
+ .len = sizeof(struct fuse_out_header) \
+ + sizeof(struct type1) \
+ + sizeof(struct type2), \
+ .unique = ((struct fuse_in_header *) \
+ bytes_in)->unique, \
+ }; \
+ *(struct type1 *) (bytes_out \
+ + sizeof(struct fuse_out_header)) \
+ = obj1; \
+ *(struct type2 *) (bytes_out \
+ + sizeof(struct fuse_out_header) \
+ + sizeof(struct type1)) \
+ = obj2; \
+ TESTEQUAL(write(fuse_dev, bytes_out, \
+ ((struct fuse_out_header *)bytes_out)->len), \
+ ((struct fuse_out_header *)bytes_out)->len); \
+ } while (false)
+
+#define TESTFUSEINITFLAGS(fuse_connection_flags) \
+ do { \
+ DECL_FUSE_IN(init); \
+ \
+ TESTFUSEIN(FUSE_INIT, init_in); \
+ TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); \
+ TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); \
+ TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { \
+ .major = FUSE_KERNEL_VERSION, \
+ .minor = FUSE_KERNEL_MINOR_VERSION, \
+ .max_readahead = 4096, \
+ .flags = fuse_connection_flags, \
+ .max_background = 0, \
+ .congestion_threshold = 0, \
+ .max_write = 4096, \
+ .time_gran = 1000, \
+ .max_pages = 12, \
+ .map_alignment = 4096, \
+ })); \
+ } while (false)
+
+#define TESTFUSEINIT() \
+ TESTFUSEINITFLAGS(0)
+
+#define DECL_FUSE_IN(name) \
+ struct fuse_##name##_in *name##_in = \
+ (struct fuse_##name##_in *) \
+ (bytes_in + sizeof(struct fuse_in_header))
+
+#define DECL_FUSE(name) \
+ struct fuse_##name##_in *name##_in __maybe_unused; \
+ struct fuse_##name##_out *name##_out __maybe_unused
+
+#define FUSE_DECLARE_DAEMON \
+ int daemon = -1; \
+ int status; \
+ bool action; \
+ uint8_t bytes_in[FUSE_MIN_READ_BUFFER] __maybe_unused; \
+ uint8_t bytes_out[FUSE_MIN_READ_BUFFER] __maybe_unused
+
+#define FUSE_START_DAEMON() \
+ do { \
+ TEST(daemon = fork(), daemon != -1); \
+ action = daemon != 0; \
+ } while (false)
+
+#define FUSE_END_DAEMON() \
+ do { \
+ TESTEQUAL(waitpid(daemon, &status, 0), daemon); \
+ TESTEQUAL(status, TEST_SUCCESS); \
+ result = TEST_SUCCESS; \
+out: \
+ if (!daemon) \
+ exit(TEST_FAILURE); \
+ } while (false)
+
+
+struct map_relocation {
+ char *name;
+ int fd;
+ int value;
+};
+
+int mount_fuse(const char *mount_dir, int bpf_fd, int dir_fd,
+ int *fuse_dev_ptr);
+int mount_fuse_no_init(const char *mount_dir, int bpf_fd, int dir_fd,
+ int *fuse_dev_ptr);
+int install_elf_bpf(const char *file, const char *section, int *fd,
+ struct map_relocation **map_relocations, size_t *map_count);
+#endif
diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse_bpf.h b/tools/testing/selftests/filesystems/fuse/test_fuse_bpf.h
new file mode 100644
index 0000000..9097626
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/test_fuse_bpf.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2022 Google LLC
+ */
+
+#ifndef TEST_FUSE__BPF__H
+#define TEST_FUSE__BPF__H
+
+#define __EXPORTED_HEADERS__
+#define __KERNEL__
+
+#ifdef __ANDROID__
+#include <stdint.h>
+#endif
+
+#include <uapi/linux/types.h>
+#include <uapi/linux/bpf.h>
+#include <uapi/linux/android_fuse.h>
+#include <uapi/linux/fuse.h>
+#include <uapi/linux/errno.h>
+
+#define SEC(NAME) __section(NAME)
+
+struct fuse_bpf_map {
+ int map_type;
+ size_t key_size;
+ size_t value_size;
+ int max_entries;
+};
+
+static void *(*bpf_map_lookup_elem)(struct fuse_bpf_map *map, void *key)
+ = (void *) 1;
+
+static void *(*bpf_map_update_elem)(struct fuse_bpf_map *map, void *key,
+ void *value, int flags)
+ = (void *) 2;
+
+static long (*bpf_trace_printk)(const char *fmt, __u32 fmt_size, ...)
+ = (void *) 6;
+
+static long (*bpf_get_current_pid_tgid)()
+ = (void *) 14;
+
+static long (*bpf_get_current_uid_gid)()
+ = (void *) 15;
+
+#define bpf_printk(fmt, ...) \
+ ({ \
+ char ____fmt[] = fmt; \
+ bpf_trace_printk(____fmt, sizeof(____fmt), \
+ ##__VA_ARGS__); \
+ })
+
+SEC("dummy") inline int strcmp(const char *a, const char *b)
+{
+ int i;
+
+ for (i = 0; i < __builtin_strlen(b) + 1; ++i)
+ if (a[i] != b[i])
+ return -1;
+
+ return 0;
+}
+
+#endif