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:
parent
fb5ea70e2e
commit
57f3ff9648
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
2468
fs/fuse/backing.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
530
fs/fuse/dir.c
530
fs/fuse/dir.c
@ -8,8 +8,10 @@
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/fdtable.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/fs_context.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/sched.h>
|
||||
@ -27,6 +29,8 @@ module_param(allow_sys_admin_access, bool, 0644);
|
||||
MODULE_PARM_DESC(allow_sys_admin_access,
|
||||
"Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check");
|
||||
|
||||
#include "../internal.h"
|
||||
|
||||
static void fuse_advise_use_readdirplus(struct inode *dir)
|
||||
{
|
||||
struct fuse_inode *fi = get_fuse_inode(dir);
|
||||
@ -34,7 +38,7 @@ static void fuse_advise_use_readdirplus(struct inode *dir)
|
||||
set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);
|
||||
}
|
||||
|
||||
#if BITS_PER_LONG >= 64
|
||||
#if BITS_PER_LONG >= 64 && !defined(CONFIG_FUSE_BPF)
|
||||
static inline void __fuse_dentry_settime(struct dentry *entry, u64 time)
|
||||
{
|
||||
entry->d_fsdata = (void *) time;
|
||||
@ -46,19 +50,15 @@ static inline u64 fuse_dentry_time(const struct dentry *entry)
|
||||
}
|
||||
|
||||
#else
|
||||
union fuse_dentry {
|
||||
u64 time;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time)
|
||||
{
|
||||
((union fuse_dentry *) dentry->d_fsdata)->time = time;
|
||||
((struct fuse_dentry *) dentry->d_fsdata)->time = time;
|
||||
}
|
||||
|
||||
static inline u64 fuse_dentry_time(const struct dentry *entry)
|
||||
{
|
||||
return ((union fuse_dentry *) entry->d_fsdata)->time;
|
||||
return ((struct fuse_dentry *) entry->d_fsdata)->time;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -83,26 +83,16 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
|
||||
__fuse_dentry_settime(dentry, time);
|
||||
}
|
||||
|
||||
/*
|
||||
* FUSE caches dentries and attributes with separate timeout. The
|
||||
* time in jiffies until the dentry/attributes are valid is stored in
|
||||
* dentry->d_fsdata and fuse_inode->i_time respectively.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Calculate the time in jiffies until a dentry/attributes are valid
|
||||
*/
|
||||
static u64 time_to_jiffies(u64 sec, u32 nsec)
|
||||
void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir)
|
||||
{
|
||||
if (sec || nsec) {
|
||||
struct timespec64 ts = {
|
||||
sec,
|
||||
min_t(u32, nsec, NSEC_PER_SEC - 1)
|
||||
};
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_dentry *fuse_dentry = root->d_fsdata;
|
||||
|
||||
return get_jiffies_64() + timespec64_to_jiffies(&ts);
|
||||
} else
|
||||
return 0;
|
||||
if (backing_dir) {
|
||||
fuse_dentry->backing_path = backing_dir->f_path;
|
||||
path_get(&fuse_dentry->backing_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@ -115,11 +105,6 @@ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o)
|
||||
time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
|
||||
}
|
||||
|
||||
static u64 attr_timeout(struct fuse_attr_out *o)
|
||||
{
|
||||
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
||||
}
|
||||
|
||||
u64 entry_attr_timeout(struct fuse_entry_out *o)
|
||||
{
|
||||
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
||||
@ -180,7 +165,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
|
||||
|
||||
static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
|
||||
u64 nodeid, const struct qstr *name,
|
||||
struct fuse_entry_out *outarg)
|
||||
struct fuse_entry_out *outarg,
|
||||
struct fuse_entry_bpf_out *bpf_outarg)
|
||||
{
|
||||
memset(outarg, 0, sizeof(struct fuse_entry_out));
|
||||
args->opcode = FUSE_LOOKUP;
|
||||
@ -188,11 +174,52 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
|
||||
args->in_numargs = 1;
|
||||
args->in_args[0].size = name->len + 1;
|
||||
args->in_args[0].value = name->name;
|
||||
args->out_numargs = 1;
|
||||
args->out_argvar = true;
|
||||
args->out_numargs = 2;
|
||||
args->out_args[0].size = sizeof(struct fuse_entry_out);
|
||||
args->out_args[0].value = outarg;
|
||||
args->out_args[1].size = sizeof(struct fuse_entry_bpf_out);
|
||||
args->out_args[1].value = bpf_outarg;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
static bool backing_data_changed(struct fuse_inode *fi, struct dentry *entry,
|
||||
struct fuse_entry_bpf *bpf_arg)
|
||||
{
|
||||
struct path new_backing_path;
|
||||
struct inode *new_backing_inode;
|
||||
struct bpf_prog *bpf = NULL;
|
||||
int err;
|
||||
bool ret = true;
|
||||
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
get_fuse_backing_path(entry, &new_backing_path);
|
||||
new_backing_inode = fi->backing_inode;
|
||||
ihold(new_backing_inode);
|
||||
|
||||
err = fuse_handle_backing(bpf_arg, &new_backing_inode, &new_backing_path);
|
||||
|
||||
if (err)
|
||||
goto put_inode;
|
||||
|
||||
err = fuse_handle_bpf_prog(bpf_arg, entry->d_parent->d_inode, &bpf);
|
||||
if (err)
|
||||
goto put_bpf;
|
||||
|
||||
ret = (bpf != fi->bpf || fi->backing_inode != new_backing_inode ||
|
||||
!path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path));
|
||||
put_bpf:
|
||||
if (bpf)
|
||||
bpf_prog_put(bpf);
|
||||
put_inode:
|
||||
iput(new_backing_inode);
|
||||
path_put(&new_backing_path);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Check whether the dentry is still valid
|
||||
*
|
||||
@ -213,9 +240,23 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
||||
inode = d_inode_rcu(entry);
|
||||
if (inode && fuse_is_bad(inode))
|
||||
goto invalid;
|
||||
else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
/* TODO: Do we need bpf support for revalidate?
|
||||
* If the lower filesystem says the entry is invalid, FUSE probably shouldn't
|
||||
* try to fix that without going through the normal lookup path...
|
||||
*/
|
||||
if (get_fuse_dentry(entry)->backing_path.dentry) {
|
||||
ret = fuse_revalidate_backing(entry, flags);
|
||||
if (ret <= 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
|
||||
(flags & (LOOKUP_EXCL | LOOKUP_REVAL))) {
|
||||
struct fuse_entry_out outarg;
|
||||
struct fuse_entry_bpf bpf_arg;
|
||||
FUSE_ARGS(args);
|
||||
struct fuse_forget_link *forget;
|
||||
u64 attr_version;
|
||||
@ -227,27 +268,44 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
||||
ret = -ECHILD;
|
||||
if (flags & LOOKUP_RCU)
|
||||
goto out;
|
||||
|
||||
fm = get_fuse_mount(inode);
|
||||
|
||||
parent = dget_parent(entry);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
/* TODO: Once we're handling timeouts for backing inodes, do a
|
||||
* bpf based lookup_revalidate here.
|
||||
*/
|
||||
if (get_fuse_inode(parent->d_inode)->backing_inode) {
|
||||
dput(parent);
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
forget = fuse_alloc_forget();
|
||||
ret = -ENOMEM;
|
||||
if (!forget)
|
||||
if (!forget) {
|
||||
dput(parent);
|
||||
goto out;
|
||||
}
|
||||
|
||||
attr_version = fuse_get_attr_version(fm->fc);
|
||||
|
||||
parent = dget_parent(entry);
|
||||
fuse_lookup_init(fm->fc, &args, get_node_id(d_inode(parent)),
|
||||
&entry->d_name, &outarg);
|
||||
&entry->d_name, &outarg, &bpf_arg.out);
|
||||
ret = fuse_simple_request(fm, &args);
|
||||
dput(parent);
|
||||
|
||||
/* Zero nodeid is same as -ENOENT */
|
||||
if (!ret && !outarg.nodeid)
|
||||
ret = -ENOENT;
|
||||
if (!ret) {
|
||||
if (!ret || ret == sizeof(bpf_arg.out)) {
|
||||
fi = get_fuse_inode(inode);
|
||||
if (outarg.nodeid != get_node_id(inode) ||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
(ret == sizeof(bpf_arg.out) &&
|
||||
backing_data_changed(fi, entry, &bpf_arg)) ||
|
||||
#endif
|
||||
(bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
|
||||
fuse_queue_forget(fm->fc, forget,
|
||||
outarg.nodeid, 1);
|
||||
@ -289,17 +347,20 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
||||
goto out;
|
||||
}
|
||||
|
||||
#if BITS_PER_LONG < 64
|
||||
#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
|
||||
static int fuse_dentry_init(struct dentry *dentry)
|
||||
{
|
||||
dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry),
|
||||
dentry->d_fsdata = kzalloc(sizeof(struct fuse_dentry),
|
||||
GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);
|
||||
|
||||
return dentry->d_fsdata ? 0 : -ENOMEM;
|
||||
}
|
||||
static void fuse_dentry_release(struct dentry *dentry)
|
||||
{
|
||||
union fuse_dentry *fd = dentry->d_fsdata;
|
||||
struct fuse_dentry *fd = dentry->d_fsdata;
|
||||
|
||||
if (fd && fd->backing_path.dentry)
|
||||
path_put(&fd->backing_path);
|
||||
|
||||
kfree_rcu(fd, rcu);
|
||||
}
|
||||
@ -353,6 +414,18 @@ static void fuse_dentry_canonical_path(const struct path *path,
|
||||
char *path_name;
|
||||
int err;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_dummy_io,
|
||||
fuse_canonical_path_initialize,
|
||||
fuse_canonical_path_backing,
|
||||
fuse_canonical_path_finalize, path,
|
||||
canonical_path);
|
||||
if (fer.ret)
|
||||
return;
|
||||
#endif
|
||||
|
||||
path_name = (char *)get_zeroed_page(GFP_KERNEL);
|
||||
if (!path_name)
|
||||
goto default_path;
|
||||
@ -379,7 +452,7 @@ static void fuse_dentry_canonical_path(const struct path *path,
|
||||
const struct dentry_operations fuse_dentry_operations = {
|
||||
.d_revalidate = fuse_dentry_revalidate,
|
||||
.d_delete = fuse_dentry_delete,
|
||||
#if BITS_PER_LONG < 64
|
||||
#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
|
||||
.d_init = fuse_dentry_init,
|
||||
.d_release = fuse_dentry_release,
|
||||
#endif
|
||||
@ -388,7 +461,7 @@ const struct dentry_operations fuse_dentry_operations = {
|
||||
};
|
||||
|
||||
const struct dentry_operations fuse_root_dentry_operations = {
|
||||
#if BITS_PER_LONG < 64
|
||||
#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
|
||||
.d_init = fuse_dentry_init,
|
||||
.d_release = fuse_dentry_release,
|
||||
#endif
|
||||
@ -407,10 +480,13 @@ bool fuse_invalid_attr(struct fuse_attr *attr)
|
||||
}
|
||||
|
||||
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
|
||||
struct fuse_entry_out *outarg, struct inode **inode)
|
||||
struct fuse_entry_out *outarg,
|
||||
struct dentry *entry,
|
||||
struct inode **inode)
|
||||
{
|
||||
struct fuse_mount *fm = get_fuse_mount_super(sb);
|
||||
FUSE_ARGS(args);
|
||||
struct fuse_entry_bpf bpf_arg = {0};
|
||||
struct fuse_forget_link *forget;
|
||||
u64 attr_version;
|
||||
int err;
|
||||
@ -428,23 +504,68 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
|
||||
|
||||
attr_version = fuse_get_attr_version(fm->fc);
|
||||
|
||||
fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
|
||||
fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, &bpf_arg.out);
|
||||
err = fuse_simple_request(fm, &args);
|
||||
/* Zero nodeid is same as -ENOENT, but with valid timeout */
|
||||
if (err || !outarg->nodeid)
|
||||
goto out_put_forget;
|
||||
|
||||
err = -EIO;
|
||||
if (!outarg->nodeid)
|
||||
goto out_put_forget;
|
||||
if (fuse_invalid_attr(&outarg->attr))
|
||||
goto out_put_forget;
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
if (err == sizeof(bpf_arg.out)) {
|
||||
/* TODO Make sure this handles invalid handles */
|
||||
struct file *backing_file;
|
||||
struct inode *backing_inode;
|
||||
|
||||
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 = {
|
||||
|
130
fs/fuse/file.c
130
fs/fuse/file.c
@ -8,6 +8,7 @@
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/filter.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -235,6 +236,20 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
{
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_open_io,
|
||||
fuse_open_initialize,
|
||||
fuse_open_backing,
|
||||
fuse_open_finalize,
|
||||
inode, file, isdir);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_wb_truncate || dax_truncate)
|
||||
inode_lock(inode);
|
||||
|
||||
@ -346,6 +361,17 @@ static int fuse_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_release_in,
|
||||
fuse_release_initialize, fuse_release_backing,
|
||||
fuse_release_finalize,
|
||||
inode, file);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dirty pages might remain despite write_inode_now() call from
|
||||
* fuse_flush() due to writes racing with the close.
|
||||
@ -488,6 +514,17 @@ static int fuse_flush(struct file *file, fl_owner_t id)
|
||||
FUSE_ARGS(args);
|
||||
int err;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(file->f_inode, struct fuse_flush_in,
|
||||
fuse_flush_initialize, fuse_flush_backing,
|
||||
fuse_flush_finalize,
|
||||
file, id);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (fuse_is_bad(inode))
|
||||
return -EIO;
|
||||
|
||||
@ -563,6 +600,17 @@ static int fuse_fsync(struct file *file, loff_t start, loff_t end,
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
int err;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_fsync_in,
|
||||
fuse_fsync_initialize, fuse_fsync_backing,
|
||||
fuse_fsync_finalize,
|
||||
file, start, end, datasync);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (fuse_is_bad(inode))
|
||||
return -EIO;
|
||||
|
||||
@ -1600,6 +1648,20 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
if (FUSE_IS_DAX(inode))
|
||||
return fuse_dax_read_iter(iocb, to);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
{
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_file_read_iter_io,
|
||||
fuse_file_read_iter_initialize,
|
||||
fuse_file_read_iter_backing,
|
||||
fuse_file_read_iter_finalize,
|
||||
iocb, to);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ff->passthrough.filp)
|
||||
return fuse_passthrough_read_iter(iocb, to);
|
||||
else if (!(ff->open_flags & FOPEN_DIRECT_IO))
|
||||
@ -1620,6 +1682,20 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
if (FUSE_IS_DAX(inode))
|
||||
return fuse_dax_write_iter(iocb, from);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
{
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_file_write_iter_io,
|
||||
fuse_file_write_iter_initialize,
|
||||
fuse_file_write_iter_backing,
|
||||
fuse_file_write_iter_finalize,
|
||||
iocb, from);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ff->passthrough.filp)
|
||||
return fuse_passthrough_write_iter(iocb, from);
|
||||
else if (!(ff->open_flags & FOPEN_DIRECT_IO))
|
||||
@ -1868,6 +1944,19 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc)
|
||||
struct fuse_file *ff;
|
||||
int err;
|
||||
|
||||
/**
|
||||
* TODO - fully understand why this is necessary
|
||||
*
|
||||
* With fuse-bpf, fsstress fails if rename is enabled without this
|
||||
*
|
||||
* We are getting writes here on directory inodes, which do not have an
|
||||
* initialized file list so crash.
|
||||
*
|
||||
* The question is why we are getting those writes
|
||||
*/
|
||||
if (!S_ISREG(inode->i_mode))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Inode is always written before the last reference is dropped and
|
||||
* hence this should not be reached from reclaim.
|
||||
@ -2439,6 +2528,12 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
if (FUSE_IS_DAX(file_inode(file)))
|
||||
return fuse_dax_mmap(file, vma);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
/* TODO - this is simply passthrough, not a proper BPF filter */
|
||||
if (ff->backing_file)
|
||||
return fuse_backing_mmap(file, vma);
|
||||
#endif
|
||||
|
||||
if (ff->passthrough.filp)
|
||||
return fuse_passthrough_mmap(file, vma);
|
||||
|
||||
@ -2687,6 +2782,17 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
|
||||
{
|
||||
loff_t retval;
|
||||
struct inode *inode = file_inode(file);
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_lseek_io,
|
||||
fuse_lseek_initialize,
|
||||
fuse_lseek_backing,
|
||||
fuse_lseek_finalize,
|
||||
file, offset, whence);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
@ -2976,6 +3082,18 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
|
||||
(!(mode & FALLOC_FL_KEEP_SIZE) ||
|
||||
(mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE)));
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_fallocate_in,
|
||||
fuse_file_fallocate_initialize,
|
||||
fuse_file_fallocate_backing,
|
||||
fuse_file_fallocate_finalize,
|
||||
file, mode, offset, length);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
|
||||
FALLOC_FL_ZERO_RANGE))
|
||||
return -EOPNOTSUPP;
|
||||
@ -3079,6 +3197,18 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
|
||||
bool is_unstable = (!fc->writeback_cache) &&
|
||||
((pos_out + len) > inode_out->i_size);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(file_in->f_inode, struct fuse_copy_file_range_io,
|
||||
fuse_copy_file_range_initialize,
|
||||
fuse_copy_file_range_backing,
|
||||
fuse_copy_file_range_finalize,
|
||||
file_in, pos_in, file_out, pos_out, len, flags);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (fc->no_copy_file_range)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
|
720
fs/fuse/fuse_i.h
720
fs/fuse/fuse_i.h
@ -13,6 +13,9 @@
|
||||
# define pr_fmt(fmt) "fuse: " fmt
|
||||
#endif
|
||||
|
||||
#include <linux/android_fuse.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/fuse.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
@ -31,6 +34,9 @@
|
||||
#include <linux/pid_namespace.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <linux/statfs.h>
|
||||
|
||||
#define FUSE_SUPER_MAGIC 0x65735546
|
||||
|
||||
/** Default max number of pages that can be used in a single read request */
|
||||
#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
|
||||
@ -63,11 +69,57 @@ struct fuse_forget_link {
|
||||
struct fuse_forget_link *next;
|
||||
};
|
||||
|
||||
/** FUSE specific dentry data */
|
||||
#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF)
|
||||
struct fuse_dentry {
|
||||
union {
|
||||
u64 time;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
struct path backing_path;
|
||||
};
|
||||
|
||||
static inline struct fuse_dentry *get_fuse_dentry(const struct dentry *entry)
|
||||
{
|
||||
return entry->d_fsdata;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
static inline void get_fuse_backing_path(const struct dentry *d,
|
||||
struct path *path)
|
||||
{
|
||||
struct fuse_dentry *di = get_fuse_dentry(d);
|
||||
|
||||
if (!di) {
|
||||
*path = (struct path) {};
|
||||
return;
|
||||
}
|
||||
|
||||
*path = di->backing_path;
|
||||
path_get(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** FUSE inode */
|
||||
struct fuse_inode {
|
||||
/** Inode data */
|
||||
struct inode inode;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
/**
|
||||
* Backing inode, if this inode is from a backing file system.
|
||||
* If this is set, nodeid is 0.
|
||||
*/
|
||||
struct inode *backing_inode;
|
||||
|
||||
/**
|
||||
* bpf_prog, run on all operations to determine whether to pass through
|
||||
* or handle in place
|
||||
*/
|
||||
struct bpf_prog *bpf;
|
||||
#endif
|
||||
|
||||
/** Unique ID, which identifies the inode between userspace
|
||||
* and kernel */
|
||||
u64 nodeid;
|
||||
@ -232,6 +284,14 @@ struct fuse_file {
|
||||
/** Container for data related to the passthrough functionality */
|
||||
struct fuse_passthrough passthrough;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
/**
|
||||
* TODO: Reconcile with passthrough file
|
||||
* backing file when in bpf mode
|
||||
*/
|
||||
struct file *backing_file;
|
||||
#endif
|
||||
|
||||
/** RB node to be linked on fuse_conn->polled_files */
|
||||
struct rb_node polled_node;
|
||||
|
||||
@ -263,6 +323,7 @@ struct fuse_page_desc {
|
||||
struct fuse_args {
|
||||
uint64_t nodeid;
|
||||
uint32_t opcode;
|
||||
uint32_t error_in;
|
||||
unsigned short in_numargs;
|
||||
unsigned short out_numargs;
|
||||
bool force:1;
|
||||
@ -275,8 +336,8 @@ struct fuse_args {
|
||||
bool page_zeroing:1;
|
||||
bool page_replace:1;
|
||||
bool may_block:1;
|
||||
struct fuse_in_arg in_args[3];
|
||||
struct fuse_arg out_args[2];
|
||||
struct fuse_in_arg in_args[FUSE_MAX_IN_ARGS];
|
||||
struct fuse_arg out_args[FUSE_MAX_OUT_ARGS];
|
||||
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
|
||||
|
||||
/* Path used for completing d_canonical_path */
|
||||
@ -526,9 +587,12 @@ struct fuse_fs_context {
|
||||
bool no_force_umount:1;
|
||||
bool legacy_opts_show:1;
|
||||
enum fuse_dax_mode dax_mode;
|
||||
bool no_daemon:1;
|
||||
unsigned int max_read;
|
||||
unsigned int blksize;
|
||||
const char *subtype;
|
||||
struct bpf_prog *root_bpf;
|
||||
struct file *root_dir;
|
||||
|
||||
/* DAX device, may be NULL */
|
||||
struct dax_device *dax_dev;
|
||||
@ -805,6 +869,9 @@ struct fuse_conn {
|
||||
/* Is tmpfile not implemented by fs? */
|
||||
unsigned int no_tmpfile:1;
|
||||
|
||||
/** BPF Only, no Daemon running */
|
||||
unsigned int no_daemon:1;
|
||||
|
||||
/** The number of requests waiting for completion */
|
||||
atomic_t num_waiting;
|
||||
|
||||
@ -979,14 +1046,18 @@ extern const struct dentry_operations fuse_dentry_operations;
|
||||
extern const struct dentry_operations fuse_root_dentry_operations;
|
||||
|
||||
/**
|
||||
* Get a filled in inode
|
||||
* Get a filled-in inode
|
||||
*/
|
||||
struct inode *fuse_iget_backing(struct super_block *sb,
|
||||
u64 nodeid,
|
||||
struct inode *backing_inode);
|
||||
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||
int generation, struct fuse_attr *attr,
|
||||
u64 attr_valid, u64 attr_version);
|
||||
|
||||
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
|
||||
struct fuse_entry_out *outarg, struct inode **inode);
|
||||
struct fuse_entry_out *outarg,
|
||||
struct dentry *entry, struct inode **inode);
|
||||
|
||||
/**
|
||||
* Send FORGET command
|
||||
@ -1023,7 +1094,6 @@ struct fuse_io_args {
|
||||
void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
|
||||
size_t count, int opcode);
|
||||
|
||||
|
||||
/**
|
||||
* Send OPEN or OPENDIR request
|
||||
*/
|
||||
@ -1095,7 +1165,7 @@ int fuse_dev_init(void);
|
||||
void fuse_dev_cleanup(void);
|
||||
|
||||
int fuse_ctl_init(void);
|
||||
void __exit fuse_ctl_cleanup(void);
|
||||
void fuse_ctl_cleanup(void);
|
||||
|
||||
/**
|
||||
* Simple request sending that does request allocation and freeing
|
||||
@ -1131,6 +1201,7 @@ void fuse_invalidate_entry_cache(struct dentry *entry);
|
||||
void fuse_invalidate_atime(struct inode *inode);
|
||||
|
||||
u64 entry_attr_timeout(struct fuse_entry_out *o);
|
||||
void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir);
|
||||
void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o);
|
||||
|
||||
/**
|
||||
@ -1344,6 +1415,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
|
||||
unsigned int open_flags, fl_owner_t id, bool isdir);
|
||||
|
||||
/* passthrough.c */
|
||||
void fuse_copyattr(struct file *dst_file, struct file *src_file);
|
||||
int fuse_passthrough_open(struct fuse_dev *fud, u32 lower_fd);
|
||||
int fuse_passthrough_setup(struct fuse_conn *fc, struct fuse_file *ff,
|
||||
struct fuse_open_out *openarg);
|
||||
@ -1352,4 +1424,640 @@ ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *to);
|
||||
ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *from);
|
||||
ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma);
|
||||
|
||||
/* backing.c */
|
||||
|
||||
struct bpf_prog *fuse_get_bpf_prog(struct file *file);
|
||||
|
||||
/*
|
||||
* Dummy io passed to fuse_bpf_backing when io operation needs no scratch space
|
||||
*/
|
||||
struct fuse_dummy_io {
|
||||
int unused;
|
||||
};
|
||||
|
||||
struct fuse_open_io {
|
||||
struct fuse_open_in foi;
|
||||
struct fuse_open_out foo;
|
||||
};
|
||||
|
||||
int fuse_open_initialize(struct fuse_bpf_args *fa, struct fuse_open_io *foi,
|
||||
struct inode *inode, struct file *file, bool isdir);
|
||||
int fuse_open_backing(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct file *file, bool isdir);
|
||||
void *fuse_open_finalize(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct file *file, bool isdir);
|
||||
|
||||
struct fuse_create_open_io {
|
||||
struct fuse_create_in fci;
|
||||
struct fuse_entry_out feo;
|
||||
struct fuse_open_out foo;
|
||||
};
|
||||
|
||||
int fuse_create_open_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_create_open_io *fcoi,
|
||||
struct inode *dir, struct dentry *entry,
|
||||
struct file *file, unsigned int flags, umode_t mode);
|
||||
int fuse_create_open_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry,
|
||||
struct file *file, unsigned int flags, umode_t mode);
|
||||
void *fuse_create_open_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry,
|
||||
struct file *file, unsigned int flags, umode_t mode);
|
||||
|
||||
int fuse_mknod_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_mknod_in *fmi,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
|
||||
int fuse_mknod_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
|
||||
void *fuse_mknod_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
|
||||
|
||||
int fuse_mkdir_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_mkdir_in *fmi,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
int fuse_mkdir_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
void *fuse_mkdir_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
|
||||
int fuse_rmdir_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_dummy_io *fmi,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
int fuse_rmdir_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
void *fuse_rmdir_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
|
||||
int fuse_rename2_initialize(struct fuse_bpf_args *fa, struct fuse_rename2_in *fri,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent,
|
||||
unsigned int flags);
|
||||
int fuse_rename2_backing(struct fuse_bpf_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent,
|
||||
unsigned int flags);
|
||||
void *fuse_rename2_finalize(struct fuse_bpf_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent,
|
||||
unsigned int flags);
|
||||
|
||||
int fuse_rename_initialize(struct fuse_bpf_args *fa, struct fuse_rename_in *fri,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent);
|
||||
int fuse_rename_backing(struct fuse_bpf_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent);
|
||||
void *fuse_rename_finalize(struct fuse_bpf_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent);
|
||||
|
||||
int fuse_unlink_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_dummy_io *fmi,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
int fuse_unlink_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
void *fuse_unlink_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
|
||||
int fuse_link_initialize(struct fuse_bpf_args *fa, struct fuse_link_in *fli,
|
||||
struct dentry *entry, struct inode *dir,
|
||||
struct dentry *newent);
|
||||
int fuse_link_backing(struct fuse_bpf_args *fa, struct dentry *entry,
|
||||
struct inode *dir, struct dentry *newent);
|
||||
void *fuse_link_finalize(struct fuse_bpf_args *fa, struct dentry *entry,
|
||||
struct inode *dir, struct dentry *newent);
|
||||
|
||||
int fuse_release_initialize(struct fuse_bpf_args *fa, struct fuse_release_in *fri,
|
||||
struct inode *inode, struct file *file);
|
||||
int fuse_releasedir_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_release_in *fri,
|
||||
struct inode *inode, struct file *file);
|
||||
int fuse_release_backing(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct file *file);
|
||||
void *fuse_release_finalize(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct file *file);
|
||||
|
||||
int fuse_flush_initialize(struct fuse_bpf_args *fa, struct fuse_flush_in *ffi,
|
||||
struct file *file, fl_owner_t id);
|
||||
int fuse_flush_backing(struct fuse_bpf_args *fa, struct file *file, fl_owner_t id);
|
||||
void *fuse_flush_finalize(struct fuse_bpf_args *fa,
|
||||
struct file *file, fl_owner_t id);
|
||||
|
||||
struct fuse_lseek_io {
|
||||
struct fuse_lseek_in fli;
|
||||
struct fuse_lseek_out flo;
|
||||
};
|
||||
|
||||
int fuse_lseek_initialize(struct fuse_bpf_args *fa, struct fuse_lseek_io *fli,
|
||||
struct file *file, loff_t offset, int whence);
|
||||
int fuse_lseek_backing(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence);
|
||||
void *fuse_lseek_finalize(struct fuse_bpf_args *fa, struct file *file, loff_t offset, int whence);
|
||||
|
||||
struct fuse_copy_file_range_io {
|
||||
struct fuse_copy_file_range_in fci;
|
||||
struct fuse_write_out fwo;
|
||||
};
|
||||
|
||||
int fuse_copy_file_range_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_copy_file_range_io *fcf,
|
||||
struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
size_t len, unsigned int flags);
|
||||
int fuse_copy_file_range_backing(struct fuse_bpf_args *fa,
|
||||
struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
size_t len, unsigned int flags);
|
||||
void *fuse_copy_file_range_finalize(struct fuse_bpf_args *fa,
|
||||
struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
size_t len, unsigned int flags);
|
||||
|
||||
int fuse_fsync_initialize(struct fuse_bpf_args *fa, struct fuse_fsync_in *ffi,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
int fuse_fsync_backing(struct fuse_bpf_args *fa,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
void *fuse_fsync_finalize(struct fuse_bpf_args *fa,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
int fuse_dir_fsync_initialize(struct fuse_bpf_args *fa, struct fuse_fsync_in *ffi,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
|
||||
struct fuse_getxattr_io {
|
||||
struct fuse_getxattr_in fgi;
|
||||
struct fuse_getxattr_out fgo;
|
||||
};
|
||||
|
||||
int fuse_getxattr_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_getxattr_io *fgio,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
int fuse_getxattr_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
void *fuse_getxattr_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
|
||||
int fuse_listxattr_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_getxattr_io *fgio,
|
||||
struct dentry *dentry, char *list, size_t size);
|
||||
int fuse_listxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
|
||||
char *list, size_t size);
|
||||
void *fuse_listxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
|
||||
char *list, size_t size);
|
||||
|
||||
int fuse_setxattr_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_setxattr_in *fsxi,
|
||||
struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags);
|
||||
int fuse_setxattr_backing(struct fuse_bpf_args *fa, struct dentry *dentry,
|
||||
const char *name, const void *value, size_t size,
|
||||
int flags);
|
||||
void *fuse_setxattr_finalize(struct fuse_bpf_args *fa, struct dentry *dentry,
|
||||
const char *name, const void *value, size_t size,
|
||||
int flags);
|
||||
|
||||
int fuse_removexattr_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_dummy_io *unused,
|
||||
struct dentry *dentry, const char *name);
|
||||
int fuse_removexattr_backing(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, const char *name);
|
||||
void *fuse_removexattr_finalize(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, const char *name);
|
||||
|
||||
struct fuse_read_iter_out {
|
||||
uint64_t ret;
|
||||
};
|
||||
struct fuse_file_read_iter_io {
|
||||
struct fuse_read_in fri;
|
||||
struct fuse_read_iter_out frio;
|
||||
};
|
||||
|
||||
int fuse_file_read_iter_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_file_read_iter_io *fri,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
int fuse_file_read_iter_backing(struct fuse_bpf_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
void *fuse_file_read_iter_finalize(struct fuse_bpf_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
|
||||
struct fuse_write_iter_out {
|
||||
uint64_t ret;
|
||||
};
|
||||
struct fuse_file_write_iter_io {
|
||||
struct fuse_write_in fwi;
|
||||
struct fuse_write_out fwo;
|
||||
struct fuse_write_iter_out fwio;
|
||||
};
|
||||
|
||||
int fuse_file_write_iter_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_file_write_iter_io *fwio,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
int fuse_file_write_iter_backing(struct fuse_bpf_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
void *fuse_file_write_iter_finalize(struct fuse_bpf_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
|
||||
ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma);
|
||||
|
||||
int fuse_file_fallocate_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_fallocate_in *ffi,
|
||||
struct file *file, int mode, loff_t offset, loff_t length);
|
||||
int fuse_file_fallocate_backing(struct fuse_bpf_args *fa,
|
||||
struct file *file, int mode, loff_t offset, loff_t length);
|
||||
void *fuse_file_fallocate_finalize(struct fuse_bpf_args *fa,
|
||||
struct file *file, int mode, loff_t offset, loff_t length);
|
||||
|
||||
struct fuse_lookup_io {
|
||||
struct fuse_entry_out feo;
|
||||
struct fuse_entry_bpf feb;
|
||||
};
|
||||
|
||||
int fuse_handle_backing(struct fuse_entry_bpf *feb, struct inode **backing_inode,
|
||||
struct path *backing_path);
|
||||
int fuse_handle_bpf_prog(struct fuse_entry_bpf *feb, struct inode *parent,
|
||||
struct bpf_prog **bpf);
|
||||
|
||||
int fuse_lookup_initialize(struct fuse_bpf_args *fa, struct fuse_lookup_io *feo,
|
||||
struct inode *dir, struct dentry *entry, unsigned int flags);
|
||||
int fuse_lookup_backing(struct fuse_bpf_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
struct dentry *fuse_lookup_finalize(struct fuse_bpf_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
int fuse_revalidate_backing(struct dentry *entry, unsigned int flags);
|
||||
|
||||
int fuse_canonical_path_initialize(struct fuse_bpf_args *fa,
|
||||
struct fuse_dummy_io *fdi,
|
||||
const struct path *path,
|
||||
struct path *canonical_path);
|
||||
int fuse_canonical_path_backing(struct fuse_bpf_args *fa, const struct path *path,
|
||||
struct path *canonical_path);
|
||||
void *fuse_canonical_path_finalize(struct fuse_bpf_args *fa,
|
||||
const struct path *path,
|
||||
struct path *canonical_path);
|
||||
|
||||
struct fuse_getattr_io {
|
||||
struct fuse_getattr_in fgi;
|
||||
struct fuse_attr_out fao;
|
||||
};
|
||||
int fuse_getattr_initialize(struct fuse_bpf_args *fa, struct fuse_getattr_io *fgio,
|
||||
const struct dentry *entry, struct kstat *stat,
|
||||
u32 request_mask, unsigned int flags);
|
||||
int fuse_getattr_backing(struct fuse_bpf_args *fa,
|
||||
const struct dentry *entry, struct kstat *stat,
|
||||
u32 request_mask, unsigned int flags);
|
||||
void *fuse_getattr_finalize(struct fuse_bpf_args *fa,
|
||||
const struct dentry *entry, struct kstat *stat,
|
||||
u32 request_mask, unsigned int flags);
|
||||
|
||||
struct fuse_setattr_io {
|
||||
struct fuse_setattr_in fsi;
|
||||
struct fuse_attr_out fao;
|
||||
};
|
||||
|
||||
int fuse_setattr_initialize(struct fuse_bpf_args *fa, struct fuse_setattr_io *fsi,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
int fuse_setattr_backing(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
void *fuse_setattr_finalize(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
|
||||
int fuse_statfs_initialize(struct fuse_bpf_args *fa, struct fuse_statfs_out *fso,
|
||||
struct dentry *dentry, struct kstatfs *buf);
|
||||
int fuse_statfs_backing(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, struct kstatfs *buf);
|
||||
void *fuse_statfs_finalize(struct fuse_bpf_args *fa,
|
||||
struct dentry *dentry, struct kstatfs *buf);
|
||||
|
||||
int fuse_get_link_initialize(struct fuse_bpf_args *fa, struct fuse_dummy_io *dummy,
|
||||
struct inode *inode, struct dentry *dentry,
|
||||
struct delayed_call *callback, const char **out);
|
||||
int fuse_get_link_backing(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct dentry *dentry,
|
||||
struct delayed_call *callback, const char **out);
|
||||
void *fuse_get_link_finalize(struct fuse_bpf_args *fa,
|
||||
struct inode *inode, struct dentry *dentry,
|
||||
struct delayed_call *callback, const char **out);
|
||||
|
||||
int fuse_symlink_initialize(
|
||||
struct fuse_bpf_args *fa, struct fuse_dummy_io *unused,
|
||||
struct inode *dir, struct dentry *entry, const char *link, int len);
|
||||
int fuse_symlink_backing(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, const char *link, int len);
|
||||
void *fuse_symlink_finalize(
|
||||
struct fuse_bpf_args *fa,
|
||||
struct inode *dir, struct dentry *entry, const char *link, int len);
|
||||
|
||||
struct fuse_read_io {
|
||||
struct fuse_read_in fri;
|
||||
struct fuse_read_out fro;
|
||||
};
|
||||
|
||||
int fuse_readdir_initialize(struct fuse_bpf_args *fa, struct fuse_read_io *frio,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force, bool is_continued);
|
||||
int fuse_readdir_backing(struct fuse_bpf_args *fa,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force, bool is_continued);
|
||||
void *fuse_readdir_finalize(struct fuse_bpf_args *fa,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force, bool is_continued);
|
||||
|
||||
int fuse_access_initialize(struct fuse_bpf_args *fa, struct fuse_access_in *fai,
|
||||
struct inode *inode, int mask);
|
||||
int fuse_access_backing(struct fuse_bpf_args *fa, struct inode *inode, int mask);
|
||||
void *fuse_access_finalize(struct fuse_bpf_args *fa, struct inode *inode, int mask);
|
||||
|
||||
/*
|
||||
* FUSE caches dentries and attributes with separate timeout. The
|
||||
* time in jiffies until the dentry/attributes are valid is stored in
|
||||
* dentry->d_fsdata and fuse_inode->i_time respectively.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Calculate the time in jiffies until a dentry/attributes are valid
|
||||
*/
|
||||
static inline u64 time_to_jiffies(u64 sec, u32 nsec)
|
||||
{
|
||||
if (sec || nsec) {
|
||||
struct timespec64 ts = {
|
||||
sec,
|
||||
min_t(u32, nsec, NSEC_PER_SEC - 1)
|
||||
};
|
||||
|
||||
return get_jiffies_64() + timespec64_to_jiffies(&ts);
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u64 attr_timeout(struct fuse_attr_out *o)
|
||||
{
|
||||
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
||||
}
|
||||
|
||||
static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtime)
|
||||
{
|
||||
/* Always update if mtime is explicitly set */
|
||||
if (ivalid & ATTR_MTIME_SET)
|
||||
return true;
|
||||
|
||||
/* Or if kernel i_mtime is the official one */
|
||||
if (trust_local_mtime)
|
||||
return true;
|
||||
|
||||
/* If it's an open(O_TRUNC) or an ftruncate(), don't update */
|
||||
if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
|
||||
return false;
|
||||
|
||||
/* In all other cases update */
|
||||
return true;
|
||||
}
|
||||
|
||||
void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
|
||||
struct kstat *stat);
|
||||
|
||||
static inline void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr,
|
||||
struct fuse_setattr_in *arg, bool trust_local_cmtime)
|
||||
{
|
||||
unsigned int ivalid = iattr->ia_valid;
|
||||
|
||||
if (ivalid & ATTR_MODE)
|
||||
arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode;
|
||||
if (ivalid & ATTR_UID)
|
||||
arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid);
|
||||
if (ivalid & ATTR_GID)
|
||||
arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid);
|
||||
if (ivalid & ATTR_SIZE)
|
||||
arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size;
|
||||
if (ivalid & ATTR_ATIME) {
|
||||
arg->valid |= FATTR_ATIME;
|
||||
arg->atime = iattr->ia_atime.tv_sec;
|
||||
arg->atimensec = iattr->ia_atime.tv_nsec;
|
||||
if (!(ivalid & ATTR_ATIME_SET))
|
||||
arg->valid |= FATTR_ATIME_NOW;
|
||||
}
|
||||
if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) {
|
||||
arg->valid |= FATTR_MTIME;
|
||||
arg->mtime = iattr->ia_mtime.tv_sec;
|
||||
arg->mtimensec = iattr->ia_mtime.tv_nsec;
|
||||
if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime)
|
||||
arg->valid |= FATTR_MTIME_NOW;
|
||||
}
|
||||
if ((ivalid & ATTR_CTIME) && trust_local_cmtime) {
|
||||
arg->valid |= FATTR_CTIME;
|
||||
arg->ctime = iattr->ia_ctime.tv_sec;
|
||||
arg->ctimensec = iattr->ia_ctime.tv_nsec;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int finalize_attr(struct inode *inode, struct fuse_attr_out *outarg,
|
||||
u64 attr_version, struct kstat *stat)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (fuse_invalid_attr(&outarg->attr) ||
|
||||
((inode->i_mode ^ outarg->attr.mode) & S_IFMT)) {
|
||||
fuse_make_bad(inode);
|
||||
err = -EIO;
|
||||
} else {
|
||||
fuse_change_attributes(inode, &outarg->attr,
|
||||
attr_timeout(outarg),
|
||||
attr_version);
|
||||
if (stat)
|
||||
fuse_fillattr(inode, &outarg->attr, stat);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline void convert_statfs_to_fuse(struct fuse_kstatfs *attr, struct kstatfs *stbuf)
|
||||
{
|
||||
attr->bsize = stbuf->f_bsize;
|
||||
attr->frsize = stbuf->f_frsize;
|
||||
attr->blocks = stbuf->f_blocks;
|
||||
attr->bfree = stbuf->f_bfree;
|
||||
attr->bavail = stbuf->f_bavail;
|
||||
attr->files = stbuf->f_files;
|
||||
attr->ffree = stbuf->f_ffree;
|
||||
attr->namelen = stbuf->f_namelen;
|
||||
/* fsid is left zero */
|
||||
}
|
||||
|
||||
static inline void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
|
||||
{
|
||||
stbuf->f_type = FUSE_SUPER_MAGIC;
|
||||
stbuf->f_bsize = attr->bsize;
|
||||
stbuf->f_frsize = attr->frsize;
|
||||
stbuf->f_blocks = attr->blocks;
|
||||
stbuf->f_bfree = attr->bfree;
|
||||
stbuf->f_bavail = attr->bavail;
|
||||
stbuf->f_files = attr->files;
|
||||
stbuf->f_ffree = attr->ffree;
|
||||
stbuf->f_namelen = attr->namelen;
|
||||
/* fsid is left zero */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret {
|
||||
void *result;
|
||||
bool ret;
|
||||
};
|
||||
|
||||
int __init fuse_bpf_init(void);
|
||||
void __exit fuse_bpf_cleanup(void);
|
||||
|
||||
ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct fuse_bpf_args *args);
|
||||
|
||||
/*
|
||||
* expression statement to wrap the backing filter logic
|
||||
* struct inode *inode: inode with bpf and backing inode
|
||||
* typedef io: (typically complex) type whose components fuse_args can point to.
|
||||
* An instance of this type is created locally and passed to initialize
|
||||
* void initialize(struct fuse_bpf_args *fa, io *in_out, args...): function that sets
|
||||
* up fa and io based on args
|
||||
* int backing(struct fuse_bpf_args *fa, args...): function that actually performs
|
||||
* the backing io operation
|
||||
* void *finalize(struct fuse_bpf_args *, args...): function that performs any final
|
||||
* work needed to commit the backing io
|
||||
*/
|
||||
#define fuse_bpf_backing(inode, io, initialize, backing, finalize, \
|
||||
args...) \
|
||||
({ \
|
||||
struct fuse_err_ret fer = {0}; \
|
||||
int ext_flags; \
|
||||
struct fuse_inode *fuse_inode = get_fuse_inode(inode); \
|
||||
struct fuse_mount *fm = get_fuse_mount(inode); \
|
||||
io feo = {0}; \
|
||||
struct fuse_bpf_args fa = {0}, fa_backup = {0}; \
|
||||
bool locked; \
|
||||
ssize_t res; \
|
||||
void *err; \
|
||||
int i; \
|
||||
bool initialized = false; \
|
||||
\
|
||||
do { \
|
||||
if (!fuse_inode || !fuse_inode->backing_inode) \
|
||||
break; \
|
||||
\
|
||||
err = ERR_PTR(initialize(&fa, &feo, args)); \
|
||||
if (err) { \
|
||||
fer = (struct fuse_err_ret) { \
|
||||
err, \
|
||||
true, \
|
||||
}; \
|
||||
break; \
|
||||
} \
|
||||
initialized = true; \
|
||||
\
|
||||
fa_backup = fa; \
|
||||
fa.opcode |= FUSE_PREFILTER; \
|
||||
for (i = 0; i < fa.in_numargs; ++i) \
|
||||
fa.out_args[i] = (struct fuse_bpf_arg) { \
|
||||
.size = fa.in_args[i].size, \
|
||||
.value = (void *)fa.in_args[i].value, \
|
||||
}; \
|
||||
fa.out_numargs = fa.in_numargs; \
|
||||
\
|
||||
ext_flags = fuse_inode->bpf ? \
|
||||
bpf_prog_run(fuse_inode->bpf, &fa) : \
|
||||
FUSE_BPF_BACKING; \
|
||||
if (ext_flags < 0) { \
|
||||
fer = (struct fuse_err_ret) { \
|
||||
ERR_PTR(ext_flags), \
|
||||
true, \
|
||||
}; \
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
if (ext_flags & FUSE_BPF_USER_FILTER) { \
|
||||
locked = fuse_lock_inode(inode); \
|
||||
res = fuse_bpf_simple_request(fm, &fa); \
|
||||
fuse_unlock_inode(inode, locked); \
|
||||
if (res < 0) { \
|
||||
fer = (struct fuse_err_ret) { \
|
||||
ERR_PTR(res), \
|
||||
true, \
|
||||
}; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if (!(ext_flags & FUSE_BPF_BACKING)) \
|
||||
break; \
|
||||
\
|
||||
fa.opcode &= ~FUSE_PREFILTER; \
|
||||
for (i = 0; i < fa.in_numargs; ++i) \
|
||||
fa.in_args[i] = (struct fuse_bpf_in_arg) { \
|
||||
.size = fa.out_args[i].size, \
|
||||
.value = fa.out_args[i].value, \
|
||||
}; \
|
||||
for (i = 0; i < fa_backup.out_numargs; ++i) \
|
||||
fa.out_args[i] = (struct fuse_bpf_arg) { \
|
||||
.size = fa_backup.out_args[i].size, \
|
||||
.value = fa_backup.out_args[i].value, \
|
||||
}; \
|
||||
fa.out_numargs = fa_backup.out_numargs; \
|
||||
\
|
||||
fer = (struct fuse_err_ret) { \
|
||||
ERR_PTR(backing(&fa, args)), \
|
||||
true, \
|
||||
}; \
|
||||
if (IS_ERR(fer.result)) \
|
||||
fa.error_in = PTR_ERR(fer.result); \
|
||||
if (!(ext_flags & FUSE_BPF_POST_FILTER)) \
|
||||
break; \
|
||||
\
|
||||
fa.opcode |= FUSE_POSTFILTER; \
|
||||
for (i = 0; i < fa.out_numargs; ++i) \
|
||||
fa.in_args[fa.in_numargs++] = \
|
||||
(struct fuse_bpf_in_arg) { \
|
||||
.size = fa.out_args[i].size, \
|
||||
.value = fa.out_args[i].value, \
|
||||
}; \
|
||||
ext_flags = bpf_prog_run(fuse_inode->bpf, &fa); \
|
||||
if (ext_flags < 0) { \
|
||||
fer = (struct fuse_err_ret) { \
|
||||
ERR_PTR(ext_flags), \
|
||||
true, \
|
||||
}; \
|
||||
break; \
|
||||
} \
|
||||
if (!(ext_flags & FUSE_BPF_USER_FILTER)) \
|
||||
break; \
|
||||
\
|
||||
fa.out_args[0].size = fa_backup.out_args[0].size; \
|
||||
fa.out_args[1].size = fa_backup.out_args[1].size; \
|
||||
fa.out_numargs = fa_backup.out_numargs; \
|
||||
locked = fuse_lock_inode(inode); \
|
||||
res = fuse_bpf_simple_request(fm, &fa); \
|
||||
fuse_unlock_inode(inode, locked); \
|
||||
if (res < 0) { \
|
||||
fer.result = ERR_PTR(res); \
|
||||
break; \
|
||||
} \
|
||||
} while (false); \
|
||||
\
|
||||
if (initialized && fer.ret) { \
|
||||
err = finalize(&fa, args); \
|
||||
if (err) \
|
||||
fer.result = err; \
|
||||
} \
|
||||
\
|
||||
fer; \
|
||||
})
|
||||
|
||||
struct bpf_prog *fuse_get_bpf_prog(struct file *file);
|
||||
#endif /* CONFIG_FUSE_BPF */
|
||||
|
||||
#endif /* _FS_FUSE_I_H */
|
||||
|
322
fs/fuse/inode.c
322
fs/fuse/inode.c
@ -78,6 +78,10 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
|
||||
|
||||
fi->i_time = 0;
|
||||
fi->inval_mask = 0;
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
fi->backing_inode = NULL;
|
||||
fi->bpf = NULL;
|
||||
#endif
|
||||
fi->nodeid = 0;
|
||||
fi->nlookup = 0;
|
||||
fi->attr_version = 0;
|
||||
@ -120,6 +124,12 @@ static void fuse_evict_inode(struct inode *inode)
|
||||
/* Will write inode on close/munmap and in all other dirtiers */
|
||||
WARN_ON(inode->i_state & I_DIRTY_INODE);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
iput(fi->backing_inode);
|
||||
if (fi->bpf)
|
||||
bpf_prog_put(fi->bpf);
|
||||
fi->bpf = NULL;
|
||||
#endif
|
||||
truncate_inode_pages_final(&inode->i_data);
|
||||
clear_inode(inode);
|
||||
if (inode->i_sb->s_flags & SB_ACTIVE) {
|
||||
@ -162,6 +172,28 @@ static ino_t fuse_squash_ino(u64 ino64)
|
||||
return ino;
|
||||
}
|
||||
|
||||
static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
|
||||
const struct inode *inode)
|
||||
{
|
||||
*attr = (struct fuse_attr){
|
||||
.ino = inode->i_ino,
|
||||
.size = inode->i_size,
|
||||
.blocks = inode->i_blocks,
|
||||
.atime = inode->i_atime.tv_sec,
|
||||
.mtime = inode->i_mtime.tv_sec,
|
||||
.ctime = inode->i_ctime.tv_sec,
|
||||
.atimensec = inode->i_atime.tv_nsec,
|
||||
.mtimensec = inode->i_mtime.tv_nsec,
|
||||
.ctimensec = inode->i_ctime.tv_nsec,
|
||||
.mode = inode->i_mode,
|
||||
.nlink = inode->i_nlink,
|
||||
.uid = inode->i_uid.val,
|
||||
.gid = inode->i_gid.val,
|
||||
.rdev = inode->i_rdev,
|
||||
.blksize = 1u << inode->i_blkbits,
|
||||
};
|
||||
}
|
||||
|
||||
void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
|
||||
u64 attr_valid, u32 cache_mask)
|
||||
{
|
||||
@ -329,28 +361,104 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
|
||||
else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
|
||||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
|
||||
fuse_init_common(inode);
|
||||
init_special_inode(inode, inode->i_mode,
|
||||
new_decode_dev(attr->rdev));
|
||||
init_special_inode(inode, inode->i_mode, attr->rdev);
|
||||
} else
|
||||
BUG();
|
||||
}
|
||||
|
||||
struct fuse_inode_identifier {
|
||||
u64 nodeid;
|
||||
struct inode *backing_inode;
|
||||
};
|
||||
|
||||
static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
u64 nodeid = *(u64 *) _nodeidp;
|
||||
if (get_node_id(inode) == nodeid)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
struct fuse_inode_identifier *fii =
|
||||
(struct fuse_inode_identifier *) _nodeidp;
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
|
||||
return fii->nodeid == fi->nodeid;
|
||||
}
|
||||
|
||||
static int fuse_inode_backing_eq(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
struct fuse_inode_identifier *fii =
|
||||
(struct fuse_inode_identifier *) _nodeidp;
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
|
||||
return fii->nodeid == fi->nodeid
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
&& fii->backing_inode == fi->backing_inode
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
static int fuse_inode_set(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
u64 nodeid = *(u64 *) _nodeidp;
|
||||
get_fuse_inode(inode)->nodeid = nodeid;
|
||||
struct fuse_inode_identifier *fii =
|
||||
(struct fuse_inode_identifier *) _nodeidp;
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
|
||||
fi->nodeid = fii->nodeid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_inode_backing_set(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
struct fuse_inode_identifier *fii =
|
||||
(struct fuse_inode_identifier *) _nodeidp;
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
|
||||
fi->nodeid = fii->nodeid;
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
fi->backing_inode = fii->backing_inode;
|
||||
if (fi->backing_inode)
|
||||
ihold(fi->backing_inode);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct inode *fuse_iget_backing(struct super_block *sb, u64 nodeid,
|
||||
struct inode *backing_inode)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct fuse_inode *fi;
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
struct fuse_inode_identifier fii = {
|
||||
.nodeid = nodeid,
|
||||
.backing_inode = backing_inode,
|
||||
};
|
||||
struct fuse_attr attr;
|
||||
unsigned long hash = (unsigned long) backing_inode;
|
||||
|
||||
if (nodeid)
|
||||
hash = nodeid;
|
||||
|
||||
fuse_fill_attr_from_inode(&attr, backing_inode);
|
||||
inode = iget5_locked(sb, hash, fuse_inode_backing_eq,
|
||||
fuse_inode_backing_set, &fii);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
if ((inode->i_state & I_NEW)) {
|
||||
inode->i_flags |= S_NOATIME;
|
||||
if (!fc->writeback_cache)
|
||||
inode->i_flags |= S_NOCMTIME;
|
||||
fuse_init_common(inode);
|
||||
unlock_new_inode(inode);
|
||||
}
|
||||
|
||||
fi = get_fuse_inode(inode);
|
||||
fuse_init_inode(inode, &attr);
|
||||
spin_lock(&fi->lock);
|
||||
fi->nlookup++;
|
||||
spin_unlock(&fi->lock);
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||
int generation, struct fuse_attr *attr,
|
||||
u64 attr_valid, u64 attr_version)
|
||||
@ -358,6 +466,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||
struct inode *inode;
|
||||
struct fuse_inode *fi;
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
struct fuse_inode_identifier fii = {
|
||||
.nodeid = nodeid,
|
||||
};
|
||||
|
||||
/*
|
||||
* Auto mount points get their node id from the submount root, which is
|
||||
@ -379,7 +490,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||
}
|
||||
|
||||
retry:
|
||||
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
|
||||
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &fii);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
@ -411,13 +522,16 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 nodeid,
|
||||
{
|
||||
struct fuse_mount *fm_iter;
|
||||
struct inode *inode;
|
||||
struct fuse_inode_identifier fii = {
|
||||
.nodeid = nodeid,
|
||||
};
|
||||
|
||||
WARN_ON(!rwsem_is_locked(&fc->killsb));
|
||||
list_for_each_entry(fm_iter, &fc->mounts, fc_entry) {
|
||||
if (!fm_iter->sb)
|
||||
continue;
|
||||
|
||||
inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &nodeid);
|
||||
inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &fii);
|
||||
if (inode) {
|
||||
if (fm)
|
||||
*fm = fm_iter;
|
||||
@ -504,20 +618,6 @@ static void fuse_send_destroy(struct fuse_mount *fm)
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
|
||||
{
|
||||
stbuf->f_type = FUSE_SUPER_MAGIC;
|
||||
stbuf->f_bsize = attr->bsize;
|
||||
stbuf->f_frsize = attr->frsize;
|
||||
stbuf->f_blocks = attr->blocks;
|
||||
stbuf->f_bfree = attr->bfree;
|
||||
stbuf->f_bavail = attr->bavail;
|
||||
stbuf->f_files = attr->files;
|
||||
stbuf->f_ffree = attr->ffree;
|
||||
stbuf->f_namelen = attr->namelen;
|
||||
/* fsid is left zero */
|
||||
}
|
||||
|
||||
static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
@ -525,12 +625,24 @@ static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
FUSE_ARGS(args);
|
||||
struct fuse_statfs_out outarg;
|
||||
int err;
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret fer;
|
||||
#endif
|
||||
|
||||
if (!fuse_allow_current_process(fm->fc)) {
|
||||
buf->f_type = FUSE_SUPER_MAGIC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
fer = fuse_bpf_backing(dentry->d_inode, struct fuse_statfs_out,
|
||||
fuse_statfs_initialize, fuse_statfs_backing,
|
||||
fuse_statfs_finalize,
|
||||
dentry, buf);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
memset(&outarg, 0, sizeof(outarg));
|
||||
args.in_numargs = 0;
|
||||
args.opcode = FUSE_STATFS;
|
||||
@ -647,6 +759,9 @@ enum {
|
||||
OPT_ALLOW_OTHER,
|
||||
OPT_MAX_READ,
|
||||
OPT_BLKSIZE,
|
||||
OPT_ROOT_BPF,
|
||||
OPT_ROOT_DIR,
|
||||
OPT_NO_DAEMON,
|
||||
OPT_ERR
|
||||
};
|
||||
|
||||
@ -661,6 +776,9 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
|
||||
fsparam_u32 ("max_read", OPT_MAX_READ),
|
||||
fsparam_u32 ("blksize", OPT_BLKSIZE),
|
||||
fsparam_string ("subtype", OPT_SUBTYPE),
|
||||
fsparam_u32 ("root_bpf", OPT_ROOT_BPF),
|
||||
fsparam_u32 ("root_dir", OPT_ROOT_DIR),
|
||||
fsparam_flag ("no_daemon", OPT_NO_DAEMON),
|
||||
{}
|
||||
};
|
||||
|
||||
@ -744,6 +862,26 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
|
||||
ctx->blksize = result.uint_32;
|
||||
break;
|
||||
|
||||
case OPT_ROOT_BPF:
|
||||
ctx->root_bpf = bpf_prog_get_type_dev(result.uint_32,
|
||||
BPF_PROG_TYPE_FUSE, false);
|
||||
if (IS_ERR(ctx->root_bpf)) {
|
||||
ctx->root_bpf = NULL;
|
||||
return invalfc(fsc, "Unable to open bpf program");
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_ROOT_DIR:
|
||||
ctx->root_dir = fget(result.uint_32);
|
||||
if (!ctx->root_dir)
|
||||
return invalfc(fsc, "Unable to open root directory");
|
||||
break;
|
||||
|
||||
case OPT_NO_DAEMON:
|
||||
ctx->no_daemon = true;
|
||||
ctx->fd_present = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -756,6 +894,10 @@ static void fuse_free_fsc(struct fs_context *fsc)
|
||||
struct fuse_fs_context *ctx = fsc->fs_private;
|
||||
|
||||
if (ctx) {
|
||||
if (ctx->root_dir)
|
||||
fput(ctx->root_dir);
|
||||
if (ctx->root_bpf)
|
||||
bpf_prog_put(ctx->root_bpf);
|
||||
kfree(ctx->subtype);
|
||||
kfree(ctx);
|
||||
}
|
||||
@ -885,15 +1027,34 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_conn_get);
|
||||
|
||||
static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode)
|
||||
static struct inode *fuse_get_root_inode(struct super_block *sb,
|
||||
unsigned int mode,
|
||||
struct bpf_prog *root_bpf,
|
||||
struct file *backing_fd)
|
||||
{
|
||||
struct fuse_attr attr;
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
struct inode *inode;
|
||||
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
attr.mode = mode;
|
||||
attr.ino = FUSE_ROOT_ID;
|
||||
attr.nlink = 1;
|
||||
return fuse_iget(sb, 1, 0, &attr, 0, 0);
|
||||
inode = fuse_iget(sb, 1, 0, &attr, 0, 0);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
get_fuse_inode(inode)->bpf = root_bpf;
|
||||
if (root_bpf)
|
||||
bpf_prog_inc(root_bpf);
|
||||
|
||||
if (backing_fd) {
|
||||
get_fuse_inode(inode)->backing_inode = backing_fd->f_inode;
|
||||
ihold(backing_fd->f_inode);
|
||||
}
|
||||
#endif
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
struct fuse_inode_handle {
|
||||
@ -908,11 +1069,14 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
|
||||
struct inode *inode;
|
||||
struct dentry *entry;
|
||||
int err = -ESTALE;
|
||||
struct fuse_inode_identifier fii = {
|
||||
.nodeid = handle->nodeid,
|
||||
};
|
||||
|
||||
if (handle->nodeid == 0)
|
||||
goto out_err;
|
||||
|
||||
inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid);
|
||||
inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &fii);
|
||||
if (!inode) {
|
||||
struct fuse_entry_out outarg;
|
||||
const struct qstr name = QSTR_INIT(".", 1);
|
||||
@ -921,7 +1085,7 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
|
||||
goto out_err;
|
||||
|
||||
err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg,
|
||||
&inode);
|
||||
NULL, &inode);
|
||||
if (err && err != -ENOENT)
|
||||
goto out_err;
|
||||
if (err || !inode) {
|
||||
@ -1015,13 +1179,14 @@ static struct dentry *fuse_get_parent(struct dentry *child)
|
||||
struct inode *inode;
|
||||
struct dentry *parent;
|
||||
struct fuse_entry_out outarg;
|
||||
const struct qstr name = QSTR_INIT("..", 2);
|
||||
int err;
|
||||
|
||||
if (!fc->export_support)
|
||||
return ERR_PTR(-ESTALE);
|
||||
|
||||
err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
|
||||
&dotdot_name, &outarg, &inode);
|
||||
&name, &outarg, NULL, &inode);
|
||||
if (err) {
|
||||
if (err == -ENOENT)
|
||||
return ERR_PTR(-ESTALE);
|
||||
@ -1284,7 +1449,7 @@ void fuse_send_init(struct fuse_mount *fm)
|
||||
ia->args.nocreds = true;
|
||||
ia->args.end = process_init_reply;
|
||||
|
||||
if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
|
||||
if (unlikely(fm->fc->no_daemon) || fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
|
||||
process_init_reply(fm, &ia->args, -ENOTCONN);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_send_init);
|
||||
@ -1408,28 +1573,6 @@ void fuse_dev_free(struct fuse_dev *fud)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_free);
|
||||
|
||||
static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
|
||||
const struct fuse_inode *fi)
|
||||
{
|
||||
*attr = (struct fuse_attr){
|
||||
.ino = fi->inode.i_ino,
|
||||
.size = fi->inode.i_size,
|
||||
.blocks = fi->inode.i_blocks,
|
||||
.atime = fi->inode.i_atime.tv_sec,
|
||||
.mtime = fi->inode.i_mtime.tv_sec,
|
||||
.ctime = fi->inode.i_ctime.tv_sec,
|
||||
.atimensec = fi->inode.i_atime.tv_nsec,
|
||||
.mtimensec = fi->inode.i_mtime.tv_nsec,
|
||||
.ctimensec = fi->inode.i_ctime.tv_nsec,
|
||||
.mode = fi->inode.i_mode,
|
||||
.nlink = fi->inode.i_nlink,
|
||||
.uid = fi->inode.i_uid.val,
|
||||
.gid = fi->inode.i_gid.val,
|
||||
.rdev = fi->inode.i_rdev,
|
||||
.blksize = 1u << fi->inode.i_blkbits,
|
||||
};
|
||||
}
|
||||
|
||||
static void fuse_sb_defaults(struct super_block *sb)
|
||||
{
|
||||
sb->s_magic = FUSE_SUPER_MAGIC;
|
||||
@ -1473,7 +1616,7 @@ static int fuse_fill_super_submount(struct super_block *sb,
|
||||
if (parent_sb->s_subtype && !sb->s_subtype)
|
||||
return -ENOMEM;
|
||||
|
||||
fuse_fill_attr_from_inode(&root_attr, parent_fi);
|
||||
fuse_fill_attr_from_inode(&root_attr, &parent_fi->inode);
|
||||
root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0);
|
||||
/*
|
||||
* This inode is just a duplicate, so it is not looked up and
|
||||
@ -1600,13 +1743,16 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
fc->destroy = ctx->destroy;
|
||||
fc->no_control = ctx->no_control;
|
||||
fc->no_force_umount = ctx->no_force_umount;
|
||||
fc->no_daemon = ctx->no_daemon;
|
||||
|
||||
err = -ENOMEM;
|
||||
root = fuse_get_root_inode(sb, ctx->rootmode);
|
||||
root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_bpf,
|
||||
ctx->root_dir);
|
||||
sb->s_d_op = &fuse_root_dentry_operations;
|
||||
root_dentry = d_make_root(root);
|
||||
if (!root_dentry)
|
||||
goto err_dev_free;
|
||||
fuse_init_dentry_root(root_dentry, ctx->root_dir);
|
||||
/* Root dentry doesn't have .d_revalidate */
|
||||
sb->s_d_op = &fuse_dentry_operations;
|
||||
|
||||
@ -1645,18 +1791,20 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
|
||||
struct fuse_fs_context *ctx = fsc->fs_private;
|
||||
int err;
|
||||
|
||||
if (!ctx->file || !ctx->rootmode_present ||
|
||||
!ctx->user_id_present || !ctx->group_id_present)
|
||||
return -EINVAL;
|
||||
if (!ctx->no_daemon) {
|
||||
if (!ctx->file || !ctx->rootmode_present ||
|
||||
!ctx->user_id_present || !ctx->group_id_present)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Require mount to happen from the same user namespace which
|
||||
* opened /dev/fuse to prevent potential attacks.
|
||||
*/
|
||||
if ((ctx->file->f_op != &fuse_dev_operations) ||
|
||||
(ctx->file->f_cred->user_ns != sb->s_user_ns))
|
||||
return -EINVAL;
|
||||
ctx->fudptr = &ctx->file->private_data;
|
||||
/*
|
||||
* Require mount to happen from the same user namespace which
|
||||
* opened /dev/fuse to prevent potential attacks.
|
||||
*/
|
||||
if ((ctx->file->f_op != &fuse_dev_operations) ||
|
||||
(ctx->file->f_cred->user_ns != sb->s_user_ns))
|
||||
return -EINVAL;
|
||||
ctx->fudptr = &ctx->file->private_data;
|
||||
}
|
||||
|
||||
err = fuse_fill_super_common(sb, ctx);
|
||||
if (err)
|
||||
@ -1937,6 +2085,26 @@ static void fuse_fs_cleanup(void)
|
||||
|
||||
static struct kobject *fuse_kobj;
|
||||
|
||||
/* TODO Remove this once BPF_PROG_TYPE_FUSE is upstreamed */
|
||||
static ssize_t bpf_prog_type_fuse_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buff)
|
||||
{
|
||||
return sysfs_emit(buff, "%d\n", BPF_PROG_TYPE_FUSE);
|
||||
}
|
||||
|
||||
static struct kobj_attribute bpf_prog_type_fuse_attr =
|
||||
__ATTR_RO(bpf_prog_type_fuse);
|
||||
|
||||
static struct attribute *bpf_attributes[] = {
|
||||
&bpf_prog_type_fuse_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group bpf_attr_group = {
|
||||
.attrs = bpf_attributes,
|
||||
};
|
||||
/* TODO remove to here */
|
||||
|
||||
static int fuse_sysfs_init(void)
|
||||
{
|
||||
int err;
|
||||
@ -1951,8 +2119,15 @@ static int fuse_sysfs_init(void)
|
||||
if (err)
|
||||
goto out_fuse_unregister;
|
||||
|
||||
/* TODO Remove when BPF_PROG_TYPE_FUSE is upstreamed */
|
||||
err = sysfs_create_group(fuse_kobj, &bpf_attr_group);
|
||||
if (err)
|
||||
goto out_fuse_remove_mount_point;
|
||||
|
||||
return 0;
|
||||
|
||||
out_fuse_remove_mount_point:
|
||||
sysfs_remove_mount_point(fuse_kobj, "connections");
|
||||
out_fuse_unregister:
|
||||
kobject_put(fuse_kobj);
|
||||
out_err:
|
||||
@ -1989,11 +2164,21 @@ static int __init fuse_init(void)
|
||||
if (res)
|
||||
goto err_sysfs_cleanup;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
res = fuse_bpf_init();
|
||||
if (res)
|
||||
goto err_ctl_cleanup;
|
||||
#endif
|
||||
|
||||
sanitize_global_limit(&max_user_bgreq);
|
||||
sanitize_global_limit(&max_user_congthresh);
|
||||
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
err_ctl_cleanup:
|
||||
fuse_ctl_cleanup();
|
||||
#endif
|
||||
err_sysfs_cleanup:
|
||||
fuse_sysfs_cleanup();
|
||||
err_dev_cleanup:
|
||||
@ -2011,6 +2196,9 @@ static void __exit fuse_exit(void)
|
||||
fuse_ctl_cleanup();
|
||||
fuse_sysfs_cleanup();
|
||||
fuse_fs_cleanup();
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
fuse_bpf_cleanup();
|
||||
#endif
|
||||
fuse_dev_cleanup();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
95
include/uapi/linux/android_fuse.h
Normal file
95
include/uapi/linux/android_fuse.h
Normal 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
|
@ -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 {
|
||||
|
@ -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
128
kernel/bpf/bpf_fuse.c
Normal 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);
|
||||
|
||||
|
@ -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>
|
||||
|
2
tools/testing/selftests/filesystems/fuse/.gitignore
vendored
Normal file
2
tools/testing/selftests/filesystems/fuse/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
fuse_test
|
||||
*.raw
|
34
tools/testing/selftests/filesystems/fuse/Makefile
Normal file
34
tools/testing/selftests/filesystems/fuse/Makefile
Normal 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 $@
|
||||
|
2
tools/testing/selftests/filesystems/fuse/OWNERS
Normal file
2
tools/testing/selftests/filesystems/fuse/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
# include OWNERS from the authoritative android-mainline branch
|
||||
include kernel/common:android-mainline:/tools/testing/selftests/filesystems/incfs/OWNERS
|
791
tools/testing/selftests/filesystems/fuse/bpf_loader.c
Normal file
791
tools/testing/selftests/filesystems/fuse/bpf_loader.c
Normal 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;
|
||||
}
|
||||
|
||||
|
21
tools/testing/selftests/filesystems/fuse/fd.txt
Normal file
21
tools/testing/selftests/filesystems/fuse/fd.txt
Normal 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
|
252
tools/testing/selftests/filesystems/fuse/fd_bpf.c
Normal file
252
tools/testing/selftests/filesystems/fuse/fd_bpf.c
Normal 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;
|
||||
}
|
||||
}
|
294
tools/testing/selftests/filesystems/fuse/fuse_daemon.c
Normal file
294
tools/testing/selftests/filesystems/fuse/fuse_daemon.c
Normal 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;
|
||||
}
|
2142
tools/testing/selftests/filesystems/fuse/fuse_test.c
Normal file
2142
tools/testing/selftests/filesystems/fuse/fuse_test.c
Normal file
File diff suppressed because it is too large
Load Diff
507
tools/testing/selftests/filesystems/fuse/test_bpf.c
Normal file
507
tools/testing/selftests/filesystems/fuse/test_bpf.c
Normal 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;
|
||||
}
|
||||
}
|
179
tools/testing/selftests/filesystems/fuse/test_framework.h
Normal file
179
tools/testing/selftests/filesystems/fuse/test_framework.h
Normal 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
|
337
tools/testing/selftests/filesystems/fuse/test_fuse.h
Normal file
337
tools/testing/selftests/filesystems/fuse/test_fuse.h
Normal 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
|
65
tools/testing/selftests/filesystems/fuse/test_fuse_bpf.h
Normal file
65
tools/testing/selftests/filesystems/fuse/test_fuse_bpf.h
Normal 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
|
Loading…
Reference in New Issue
Block a user