This allows fuse-bpf to call out to userspace to handle pre and post filters. Any of the inputs may be changed by the prefilter, so we must handle up to 3 outputs. For the postfilter, our inputs include the output arguments, so we must handle up to 5 inputs. As long as you don't request both pre-filter and post-filter in userspace, we will end up doing fewer round trips to userspace. Signed-off-by: Daniel Rosenberg <drosen@xxxxxxxxxx> --- fs/fuse/backing.c | 70 ++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 2 ++ fs/fuse/fuse_i.h | 42 ++++++++++++++++++++++-- include/linux/bpf_fuse.h | 1 + 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 485b6f1e8503..7a3b1fdb2c56 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2681,3 +2681,73 @@ void __exit fuse_bpf_cleanup(void) { kmem_cache_destroy(fuse_bpf_aio_request_cachep); } + +static ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa, + unsigned short in_numargs, unsigned short out_numargs, + struct bpf_fuse_arg *out_arg_array, bool add_out_to_in) +{ + int i; + uint32_t max_size; + ssize_t res; + + struct fuse_args args = { + .nodeid = fa->nodeid, + .opcode = fa->opcode, + .error_in = fa->error_in, + .in_numargs = in_numargs, + .out_numargs = out_numargs, + .force = !!(fa->flags & FUSE_BPF_FORCE), + .out_argvar = !!(fa->flags & FUSE_BPF_OUT_ARGVAR), + .is_lookup = !!(fa->flags & FUSE_BPF_IS_LOOKUP), + }; + + /* Set in args */ + for (i = 0; i < fa->in_numargs; ++i) + args.in_args[i] = (struct fuse_in_arg) { + .size = fa->in_args[i].size, + .value = fa->in_args[i].value, + }; + if (add_out_to_in) { + for (i = 0; i < fa->out_numargs; ++i) + args.in_args[fa->in_numargs + i] = (struct fuse_in_arg) { + .size = fa->out_args[i].size, + .value = fa->out_args[i].value, + }; + } + + /* All out args must be writeable */ + for (i = 0; i < out_numargs; ++i) { + max_size = out_arg_array[i].max_size ?: out_arg_array[i].size; + if (!bpf_fuse_get_writeable(&out_arg_array[i], max_size, true)) + return -ENOMEM; + } + + /* Set out args */ + for (i = 0; i < out_numargs; ++i) + args.out_args[i] = (struct fuse_arg) { + .size = out_arg_array[i].size, + .value = out_arg_array[i].value, + }; + + res = fuse_simple_request(fm, &args); + + /* update used areas of buffers */ + for (i = 0; i < out_numargs; ++i) + if (out_arg_array[i].flags & BPF_FUSE_VARIABLE_SIZE) + out_arg_array[i].size = args.out_args[i].size; + fa->ret = args.ret; + + return res; +} + +ssize_t fuse_prefilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa) +{ + return fuse_bpf_simple_request(fm, fa, fa->in_numargs, fa->in_numargs, + fa->in_args, false); +} + +ssize_t fuse_postfilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa) +{ + return fuse_bpf_simple_request(fm, fa, fa->in_numargs + fa->out_numargs, fa->out_numargs, + fa->out_args, true); +} diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 626dbbf92874..765bc95bd560 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -520,6 +520,8 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args) BUG_ON(args->out_numargs == 0); ret = args->out_args[args->out_numargs - 1].size; } + if (args->is_filter) + args->ret = req->out.h.error; fuse_put_request(req); return ret; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 07b50be2c6e4..a619c6eac6e5 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -305,6 +305,17 @@ struct fuse_page_desc { unsigned int offset; }; +/* To deal with bpf pre and post filters in userspace calls, we must support + * passing the inputs and outputs as inputs, and we must have enough space in + * outputs to handle all of the inputs. + */ +#define FUSE_EXTENDED_MAX_ARGS_IN (FUSE_MAX_ARGS_IN + FUSE_MAX_ARGS_OUT) +#if FUSE_MAX_ARGS_IN > FUSE_MAX_ARGS_OUT +#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_IN +#else +#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_OUT +#endif + struct fuse_args { uint64_t nodeid; uint32_t opcode; @@ -321,9 +332,11 @@ struct fuse_args { bool page_zeroing:1; bool page_replace:1; bool may_block:1; + bool is_filter:1; bool is_lookup:1; - struct fuse_in_arg in_args[3]; - struct fuse_arg out_args[2]; + uint32_t ret; + struct fuse_in_arg in_args[FUSE_EXTENDED_MAX_ARGS_IN]; + struct fuse_arg out_args[FUSE_EXTENDED_MAX_ARGS_OUT]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); }; @@ -1936,6 +1949,9 @@ static inline void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatf int __init fuse_bpf_init(void); void __exit fuse_bpf_cleanup(void); +ssize_t fuse_prefilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *args); +ssize_t fuse_postfilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *args); + static inline void fuse_bpf_set_in_ends(struct bpf_fuse_args *fa) { int i; @@ -1994,9 +2010,11 @@ static inline void fuse_bpf_free_alloced(struct bpf_fuse_args *fa) backing, finalize, args...) \ ({ \ struct fuse_inode *fuse_inode = get_fuse_inode(inode); \ + struct fuse_mount *fm = get_fuse_mount(inode); \ struct bpf_fuse_args fa = { 0 }; \ bool initialized = false; \ bool handled = false; \ + bool locked; \ ssize_t res; \ int bpf_next; \ io feo = { 0 }; \ @@ -2021,6 +2039,16 @@ static inline void fuse_bpf_free_alloced(struct bpf_fuse_args *fa) break; \ } \ \ + if (bpf_next == BPF_FUSE_USER_PREFILTER) { \ + locked = fuse_lock_inode(inode); \ + res = fuse_prefilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error = res; \ + break; \ + } \ + bpf_next = fa.ret; \ + } \ fuse_bpf_set_in_immutable(&fa); \ \ error = initialize_out(&fa, &feo, args); \ @@ -2051,6 +2079,16 @@ static inline void fuse_bpf_free_alloced(struct bpf_fuse_args *fa) break; \ } \ \ + if (!(bpf_next == BPF_FUSE_USER_POSTFILTER)) \ + break; \ + \ + locked = fuse_lock_inode(inode); \ + res = fuse_postfilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error = res; \ + break; \ + } \ } while (false); \ \ if (initialized && handled) { \ diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index ef5c8fdaffee..2802ca71ddd1 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -40,6 +40,7 @@ struct bpf_fuse_args { uint32_t in_numargs; uint32_t out_numargs; uint32_t flags; + uint32_t ret; struct bpf_fuse_arg in_args[FUSE_MAX_ARGS_IN]; struct bpf_fuse_arg out_args[FUSE_MAX_ARGS_OUT]; }; -- 2.37.3.998.g577e59143f-goog