forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fuse: Add fuse-bpf, a stacked fs extension for FUSE
Fuse-bpf provides a short circuit path for Fuse implementations that act as a stacked filesystem. For cases that are directly unchanged, operations are passed directly to the backing filesystem. Small adjustments can be handled by bpf prefilters or postfilters, with the option to fall back to userspace as needed. Fuse implementations may supply backing node information, as well as bpf programs via an optional add on to the lookup structure. This has been split over the next set of patches for readability. Clusters of fuse ops have been split into their own patches, as well as the actual bpf calls and userspace calls for filters. Signed-off-by: Daniel Rosenberg <drosen@google.com> Signed-off-by: Paul Lawrence <paullawrence@google.com> Signed-off-by: Alessio Balsini <balsini@google.com>
- Loading branch information
1 parent
0908c91
commit 6e7c858
Showing
10 changed files
with
913 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* FUSE-BPF: Filesystem in Userspace with BPF | ||
* Copyright (c) 2021 Google LLC | ||
*/ | ||
|
||
#include "fuse_i.h" | ||
|
||
#include <linux/fdtable.h> | ||
#include <linux/filter.h> | ||
#include <linux/fs_stack.h> | ||
#include <linux/namei.h> | ||
#include <linux/bpf_fuse.h> | ||
|
||
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; | ||
|
||
if (file->f_op != &bpf_prog_fops) | ||
return bpf_prog; | ||
|
||
bpf_prog = file->private_data; | ||
if (bpf_prog->type == BPF_PROG_TYPE_FUSE) | ||
bpf_prog_inc(bpf_prog); | ||
else | ||
bpf_prog = ERR_PTR(-EINVAL); | ||
|
||
return bpf_prog; | ||
} | ||
|
||
void fuse_get_backing_path(struct file *file, struct path *path) | ||
{ | ||
path_get(&file->f_path); | ||
*path = file->f_path; | ||
} | ||
|
||
int parse_fuse_entry_bpf(struct fuse_entry_bpf *feb) | ||
{ | ||
struct fuse_entry_bpf_out *febo = &feb->out; | ||
struct bpf_prog *bpf; | ||
struct file *file; | ||
int err = 0; | ||
|
||
if (febo->backing_action == FUSE_ACTION_REPLACE) { | ||
file = fget(febo->backing_fd); | ||
if (!file) { | ||
err = -EBADF; | ||
goto out_err; | ||
} | ||
fuse_get_backing_path(file, &feb->backing_path); | ||
fput(file); | ||
} | ||
if (febo->bpf_action == FUSE_ACTION_REPLACE) { | ||
file = fget(febo->bpf_fd); | ||
if (!file) { | ||
err = -EBADF; | ||
goto out_put; | ||
} | ||
bpf = fuse_get_bpf_prog(file); | ||
if (IS_ERR(bpf)) { | ||
err = PTR_ERR(bpf); | ||
goto out_fput; | ||
} | ||
feb->bpf = bpf; | ||
fput(file); | ||
} | ||
|
||
return 0; | ||
out_fput: | ||
fput(file); | ||
out_put: | ||
path_put_init(&feb->backing_path); | ||
out_err: | ||
return err; | ||
} | ||
|
||
/******************************************************************************* | ||
* Directory operations after here * | ||
******************************************************************************/ | ||
|
||
int fuse_lookup_initialize_in(struct bpf_fuse_args *fa, struct fuse_lookup_io *fli, | ||
struct inode *dir, struct dentry *entry, unsigned int flags) | ||
{ | ||
*fa = (struct bpf_fuse_args) { | ||
.nodeid = get_fuse_inode(dir)->nodeid, | ||
.opcode = FUSE_LOOKUP, | ||
.in_numargs = 1, | ||
.in_args[0] = (struct bpf_fuse_arg) { | ||
.size = entry->d_name.len + 1, | ||
.max_size = NAME_MAX + 1, | ||
.flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, | ||
.value = (void *) entry->d_name.name, | ||
}, | ||
}; | ||
|
||
return 0; | ||
} | ||
|
||
int fuse_lookup_initialize_out(struct bpf_fuse_args *fa, struct fuse_lookup_io *fli, | ||
struct inode *dir, struct dentry *entry, unsigned int flags) | ||
{ | ||
fa->out_numargs = 2; | ||
fa->flags = FUSE_BPF_OUT_ARGVAR | FUSE_BPF_IS_LOOKUP; | ||
fa->out_args[0] = (struct bpf_fuse_arg) { | ||
.size = sizeof(fli->feo), | ||
.value = &fli->feo, | ||
}; | ||
fa->out_args[1] = (struct bpf_fuse_arg) { | ||
.size = sizeof(fli->feb.out), | ||
.value = &fli->feb.out, | ||
}; | ||
|
||
return 0; | ||
} | ||
|
||
int fuse_lookup_backing(struct bpf_fuse_args *fa, struct dentry **out, struct inode *dir, | ||
struct dentry *entry, unsigned int flags) | ||
{ | ||
struct fuse_dentry *fuse_entry = get_fuse_dentry(entry); | ||
struct fuse_dentry *dir_fuse_entry = get_fuse_dentry(entry->d_parent); | ||
struct dentry *dir_backing_entry = dir_fuse_entry->backing_path.dentry; | ||
struct inode *dir_backing_inode = dir_backing_entry->d_inode; | ||
struct dentry *backing_entry; | ||
const char *name; | ||
int len; | ||
|
||
/* TODO this will not handle lookups over mount points */ | ||
inode_lock_nested(dir_backing_inode, I_MUTEX_PARENT); | ||
if (fa->in_args[0].flags & BPF_FUSE_MODIFIED) { | ||
name = (char *)fa->in_args[0].value; | ||
len = strnlen(name, fa->in_args[0].size); | ||
} else { | ||
name = entry->d_name.name; | ||
len = entry->d_name.len; | ||
} | ||
backing_entry = lookup_one_len(name, dir_backing_entry, len); | ||
inode_unlock(dir_backing_inode); | ||
|
||
if (IS_ERR(backing_entry)) | ||
return PTR_ERR(backing_entry); | ||
|
||
fuse_entry->backing_path = (struct path) { | ||
.dentry = backing_entry, | ||
.mnt = dir_fuse_entry->backing_path.mnt, | ||
}; | ||
|
||
mntget(fuse_entry->backing_path.mnt); | ||
return 0; | ||
} | ||
|
||
int fuse_handle_backing(struct fuse_entry_bpf *feb, struct path *backing_path) | ||
{ | ||
switch (feb->out.backing_action) { | ||
case FUSE_ACTION_KEEP: | ||
/* backing inode/path are added in fuse_lookup_backing */ | ||
break; | ||
|
||
case FUSE_ACTION_REMOVE: | ||
path_put_init(backing_path); | ||
break; | ||
|
||
case FUSE_ACTION_REPLACE: { | ||
if (!feb->backing_path.dentry) | ||
return -EINVAL; | ||
|
||
path_put(backing_path); | ||
*backing_path = feb->backing_path; | ||
feb->backing_path.dentry = NULL; | ||
feb->backing_path.mnt = NULL; | ||
|
||
break; | ||
} | ||
|
||
default: | ||
return -EINVAL; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int fuse_handle_bpf_prog(struct fuse_entry_bpf *feb, struct inode *parent, | ||
struct bpf_prog **bpf) | ||
{ | ||
struct fuse_inode *pi; | ||
|
||
// Parent isn't presented, but we want to keep | ||
// Don't touch bpf program at all in this case | ||
if (feb->out.bpf_action == FUSE_ACTION_KEEP && !parent) | ||
goto out; | ||
|
||
if (*bpf) { | ||
bpf_prog_put(*bpf); | ||
*bpf = NULL; | ||
} | ||
|
||
switch (feb->out.bpf_action) { | ||
case FUSE_ACTION_KEEP: | ||
pi = get_fuse_inode(parent); | ||
*bpf = pi->bpf; | ||
if (*bpf) | ||
bpf_prog_inc(*bpf); | ||
break; | ||
|
||
case FUSE_ACTION_REMOVE: | ||
break; | ||
|
||
case FUSE_ACTION_REPLACE: { | ||
struct bpf_prog *bpf_prog = feb->bpf; | ||
|
||
if (IS_ERR(bpf_prog)) | ||
return PTR_ERR(bpf_prog); | ||
|
||
*bpf = bpf_prog; | ||
break; | ||
} | ||
|
||
default: | ||
return -EINVAL; | ||
} | ||
|
||
out: | ||
return 0; | ||
} | ||
|
||
int fuse_lookup_finalize(struct bpf_fuse_args *fa, struct dentry **out, | ||
struct inode *dir, struct dentry *entry, unsigned int flags) | ||
{ | ||
struct fuse_dentry *fd; | ||
struct dentry *backing_dentry; | ||
struct inode *inode, *backing_inode; | ||
struct inode *d_inode = entry->d_inode; | ||
struct fuse_entry_out *feo = fa->out_args[0].value; | ||
struct fuse_entry_bpf_out *febo = fa->out_args[1].value; | ||
struct fuse_entry_bpf *feb = container_of(febo, struct fuse_entry_bpf, out); | ||
int error = -1; | ||
u64 target_nodeid = 0; | ||
|
||
fd = get_fuse_dentry(entry); | ||
if (!fd) | ||
return -EIO; | ||
error = fuse_handle_backing(feb, &fd->backing_path); | ||
if (error) | ||
return error; | ||
backing_dentry = fd->backing_path.dentry; | ||
if (!backing_dentry) | ||
return -ENOENT; | ||
backing_inode = backing_dentry->d_inode; | ||
if (!backing_inode) { | ||
*out = 0; | ||
return 0; | ||
} | ||
|
||
if (d_inode) | ||
target_nodeid = get_fuse_inode(d_inode)->nodeid; | ||
|
||
inode = fuse_iget_backing(dir->i_sb, target_nodeid, backing_inode); | ||
|
||
if (IS_ERR(inode)) | ||
return PTR_ERR(inode); | ||
|
||
error = fuse_handle_bpf_prog(feb, dir, &get_fuse_inode(inode)->bpf); | ||
if (error) | ||
return error; | ||
|
||
get_fuse_inode(inode)->nodeid = feo->nodeid; | ||
|
||
*out = d_splice_alias(inode, entry); | ||
return 0; | ||
} | ||
|
||
int fuse_revalidate_backing(struct dentry *entry, unsigned int flags) | ||
{ | ||
struct fuse_dentry *fuse_dentry = get_fuse_dentry(entry); | ||
struct dentry *backing_entry = fuse_dentry->backing_path.dentry; | ||
|
||
spin_lock(&backing_entry->d_lock); | ||
if (d_unhashed(backing_entry)) { | ||
spin_unlock(&backing_entry->d_lock); | ||
return 0; | ||
} | ||
spin_unlock(&backing_entry->d_lock); | ||
|
||
if (unlikely(backing_entry->d_flags & DCACHE_OP_REVALIDATE)) | ||
return backing_entry->d_op->d_revalidate(backing_entry, flags); | ||
return 1; | ||
} | ||
|
||
int fuse_access_initialize_in(struct bpf_fuse_args *fa, struct fuse_access_in *fai, | ||
struct inode *inode, int mask) | ||
{ | ||
*fai = (struct fuse_access_in) { | ||
.mask = mask, | ||
}; | ||
|
||
*fa = (struct bpf_fuse_args) { | ||
.opcode = FUSE_ACCESS, | ||
.nodeid = get_node_id(inode), | ||
.in_numargs = 1, | ||
.in_args[0].size = sizeof(*fai), | ||
.in_args[0].value = fai, | ||
}; | ||
|
||
return 0; | ||
} | ||
|
||
int fuse_access_initialize_out(struct bpf_fuse_args *fa, struct fuse_access_in *fai, | ||
struct inode *inode, int mask) | ||
{ | ||
return 0; | ||
} | ||
|
||
int fuse_access_backing(struct bpf_fuse_args *fa, int *out, struct inode *inode, int mask) | ||
{ | ||
struct fuse_inode *fi = get_fuse_inode(inode); | ||
const struct fuse_access_in *fai = fa->in_args[0].value; | ||
|
||
*out = inode_permission(&init_user_ns, fi->backing_inode, fai->mask); | ||
return 0; | ||
} | ||
|
||
int fuse_access_finalize(struct bpf_fuse_args *fa, int *out, struct inode *inode, int mask) | ||
{ | ||
return 0; | ||
} | ||
|
Oops, something went wrong.