From fdf279db8fe0921dfd273d64fabcece46d8419a2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenberg Date: Mon, 14 Mar 2022 18:21:02 -0700 Subject: [PATCH] ANDROID: fuse-bpf: support FUSE_LSEEK Adds support for lseek via fuse-bpf Bug: 224855060 Test: bpf_test_lseek Signed-off-by: Daniel Rosenberg Change-Id: Ic282940d53b9bb44a291cb3a5dfe09847b4e5c9a --- fs/fuse/backing.c | 59 +++++++++++++++++++ fs/fuse/file.c | 11 ++++ fs/fuse/fuse_i.h | 10 ++++ .../selftests/filesystems/fuse/fd_bpf.c | 7 +++ .../selftests/filesystems/fuse/fuse_test.c | 43 ++++++++++++++ .../selftests/filesystems/fuse/test_bpf.c | 7 +++ 6 files changed, 137 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index efd76dc08f3d..70e2fe352371 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -403,6 +403,65 @@ void *fuse_flush_finalize(struct fuse_args *fa, struct file *file, fl_owner_t id return NULL; } +int fuse_lseek_initialize(struct fuse_args *fa, struct fuse_lseek_io *flio, + struct file *file, loff_t offset, int whence) +{ + struct fuse_file *fuse_file = file->private_data; + + flio->fli = (struct fuse_lseek_in) { + .fh = fuse_file->fh, + .offset = offset, + .whence = whence, + }; + + *fa = (struct fuse_args) { + .nodeid = get_node_id(file->f_inode), + .opcode = FUSE_LSEEK, + .in_numargs = 1, + .in_args[0].size = sizeof(flio->fli), + .in_args[0].value = &flio->fli, + .out_numargs = 1, + .out_args[0].size = sizeof(flio->flo), + .out_args[0].value = &flio->flo, + }; + + return 0; +} + +int fuse_lseek_backing(struct fuse_args *fa, struct file *file, loff_t offset, int whence) +{ + const struct fuse_lseek_in *fli = fa->in_args[0].value; + struct fuse_lseek_out *flo = fa->out_args[0].value; + struct fuse_file *fuse_file = file->private_data; + struct file *backing_file = fuse_file->backing_file; + loff_t ret; + + /* TODO: Handle changing of the file handle */ + if (offset == 0) { + if (whence == SEEK_CUR) + return file->f_pos; + + if (whence == SEEK_SET) + return vfs_setpos(file, 0, 0); + } + + inode_lock(file->f_inode); + backing_file->f_pos = file->f_pos; + ret = vfs_llseek(backing_file, fli->offset, fli->whence); + flo->offset = ret; + inode_unlock(file->f_inode); + return ret; +} + +void *fuse_lseek_finalize(struct fuse_args *fa, struct file *file, loff_t offset, int whence) +{ + struct fuse_lseek_out *flo = fa->out_args[0].value; + + if (!fa->error_in) + file->f_pos = flo->offset; + return ERR_PTR(flo->offset); +} + int fuse_copy_file_range_initialize(struct fuse_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) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 8ba3e914f3d4..b8a992d0a3fe 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2738,6 +2738,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: diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index fd22f1496f9b..84d2f74f9d9f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1433,6 +1433,16 @@ 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); +struct fuse_lseek_io { + struct fuse_lseek_in fli; + struct fuse_lseek_out flo; +}; + +int fuse_lseek_initialize(struct fuse_args *fa, struct fuse_lseek_io *fli, + struct file *file, loff_t offset, int whence); +int fuse_lseek_backing(struct fuse_args *fa, struct file *file, loff_t offset, int whence); +void *fuse_lseek_finalize(struct fuse_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; diff --git a/tools/testing/selftests/filesystems/fuse/fd_bpf.c b/tools/testing/selftests/filesystems/fuse/fd_bpf.c index f54d04bbb20c..f9102058515f 100644 --- a/tools/testing/selftests/filesystems/fuse/fd_bpf.c +++ b/tools/testing/selftests/filesystems/fuse/fd_bpf.c @@ -289,6 +289,13 @@ int trace_daemon(struct fuse_args *fa) 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", diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c index 9117b21cc461..857484b90ae7 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c @@ -1450,6 +1450,48 @@ out: return result; } +static int bpf_test_lseek(const char *mount_dir) +{ + const char *file = "real"; + const char *test_data = "data"; + int result = TEST_FAILURE; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", + &bpf_fd, NULL, NULL), 0); + TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); + + TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC), + fd != -1); + TESTEQUAL(lseek(fd, 3, SEEK_SET), 3); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 5, SEEK_END), 9); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_CUR), 10); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_DATA), 1); + TESTEQUAL(bpf_test_trace("lseek"), 0); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(bpf_fd); + close(src_fd); + return result; +} + + static int parse_options(int argc, char *const *argv) { signed char c; @@ -1553,6 +1595,7 @@ int main(int argc, char *argv[]) MAKE_TEST(readdir_perms_test), MAKE_TEST(inotify_test), MAKE_TEST(bpf_test_statfs), + MAKE_TEST(bpf_test_lseek), }; #undef MAKE_TEST diff --git a/tools/testing/selftests/filesystems/fuse/test_bpf.c b/tools/testing/selftests/filesystems/fuse/test_bpf.c index 7d8ef494c589..8afef42b4657 100644 --- a/tools/testing/selftests/filesystems/fuse/test_bpf.c +++ b/tools/testing/selftests/filesystems/fuse/test_bpf.c @@ -346,6 +346,13 @@ int trace_test(struct fuse_args *fa) 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;