mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-05 19:31:57 -04:00
BACKPORT: ANDROID: fuse-bpf v1
Bug: 202785178 Test: test_fuse passes on linux, feature works on cuttlefish Signed-off-by: Paul Lawrence <paullawrence@google.com> Signed-off-by: Daniel Rosenberg <drosen@google.com> Change-Id: I987684b799b07391ccde350e98fde7976f5601aa
This commit is contained in:
parent
2e6dc19099
commit
b3fbcd3ca5
16 changed files with 3213 additions and 70 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,5 +9,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 passthrough.o
|
||||
fuse-$(CONFIG_FUSE_DAX) += dax.o
|
||||
fuse-$(CONFIG_FUSE_BPF) += backing.o
|
||||
|
||||
virtiofs-y := virtio_fs.o
|
||||
|
|
|
|||
1924
fs/fuse/backing.c
Normal file
1924
fs/fuse/backing.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -243,6 +243,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;
|
||||
|
||||
|
|
@ -486,6 +491,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.error_in = args->error_in;
|
||||
req->args = args;
|
||||
if (args->end)
|
||||
__set_bit(FR_ASYNC, &req->flags);
|
||||
|
|
|
|||
382
fs/fuse/dir.c
382
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/sched.h>
|
||||
#include <linux/namei.h>
|
||||
|
|
@ -18,6 +20,8 @@
|
|||
#include <linux/iversion.h>
|
||||
#include <linux/posix_acl.h>
|
||||
|
||||
#include "../internal.h"
|
||||
|
||||
static void fuse_advise_use_readdirplus(struct inode *dir)
|
||||
{
|
||||
struct fuse_inode *fi = get_fuse_inode(dir);
|
||||
|
|
@ -25,7 +29,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;
|
||||
|
|
@ -37,19 +41,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
|
||||
|
||||
|
|
@ -74,6 +74,18 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
|
|||
__fuse_dentry_settime(dentry, time);
|
||||
}
|
||||
|
||||
void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir)
|
||||
{
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_dentry *fuse_dentry = root->d_fsdata;
|
||||
|
||||
if (backing_dir) {
|
||||
fuse_dentry->backing_path = backing_dir->f_path;
|
||||
path_get(&fuse_dentry->backing_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Set dentry and possibly attribute timeouts from the lookup/mk*
|
||||
* replies
|
||||
|
|
@ -144,7 +156,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;
|
||||
|
|
@ -152,9 +165,12 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -180,6 +196,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
|||
else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
|
||||
(flags & LOOKUP_REVAL)) {
|
||||
struct fuse_entry_out outarg;
|
||||
struct fuse_entry_bpf_out bpf_outarg;
|
||||
FUSE_ARGS(args);
|
||||
struct fuse_forget_link *forget;
|
||||
u64 attr_version;
|
||||
|
|
@ -191,7 +208,20 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
|||
ret = -ECHILD;
|
||||
if (flags & LOOKUP_RCU)
|
||||
goto out;
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
{
|
||||
struct fuse_err_ret fer;
|
||||
|
||||
fer = fuse_bpf_backing(entry->d_parent->d_inode,
|
||||
struct fuse_lookup_io,
|
||||
fuse_lookup_initialize,
|
||||
fuse_revalidate_backing,
|
||||
fuse_revalidate_finalize,
|
||||
d_inode(entry->d_parent), entry, flags);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
}
|
||||
#endif
|
||||
fm = get_fuse_mount(inode);
|
||||
|
||||
forget = fuse_alloc_forget();
|
||||
|
|
@ -202,8 +232,9 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
|||
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_outarg);
|
||||
ret = fuse_simple_request(fm, &args);
|
||||
dput(parent);
|
||||
/* Zero nodeid is same as -ENOENT */
|
||||
|
|
@ -253,17 +284,20 @@ invalid:
|
|||
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);
|
||||
}
|
||||
|
|
@ -402,7 +436,7 @@ out:
|
|||
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
|
||||
|
|
@ -411,7 +445,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
|
||||
|
|
@ -430,7 +464,10 @@ 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 fuse_entry_bpf_out *bpf_outarg,
|
||||
struct dentry *entry,
|
||||
struct inode **inode)
|
||||
{
|
||||
struct fuse_mount *fm = get_fuse_mount_super(sb);
|
||||
FUSE_ARGS(args);
|
||||
|
|
@ -451,23 +488,82 @@ 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_outarg);
|
||||
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_outarg)) {
|
||||
/* TODO Make sure this handles invalid handles */
|
||||
/* TODO Do we need the same code in revalidate */
|
||||
struct file *backing_file;
|
||||
struct inode *backing_inode;
|
||||
|
||||
err = -ENOENT;
|
||||
if (!entry)
|
||||
goto out_queue_forget;
|
||||
|
||||
err = -EINVAL;
|
||||
if (bpf_outarg->backing_action != FUSE_ACTION_REPLACE)
|
||||
goto out_queue_forget;
|
||||
|
||||
backing_file = fuse_fget(fm->fc, bpf_outarg->backing_fd);
|
||||
if (!backing_file)
|
||||
goto out_queue_forget;
|
||||
|
||||
/* TODO userspace doesn't really know when the right time to
|
||||
* close the passed fd is. This because after replying to the
|
||||
* driver request, so assume that after a lookup with bpf_args,
|
||||
* the daemon passes the fd ownership to the kernel, which also
|
||||
* takes care of closing it at the right time.
|
||||
*/
|
||||
__close_fd(fm->fc->task->files, bpf_outarg->backing_fd);
|
||||
|
||||
backing_inode = backing_file->f_inode;
|
||||
*inode = fuse_iget_backing(sb, backing_inode);
|
||||
if (!*inode)
|
||||
goto bpf_outarg_out;
|
||||
|
||||
if (bpf_outarg->bpf_action == FUSE_ACTION_REPLACE) {
|
||||
struct bpf_prog *bpf_prog = fuse_get_bpf_prog(fm->fc,
|
||||
bpf_outarg->bpf_fd);
|
||||
|
||||
if (IS_ERR(bpf_prog)) {
|
||||
iput(*inode);
|
||||
*inode = NULL;
|
||||
err = PTR_ERR(bpf_prog);
|
||||
goto bpf_outarg_out;
|
||||
}
|
||||
get_fuse_inode(*inode)->bpf = bpf_prog;
|
||||
}
|
||||
|
||||
get_fuse_dentry(entry)->backing_path = backing_file->f_path;
|
||||
path_get(&get_fuse_dentry(entry)->backing_path);
|
||||
|
||||
bpf_outarg_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;
|
||||
}
|
||||
|
|
@ -484,17 +580,29 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
|
|||
{
|
||||
int err;
|
||||
struct fuse_entry_out outarg;
|
||||
struct fuse_entry_bpf_out bpf_outarg = {0};
|
||||
struct inode *inode;
|
||||
struct dentry *newent;
|
||||
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, &bpf_outarg, entry, &inode);
|
||||
fuse_unlock_inode(dir, locked);
|
||||
if (err == -ENOENT) {
|
||||
outarg_valid = false;
|
||||
|
|
@ -553,6 +661,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)
|
||||
|
|
@ -745,6 +867,17 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, umode_t mode,
|
|||
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();
|
||||
|
||||
|
|
@ -773,6 +906,17 @@ static int fuse_mkdir(struct inode *dir, struct dentry *entry, umode_t mode)
|
|||
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();
|
||||
|
||||
|
|
@ -795,6 +939,17 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry,
|
|||
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;
|
||||
|
|
@ -821,6 +976,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;
|
||||
|
|
@ -860,6 +1029,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;
|
||||
|
|
@ -945,6 +1128,18 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
|
|||
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;
|
||||
|
||||
|
|
@ -956,6 +1151,17 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
|
|||
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));
|
||||
|
|
@ -973,6 +1179,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;
|
||||
|
|
@ -1073,23 +1289,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)
|
||||
{
|
||||
|
|
@ -1097,6 +1303,17 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|||
int err = 0;
|
||||
bool sync;
|
||||
|
||||
#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)
|
||||
|
|
@ -1121,7 +1338,7 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|||
int fuse_update_attributes(struct inode *inode, struct file *file)
|
||||
{
|
||||
/* Do *not* need to get atime for internal purposes */
|
||||
return fuse_update_get_attr(inode, file, NULL,
|
||||
return fuse_update_get_attr(inode, file, &file->f_path, NULL,
|
||||
STATX_BASIC_STATS & ~STATX_ATIME, 0);
|
||||
}
|
||||
|
||||
|
|
@ -1230,6 +1447,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)
|
||||
|
|
@ -1256,6 +1483,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask)
|
|||
return -ECHILD;
|
||||
|
||||
forget_all_cached_acls(inode);
|
||||
/* TODO: BPF stuff here? But we have no dentry for path for vfs_getattr */
|
||||
return fuse_do_getattr(inode, NULL, NULL);
|
||||
}
|
||||
|
||||
|
|
@ -1379,6 +1607,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_raw(dentry, inode, callback);
|
||||
|
||||
|
|
@ -1412,8 +1655,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;
|
||||
}
|
||||
|
||||
|
|
@ -1427,6 +1680,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_BFP
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -1578,6 +1844,16 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
|
|||
bool trust_local_cmtime = is_wb && S_ISREG(inode->i_mode);
|
||||
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;
|
||||
|
||||
|
|
@ -1741,11 +2017,22 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
|
|||
* This should be done on write(), truncate() and chown().
|
||||
*/
|
||||
if (!fc->handle_killpriv) {
|
||||
#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;
|
||||
|
||||
|
|
@ -1801,7 +2088,8 @@ static int fuse_getattr(const struct path *path, struct kstat *stat,
|
|||
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 = {
|
||||
|
|
|
|||
107
fs/fuse/file.c
107
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>
|
||||
|
|
@ -137,7 +138,11 @@ int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
|
|||
struct fuse_file *ff;
|
||||
int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
|
||||
|
||||
ff = fuse_file_alloc(fm);
|
||||
if (file->private_data) {
|
||||
ff = file->private_data;
|
||||
file->private_data = NULL;
|
||||
} else
|
||||
ff = fuse_file_alloc(fm);
|
||||
if (!ff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -236,6 +241,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);
|
||||
fuse_set_nowrite(inode);
|
||||
|
|
@ -334,6 +353,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
|
||||
|
||||
/* see fuse_vma_close() for !writeback_cache case */
|
||||
if (fc->writeback_cache)
|
||||
write_inode_now(inode, 1);
|
||||
|
|
@ -471,6 +501,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;
|
||||
|
||||
|
|
@ -543,6 +584,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;
|
||||
|
||||
|
|
@ -1631,6 +1683,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_read_in,
|
||||
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))
|
||||
|
|
@ -1651,6 +1717,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))
|
||||
|
|
@ -1906,6 +1986,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;
|
||||
|
||||
ff = __fuse_write_file_get(fc, fi);
|
||||
err = fuse_flush_times(inode, ff);
|
||||
if (ff)
|
||||
|
|
@ -3327,6 +3420,18 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
|
|||
(!(mode & FALLOC_FL_KEEP_SIZE) ||
|
||||
(mode & FALLOC_FL_PUNCH_HOLE));
|
||||
|
||||
#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))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
|
|
|
|||
519
fs/fuse/fuse_i.h
519
fs/fuse/fuse_i.h
|
|
@ -13,6 +13,8 @@
|
|||
# define pr_fmt(fmt) "fuse: " fmt
|
||||
#endif
|
||||
|
||||
#include <linux/filter.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/fuse.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
|
|
@ -63,11 +65,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;
|
||||
|
|
@ -239,6 +287,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;
|
||||
|
||||
|
|
@ -488,6 +544,8 @@ struct fuse_fs_context {
|
|||
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;
|
||||
|
|
@ -780,6 +838,9 @@ struct fuse_conn {
|
|||
|
||||
/** Protects passthrough_req */
|
||||
spinlock_t passthrough_req_lock;
|
||||
|
||||
/** task_struct for fd lookups in fuse-bpf */
|
||||
struct task_struct *task;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -876,12 +937,16 @@ extern const struct dentry_operations fuse_root_dentry_operations;
|
|||
/**
|
||||
* Get a filled in inode
|
||||
*/
|
||||
struct inode *fuse_iget_backing(struct super_block *sb,
|
||||
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 fuse_entry_bpf_out *bpf_outarg,
|
||||
struct dentry *entry, struct inode **inode);
|
||||
|
||||
/**
|
||||
* Send FORGET command
|
||||
|
|
@ -1018,6 +1083,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);
|
||||
|
||||
/**
|
||||
|
|
@ -1232,6 +1298,293 @@ 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 file *fuse_fget(struct fuse_conn *fc, unsigned int fd);
|
||||
struct bpf_prog *fuse_get_bpf_prog(struct fuse_conn *fc, unsigned int fd);
|
||||
|
||||
/*
|
||||
* 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_args *fa, struct fuse_open_io *foi,
|
||||
struct inode *inode, struct file *file, bool isdir);
|
||||
int fuse_open_backing(struct fuse_args *fa,
|
||||
struct inode *inode, struct file *file, bool isdir);
|
||||
void *fuse_open_finalize(struct fuse_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_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_args *fa,
|
||||
struct inode *dir, struct dentry *entry,
|
||||
struct file *file, unsigned int flags, umode_t mode);
|
||||
void *fuse_create_open_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry,
|
||||
struct file *file, unsigned int flags, umode_t mode);
|
||||
|
||||
int fuse_mknod_initialize(
|
||||
struct fuse_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_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
|
||||
void *fuse_mknod_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev);
|
||||
|
||||
int fuse_mkdir_initialize(
|
||||
struct fuse_args *fa, struct fuse_mkdir_in *fmi,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
int fuse_mkdir_backing(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
void *fuse_mkdir_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry, umode_t mode);
|
||||
|
||||
int fuse_rmdir_initialize(
|
||||
struct fuse_args *fa, struct fuse_dummy_io *fmi,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
int fuse_rmdir_backing(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
void *fuse_rmdir_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
|
||||
int fuse_rename2_initialize(struct fuse_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_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent,
|
||||
unsigned int flags);
|
||||
void *fuse_rename2_finalize(struct fuse_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent,
|
||||
unsigned int flags);
|
||||
|
||||
int fuse_rename_initialize(struct fuse_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_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent);
|
||||
void *fuse_rename_finalize(struct fuse_args *fa,
|
||||
struct inode *olddir, struct dentry *oldent,
|
||||
struct inode *newdir, struct dentry *newent);
|
||||
|
||||
int fuse_unlink_initialize(
|
||||
struct fuse_args *fa, struct fuse_dummy_io *fmi,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
int fuse_unlink_backing(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
void *fuse_unlink_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry);
|
||||
|
||||
int fuse_link_initialize(struct fuse_args *fa, struct fuse_link_in *fli,
|
||||
struct dentry *entry, struct inode *dir,
|
||||
struct dentry *newent);
|
||||
int fuse_link_backing(struct fuse_args *fa, struct dentry *entry,
|
||||
struct inode *dir, struct dentry *newent);
|
||||
void *fuse_link_finalize(struct fuse_args *fa, struct dentry *entry,
|
||||
struct inode *dir, struct dentry *newent);
|
||||
|
||||
int fuse_release_initialize(struct fuse_args *fa, struct fuse_release_in *fri,
|
||||
struct inode *inode, struct file *file);
|
||||
int fuse_releasedir_initialize(struct fuse_args *fa,
|
||||
struct fuse_release_in *fri,
|
||||
struct inode *inode, struct file *file);
|
||||
int fuse_release_backing(struct fuse_args *fa,
|
||||
struct inode *inode, struct file *file);
|
||||
void *fuse_release_finalize(struct fuse_args *fa,
|
||||
struct inode *inode, struct file *file);
|
||||
|
||||
int fuse_flush_initialize(struct fuse_args *fa, struct fuse_flush_in *ffi,
|
||||
struct file *file, fl_owner_t id);
|
||||
int fuse_flush_backing(struct fuse_args *fa, struct file *file, fl_owner_t id);
|
||||
void *fuse_flush_finalize(struct fuse_args *fa,
|
||||
struct file *file, fl_owner_t id);
|
||||
|
||||
int fuse_fsync_initialize(struct fuse_args *fa, struct fuse_fsync_in *ffi,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
int fuse_fsync_backing(struct fuse_args *fa,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
void *fuse_fsync_finalize(struct fuse_args *fa,
|
||||
struct file *file, loff_t start, loff_t end, int datasync);
|
||||
int fuse_dir_fsync_initialize(struct fuse_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_args *fa, struct fuse_getxattr_io *fgio,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
int fuse_getxattr_backing(
|
||||
struct fuse_args *fa,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
void *fuse_getxattr_finalize(
|
||||
struct fuse_args *fa,
|
||||
struct dentry *dentry, const char *name, void *value,
|
||||
size_t size);
|
||||
|
||||
int fuse_listxattr_initialize(struct fuse_args *fa,
|
||||
struct fuse_getxattr_io *fgio,
|
||||
struct dentry *dentry, char *list, size_t size);
|
||||
int fuse_listxattr_backing(struct fuse_args *fa, struct dentry *dentry,
|
||||
char *list, size_t size);
|
||||
void *fuse_listxattr_finalize(struct fuse_args *fa, struct dentry *dentry,
|
||||
char *list, size_t size);
|
||||
|
||||
int fuse_setxattr_initialize(struct fuse_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_args *fa, struct dentry *dentry,
|
||||
const char *name, const void *value, size_t size,
|
||||
int flags);
|
||||
void *fuse_setxattr_finalize(struct fuse_args *fa, struct dentry *dentry,
|
||||
const char *name, const void *value, size_t size,
|
||||
int flags);
|
||||
|
||||
int fuse_file_read_iter_initialize(
|
||||
struct fuse_args *fa, struct fuse_read_in *fri,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
int fuse_file_read_iter_backing(struct fuse_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
void *fuse_file_read_iter_finalize(struct fuse_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *to);
|
||||
|
||||
struct fuse_file_write_iter_io {
|
||||
struct fuse_write_in fwi;
|
||||
struct fuse_write_out fwo;
|
||||
};
|
||||
|
||||
int fuse_file_write_iter_initialize(
|
||||
struct fuse_args *fa, struct fuse_file_write_iter_io *fwio,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
int fuse_file_write_iter_backing(struct fuse_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
void *fuse_file_write_iter_finalize(struct fuse_args *fa,
|
||||
struct kiocb *iocb, struct iov_iter *from);
|
||||
|
||||
int fuse_file_fallocate_initialize(struct fuse_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_args *fa,
|
||||
struct file *file, int mode, loff_t offset, loff_t length);
|
||||
void *fuse_file_fallocate_finalize(struct fuse_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_out febo;
|
||||
};
|
||||
|
||||
int fuse_lookup_initialize(struct fuse_args *fa, struct fuse_lookup_io *feo,
|
||||
struct inode *dir, struct dentry *entry, unsigned int flags);
|
||||
int fuse_lookup_backing(struct fuse_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
struct dentry *fuse_lookup_finalize(struct fuse_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
int fuse_revalidate_backing(struct fuse_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
void *fuse_revalidate_finalize(struct fuse_args *fa, struct inode *dir,
|
||||
struct dentry *entry, unsigned int flags);
|
||||
|
||||
struct fuse_getattr_io {
|
||||
struct fuse_getattr_in fgi;
|
||||
struct fuse_attr_out fao;
|
||||
};
|
||||
int fuse_getattr_initialize(struct fuse_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_args *fa,
|
||||
const struct dentry *entry, struct kstat *stat,
|
||||
u32 request_mask, unsigned int flags);
|
||||
void *fuse_getattr_finalize(struct fuse_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_args *fa, struct fuse_setattr_io *fsi,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
int fuse_setattr_backing(struct fuse_args *fa,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
void *fuse_setattr_finalize(struct fuse_args *fa,
|
||||
struct dentry *dentry, struct iattr *attr, struct file *file);
|
||||
|
||||
int fuse_get_link_initialize(struct fuse_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_args *fa,
|
||||
struct inode *inode, struct dentry *dentry,
|
||||
struct delayed_call *callback, const char **out);
|
||||
void *fuse_get_link_finalize(struct fuse_args *fa,
|
||||
struct inode *inode, struct dentry *dentry,
|
||||
struct delayed_call *callback, const char **out);
|
||||
|
||||
int fuse_symlink_initialize(
|
||||
struct fuse_args *fa, struct fuse_dummy_io *unused,
|
||||
struct inode *dir, struct dentry *entry, const char *link, int len);
|
||||
int fuse_symlink_backing(
|
||||
struct fuse_args *fa,
|
||||
struct inode *dir, struct dentry *entry, const char *link, int len);
|
||||
void *fuse_symlink_finalize(
|
||||
struct fuse_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_args *fa, struct fuse_read_io *frio,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force);
|
||||
int fuse_readdir_backing(struct fuse_args *fa,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force);
|
||||
void *fuse_readdir_finalize(struct fuse_args *fa,
|
||||
struct file *file, struct dir_context *ctx,
|
||||
bool *force_again, bool *allow_force);
|
||||
|
||||
int fuse_access_initialize(struct fuse_args *fa, struct fuse_access_in *fai,
|
||||
struct inode *inode, int mask);
|
||||
int fuse_access_backing(struct fuse_args *fa, struct inode *inode, int mask);
|
||||
void *fuse_access_finalize(struct fuse_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
|
||||
|
|
@ -1314,4 +1667,168 @@ static inline void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr,
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
struct fuse_err_ret {
|
||||
void *result;
|
||||
bool ret;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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_args *fa, io *in_out, args...): function that sets
|
||||
* up fa and io based on args
|
||||
* int backing(struct fuse_args *fa, args...): function that actually performs
|
||||
* the backing io operation
|
||||
* void *finalize(struct fuse_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_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_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_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_in_arg) { \
|
||||
.size = fa.out_args[i].size, \
|
||||
.value = fa.out_args[i].value, \
|
||||
}; \
|
||||
for (i = 0; i < fa.out_numargs; ++i) \
|
||||
fa.out_args[i] = (struct fuse_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 (!(ext_flags & FUSE_BPF_POST_FILTER)) \
|
||||
break; \
|
||||
\
|
||||
fa.opcode |= FUSE_POSTFILTER; \
|
||||
if (IS_ERR(fer.result)) \
|
||||
fa.error_in = PTR_ERR(fer.result); \
|
||||
for (i = 0; i < fa.out_numargs; ++i) \
|
||||
fa.in_args[fa.in_numargs++] = \
|
||||
(struct fuse_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_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; \
|
||||
})
|
||||
#endif /* CONFIG_FUSE_BPF */
|
||||
|
||||
#endif /* _FS_FUSE_I_H */
|
||||
|
|
|
|||
179
fs/fuse/inode.c
179
fs/fuse/inode.c
|
|
@ -79,6 +79,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;
|
||||
|
|
@ -119,6 +123,12 @@ static void fuse_evict_inode(struct inode *inode)
|
|||
{
|
||||
struct fuse_inode *fi = get_fuse_inode(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) {
|
||||
|
|
@ -313,22 +323,74 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
|
|||
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
|
||||
#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;
|
||||
#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,
|
||||
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 = {
|
||||
.backing_inode = backing_inode,
|
||||
};
|
||||
struct fuse_attr attr;
|
||||
|
||||
fuse_fill_attr_from_inode(&attr, backing_inode);
|
||||
inode = iget5_locked(sb, (unsigned long) backing_inode, fuse_inode_eq,
|
||||
fuse_inode_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)
|
||||
|
|
@ -336,6 +398,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
|
||||
|
|
@ -357,7 +422,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;
|
||||
|
||||
|
|
@ -392,13 +457,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;
|
||||
|
|
@ -537,6 +605,8 @@ enum {
|
|||
OPT_ALLOW_OTHER,
|
||||
OPT_MAX_READ,
|
||||
OPT_BLKSIZE,
|
||||
OPT_ROOT_BPF,
|
||||
OPT_ROOT_DIR,
|
||||
OPT_ERR
|
||||
};
|
||||
|
||||
|
|
@ -551,6 +621,8 @@ static const struct fs_parameter_spec fuse_param_specs[] = {
|
|||
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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
@ -639,6 +711,21 @@ static int fuse_parse_param(struct fs_context *fc, 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 invalf(fc, "Unable to open bpf program");
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_ROOT_DIR:
|
||||
ctx->root_dir = fget(result.uint_32);
|
||||
if (!ctx->root_dir)
|
||||
return invalf(fc, "Unable to open root directory");
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -651,6 +738,10 @@ static void fuse_free_fc(struct fs_context *fc)
|
|||
struct fuse_fs_context *ctx = fc->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);
|
||||
}
|
||||
|
|
@ -787,15 +878,34 @@ struct fuse_mount *fuse_mount_get(struct fuse_mount *fm)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_mount_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 {
|
||||
|
|
@ -810,20 +920,24 @@ 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;
|
||||
struct fuse_entry_bpf_out bpf_outarg;
|
||||
const struct qstr name = QSTR_INIT(".", 1);
|
||||
|
||||
if (!fc->export_support)
|
||||
goto out_err;
|
||||
|
||||
err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg,
|
||||
&inode);
|
||||
&bpf_outarg, NULL, &inode);
|
||||
if (err && err != -ENOENT)
|
||||
goto out_err;
|
||||
if (err || !inode) {
|
||||
|
|
@ -917,6 +1031,7 @@ static struct dentry *fuse_get_parent(struct dentry *child)
|
|||
struct inode *inode;
|
||||
struct dentry *parent;
|
||||
struct fuse_entry_out outarg;
|
||||
struct fuse_entry_bpf_out bpf_outarg;
|
||||
const struct qstr name = QSTR_INIT("..", 2);
|
||||
int err;
|
||||
|
||||
|
|
@ -924,7 +1039,7 @@ static struct dentry *fuse_get_parent(struct dentry *child)
|
|||
return ERR_PTR(-ESTALE);
|
||||
|
||||
err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
|
||||
&name, &outarg, &inode);
|
||||
&name, &outarg, &bpf_outarg, NULL, &inode);
|
||||
if (err) {
|
||||
if (err == -ENOENT)
|
||||
return ERR_PTR(-ESTALE);
|
||||
|
|
@ -1110,6 +1225,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
|
|||
fc->minor = arg->minor;
|
||||
fc->max_write = arg->minor < 5 ? 4096 : arg->max_write;
|
||||
fc->max_write = max_t(unsigned, 4096, fc->max_write);
|
||||
fc->task = get_task_struct(current);
|
||||
fc->conn_init = 1;
|
||||
}
|
||||
kfree(ia);
|
||||
|
|
@ -1186,6 +1302,8 @@ void fuse_free_conn(struct fuse_conn *fc)
|
|||
idr_for_each(&fc->passthrough_req, free_fuse_passthrough, NULL);
|
||||
idr_destroy(&fc->passthrough_req);
|
||||
kfree_rcu(fc, rcu);
|
||||
if (fc->task)
|
||||
put_task_struct(fc->task);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_free_conn);
|
||||
|
||||
|
|
@ -1410,11 +1528,13 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
|||
fc->no_force_umount = ctx->no_force_umount;
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -1710,6 +1830,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;
|
||||
|
|
@ -1724,8 +1864,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:
|
||||
|
|
|
|||
|
|
@ -579,6 +579,21 @@ 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 force_again, allow_force;
|
||||
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);
|
||||
if (force_again && !IS_ERR(fer.result))
|
||||
goto again;
|
||||
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (fuse_is_bad(inode))
|
||||
return -EIO;
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,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;
|
||||
|
||||
|
|
@ -181,6 +192,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, int flags)
|
||||
{
|
||||
#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;
|
||||
|
||||
|
|
@ -192,6 +214,17 @@ 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;
|
||||
|
||||
fer = fuse_bpf_backing(inode, struct fuse_setxattr_in,
|
||||
fuse_setxattr_initialize, fuse_setxattr_backing,
|
||||
fuse_setxattr_finalize, dentry, name, value,
|
||||
size, flags);
|
||||
if (fer.ret)
|
||||
return PTR_ERR(fer.result);
|
||||
#endif
|
||||
|
||||
if (fuse_is_bad(inode))
|
||||
return -EIO;
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LSM, lsm,
|
|||
void *, void *)
|
||||
#endif /* CONFIG_BPF_LSM */
|
||||
#endif
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
BPF_PROG_TYPE(BPF_PROG_TYPE_FUSE, fuse, struct fuse_args, struct fuse_args)
|
||||
#endif
|
||||
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ enum bpf_prog_type {
|
|||
BPF_PROG_TYPE_EXT,
|
||||
BPF_PROG_TYPE_LSM,
|
||||
BPF_PROG_TYPE_SK_LOOKUP,
|
||||
BPF_PROG_TYPE_FUSE,
|
||||
};
|
||||
|
||||
enum bpf_attach_type {
|
||||
|
|
|
|||
|
|
@ -516,6 +516,17 @@ struct fuse_entry_out {
|
|||
struct fuse_attr attr;
|
||||
};
|
||||
|
||||
#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_forget_in {
|
||||
uint64_t nlookup;
|
||||
};
|
||||
|
|
@ -813,7 +824,7 @@ struct fuse_in_header {
|
|||
uint32_t uid;
|
||||
uint32_t gid;
|
||||
uint32_t pid;
|
||||
uint32_t padding;
|
||||
uint32_t error_in;
|
||||
};
|
||||
|
||||
struct fuse_out_header {
|
||||
|
|
@ -978,12 +989,20 @@ 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[5];
|
||||
struct fuse_arg out_args[3];
|
||||
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
|
||||
|
||||
/* Path used for completing d_canonical_path */
|
||||
struct path *canonical_path;
|
||||
};
|
||||
|
||||
#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_FUSE_H */
|
||||
|
|
|
|||
|
|
@ -36,3 +36,6 @@ obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o
|
|||
obj-${CONFIG_BPF_LSM} += bpf_lsm.o
|
||||
endif
|
||||
obj-$(CONFIG_BPF_PRELOAD) += preload/
|
||||
ifeq ($(CONFIG_FUSE_BPF),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += bpf_fuse.o
|
||||
endif
|
||||
|
|
|
|||
72
kernel/bpf/bpf_fuse.c
Normal file
72
kernel/bpf/bpf_fuse.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2021 Google LLC
|
||||
|
||||
#include <linux/filter.h>
|
||||
#include <linux/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_args, out_args))
|
||||
return false;
|
||||
|
||||
/* TODO This is garbage. Do it properly */
|
||||
for (i = 0; i < 5; i++) {
|
||||
if (off == offsetof(struct fuse_args, in_args[i].value)) {
|
||||
info->reg_type = PTR_TO_RDONLY_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_args, out_args[i].value)) {
|
||||
info->reg_type = PTR_TO_RDWR_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 = {
|
||||
};
|
||||
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <uapi/linux/btf.h>
|
||||
#include <uapi/linux/bpf.h>
|
||||
#include <uapi/linux/fuse.h>
|
||||
#include <uapi/linux/bpf_perf_event.h>
|
||||
#include <uapi/linux/types.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
|
|
|||
Loading…
Reference in a new issue