Add support for the fuse low-level API in erofsfuse, proven correct in
22 test cases. Lowlevel API offers improved performance compared to the
high-level API, while maintaining compatibility with fuse version
2(>=2.6) and 3 (>=3.0).
Dataset: linux 5.15
Compression algorithm: -z4hc,12
Additional options: -T0 -C16384
Test options: --warmup 3 -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1"
Evaluation result (highlevel->lowlevel avg time):
- Sequence metadata: 777.3 ms->180.9 ms
- Sequence data: 3.282 s->818.1 ms
- Random metadata: 1.571 s->928.3 ms
- Random data: 2.461 s->597.8 ms
Signed-off-by: Li Yiyan <[email protected]>
---
Changes since v6:
- remove redundant code
- optimize naming
- add eval data
Changes since v5:
- name retval to `ret` from `err`
Changes since v4:
- support fuse option
- default run in daemon
- remove redundant log
Changes since v3:
- remove ll identifier
- optimize naming
- remove redundant log
- add fixme label for confusing xattr_buf
Changes since v2:
- merge lowlevel.c into main.c
- fix typo in autoconf
- optimize naming
- remove redundant code
- avoid global sbi dependencies
Changes since v1:
- remove highlevel fallback path
- remove redundant code
- add static for erofsfuse_ll_func
---
configure.ac | 23 +-
fuse/Makefile.am | 4 +-
fuse/main.c | 621 ++++++++++++++++++++++++++++++++++++++---------
3 files changed, 523 insertions(+), 125 deletions(-)
diff --git a/configure.ac b/configure.ac
index a8cecd0..6d08d96 100644
--- a/configure.ac
+++ b/configure.ac
@@ -336,15 +336,26 @@ AS_IF([test "x$with_selinux" != "xno"], [
# Configure fuse
AS_IF([test "x$enable_fuse" != "xno"], [
- PKG_CHECK_MODULES([libfuse], [fuse >= 2.6])
# Paranoia: don't trust the result reported by pkgconfig before trying out
saved_LIBS="$LIBS"
saved_CPPFLAGS=${CPPFLAGS}
- CPPFLAGS="${libfuse_CFLAGS} ${CPPFLAGS}"
- LIBS="${libfuse_LIBS} $LIBS"
- AC_CHECK_LIB(fuse, fuse_main, [
- have_fuse="yes" ], [
- AC_MSG_ERROR([libfuse (>= 2.6) doesn't work properly])])
+ PKG_CHECK_MODULES([libfuse3], [fuse3 >= 3.0], [
+ AC_DEFINE([FUSE_USE_VERSION], [30], [used FUSE API version])
+ CPPFLAGS="${libfuse3_CFLAGS} ${CPPFLAGS}"
+ LIBS="${libfuse3_LIBS} $LIBS"
+ AC_CHECK_LIB(fuse3, fuse_session_new, [], [
+ AC_MSG_ERROR([libfuse3 (>= 3.0) doesn't work properly for lowlevel api])])
+ have_fuse="yes"
+ ], [
+ PKG_CHECK_MODULES([libfuse2], [fuse >= 2.6], [
+ AC_DEFINE([FUSE_USE_VERSION], [26], [used FUSE API version])
+ CPPFLAGS="${libfuse2_CFLAGS} ${CPPFLAGS}"
+ LIBS="${libfuse2_LIBS} $LIBS"
+ AC_CHECK_LIB(fuse, fuse_lowlevel_new, [], [
+ AC_MSG_ERROR([libfuse (>= 2.6) doesn't work properly for lowlevel
api])])
+ have_fuse="yes"
+ ], [have_fuse="no"])
+ ])
LIBS="${saved_LIBS}"
CPPFLAGS="${saved_CPPFLAGS}"], [have_fuse="no"])
diff --git a/fuse/Makefile.am b/fuse/Makefile.am
index 50be783..c63efcd 100644
--- a/fuse/Makefile.am
+++ b/fuse/Makefile.am
@@ -5,6 +5,6 @@ noinst_HEADERS = $(top_srcdir)/fuse/macosx.h
bin_PROGRAMS = erofsfuse
erofsfuse_SOURCES = main.c
erofsfuse_CFLAGS = -Wall -I$(top_srcdir)/include
-erofsfuse_CFLAGS += -DFUSE_USE_VERSION=26 ${libfuse_CFLAGS}
${libselinux_CFLAGS}
-erofsfuse_LDADD = $(top_builddir)/lib/liberofs.la ${libfuse_LIBS}
${liblz4_LIBS} \
+erofsfuse_CFLAGS += ${libfuse2_CFLAGS} ${libfuse3_CFLAGS} ${libselinux_CFLAGS}
+erofsfuse_LDADD = $(top_builddir)/lib/liberofs.la ${libfuse2_LIBS}
${libfuse3_LIBS} ${liblz4_LIBS} \
${libselinux_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS}
diff --git a/fuse/main.c b/fuse/main.c
index 821d98c..ecb4e81 100644
--- a/fuse/main.c
+++ b/fuse/main.c
@@ -1,13 +1,12 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Created by Li Guifu <[email protected]>
+ * Lowlevel added by Li Yiyan <[email protected]>
*/
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <libgen.h>
-#include <fuse.h>
-#include <fuse_opt.h>
#include "macosx.h"
#include "erofs/config.h"
#include "erofs/print.h"
@@ -15,177 +14,507 @@
#include "erofs/dir.h"
#include "erofs/inode.h"
-struct erofsfuse_dir_context {
+#include <float.h>
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+
+/* used in list/getxattr, given buf size is 0 and
+ * expected return val is xattr size
+ * FIXME: remove this workaround by new lib interface
+ */
+#define EROFSFUSE_XATTR_BUF_SIZE 4096
+#define EROFSFUSE_TIMEOUT DBL_MAX
+
+struct erofsfuse_readdir_context {
struct erofs_dir_context ctx;
- fuse_fill_dir_t filler;
- struct fuse_file_info *fi;
+
+ fuse_req_t req;
void *buf;
+ int is_plus;
+ size_t offset;
+ size_t buf_size;
+ size_t start_off;
+ struct fuse_file_info *fi;
+};
+
+struct erofsfuse_lookupdir_context {
+ struct erofs_dir_context ctx;
+
+ const char *target_name;
+ struct fuse_entry_param *ent;
};
-static int erofsfuse_fill_dentries(struct erofs_dir_context *ctx)
+static int erofsfuse_add_dentry(struct erofs_dir_context *ctx)
{
- struct erofsfuse_dir_context *fusectx = (void *)ctx;
- struct stat st = {0};
+ size_t size = 0;
char dname[EROFS_NAME_LEN + 1];
+ struct erofsfuse_readdir_context *readdir_ctx = (void *)ctx;
+
+ if (readdir_ctx->offset < readdir_ctx->start_off) {
+ readdir_ctx->offset +=
+ ctx->de_namelen + sizeof(struct erofs_dirent);
+ return 0;
+ }
strncpy(dname, ctx->dname, ctx->de_namelen);
dname[ctx->de_namelen] = '\0';
- st.st_mode = erofs_ftype_to_dtype(ctx->de_ftype) << 12;
- fusectx->filler(fusectx->buf, dname, &st, 0);
+ readdir_ctx->offset += ctx->de_namelen + sizeof(struct erofs_dirent);
+
+ if (!readdir_ctx->is_plus) { /* fuse 3 still use non-plus readdir */
+ struct stat st = { 0 };
+
+ st.st_mode = erofs_ftype_to_dtype(ctx->de_ftype);
+ st.st_ino = ctx->de_nid;
+ size = fuse_add_direntry(readdir_ctx->req, readdir_ctx->buf,
+ readdir_ctx->buf_size, dname, &st,
+ readdir_ctx->offset);
+ } else {
+#if FUSE_MAJOR_VERSION >= 3
+ struct fuse_entry_param param;
+
+ param.ino = ctx->de_nid;
+ param.generation = 0;
+ param.attr.st_ino = ctx->de_nid;
+ param.attr.st_mode = erofs_ftype_to_dtype(ctx->de_ftype);
+ param.attr_timeout = param.entry_timeout = EROFSFUSE_TIMEOUT;
+ size = fuse_add_direntry_plus(readdir_ctx->req,
+ readdir_ctx->buf,
+ readdir_ctx->buf_size, dname,
+ ¶m, readdir_ctx->offset);
+#else
+ erofs_err("fuse 2 readdirplus is not supported");