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>
This commit is contained in:
Daniel Rosenberg 2021-12-02 13:50:02 -08:00 committed by Paul Lawrence
parent fb5ea70e2e
commit 57f3ff9648
32 changed files with 8928 additions and 204 deletions

View File

@ -569,6 +569,7 @@ CONFIG_QUOTA=y
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

View File

@ -509,6 +509,7 @@ CONFIG_QUOTA=y
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

View File

@ -52,3 +52,11 @@ config FUSE_DAX
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

View File

@ -10,5 +10,6 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
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

2468
fs/fuse/backing.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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)

View File

@ -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;
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);
}
*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 = {

View File

@ -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;

View File

@ -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 */

View File

@ -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();
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -43,3 +43,6 @@ obj-$(CONFIG_BPF_PRELOAD) += preload/
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

128
kernel/bpf/bpf_fuse.c Normal file
View File

@ -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);

View File

@ -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>

View File

@ -0,0 +1,2 @@
fuse_test
*.raw

View File

@ -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 $@

View File

@ -0,0 +1,2 @@
# include OWNERS from the authoritative android-mainline branch
include kernel/common:android-mainline:/tools/testing/selftests/filesystems/incfs/OWNERS

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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