* [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
@ 2025-11-06 22:43 ` Darrick J. Wong
2025-11-06 22:43 ` [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API Darrick J. Wong
` (21 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Separate the detection of libfuse and fuse2fs so that we can add another
fuse server (fuse4fs) without tangling it up in --disable-fuse2fs.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
configure | 301 +++++++++++++-----------------------------------------
configure.ac | 79 ++++++++------
misc/Makefile.in | 6 +
3 files changed, 116 insertions(+), 270 deletions(-)
diff --git a/configure b/configure
index 86c9bc77321eee..22031343f078ab 100755
--- a/configure
+++ b/configure
@@ -701,7 +701,7 @@ gcc_ranlib
gcc_ar
UNI_DIFF_OPTS
SEM_INIT_LIB
-FUSE_CMT
+FUSE2FS_CMT
FUSE_LIB
fuse3_LIBS
fuse3_CFLAGS
@@ -14052,214 +14052,8 @@ then :
fi
-FUSE_CMT=
+
FUSE_LIB=
-# Check whether --enable-fuse2fs was given.
-if test ${enable_fuse2fs+y}
-then :
- enableval=$enable_fuse2fs;
- if test "$enableval" = "no"
- then
- FUSE_CMT="#"
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
-printf "%s\n" "Disabling fuse2fs" >&6; }
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#ifdef __linux__
- #include <linux/fs.h>
- #include <linux/falloc.h>
- #include <linux/xattr.h>
- #endif
-
-int
-main (void)
-{
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"
-then :
-
-else $as_nop
- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse2fs Linux headers.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-
-pkg_failed=no
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
-printf %s "checking for fuse3... " >&6; }
-
-if test -n "$fuse3_CFLAGS"; then
- pkg_cv_fuse3_CFLAGS="$fuse3_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
- if test -n "$PKG_CONFIG" && \
- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3\""; } >&5
- ($PKG_CONFIG --exists --print-errors "fuse3") 2>&5
- ac_status=$?
- printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
- test $ac_status = 0; }; then
- pkg_cv_fuse3_CFLAGS=`$PKG_CONFIG --cflags "fuse3" 2>/dev/null`
- test "x$?" != "x0" && pkg_failed=yes
-else
- pkg_failed=yes
-fi
- else
- pkg_failed=untried
-fi
-if test -n "$fuse3_LIBS"; then
- pkg_cv_fuse3_LIBS="$fuse3_LIBS"
- elif test -n "$PKG_CONFIG"; then
- if test -n "$PKG_CONFIG" && \
- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3\""; } >&5
- ($PKG_CONFIG --exists --print-errors "fuse3") 2>&5
- ac_status=$?
- printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
- test $ac_status = 0; }; then
- pkg_cv_fuse3_LIBS=`$PKG_CONFIG --libs "fuse3" 2>/dev/null`
- test "x$?" != "x0" && pkg_failed=yes
-else
- pkg_failed=yes
-fi
- else
- pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf "%s\n" "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
- _pkg_short_errors_supported=yes
-else
- _pkg_short_errors_supported=no
-fi
- if test $_pkg_short_errors_supported = yes; then
- fuse3_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "fuse3" 2>&1`
- else
- fuse3_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "fuse3" 2>&1`
- fi
- # Put the nasty error message in config.log where it belongs
- echo "$fuse3_PKG_ERRORS" >&5
-
-
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
-printf %s "checking for fuse_main in -losxfuse... " >&6; }
-if test ${ac_cv_lib_osxfuse_fuse_main+y}
-then :
- printf %s "(cached) " >&6
-else $as_nop
- ac_check_lib_save_LIBS=$LIBS
-LIBS="-losxfuse $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-/* Override any GCC internal prototype to avoid an error.
- Use char because int might match the return type of a GCC
- builtin and then its argument prototype would still apply. */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
- ac_cv_lib_osxfuse_fuse_main=yes
-else $as_nop
- ac_cv_lib_osxfuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
- conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_osxfuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
-then :
- FUSE_LIB=-losxfuse
-else $as_nop
- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse library.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-
-elif test $pkg_failed = untried; then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf "%s\n" "no" >&6; }
-
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
-printf %s "checking for fuse_main in -losxfuse... " >&6; }
-if test ${ac_cv_lib_osxfuse_fuse_main+y}
-then :
- printf %s "(cached) " >&6
-else $as_nop
- ac_check_lib_save_LIBS=$LIBS
-LIBS="-losxfuse $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-/* Override any GCC internal prototype to avoid an error.
- Use char because int might match the return type of a GCC
- builtin and then its argument prototype would still apply. */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
- ac_cv_lib_osxfuse_fuse_main=yes
-else $as_nop
- ac_cv_lib_osxfuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
- conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_osxfuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
-then :
- FUSE_LIB=-losxfuse
-else $as_nop
- { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse library.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-
-else
- fuse3_CFLAGS=$pkg_cv_fuse3_CFLAGS
- fuse3_LIBS=$pkg_cv_fuse3_LIBS
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-printf "%s\n" "yes" >&6; }
- FUSE_LIB=-lfuse3
-fi
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
-printf "%s\n" "Enabling fuse2fs" >&6; }
- fi
-
-else $as_nop
-
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
@@ -14320,7 +14114,7 @@ fi
echo "$fuse3_PKG_ERRORS" >&5
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
printf %s "checking for fuse_main in -losxfuse... " >&6; }
if test ${ac_cv_lib_osxfuse_fuse_main+y}
then :
@@ -14358,8 +14152,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
then :
FUSE_LIB=-losxfuse
-else $as_nop
- FUSE_CMT="#"
fi
@@ -14367,7 +14159,7 @@ elif test $pkg_failed = untried; then
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; }
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
printf %s "checking for fuse_main in -losxfuse... " >&6; }
if test ${ac_cv_lib_osxfuse_fuse_main+y}
then :
@@ -14405,8 +14197,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
then :
FUSE_LIB=-losxfuse
-else $as_nop
- FUSE_CMT="#"
fi
@@ -14417,15 +14207,6 @@ else
printf "%s\n" "yes" >&6; }
FUSE_LIB=-lfuse3
fi
- if test -z "$FUSE_CMT"
- then
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default." >&5
-printf "%s\n" "Enabling fuse2fs by default." >&6; }
- fi
-
-
-fi
-
if test -n "$FUSE_LIB"
@@ -14437,12 +14218,7 @@ then
do :
as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS 64
-#define FUSE_USE_VERSION 314
-#ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif
+#define FUSE_USE_VERSION 314
"
if eval test \"x\$"$as_ac_Header"\" = x"yes"
then :
@@ -14453,7 +14229,7 @@ _ACEOF
else $as_nop
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse3 fuse2fs headers.
+as_fn_error $? "Cannot build against fuse3 headers
See \`config.log' for more details" "$LINENO" 5; }
fi
@@ -14466,6 +14242,71 @@ printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
fi
+FUSE2FS_CMT=
+# Check whether --enable-fuse2fs was given.
+if test ${enable_fuse2fs+y}
+then :
+ enableval=$enable_fuse2fs;
+ if test "$enableval" = "no"
+ then
+ FUSE2FS_CMT="#"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
+printf "%s\n" "Disabling fuse2fs" >&6; }
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __linux__
+ #include <linux/fs.h>
+ #include <linux/falloc.h>
+ #include <linux/xattr.h>
+ #endif
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse2fs Linux headers
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ if test -z "$FUSE_USE_VERSION"
+ then
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse library.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
+printf "%s\n" "Enabling fuse2fs" >&6; }
+ fi
+
+else $as_nop
+
+ if test -n "$FUSE_USE_VERSION"
+ then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default" >&5
+printf "%s\n" "Enabling fuse2fs by default" >&6; }
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs by default" >&5
+printf "%s\n" "Disabling fuse2fs by default" >&6; }
+ fi
+
+
+fi
+
+
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5
printf %s "checking for PR_SET_IO_FLUSHER... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/configure.ac b/configure.ac
index bf1b57377cd848..b40ed1456d1515 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1366,18 +1366,48 @@ dnl Check to see if librt is required for clock_gettime() (glibc < 2.17)
dnl
AC_CHECK_LIB(rt, clock_gettime, [CLOCK_GETTIME_LIB=-lrt])
AC_SUBST(CLOCK_GETTIME_LIB)
+
dnl
dnl Check to see if the FUSE library is -lfuse3 or -losxfuse
dnl
-FUSE_CMT=
FUSE_LIB=
dnl osxfuse.dylib supersedes fuselib.dylib
+PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
+[
+ AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse])
+])
+AC_SUBST(FUSE_LIB)
+
+dnl
+dnl Set FUSE_USE_VERSION, which is how fuse servers build against a particular
+dnl libfuse ABI. Currently we link against the libfuse 3.14 ABI (hence 314)
+dnl
+if test -n "$FUSE_LIB"
+then
+ FUSE_USE_VERSION=314
+ CFLAGS="$fuse3_CFLAGS $CFLAGS"
+ FUSE_LIB="$fuse3_LIBS"
+ AC_CHECK_HEADERS([pthread.h fuse.h], [],
+ [AC_MSG_FAILURE([Cannot build against fuse3 headers])],
+[#define _FILE_OFFSET_BITS 64
+#define FUSE_USE_VERSION 314])
+fi
+if test -n "$FUSE_USE_VERSION"
+then
+ AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
+ [Define to the version of FUSE to use])
+fi
+
+dnl
+dnl Check if fuse2fs is actually built.
+dnl
+FUSE2FS_CMT=
AC_ARG_ENABLE([fuse2fs],
AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
[
if test "$enableval" = "no"
then
- FUSE_CMT="#"
+ FUSE2FS_CMT="#"
AC_MSG_RESULT([Disabling fuse2fs])
else
AC_PREPROC_IFELSE(
@@ -1386,49 +1416,24 @@ AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
#include <linux/falloc.h>
#include <linux/xattr.h>
#endif
- ]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers.])])
+ ]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers])])
- PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
- [
- AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
- [AC_MSG_FAILURE([Cannot find fuse library.])])
- ])
+ if test -z "$FUSE_USE_VERSION"
+ then
+ AC_MSG_FAILURE([Cannot find fuse library.])
+ fi
AC_MSG_RESULT([Enabling fuse2fs])
fi
], [
- PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
- [
- AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
- [FUSE_CMT="#"])
- ])
- if test -z "$FUSE_CMT"
+ if test -n "$FUSE_USE_VERSION"
then
- AC_MSG_RESULT([Enabling fuse2fs by default.])
+ AC_MSG_RESULT([Enabling fuse2fs by default])
+ else
+ AC_MSG_RESULT([Disabling fuse2fs by default])
fi
]
)
-AC_SUBST(FUSE_LIB)
-AC_SUBST(FUSE_CMT)
-if test -n "$FUSE_LIB"
-then
- FUSE_USE_VERSION=314
- CFLAGS="$fuse3_CFLAGS $CFLAGS"
- FUSE_LIB="$fuse3_LIBS"
- AC_CHECK_HEADERS([pthread.h fuse.h], [],
- [AC_MSG_FAILURE([Cannot find fuse3 fuse2fs headers.])],
-[#define _FILE_OFFSET_BITS 64
-#define FUSE_USE_VERSION 314
-#ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif])
-fi
-if test -n "$FUSE_USE_VERSION"
-then
- AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
- [Define to the version of FUSE to use])
-fi
+AC_SUBST(FUSE2FS_CMT)
dnl
dnl see if PR_SET_IO_FLUSHER exists
diff --git a/misc/Makefile.in b/misc/Makefile.in
index 0e3bed66dcb63d..b63a0424b19fec 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -34,7 +34,7 @@ MKDIR_P = @MKDIR_P@
@BLKID_CMT@FINDFS_LINK= findfs
@BLKID_CMT@FINDFS_MAN= findfs.8
-@FUSE_CMT@FUSE_PROG= fuse2fs
+@FUSE2FS_CMT@FUSE2FS_PROG= fuse2fs
SPROGS= mke2fs badblocks tune2fs dumpe2fs $(BLKID_PROG) logsave \
$(E2IMAGE_PROG) @FSCK_PROG@ e2undo
@@ -47,9 +47,9 @@ SMANPAGES= tune2fs.8 mklost+found.8 mke2fs.8 dumpe2fs.8 badblocks.8 \
e2mmpstatus.8
FMANPAGES= mke2fs.conf.5 ext4.5
-UPROGS= chattr lsattr $(FUSE_PROG) @UUID_CMT@ uuidgen
+UPROGS= chattr lsattr $(FUSE2FS_PROG) @UUID_CMT@ uuidgen
UMANPAGES= chattr.1 lsattr.1 @UUID_CMT@ uuidgen.1
-UMANPAGES+= @FUSE_CMT@ fuse2fs.1
+UMANPAGES+= @FUSE2FS_CMT@ fuse2fs.1
LPROGS= @E2INITRD_PROG@
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
2025-11-06 22:43 ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
@ 2025-11-06 22:43 ` Darrick J. Wong
2025-11-06 22:43 ` [PATCH 03/23] debian: create new package for fuse4fs Darrick J. Wong
` (20 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Copy fuse2fs.c to fuse4fs.c. This will become our testbed for trying
out lowlevel fuse server support in the next few patches.
Namespacing conversions performed via:
sed -e 's/fuse2fs/fuse4fs/g' -e 's/FUSE2FS/FUSE4FS/g' -e 's/F2OP_/F4OP_/g' -e 's/FUSE server/FUSE low-level server/g'
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
Makefile.in | 3
configure | 113 +
configure.ac | 65 +
fuse4fs/Makefile.in | 192 ++
fuse4fs/fuse4fs.1.in | 118 +
fuse4fs/fuse4fs.c | 5781 ++++++++++++++++++++++++++++++++++++++++++++++++++
lib/config.h.in | 3
7 files changed, 6272 insertions(+), 3 deletions(-)
create mode 100644 fuse4fs/Makefile.in
create mode 100644 fuse4fs/fuse4fs.1.in
create mode 100644 fuse4fs/fuse4fs.c
diff --git a/Makefile.in b/Makefile.in
index 277b500bbc9acc..d000f94bc88f0f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -18,12 +18,13 @@ MKDIR_P = @MKDIR_P@
@ALL_CMT@SUPPORT_LIB_SUBDIR= lib/support
@ALL_CMT@E2P_LIB_SUBDIR= lib/e2p
@ALL_CMT@EXT2FS_LIB_SUBDIR= lib/ext2fs
+@FUSE4FS_CMT@FUSE4FS_DIR=fuse4fs
LIB_SUBDIRS=lib/et lib/ss $(E2P_LIB_SUBDIR) $(UUID_LIB_SUBDIR) \
$(BLKID_LIB_SUBDIR) $(SUPPORT_LIB_SUBDIR) $(EXT2FS_LIB_SUBDIR)
PROG_SUBDIRS=e2fsck $(DEBUGFS_DIR) misc $(RESIZE_DIR) tests/progs \
- tests/fuzz po $(E2SCRUB_DIR)
+ tests/fuzz po $(E2SCRUB_DIR) $(FUSE4FS_DIR)
SUBDIRS=util $(LIB_SUBDIRS) $(PROG_SUBDIRS) tests
diff --git a/configure b/configure
index 22031343f078ab..7f5fb7c1a62084 100755
--- a/configure
+++ b/configure
@@ -701,6 +701,7 @@ gcc_ranlib
gcc_ar
UNI_DIFF_OPTS
SEM_INIT_LIB
+FUSE4FS_CMT
FUSE2FS_CMT
FUSE_LIB
fuse3_LIBS
@@ -933,6 +934,7 @@ with_libintl_prefix
enable_largefile
with_libarchive
enable_fuse2fs
+enable_fuse4fs
enable_lto
enable_ubsan
enable_addrsan
@@ -1628,6 +1630,7 @@ Optional Features:
--disable-rpath do not hardcode runtime library paths
--disable-largefile omit support for large files
--disable-fuse2fs do not build fuse2fs
+ --disable-fuse4fs do not build fuse4fs
--enable-lto enable link time optimization
--enable-ubsan enable undefined behavior sanitizer
--enable-addrsan enable address sanitizer
@@ -14242,6 +14245,49 @@ printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
fi
+have_fuse_lowlevel=
+if test -n "$FUSE_USE_VERSION"
+then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for lowlevel interface in libfuse" >&5
+printf %s "checking for lowlevel interface in libfuse... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #define _GNU_SOURCE
+ #define _FILE_OFFSET_BITS 64
+ #define FUSE_USE_VERSION 314
+ #include <fuse_lowlevel.h>
+
+int
+main (void)
+{
+
+ struct fuse_lowlevel_ops fs_ops = { };
+
+ ;
+ return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ have_fuse_lowlevel=yes
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+if test -n "$have_fuse_lowlevel"
+then
+
+printf "%s\n" "#define HAVE_FUSE_LOWLEVEL 1" >>confdefs.h
+
+fi
+
FUSE2FS_CMT=
# Check whether --enable-fuse2fs was given.
if test ${enable_fuse2fs+y}
@@ -14307,6 +14353,71 @@ fi
+FUSE4FS_CMT=
+# Check whether --enable-fuse4fs was given.
+if test ${enable_fuse4fs+y}
+then :
+ enableval=$enable_fuse4fs;
+ if test "$enableval" = "no"
+ then
+ FUSE4FS_CMT="#"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse4fs" >&5
+printf "%s\n" "Disabling fuse4fs" >&6; }
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __linux__
+ #include <linux/fs.h>
+ #include <linux/falloc.h>
+ #include <linux/xattr.h>
+ #endif
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse4fs Linux headers
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ if test -z "$have_fuse_lowlevel"
+ then
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse lowlevel library.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse4fs" >&5
+printf "%s\n" "Enabling fuse4fs" >&6; }
+ fi
+
+else $as_nop
+
+ if test -n "$have_fuse_lowlevel"
+ then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse4fs by default" >&5
+printf "%s\n" "Enabling fuse4fs by default" >&6; }
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse4fs by default" >&5
+printf "%s\n" "Disabling fuse4fs by default" >&6; }
+ fi
+
+
+fi
+
+
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5
printf %s "checking for PR_SET_IO_FLUSHER... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -15984,7 +16095,7 @@ for i in MCONFIG Makefile \
misc/Makefile ext2ed/Makefile e2fsck/Makefile \
debugfs/Makefile tests/Makefile tests/progs/Makefile \
tests/fuzz/Makefile resize/Makefile doc/Makefile \
- po/Makefile.in scrub/Makefile; do
+ po/Makefile.in scrub/Makefile fuse4fs/Makefile; do
if test -d `dirname ${srcdir}/$i` ; then
outlist="$outlist $i"
fi
diff --git a/configure.ac b/configure.ac
index b40ed1456d1515..2eb11873ea0e50 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1398,6 +1398,32 @@ then
[Define to the version of FUSE to use])
fi
+dnl
+dnl Check if the FUSE lowlevel library is supported
+dnl
+have_fuse_lowlevel=
+if test -n "$FUSE_USE_VERSION"
+then
+ AC_MSG_CHECKING(for lowlevel interface in libfuse)
+ AC_LINK_IFELSE(
+ [ AC_LANG_PROGRAM([[
+ #define _GNU_SOURCE
+ #define _FILE_OFFSET_BITS 64
+ #define FUSE_USE_VERSION 314
+ #include <fuse_lowlevel.h>
+ ]], [[
+ struct fuse_lowlevel_ops fs_ops = { };
+ ]])
+ ], have_fuse_lowlevel=yes
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+fi
+if test -n "$have_fuse_lowlevel"
+then
+ AC_DEFINE(HAVE_FUSE_LOWLEVEL, 1,
+ [Define to 1 if fuse supports lowlevel API])
+fi
+
dnl
dnl Check if fuse2fs is actually built.
dnl
@@ -1435,6 +1461,43 @@ AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
)
AC_SUBST(FUSE2FS_CMT)
+dnl
+dnl Check if fuse4fs is actually built.
+dnl
+FUSE4FS_CMT=
+AC_ARG_ENABLE([fuse4fs],
+AS_HELP_STRING([--disable-fuse4fs],[do not build fuse4fs]),
+[
+ if test "$enableval" = "no"
+ then
+ FUSE4FS_CMT="#"
+ AC_MSG_RESULT([Disabling fuse4fs])
+ else
+ AC_PREPROC_IFELSE(
+ [AC_LANG_PROGRAM([[#ifdef __linux__
+ #include <linux/fs.h>
+ #include <linux/falloc.h>
+ #include <linux/xattr.h>
+ #endif
+ ]], [])], [], [AC_MSG_FAILURE([Cannot find fuse4fs Linux headers])])
+
+ if test -z "$have_fuse_lowlevel"
+ then
+ AC_MSG_FAILURE([Cannot find fuse lowlevel library.])
+ fi
+ AC_MSG_RESULT([Enabling fuse4fs])
+ fi
+], [
+ if test -n "$have_fuse_lowlevel"
+ then
+ AC_MSG_RESULT([Enabling fuse4fs by default])
+ else
+ AC_MSG_RESULT([Disabling fuse4fs by default])
+ fi
+]
+)
+AC_SUBST(FUSE4FS_CMT)
+
dnl
dnl see if PR_SET_IO_FLUSHER exists
dnl
@@ -2042,7 +2105,7 @@ for i in MCONFIG Makefile \
misc/Makefile ext2ed/Makefile e2fsck/Makefile \
debugfs/Makefile tests/Makefile tests/progs/Makefile \
tests/fuzz/Makefile resize/Makefile doc/Makefile \
- po/Makefile.in scrub/Makefile; do
+ po/Makefile.in scrub/Makefile fuse4fs/Makefile; do
if test -d `dirname ${srcdir}/$i` ; then
outlist="$outlist $i"
fi
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
new file mode 100644
index 00000000000000..bc137a765ee2b7
--- /dev/null
+++ b/fuse4fs/Makefile.in
@@ -0,0 +1,192 @@
+#
+# Standard e2fsprogs prologue....
+#
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+top_builddir = ..
+my_dir = misc
+INSTALL = @INSTALL@
+MKDIR_P = @MKDIR_P@
+
+@MCONFIG@
+
+UPROGS=
+UMANPAGES=
+@FUSE4FS_CMT@UPROGS+=fuse4fs
+@FUSE4FS_CMT@UMANPAGES+=fuse4fs.1
+
+FUSE4FS_OBJS= fuse4fs.o journal.o recovery.o revoke.o
+
+PROFILED_FUSE4FS_OJBS= profiled/fuse4fs.o profiled/journal.o \
+ profiled/recovery.o profiled/revoke.o
+
+SRCS=\
+ $(srcdir)/fuse4fs.c \
+ $(srcdir)/../debugfs/journal.c \
+ $(srcdir)/../e2fsck/revoke.c \
+ $(srcdir)/../e2fsck/recovery.c
+
+LIBS= $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBSUPPORT)
+DEPLIBS= $(LIBEXT2FS) $(DEPLIBCOM_ERR) $(DEPLIBSUPPORT)
+PROFILED_LIBS= $(LIBSUPPORT) $(PROFILED_LIBEXT2FS) $(PROFILED_LIBCOM_ERR)
+PROFILED_DEPLIBS= $(DEPLIBSUPPORT) $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBCOM_ERR)
+
+STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR)
+STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR)
+
+LIBS_E2P= $(LIBE2P) $(LIBCOM_ERR)
+DEPLIBS_E2P= $(LIBE2P) $(DEPLIBCOM_ERR)
+
+COMPILE_ET= _ET_DIR_OVERRIDE=$(srcdir)/../lib/et/et ../lib/et/compile_et
+
+# This nastiness is needed because of jfs_user.h hackery; when we finally
+# clean up this mess, we should be able to drop it
+JOURNAL_CFLAGS = -I$(srcdir)/../e2fsck $(ALL_CFLAGS) -DDEBUGFS
+DEPEND_CFLAGS = -I$(top_srcdir)/e2fsck
+
+.c.o:
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(ALL_CFLAGS) $< -o $@
+ $(Q) $(CHECK_CMD) $(ALL_CFLAGS) $<
+ $(Q) $(CPPCHECK_CMD) $(CPPFLAGS) $<
+@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \
+ $(FMANPAGES) $(LPROGS)
+
+all-static::
+
+@PROFILE_CMT@all:: fuse4fs.profiled
+
+profiled:
+@PROFILE_CMT@ $(E) " MKDIR $@"
+@PROFILE_CMT@ $(Q) mkdir profiled
+
+fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \
+ $(LIBEXT2FS) $(DEPLIBS_E2P)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o fuse4fs $(FUSE4FS_OBJS) $(LIBS) \
+ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \
+ $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P)
+
+journal.o: $(srcdir)/../debugfs/journal.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../debugfs/journal.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+recovery.o: $(srcdir)/../e2fsck/recovery.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../e2fsck/recovery.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+revoke.o: $(srcdir)/../e2fsck/revoke.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../e2fsck/revoke.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+fuse4fs.1: $(DEP_SUBSTITUTE) $(srcdir)/fuse4fs.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fuse4fs.1.in fuse4fs.1
+
+installdirs:
+ $(E) " MKDIR_P $(bindir) $(man1dir)"
+ $(Q) $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
+
+install: all $(UMANPAGES) installdirs
+ $(Q) for i in $(UPROGS); do \
+ $(ES) " INSTALL $(bindir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(bindir)/$$i; \
+ done
+ $(Q) for i in $(UMANPAGES); do \
+ for j in $(COMPRESS_EXT); do \
+ $(RM) -f $(DESTDIR)$(man1dir)/$$i.$$j; \
+ done; \
+ $(ES) " INSTALL_DATA $(man1dir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(man1dir)/$$i; \
+ done
+
+install-strip: install
+ $(Q) for i in $(UPROGS); do \
+ $(E) " STRIP $(bindir)/$$i"; \
+ $(STRIP) $(DESTDIR)$(bindir)/$$i; \
+ done
+
+uninstall:
+ for i in $(UPROGS); do \
+ $(RM) -f $(DESTDIR)$(bindir)/$$i; \
+ done
+ for i in $(UMANPAGES); do \
+ $(RM) -f $(DESTDIR)$(man1dir)/$$i; \
+ done
+
+clean::
+ $(RM) -f $(UPROGS) $(UMANPAGES) profile.h \
+ fuse4fs.profiled \
+ profiled/*.o \#* *.s *.o *.a *~ core gmon.out
+
+mostlyclean: clean
+distclean: clean
+ $(RM) -f .depend Makefile $(srcdir)/TAGS $(srcdir)/Makefile.in.old
+
+# +++ Dependency line eater +++
+#
+# Makefile dependencies follow. This must be the last section in
+# the Makefile.in file
+#
+fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/version.h \
+ $(top_srcdir)/lib/e2p/e2p.h
+journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/../debugfs/journal.h \
+ $(top_srcdir)/e2fsck/jfs_user.h $(top_srcdir)/e2fsck/e2fsck.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/fuse4fs/fuse4fs.1.in b/fuse4fs/fuse4fs.1.in
new file mode 100644
index 00000000000000..8bef5f48802385
--- /dev/null
+++ b/fuse4fs/fuse4fs.1.in
@@ -0,0 +1,118 @@
+.\" -*- nroff -*-
+.\" Copyright 2025 Oracle. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FUSE4FS 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+fuse4fs \- FUSE file system client for ext2/ext3/ext4 file systems
+.SH SYNOPSIS
+.B fuse4fs
+[
+.B device/image
+]
+[
+.B mountpoint
+]
+[
+.I options
+]
+.SH DESCRIPTION
+.B fuse4fs
+is a FUSE file system client that supports reading and writing from
+devices or image files containing ext2, ext3, and ext4 file systems.
+.SH OPTIONS
+.SS "general options:"
+.TP
+\fB\-o\fR opt,[opt...]
+mount options
+.TP
+\fB\-h\fR \fB\-\-help\fR
+print help
+.TP
+\fB\-V\fR \fB\-\-version\fR
+print version
+.SS "fuse4fs options:"
+.TP
+\fB-o\fR ro
+read-only mount
+.TP
+\fB-o\fR rw
+read-write mount (default)
+.TP
+\fB-o\fR bsddf
+bsd-style df (default)
+.TP
+\fB-o\fR minixdf
+minix-style df
+.TP
+\fB-o\fR acl
+enable file access control lists
+.TP
+\fB-o\fR cache_size
+Set the disk cache size to this quantity.
+The quantity may contain the suffixes k, m, or g.
+By default, the size is 32MB.
+The size may not be larger than 2GB.
+.TP
+\fB-o\fR direct
+Use O_DIRECT to access the block device.
+.TP
+\fB-o\fR dirsync
+Flush dirty metadata to disk after every directory update.
+.TP
+\fB-o\fR errors=continue
+ignore errors
+.TP
+\fB-o\fR errors=remount-ro
+stop allowing writes after errors
+.TP
+\fB-o\fR errors=panic
+dump core on error
+.TP
+\fB-o\fR fakeroot
+pretend to be root for permission checks
+.TP
+\fB-o\fR fuse4fs_debug
+enable fuse4fs debugging
+.TP
+\fB-o\fR kernel
+Behave more like the kernel ext4 driver in the following ways:
+Allows processes owned by other users to access the filesystem.
+Uses the kernel's permissions checking logic instead of fuse4fs's.
+Enables setuid and device files.
+Note that these options can still be overridden (e.g.
+.I nosuid
+) later.
+.TP
+\fB-o\fR lockfile=path
+use this file to control access to the filesystem
+.TP
+\fB-o\fR no_default_opts
+do not include default fuse options
+.TP
+\fB-o\fR norecovery
+do not replay the journal and mount the file system read-only
+.SS "FUSE options:"
+.TP
+\fB-d -o\fR debug
+enable debug output (implies -f)
+.TP
+\fB-f\fR
+foreground operation
+.TP
+\fB-s\fR
+disable multi-threaded operation
+.P
+For other FUSE options please see
+.BR mount.fuse (8)
+or see the output of
+.I fuse4fs \-\-helpfull
+.SH AVAILABILITY
+.B fuse4fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR ext4 (5)
+.BR e2fsck (8),
+.BR mount.fuse (8)
+
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
new file mode 100644
index 00000000000000..daf22e0fe7fde5
--- /dev/null
+++ b/fuse4fs/fuse4fs.c
@@ -0,0 +1,5781 @@
+/*
+ * fuse4fs.c - FUSE low-level server for e2fsprogs.
+ *
+ * Copyright (C) 2014-2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include "config.h"
+#include <pthread.h>
+#ifdef __linux__
+# include <linux/fs.h>
+# include <linux/falloc.h>
+# include <linux/xattr.h>
+# include <sys/prctl.h>
+#endif
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+#endif
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <ctype.h>
+#define FUSE_DARWIN_ENABLE_EXTENSIONS 0
+#ifdef __SET_FOB_FOR_FUSE
+# error Do not set magic value __SET_FOB_FOR_FUSE!!!!
+#endif
+#ifndef _FILE_OFFSET_BITS
+/*
+ * Old versions of libfuse (e.g. Debian 2.9.9 package) required that the build
+ * system set _FILE_OFFSET_BITS explicitly, even if doing so isn't required to
+ * get a 64-bit off_t. AC_SYS_LARGEFILE doesn't set any _FILE_OFFSET_BITS if
+ * it's not required (such as on aarch64), so we must inject it here.
+ */
+# define __SET_FOB_FOR_FUSE
+# define _FILE_OFFSET_BITS 64
+#endif /* _FILE_OFFSET_BITS */
+#include <fuse.h>
+#ifdef __SET_FOB_FOR_FUSE
+# undef _FILE_OFFSET_BITS
+#endif /* __SET_FOB_FOR_FUSE */
+#include <inttypes.h>
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fsP.h"
+#include "support/bthread.h"
+
+#include "../version.h"
+#include "uuid/uuid.h"
+#include "e2p/e2p.h"
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#include <locale.h>
+#define _(a) (gettext(a))
+#ifdef gettext_noop
+#define N_(a) gettext_noop(a)
+#else
+#define N_(a) (a)
+#endif
+#define P_(singular, plural, n) (ngettext(singular, plural, n))
+#ifndef NLS_CAT_NAME
+#define NLS_CAT_NAME "e2fsprogs"
+#endif
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+#else
+#define _(a) (a)
+#define N_(a) a
+#define P_(singular, plural, n) ((n) == 1 ? (singular) : (plural))
+#endif
+
+#ifndef XATTR_NAME_POSIX_ACL_DEFAULT
+#define XATTR_NAME_POSIX_ACL_DEFAULT "posix_acl_default"
+#endif
+#ifndef XATTR_SECURITY_PREFIX
+#define XATTR_SECURITY_PREFIX "security."
+#define XATTR_SECURITY_PREFIX_LEN (sizeof (XATTR_SECURITY_PREFIX) - 1)
+#endif
+
+/*
+ * Linux and MacOS implement the setxattr(2) interface, which defines
+ * XATTR_CREATE and XATTR_REPLACE. However, FreeBSD uses
+ * extattr_set_file(2), which does not have a flags or options
+ * parameter, and does not define XATTR_CREATE and XATTR_REPLACE.
+ */
+#ifndef XATTR_CREATE
+#define XATTR_CREATE 0
+#endif
+
+#ifndef XATTR_REPLACE
+#define XATTR_REPLACE 0
+#endif
+
+#if !defined(EUCLEAN)
+#if !defined(EBADMSG)
+#define EUCLEAN EBADMSG
+#elif !defined(EPROTO)
+#define EUCLEAN EPROTO
+#else
+#define EUCLEAN EIO
+#endif
+#endif /* !defined(EUCLEAN) */
+
+#if !defined(ENODATA)
+#ifdef ENOATTR
+#define ENODATA ENOATTR
+#else
+#define ENODATA ENOENT
+#endif
+#endif /* !defined(ENODATA) */
+
+static inline uint64_t round_up(uint64_t b, unsigned int align)
+{
+ unsigned int m;
+
+ if (align == 0)
+ return b;
+ m = b % align;
+ if (m)
+ b += align - m;
+ return b;
+}
+
+static inline uint64_t round_down(uint64_t b, unsigned int align)
+{
+ unsigned int m;
+
+ if (align == 0)
+ return b;
+ m = b % align;
+ return b - m;
+}
+
+#define dbg_printf(fuse4fs, format, ...) \
+ while ((fuse4fs)->debug) { \
+ printf("FUSE4FS (%s): tid=%d " format, (fuse4fs)->shortdev, gettid(), ##__VA_ARGS__); \
+ fflush(stdout); \
+ break; \
+ }
+
+#define log_printf(fuse4fs, format, ...) \
+ do { \
+ printf("FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+#define err_printf(fuse4fs, format, ...) \
+ do { \
+ fprintf(stderr, "FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+ fflush(stderr); \
+ } while (0)
+
+#define timing_printf(fuse4fs, format, ...) \
+ while ((fuse4fs)->timing) { \
+ printf("FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+ break; \
+ }
+
+#ifdef _IOR
+# ifdef _IOW
+# define SUPPORT_I_FLAGS
+# endif
+#endif
+
+#ifdef FALLOC_FL_KEEP_SIZE
+# define FL_KEEP_SIZE_FLAG FALLOC_FL_KEEP_SIZE
+# define SUPPORT_FALLOCATE
+#else
+# define FL_KEEP_SIZE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_PUNCH_HOLE
+# define FL_PUNCH_HOLE_FLAG FALLOC_FL_PUNCH_HOLE
+#else
+# define FL_PUNCH_HOLE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_ZERO_RANGE
+# define FL_ZERO_RANGE_FLAG FALLOC_FL_ZERO_RANGE
+#else
+# define FL_ZERO_RANGE_FLAG (0)
+#endif
+
+errcode_t ext2fs_check_ext3_journal(ext2_filsys fs);
+errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
+
+const char *err_shortdev;
+
+#ifdef CONFIG_JBD_DEBUG /* Enabled by configure --enable-jbd-debug */
+int journal_enable_debug = -1;
+#endif
+
+/*
+ * ext2_file_t contains a struct inode, so we can't leave files open.
+ * Use this as a proxy instead.
+ */
+#define FUSE4FS_FILE_MAGIC (0xEF53DEAFUL)
+struct fuse4fs_file_handle {
+ unsigned long magic;
+ ext2_ino_t ino;
+ int open_flags;
+ int check_flags;
+};
+
+enum fuse4fs_opstate {
+ F4OP_READONLY,
+ F4OP_WRITABLE,
+ F4OP_SHUTDOWN,
+};
+
+/* Main program context */
+#define FUSE4FS_MAGIC (0xEF53DEADUL)
+struct fuse4fs {
+ unsigned long magic;
+ ext2_filsys fs;
+ pthread_mutex_t bfl;
+ char *device;
+ char *shortdev;
+
+ /* options set by fuse_opt_parse must be of type int */
+ int ro;
+ int debug;
+ int no_default_opts;
+ int errors_behavior; /* actually an enum */
+ int minixdf;
+ int fakeroot;
+ int alloc_all_blocks;
+ int norecovery;
+ int kernel;
+ int directio;
+ int acl;
+ int dirsync;
+
+ enum fuse4fs_opstate opstate;
+ int logfd;
+ int blocklog;
+ int oom_score_adj;
+ unsigned int blockmask;
+ unsigned long offset;
+ unsigned int next_generation;
+ unsigned long long cache_size;
+ char *lockfile;
+#ifdef CONFIG_MMP
+ struct bthread *mmp_thread;
+ unsigned int mmp_update_interval;
+#endif
+#ifdef HAVE_CLOCK_MONOTONIC
+ double lock_start_time;
+ double op_start_time;
+
+ /* options set by fuse_opt_parse must be of type int */
+ int timing;
+#endif
+};
+
+#define FUSE4FS_CHECK_HANDLE(ff, fh) \
+ do { \
+ if ((fh) == NULL || (fh)->magic != FUSE4FS_FILE_MAGIC) { \
+ fprintf(stderr, \
+ "FUSE4FS: Corrupt in-memory file handle at %s:%d!\n", \
+ __func__, __LINE__); \
+ fflush(stderr); \
+ return -EUCLEAN; \
+ } \
+ } while (0)
+
+#define __FUSE4FS_CHECK_CONTEXT(ff, retcode, shutcode) \
+ do { \
+ if ((ff) == NULL || (ff)->magic != FUSE4FS_MAGIC) { \
+ fprintf(stderr, \
+ "FUSE4FS: Corrupt in-memory data at %s:%d!\n", \
+ __func__, __LINE__); \
+ fflush(stderr); \
+ retcode; \
+ } \
+ if ((ff)->opstate == F4OP_SHUTDOWN) { \
+ shutcode; \
+ } \
+ } while (0)
+
+#define FUSE4FS_CHECK_CONTEXT(ff) \
+ __FUSE4FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
+#define FUSE4FS_CHECK_CONTEXT_DESTROY(ff) \
+ __FUSE4FS_CHECK_CONTEXT((ff), return, /* do not return */)
+#define FUSE4FS_CHECK_CONTEXT_INIT(ff) \
+ __FUSE4FS_CHECK_CONTEXT((ff), abort(), abort())
+
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
+ const char *func, int line);
+#define translate_error(fs, ino, err) __translate_error((fs), (ino), (err), \
+ __func__, __LINE__)
+
+/* for macosx */
+#ifndef W_OK
+# define W_OK 2
+#endif
+
+#ifndef R_OK
+# define R_OK 4
+#endif
+
+static inline int u_log2(unsigned int arg)
+{
+ int l = 0;
+
+ arg >>= 1;
+ while (arg) {
+ l++;
+ arg >>= 1;
+ }
+ return l;
+}
+
+static inline blk64_t FUSE4FS_B_TO_FSBT(const struct fuse4fs *ff, off_t pos)
+{
+ return pos >> ff->blocklog;
+}
+
+static inline blk64_t FUSE4FS_B_TO_FSB(const struct fuse4fs *ff, off_t pos)
+{
+ return (pos + ff->blockmask) >> ff->blocklog;
+}
+
+static inline unsigned int FUSE4FS_OFF_IN_FSB(const struct fuse4fs *ff,
+ off_t pos)
+{
+ return pos & ff->blockmask;
+}
+
+static inline off_t FUSE4FS_FSB_TO_B(const struct fuse4fs *ff, blk64_t bno)
+{
+ return bno << ff->blocklog;
+}
+
+static double gettime_monotonic(void)
+{
+#ifdef CLOCK_MONOTONIC
+ struct timespec ts;
+#endif
+ struct timeval tv;
+ static pthread_mutex_t fake_lock = PTHREAD_MUTEX_INITIALIZER;
+ static double fake_time = 0;
+ double dret;
+ int ret;
+
+#ifdef CLOCK_MONOTONIC
+ ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (ret == 0)
+ return (double)ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+#endif
+ ret = gettimeofday(&tv, NULL);
+ if (ret == 0)
+ return (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+
+ /* If we have no clock sources at all, fake it */
+ pthread_mutex_lock(&fake_lock);
+ fake_time += 1.0;
+ dret = fake_time;
+ pthread_mutex_unlock(&fake_lock);
+
+ return dret;
+}
+
+static double init_deadline(double timeout)
+{
+ return gettime_monotonic() + timeout;
+}
+
+static int retry_before_deadline(double deadline)
+{
+ double now = gettime_monotonic();
+
+ if (now >= deadline)
+ return 0;
+
+ /* sleep for 0.1s before trying again */
+ usleep(100000);
+ return 1;
+}
+
+/* Wait this many seconds to acquire the filesystem device */
+#define FUSE4FS_OPEN_TIMEOUT (15.0)
+
+#define EXT4_EPOCH_BITS 2
+#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
+#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
+
+/*
+ * Extended fields will fit into an inode if the filesystem was formatted
+ * with large inodes (-I 256 or larger) and there are not currently any EAs
+ * consuming all of the available space. For new inodes we always reserve
+ * enough space for the kernel's known extended fields, but for inodes
+ * created with an old kernel this might not have been the case. None of
+ * the extended inode fields is critical for correct filesystem operation.
+ * This macro checks if a certain field fits in the inode. Note that
+ * inode-size = GOOD_OLD_INODE_SIZE + i_extra_isize
+ */
+#define EXT4_FITS_IN_INODE(ext4_inode, field) \
+ ((offsetof(typeof(*ext4_inode), field) + \
+ sizeof((ext4_inode)->field)) \
+ <= ((size_t) EXT2_GOOD_OLD_INODE_SIZE + \
+ (ext4_inode)->i_extra_isize)) \
+
+static inline __u32 ext4_encode_extra_time(const struct timespec *time)
+{
+ __u32 extra = sizeof(time->tv_sec) > 4 ?
+ ((time->tv_sec - (__s32)time->tv_sec) >> 32) &
+ EXT4_EPOCH_MASK : 0;
+ return extra | (time->tv_nsec << EXT4_EPOCH_BITS);
+}
+
+static inline void ext4_decode_extra_time(struct timespec *time, __u32 extra)
+{
+ if (sizeof(time->tv_sec) > 4 && (extra & EXT4_EPOCH_MASK)) {
+ __u64 extra_bits = extra & EXT4_EPOCH_MASK;
+ /*
+ * Prior to kernel 3.14?, we had a broken decode function,
+ * wherein we effectively did this:
+ * if (extra_bits == 3)
+ * extra_bits = 0;
+ */
+ time->tv_sec += extra_bits << 32;
+ }
+ time->tv_nsec = ((extra) & EXT4_NSEC_MASK) >> EXT4_EPOCH_BITS;
+}
+
+#define EXT4_CLAMP_TIMESTAMP(xtime, timespec, raw_inode) \
+do { \
+ if ((timespec)->tv_sec < EXT4_TIMESTAMP_MIN) \
+ (timespec)->tv_sec = EXT4_TIMESTAMP_MIN; \
+ if ((timespec)->tv_sec < EXT4_TIMESTAMP_MIN) \
+ (timespec)->tv_sec = EXT4_TIMESTAMP_MIN; \
+ \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) { \
+ if ((timespec)->tv_sec > EXT4_EXTRA_TIMESTAMP_MAX) \
+ (timespec)->tv_sec = EXT4_EXTRA_TIMESTAMP_MAX; \
+ } else { \
+ if ((timespec)->tv_sec > EXT4_NON_EXTRA_TIMESTAMP_MAX) \
+ (timespec)->tv_sec = EXT4_NON_EXTRA_TIMESTAMP_MAX; \
+ } \
+} while (0)
+
+#define EXT4_INODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ typeof(*(timespec)) _ts = *(timespec); \
+ \
+ EXT4_CLAMP_TIMESTAMP(xtime, &_ts, raw_inode); \
+ (raw_inode)->xtime = _ts.tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(&_ts); \
+} while (0)
+
+#define EXT4_EINODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ typeof(*(timespec)) _ts = *(timespec); \
+ \
+ EXT4_CLAMP_TIMESTAMP(xtime, &_ts, raw_inode); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (raw_inode)->xtime = _ts.tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(&_ts); \
+} while (0)
+
+#define EXT4_INODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ (timespec)->tv_sec = (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ (raw_inode)->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (timespec)->tv_sec = \
+ (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ raw_inode->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+static inline errcode_t fuse4fs_read_inode(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ memset(inode, 0, sizeof(*inode));
+ return ext2fs_read_inode_full(fs, ino, EXT2_INODE(inode),
+ sizeof(*inode));
+}
+
+static inline errcode_t fuse4fs_write_inode(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ return ext2fs_write_inode_full(fs, ino, EXT2_INODE(inode),
+ sizeof(*inode));
+}
+
+static inline ext2_filsys fuse4fs_start(struct fuse4fs *ff)
+{
+ if (ff->timing) {
+ double lock_time = gettime_monotonic();
+
+ pthread_mutex_lock(&ff->bfl);
+
+ ff->op_start_time = gettime_monotonic();
+ ff->lock_start_time = lock_time;
+ } else {
+ pthread_mutex_lock(&ff->bfl);
+ }
+
+ return ff->fs;
+}
+
+static inline void fuse4fs_finish_timing(struct fuse4fs *ff, const char *func)
+{
+ double now;
+
+ if (!ff->timing)
+ return;
+
+ now = gettime_monotonic();
+
+ timing_printf(ff, "%s: lock=%.2fms elapsed=%.2fms\n", func,
+ (ff->op_start_time - ff->lock_start_time) * 1000.0,
+ (now - ff->op_start_time) * 1000.0);
+}
+
+static inline void __fuse4fs_finish(struct fuse4fs *ff, int ret,
+ const char *func)
+{
+ fuse4fs_finish_timing(ff, func);
+ if (ret)
+ dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
+ pthread_mutex_unlock(&ff->bfl);
+}
+#define fuse4fs_finish(ff, ret) __fuse4fs_finish((ff), (ret), __func__)
+
+#ifdef CONFIG_MMP
+static bool fuse4fs_mmp_wanted(const struct fuse4fs *ff)
+{
+ ext2_filsys fs = ff->fs;
+
+ if (!fs || !ext2fs_has_feature_mmp(fs->super) ||
+ ff->opstate != F4OP_WRITABLE || (fs->flags & EXT2_FLAG_SKIP_MMP))
+ return false;
+ return true;
+}
+
+static int fuse4fs_mmp_touch(struct fuse4fs *ff, bool immediate)
+{
+ ext2_filsys fs = ff->fs;
+ struct mmp_struct *mmp = fs->mmp_buf;
+ struct mmp_struct *mmp_cmp = fs->mmp_cmp;
+ struct timeval tv;
+ errcode_t retval = 0;
+
+ gettimeofday(&tv, 0);
+ if (!immediate &&
+ tv.tv_sec - fs->mmp_last_written < ff->mmp_update_interval)
+ return 0;
+
+ retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
+ if (retval)
+ return translate_error(fs, 0, retval);
+
+ if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
+ return translate_error(fs, 0, EXT2_ET_MMP_CHANGE_ABORT);
+
+ /*
+ * Believe it or not, ext2fs_mmp_read actually overwrites fs->mmp_cmp
+ * and leaves fs->mmp_buf untouched. Hence we copy mmp_cmp into
+ * mmp_buf, update mmp_buf, and write mmp_buf out to disk.
+ */
+ memcpy(mmp, mmp_cmp, sizeof(*mmp_cmp));
+ mmp->mmp_time = tv.tv_sec;
+ mmp->mmp_seq = ext2fs_mmp_new_seq();
+
+ retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
+ if (retval)
+ return translate_error(fs, 0, retval);
+
+ return 0;
+}
+
+static void fuse4fs_mmp_bthread(void *data)
+{
+ struct fuse4fs *ff = data;
+
+ fuse4fs_start(ff);
+ if (fuse4fs_mmp_wanted(ff) && !bthread_cancelled(ff->mmp_thread))
+ fuse4fs_mmp_touch(ff, false);
+ fuse4fs_finish(ff, 0);
+}
+
+static void fuse4fs_mmp_start(struct fuse4fs *ff)
+{
+ int ret;
+
+ if (!fuse4fs_mmp_wanted(ff))
+ return;
+
+ ret = bthread_create("fuse4fs_mmp", fuse4fs_mmp_bthread, ff,
+ ff->mmp_update_interval, &ff->mmp_thread);
+ if (ret) {
+ err_printf(ff, "MMP: %s.\n", error_message(ret));
+ return;
+ }
+
+ ret = bthread_start(ff->mmp_thread);
+ if (ret)
+ err_printf(ff, "MMP: %s.\n", error_message(ret));
+}
+
+static void fuse4fs_mmp_cancel(struct fuse4fs *ff)
+{
+ if (ff->mmp_thread)
+ bthread_cancel(ff->mmp_thread);
+}
+
+static void fuse4fs_mmp_config(struct fuse4fs *ff)
+{
+ ext2_filsys fs = ff->fs;
+ struct mmp_struct *mmp_s = fs->mmp_buf;
+ unsigned int mmp_update_interval = fs->super->s_mmp_update_interval;
+
+ if (!ext2fs_has_feature_mmp(fs->super) ||
+ !(fs->flags & EXT2_FLAG_RW) ||
+ (fs->flags & EXT2_FLAG_SKIP_MMP))
+ return;
+
+ /*
+ * If update_interval in MMP block is larger, use that instead of
+ * update_interval from the superblock.
+ */
+ if (mmp_s->mmp_check_interval > mmp_update_interval)
+ mmp_update_interval = mmp_s->mmp_check_interval;
+
+ /* Clamp to the relevant(?) interval values */
+ if (mmp_update_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
+ mmp_update_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
+ if (mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
+ mmp_update_interval = EXT4_MMP_MAX_UPDATE_INTERVAL;
+
+ ff->mmp_update_interval = mmp_update_interval;
+
+ /*
+ * libext2fs writes EXT4_MMP_SEQ_FSCK after mounting, so we need to
+ * update it immediately so that it doesn't look like another node is
+ * actually running fsck.
+ */
+ fuse4fs_mmp_touch(ff, true);
+}
+
+static void fuse4fs_mmp_destroy(struct fuse4fs *ff)
+{
+ bthread_destroy(&ff->mmp_thread);
+}
+#else
+# define fuse4fs_mmp_start(...) ((void)0)
+# define fuse4fs_mmp_cancel(...) ((void)0)
+# define fuse4fs_mmp_config(...) ((void)0)
+# define fuse4fs_mmp_destroy(...) ((void)0)
+#endif
+
+static inline struct fuse4fs *fuse4fs_get(void)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+
+ return ctxt->private_data;
+}
+
+static inline struct fuse4fs_file_handle *
+fuse4fs_get_handle(const struct fuse_file_info *fp)
+{
+ return (struct fuse4fs_file_handle *)(uintptr_t)fp->fh;
+}
+
+static inline void
+fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh)
+{
+ fp->fh = (uintptr_t)fh;
+}
+
+static void get_now(struct timespec *now)
+{
+#ifdef CLOCK_REALTIME
+ if (!clock_gettime(CLOCK_REALTIME, now))
+ return;
+#endif
+
+ now->tv_sec = time(NULL);
+ now->tv_nsec = 0;
+}
+
+static void increment_version(struct ext2_inode_large *inode)
+{
+ __u64 ver;
+
+ ver = inode->osd1.linux1.l_i_version;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ ver |= (__u64)inode->i_version_hi << 32;
+ ver++;
+ inode->osd1.linux1.l_i_version = ver;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ inode->i_version_hi = ver >> 32;
+}
+
+static void init_times(struct ext2_inode_large *inode)
+{
+ struct timespec now;
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_atime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, inode);
+ EXT4_EINODE_SET_XTIME(i_crtime, &now, inode);
+ increment_version(inode);
+}
+
+static int update_ctime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct timespec now;
+ struct ext2_inode_large inode;
+
+ get_now(&now);
+
+ /* If user already has a inode buffer, just update that */
+ if (pinode) {
+ increment_version(pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ return 0;
+ }
+
+ /* Otherwise we have to read-modify-write the inode */
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ increment_version(&inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_atime(ext2_filsys fs, ext2_ino_t ino)
+{
+ errcode_t err;
+ struct ext2_inode_large inode, *pinode;
+ struct timespec atime, mtime, now;
+ double datime, dmtime, dnow;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ pinode = &inode;
+ EXT4_INODE_GET_XTIME(i_atime, &atime, pinode);
+ EXT4_INODE_GET_XTIME(i_mtime, &mtime, pinode);
+ get_now(&now);
+
+ datime = atime.tv_sec + ((double)atime.tv_nsec / 1000000000);
+ dmtime = mtime.tv_sec + ((double)mtime.tv_nsec / 1000000000);
+ dnow = now.tv_sec + ((double)now.tv_nsec / 1000000000);
+
+ /*
+ * If atime is newer than mtime and atime hasn't been updated in thirty
+ * seconds, skip the atime update. Same idea as Linux "relatime". Use
+ * doubles to account for nanosecond resolution.
+ */
+ if (datime >= dmtime && datime >= dnow - 30)
+ return 0;
+ EXT4_INODE_SET_XTIME(i_atime, &now, &inode);
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_mtime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct timespec now;
+
+ if (pinode) {
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ increment_version(pinode);
+ return 0;
+ }
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, &inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+ increment_version(&inode);
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int ext2_file_type(unsigned int mode)
+{
+ if (LINUX_S_ISREG(mode))
+ return EXT2_FT_REG_FILE;
+
+ if (LINUX_S_ISDIR(mode))
+ return EXT2_FT_DIR;
+
+ if (LINUX_S_ISCHR(mode))
+ return EXT2_FT_CHRDEV;
+
+ if (LINUX_S_ISBLK(mode))
+ return EXT2_FT_BLKDEV;
+
+ if (LINUX_S_ISLNK(mode))
+ return EXT2_FT_SYMLINK;
+
+ if (LINUX_S_ISFIFO(mode))
+ return EXT2_FT_FIFO;
+
+ if (LINUX_S_ISSOCK(mode))
+ return EXT2_FT_SOCK;
+
+ return 0;
+}
+
+static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
+{
+ ext2_filsys fs = ff->fs;
+ blk64_t reserved;
+
+ dbg_printf(ff, "%s: Asking for %llu; alloc_all=%d total=%llu free=%llu "
+ "rsvd=%llu\n", __func__, num, ff->alloc_all_blocks,
+ ext2fs_blocks_count(fs->super),
+ ext2fs_free_blocks_count(fs->super),
+ ext2fs_r_blocks_count(fs->super));
+ if (num > ext2fs_blocks_count(fs->super))
+ return 0;
+
+ if (ff->alloc_all_blocks)
+ return 1;
+
+ /*
+ * Different meaning for r_blocks -- libext2fs has bugs where the FS
+ * can get corrupted if it totally runs out of blocks. Avoid this
+ * by refusing to allocate any of the reserve blocks to anybody.
+ */
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (reserved == 0)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ return ext2fs_free_blocks_count(fs->super) > reserved + num;
+}
+
+static int fuse4fs_is_writeable(struct fuse4fs *ff)
+{
+ return ff->opstate == F4OP_WRITABLE &&
+ (ff->fs->super->s_error_count == 0);
+}
+
+static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
+{
+ if (ff->fakeroot)
+ return 1;
+ return ctxt->uid == 0;
+}
+
+static inline int want_check_owner(struct fuse4fs *ff,
+ struct fuse_context *ctxt)
+{
+ /*
+ * The kernel is responsible for access control, so we allow anything
+ * that the superuser can do.
+ */
+ if (ff->kernel)
+ return 0;
+ return !is_superuser(ff, ctxt);
+}
+
+/* Test for append permission */
+#define A_OK 16
+
+static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
+ const struct ext2_inode *inode, int mask)
+{
+ EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
+
+ /* no writing or metadata changes to read-only or broken fs */
+ if ((mask & (W_OK | A_OK)) && !fuse4fs_is_writeable(ff))
+ return -EROFS;
+
+ dbg_printf(ff, "access ino=%d mask=e%s%s%s%s iflags=0x%x\n",
+ ino,
+ (mask & R_OK ? "r" : ""),
+ (mask & W_OK ? "w" : ""),
+ (mask & X_OK ? "x" : ""),
+ (mask & A_OK ? "a" : ""),
+ inode->i_flags);
+
+ /* is immutable? */
+ if ((mask & W_OK) &&
+ (inode->i_flags & EXT2_IMMUTABLE_FL))
+ return -EPERM;
+
+ /* is append-only? */
+ if ((inode->i_flags & EXT2_APPEND_FL) && (mask & W_OK) && !(mask & A_OK))
+ return -EPERM;
+
+ return 0;
+}
+
+static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ ext2_filsys fs = ff->fs;
+ struct ext2_inode inode;
+ mode_t perms;
+ errcode_t err;
+ int ret;
+
+ /* no writing to read-only or broken fs */
+ if ((mask & (W_OK | A_OK)) && !fuse4fs_is_writeable(ff))
+ return -EROFS;
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+ perms = inode.i_mode & 0777;
+
+ dbg_printf(ff, "access ino=%d mask=e%s%s%s%s perms=0%o iflags=0x%x "
+ "fuid=%d fgid=%d uid=%d gid=%d\n", ino,
+ (mask & R_OK ? "r" : ""),
+ (mask & W_OK ? "w" : ""),
+ (mask & X_OK ? "x" : ""),
+ (mask & A_OK ? "a" : ""),
+ perms, inode.i_flags,
+ inode_uid(inode), inode_gid(inode),
+ ctxt->uid, ctxt->gid);
+
+ /* existence check */
+ if (mask == 0)
+ return 0;
+
+ ret = check_iflags_access(ff, ino, &inode, mask);
+ if (ret)
+ return ret;
+
+ /* If kernel is responsible for mode and acl checks, we're done. */
+ if (ff->kernel)
+ return 0;
+
+ /* Figure out what root's allowed to do */
+ if (is_superuser(ff, ctxt)) {
+ /* Non-file access always ok */
+ if (!LINUX_S_ISREG(inode.i_mode))
+ return 0;
+
+ /* R/W access to a file always ok */
+ if (!(mask & X_OK))
+ return 0;
+
+ /* X access to a file ok if a user/group/other can X */
+ if (perms & 0111)
+ return 0;
+
+ /* Trying to execute a file that's not executable. BZZT! */
+ return -EACCES;
+ }
+
+ /* Remove the O_APPEND flag before testing permissions */
+ mask &= ~A_OK;
+
+ /* allow owner, if perms match */
+ if (inode_uid(inode) == ctxt->uid) {
+ if ((mask & (perms >> 6)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* allow group, if perms match */
+ if (inode_gid(inode) == ctxt->gid) {
+ if ((mask & (perms >> 3)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* otherwise check other */
+ if ((mask & perms) == mask)
+ return 0;
+ return -EACCES;
+}
+
+static errcode_t fuse4fs_check_support(struct fuse4fs *ff)
+{
+ ext2_filsys fs = ff->fs;
+
+ if (ext2fs_has_feature_quota(fs->super)) {
+ err_printf(ff, "%s\n", _("quotas not supported."));
+ return EXT2_ET_UNSUPP_FEATURE;
+ }
+ if (ext2fs_has_feature_verity(fs->super)) {
+ err_printf(ff, "%s\n", _("verity not supported."));
+ return EXT2_ET_UNSUPP_FEATURE;
+ }
+ if (ext2fs_has_feature_encrypt(fs->super)) {
+ err_printf(ff, "%s\n", _("encryption not supported."));
+ return EXT2_ET_UNSUPP_FEATURE;
+ }
+ if (ext2fs_has_feature_casefold(fs->super)) {
+ err_printf(ff, "%s\n", _("casefolding not supported."));
+ return EXT2_ET_UNSUPP_FEATURE;
+ }
+
+ if (fs->super->s_state & EXT2_ERROR_FS) {
+ err_printf(ff, "%s\n",
+ _("Errors detected; running e2fsck is required."));
+ return EXT2_ET_FILESYSTEM_CORRUPTED;
+ }
+
+ return 0;
+}
+
+static errcode_t fuse4fs_acquire_lockfile(struct fuse4fs *ff)
+{
+ char *resolved;
+ int lockfd;
+ errcode_t err;
+
+ lockfd = open(ff->lockfile, O_RDWR | O_CREAT | O_EXCL, 0400);
+ if (lockfd < 0) {
+ if (errno == EEXIST)
+ err = EWOULDBLOCK;
+ else
+ err = errno;
+ err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+ _("opening lockfile failed"),
+ strerror(err));
+ ff->lockfile = NULL;
+ return err;
+ }
+ close(lockfd);
+
+ resolved = realpath(ff->lockfile, NULL);
+ if (!resolved) {
+ err = errno;
+ err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+ _("resolving lockfile failed"),
+ strerror(err));
+ unlink(ff->lockfile);
+ ff->lockfile = NULL;
+ return err;
+ }
+ free(ff->lockfile);
+ ff->lockfile = resolved;
+
+ return 0;
+}
+
+static void fuse4fs_release_lockfile(struct fuse4fs *ff)
+{
+ if (unlink(ff->lockfile)) {
+ errcode_t err = errno;
+
+ err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+ _("removing lockfile failed"),
+ strerror(err));
+ }
+ free(ff->lockfile);
+}
+
+static void fuse4fs_unmount(struct fuse4fs *ff)
+{
+ char uuid[UUID_STR_SIZE];
+ errcode_t err;
+
+ if (ff->fs) {
+ uuid_unparse(ff->fs->super->s_uuid, uuid);
+ err = ext2fs_close_free(&ff->fs);
+ if (err)
+ err_printf(ff, "%s: %s\n", _("while closing fs"),
+ error_message(err));
+
+ if (ff->kernel)
+ log_printf(ff, "%s %s.\n", _("unmounted filesystem"),
+ uuid);
+ }
+
+ if (ff->lockfile)
+ fuse4fs_release_lockfile(ff);
+}
+
+static errcode_t fuse4fs_open(struct fuse4fs *ff)
+{
+ char options[128];
+ double deadline;
+ int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |
+ EXT2_FLAG_EXCLUSIVE;
+ errcode_t err;
+
+ if (ff->lockfile) {
+ err = fuse4fs_acquire_lockfile(ff);
+ if (err)
+ return err;
+ }
+
+ snprintf(options, sizeof(options) - 1, "offset=%lu", ff->offset);
+ ff->opstate = F4OP_READONLY;
+
+ if (ff->directio)
+ flags |= EXT2_FLAG_DIRECT_IO;
+
+ /*
+ * If the filesystem is stored on a block device, the _EXCLUSIVE flag
+ * causes libext2fs to try to open the block device with O_EXCL. If
+ * the block device is already opened O_EXCL by something else, the
+ * open call returns EBUSY.
+ *
+ * Unfortunately, there's a nasty race between fuse4fs going through
+ * its startup sequence (open fs, parse superblock, daemonize, create
+ * mount, respond to FUSE_INIT) in response to a mount(8) invocation
+ * and another process that calls umount(2) on the same mount.
+ *
+ * If fuse4fs is being run as a mount(8) helper and has daemonized, the
+ * original fuse4fs subprocess exits and so will mount(8). This can
+ * occur before the kernel issues a FUSE_INIT request to fuse4fs. If
+ * a process then umount(2)'s the mount, the kernel will abort the
+ * fuse connection. If the FUSE_INIT request hasn't been issued, now
+ * it won't ever be issued. The kernel tears down the mount and
+ * returns from umount(2), but fuse4fs has no idea that any of this has
+ * happened because it receives no requests.
+ *
+ * At this point, the original fuse4fs server holds the block device
+ * open O_EXCL. If mount(8) is invoked again on the same device, the
+ * new fuse4fs server will try to open the block device O_EXCL and
+ * fail. A crappy solution here is to retry for 5 seconds, hoping that
+ * the first fuse4fs server will wake up and exit.
+ *
+ * If the filesystem is in a regular file, O_EXCL (without O_CREAT) has
+ * no defined behavior, but it never returns EBUSY.
+ */
+ deadline = init_deadline(FUSE4FS_OPEN_TIMEOUT);
+ do {
+ err = ext2fs_open2(ff->device, options, flags, 0, 0,
+ unix_io_manager, &ff->fs);
+ if ((err == EPERM || err == EACCES) &&
+ (!ff->ro || (flags & EXT2_FLAG_RW))) {
+ /*
+ * Source device cannot be opened for write. Under
+ * these circumstances, mount(8) will try again with a
+ * ro mount, and the kernel will open the block device
+ * readonly.
+ */
+ log_printf(ff, "%s\n",
+ _("WARNING: source write-protected, mounted read-only."));
+ flags &= ~EXT2_FLAG_RW;
+ ff->ro = 1;
+
+ /* Force the loop to run once more */
+ err = -1;
+ }
+ } while (err == -1 ||
+ (err == EBUSY && retry_before_deadline(deadline)));
+ if (err == EBUSY) {
+ err_printf(ff, "%s: %s.\n",
+ _("Could not lock filesystem block device"), error_message(err));
+ return err;
+ }
+ if (err) {
+ err_printf(ff, "%s.\n", error_message(err));
+ err_printf(ff, "%s\n", _("Please run e2fsck -fy."));
+ return err;
+ }
+
+ /*
+ * If the filesystem is stored in a regular file, take an (advisory)
+ * exclusive lock to prevent other instances of e2fsprogs from writing
+ * to the filesystem image. On Linux we don't want to do this for
+ * block devices because udev will spin forever trying to settle a
+ * uevent and cause weird userspace stalls, and block devices have
+ * O_EXCL so we don't need this there.
+ */
+ if (!(ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+ unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
+
+ if (ff->fs->flags & IO_FLAG_RW)
+ lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
+ else
+ lock_flags |= IO_CHANNEL_FLOCK_SHARED;
+
+ deadline = init_deadline(FUSE4FS_OPEN_TIMEOUT);
+ do {
+ err = io_channel_flock(ff->fs->io, lock_flags);
+ } while (err == EWOULDBLOCK && retry_before_deadline(deadline));
+ if (err) {
+ err_printf(ff, "%s: %s\n",
+ _("Could not lock filesystem image"), error_message(err));
+ return err;
+ }
+ }
+
+ if (ff->kernel) {
+ char uuid[UUID_STR_SIZE];
+
+ uuid_unparse(ff->fs->super->s_uuid, uuid);
+ log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
+ }
+
+ ff->fs->priv_data = ff;
+ ff->blocklog = u_log2(ff->fs->blocksize);
+ ff->blockmask = ff->fs->blocksize - 1;
+
+ fuse4fs_mmp_config(ff);
+ return 0;
+}
+
+/* Figure out a reasonable default size for the disk cache */
+static unsigned long long default_cache_size(void)
+{
+ long pages = 0, pagesize = 0;
+ unsigned long long max_cache;
+ unsigned long long ret = 32ULL << 20; /* 32 MB */
+
+#ifdef _SC_PHYS_PAGES
+ pages = sysconf(_SC_PHYS_PAGES);
+#endif
+#ifdef _SC_PAGESIZE
+ pagesize = sysconf(_SC_PAGESIZE);
+#endif
+ if (pages > 0 && pagesize > 0) {
+ max_cache = (unsigned long long)pagesize * pages / 20;
+
+ if (max_cache > 0 && ret > max_cache)
+ ret = max_cache;
+ }
+ return ret;
+}
+
+static errcode_t fuse4fs_config_cache(struct fuse4fs *ff)
+{
+ char buf[128];
+ errcode_t err;
+
+ if (!ff->cache_size)
+ ff->cache_size = default_cache_size();
+ if (!ff->cache_size)
+ return 0;
+
+ snprintf(buf, sizeof(buf), "cache_blocks=%llu",
+ FUSE4FS_B_TO_FSBT(ff, ff->cache_size));
+ err = io_channel_set_options(ff->fs->io, buf);
+ if (err) {
+ err_printf(ff, "%s %lluk: %s\n",
+ _("cannot set disk cache size to"),
+ ff->cache_size >> 10,
+ error_message(err));
+ return err;
+ }
+
+ return 0;
+}
+
+static int fuse4fs_mount(struct fuse4fs *ff)
+{
+ struct ext2_inode_large inode;
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+
+ if (ext2fs_has_feature_journal_needs_recovery(fs->super)) {
+ if (ff->norecovery) {
+ log_printf(ff, "%s\n",
+ _("Mounting read-only without recovering journal."));
+ ff->ro = 1;
+ ff->fs->flags &= ~EXT2_FLAG_RW;
+ } else if (!(fs->flags & EXT2_FLAG_RW)) {
+ err_printf(ff, "%s\n",
+ _("Cannot replay journal on read-only device."));
+ return -1;
+ } else {
+ log_printf(ff, "%s\n", _("Recovering journal."));
+ err = ext2fs_run_ext3_journal(&ff->fs);
+ if (err) {
+ err_printf(ff, "%s.\n", error_message(err));
+ err_printf(ff, "%s\n",
+ _("Please run e2fsck -fy."));
+ return translate_error(fs, 0, err);
+ }
+ fs = ff->fs;
+
+ err = fuse4fs_check_support(ff);
+ if (err)
+ return err;
+ }
+ } else if (ext2fs_has_feature_journal(fs->super)) {
+ err = ext2fs_check_ext3_journal(fs);
+ if (err)
+ return translate_error(fs, 0, err);
+ }
+
+ /* Make sure the root directory is readable. */
+ err = fuse4fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+ if (err)
+ return translate_error(fs, EXT2_ROOT_INO, err);
+
+ if (fs->flags & EXT2_FLAG_RW) {
+ if (ext2fs_has_feature_journal(fs->super))
+ log_printf(ff, "%s",
+ _("Warning: fuse4fs does not support using the journal.\n"
+ "There may be file system corruption or data loss if\n"
+ "the file system is not gracefully unmounted.\n"));
+ ff->opstate = F4OP_WRITABLE;
+ }
+
+ if (!(fs->super->s_state & EXT2_VALID_FS))
+ err_printf(ff, "%s\n",
+ _("Warning: Mounting unchecked fs, running e2fsck is recommended."));
+ if (fs->super->s_max_mnt_count > 0 &&
+ fs->super->s_mnt_count >= fs->super->s_max_mnt_count)
+ err_printf(ff, "%s\n",
+ _("Warning: Maximal mount count reached, running e2fsck is recommended."));
+ if (fs->super->s_checkinterval > 0 &&
+ (time_t) (fs->super->s_lastcheck +
+ fs->super->s_checkinterval) <= time(0))
+ err_printf(ff, "%s\n",
+ _("Warning: Check time reached; running e2fsck is recommended."));
+ if (fs->super->s_last_orphan)
+ err_printf(ff, "%s\n",
+ _("Orphans detected; running e2fsck is recommended."));
+
+ if (!ff->errors_behavior)
+ ff->errors_behavior = fs->super->s_errors;
+
+ /* Clear the valid flag so that an unclean shutdown forces a fsck */
+ if (ff->opstate == F4OP_WRITABLE) {
+ fs->super->s_mnt_count++;
+ ext2fs_set_tstamp(fs->super, s_mtime, time(NULL));
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ return translate_error(fs, 0, err);
+ }
+
+ return 0;
+}
+
+static void op_destroy(void *p EXT2FS_ATTR((unused)))
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+
+ FUSE4FS_CHECK_CONTEXT_DESTROY(ff);
+
+ fs = fuse4fs_start(ff);
+
+ dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
+ if (ff->opstate == F4OP_WRITABLE) {
+ fs->super->s_state |= EXT2_VALID_FS;
+ if (fs->super->s_error_count)
+ fs->super->s_state |= EXT2_ERROR_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_set_gdt_csum(fs);
+ if (err)
+ translate_error(fs, 0, err);
+
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ translate_error(fs, 0, err);
+ }
+
+ if (ff->debug && fs->io->manager->get_stats) {
+ io_stats stats = NULL;
+
+ fs->io->manager->get_stats(fs->io, &stats);
+ dbg_printf(ff, "read: %lluk\n", stats->bytes_read >> 10);
+ dbg_printf(ff, "write: %lluk\n", stats->bytes_written >> 10);
+ dbg_printf(ff, "hits: %llu\n", stats->cache_hits);
+ dbg_printf(ff, "misses: %llu\n", stats->cache_misses);
+ dbg_printf(ff, "hit_ratio: %.1f%%\n",
+ (100.0 * stats->cache_hits) /
+ (stats->cache_hits + stats->cache_misses));
+ }
+
+ fuse4fs_finish(ff, 0);
+}
+
+/* Reopen @stream with @fileno */
+static int fuse4fs_freopen_stream(const char *path, int fileno, FILE *stream)
+{
+ char _fdpath[256];
+ const char *fdpath;
+ FILE *fp;
+ int ret;
+
+ ret = snprintf(_fdpath, sizeof(_fdpath), "/dev/fd/%d", fileno);
+ if (ret >= sizeof(_fdpath))
+ fdpath = path;
+ else
+ fdpath = _fdpath;
+
+ /*
+ * C23 defines std{out,err} as an expression of type FILE* that need
+ * not be an lvalue. What this means is that we can't just assign to
+ * stdout: we have to use freopen, which takes a path.
+ *
+ * There's no guarantee that the OS provides a /dev/fd/X alias for open
+ * file descriptors, so if that fails, fall back to the original log
+ * file path. We'd rather not do a path-based reopen because that
+ * exposes us to rename race attacks.
+ */
+ fp = freopen(fdpath, "a", stream);
+ if (!fp && errno == ENOENT && fdpath == _fdpath)
+ fp = freopen(path, "a", stream);
+ if (!fp) {
+ perror(fdpath);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Redirect stdout/stderr to a file, or return a mount-compatible error. */
+static int fuse4fs_capture_output(struct fuse4fs *ff, const char *path)
+{
+ int ret;
+ int fd;
+
+ /*
+ * First, open the log file path with system calls so that we can
+ * redirect the stdout/stderr file numbers (typically 1 and 2) to our
+ * logfile descriptor. We'd like to avoid allocating extra file
+ * objects in the kernel if we can because pos will be the same between
+ * stdout and stderr.
+ */
+ if (ff->logfd < 0) {
+ fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (fd < 0) {
+ perror(path);
+ return -1;
+ }
+
+ /*
+ * Save the newly opened fd in case we have to do this again in
+ * op_init.
+ */
+ ff->logfd = fd;
+ }
+
+ ret = dup2(ff->logfd, STDOUT_FILENO);
+ if (ret < 0) {
+ perror(path);
+ return -1;
+ }
+
+ ret = dup2(ff->logfd, STDERR_FILENO);
+ if (ret < 0) {
+ perror(path);
+ return -1;
+ }
+
+ /*
+ * Now that we've changed STD{OUT,ERR}_FILENO to be the log file, use
+ * freopen to make sure that std{out,err} (the C library abstractions)
+ * point to the STDXXX_FILENO because any of our library dependencies
+ * might decide to printf to one of those streams and we want to
+ * capture all output in the log.
+ */
+ ret = fuse4fs_freopen_stream(path, STDOUT_FILENO, stdout);
+ if (ret)
+ return ret;
+ ret = fuse4fs_freopen_stream(path, STDERR_FILENO, stderr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Set up debug and error logging files */
+static int fuse4fs_setup_logging(struct fuse4fs *ff)
+{
+ char *logfile = getenv("FUSE4FS_LOGFILE");
+ if (logfile)
+ return fuse4fs_capture_output(ff, logfile);
+
+ /* in kernel mode, try to log errors to the kernel log */
+ if (ff->kernel)
+ fuse4fs_capture_output(ff, "/dev/ttyprintk");
+
+ return 0;
+}
+
+static int fuse4fs_read_bitmaps(struct fuse4fs *ff)
+{
+ errcode_t err;
+
+ err = ext2fs_read_inode_bitmap(ff->fs);
+ if (err)
+ return translate_error(ff->fs, 0, err);
+
+ err = ext2fs_read_block_bitmap(ff->fs);
+ if (err)
+ return translate_error(ff->fs, 0, err);
+
+ return 0;
+}
+
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 17)
+static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
+ uint64_t flag)
+{
+ if (conn->capable & flag) {
+ conn->want |= flag;
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+static void *op_init(struct fuse_conn_info *conn,
+ struct fuse_config *cfg EXT2FS_ATTR((unused)))
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+
+ FUSE4FS_CHECK_CONTEXT_INIT(ff);
+
+ /*
+ * Configure logging a second time, because libfuse might have
+ * redirected std{out,err} as part of daemonization. If this fails,
+ * give up and move on.
+ */
+ fuse4fs_setup_logging(ff);
+ if (ff->logfd >= 0)
+ close(ff->logfd);
+ ff->logfd = -1;
+
+ fs = ff->fs;
+ dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
+#ifdef FUSE_CAP_IOCTL_DIR
+ fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR);
+#endif
+#ifdef FUSE_CAP_POSIX_ACL
+ if (ff->acl)
+ fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL);
+#endif
+#ifdef FUSE_CAP_CACHE_SYMLINKS
+ fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
+#endif
+#ifdef FUSE_CAP_NO_EXPORT_SUPPORT
+ fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
+#endif
+ conn->time_gran = 1;
+ cfg->use_ino = 1;
+ if (ff->debug)
+ cfg->debug = 1;
+ cfg->nullpath_ok = 1;
+
+ if (ff->opstate == F4OP_WRITABLE)
+ fuse4fs_read_bitmaps(ff);
+
+ /*
+ * Background threads must be started from op_init because libfuse
+ * might daemonize us in fuse_main() by forking, and threads are not
+ * conveyed to the new child process.
+ */
+ fuse4fs_mmp_start(ff);
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+ /*
+ * THIS MUST GO LAST!
+ *
+ * fuse_set_feature_flag in 3.17.0 has a strange bug: it sets feature
+ * flags in conn->want_ext, but not conn->want. Upon return to
+ * libfuse, the lower level library observes that want and want_ext
+ * have gotten out of sync, and refuses to mount. Therefore,
+ * synchronize the two. This bug went away in 3.17.3, but we're stuck
+ * with this forever because Debian trixie released with 3.17.2.
+ */
+ conn->want = conn->want_ext & 0xFFFFFFFF;
+#endif
+ return ff;
+}
+
+static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+{
+ struct ext2_inode_large inode;
+ dev_t fakedev = 0;
+ errcode_t err;
+ int ret = 0;
+ struct timespec tv;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
+ statbuf->st_dev = fakedev;
+ statbuf->st_ino = ino;
+ statbuf->st_mode = inode.i_mode;
+ statbuf->st_nlink = inode.i_links_count;
+ statbuf->st_uid = inode_uid(inode);
+ statbuf->st_gid = inode_gid(inode);
+ statbuf->st_size = EXT2_I_SIZE(&inode);
+ statbuf->st_blksize = fs->blocksize;
+ statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
+ EXT2_INODE(&inode));
+ EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+ statbuf->st_atim = tv;
+#else
+ statbuf->st_atime = tv.tv_sec;
+#endif
+ EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+ statbuf->st_mtim = tv;
+#else
+ statbuf->st_mtime = tv.tv_sec;
+#endif
+ EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+ statbuf->st_ctim = tv;
+#else
+ statbuf->st_ctime = tv.tv_sec;
+#endif
+ if (LINUX_S_ISCHR(inode.i_mode) ||
+ LINUX_S_ISBLK(inode.i_mode)) {
+ if (inode.i_block[0])
+ statbuf->st_rdev = inode.i_block[0];
+ else
+ statbuf->st_rdev = inode.i_block[1];
+ }
+
+ return ret;
+}
+
+static int __fuse4fs_file_ino(struct fuse4fs *ff, const char *path,
+ struct fuse_file_info *fp EXT2FS_ATTR((unused)),
+ ext2_ino_t *inop,
+ const char *func,
+ int line)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+
+ if (fp) {
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+
+ if (fh->ino == 0)
+ return -ESTALE;
+
+ *inop = fh->ino;
+ dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
+ return 0;
+ }
+
+ dbg_printf(ff, "%s: get path=%s\n", func, path);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
+ if (err)
+ return __translate_error(fs, 0, err, func, line);
+
+ return 0;
+}
+
+# define fuse4fs_file_ino(ff, path, fp, inop) \
+ __fuse4fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
+
+static int op_getattr(const char *path, struct stat *statbuf,
+ struct fuse_file_info *fi)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ ret = fuse4fs_file_ino(ff, path, fi, &ino);
+ if (ret)
+ goto out;
+ ret = stat_inode(fs, ino, statbuf);
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_readlink(const char *path, char *buf, size_t len)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ unsigned int got;
+ ext2_file_t file;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s\n", __func__, path);
+ fs = fuse4fs_start(ff);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ if (!LINUX_S_ISLNK(inode.i_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ len--;
+ if (inode.i_size < len)
+ len = inode.i_size;
+ if (ext2fs_is_fast_symlink(&inode))
+ memcpy(buf, (char *)inode.i_block, len);
+ else {
+ /* big/inline symlink */
+
+ err = ext2fs_file_open(fs, ino, 0, &file);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_read(file, buf, len, &got);
+ if (err)
+ ret = translate_error(fs, ino, err);
+ else if (got != len)
+ ret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+
+ err = ext2fs_file_close(file);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ }
+ buf[len] = 0;
+
+ if (fuse4fs_is_writeable(ff)) {
+ ret = update_atime(fs, ino);
+ if (ret)
+ goto out;
+ }
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
+ void **value, size_t *value_len)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_xattr_handle *h;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+ err = ext2fs_xattr_get(h, name, value, value_len);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+out_close:
+ err = ext2fs_xattrs_close(&h);
+ if (err && !ret)
+ ret = translate_error(fs, ino, err);
+ return ret;
+}
+
+static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
+ void *value, size_t valuelen)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_xattr_handle *h;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+ err = ext2fs_xattr_set(h, name, value, valuelen);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+out_close:
+ err = ext2fs_xattrs_close(&h);
+ if (err && !ret)
+ ret = translate_error(fs, ino, err);
+ return ret;
+}
+
+static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
+ ext2_ino_t child, mode_t mode)
+{
+ void *def;
+ size_t deflen;
+ int ret;
+
+ if (!ff->acl || S_ISDIR(mode))
+ return 0;
+
+ ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
+ &deflen);
+ switch (ret) {
+ case -ENODATA:
+ case -ENOENT:
+ /* no default acl */
+ return 0;
+ case 0:
+ break;
+ default:
+ return ret;
+ }
+
+ ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
+ ext2fs_free_mem(&def);
+ return ret;
+}
+
+static inline void fuse4fs_set_uid(struct ext2_inode_large *inode, uid_t uid)
+{
+ inode->i_uid = uid;
+ ext2fs_set_i_uid_high(*inode, uid >> 16);
+}
+
+static inline void fuse4fs_set_gid(struct ext2_inode_large *inode, gid_t gid)
+{
+ inode->i_gid = gid;
+ ext2fs_set_i_gid_high(*inode, gid >> 16);
+}
+
+static int fuse4fs_new_child_gid(struct fuse4fs *ff, ext2_ino_t parent,
+ gid_t *gid, int *parent_sgid)
+{
+ struct ext2_inode_large inode;
+ struct fuse_context *ctxt = fuse_get_context();
+ errcode_t err;
+
+ err = fuse4fs_read_inode(ff->fs, parent, &inode);
+ if (err)
+ return translate_error(ff->fs, parent, err);
+
+ if (inode.i_mode & S_ISGID) {
+ if (parent_sgid)
+ *parent_sgid = 1;
+ *gid = inode.i_gid;
+ } else {
+ if (parent_sgid)
+ *parent_sgid = 0;
+ *gid = ctxt->gid;
+ }
+
+ return 0;
+}
+
+/*
+ * Flush dirty data to disk if we're running in dirsync mode. If @flushed is a
+ * non-null pointer, this function sets @flushed to 1 if we decided to flush
+ * data, or 0 if not.
+ */
+static inline int fuse4fs_dirsync_flush(struct fuse4fs *ff, ext2_ino_t ino,
+ int *flushed)
+{
+ struct ext2_inode_large inode;
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+
+ if (ff->dirsync)
+ goto flush;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, 0, err);
+
+ if (inode.i_flags & EXT2_DIRSYNC_FL)
+ goto flush;
+
+ if (flushed)
+ *flushed = 0;
+ return 0;
+flush:
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ return translate_error(fs, 0, err);
+
+ if (flushed)
+ *flushed = 1;
+ return 0;
+}
+
+static void fuse4fs_set_extra_isize(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ ext2_filsys fs = ff->fs;
+ size_t extra = sizeof(struct ext2_inode_large) -
+ EXT2_GOOD_OLD_INODE_SIZE;
+
+ if (ext2fs_has_feature_extra_isize(fs->super)) {
+ dbg_printf(ff, "%s: ino=%u extra=%zu want=%u min=%u\n",
+ __func__, ino, extra, fs->super->s_want_extra_isize,
+ fs->super->s_min_extra_isize);
+
+ if (fs->super->s_want_extra_isize > extra)
+ extra = fs->super->s_want_extra_isize;
+ if (fs->super->s_min_extra_isize > extra)
+ extra = fs->super->s_min_extra_isize;
+ }
+
+ inode->i_extra_isize = extra;
+}
+
+static int op_mknod(const char *path, mode_t mode, dev_t dev)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ gid_t gid;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
+ (unsigned int)dev);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, parent, A_OK | W_OK);
+ if (ret)
+ goto out2;
+
+ *node_name = a;
+
+ if (LINUX_S_ISCHR(mode))
+ filetype = EXT2_FT_CHRDEV;
+ else if (LINUX_S_ISBLK(mode))
+ filetype = EXT2_FT_BLKDEV;
+ else if (LINUX_S_ISFIFO(mode))
+ filetype = EXT2_FT_FIFO;
+ else if (LINUX_S_ISSOCK(mode))
+ filetype = EXT2_FT_SOCK;
+ else {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ if (err)
+ goto out2;
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ dbg_printf(ff, "%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype | EXT2FS_LINK_EXPAND);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+
+ if (dev & ~0xFFFF)
+ inode.i_block[1] = dev;
+ else
+ inode.i_block[0] = dev;
+ inode.i_links_count = 1;
+ fuse4fs_set_extra_isize(ff, child, &inode);
+ fuse4fs_set_uid(&inode, ctxt->uid);
+ fuse4fs_set_gid(&inode, gid);
+
+ err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = fuse4fs_write_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+ ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ if (ret)
+ goto out2;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ fuse4fs_finish(ff, ret);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_mkdir(const char *path, mode_t mode)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ char *block;
+ blk64_t blk;
+ int ret = 0;
+ gid_t gid;
+ int parent_sgid;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, parent, A_OK | W_OK);
+ if (ret)
+ goto out2;
+
+ err = fuse4fs_new_child_gid(ff, parent, &gid, &parent_sgid);
+ if (err)
+ goto out2;
+
+ *node_name = a;
+
+ err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND,
+ node_name, NULL);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the dir */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf(ff, "%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+
+ err = fuse4fs_read_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ fuse4fs_set_extra_isize(ff, child, &inode);
+ fuse4fs_set_uid(&inode, ctxt->uid);
+ fuse4fs_set_gid(&inode, gid);
+ inode.i_mode = LINUX_S_IFDIR | (mode & ~S_ISUID);
+ if (parent_sgid)
+ inode.i_mode |= S_ISGID;
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+
+ err = fuse4fs_write_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ /* Rewrite the directory block checksum, having set i_generation */
+ if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
+ !ext2fs_has_feature_metadata_csum(fs->super))
+ goto out2;
+ err = ext2fs_new_dir_block(fs, child, parent, &block);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+ err = ext2fs_bmap2(fs, child, EXT2_INODE(&inode), NULL, 0, 0,
+ NULL, &blk);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+ err = ext2fs_write_dir_block4(fs, blk, block, 0, child);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+
+ ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ if (ret)
+ goto out3;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out3;
+
+out3:
+ ext2fs_free_mem(&block);
+out2:
+ fuse4fs_finish(ff, ret);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
+ ext2_ino_t *parent)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ ext2_ino_t dir;
+ char *filename = strdup(path);
+ char *base_name;
+ int ret;
+
+ base_name = strrchr(filename, '/');
+ if (base_name) {
+ *base_name++ = '\0';
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
+ &dir);
+ if (err) {
+ free(filename);
+ return translate_error(fs, 0, err);
+ }
+ } else {
+ dir = EXT2_ROOT_INO;
+ base_name = filename;
+ }
+
+ ret = check_inum_access(ff, dir, W_OK);
+ if (ret) {
+ free(filename);
+ return ret;
+ }
+
+ dbg_printf(ff, "%s: unlinking name=%s from dir=%d\n", __func__,
+ base_name, dir);
+ err = ext2fs_unlink(fs, dir, base_name, 0, 0);
+ free(filename);
+ if (err)
+ return translate_error(fs, dir, err);
+
+ ret = update_mtime(fs, dir, NULL);
+ if (ret)
+ return ret;
+
+ if (parent)
+ *parent = dir;
+ return 0;
+}
+
+static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_xattr_handle *h;
+ errcode_t err;
+ int ret = 0;
+
+ /*
+ * The xattr handle maintains its own private copy of the inode, so
+ * write ours to disk so that we can read it.
+ */
+ err = fuse4fs_write_inode(fs, ino, inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+ err = ext2fs_xattr_remove_all(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+out_close:
+ ext2fs_xattrs_close(&h);
+ if (ret)
+ return ret;
+
+ /* Now read the inode back in. */
+ err = fuse4fs_read_inode(fs, ino, inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ dbg_printf(ff, "%s: put ino=%d links=%d\n", __func__, ino,
+ inode.i_links_count);
+
+ if (S_ISDIR(inode.i_mode)) {
+ /*
+ * Caller should have checked that this is an empty directory
+ * before starting the unlink process. nlink is usually 2, but
+ * it could be 1 if this dir ever had more than 65000 subdirs.
+ * Zero the link count.
+ */
+ if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
+ return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+ inode.i_links_count = 0;
+ ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ } else {
+ /*
+ * Any other file type can be hardlinked, so all we need to do
+ * is decrement the nlink.
+ */
+ if (inode.i_links_count == 0)
+ return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+ inode.i_links_count--;
+ if (!inode.i_links_count)
+ ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ return ret;
+
+ /* Still linked? Leave it be. */
+ if (inode.i_links_count)
+ goto write_out;
+
+ if (ext2fs_has_feature_ea_inode(fs->super)) {
+ ret = remove_ea_inodes(ff, ino, &inode);
+ if (ret)
+ return ret;
+ }
+
+ /* Nobody holds this file; free its blocks! */
+ err = ext2fs_free_ext_attr(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(&inode))) {
+ err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), NULL,
+ 0, ~0ULL);
+ if (err)
+ return translate_error(fs, ino, err);
+ }
+
+ ext2fs_inode_alloc_stats2(fs, ino, -1,
+ LINUX_S_ISDIR(inode.i_mode));
+
+write_out:
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int __op_unlink(struct fuse4fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t parent, ino;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = check_inum_access(ff, ino, W_OK);
+ if (ret)
+ goto out;
+
+ ret = fuse4fs_unlink(ff, path, &parent);
+ if (ret)
+ goto out;
+
+ ret = remove_inode(ff, ino);
+ if (ret)
+ goto out;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+static int op_unlink(const char *path)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ int ret;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fuse4fs_start(ff);
+ ret = __op_unlink(ff, path);
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+struct rd_struct {
+ ext2_ino_t parent;
+ int empty;
+};
+
+static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)),
+ void *private)
+{
+ struct rd_struct *rds = (struct rd_struct *) private;
+
+ if (dirent->inode == 0)
+ return 0;
+ if (((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.'))
+ return 0;
+ if (((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') &&
+ (dirent->name[1] == '.')) {
+ rds->parent = dirent->inode;
+ return 0;
+ }
+ rds->empty = 0;
+ return 0;
+}
+
+static int __op_rmdir(struct fuse4fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t parent, child;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct rd_struct rds;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
+
+ ret = check_inum_access(ff, child, W_OK);
+ if (ret)
+ goto out;
+
+ rds.parent = 0;
+ rds.empty = 1;
+
+ err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out;
+ }
+
+ /* the kernel checks parent permissions before emptiness */
+ if (rds.parent == 0) {
+ ret = translate_error(fs, child, EXT2_ET_FILESYSTEM_CORRUPTED);
+ goto out;
+ }
+
+ ret = check_inum_access(ff, rds.parent, W_OK);
+ if (ret)
+ goto out;
+
+ if (rds.empty == 0) {
+ ret = -ENOTEMPTY;
+ goto out;
+ }
+
+ ret = fuse4fs_unlink(ff, path, &parent);
+ if (ret)
+ goto out;
+ ret = remove_inode(ff, child);
+ if (ret)
+ goto out;
+
+ if (rds.parent) {
+ dbg_printf(ff, "%s: decr dir=%d link count\n", __func__,
+ rds.parent);
+ err = fuse4fs_read_inode(fs, rds.parent, &inode);
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ ext2fs_dec_nlink(EXT2_INODE(&inode));
+ ret = update_mtime(fs, rds.parent, &inode);
+ if (ret)
+ goto out;
+ err = fuse4fs_write_inode(fs, rds.parent, &inode);
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ }
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+static int op_rmdir(const char *path)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ int ret;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fuse4fs_start(ff);
+ ret = __op_rmdir(ff, path);
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_symlink(const char *src, const char *dest)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ gid_t gid;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: symlink %s to %s\n", __func__, src, dest);
+ temp_path = strdup(dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, parent, A_OK | W_OK);
+ if (ret)
+ goto out2;
+
+ err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ if (err)
+ goto out2;
+
+ /* Create symlink */
+ err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ /* Update parent dir's mtime */
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the symlink */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf(ff, "%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
+ child, node_name, parent);
+
+ err = fuse4fs_read_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ fuse4fs_set_extra_isize(ff, child, &inode);
+ fuse4fs_set_uid(&inode, ctxt->uid);
+ fuse4fs_set_gid(&inode, gid);
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+
+ err = fuse4fs_write_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ fuse4fs_finish(ff, ret);
+out:
+ free(temp_path);
+ return ret;
+}
+
+struct update_dotdot {
+ ext2_ino_t new_dotdot;
+};
+
+static int update_dotdot_helper(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ struct update_dotdot *ud = priv_data;
+
+ if (ext2fs_dirent_name_len(dirent) == 2 &&
+ dirent->name[0] == '.' && dirent->name[1] == '.') {
+ dirent->inode = ud->new_dotdot;
+ return DIRENT_CHANGED | DIRENT_ABORT;
+ }
+
+ return 0;
+}
+
+/*
+ * If we're moving a directory, make sure that the new parent of that directory
+ * can handle the nlink bump.
+ */
+static int fuse4fs_check_from_dir_nlink(struct fuse4fs *ff, ext2_ino_t from_ino,
+ ext2_ino_t to_ino,
+ ext2_ino_t from_dir_ino,
+ ext2_ino_t to_dir_ino)
+{
+ struct ext2_inode_large inode;
+ errcode_t err;
+
+ err = fuse4fs_read_inode(ff->fs, from_ino, &inode);
+ if (err)
+ return translate_error(ff->fs, from_ino, err);
+
+ if (!S_ISDIR(inode.i_mode))
+ return 0;
+
+ if (to_ino != 0)
+ return 0;
+
+ if (to_dir_ino == from_dir_ino)
+ return 0;
+
+ err = fuse4fs_read_inode(ff->fs, to_dir_ino, &inode);
+ if (err)
+ return translate_error(ff->fs, from_ino, err);
+
+ if (ext2fs_dir_link_max(ff->fs, &inode))
+ return -EMLINK;
+
+ return 0;
+}
+
+static int op_rename(const char *from, const char *to,
+ unsigned int flags EXT2FS_ATTR((unused)))
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
+ char *temp_to = NULL, *temp_from = NULL;
+ char *cp, a;
+ struct ext2_inode inode;
+ struct update_dotdot ud;
+ int flushed = 0;
+ int ret = 0;
+
+ /* renameat2 is not supported */
+ if (flags)
+ return -ENOSYS;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 5)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+ if (err || from_ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+ if (err && err != EXT2_ET_FILE_NOT_FOUND) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ if (err == EXT2_ET_FILE_NOT_FOUND)
+ to_ino = 0;
+
+ /* Already the same file? */
+ if (to_ino != 0 && to_ino == from_ino) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = check_inum_access(ff, from_ino, W_OK);
+ if (ret)
+ goto out;
+
+ if (to_ino) {
+ ret = check_inum_access(ff, to_ino, W_OK);
+ if (ret)
+ goto out;
+ }
+
+ temp_to = strdup(to);
+ if (!temp_to) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ temp_from = strdup(from);
+ if (!temp_from) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+
+ /* Find parent dir of the source and check write access */
+ cp = strrchr(temp_from, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
+ &from_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (from_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, from_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ /* Find parent dir of the destination and check write access */
+ cp = strrchr(temp_to, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
+ &to_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (to_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, to_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ ret = fuse4fs_check_from_dir_nlink(ff, from_ino, to_ino, from_dir_ino,
+ to_dir_ino);
+ if (ret)
+ goto out2;
+
+ /* If the target exists, unlink it first */
+ if (to_ino != 0) {
+ err = ext2fs_read_inode(fs, to_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_ino, err);
+ goto out2;
+ }
+
+ dbg_printf(ff, "%s: unlinking %s ino=%d\n", __func__,
+ LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
+ to_ino);
+ if (LINUX_S_ISDIR(inode.i_mode))
+ ret = __op_rmdir(ff, to);
+ else
+ ret = __op_unlink(ff, to);
+ if (ret)
+ goto out2;
+ }
+
+ /* Get ready to do the move */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Link in the new file */
+ dbg_printf(ff, "%s: linking ino=%d/path=%s to dir=%d\n", __func__,
+ from_ino, cp + 1, to_dir_ino);
+ err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+
+ /* Update '..' pointer if dir */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ if (LINUX_S_ISDIR(inode.i_mode)) {
+ ud.new_dotdot = to_dir_ino;
+ dbg_printf(ff, "%s: updating .. entry for dir=%d\n", __func__,
+ to_dir_ino);
+ err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
+ update_dotdot_helper, &ud);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Decrease from_dir_ino's links_count */
+ dbg_printf(ff, "%s: moving linkcount from dir=%d to dir=%d\n",
+ __func__, from_dir_ino, to_dir_ino);
+ err = ext2fs_read_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+ ext2fs_dec_nlink(&inode);
+ err = ext2fs_write_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+
+ /* Increase to_dir_ino's links_count */
+ err = ext2fs_read_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ ext2fs_inc_nlink(fs, &inode);
+ err = ext2fs_write_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ }
+
+ /* Update timestamps */
+ ret = update_ctime(fs, from_ino, NULL);
+ if (ret)
+ goto out2;
+
+ ret = update_mtime(fs, to_dir_ino, NULL);
+ if (ret)
+ goto out2;
+
+ /* Remove the old file */
+ ret = fuse4fs_unlink(ff, from, NULL);
+ if (ret)
+ goto out2;
+
+ ret = fuse4fs_dirsync_flush(ff, from_dir_ino, &flushed);
+ if (ret)
+ goto out2;
+
+ if (from_dir_ino != to_dir_ino && !flushed) {
+ ret = fuse4fs_dirsync_flush(ff, to_dir_ino, NULL);
+ if (ret)
+ goto out2;
+ }
+
+out2:
+ free(temp_from);
+ free(temp_to);
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_link(const char *src, const char *dest)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ ext2_ino_t parent, ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: src=%s dest=%s\n", __func__, src, dest);
+ temp_path = strdup(dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ err = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, parent, A_OK | W_OK);
+ if (ret)
+ goto out2;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ if (ret)
+ goto out2;
+
+ if (ext2fs_dir_link_max(ff->fs, &inode)) {
+ ret = -EMLINK;
+ goto out2;
+ }
+
+ ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out2;
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ dbg_printf(ff, "%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, ino,
+ ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ fuse4fs_finish(ff, ret);
+out:
+ free(temp_path);
+ return ret;
+}
+
+/* Obtain group ids of the process that sent us a command(?) */
+static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ gid_t *array;
+ int nr = 32; /* nobody has more than 32 groups right? */
+ int ret;
+
+ do {
+ err = ext2fs_get_array(nr, sizeof(gid_t), &array);
+ if (err)
+ return translate_error(fs, 0, err);
+
+ ret = fuse_getgroups(nr, array);
+ if (ret < 0) {
+ /*
+ * If there's an error, we failed to find the group
+ * membership of the process that initiated the file
+ * change, either because the process went away or
+ * because there's no Linux procfs. Regardless of the
+ * cause, we return -ENOENT.
+ */
+ ext2fs_free_mem(&array);
+ return -ENOENT;
+ }
+
+ if (ret <= nr) {
+ *gids = array;
+ *nr_gids = ret;
+ return 0;
+ }
+
+ ext2fs_free_mem(&array);
+ nr = ret;
+ } while (0);
+
+ /* shut up gcc */
+ return -ENOMEM;
+}
+
+/*
+ * Is this file's group id in the set of groups associated with the process
+ * that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative
+ * errno.
+ */
+static int in_file_group(struct fuse_context *ctxt,
+ const struct ext2_inode_large *inode)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ gid_t *gids = NULL;
+ size_t i, nr_gids = 0;
+ gid_t gid = inode_gid(*inode);
+ int ret;
+
+ /* If the inode gid matches the process' primary group, we're done. */
+ if (ctxt->gid == gid)
+ return 1;
+
+ ret = get_req_groups(ff, &gids, &nr_gids);
+ if (ret == -ENOENT) {
+ /* magic return code for "could not get caller group info" */
+ return 0;
+ }
+ if (ret < 0)
+ return ret;
+
+ ret = 0;
+ for (i = 0; i < nr_gids; i++) {
+ if (gids[i] == gid) {
+ ret = 1;
+ break;
+ }
+ }
+
+ ext2fs_free_mem(&gids);
+ return ret;
+}
+
+static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ ret = fuse4fs_file_ino(ff, path, fi, &ino);
+ if (ret)
+ goto out;
+ dbg_printf(ff, "%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ if (ret)
+ goto out;
+
+ if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /*
+ * XXX: We should really check that the inode gid is not in /any/
+ * of the user's groups, but FUSE only tells us about the primary
+ * group.
+ */
+ if (!is_superuser(ff, ctxt)) {
+ ret = in_file_group(ctxt, &inode);
+ if (ret < 0)
+ goto out;
+
+ if (!ret)
+ mode &= ~S_ISGID;
+ }
+
+ inode.i_mode &= ~0xFFF;
+ inode.i_mode |= mode & 0xFFF;
+
+ dbg_printf(ff, "%s: path=%s new_mode=0%o ino=%d\n", __func__,
+ path, inode.i_mode, ino);
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_chown(const char *path, uid_t owner, gid_t group,
+ struct fuse_file_info *fi)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ ret = fuse4fs_file_ino(ff, path, fi, &ino);
+ if (ret)
+ goto out;
+ dbg_printf(ff, "%s: path=%s owner=%d group=%d ino=%d\n", __func__,
+ path, owner, group, ino);
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ if (ret)
+ goto out;
+
+ /* FUSE seems to feed us ~0 to mean "don't change" */
+ if (owner != (uid_t) ~0) {
+ /* Only root gets to change UID. */
+ if (want_check_owner(ff, ctxt) &&
+ !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
+ ret = -EPERM;
+ goto out;
+ }
+ fuse4fs_set_uid(&inode, owner);
+ }
+
+ if (group != (gid_t) ~0) {
+ /* Only root or the owner get to change GID. */
+ if (want_check_owner(ff, ctxt) &&
+ inode_uid(inode) != ctxt->uid) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /* XXX: We /should/ check group membership but FUSE */
+ fuse4fs_set_gid(&inode, group);
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int fuse4fs_punch_posteof(struct fuse4fs *ff, ext2_ino_t ino,
+ off_t new_size)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_inode_large inode;
+ blk64_t truncate_block = FUSE4FS_B_TO_FSB(ff, new_size);
+ errcode_t err;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), 0, truncate_block,
+ ~0ULL);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int fuse4fs_truncate(struct fuse4fs *ff, ext2_ino_t ino, off_t new_size)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_file_t file;
+ __u64 old_isize;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ err = ext2fs_file_get_lsize(file, &old_isize);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out_close;
+ }
+
+ dbg_printf(ff, "%s: ino=%u isize=0x%llx new_size=0x%llx\n", __func__,
+ ino,
+ (unsigned long long)old_isize,
+ (unsigned long long)new_size);
+
+ err = ext2fs_file_set_size2(file, new_size);
+ if (err)
+ ret = translate_error(fs, ino, err);
+
+out_close:
+ err = ext2fs_file_close(file);
+ if (ret)
+ return ret;
+ if (err)
+ return translate_error(fs, ino, err);
+
+ ret = update_mtime(fs, ino, NULL);
+ if (ret)
+ return ret;
+
+ /*
+ * Truncating to the current size is usually understood to mean that
+ * we should clear out post-EOF preallocations.
+ */
+ if (new_size == old_isize)
+ return fuse4fs_punch_posteof(ff, ino, new_size);
+
+ return 0;
+}
+
+static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fuse4fs_start(ff);
+ ret = fuse4fs_file_ino(ff, path, fi, &ino);
+ if (ret)
+ goto out;
+ dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
+
+ ret = check_inum_access(ff, ino, W_OK);
+ if (ret)
+ goto out;
+
+ ret = fuse4fs_truncate(ff, ino, len);
+ if (ret)
+ goto out;
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+#ifdef __linux__
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /*
+ * On Linux, execve will bleed __FMODE_EXEC into the file mode flags,
+ * and FUSE is more than happy to let that slip through.
+ */
+ if (kernel_flags & 0x20) {
+ *access_check = X_OK;
+ *e2fs_open_flags &= ~EXT2_FILE_WRITE;
+ }
+}
+#else
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /* empty */
+}
+#endif /* __linux__ */
+
+static int __op_open(struct fuse4fs *ff, const char *path,
+ struct fuse_file_info *fp)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct fuse4fs_file_handle *file;
+ int check = 0, ret = 0;
+
+ dbg_printf(ff, "%s: path=%s oflags=0o%o\n", __func__, path, fp->flags);
+ err = ext2fs_get_mem(sizeof(*file), &file);
+ if (err)
+ return translate_error(fs, 0, err);
+ file->magic = FUSE4FS_FILE_MAGIC;
+
+ file->open_flags = 0;
+ switch (fp->flags & O_ACCMODE) {
+ case O_RDONLY:
+ check = R_OK;
+ break;
+ case O_WRONLY:
+ check = W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ case O_RDWR:
+ check = R_OK | W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ }
+
+ /*
+ * If the caller wants to truncate the file, we need to ask for full
+ * write access even if the caller claims to be appending.
+ */
+ if ((fp->flags & O_APPEND) && !(fp->flags & O_TRUNC))
+ check |= A_OK;
+
+ detect_linux_executable_open(fp->flags, &check, &file->open_flags);
+
+ if (fp->flags & O_CREAT)
+ file->open_flags |= EXT2_FILE_CREATE;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
+ if (err || file->ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
+
+ ret = check_inum_access(ff, file->ino, check);
+ if (ret) {
+ /*
+ * In a regular (Linux) fs driver, the kernel will open
+ * binaries for reading if the user has --x privileges (i.e.
+ * execute without read). Since the kernel doesn't have any
+ * way to tell us if it's opening a file via execve, we'll
+ * just assume that allowing access is ok if asking for ro mode
+ * fails but asking for x mode succeeds. Of course we can
+ * also employ undocumented hacks (see above).
+ */
+ if (check == R_OK) {
+ ret = check_inum_access(ff, file->ino, X_OK);
+ if (ret)
+ goto out;
+ check = X_OK;
+ } else
+ goto out;
+ }
+
+ if (fp->flags & O_TRUNC) {
+ ret = fuse4fs_truncate(ff, file->ino, 0);
+ if (ret)
+ goto out;
+ }
+
+ file->check_flags = check;
+ fuse4fs_set_handle(fp, file);
+
+out:
+ if (ret)
+ ext2fs_free_mem(&file);
+ return ret;
+}
+
+static int op_open(const char *path, struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ int ret;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fuse4fs_start(ff);
+ ret = __op_open(ff, path, fp);
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
+ size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+ (unsigned long long)offset, len);
+ fs = fuse4fs_start(ff);
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_read(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fh->check_flags != X_OK && fuse4fs_is_writeable(ff)) {
+ ret = update_atime(fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ fuse4fs_finish(ff, ret);
+ return got ? (int) got : ret;
+}
+
+static int op_write(const char *path EXT2FS_ATTR((unused)),
+ const char *buf, size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+ (unsigned long long) offset, len);
+ fs = fuse4fs_start(ff);
+ if (!fuse4fs_is_writeable(ff)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_write(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_flush(efp);
+ if (err) {
+ got = 0;
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ fuse4fs_finish(ff, ret);
+ return got ? (int) got : ret;
+}
+
+static int op_release(const char *path EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ fs = fuse4fs_start(ff);
+
+ if ((fp->flags & O_SYNC) &&
+ fuse4fs_is_writeable(ff) &&
+ (fh->open_flags & EXT2_FILE_WRITE)) {
+ err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+
+ fp->fh = 0;
+ fuse4fs_finish(ff, ret);
+
+ ext2fs_free_mem(&fh);
+
+ return ret;
+}
+
+static int op_fsync(const char *path EXT2FS_ATTR((unused)),
+ int datasync EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ fs = fuse4fs_start(ff);
+ /* For now, flush everything, even if it's slow */
+ if (fuse4fs_is_writeable(ff) && fh->open_flags & EXT2_FILE_WRITE) {
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+static int op_statfs(const char *path EXT2FS_ATTR((unused)),
+ struct statvfs *buf)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ uint64_t fsid, *f;
+ blk64_t overhead, reserved, free;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s\n", __func__, path);
+ fs = fuse4fs_start(ff);
+ buf->f_bsize = fs->blocksize;
+ buf->f_frsize = 0;
+
+ if (ff->minixdf)
+ overhead = 0;
+ else
+ overhead = fs->desc_blocks +
+ (blk64_t)fs->group_desc_count *
+ (fs->inode_blocks_per_group + 2);
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (!reserved)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ free = ext2fs_free_blocks_count(fs->super);
+
+ buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+ buf->f_bfree = free;
+ if (free < reserved)
+ buf->f_bavail = 0;
+ else
+ buf->f_bavail = free - reserved;
+ buf->f_files = fs->super->s_inodes_count;
+ buf->f_ffree = fs->super->s_free_inodes_count;
+ buf->f_favail = fs->super->s_free_inodes_count;
+ f = (uint64_t *)fs->super->s_uuid;
+ fsid = *f;
+ f++;
+ fsid ^= *f;
+ buf->f_fsid = fsid;
+ buf->f_flag = 0;
+ if (ff->opstate != F4OP_WRITABLE)
+ buf->f_flag |= ST_RDONLY;
+ buf->f_namemax = EXT2_NAME_LEN;
+ fuse4fs_finish(ff, 0);
+
+ return 0;
+}
+
+static const char *valid_xattr_prefixes[] = {
+ "user.",
+ "trusted.",
+ "security.",
+ "gnu.",
+ "system.",
+};
+
+static int validate_xattr_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(valid_xattr_prefixes); i++) {
+ if (!strncmp(name, valid_xattr_prefixes[i],
+ strlen(valid_xattr_prefixes[i])))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int op_getxattr(const char *path, const char *key, char *value,
+ size_t len)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ void *ptr;
+ size_t plen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ if (!validate_xattr_name(key))
+ return -ENODATA;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+ ret = check_inum_access(ff, ino, R_OK);
+ if (ret)
+ goto out;
+
+ ret = __getxattr(ff, ino, key, &ptr, &plen);
+ if (ret)
+ goto out;
+
+ if (!len) {
+ ret = plen;
+ } else if (len < plen) {
+ ret = -ERANGE;
+ } else {
+ memcpy(value, ptr, plen);
+ ret = plen;
+ }
+
+ ext2fs_free_mem(&ptr);
+out:
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+static int count_buffer_space(char *name, char *value EXT2FS_ATTR((unused)),
+ size_t value_len EXT2FS_ATTR((unused)),
+ void *data)
+{
+ unsigned int *x = data;
+
+ *x = *x + strlen(name) + 1;
+ return 0;
+}
+
+static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
+ size_t value_len EXT2FS_ATTR((unused)), void *data)
+{
+ char **b = data;
+ size_t name_len = strlen(name);
+
+ memcpy(*b, name, name_len + 1);
+ *b = *b + name_len + 1;
+
+ return 0;
+}
+
+static int op_listxattr(const char *path, char *names, size_t len)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ unsigned int bufsz;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(ff, ino, R_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ /* Count buffer space needed for names */
+ bufsz = 0;
+ err = ext2fs_xattrs_iterate(h, count_buffer_space, &bufsz);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ if (len == 0) {
+ ret = bufsz;
+ goto out2;
+ } else if (len < bufsz) {
+ ret = -ERANGE;
+ goto out2;
+ }
+
+ /* Copy names out */
+ memset(names, 0, len);
+ err = ext2fs_xattrs_iterate(h, copy_names, &names);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+ ret = bufsz;
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err && !ret)
+ ret = translate_error(fs, ino, err);
+out:
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
+ const char *key, const char *value,
+ size_t len, int flags)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
+ return -EOPNOTSUPP;
+
+ if (!validate_xattr_name(key))
+ return -EINVAL;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+ ret = check_inum_access(ff, ino, W_OK);
+ if (ret == -EACCES) {
+ ret = -EPERM;
+ goto out;
+ } else if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ if (flags & (XATTR_CREATE | XATTR_REPLACE)) {
+ void *buf;
+ size_t buflen;
+
+ err = ext2fs_xattr_get(h, key, &buf, &buflen);
+ switch (err) {
+ case EXT2_ET_EA_KEY_NOT_FOUND:
+ if (flags & XATTR_REPLACE) {
+ ret = -ENODATA;
+ goto out2;
+ }
+ break;
+ case 0:
+ ext2fs_free_mem(&buf);
+ if (flags & XATTR_CREATE) {
+ ret = -EEXIST;
+ goto out2;
+ }
+ break;
+ default:
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+ }
+
+ err = ext2fs_xattr_set(h, key, value, len);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (!ret && err)
+ ret = translate_error(fs, ino, err);
+out:
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+static int op_removexattr(const char *path, const char *key)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ void *buf;
+ size_t buflen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ /*
+ * Once in a while libfuse gives us a no-name xattr to delete as part
+ * of clearing ACLs. Just pretend we cleared them.
+ */
+ if (key[0] == 0)
+ return 0;
+
+ if (!validate_xattr_name(key))
+ return -ENODATA;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+ ret = check_inum_access(ff, ino, W_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_get(h, key, &buf, &buflen);
+ switch (err) {
+ case EXT2_ET_EA_KEY_NOT_FOUND:
+ /*
+ * ACLs are special snowflakes that require a 0 return when
+ * the ACL never existed in the first place.
+ */
+ if (!strncmp(XATTR_SECURITY_PREFIX, key,
+ XATTR_SECURITY_PREFIX_LEN))
+ ret = 0;
+ else
+ ret = -ENODATA;
+ goto out2;
+ case 0:
+ ext2fs_free_mem(&buf);
+ break;
+ default:
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_remove(h, key);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err && !ret)
+ ret = translate_error(fs, ino, err);
+out:
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+struct readdir_iter {
+ void *buf;
+ ext2_filsys fs;
+ fuse_fill_dir_t func;
+
+ struct fuse4fs *ff;
+ enum fuse_readdir_flags flags;
+ unsigned int nr;
+ off_t startpos;
+ off_t dirpos;
+};
+
+static inline mode_t dirent_fmode(ext2_filsys fs,
+ const struct ext2_dir_entry *dirent)
+{
+ if (!ext2fs_has_feature_filetype(fs->super))
+ return 0;
+
+ switch (ext2fs_dirent_file_type(dirent)) {
+ case EXT2_FT_REG_FILE:
+ return S_IFREG;
+ case EXT2_FT_DIR:
+ return S_IFDIR;
+ case EXT2_FT_CHRDEV:
+ return S_IFCHR;
+ case EXT2_FT_BLKDEV:
+ return S_IFBLK;
+ case EXT2_FT_FIFO:
+ return S_IFIFO;
+ case EXT2_FT_SOCK:
+ return S_IFSOCK;
+ case EXT2_FT_SYMLINK:
+ return S_IFLNK;
+ }
+
+ return 0;
+}
+
+static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)), void *data)
+{
+ struct readdir_iter *i = data;
+ char namebuf[EXT2_NAME_LEN + 1];
+ struct stat stat = {
+ .st_ino = dirent->inode,
+ .st_mode = dirent_fmode(i->fs, dirent),
+ };
+ int ret;
+
+ i->dirpos++;
+ if (i->startpos >= i->dirpos)
+ return 0;
+
+ dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
+ i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+ dir,
+ i->nr++,
+ (unsigned long long)i->dirpos);
+
+ if (i->flags == FUSE_READDIR_PLUS) {
+ ret = stat_inode(i->fs, dirent->inode, &stat);
+ if (ret)
+ return DIRENT_ABORT;
+ }
+
+ memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
+ namebuf[dirent->name_len & 0xFF] = 0;
+ ret = i->func(i->buf, namebuf, &stat, i->dirpos , 0);
+ if (ret)
+ return DIRENT_ABORT;
+
+ return 0;
+}
+
+static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
+ fuse_fill_dir_t fill_func, off_t offset,
+ struct fuse_file_info *fp, enum fuse_readdir_flags flags)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ errcode_t err;
+ struct readdir_iter i = {
+ .ff = ff,
+ .dirpos = 0,
+ .startpos = offset,
+ .flags = flags,
+ };
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d offset=0x%llx\n", __func__, fh->ino,
+ (unsigned long long)offset);
+ i.fs = fuse4fs_start(ff);
+ i.buf = buf;
+ i.func = fill_func;
+ err = ext2fs_dir_iterate2(i.fs, fh->ino, 0, NULL, op_readdir_iter, &i);
+ if (err) {
+ ret = translate_error(i.fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fuse4fs_is_writeable(ff)) {
+ ret = update_atime(i.fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_access(const char *path, int mask)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
+ fs = fuse4fs_start(ff);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = check_inum_access(ff, ino, mask);
+ if (ret)
+ goto out;
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ gid_t gid;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ fs = fuse4fs_start(ff);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(ff, parent, A_OK | W_OK);
+ if (ret)
+ goto out2;
+
+ err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ if (err)
+ goto out2;
+
+ *node_name = a;
+
+ filetype = ext2_file_type(mode);
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ dbg_printf(ff, "%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype | EXT2FS_LINK_EXPAND);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+ inode.i_links_count = 1;
+ fuse4fs_set_extra_isize(ff, child, &inode);
+ fuse4fs_set_uid(&inode, ctxt->uid);
+ fuse4fs_set_gid(&inode, gid);
+ if (ext2fs_has_feature_extents(fs->super)) {
+ ext2_extent_handle_t handle;
+
+ inode.i_flags &= ~EXT4_EXTENTS_FL;
+ ret = ext2fs_extent_open2(fs, child,
+ EXT2_INODE(&inode), &handle);
+ if (ret) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_extent_free(handle);
+ }
+
+ err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = fuse4fs_write_inode(fs, child, &inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+ ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ if (ret)
+ goto out2;
+
+ fp->flags &= ~O_TRUNC;
+ ret = __op_open(ff, path, fp);
+ if (ret)
+ goto out2;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ fuse4fs_finish(ff, ret);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_utimens(const char *path, const struct timespec ctv[2],
+ struct fuse_file_info *fi)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct timespec tv[2];
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int access = W_OK;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ ret = fuse4fs_file_ino(ff, path, fi, &ino);
+ if (ret)
+ goto out;
+ dbg_printf(ff, "%s: ino=%d atime=%lld.%ld mtime=%lld.%ld\n", __func__,
+ ino,
+ (long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
+ (long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
+
+ /*
+ * ext4 allows timestamp updates of append-only files but only if we're
+ * setting to current time
+ */
+ if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
+ access |= A_OK;
+ ret = check_inum_access(ff, ino, access);
+ if (ret)
+ goto out;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ tv[0] = ctv[0];
+ tv[1] = ctv[1];
+#ifdef UTIME_NOW
+ if (tv[0].tv_nsec == UTIME_NOW)
+ get_now(tv);
+ if (tv[1].tv_nsec == UTIME_NOW)
+ get_now(tv + 1);
+#endif /* UTIME_NOW */
+#ifdef UTIME_OMIT
+ if (tv[0].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_atime, &tv[0], &inode);
+ if (tv[1].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_mtime, &tv[1], &inode);
+#endif /* UTIME_OMIT */
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+#define FUSE4FS_MODIFIABLE_IFLAGS \
+ (EXT2_FL_USER_MODIFIABLE & ~(EXT4_EXTENTS_FL | EXT4_CASEFOLD_FL | \
+ EXT3_JOURNAL_DATA_FL))
+
+static inline int set_iflags(struct ext2_inode_large *inode, __u32 iflags)
+{
+ if ((inode->i_flags ^ iflags) & ~FUSE4FS_MODIFIABLE_IFLAGS)
+ return -EINVAL;
+
+ inode->i_flags = (inode->i_flags & ~FUSE4FS_MODIFIABLE_IFLAGS) |
+ (iflags & FUSE4FS_MODIFIABLE_IFLAGS);
+ return 0;
+}
+
+#ifdef SUPPORT_I_FLAGS
+static int ioctl_getflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+ return 0;
+}
+
+static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 flags = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ return -EPERM;
+
+ ret = set_iflags(&inode, flags);
+ if (ret)
+ return ret;
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = fuse4fs_write_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int ioctl_getversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_generation;
+ return 0;
+}
+
+static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 generation = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ return -EPERM;
+
+ inode.i_generation = generation;
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = fuse4fs_write_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+#endif /* SUPPORT_I_FLAGS */
+
+#ifdef FS_IOC_FSGETXATTR
+static __u32 iflags_to_fsxflags(__u32 iflags)
+{
+ __u32 xflags = 0;
+
+ if (iflags & FS_SYNC_FL)
+ xflags |= FS_XFLAG_SYNC;
+ if (iflags & FS_IMMUTABLE_FL)
+ xflags |= FS_XFLAG_IMMUTABLE;
+ if (iflags & FS_APPEND_FL)
+ xflags |= FS_XFLAG_APPEND;
+ if (iflags & FS_NODUMP_FL)
+ xflags |= FS_XFLAG_NODUMP;
+ if (iflags & FS_NOATIME_FL)
+ xflags |= FS_XFLAG_NOATIME;
+ if (iflags & FS_DAX_FL)
+ xflags |= FS_XFLAG_DAX;
+ if (iflags & FS_PROJINHERIT_FL)
+ xflags |= FS_XFLAG_PROJINHERIT;
+ return xflags;
+}
+
+static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct fsxattr *fsx = data;
+ unsigned int inode_size;
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ memset(fsx, 0, sizeof(*fsx));
+ inode_size = EXT2_GOOD_OLD_INODE_SIZE + inode.i_extra_isize;
+ if (ext2fs_inode_includes(inode_size, i_projid))
+ fsx->fsx_projid = inode_projid(inode);
+ fsx->fsx_xflags = iflags_to_fsxflags(inode.i_flags);
+ return 0;
+}
+
+static __u32 fsxflags_to_iflags(__u32 xflags)
+{
+ __u32 iflags = 0;
+
+ if (xflags & FS_XFLAG_IMMUTABLE)
+ iflags |= FS_IMMUTABLE_FL;
+ if (xflags & FS_XFLAG_APPEND)
+ iflags |= FS_APPEND_FL;
+ if (xflags & FS_XFLAG_SYNC)
+ iflags |= FS_SYNC_FL;
+ if (xflags & FS_XFLAG_NOATIME)
+ iflags |= FS_NOATIME_FL;
+ if (xflags & FS_XFLAG_NODUMP)
+ iflags |= FS_NODUMP_FL;
+ if (xflags & FS_XFLAG_DAX)
+ iflags |= FS_DAX_FL;
+ if (xflags & FS_XFLAG_PROJINHERIT)
+ iflags |= FS_PROJINHERIT_FL;
+ return iflags;
+}
+
+#define FUSE4FS_MODIFIABLE_XFLAGS (FS_XFLAG_IMMUTABLE | \
+ FS_XFLAG_APPEND | \
+ FS_XFLAG_SYNC | \
+ FS_XFLAG_NOATIME | \
+ FS_XFLAG_NODUMP | \
+ FS_XFLAG_PROJINHERIT)
+
+#define FUSE4FS_MODIFIABLE_IXFLAGS (FS_IMMUTABLE_FL | \
+ FS_APPEND_FL | \
+ FS_SYNC_FL | \
+ FS_NOATIME_FL | \
+ FS_NODUMP_FL | \
+ FS_PROJINHERIT_FL)
+
+static inline int set_xflags(struct ext2_inode_large *inode, __u32 xflags)
+{
+ __u32 iflags;
+
+ if (xflags & ~FUSE4FS_MODIFIABLE_XFLAGS)
+ return -EINVAL;
+
+ iflags = fsxflags_to_iflags(xflags);
+ inode->i_flags = (inode->i_flags & ~FUSE4FS_MODIFIABLE_IXFLAGS) |
+ (iflags & FUSE4FS_MODIFIABLE_IXFLAGS);
+ return 0;
+}
+
+static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fsxattr *fsx = data;
+ unsigned int inode_size;
+
+ dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ return -EPERM;
+
+ ret = set_xflags(&inode, fsx->fsx_xflags);
+ if (ret)
+ return ret;
+
+ inode_size = EXT2_GOOD_OLD_INODE_SIZE + inode.i_extra_isize;
+ if (ext2fs_inode_includes(inode_size, i_projid))
+ inode.i_projid = fsx->fsx_projid;
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = fuse4fs_write_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+#endif /* FS_IOC_FSGETXATTR */
+
+#ifdef FITRIM
+static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ ext2_filsys fs = ff->fs;
+ struct fstrim_range *fr = data;
+ blk64_t start, end, max_blocks, b, cleared, minlen;
+ blk64_t max_blks = ext2fs_blocks_count(fs->super);
+ errcode_t err = 0;
+
+ if (!fuse4fs_is_writeable(ff))
+ return -EROFS;
+
+ start = FUSE4FS_B_TO_FSBT(ff, fr->start);
+ if (fr->len == -1ULL)
+ end = -1ULL;
+ else
+ end = FUSE4FS_B_TO_FSBT(ff, fr->start + fr->len - 1);
+ minlen = FUSE4FS_B_TO_FSBT(ff, fr->minlen);
+
+ if (EXT2FS_NUM_B2C(fs, minlen) > EXT2_CLUSTERS_PER_GROUP(fs->super) ||
+ start >= max_blks ||
+ fr->len < fs->blocksize)
+ return -EINVAL;
+
+ dbg_printf(ff, "%s: start=0x%llx end=0x%llx minlen=0x%llx\n", __func__,
+ start, end, minlen);
+
+ if (start < fs->super->s_first_data_block)
+ start = fs->super->s_first_data_block;
+
+ if (end < fs->super->s_first_data_block)
+ end = fs->super->s_first_data_block;
+ if (end >= ext2fs_blocks_count(fs->super))
+ end = ext2fs_blocks_count(fs->super) - 1;
+
+ cleared = 0;
+ max_blocks = FUSE4FS_B_TO_FSBT(ff, 2048ULL * 1024 * 1024);
+
+ fr->len = 0;
+ while (start <= end) {
+ err = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ start, end, &start);
+ switch (err) {
+ case 0:
+ break;
+ case ENOENT:
+ /* no free blocks found, so we're done */
+ err = 0;
+ goto out;
+ default:
+ return translate_error(fs, fh->ino, err);
+ }
+
+ b = start + max_blocks < end ? start + max_blocks : end;
+ err = ext2fs_find_first_set_block_bitmap2(fs->block_map,
+ start, b, &b);
+ switch (err) {
+ case 0:
+ break;
+ case ENOENT:
+ /*
+ * No free blocks found between start and b; discard
+ * the entire range.
+ */
+ err = 0;
+ break;
+ default:
+ return translate_error(fs, fh->ino, err);
+ }
+
+ if (b - start >= minlen) {
+ err = io_channel_discard(fs->io, start, b - start);
+ if (err == EBUSY) {
+ /*
+ * Apparently dm-thinp can return EBUSY when
+ * it's too busy deallocating thinp units to
+ * deallocate more. Swallow these errors.
+ */
+ err = 0;
+ }
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ cleared += b - start;
+ fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+ }
+ start = b + 1;
+ }
+
+out:
+ fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+ dbg_printf(ff, "%s: len=%llu err=%ld\n", __func__, fr->len, err);
+ return err;
+}
+#endif /* FITRIM */
+
+#ifndef EXT4_IOC_SHUTDOWN
+# define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)
+#endif
+
+static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+ void *data)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ ext2_filsys fs = ff->fs;
+
+ if (!is_superuser(ff, ctxt))
+ return -EPERM;
+
+ err_printf(ff, "%s.\n", _("shut down requested"));
+
+ fuse4fs_mmp_cancel(ff);
+
+ /*
+ * EXT4_IOC_SHUTDOWN inherited the inverted polarity on the ioctl
+ * direction from XFS. Unfortunately, that means we can't implement
+ * any of the flags. Flush whatever is dirty and shut down.
+ */
+ if (ff->opstate == F4OP_WRITABLE)
+ ext2fs_flush2(fs, 0);
+ ff->opstate = F4OP_SHUTDOWN;
+ fs->flags &= ~EXT2_FLAG_RW;
+
+ return 0;
+}
+
+static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
+ unsigned int cmd,
+ void *arg EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp,
+ unsigned int flags EXT2FS_ATTR((unused)), void *data)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ fuse4fs_start(ff);
+ switch ((unsigned long) cmd) {
+#ifdef SUPPORT_I_FLAGS
+ case EXT2_IOC_GETFLAGS:
+ ret = ioctl_getflags(ff, fh, data);
+ break;
+ case EXT2_IOC_SETFLAGS:
+ ret = ioctl_setflags(ff, fh, data);
+ break;
+ case EXT2_IOC_GETVERSION:
+ ret = ioctl_getversion(ff, fh, data);
+ break;
+ case EXT2_IOC_SETVERSION:
+ ret = ioctl_setversion(ff, fh, data);
+ break;
+#endif
+#ifdef FS_IOC_FSGETXATTR
+ case FS_IOC_FSGETXATTR:
+ ret = ioctl_fsgetxattr(ff, fh, data);
+ break;
+ case FS_IOC_FSSETXATTR:
+ ret = ioctl_fssetxattr(ff, fh, data);
+ break;
+#endif
+#ifdef FITRIM
+ case FITRIM:
+ ret = ioctl_fitrim(ff, fh, data);
+ break;
+#endif
+ case EXT4_IOC_SHUTDOWN:
+ ret = ioctl_shutdown(ff, fh, data);
+ break;
+ default:
+ dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
+ ret = -ENOTTY;
+ }
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+
+static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
+ uint64_t *idx)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ fs = fuse4fs_start(ff);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+
+ err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+#ifdef SUPPORT_FALLOCATE
+static int fuse4fs_allocate_range(struct fuse4fs *ff,
+ struct fuse4fs_file_handle *fh, int mode,
+ off_t offset, off_t len)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ __u64 fsize;
+ errcode_t err;
+ int flags;
+
+ start = FUSE4FS_B_TO_FSBT(ff, offset);
+ end = FUSE4FS_B_TO_FSBT(ff, offset + len - 1);
+ dbg_printf(ff, "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+ __func__, fh->ino, mode,
+ (unsigned long long)offset,
+ (unsigned long long)len,
+ (unsigned long long)start,
+ (unsigned long long)end);
+ if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
+ return -ENOSPC;
+
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return err;
+ fsize = EXT2_I_SIZE(&inode);
+
+ /* Indirect files do not support unwritten extents */
+ if (!(inode.i_flags & EXT4_EXTENTS_FL))
+ return -EOPNOTSUPP;
+
+ /* Allocate a bunch of blocks */
+ flags = (mode & FL_KEEP_SIZE_FLAG ? 0 :
+ EXT2_FALLOCATE_INIT_BEYOND_EOF);
+ err = ext2fs_fallocate(fs, flags, fh->ino,
+ EXT2_INODE(&inode),
+ ~0ULL, start, end - start + 1);
+ if (err && err != EXT2_ET_BLOCK_ALLOC_FAIL)
+ return translate_error(fs, fh->ino, err);
+
+ /* Update i_size */
+ if (!(mode & FL_KEEP_SIZE_FLAG)) {
+ if ((__u64) offset + len > fsize) {
+ err = ext2fs_inode_size_set(fs,
+ EXT2_INODE(&inode),
+ offset + len);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = fuse4fs_write_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return err;
+}
+
+static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode,
+ off_t offset, off_t len, char **buf)
+{
+ ext2_filsys fs = ff->fs;
+ blk64_t blk;
+ off_t residue = FUSE4FS_OFF_IN_FSB(ff, offset);
+ int retflags;
+ errcode_t err;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), *buf, 0,
+ FUSE4FS_B_TO_FSBT(ff, offset), &retflags, &blk);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ err = io_channel_read_blk64(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+
+ dbg_printf(ff, "%s: ino=%d offset=0x%llx len=0x%llx\n",
+ __func__, ino,
+ (unsigned long long)offset + residue,
+ (unsigned long long)len);
+ memset(*buf + residue, 0, len);
+
+ return io_channel_write_blk64(fs->io, blk, 1, *buf);
+}
+
+static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ int clean_before, char **buf)
+{
+ ext2_filsys fs = ff->fs;
+ blk64_t blk;
+ int retflags;
+ off_t residue;
+ errcode_t err;
+
+ residue = FUSE4FS_OFF_IN_FSB(ff, offset);
+ if (residue == 0)
+ return 0;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), *buf, 0,
+ FUSE4FS_B_TO_FSBT(ff, offset), &retflags, &blk);
+ if (err)
+ return err;
+
+ err = io_channel_read_blk64(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ if (clean_before) {
+ dbg_printf(ff, "%s: ino=%d before offset=0x%llx len=0x%llx\n",
+ __func__, ino,
+ (unsigned long long)offset,
+ (unsigned long long)residue);
+ memset(*buf, 0, residue);
+ } else {
+ dbg_printf(ff, "%s: ino=%d after offset=0x%llx len=0x%llx\n",
+ __func__, ino,
+ (unsigned long long)offset,
+ (unsigned long long)fs->blocksize - residue);
+ memset(*buf + residue, 0, fs->blocksize - residue);
+ }
+
+ return io_channel_write_blk64(fs->io, blk, 1, *buf);
+}
+
+static int fuse4fs_punch_range(struct fuse4fs *ff,
+ struct fuse4fs_file_handle *fh, int mode,
+ off_t offset, off_t len)
+{
+ ext2_filsys fs = ff->fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ errcode_t err;
+ char *buf = NULL;
+
+ /* kernel ext4 punch requires this flag to be set */
+ if (!(mode & FL_KEEP_SIZE_FLAG))
+ return -EINVAL;
+
+ /*
+ * Unmap out all full blocks in the middle of the range being punched.
+ * The start of the unmap range should be the first byte of the first
+ * fsblock that starts within the range. The end of the range should
+ * be the next byte after the last fsblock to end in the range.
+ */
+ start = FUSE4FS_B_TO_FSBT(ff, round_up(offset, fs->blocksize));
+ end = FUSE4FS_B_TO_FSBT(ff, round_down(offset + len, fs->blocksize));
+
+ dbg_printf(ff,
+ "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+ __func__, fh->ino, mode,
+ (unsigned long long)offset,
+ (unsigned long long)len,
+ (unsigned long long)start,
+ (unsigned long long)end);
+
+ err = fuse4fs_read_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /*
+ * Indirect files do not support unwritten extents, which means we
+ * can't support zero range. Punch goes first in zero-range, which
+ * is why the check is here.
+ */
+ if ((mode & FL_ZERO_RANGE_FLAG) && !(inode.i_flags & EXT4_EXTENTS_FL))
+ return -EOPNOTSUPP;
+
+ /* Zero everything before the first block and after the last block */
+ if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
+ err = clean_block_middle(ff, fh->ino, &inode, offset,
+ len, &buf);
+ else {
+ err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
+ if (!err)
+ err = clean_block_edge(ff, fh->ino, &inode,
+ offset + len, 1, &buf);
+ }
+ if (buf)
+ ext2fs_free_mem(&buf);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /*
+ * Unmap full blocks in the middle, which is to say that start - end
+ * must be at least one fsblock. ext2fs_punch takes a closed interval
+ * as its argument, so we pass [start, end - 1].
+ */
+ if (start < end) {
+ err = ext2fs_punch(fs, fh->ino, EXT2_INODE(&inode),
+ NULL, start, end - 1);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = fuse4fs_write_inode(fs, fh->ino, &inode);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int fuse4fs_zero_range(struct fuse4fs *ff,
+ struct fuse4fs_file_handle *fh, int mode,
+ off_t offset, off_t len)
+{
+ int ret = fuse4fs_punch_range(ff, fh, mode | FL_KEEP_SIZE_FLAG, offset,
+ len);
+
+ if (!ret)
+ ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
+ return ret;
+}
+
+static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
+ off_t offset, off_t len,
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ int ret;
+
+ /* Catch unknown flags */
+ if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
+ return -EOPNOTSUPP;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ fuse4fs_start(ff);
+ if (!fuse4fs_is_writeable(ff)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ dbg_printf(ff, "%s: ino=%d mode=0x%x start=0x%llx end=0x%llx\n", __func__,
+ fh->ino, mode,
+ (unsigned long long)offset,
+ (unsigned long long)offset + len);
+
+ if (mode & FL_ZERO_RANGE_FLAG)
+ ret = fuse4fs_zero_range(ff, fh, mode, offset, len);
+ else if (mode & FL_PUNCH_HOLE_FLAG)
+ ret = fuse4fs_punch_range(ff, fh, mode, offset, len);
+ else
+ ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
+out:
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+#endif /* SUPPORT_FALLOCATE */
+
+static struct fuse_operations fs_ops = {
+ .init = op_init,
+ .destroy = op_destroy,
+ .getattr = op_getattr,
+ .readlink = op_readlink,
+ .mknod = op_mknod,
+ .mkdir = op_mkdir,
+ .unlink = op_unlink,
+ .rmdir = op_rmdir,
+ .symlink = op_symlink,
+ .rename = op_rename,
+ .link = op_link,
+ .chmod = op_chmod,
+ .chown = op_chown,
+ .truncate = op_truncate,
+ .open = op_open,
+ .read = op_read,
+ .write = op_write,
+ .statfs = op_statfs,
+ .release = op_release,
+ .fsync = op_fsync,
+ .setxattr = op_setxattr,
+ .getxattr = op_getxattr,
+ .listxattr = op_listxattr,
+ .removexattr = op_removexattr,
+ .opendir = op_open,
+ .readdir = op_readdir,
+ .releasedir = op_release,
+ .fsyncdir = op_fsync,
+ .access = op_access,
+ .create = op_create,
+ .utimens = op_utimens,
+ .bmap = op_bmap,
+#ifdef SUPERFLUOUS
+ .lock = op_lock,
+ .poll = op_poll,
+#endif
+ .ioctl = op_ioctl,
+#ifdef SUPPORT_FALLOCATE
+ .fallocate = op_fallocate,
+#endif
+};
+
+static int get_random_bytes(void *p, size_t sz)
+{
+ int fd;
+ ssize_t r;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ perror("/dev/urandom");
+ return 0;
+ }
+
+ r = read(fd, p, sz);
+
+ close(fd);
+ return (size_t) r == sz;
+}
+
+enum {
+ FUSE4FS_IGNORED,
+ FUSE4FS_VERSION,
+ FUSE4FS_HELP,
+ FUSE4FS_HELPFULL,
+ FUSE4FS_CACHE_SIZE,
+ FUSE4FS_DIRSYNC,
+ FUSE4FS_ERRORS_BEHAVIOR,
+};
+
+#define FUSE4FS_OPT(t, p, v) { t, offsetof(struct fuse4fs, p), v }
+
+static struct fuse_opt fuse4fs_opts[] = {
+ FUSE4FS_OPT("ro", ro, 1),
+ FUSE4FS_OPT("rw", ro, 0),
+ FUSE4FS_OPT("minixdf", minixdf, 1),
+ FUSE4FS_OPT("bsddf", minixdf, 0),
+ FUSE4FS_OPT("fakeroot", fakeroot, 1),
+ FUSE4FS_OPT("fuse4fs_debug", debug, 1),
+ FUSE4FS_OPT("no_default_opts", no_default_opts, 1),
+ FUSE4FS_OPT("norecovery", norecovery, 1),
+ FUSE4FS_OPT("noload", norecovery, 1),
+ FUSE4FS_OPT("offset=%lu", offset, 0),
+ FUSE4FS_OPT("oom_score_adj=%d", oom_score_adj, -500),
+ FUSE4FS_OPT("kernel", kernel, 1),
+ FUSE4FS_OPT("directio", directio, 1),
+ FUSE4FS_OPT("acl", acl, 1),
+ FUSE4FS_OPT("noacl", acl, 0),
+ FUSE4FS_OPT("lockfile=%s", lockfile, 0),
+#ifdef HAVE_CLOCK_MONOTONIC
+ FUSE4FS_OPT("timing", timing, 1),
+#endif
+
+ FUSE_OPT_KEY("user_xattr", FUSE4FS_IGNORED),
+ FUSE_OPT_KEY("noblock_validity", FUSE4FS_IGNORED),
+ FUSE_OPT_KEY("nodelalloc", FUSE4FS_IGNORED),
+ FUSE_OPT_KEY("cache_size=%s", FUSE4FS_CACHE_SIZE),
+ FUSE_OPT_KEY("dirsync", FUSE4FS_DIRSYNC),
+ FUSE_OPT_KEY("errors=%s", FUSE4FS_ERRORS_BEHAVIOR),
+
+ FUSE_OPT_KEY("-V", FUSE4FS_VERSION),
+ FUSE_OPT_KEY("--version", FUSE4FS_VERSION),
+ FUSE_OPT_KEY("-h", FUSE4FS_HELP),
+ FUSE_OPT_KEY("--help", FUSE4FS_HELP),
+ FUSE_OPT_KEY("--helpfull", FUSE4FS_HELPFULL),
+ FUSE_OPT_END
+};
+
+
+static int fuse4fs_opt_proc(void *data, const char *arg,
+ int key, struct fuse_args *outargs)
+{
+ struct fuse4fs *ff = data;
+
+ switch (key) {
+ case FUSE4FS_DIRSYNC:
+ ff->dirsync = 1;
+ /* pass through to libfuse */
+ return 1;
+ case FUSE_OPT_KEY_NONOPT:
+ if (!ff->device) {
+ ff->device = strdup(arg);
+ return 0;
+ }
+ return 1;
+ case FUSE4FS_CACHE_SIZE:
+ ff->cache_size = parse_num_blocks2(arg + 11, -1);
+ if (ff->cache_size < 1 || ff->cache_size > INT32_MAX) {
+ fprintf(stderr, "%s: %s\n", arg,
+ _("cache size must be between 1 block and 2GB."));
+ return -1;
+ }
+
+ /* do not pass through to libfuse */
+ return 0;
+ case FUSE4FS_ERRORS_BEHAVIOR:
+ if (strcmp(arg + 7, "continue") == 0)
+ ff->errors_behavior = EXT2_ERRORS_CONTINUE;
+ else if (strcmp(arg + 7, "remount-ro") == 0)
+ ff->errors_behavior = EXT2_ERRORS_RO;
+ else if (strcmp(arg + 7, "panic") == 0)
+ ff->errors_behavior = EXT2_ERRORS_PANIC;
+ else {
+ fprintf(stderr, "%s: %s\n", arg,
+ _("unknown errors behavior."));
+ return -1;
+ }
+
+ /* do not pass through to libfuse */
+ return 0;
+ case FUSE4FS_IGNORED:
+ return 0;
+ case FUSE4FS_HELP:
+ case FUSE4FS_HELPFULL:
+ fprintf(stderr,
+ "usage: %s device/image mountpoint [options]\n"
+ "\n"
+ "general options:\n"
+ " -o opt,[opt...] mount options\n"
+ " -h --help print help\n"
+ " -V --version print version\n"
+ "\n"
+ "fuse4fs options:\n"
+ " -o errors=panic dump core on error\n"
+ " -o minixdf minix-style df\n"
+ " -o fakeroot pretend to be root for permission checks\n"
+ " -o no_default_opts do not include default fuse options\n"
+ " -o offset=<bytes> similar to mount -o offset=<bytes>, mount the partition starting at <bytes>\n"
+ " -o norecovery don't replay the journal\n"
+ " -o fuse4fs_debug enable fuse4fs debugging\n"
+ " -o lockfile=<file> file to show that fuse is still using the file system image\n"
+ " -o kernel run this as if it were the kernel, which sets:\n"
+ " allow_others,default_permissions,suid,dev\n"
+ " -o directio use O_DIRECT to read and write the disk\n"
+ " -o cache_size=N[KMG] use a disk cache of this size\n"
+ " -o errors= behavior when an error is encountered:\n"
+ " continue|remount-ro|panic\n"
+ "\n",
+ outargs->argv[0]);
+ if (key == FUSE4FS_HELPFULL) {
+ fuse_opt_add_arg(outargs, "-h");
+ fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ } else {
+ fprintf(stderr, "Try --helpfull to get a list of "
+ "all flags, including the FUSE options.\n");
+ }
+ exit(1);
+
+ case FUSE4FS_VERSION:
+ fprintf(stderr, "fuse4fs %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+ fuse_opt_add_arg(outargs, "--version");
+ fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ exit(0);
+ }
+ return 1;
+}
+
+static const char *get_subtype(const char *argv0)
+{
+ size_t argvlen = strlen(argv0);
+
+ if (argvlen < 4)
+ goto out_default;
+
+ if (argv0[argvlen - 4] == 'e' &&
+ argv0[argvlen - 3] == 'x' &&
+ argv0[argvlen - 2] == 't' &&
+ isdigit(argv0[argvlen - 1]))
+ return &argv0[argvlen - 4];
+
+out_default:
+ return "ext4";
+}
+
+static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
+ struct fuse_args *args,
+ const char *argv0)
+{
+ char extra_args[BUFSIZ];
+
+ /* Set up default fuse parameters */
+ snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
+ "fsname=%s,attr_timeout=0",
+ get_subtype(argv0),
+ ff->device);
+ if (ff->no_default_opts == 0)
+ fuse_opt_add_arg(args, extra_args);
+
+ if (ff->ro)
+ fuse_opt_add_arg(args, "-oro");
+
+ if (ff->fakeroot) {
+#ifdef HAVE_MOUNT_NODEV
+ fuse_opt_add_arg(args,"-onodev");
+#endif
+#ifdef HAVE_MOUNT_NOSUID
+ fuse_opt_add_arg(args,"-onosuid");
+#endif
+ }
+
+ if (ff->kernel) {
+ /*
+ * ACLs are always enforced when kernel mode is enabled, to
+ * match the kernel ext4 driver which always enables ACLs.
+ */
+ ff->acl = 1;
+ fuse_opt_insert_arg(args, 1,
+ "-oallow_other,default_permissions,suid,dev");
+ }
+
+ /*
+ * Since there's a Big Kernel Lock around all the libext2fs code, we
+ * only need to start four threads -- one to decode a request, another
+ * to do the filesystem work, a third to transmit the reply, and a
+ * fourth to handle fuse notifications.
+ */
+ fuse_opt_insert_arg(args, 1, "-omax_threads=4");
+
+ if (ff->debug) {
+ int i;
+
+ printf("FUSE4FS (%s): fuse arguments:", ff->shortdev);
+ for (i = 0; i < args->argc; i++)
+ printf(" '%s'", args->argv[i]);
+ printf("\n");
+ fflush(stdout);
+ }
+}
+
+/*
+ * Try to register as a filesystem I/O server process so that our memory
+ * allocations don't cause fs reclaim.
+ */
+static void try_set_io_flusher(struct fuse4fs *ff)
+{
+#ifdef HAVE_PR_SET_IO_FLUSHER
+ int ret = prctl(PR_GET_IO_FLUSHER, 0, 0, 0, 0);
+
+ /*
+ * positive ret means it's already set, negative means we can't even
+ * look at the value so don't bother setting it
+ */
+ if (ret)
+ return;
+
+ ret = prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0);
+ if (ret < 0)
+ err_printf(ff, "%s: %s.\n",
+ _("Could not register as IO flusher thread"),
+ strerror(errno));
+#endif
+}
+
+/* Try to adjust the OOM score so that we don't get killed */
+static void try_adjust_oom_score(struct fuse4fs *ff)
+{
+ FILE *fp = fopen("/proc/self/oom_score_adj", "w+");
+
+ if (!fp)
+ return;
+
+ fprintf(fp, "%d\n", ff->oom_score_adj);
+ fclose(fp);
+}
+
+static void fuse4fs_com_err_proc(const char *whoami, errcode_t code,
+ const char *fmt, va_list args)
+{
+ fprintf(stderr, "FUSE4FS (%s): ", err_shortdev ? err_shortdev : "?");
+ if (whoami)
+ fprintf(stderr, "%s: ", whoami);
+ fprintf(stderr, "%s ", error_message(code));
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+int main(int argc, char *argv[])
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct fuse4fs fctx = {
+ .magic = FUSE4FS_MAGIC,
+ .logfd = -1,
+ .bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,
+ .oom_score_adj = -500,
+ .opstate = F4OP_WRITABLE,
+ };
+ errcode_t err;
+ FILE *orig_stderr = stderr;
+ int ret;
+
+ ret = fuse_opt_parse(&args, &fctx, fuse4fs_opts, fuse4fs_opt_proc);
+ if (ret)
+ exit(1);
+ if (fctx.device == NULL) {
+ fprintf(stderr, "Missing ext4 device/image\n");
+ fprintf(stderr, "See '%s -h' for usage\n", argv[0]);
+ exit(1);
+ }
+
+ /* /dev/sda -> sda for reporting */
+ fctx.shortdev = strrchr(fctx.device, '/');
+ if (fctx.shortdev)
+ fctx.shortdev++;
+ else
+ fctx.shortdev = fctx.device;
+
+ /* capture library error messages */
+ err_shortdev = fctx.shortdev;
+ set_com_err_hook(fuse4fs_com_err_proc);
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ add_error_table(&et_ext2_error_table);
+
+ ret = fuse4fs_setup_logging(&fctx);
+ if (ret) {
+ /* operational error */
+ ret = 2;
+ goto out;
+ }
+
+ try_set_io_flusher(&fctx);
+ try_adjust_oom_score(&fctx);
+
+ /* Will we allow users to allocate every last block? */
+ if (getenv("FUSE4FS_ALLOC_ALL_BLOCKS")) {
+ log_printf(&fctx, "%s\n",
+ _("Allowing users to allocate all blocks. This is dangerous!"));
+ fctx.alloc_all_blocks = 1;
+ }
+
+ err = fuse4fs_open(&fctx);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ err = fuse4fs_config_cache(&fctx);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ err = fuse4fs_check_support(&fctx);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ /*
+ * ext4 can't do COW of shared blocks, so if the feature is enabled,
+ * we must force ro mode.
+ */
+ if (ext2fs_has_feature_shared_blocks(fctx.fs->super))
+ fctx.ro = 1;
+
+ err = fuse4fs_mount(&fctx);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ /* Initialize generation counter */
+ get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
+
+ fuse4fs_compute_libfuse_args(&fctx, &args, argv[0]);
+
+ ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+ switch(ret) {
+ case 0:
+ /* success */
+ ret = 0;
+ break;
+ case 1:
+ case 2:
+ /* invalid option or no mountpoint */
+ ret = 1;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ /* setup or mounting failed */
+ ret = 32;
+ break;
+ default:
+ /* fuse started up enough to call op_init */
+ ret = 0;
+ break;
+ }
+out:
+ if (ret & 1) {
+ fprintf(orig_stderr, "%s\n",
+ _("Mount failed due to unrecognized options. Check dmesg(1) for details."));
+ fflush(orig_stderr);
+ }
+ if (ret & 32) {
+ fprintf(orig_stderr, "%s\n",
+ _("Mount failed while opening filesystem. Check dmesg(1) for details."));
+ fflush(orig_stderr);
+ }
+ fuse4fs_mmp_destroy(&fctx);
+ fuse4fs_unmount(&fctx);
+ reset_com_err_hook();
+ err_shortdev = NULL;
+ if (fctx.device)
+ free(fctx.device);
+ pthread_mutex_destroy(&fctx.bfl);
+ fuse_opt_free_args(&args);
+ return ret;
+}
+
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
+ const char *func, int line)
+{
+ struct timespec now;
+ int ret = err;
+ struct fuse4fs *ff = fs->priv_data;
+ int is_err = 0;
+
+ /* Translate ext2 error to unix error code */
+ switch (err) {
+ case 0:
+ break;
+ case EXT2_ET_NO_MEMORY:
+ case EXT2_ET_TDB_ERR_OOM:
+ ret = -ENOMEM;
+ break;
+ case EXT2_ET_INVALID_ARGUMENT:
+ case EXT2_ET_LLSEEK_FAILED:
+ ret = -EINVAL;
+ break;
+ case EXT2_ET_NO_DIRECTORY:
+ ret = -ENOTDIR;
+ break;
+ case EXT2_ET_FILE_NOT_FOUND:
+ ret = -ENOENT;
+ break;
+ case EXT2_ET_DIR_NO_SPACE:
+ is_err = 1;
+ /* fallthrough */
+ case EXT2_ET_TOOSMALL:
+ case EXT2_ET_BLOCK_ALLOC_FAIL:
+ case EXT2_ET_INODE_ALLOC_FAIL:
+ case EXT2_ET_EA_NO_SPACE:
+ ret = -ENOSPC;
+ break;
+ case EXT2_ET_SYMLINK_LOOP:
+ ret = -EMLINK;
+ break;
+ case EXT2_ET_FILE_TOO_BIG:
+ ret = -EFBIG;
+ break;
+ case EXT2_ET_TDB_ERR_EXISTS:
+ case EXT2_ET_FILE_EXISTS:
+ ret = -EEXIST;
+ break;
+ case EXT2_ET_MMP_FAILED:
+ case EXT2_ET_MMP_FSCK_ON:
+ ret = -EBUSY;
+ break;
+ case EXT2_ET_EA_KEY_NOT_FOUND:
+ ret = -ENODATA;
+ break;
+ case EXT2_ET_UNIMPLEMENTED:
+ ret = -EOPNOTSUPP;
+ break;
+ case EXT2_ET_RO_FILSYS:
+ ret = -EROFS;
+ break;
+ case EXT2_ET_MAGIC_EXT2_FILE:
+ case EXT2_ET_MAGIC_EXT2FS_FILSYS:
+ case EXT2_ET_MAGIC_BADBLOCKS_LIST:
+ case EXT2_ET_MAGIC_BADBLOCKS_ITERATE:
+ case EXT2_ET_MAGIC_INODE_SCAN:
+ case EXT2_ET_MAGIC_IO_CHANNEL:
+ case EXT2_ET_MAGIC_UNIX_IO_CHANNEL:
+ case EXT2_ET_MAGIC_IO_MANAGER:
+ case EXT2_ET_MAGIC_BLOCK_BITMAP:
+ case EXT2_ET_MAGIC_INODE_BITMAP:
+ case EXT2_ET_MAGIC_GENERIC_BITMAP:
+ case EXT2_ET_MAGIC_TEST_IO_CHANNEL:
+ case EXT2_ET_MAGIC_DBLIST:
+ case EXT2_ET_MAGIC_ICOUNT:
+ case EXT2_ET_MAGIC_PQ_IO_CHANNEL:
+ case EXT2_ET_MAGIC_E2IMAGE:
+ case EXT2_ET_MAGIC_INODE_IO_CHANNEL:
+ case EXT2_ET_MAGIC_EXTENT_HANDLE:
+ case EXT2_ET_BAD_MAGIC:
+ case EXT2_ET_MAGIC_EXTENT_PATH:
+ case EXT2_ET_MAGIC_GENERIC_BITMAP64:
+ case EXT2_ET_MAGIC_BLOCK_BITMAP64:
+ case EXT2_ET_MAGIC_INODE_BITMAP64:
+ case EXT2_ET_MAGIC_RESERVED_13:
+ case EXT2_ET_MAGIC_RESERVED_14:
+ case EXT2_ET_MAGIC_RESERVED_15:
+ case EXT2_ET_MAGIC_RESERVED_16:
+ case EXT2_ET_MAGIC_RESERVED_17:
+ case EXT2_ET_MAGIC_RESERVED_18:
+ case EXT2_ET_MAGIC_RESERVED_19:
+ case EXT2_ET_MMP_MAGIC_INVALID:
+ case EXT2_ET_MAGIC_EA_HANDLE:
+ case EXT2_ET_DIR_CORRUPTED:
+ case EXT2_ET_CORRUPT_SUPERBLOCK:
+ case EXT2_ET_RESIZE_INODE_CORRUPT:
+ case EXT2_ET_TDB_ERR_CORRUPT:
+ case EXT2_ET_UNDO_FILE_CORRUPT:
+ case EXT2_ET_FILESYSTEM_CORRUPTED:
+ case EXT2_ET_CORRUPT_JOURNAL_SB:
+ case EXT2_ET_INODE_CORRUPTED:
+ case EXT2_ET_EA_INODE_CORRUPTED:
+ /* same errno that linux uses */
+ is_err = 1;
+ ret = -EUCLEAN;
+ break;
+ case EIO:
+#ifdef EILSEQ
+ case EILSEQ:
+#endif
+ case EUCLEAN:
+ /* these errnos usually denote corruption or persistence fail */
+ is_err = 1;
+ ret = -err;
+ break;
+ default:
+ if (err < 256) {
+ /* other errno are usually operational errors */
+ ret = -err;
+ } else {
+ is_err = 1;
+ ret = -EIO;
+ }
+ break;
+ }
+
+ if (!is_err)
+ return ret;
+
+ if (ino)
+ err_printf(ff, "%s (inode #%d) at %s:%d.\n",
+ error_message(err), ino, func, line);
+ else
+ err_printf(ff, "%s at %s:%d.\n",
+ error_message(err), func, line);
+
+ /* Make a note in the error log */
+ get_now(&now);
+ ext2fs_set_tstamp(fs->super, s_last_error_time, now.tv_sec);
+ fs->super->s_last_error_ino = ino;
+ fs->super->s_last_error_line = line;
+ fs->super->s_last_error_block = err; /* Yeah... */
+ strncpy((char *)fs->super->s_last_error_func, func,
+ sizeof(fs->super->s_last_error_func));
+ if (ext2fs_get_tstamp(fs->super, s_first_error_time) == 0) {
+ ext2fs_set_tstamp(fs->super, s_first_error_time, now.tv_sec);
+ fs->super->s_first_error_ino = ino;
+ fs->super->s_first_error_line = line;
+ fs->super->s_first_error_block = err;
+ strncpy((char *)fs->super->s_first_error_func, func,
+ sizeof(fs->super->s_first_error_func));
+ }
+
+ fs->super->s_state |= EXT2_ERROR_FS;
+ fs->super->s_error_count++;
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_flush(fs);
+ switch (ff->errors_behavior) {
+ case EXT2_ERRORS_CONTINUE:
+ err_printf(ff, "%s\n",
+ _("Continuing after errors; is this a good idea?"));
+ break;
+ case EXT2_ERRORS_RO:
+ if (ff->opstate == F4OP_WRITABLE) {
+ err_printf(ff, "%s\n",
+ _("Remounting read-only due to errors."));
+ ff->opstate = F4OP_READONLY;
+ }
+ fuse4fs_mmp_cancel(ff);
+ fs->flags &= ~EXT2_FLAG_RW;
+ break;
+ case EXT2_ERRORS_PANIC:
+ err_printf(ff, "%s\n",
+ _("Aborting filesystem mount due to errors."));
+ abort();
+ break;
+ }
+
+ return ret;
+}
diff --git a/lib/config.h.in b/lib/config.h.in
index a4d8ce1c3765ed..c3379758c3c9bc 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -73,6 +73,9 @@
/* Define to 1 if PR_SET_IO_FLUSHER is present */
#undef HAVE_PR_SET_IO_FLUSHER
+/* Define to 1 if fuse supports lowlevel API */
+#undef HAVE_FUSE_LOWLEVEL
+
/* Define to 1 if you have the Mac OS X function
CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
#undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 03/23] debian: create new package for fuse4fs
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
2025-11-06 22:43 ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
2025-11-06 22:43 ` [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API Darrick J. Wong
@ 2025-11-06 22:43 ` Darrick J. Wong
2025-11-06 22:44 ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
` (19 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Create a new package for fuse4fs.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
debian/control | 12 +++++++++++-
debian/fuse4fs.install | 2 ++
debian/fuse4fs.links | 3 +++
debian/rules | 11 +++++++++++
4 files changed, 27 insertions(+), 1 deletion(-)
create mode 100644 debian/fuse4fs.install
create mode 100644 debian/fuse4fs.links
diff --git a/debian/control b/debian/control
index fb3487cd32b99a..04df691d81b230 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: e2fsprogs
Section: admin
Priority: important
Maintainer: Theodore Y. Ts'o <tytso@mit.edu>
-Build-Depends: dpkg-dev (>= 1.22.5), gettext, texinfo, pkgconf, libarchive-dev <!nocheck>, libfuse3-dev [linux-any kfreebsd-any] <!pkg.e2fsprogs.no-fuse2fs>, debhelper-compat (= 12), dh-exec, libblkid-dev, uuid-dev, m4, udev [linux-any], systemd [linux-any], systemd-dev [linux-any], cron [linux-any], dh-sequence-movetousr
+Build-Depends: dpkg-dev (>= 1.22.5), gettext, texinfo, pkgconf, libarchive-dev <!nocheck>, libfuse3-dev [linux-any kfreebsd-any] <!pkg.e2fsprogs.no-fuse2fs> <!pkg.e2fsprogs.no-fuse4fs>, debhelper-compat (= 12), dh-exec, libblkid-dev, uuid-dev, m4, udev [linux-any], systemd [linux-any], systemd-dev [linux-any], cron [linux-any], dh-sequence-movetousr
Rules-Requires-Root: no
Standards-Version: 4.7.2
Homepage: http://e2fsprogs.sourceforge.net
@@ -21,6 +21,16 @@ Description: ext2 / ext3 / ext4 file system driver for FUSE
writing from devices or image files containing ext2, ext3, and ext4
file systems.
+Package: fuse4fs
+Build-Profiles: <!pkg.e2fsprogs.no-fuse4fs>
+Priority: optional
+Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3
+Architecture: linux-any kfreebsd-any
+Description: ext2 / ext3 / ext4 file system driver for FUSE
+ fuse4fs is a faster FUSE file system client that supports reading and
+ writing from devices or image files containing ext2, ext3, and ext4
+ file systems.
+
Package: fuseext2
Build-Profiles: <!pkg.e2fsprogs.no-fuse2fs>
Depends: fuse2fs (>= 1.47.1-2), ${misc:Depends}
diff --git a/debian/fuse4fs.install b/debian/fuse4fs.install
new file mode 100644
index 00000000000000..17bdc90e33cb67
--- /dev/null
+++ b/debian/fuse4fs.install
@@ -0,0 +1,2 @@
+usr/bin/fuse4fs
+usr/share/man/man1/fuse4fs.1
diff --git a/debian/fuse4fs.links b/debian/fuse4fs.links
new file mode 100644
index 00000000000000..825017e11b951e
--- /dev/null
+++ b/debian/fuse4fs.links
@@ -0,0 +1,3 @@
+/usr/bin/fuse4fs /usr/bin/ext4
+/usr/bin/fuse4fs /usr/bin/ext3
+/usr/bin/fuse4fs /usr/bin/ext2
diff --git a/debian/rules b/debian/rules
index c88675c9228bd0..b680eb33ceac9e 100755
--- a/debian/rules
+++ b/debian/rules
@@ -12,6 +12,7 @@ export LC_ALL ?= C
ifeq ($(DEB_HOST_ARCH_OS), hurd)
SKIP_FUSE2FS=yes
+SKIP_FUSE4FS=yes
endif
ifeq ($(DEB_HOST_ARCH_OS), linux)
@@ -22,6 +23,9 @@ endif
ifneq ($(filter pkg.e2fsprogs.no-fuse2fs,$(DEB_BUILD_PROFILES)),)
SKIP_FUSE2FS=yes
endif
+ifneq ($(filter pkg.e2fsprogs.no-fuse4fs,$(DEB_BUILD_PROFILES)),)
+SKIP_FUSE4FS=yes
+endif
ifneq (,$(filter-out parallel=1,$(filter parallel=%,$(DEB_BUILD_OPTIONS))))
NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
@@ -60,6 +64,9 @@ COMMON_CONF_FLAGS = --enable-elf-shlibs --disable-ubsan \
ifneq ($(SKIP_FUSE2FS),)
COMMON_CONF_FLAGS += --disable-fuse2fs
endif
+ifneq ($(SKIP_FUSE4FS),)
+COMMON_CONF_FLAGS += --disable-fuse4fs
+endif
ifneq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE))
CC ?= $(DEB_HOST_GNU_TYPE)-gcc
@@ -189,6 +196,10 @@ endif
ifeq ($(SKIP_FUSE2FS),)
dh_shlibdeps -pfuse2fs -l${stdbuilddir}/lib \
-- -Ldebian/e2fsprogs.shlibs.local
+endif
+ifeq ($(SKIP_FUSE4FS),)
+ dh_shlibdeps -pfuse4fs -l${stdbuilddir}/lib \
+ -- -Ldebian/e2fsprogs.shlibs.local
endif
dh_shlibdeps --remaining-packages -l${stdbuilddir}/lib
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 04/23] fuse4fs: namespace some helpers
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (2 preceding siblings ...)
2025-11-06 22:43 ` [PATCH 03/23] debian: create new package for fuse4fs Darrick J. Wong
@ 2025-11-06 22:44 ` Darrick J. Wong
2025-11-07 8:09 ` Amir Goldstein
2025-11-06 22:44 ` [PATCH 05/23] fuse4fs: convert to low level API Darrick J. Wong
` (18 subsequent siblings)
22 siblings, 1 reply; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
object pointer.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fuse4fs/fuse4fs.c | 177 +++++++++++++++++++++++++++--------------------------
1 file changed, 90 insertions(+), 87 deletions(-)
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index daf22e0fe7fde5..2ef5ad60163639 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -2,6 +2,7 @@
* fuse4fs.c - FUSE low-level server for e2fsprogs.
*
* Copyright (C) 2014-2025 Oracle.
+ * Copyright (C) 2025 CTERA Networks.
*
* %Begin-Header%
* This file may be redistributed under the terms of the GNU Public
@@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
return 0;
}
-static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
+static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
{
ext2_filsys fs = ff->fs;
blk64_t reserved;
@@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
return ext2fs_free_blocks_count(fs->super) > reserved + num;
}
-static int fuse4fs_is_writeable(struct fuse4fs *ff)
+static int fuse4fs_is_writeable(const struct fuse4fs *ff)
{
return ff->opstate == F4OP_WRITABLE &&
(ff->fs->super->s_error_count == 0);
}
-static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
+static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
+ const struct fuse_context *ctxt)
{
if (ff->fakeroot)
return 1;
return ctxt->uid == 0;
}
-static inline int want_check_owner(struct fuse4fs *ff,
- struct fuse_context *ctxt)
+static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
+ const struct fuse_context *ctxt)
{
/*
* The kernel is responsible for access control, so we allow anything
@@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
*/
if (ff->kernel)
return 0;
- return !is_superuser(ff, ctxt);
+ return !fuse4fs_is_superuser(ff, ctxt);
}
/* Test for append permission */
#define A_OK 16
-static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
- const struct ext2_inode *inode, int mask)
+static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
+ const struct ext2_inode *inode, int mask)
{
EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
@@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
return 0;
}
-static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
{
struct fuse_context *ctxt = fuse_get_context();
ext2_filsys fs = ff->fs;
@@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
if (mask == 0)
return 0;
- ret = check_iflags_access(ff, ino, &inode, mask);
+ ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
if (ret)
return ret;
@@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
return 0;
/* Figure out what root's allowed to do */
- if (is_superuser(ff, ctxt)) {
+ if (fuse4fs_is_superuser(ff, ctxt)) {
/* Non-file access always ok */
if (!LINUX_S_ISREG(inode.i_mode))
return 0;
@@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
return ret;
}
-static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
- void **value, size_t *value_len)
+static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
+ const char *name, void **value, size_t *value_len)
{
ext2_filsys fs = ff->fs;
struct ext2_xattr_handle *h;
@@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
return ret;
}
-static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
- void *value, size_t valuelen)
+static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
+ const char *name, void *value, size_t valuelen)
{
ext2_filsys fs = ff->fs;
struct ext2_xattr_handle *h;
@@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
return ret;
}
-static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
- ext2_ino_t child, mode_t mode)
+static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
+ ext2_ino_t child, mode_t mode)
{
void *def;
size_t deflen;
@@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
if (!ff->acl || S_ISDIR(mode))
return 0;
- ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
- &deflen);
+ ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
+ &deflen);
switch (ret) {
case -ENODATA:
case -ENOENT:
@@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
return ret;
}
- ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
+ ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
+ deflen);
ext2fs_free_mem(&def);
return ret;
}
@@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
*node_name = 0;
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 2)) {
+ if (!fuse4fs_can_allocate(ff, 2)) {
ret = -ENOSPC;
goto out2;
}
@@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
goto out2;
}
- ret = check_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
if (ret)
goto out2;
@@ -2079,7 +2082,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
ext2fs_inode_alloc_stats2(fs, child, 1, 0);
- ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
if (ret)
goto out2;
@@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
*node_name = 0;
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 1)) {
+ if (!fuse4fs_can_allocate(ff, 1)) {
ret = -ENOSPC;
goto out2;
}
@@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
goto out2;
}
- ret = check_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
if (ret)
goto out2;
@@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
goto out3;
}
- ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
if (ret)
goto out3;
@@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
base_name = filename;
}
- ret = check_inum_access(ff, dir, W_OK);
+ ret = fuse4fs_inum_access(ff, dir, W_OK);
if (ret) {
free(filename);
return ret;
@@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
return 0;
}
-static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
- struct ext2_inode_large *inode)
+static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
{
ext2_filsys fs = ff->fs;
struct ext2_xattr_handle *h;
@@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
return 0;
}
-static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
+static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
{
ext2_filsys fs = ff->fs;
errcode_t err;
@@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
goto write_out;
if (ext2fs_has_feature_ea_inode(fs->super)) {
- ret = remove_ea_inodes(ff, ino, &inode);
+ ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
if (ret)
return ret;
}
@@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
goto out;
}
- ret = check_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ino, W_OK);
if (ret)
goto out;
@@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
if (ret)
goto out;
- ret = remove_inode(ff, ino);
+ ret = fuse4fs_remove_inode(ff, ino);
if (ret)
goto out;
@@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
}
dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
- ret = check_inum_access(ff, child, W_OK);
+ ret = fuse4fs_inum_access(ff, child, W_OK);
if (ret)
goto out;
@@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
goto out;
}
- ret = check_inum_access(ff, rds.parent, W_OK);
+ ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
if (ret)
goto out;
@@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
ret = fuse4fs_unlink(ff, path, &parent);
if (ret)
goto out;
- ret = remove_inode(ff, child);
+ ret = fuse4fs_remove_inode(ff, child);
if (ret)
goto out;
@@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
*node_name = 0;
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 1)) {
+ if (!fuse4fs_can_allocate(ff, 1)) {
ret = -ENOSPC;
goto out2;
}
@@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
goto out2;
}
- ret = check_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
if (ret)
goto out2;
@@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
FUSE4FS_CHECK_CONTEXT(ff);
dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 5)) {
+ if (!fuse4fs_can_allocate(ff, 5)) {
ret = -ENOSPC;
goto out;
}
@@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
goto out;
}
- ret = check_inum_access(ff, from_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, from_ino, W_OK);
if (ret)
goto out;
if (to_ino) {
- ret = check_inum_access(ff, to_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, to_ino, W_OK);
if (ret)
goto out;
}
@@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
goto out2;
}
- ret = check_inum_access(ff, from_dir_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
if (ret)
goto out2;
@@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
goto out2;
}
- ret = check_inum_access(ff, to_dir_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
if (ret)
goto out2;
@@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
*node_name = 0;
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 2)) {
+ if (!fuse4fs_can_allocate(ff, 2)) {
ret = -ENOSPC;
goto out2;
}
@@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
goto out2;
}
- ret = check_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
if (ret)
goto out2;
@@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
goto out2;
}
- ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
if (ret)
goto out2;
@@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
}
/* Obtain group ids of the process that sent us a command(?) */
-static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
+static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
{
ext2_filsys fs = ff->fs;
errcode_t err;
@@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
* that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative
* errno.
*/
-static int in_file_group(struct fuse_context *ctxt,
- const struct ext2_inode_large *inode)
+static int fuse4fs_in_file_group(struct fuse_context *ctxt,
+ const struct ext2_inode_large *inode)
{
struct fuse4fs *ff = fuse4fs_get();
gid_t *gids = NULL;
@@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
if (ctxt->gid == gid)
return 1;
- ret = get_req_groups(ff, &gids, &nr_gids);
+ ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
if (ret == -ENOENT) {
/* magic return code for "could not get caller group info" */
return 0;
@@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
goto out;
}
- ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
if (ret)
goto out;
- if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
+ if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
ret = -EPERM;
goto out;
}
@@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
* of the user's groups, but FUSE only tells us about the primary
* group.
*/
- if (!is_superuser(ff, ctxt)) {
- ret = in_file_group(ctxt, &inode);
+ if (!fuse4fs_is_superuser(ff, ctxt)) {
+ ret = fuse4fs_in_file_group(ctxt, &inode);
if (ret < 0)
goto out;
@@ -3236,14 +3239,14 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
goto out;
}
- ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
if (ret)
goto out;
/* FUSE seems to feed us ~0 to mean "don't change" */
if (owner != (uid_t) ~0) {
/* Only root gets to change UID. */
- if (want_check_owner(ff, ctxt) &&
+ if (fuse4fs_want_check_owner(ff, ctxt) &&
!(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
ret = -EPERM;
goto out;
@@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
if (group != (gid_t) ~0) {
/* Only root or the owner get to change GID. */
- if (want_check_owner(ff, ctxt) &&
+ if (fuse4fs_want_check_owner(ff, ctxt) &&
inode_uid(inode) != ctxt->uid) {
ret = -EPERM;
goto out;
@@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
goto out;
dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
- ret = check_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ino, W_OK);
if (ret)
goto out;
@@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
}
dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
- ret = check_inum_access(ff, file->ino, check);
+ ret = fuse4fs_inum_access(ff, file->ino, check);
if (ret) {
/*
* In a regular (Linux) fs driver, the kernel will open
@@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
* also employ undocumented hacks (see above).
*/
if (check == R_OK) {
- ret = check_inum_access(ff, file->ino, X_OK);
+ ret = fuse4fs_inum_access(ff, file->ino, X_OK);
if (ret)
goto out;
check = X_OK;
@@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
goto out;
}
- if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
+ if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
ret = -ENOSPC;
goto out;
}
@@ -3768,11 +3771,11 @@ static int op_getxattr(const char *path, const char *key, char *value,
}
dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
- ret = check_inum_access(ff, ino, R_OK);
+ ret = fuse4fs_inum_access(ff, ino, R_OK);
if (ret)
goto out;
- ret = __getxattr(ff, ino, key, &ptr, &plen);
+ ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
if (ret)
goto out;
@@ -3838,7 +3841,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
}
dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
- ret = check_inum_access(ff, ino, R_OK);
+ ret = fuse4fs_inum_access(ff, ino, R_OK);
if (ret)
goto out;
@@ -3919,7 +3922,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
}
dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
- ret = check_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ino, W_OK);
if (ret == -EACCES) {
ret = -EPERM;
goto out;
@@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
goto out;
}
- if (!fs_can_allocate(ff, 1)) {
+ if (!fuse4fs_can_allocate(ff, 1)) {
ret = -ENOSPC;
goto out;
}
@@ -4020,7 +4023,7 @@ static int op_removexattr(const char *path, const char *key)
}
dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
- ret = check_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ino, W_OK);
if (ret)
goto out;
@@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
goto out;
}
- ret = check_inum_access(ff, ino, mask);
+ ret = fuse4fs_inum_access(ff, ino, mask);
if (ret)
goto out;
@@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
*node_name = 0;
fs = fuse4fs_start(ff);
- if (!fs_can_allocate(ff, 1)) {
+ if (!fuse4fs_can_allocate(ff, 1)) {
ret = -ENOSPC;
goto out2;
}
@@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
goto out2;
}
- ret = check_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
if (ret)
goto out2;
@@ -4326,7 +4329,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
ext2fs_inode_alloc_stats2(fs, child, 1, 0);
- ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+ ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
if (ret)
goto out2;
@@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
*/
if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
access |= A_OK;
- ret = check_inum_access(ff, ino, access);
+ ret = fuse4fs_inum_access(ff, ino, access);
if (ret)
goto out;
@@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (err)
return translate_error(fs, fh->ino, err);
- if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
return -EPERM;
ret = set_iflags(&inode, flags);
@@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (err)
return translate_error(fs, fh->ino, err);
- if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
return -EPERM;
inode.i_generation = generation;
@@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (err)
return translate_error(fs, fh->ino, err);
- if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+ if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
return -EPERM;
ret = set_xflags(&inode, fsx->fsx_xflags);
@@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
struct fuse_context *ctxt = fuse_get_context();
ext2_filsys fs = ff->fs;
- if (!is_superuser(ff, ctxt))
+ if (!fuse4fs_is_superuser(ff, ctxt))
return -EPERM;
err_printf(ff, "%s.\n", _("shut down requested"));
@@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
(unsigned long long)len,
(unsigned long long)start,
(unsigned long long)end);
- if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
+ if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
return -ENOSPC;
err = fuse4fs_read_inode(fs, fh->ino, &inode);
@@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
return err;
}
-static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
- struct ext2_inode_large *inode,
- off_t offset, off_t len, char **buf)
+static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode,
+ off_t offset, off_t len, char **buf)
{
ext2_filsys fs = ff->fs;
blk64_t blk;
@@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
return io_channel_write_blk64(fs->io, blk, 1, *buf);
}
-static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
- struct ext2_inode_large *inode, off_t offset,
- int clean_before, char **buf)
+static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ int clean_before, char **buf)
{
ext2_filsys fs = ff->fs;
blk64_t blk;
@@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
/* Zero everything before the first block and after the last block */
if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
- err = clean_block_middle(ff, fh->ino, &inode, offset,
+ err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
len, &buf);
else {
- err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
+ err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
if (!err)
- err = clean_block_edge(ff, fh->ino, &inode,
- offset + len, 1, &buf);
+ err = fuse4fs_zero_edge(ff, fh->ino, &inode,
+ offset + len, 1, &buf);
}
if (buf)
ext2fs_free_mem(&buf);
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 04/23] fuse4fs: namespace some helpers
2025-11-06 22:44 ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
@ 2025-11-07 8:09 ` Amir Goldstein
2025-11-08 0:25 ` Darrick J. Wong
0 siblings, 1 reply; 84+ messages in thread
From: Amir Goldstein @ 2025-11-07 8:09 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: tytso, linux-ext4
On Thu, Nov 06, 2025 at 02:44:07PM -0800, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
> object pointer.
>
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
> fuse4fs/fuse4fs.c | 177 +++++++++++++++++++++++++++--------------------------
> 1 file changed, 90 insertions(+), 87 deletions(-)
>
>
> diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
> index daf22e0fe7fde5..2ef5ad60163639 100644
> --- a/fuse4fs/fuse4fs.c
> +++ b/fuse4fs/fuse4fs.c
> @@ -2,6 +2,7 @@
> * fuse4fs.c - FUSE low-level server for e2fsprogs.
> *
> * Copyright (C) 2014-2025 Oracle.
> + * Copyright (C) 2025 CTERA Networks.
I think this belongs to next patch :)
Thanks,
Amir.
> *
> * %Begin-Header%
> * This file may be redistributed under the terms of the GNU Public
> @@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
> return 0;
> }
>
> -static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> +static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> {
> ext2_filsys fs = ff->fs;
> blk64_t reserved;
> @@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> return ext2fs_free_blocks_count(fs->super) > reserved + num;
> }
>
> -static int fuse4fs_is_writeable(struct fuse4fs *ff)
> +static int fuse4fs_is_writeable(const struct fuse4fs *ff)
> {
> return ff->opstate == F4OP_WRITABLE &&
> (ff->fs->super->s_error_count == 0);
> }
>
> -static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
> +static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
> + const struct fuse_context *ctxt)
> {
> if (ff->fakeroot)
> return 1;
> return ctxt->uid == 0;
> }
>
> -static inline int want_check_owner(struct fuse4fs *ff,
> - struct fuse_context *ctxt)
> +static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
> + const struct fuse_context *ctxt)
> {
> /*
> * The kernel is responsible for access control, so we allow anything
> @@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
> */
> if (ff->kernel)
> return 0;
> - return !is_superuser(ff, ctxt);
> + return !fuse4fs_is_superuser(ff, ctxt);
> }
>
> /* Test for append permission */
> #define A_OK 16
>
> -static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> - const struct ext2_inode *inode, int mask)
> +static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> + const struct ext2_inode *inode, int mask)
> {
> EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
>
> @@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> return 0;
> }
>
> -static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> +static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> {
> struct fuse_context *ctxt = fuse_get_context();
> ext2_filsys fs = ff->fs;
> @@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> if (mask == 0)
> return 0;
>
> - ret = check_iflags_access(ff, ino, &inode, mask);
> + ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
> if (ret)
> return ret;
>
> @@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> return 0;
>
> /* Figure out what root's allowed to do */
> - if (is_superuser(ff, ctxt)) {
> + if (fuse4fs_is_superuser(ff, ctxt)) {
> /* Non-file access always ok */
> if (!LINUX_S_ISREG(inode.i_mode))
> return 0;
> @@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
> return ret;
> }
>
> -static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> - void **value, size_t *value_len)
> +static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
> + const char *name, void **value, size_t *value_len)
> {
> ext2_filsys fs = ff->fs;
> struct ext2_xattr_handle *h;
> @@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> return ret;
> }
>
> -static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> - void *value, size_t valuelen)
> +static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
> + const char *name, void *value, size_t valuelen)
> {
> ext2_filsys fs = ff->fs;
> struct ext2_xattr_handle *h;
> @@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> return ret;
> }
>
> -static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> - ext2_ino_t child, mode_t mode)
> +static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> + ext2_ino_t child, mode_t mode)
> {
> void *def;
> size_t deflen;
> @@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> if (!ff->acl || S_ISDIR(mode))
> return 0;
>
> - ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> - &deflen);
> + ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> + &deflen);
> switch (ret) {
> case -ENODATA:
> case -ENOENT:
> @@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> return ret;
> }
>
> - ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
> + ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
> + deflen);
> ext2fs_free_mem(&def);
> return ret;
> }
> @@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> *node_name = 0;
>
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 2)) {
> + if (!fuse4fs_can_allocate(ff, 2)) {
> ret = -ENOSPC;
> goto out2;
> }
> @@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> goto out2;
> }
>
> - ret = check_inum_access(ff, parent, A_OK | W_OK);
> + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> if (ret)
> goto out2;
>
> @@ -2079,7 +2082,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
>
> ext2fs_inode_alloc_stats2(fs, child, 1, 0);
>
> - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> if (ret)
> goto out2;
>
> @@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
> *node_name = 0;
>
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 1)) {
> + if (!fuse4fs_can_allocate(ff, 1)) {
> ret = -ENOSPC;
> goto out2;
> }
> @@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
> goto out2;
> }
>
> - ret = check_inum_access(ff, parent, A_OK | W_OK);
> + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> if (ret)
> goto out2;
>
> @@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
> goto out3;
> }
>
> - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> if (ret)
> goto out3;
>
> @@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> base_name = filename;
> }
>
> - ret = check_inum_access(ff, dir, W_OK);
> + ret = fuse4fs_inum_access(ff, dir, W_OK);
> if (ret) {
> free(filename);
> return ret;
> @@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> return 0;
> }
>
> -static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> - struct ext2_inode_large *inode)
> +static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> + struct ext2_inode_large *inode)
> {
> ext2_filsys fs = ff->fs;
> struct ext2_xattr_handle *h;
> @@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> return 0;
> }
>
> -static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> +static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> {
> ext2_filsys fs = ff->fs;
> errcode_t err;
> @@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> goto write_out;
>
> if (ext2fs_has_feature_ea_inode(fs->super)) {
> - ret = remove_ea_inodes(ff, ino, &inode);
> + ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
> if (ret)
> return ret;
> }
> @@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> goto out;
> }
>
> - ret = check_inum_access(ff, ino, W_OK);
> + ret = fuse4fs_inum_access(ff, ino, W_OK);
> if (ret)
> goto out;
>
> @@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> if (ret)
> goto out;
>
> - ret = remove_inode(ff, ino);
> + ret = fuse4fs_remove_inode(ff, ino);
> if (ret)
> goto out;
>
> @@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> }
> dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
>
> - ret = check_inum_access(ff, child, W_OK);
> + ret = fuse4fs_inum_access(ff, child, W_OK);
> if (ret)
> goto out;
>
> @@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> goto out;
> }
>
> - ret = check_inum_access(ff, rds.parent, W_OK);
> + ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
> if (ret)
> goto out;
>
> @@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> ret = fuse4fs_unlink(ff, path, &parent);
> if (ret)
> goto out;
> - ret = remove_inode(ff, child);
> + ret = fuse4fs_remove_inode(ff, child);
> if (ret)
> goto out;
>
> @@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
> *node_name = 0;
>
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 1)) {
> + if (!fuse4fs_can_allocate(ff, 1)) {
> ret = -ENOSPC;
> goto out2;
> }
> @@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
> goto out2;
> }
>
> - ret = check_inum_access(ff, parent, A_OK | W_OK);
> + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> if (ret)
> goto out2;
>
> @@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
> FUSE4FS_CHECK_CONTEXT(ff);
> dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 5)) {
> + if (!fuse4fs_can_allocate(ff, 5)) {
> ret = -ENOSPC;
> goto out;
> }
> @@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
> goto out;
> }
>
> - ret = check_inum_access(ff, from_ino, W_OK);
> + ret = fuse4fs_inum_access(ff, from_ino, W_OK);
> if (ret)
> goto out;
>
> if (to_ino) {
> - ret = check_inum_access(ff, to_ino, W_OK);
> + ret = fuse4fs_inum_access(ff, to_ino, W_OK);
> if (ret)
> goto out;
> }
> @@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
> goto out2;
> }
>
> - ret = check_inum_access(ff, from_dir_ino, W_OK);
> + ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
> if (ret)
> goto out2;
>
> @@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
> goto out2;
> }
>
> - ret = check_inum_access(ff, to_dir_ino, W_OK);
> + ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
> if (ret)
> goto out2;
>
> @@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
> *node_name = 0;
>
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 2)) {
> + if (!fuse4fs_can_allocate(ff, 2)) {
> ret = -ENOSPC;
> goto out2;
> }
> @@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
> goto out2;
> }
>
> - ret = check_inum_access(ff, parent, A_OK | W_OK);
> + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> if (ret)
> goto out2;
>
> @@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
> goto out2;
> }
>
> - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> if (ret)
> goto out2;
>
> @@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
> }
>
> /* Obtain group ids of the process that sent us a command(?) */
> -static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> +static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> {
> ext2_filsys fs = ff->fs;
> errcode_t err;
> @@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> * that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative
> * errno.
> */
> -static int in_file_group(struct fuse_context *ctxt,
> - const struct ext2_inode_large *inode)
> +static int fuse4fs_in_file_group(struct fuse_context *ctxt,
> + const struct ext2_inode_large *inode)
> {
> struct fuse4fs *ff = fuse4fs_get();
> gid_t *gids = NULL;
> @@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
> if (ctxt->gid == gid)
> return 1;
>
> - ret = get_req_groups(ff, &gids, &nr_gids);
> + ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
> if (ret == -ENOENT) {
> /* magic return code for "could not get caller group info" */
> return 0;
> @@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> goto out;
> }
>
> - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> if (ret)
> goto out;
>
> - if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> + if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> ret = -EPERM;
> goto out;
> }
> @@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> * of the user's groups, but FUSE only tells us about the primary
> * group.
> */
> - if (!is_superuser(ff, ctxt)) {
> - ret = in_file_group(ctxt, &inode);
> + if (!fuse4fs_is_superuser(ff, ctxt)) {
> + ret = fuse4fs_in_file_group(ctxt, &inode);
> if (ret < 0)
> goto out;
>
> @@ -3236,14 +3239,14 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
> goto out;
> }
>
> - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> if (ret)
> goto out;
>
> /* FUSE seems to feed us ~0 to mean "don't change" */
> if (owner != (uid_t) ~0) {
> /* Only root gets to change UID. */
> - if (want_check_owner(ff, ctxt) &&
> + if (fuse4fs_want_check_owner(ff, ctxt) &&
> !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
> ret = -EPERM;
> goto out;
> @@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
>
> if (group != (gid_t) ~0) {
> /* Only root or the owner get to change GID. */
> - if (want_check_owner(ff, ctxt) &&
> + if (fuse4fs_want_check_owner(ff, ctxt) &&
> inode_uid(inode) != ctxt->uid) {
> ret = -EPERM;
> goto out;
> @@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
> goto out;
> dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
>
> - ret = check_inum_access(ff, ino, W_OK);
> + ret = fuse4fs_inum_access(ff, ino, W_OK);
> if (ret)
> goto out;
>
> @@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> }
> dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
>
> - ret = check_inum_access(ff, file->ino, check);
> + ret = fuse4fs_inum_access(ff, file->ino, check);
> if (ret) {
> /*
> * In a regular (Linux) fs driver, the kernel will open
> @@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> * also employ undocumented hacks (see above).
> */
> if (check == R_OK) {
> - ret = check_inum_access(ff, file->ino, X_OK);
> + ret = fuse4fs_inum_access(ff, file->ino, X_OK);
> if (ret)
> goto out;
> check = X_OK;
> @@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
> goto out;
> }
>
> - if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> + if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> ret = -ENOSPC;
> goto out;
> }
> @@ -3768,11 +3771,11 @@ static int op_getxattr(const char *path, const char *key, char *value,
> }
> dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
>
> - ret = check_inum_access(ff, ino, R_OK);
> + ret = fuse4fs_inum_access(ff, ino, R_OK);
> if (ret)
> goto out;
>
> - ret = __getxattr(ff, ino, key, &ptr, &plen);
> + ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
> if (ret)
> goto out;
>
> @@ -3838,7 +3841,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
> }
> dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
>
> - ret = check_inum_access(ff, ino, R_OK);
> + ret = fuse4fs_inum_access(ff, ino, R_OK);
> if (ret)
> goto out;
>
> @@ -3919,7 +3922,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
> }
> dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
>
> - ret = check_inum_access(ff, ino, W_OK);
> + ret = fuse4fs_inum_access(ff, ino, W_OK);
> if (ret == -EACCES) {
> ret = -EPERM;
> goto out;
> @@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
> goto out;
> }
>
> - if (!fs_can_allocate(ff, 1)) {
> + if (!fuse4fs_can_allocate(ff, 1)) {
> ret = -ENOSPC;
> goto out;
> }
> @@ -4020,7 +4023,7 @@ static int op_removexattr(const char *path, const char *key)
> }
> dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
>
> - ret = check_inum_access(ff, ino, W_OK);
> + ret = fuse4fs_inum_access(ff, ino, W_OK);
> if (ret)
> goto out;
>
> @@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
> goto out;
> }
>
> - ret = check_inum_access(ff, ino, mask);
> + ret = fuse4fs_inum_access(ff, ino, mask);
> if (ret)
> goto out;
>
> @@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> *node_name = 0;
>
> fs = fuse4fs_start(ff);
> - if (!fs_can_allocate(ff, 1)) {
> + if (!fuse4fs_can_allocate(ff, 1)) {
> ret = -ENOSPC;
> goto out2;
> }
> @@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> goto out2;
> }
>
> - ret = check_inum_access(ff, parent, A_OK | W_OK);
> + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> if (ret)
> goto out2;
>
> @@ -4326,7 +4329,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
>
> ext2fs_inode_alloc_stats2(fs, child, 1, 0);
>
> - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> if (ret)
> goto out2;
>
> @@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
> */
> if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
> access |= A_OK;
> - ret = check_inum_access(ff, ino, access);
> + ret = fuse4fs_inum_access(ff, ino, access);
> if (ret)
> goto out;
>
> @@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> if (err)
> return translate_error(fs, fh->ino, err);
>
> - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> return -EPERM;
>
> ret = set_iflags(&inode, flags);
> @@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> if (err)
> return translate_error(fs, fh->ino, err);
>
> - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> return -EPERM;
>
> inode.i_generation = generation;
> @@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> if (err)
> return translate_error(fs, fh->ino, err);
>
> - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> return -EPERM;
>
> ret = set_xflags(&inode, fsx->fsx_xflags);
> @@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> struct fuse_context *ctxt = fuse_get_context();
> ext2_filsys fs = ff->fs;
>
> - if (!is_superuser(ff, ctxt))
> + if (!fuse4fs_is_superuser(ff, ctxt))
> return -EPERM;
>
> err_printf(ff, "%s.\n", _("shut down requested"));
> @@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> (unsigned long long)len,
> (unsigned long long)start,
> (unsigned long long)end);
> - if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> + if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> return -ENOSPC;
>
> err = fuse4fs_read_inode(fs, fh->ino, &inode);
> @@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> return err;
> }
>
> -static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> - struct ext2_inode_large *inode,
> - off_t offset, off_t len, char **buf)
> +static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
> + struct ext2_inode_large *inode,
> + off_t offset, off_t len, char **buf)
> {
> ext2_filsys fs = ff->fs;
> blk64_t blk;
> @@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> return io_channel_write_blk64(fs->io, blk, 1, *buf);
> }
>
> -static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
> - struct ext2_inode_large *inode, off_t offset,
> - int clean_before, char **buf)
> +static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
> + struct ext2_inode_large *inode, off_t offset,
> + int clean_before, char **buf)
> {
> ext2_filsys fs = ff->fs;
> blk64_t blk;
> @@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
>
> /* Zero everything before the first block and after the last block */
> if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
> - err = clean_block_middle(ff, fh->ino, &inode, offset,
> + err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
> len, &buf);
> else {
> - err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
> + err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
> if (!err)
> - err = clean_block_edge(ff, fh->ino, &inode,
> - offset + len, 1, &buf);
> + err = fuse4fs_zero_edge(ff, fh->ino, &inode,
> + offset + len, 1, &buf);
> }
> if (buf)
> ext2fs_free_mem(&buf);
>
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH 04/23] fuse4fs: namespace some helpers
2025-11-07 8:09 ` Amir Goldstein
@ 2025-11-08 0:25 ` Darrick J. Wong
0 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-08 0:25 UTC (permalink / raw)
To: Amir Goldstein; +Cc: tytso, linux-ext4
On Fri, Nov 07, 2025 at 09:09:58AM +0100, Amir Goldstein wrote:
> On Thu, Nov 06, 2025 at 02:44:07PM -0800, Darrick J. Wong wrote:
> > From: Darrick J. Wong <djwong@kernel.org>
> >
> > Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
> > object pointer.
> >
> > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
> > fuse4fs/fuse4fs.c | 177 +++++++++++++++++++++++++++--------------------------
> > 1 file changed, 90 insertions(+), 87 deletions(-)
> >
> >
> > diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
> > index daf22e0fe7fde5..2ef5ad60163639 100644
> > --- a/fuse4fs/fuse4fs.c
> > +++ b/fuse4fs/fuse4fs.c
> > @@ -2,6 +2,7 @@
> > * fuse4fs.c - FUSE low-level server for e2fsprogs.
> > *
> > * Copyright (C) 2014-2025 Oracle.
> > + * Copyright (C) 2025 CTERA Networks.
>
> I think this belongs to next patch :)
Moved; thanks for the feedback!
--D
> Thanks,
> Amir.
>
> > *
> > * %Begin-Header%
> > * This file may be redistributed under the terms of the GNU Public
> > @@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
> > return 0;
> > }
> >
> > -static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> > +static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> > {
> > ext2_filsys fs = ff->fs;
> > blk64_t reserved;
> > @@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> > return ext2fs_free_blocks_count(fs->super) > reserved + num;
> > }
> >
> > -static int fuse4fs_is_writeable(struct fuse4fs *ff)
> > +static int fuse4fs_is_writeable(const struct fuse4fs *ff)
> > {
> > return ff->opstate == F4OP_WRITABLE &&
> > (ff->fs->super->s_error_count == 0);
> > }
> >
> > -static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
> > +static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
> > + const struct fuse_context *ctxt)
> > {
> > if (ff->fakeroot)
> > return 1;
> > return ctxt->uid == 0;
> > }
> >
> > -static inline int want_check_owner(struct fuse4fs *ff,
> > - struct fuse_context *ctxt)
> > +static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
> > + const struct fuse_context *ctxt)
> > {
> > /*
> > * The kernel is responsible for access control, so we allow anything
> > @@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
> > */
> > if (ff->kernel)
> > return 0;
> > - return !is_superuser(ff, ctxt);
> > + return !fuse4fs_is_superuser(ff, ctxt);
> > }
> >
> > /* Test for append permission */
> > #define A_OK 16
> >
> > -static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> > - const struct ext2_inode *inode, int mask)
> > +static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> > + const struct ext2_inode *inode, int mask)
> > {
> > EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
> >
> > @@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> > return 0;
> > }
> >
> > -static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> > +static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> > {
> > struct fuse_context *ctxt = fuse_get_context();
> > ext2_filsys fs = ff->fs;
> > @@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> > if (mask == 0)
> > return 0;
> >
> > - ret = check_iflags_access(ff, ino, &inode, mask);
> > + ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
> > if (ret)
> > return ret;
> >
> > @@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> > return 0;
> >
> > /* Figure out what root's allowed to do */
> > - if (is_superuser(ff, ctxt)) {
> > + if (fuse4fs_is_superuser(ff, ctxt)) {
> > /* Non-file access always ok */
> > if (!LINUX_S_ISREG(inode.i_mode))
> > return 0;
> > @@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
> > return ret;
> > }
> >
> > -static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > - void **value, size_t *value_len)
> > +static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
> > + const char *name, void **value, size_t *value_len)
> > {
> > ext2_filsys fs = ff->fs;
> > struct ext2_xattr_handle *h;
> > @@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > return ret;
> > }
> >
> > -static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > - void *value, size_t valuelen)
> > +static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
> > + const char *name, void *value, size_t valuelen)
> > {
> > ext2_filsys fs = ff->fs;
> > struct ext2_xattr_handle *h;
> > @@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > return ret;
> > }
> >
> > -static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > - ext2_ino_t child, mode_t mode)
> > +static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > + ext2_ino_t child, mode_t mode)
> > {
> > void *def;
> > size_t deflen;
> > @@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > if (!ff->acl || S_ISDIR(mode))
> > return 0;
> >
> > - ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> > - &deflen);
> > + ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> > + &deflen);
> > switch (ret) {
> > case -ENODATA:
> > case -ENOENT:
> > @@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > return ret;
> > }
> >
> > - ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
> > + ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
> > + deflen);
> > ext2fs_free_mem(&def);
> > return ret;
> > }
> > @@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> > *node_name = 0;
> >
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 2)) {
> > + if (!fuse4fs_can_allocate(ff, 2)) {
> > ret = -ENOSPC;
> > goto out2;
> > }
> > @@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, parent, A_OK | W_OK);
> > + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -2079,7 +2082,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> >
> > ext2fs_inode_alloc_stats2(fs, child, 1, 0);
> >
> > - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> > + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> > if (ret)
> > goto out2;
> >
> > @@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
> > *node_name = 0;
> >
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 1)) {
> > + if (!fuse4fs_can_allocate(ff, 1)) {
> > ret = -ENOSPC;
> > goto out2;
> > }
> > @@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, parent, A_OK | W_OK);
> > + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
> > goto out3;
> > }
> >
> > - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> > + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> > if (ret)
> > goto out3;
> >
> > @@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> > base_name = filename;
> > }
> >
> > - ret = check_inum_access(ff, dir, W_OK);
> > + ret = fuse4fs_inum_access(ff, dir, W_OK);
> > if (ret) {
> > free(filename);
> > return ret;
> > @@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> > return 0;
> > }
> >
> > -static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> > - struct ext2_inode_large *inode)
> > +static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> > + struct ext2_inode_large *inode)
> > {
> > ext2_filsys fs = ff->fs;
> > struct ext2_xattr_handle *h;
> > @@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> > return 0;
> > }
> >
> > -static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> > +static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> > {
> > ext2_filsys fs = ff->fs;
> > errcode_t err;
> > @@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> > goto write_out;
> >
> > if (ext2fs_has_feature_ea_inode(fs->super)) {
> > - ret = remove_ea_inodes(ff, ino, &inode);
> > + ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
> > if (ret)
> > return ret;
> > }
> > @@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> > goto out;
> > }
> >
> > - ret = check_inum_access(ff, ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, ino, W_OK);
> > if (ret)
> > goto out;
> >
> > @@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> > if (ret)
> > goto out;
> >
> > - ret = remove_inode(ff, ino);
> > + ret = fuse4fs_remove_inode(ff, ino);
> > if (ret)
> > goto out;
> >
> > @@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> > }
> > dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
> >
> > - ret = check_inum_access(ff, child, W_OK);
> > + ret = fuse4fs_inum_access(ff, child, W_OK);
> > if (ret)
> > goto out;
> >
> > @@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> > goto out;
> > }
> >
> > - ret = check_inum_access(ff, rds.parent, W_OK);
> > + ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
> > if (ret)
> > goto out;
> >
> > @@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> > ret = fuse4fs_unlink(ff, path, &parent);
> > if (ret)
> > goto out;
> > - ret = remove_inode(ff, child);
> > + ret = fuse4fs_remove_inode(ff, child);
> > if (ret)
> > goto out;
> >
> > @@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
> > *node_name = 0;
> >
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 1)) {
> > + if (!fuse4fs_can_allocate(ff, 1)) {
> > ret = -ENOSPC;
> > goto out2;
> > }
> > @@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, parent, A_OK | W_OK);
> > + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
> > FUSE4FS_CHECK_CONTEXT(ff);
> > dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 5)) {
> > + if (!fuse4fs_can_allocate(ff, 5)) {
> > ret = -ENOSPC;
> > goto out;
> > }
> > @@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
> > goto out;
> > }
> >
> > - ret = check_inum_access(ff, from_ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, from_ino, W_OK);
> > if (ret)
> > goto out;
> >
> > if (to_ino) {
> > - ret = check_inum_access(ff, to_ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, to_ino, W_OK);
> > if (ret)
> > goto out;
> > }
> > @@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, from_dir_ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, to_dir_ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
> > *node_name = 0;
> >
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 2)) {
> > + if (!fuse4fs_can_allocate(ff, 2)) {
> > ret = -ENOSPC;
> > goto out2;
> > }
> > @@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, parent, A_OK | W_OK);
> > + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
> > goto out2;
> > }
> >
> > - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
> > }
> >
> > /* Obtain group ids of the process that sent us a command(?) */
> > -static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> > +static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> > {
> > ext2_filsys fs = ff->fs;
> > errcode_t err;
> > @@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> > * that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative
> > * errno.
> > */
> > -static int in_file_group(struct fuse_context *ctxt,
> > - const struct ext2_inode_large *inode)
> > +static int fuse4fs_in_file_group(struct fuse_context *ctxt,
> > + const struct ext2_inode_large *inode)
> > {
> > struct fuse4fs *ff = fuse4fs_get();
> > gid_t *gids = NULL;
> > @@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
> > if (ctxt->gid == gid)
> > return 1;
> >
> > - ret = get_req_groups(ff, &gids, &nr_gids);
> > + ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
> > if (ret == -ENOENT) {
> > /* magic return code for "could not get caller group info" */
> > return 0;
> > @@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> > goto out;
> > }
> >
> > - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > if (ret)
> > goto out;
> >
> > - if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> > + if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> > ret = -EPERM;
> > goto out;
> > }
> > @@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> > * of the user's groups, but FUSE only tells us about the primary
> > * group.
> > */
> > - if (!is_superuser(ff, ctxt)) {
> > - ret = in_file_group(ctxt, &inode);
> > + if (!fuse4fs_is_superuser(ff, ctxt)) {
> > + ret = fuse4fs_in_file_group(ctxt, &inode);
> > if (ret < 0)
> > goto out;
> >
> > @@ -3236,14 +3239,14 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
> > goto out;
> > }
> >
> > - ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > + ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > if (ret)
> > goto out;
> >
> > /* FUSE seems to feed us ~0 to mean "don't change" */
> > if (owner != (uid_t) ~0) {
> > /* Only root gets to change UID. */
> > - if (want_check_owner(ff, ctxt) &&
> > + if (fuse4fs_want_check_owner(ff, ctxt) &&
> > !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
> > ret = -EPERM;
> > goto out;
> > @@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
> >
> > if (group != (gid_t) ~0) {
> > /* Only root or the owner get to change GID. */
> > - if (want_check_owner(ff, ctxt) &&
> > + if (fuse4fs_want_check_owner(ff, ctxt) &&
> > inode_uid(inode) != ctxt->uid) {
> > ret = -EPERM;
> > goto out;
> > @@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
> > goto out;
> > dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
> >
> > - ret = check_inum_access(ff, ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, ino, W_OK);
> > if (ret)
> > goto out;
> >
> > @@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> > }
> > dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
> >
> > - ret = check_inum_access(ff, file->ino, check);
> > + ret = fuse4fs_inum_access(ff, file->ino, check);
> > if (ret) {
> > /*
> > * In a regular (Linux) fs driver, the kernel will open
> > @@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> > * also employ undocumented hacks (see above).
> > */
> > if (check == R_OK) {
> > - ret = check_inum_access(ff, file->ino, X_OK);
> > + ret = fuse4fs_inum_access(ff, file->ino, X_OK);
> > if (ret)
> > goto out;
> > check = X_OK;
> > @@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
> > goto out;
> > }
> >
> > - if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> > + if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> > ret = -ENOSPC;
> > goto out;
> > }
> > @@ -3768,11 +3771,11 @@ static int op_getxattr(const char *path, const char *key, char *value,
> > }
> > dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
> >
> > - ret = check_inum_access(ff, ino, R_OK);
> > + ret = fuse4fs_inum_access(ff, ino, R_OK);
> > if (ret)
> > goto out;
> >
> > - ret = __getxattr(ff, ino, key, &ptr, &plen);
> > + ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
> > if (ret)
> > goto out;
> >
> > @@ -3838,7 +3841,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
> > }
> > dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
> >
> > - ret = check_inum_access(ff, ino, R_OK);
> > + ret = fuse4fs_inum_access(ff, ino, R_OK);
> > if (ret)
> > goto out;
> >
> > @@ -3919,7 +3922,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
> > }
> > dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
> >
> > - ret = check_inum_access(ff, ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, ino, W_OK);
> > if (ret == -EACCES) {
> > ret = -EPERM;
> > goto out;
> > @@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
> > goto out;
> > }
> >
> > - if (!fs_can_allocate(ff, 1)) {
> > + if (!fuse4fs_can_allocate(ff, 1)) {
> > ret = -ENOSPC;
> > goto out;
> > }
> > @@ -4020,7 +4023,7 @@ static int op_removexattr(const char *path, const char *key)
> > }
> > dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
> >
> > - ret = check_inum_access(ff, ino, W_OK);
> > + ret = fuse4fs_inum_access(ff, ino, W_OK);
> > if (ret)
> > goto out;
> >
> > @@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
> > goto out;
> > }
> >
> > - ret = check_inum_access(ff, ino, mask);
> > + ret = fuse4fs_inum_access(ff, ino, mask);
> > if (ret)
> > goto out;
> >
> > @@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> > *node_name = 0;
> >
> > fs = fuse4fs_start(ff);
> > - if (!fs_can_allocate(ff, 1)) {
> > + if (!fuse4fs_can_allocate(ff, 1)) {
> > ret = -ENOSPC;
> > goto out2;
> > }
> > @@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> > goto out2;
> > }
> >
> > - ret = check_inum_access(ff, parent, A_OK | W_OK);
> > + ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> > if (ret)
> > goto out2;
> >
> > @@ -4326,7 +4329,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> >
> > ext2fs_inode_alloc_stats2(fs, child, 1, 0);
> >
> > - ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> > + ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> > if (ret)
> > goto out2;
> >
> > @@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
> > */
> > if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
> > access |= A_OK;
> > - ret = check_inum_access(ff, ino, access);
> > + ret = fuse4fs_inum_access(ff, ino, access);
> > if (ret)
> > goto out;
> >
> > @@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> > if (err)
> > return translate_error(fs, fh->ino, err);
> >
> > - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > return -EPERM;
> >
> > ret = set_iflags(&inode, flags);
> > @@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> > if (err)
> > return translate_error(fs, fh->ino, err);
> >
> > - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > return -EPERM;
> >
> > inode.i_generation = generation;
> > @@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> > if (err)
> > return translate_error(fs, fh->ino, err);
> >
> > - if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > + if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > return -EPERM;
> >
> > ret = set_xflags(&inode, fsx->fsx_xflags);
> > @@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> > struct fuse_context *ctxt = fuse_get_context();
> > ext2_filsys fs = ff->fs;
> >
> > - if (!is_superuser(ff, ctxt))
> > + if (!fuse4fs_is_superuser(ff, ctxt))
> > return -EPERM;
> >
> > err_printf(ff, "%s.\n", _("shut down requested"));
> > @@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> > (unsigned long long)len,
> > (unsigned long long)start,
> > (unsigned long long)end);
> > - if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> > + if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> > return -ENOSPC;
> >
> > err = fuse4fs_read_inode(fs, fh->ino, &inode);
> > @@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> > return err;
> > }
> >
> > -static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> > - struct ext2_inode_large *inode,
> > - off_t offset, off_t len, char **buf)
> > +static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
> > + struct ext2_inode_large *inode,
> > + off_t offset, off_t len, char **buf)
> > {
> > ext2_filsys fs = ff->fs;
> > blk64_t blk;
> > @@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> > return io_channel_write_blk64(fs->io, blk, 1, *buf);
> > }
> >
> > -static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
> > - struct ext2_inode_large *inode, off_t offset,
> > - int clean_before, char **buf)
> > +static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
> > + struct ext2_inode_large *inode, off_t offset,
> > + int clean_before, char **buf)
> > {
> > ext2_filsys fs = ff->fs;
> > blk64_t blk;
> > @@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
> >
> > /* Zero everything before the first block and after the last block */
> > if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
> > - err = clean_block_middle(ff, fh->ino, &inode, offset,
> > + err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
> > len, &buf);
> > else {
> > - err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
> > + err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
> > if (!err)
> > - err = clean_block_edge(ff, fh->ino, &inode,
> > - offset + len, 1, &buf);
> > + err = fuse4fs_zero_edge(ff, fh->ino, &inode,
> > + offset + len, 1, &buf);
> > }
> > if (buf)
> > ext2fs_free_mem(&buf);
> >
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH 05/23] fuse4fs: convert to low level API
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (3 preceding siblings ...)
2025-11-06 22:44 ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
@ 2025-11-06 22:44 ` Darrick J. Wong
2025-11-06 22:44 ` [PATCH 06/23] libsupport: port the kernel list.h to libsupport Darrick J. Wong
` (17 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
To: tytso; +Cc: amir73il, linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Convert fuse4fs to the lowlevel fuse API. Amir supplied the auto
translation; I ported and cleaned it up by hand, and did the QA work to
make sure it still runs correctly.
Co-developed-by: Claude claude-4-sonnet
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fuse4fs/fuse4fs.c | 2018 ++++++++++++++++++++++++++++-------------------------
1 file changed, 1074 insertions(+), 944 deletions(-)
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 2ef5ad60163639..7585f1ff346d84 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -40,7 +40,7 @@
# define __SET_FOB_FOR_FUSE
# define _FILE_OFFSET_BITS 64
#endif /* _FILE_OFFSET_BITS */
-#include <fuse.h>
+#include <fuse_lowlevel.h>
#ifdef __SET_FOB_FOR_FUSE
# undef _FILE_OFFSET_BITS
#endif /* __SET_FOB_FOR_FUSE */
@@ -116,6 +116,8 @@
#endif
#endif /* !defined(ENODATA) */
+#define FUSE4FS_ATTR_TIMEOUT (0.0)
+
static inline uint64_t round_up(uint64_t b, unsigned int align)
{
unsigned int m;
@@ -258,16 +260,18 @@ struct fuse4fs {
/* options set by fuse_opt_parse must be of type int */
int timing;
#endif
+ struct fuse_session *fuse;
};
-#define FUSE4FS_CHECK_HANDLE(ff, fh) \
+#define FUSE4FS_CHECK_HANDLE(req, fh) \
do { \
if ((fh) == NULL || (fh)->magic != FUSE4FS_FILE_MAGIC) { \
fprintf(stderr, \
"FUSE4FS: Corrupt in-memory file handle at %s:%d!\n", \
__func__, __LINE__); \
fflush(stderr); \
- return -EUCLEAN; \
+ fuse_reply_err(req, EUCLEAN); \
+ return; \
} \
} while (0)
@@ -285,12 +289,43 @@ struct fuse4fs {
} \
} while (0)
-#define FUSE4FS_CHECK_CONTEXT(ff) \
- __FUSE4FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
-#define FUSE4FS_CHECK_CONTEXT_DESTROY(ff) \
- __FUSE4FS_CHECK_CONTEXT((ff), return, /* do not return */)
-#define FUSE4FS_CHECK_CONTEXT_INIT(ff) \
- __FUSE4FS_CHECK_CONTEXT((ff), abort(), abort())
+#define FUSE4FS_CHECK_CONTEXT(req) \
+ __FUSE4FS_CHECK_CONTEXT(fuse4fs_get(req), \
+ fuse_reply_err((req), EUCLEAN); return, \
+ fuse_reply_err((req), EIO); return)
+#define FUSE4FS_CHECK_CONTEXT_DESTROY(req) \
+ __FUSE4FS_CHECK_CONTEXT((req), return, /* do not return */)
+#define FUSE4FS_CHECK_CONTEXT_INIT(req) \
+ __FUSE4FS_CHECK_CONTEXT((req), abort(), abort())
+
+static inline void fuse4fs_ino_from_fuse(ext2_ino_t *inop, fuse_ino_t fino)
+{
+ if (fino == FUSE_ROOT_ID)
+ *inop = EXT2_ROOT_INO;
+ else
+ *inop = fino;
+}
+
+static inline void fuse4fs_ino_to_fuse(fuse_ino_t *finop, ext2_ino_t ino)
+{
+ if (ino == EXT2_ROOT_INO)
+ *finop = FUSE_ROOT_ID;
+ else
+ *finop = ino;
+}
+
+#define FUSE4FS_CONVERT_FINO(req, ext2_inop, fuse_ino) \
+ do { \
+ if ((fuse_ino) > UINT32_MAX) { \
+ fprintf(stderr, \
+ "FUSE4FS: Bogus inode number 0x%llx at %s:%d!\n", \
+ (unsigned long long)(fuse_ino), __func__, __LINE__); \
+ fflush(stderr); \
+ fuse_reply_err((req), EIO); \
+ return; \
+ } \
+ fuse4fs_ino_from_fuse(ext2_inop, fuse_ino); \
+ } while (0)
static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
const char *func, int line);
@@ -674,11 +709,9 @@ static void fuse4fs_mmp_destroy(struct fuse4fs *ff)
# define fuse4fs_mmp_destroy(...) ((void)0)
#endif
-static inline struct fuse4fs *fuse4fs_get(void)
+static inline struct fuse4fs *fuse4fs_get(fuse_req_t req)
{
- struct fuse_context *ctxt = fuse_get_context();
-
- return ctxt->private_data;
+ return (struct fuse4fs *)fuse_req_userdata(req);
}
static inline struct fuse4fs_file_handle *
@@ -691,6 +724,7 @@ static inline void
fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh)
{
fp->fh = (uintptr_t)fh;
+ fp->keep_cache = 1;
}
static void get_now(struct timespec *now)
@@ -887,7 +921,7 @@ static int fuse4fs_is_writeable(const struct fuse4fs *ff)
}
static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
- const struct fuse_context *ctxt)
+ const struct fuse_ctx *ctxt)
{
if (ff->fakeroot)
return 1;
@@ -895,7 +929,7 @@ static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
}
static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
- const struct fuse_context *ctxt)
+ const struct fuse_ctx *ctxt)
{
/*
* The kernel is responsible for access control, so we allow anything
@@ -938,9 +972,9 @@ static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
return 0;
}
-static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ ext2_ino_t ino, int mask)
{
- struct fuse_context *ctxt = fuse_get_context();
ext2_filsys fs = ff->fs;
struct ext2_inode inode;
mode_t perms;
@@ -1372,9 +1406,9 @@ static int fuse4fs_mount(struct fuse4fs *ff)
return 0;
}
-static void op_destroy(void *p EXT2FS_ATTR((unused)))
+static void op_destroy(void *userdata)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = userdata;
ext2_filsys fs;
errcode_t err;
@@ -1546,24 +1580,13 @@ static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
}
#endif
-static void *op_init(struct fuse_conn_info *conn,
- struct fuse_config *cfg EXT2FS_ATTR((unused)))
+static void op_init(void *userdata, struct fuse_conn_info *conn)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = userdata;
ext2_filsys fs;
FUSE4FS_CHECK_CONTEXT_INIT(ff);
- /*
- * Configure logging a second time, because libfuse might have
- * redirected std{out,err} as part of daemonization. If this fails,
- * give up and move on.
- */
- fuse4fs_setup_logging(ff);
- if (ff->logfd >= 0)
- close(ff->logfd);
- ff->logfd = -1;
-
fs = ff->fs;
dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
#ifdef FUSE_CAP_IOCTL_DIR
@@ -1580,10 +1603,6 @@ static void *op_init(struct fuse_conn_info *conn,
fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
#endif
conn->time_gran = 1;
- cfg->use_ino = 1;
- if (ff->debug)
- cfg->debug = 1;
- cfg->nullpath_ok = 1;
if (ff->opstate == F4OP_WRITABLE)
fuse4fs_read_bitmaps(ff);
@@ -1608,132 +1627,151 @@ static void *op_init(struct fuse_conn_info *conn,
*/
conn->want = conn->want_ext & 0xFFFFFFFF;
#endif
- return ff;
}
-static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+struct fuse4fs_stat {
+ struct fuse_entry_param entry;
+};
+
+static int fuse4fs_stat_inode(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inodep,
+ struct fuse4fs_stat *fstat)
{
struct ext2_inode_large inode;
+ ext2_filsys fs = ff->fs;
+ struct fuse_entry_param *entry = &fstat->entry;
+ struct stat *statbuf = &entry->attr;
dev_t fakedev = 0;
errcode_t err;
- int ret = 0;
struct timespec tv;
- err = fuse4fs_read_inode(fs, ino, &inode);
- if (err)
- return translate_error(fs, ino, err);
+ memset(fstat, 0, sizeof(*fstat));
+
+ if (!inodep) {
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+ inodep = &inode;
+ }
memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
statbuf->st_dev = fakedev;
statbuf->st_ino = ino;
- statbuf->st_mode = inode.i_mode;
- statbuf->st_nlink = inode.i_links_count;
- statbuf->st_uid = inode_uid(inode);
- statbuf->st_gid = inode_gid(inode);
- statbuf->st_size = EXT2_I_SIZE(&inode);
+ statbuf->st_mode = inodep->i_mode;
+ statbuf->st_nlink = inodep->i_links_count;
+ statbuf->st_uid = inode_uid(*inodep);
+ statbuf->st_gid = inode_gid(*inodep);
+ statbuf->st_size = EXT2_I_SIZE(inodep);
statbuf->st_blksize = fs->blocksize;
statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
- EXT2_INODE(&inode));
- EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+ EXT2_INODE(inodep));
+ EXT4_INODE_GET_XTIME(i_atime, &tv, inodep);
#if HAVE_STRUCT_STAT_ST_ATIM
statbuf->st_atim = tv;
#else
statbuf->st_atime = tv.tv_sec;
#endif
- EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+ EXT4_INODE_GET_XTIME(i_mtime, &tv, inodep);
#if HAVE_STRUCT_STAT_ST_ATIM
statbuf->st_mtim = tv;
#else
statbuf->st_mtime = tv.tv_sec;
#endif
- EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+ EXT4_INODE_GET_XTIME(i_ctime, &tv, inodep);
#if HAVE_STRUCT_STAT_ST_ATIM
statbuf->st_ctim = tv;
#else
statbuf->st_ctime = tv.tv_sec;
#endif
- if (LINUX_S_ISCHR(inode.i_mode) ||
- LINUX_S_ISBLK(inode.i_mode)) {
- if (inode.i_block[0])
- statbuf->st_rdev = inode.i_block[0];
+ if (LINUX_S_ISCHR(inodep->i_mode) ||
+ LINUX_S_ISBLK(inodep->i_mode)) {
+ if (inodep->i_block[0])
+ statbuf->st_rdev = inodep->i_block[0];
else
- statbuf->st_rdev = inode.i_block[1];
+ statbuf->st_rdev = inodep->i_block[1];
}
- return ret;
-}
-
-static int __fuse4fs_file_ino(struct fuse4fs *ff, const char *path,
- struct fuse_file_info *fp EXT2FS_ATTR((unused)),
- ext2_ino_t *inop,
- const char *func,
- int line)
-{
- ext2_filsys fs = ff->fs;
- errcode_t err;
-
- if (fp) {
- struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
-
- if (fh->ino == 0)
- return -ESTALE;
-
- *inop = fh->ino;
- dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
- return 0;
- }
-
- dbg_printf(ff, "%s: get path=%s\n", func, path);
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
- if (err)
- return __translate_error(fs, 0, err, func, line);
+ fuse4fs_ino_to_fuse(&entry->ino, ino);
+ entry->generation = inodep->i_generation;
+ entry->attr_timeout = FUSE4FS_ATTR_TIMEOUT;
+ entry->entry_timeout = FUSE4FS_ATTR_TIMEOUT;
return 0;
}
-# define fuse4fs_file_ino(ff, path, fp, inop) \
- __fuse4fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
-
-static int op_getattr(const char *path, struct stat *statbuf,
- struct fuse_file_info *fi)
+static void op_lookup(fuse_req_t req, fuse_ino_t fino, const char *name)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_stat fstat;
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
- ext2_ino_t ino;
+ ext2_ino_t parent, child;
+ errcode_t err;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ dbg_printf(ff, "%s: parent=%d name='%s'\n", __func__, parent, name);
fs = fuse4fs_start(ff);
- ret = fuse4fs_file_ino(ff, path, fi, &ino);
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
+ if (err || child == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
if (ret)
goto out;
- ret = stat_inode(fs, ino, statbuf);
+
out:
fuse4fs_finish(ff, ret);
- return ret;
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_entry(req, &fstat.entry);
}
-static int op_readlink(const char *path, char *buf, size_t len)
+static void op_getattr(fuse_req_t req, fuse_ino_t fino,
+ struct fuse_file_info *fi EXT2FS_ATTR((unused)))
{
- struct fuse4fs *ff = fuse4fs_get();
- ext2_filsys fs;
- errcode_t err;
+ struct fuse4fs_stat fstat;
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ fuse4fs_start(ff);
+ ret = fuse4fs_stat_inode(ff, ino, NULL, &fstat);
+ fuse4fs_finish(ff, ret);
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_attr(req, &fstat.entry.attr,
+ fstat.entry.attr_timeout);
+}
+
+static void op_readlink(fuse_req_t req, fuse_ino_t fino)
+{
struct ext2_inode inode;
+ char buf[PATH_MAX + 1];
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_filsys fs;
+ ext2_file_t file;
+ errcode_t err;
+ ext2_ino_t ino;
+ size_t len = PATH_MAX;
unsigned int got;
- ext2_file_t file;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s\n", __func__, path);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
fs = fuse4fs_start(ff);
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- err = ext2fs_read_inode(fs, ino, &inode);
+ err = ext2fs_read_inode(fs, fino, &inode);
if (err) {
ret = translate_error(fs, ino, err);
goto out;
@@ -1744,7 +1782,6 @@ static int op_readlink(const char *path, char *buf, size_t len)
goto out;
}
- len--;
if (inode.i_size < len)
len = inode.i_size;
if (ext2fs_is_fast_symlink(&inode))
@@ -1782,7 +1819,11 @@ static int op_readlink(const char *path, char *buf, size_t len)
out:
fuse4fs_finish(ff, ret);
- return ret;
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_readlink(req, buf);
}
static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
@@ -1888,11 +1929,12 @@ static inline void fuse4fs_set_gid(struct ext2_inode_large *inode, gid_t gid)
ext2fs_set_i_gid_high(*inode, gid >> 16);
}
-static int fuse4fs_new_child_gid(struct fuse4fs *ff, ext2_ino_t parent,
- gid_t *gid, int *parent_sgid)
+static int fuse4fs_new_child_gid(struct fuse4fs *ff,
+ const struct fuse_ctx *ctxt,
+ ext2_ino_t parent, gid_t *gid,
+ int *parent_sgid)
{
struct ext2_inode_large inode;
- struct fuse_context *ctxt = fuse_get_context();
errcode_t err;
err = fuse4fs_read_inode(ff->fs, parent, &inode);
@@ -1968,36 +2010,44 @@ static void fuse4fs_set_extra_isize(struct fuse4fs *ff, ext2_ino_t ino,
inode->i_extra_isize = extra;
}
-static int op_mknod(const char *path, mode_t mode, dev_t dev)
+static void fuse4fs_reply_entry(fuse_req_t req, ext2_ino_t ino,
+ struct ext2_inode_large *inode, int ret)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs_stat fstat;
+ struct fuse4fs *ff = fuse4fs_get(req);
+
+ if (ret) {
+ fuse_reply_err(req, -ret);
+ return;
+ }
+
+ /* Get stat info for the new entry */
+ ret = fuse4fs_stat_inode(ff, ino, inode, &fstat);
+ if (ret) {
+ fuse_reply_err(req, -ret);
+ return;
+ }
+
+ fuse_reply_entry(req, &fstat.entry);
+}
+
+static void op_mknod(fuse_req_t req, fuse_ino_t fino, const char *name,
+ mode_t mode, dev_t dev)
+{
+ struct ext2_inode_large inode;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
ext2_ino_t parent, child;
- char *temp_path;
errcode_t err;
- char *node_name, a;
int filetype;
- struct ext2_inode_large inode;
gid_t gid;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
- (unsigned int)dev);
- temp_path = strdup(path);
- if (!temp_path) {
- ret = -ENOMEM;
- goto out;
- }
- node_name = strrchr(temp_path, '/');
- if (!node_name) {
- ret = -ENOMEM;
- goto out;
- }
- node_name++;
- a = *node_name;
- *node_name = 0;
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o dev=0x%x\n",
+ __func__, parent, name, mode, (unsigned int)dev);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 2)) {
@@ -2005,33 +2055,14 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
goto out2;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &parent);
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
-
- ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
if (ret)
goto out2;
- *node_name = a;
+ /* On a low level server, mknod handles all non-directory types */
+ filetype = ext2_file_type(mode);
- if (LINUX_S_ISCHR(mode))
- filetype = EXT2_FT_CHRDEV;
- else if (LINUX_S_ISBLK(mode))
- filetype = EXT2_FT_BLKDEV;
- else if (LINUX_S_ISFIFO(mode))
- filetype = EXT2_FT_FIFO;
- else if (LINUX_S_ISSOCK(mode))
- filetype = EXT2_FT_SOCK;
- else {
- ret = -EINVAL;
- goto out2;
- }
-
- err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
if (err)
goto out2;
@@ -2041,9 +2072,9 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
goto out2;
}
- dbg_printf(ff, "%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
- node_name, parent);
- err = ext2fs_link(fs, parent, node_name, child,
+ dbg_printf(ff, "%s: create ino=%d name='%s' in dir=%d\n", __func__,
+ child, name, parent);
+ err = ext2fs_link(fs, parent, name, child,
filetype | EXT2FS_LINK_EXPAND);
if (err) {
ret = translate_error(fs, parent, err);
@@ -2092,42 +2123,28 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
out2:
fuse4fs_finish(ff, ret);
-out:
- free(temp_path);
- return ret;
+ fuse4fs_reply_entry(req, child, &inode, ret);
}
-static int op_mkdir(const char *path, mode_t mode)
+static void op_mkdir(fuse_req_t req, fuse_ino_t fino, const char *name,
+ mode_t mode)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
+ struct ext2_inode_large inode;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
ext2_ino_t parent, child;
- char *temp_path;
errcode_t err;
- char *node_name, a;
- struct ext2_inode_large inode;
char *block;
blk64_t blk;
int ret = 0;
gid_t gid;
int parent_sgid;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
- temp_path = strdup(path);
- if (!temp_path) {
- ret = -ENOMEM;
- goto out;
- }
- node_name = strrchr(temp_path, '/');
- if (!node_name) {
- ret = -ENOMEM;
- goto out;
- }
- node_name++;
- a = *node_name;
- *node_name = 0;
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o\n",
+ __func__, parent, name, mode);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 1)) {
@@ -2135,25 +2152,15 @@ static int op_mkdir(const char *path, mode_t mode)
goto out2;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &parent);
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
-
- ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
if (ret)
goto out2;
- err = fuse4fs_new_child_gid(ff, parent, &gid, &parent_sgid);
+ err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, &parent_sgid);
if (err)
goto out2;
- *node_name = a;
-
- err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND,
- node_name, NULL);
+ err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND, name, NULL);
if (err) {
ret = translate_error(fs, parent, err);
goto out2;
@@ -2164,14 +2171,13 @@ static int op_mkdir(const char *path, mode_t mode)
goto out2;
/* Still have to update the uid/gid of the dir */
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &child);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
if (err) {
ret = translate_error(fs, 0, err);
goto out2;
}
- dbg_printf(ff, "%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
- node_name, parent);
+ dbg_printf(ff, "%s: created ino=%d name='%s' in dir=%d\n",
+ __func__, child, name, parent);
err = fuse4fs_read_inode(fs, child, &inode);
if (err) {
@@ -2227,55 +2233,7 @@ static int op_mkdir(const char *path, mode_t mode)
ext2fs_free_mem(&block);
out2:
fuse4fs_finish(ff, ret);
-out:
- free(temp_path);
- return ret;
-}
-
-static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
- ext2_ino_t *parent)
-{
- ext2_filsys fs = ff->fs;
- errcode_t err;
- ext2_ino_t dir;
- char *filename = strdup(path);
- char *base_name;
- int ret;
-
- base_name = strrchr(filename, '/');
- if (base_name) {
- *base_name++ = '\0';
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
- &dir);
- if (err) {
- free(filename);
- return translate_error(fs, 0, err);
- }
- } else {
- dir = EXT2_ROOT_INO;
- base_name = filename;
- }
-
- ret = fuse4fs_inum_access(ff, dir, W_OK);
- if (ret) {
- free(filename);
- return ret;
- }
-
- dbg_printf(ff, "%s: unlinking name=%s from dir=%d\n", __func__,
- base_name, dir);
- err = ext2fs_unlink(fs, dir, base_name, 0, 0);
- free(filename);
- if (err)
- return translate_error(fs, dir, err);
-
- ret = update_mtime(fs, dir, NULL);
- if (ret)
- return ret;
-
- if (parent)
- *parent = dir;
- return 0;
+ fuse4fs_reply_entry(req, child, &inode, ret);
}
static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
@@ -2397,49 +2355,78 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
return 0;
}
-static int __op_unlink(struct fuse4fs *ff, const char *path)
+static int fuse4fs_unlink(struct fuse4fs *ff, ext2_ino_t parent,
+ const char *name, ext2_ino_t child)
{
ext2_filsys fs = ff->fs;
- ext2_ino_t parent, ino;
errcode_t err;
int ret = 0;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ err = ext2fs_unlink(fs, parent, name, child, 0);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+
+static int fuse4fs_rmfile(struct fuse4fs *ff, ext2_ino_t parent,
+ const char *name, ext2_ino_t child)
+{
+ int ret;
+
+ ret = fuse4fs_unlink(ff, parent, name, child);
+ if (ret)
+ return ret;
+
+ return fuse4fs_remove_inode(ff, child);
+}
+
+static void op_unlink(fuse_req_t req, fuse_ino_t fino, const char *name)
+{
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ errcode_t err;
+ int ret;
+
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ fs = fuse4fs_start(ff);
+
+ /* Get the inode number for the file */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
if (err) {
ret = translate_error(fs, 0, err);
goto out;
}
- ret = fuse4fs_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, child, W_OK);
if (ret)
goto out;
- ret = fuse4fs_unlink(ff, path, &parent);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, W_OK);
if (ret)
goto out;
- ret = fuse4fs_remove_inode(ff, ino);
+ dbg_printf(ff, "%s: unlink parent=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
+ ret = fuse4fs_rmfile(ff, parent, name, child);
if (ret)
goto out;
ret = fuse4fs_dirsync_flush(ff, parent, NULL);
if (ret)
goto out;
-
out:
- return ret;
-}
-
-static int op_unlink(const char *path)
-{
- struct fuse4fs *ff = fuse4fs_get();
- int ret;
-
- FUSE4FS_CHECK_CONTEXT(ff);
- fuse4fs_start(ff);
- ret = __op_unlink(ff, path);
fuse4fs_finish(ff, ret);
- return ret;
+ fuse_reply_err(req, -ret);
}
struct rd_struct {
@@ -2470,51 +2457,36 @@ static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
return 0;
}
-static int __op_rmdir(struct fuse4fs *ff, const char *path)
+static int fuse4fs_rmdir(struct fuse4fs *ff, ext2_ino_t parent,
+ const char *name, ext2_ino_t child)
{
ext2_filsys fs = ff->fs;
- ext2_ino_t parent, child;
errcode_t err;
struct ext2_inode_large inode;
- struct rd_struct rds;
+ struct rd_struct rds = {
+ .parent = 0,
+ .empty = 1,
+ };
int ret = 0;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
-
- ret = fuse4fs_inum_access(ff, child, W_OK);
- if (ret)
- goto out;
-
- rds.parent = 0;
- rds.empty = 1;
-
err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
if (err) {
ret = translate_error(fs, child, err);
goto out;
}
- /* the kernel checks parent permissions before emptiness */
+ /* Make sure we found a dotdot entry */
if (rds.parent == 0) {
ret = translate_error(fs, child, EXT2_ET_FILESYSTEM_CORRUPTED);
goto out;
}
- ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
- if (ret)
- goto out;
-
if (rds.empty == 0) {
ret = -ENOTEMPTY;
goto out;
}
- ret = fuse4fs_unlink(ff, path, &parent);
+ ret = fuse4fs_unlink(ff, parent, name, child);
if (ret)
goto out;
ret = fuse4fs_remove_inode(ff, child);
@@ -2540,78 +2512,85 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
}
}
- ret = fuse4fs_dirsync_flush(ff, parent, NULL);
- if (ret)
- goto out;
-
out:
return ret;
}
-static int op_rmdir(const char *path)
+static void op_rmdir(fuse_req_t req, fuse_ino_t fino, const char *name)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ errcode_t err;
int ret;
- FUSE4FS_CHECK_CONTEXT(ff);
- fuse4fs_start(ff);
- ret = __op_rmdir(ff, path);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ fs = fuse4fs_start(ff);
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = fuse4fs_inum_access(ff, ctxt, parent, W_OK);
+ if (ret)
+ goto out;
+
+ ret = fuse4fs_inum_access(ff, ctxt, child, W_OK);
+ if (ret)
+ goto out;
+
+ dbg_printf(ff, "%s: unlink parent=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
+ ret = fuse4fs_rmdir(ff, parent, name, child);
+ if (ret)
+ goto out;
+
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out;
+
+out:
fuse4fs_finish(ff, ret);
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_symlink(const char *src, const char *dest)
+static void op_symlink(fuse_req_t req, const char *target, fuse_ino_t fino,
+ const char *name)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
- ext2_filsys fs;
- ext2_ino_t parent, child;
- char *temp_path;
- errcode_t err;
- char *node_name, a;
struct ext2_inode_large inode;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ errcode_t err;
gid_t gid;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: symlink %s to %s\n", __func__, src, dest);
- temp_path = strdup(dest);
- if (!temp_path) {
- ret = -ENOMEM;
- goto out;
- }
- node_name = strrchr(temp_path, '/');
- if (!node_name) {
- ret = -ENOMEM;
- goto out;
- }
- node_name++;
- a = *node_name;
- *node_name = 0;
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ dbg_printf(ff, "%s: symlink dir=%d name='%s' target='%s'\n",
+ __func__, parent, name, target);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 1)) {
ret = -ENOSPC;
goto out2;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &parent);
- *node_name = a;
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
- ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
if (ret)
goto out2;
- err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
if (err)
goto out2;
/* Create symlink */
- err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ err = ext2fs_symlink(fs, parent, 0, name, target);
if (err == EXT2_ET_DIR_NO_SPACE) {
err = ext2fs_expand_dir(fs, parent);
if (err) {
@@ -2619,7 +2598,7 @@ static int op_symlink(const char *src, const char *dest)
goto out2;
}
- err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ err = ext2fs_symlink(fs, parent, 0, name, target);
}
if (err) {
ret = translate_error(fs, parent, err);
@@ -2632,14 +2611,13 @@ static int op_symlink(const char *src, const char *dest)
goto out2;
/* Still have to update the uid/gid of the symlink */
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &child);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
if (err) {
ret = translate_error(fs, 0, err);
goto out2;
}
- dbg_printf(ff, "%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
- child, node_name, parent);
+ dbg_printf(ff, "%s: symlinking dir=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
err = fuse4fs_read_inode(fs, child, &inode);
if (err) {
@@ -2665,9 +2643,7 @@ static int op_symlink(const char *src, const char *dest)
out2:
fuse4fs_finish(ff, ret);
-out:
- free(temp_path);
- return ret;
+ fuse4fs_reply_entry(req, child, &inode, ret);
}
struct update_dotdot {
@@ -2728,39 +2704,43 @@ static int fuse4fs_check_from_dir_nlink(struct fuse4fs *ff, ext2_ino_t from_ino,
return 0;
}
-static int op_rename(const char *from, const char *to,
- unsigned int flags EXT2FS_ATTR((unused)))
+static void op_rename(fuse_req_t req, fuse_ino_t from_parent, const char *from,
+ fuse_ino_t to_parent, const char *to, unsigned int flags)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
errcode_t err;
ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
- char *temp_to = NULL, *temp_from = NULL;
- char *cp, a;
struct ext2_inode inode;
struct update_dotdot ud;
int flushed = 0;
int ret = 0;
/* renameat2 is not supported */
- if (flags)
- return -ENOSYS;
+ if (flags) {
+ fuse_reply_err(req, ENOSYS);
+ return;
+ }
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &from_dir_ino, from_parent);
+ FUSE4FS_CONVERT_FINO(req, &to_dir_ino, to_parent);
+ dbg_printf(ff, "%s: renaming dir=%d name='%s' to dir=%d name='%s'\n",
+ __func__, from_dir_ino, from, to_dir_ino, to);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 5)) {
ret = -ENOSPC;
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, from_dir_ino, from, &from_ino);
if (err || from_ino == 0) {
ret = translate_error(fs, 0, err);
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, to_dir_ino, to, &to_ino);
if (err && err != EXT2_ET_FILE_NOT_FOUND) {
ret = translate_error(fs, 0, err);
goto out;
@@ -2769,141 +2749,85 @@ static int op_rename(const char *from, const char *to,
if (err == EXT2_ET_FILE_NOT_FOUND)
to_ino = 0;
+ dbg_printf(ff,
+ "%s: renaming dir=%d name='%s' child=%d to dir=%d name='%s' child=%d\n",
+ __func__, from_dir_ino, from, from_ino, to_dir_ino, to,
+ to_ino);
+
/* Already the same file? */
if (to_ino != 0 && to_ino == from_ino) {
ret = 0;
goto out;
}
- ret = fuse4fs_inum_access(ff, from_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, from_ino, W_OK);
if (ret)
goto out;
if (to_ino) {
- ret = fuse4fs_inum_access(ff, to_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, to_ino, W_OK);
if (ret)
goto out;
}
- temp_to = strdup(to);
- if (!temp_to) {
- ret = -ENOMEM;
- goto out;
- }
-
- temp_from = strdup(from);
- if (!temp_from) {
- ret = -ENOMEM;
- goto out2;
- }
-
- /* Find parent dir of the source and check write access */
- cp = strrchr(temp_from, '/');
- if (!cp) {
- ret = -EINVAL;
- goto out2;
- }
-
- a = *(cp + 1);
- *(cp + 1) = 0;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
- &from_dir_ino);
- *(cp + 1) = a;
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
- if (from_dir_ino == 0) {
- ret = -ENOENT;
- goto out2;
- }
-
- ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, from_dir_ino, W_OK);
if (ret)
- goto out2;
-
- /* Find parent dir of the destination and check write access */
- cp = strrchr(temp_to, '/');
- if (!cp) {
- ret = -EINVAL;
- goto out2;
- }
-
- a = *(cp + 1);
- *(cp + 1) = 0;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
- &to_dir_ino);
- *(cp + 1) = a;
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
- if (to_dir_ino == 0) {
- ret = -ENOENT;
- goto out2;
- }
+ goto out;
- ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, to_dir_ino, W_OK);
if (ret)
- goto out2;
+ goto out;
ret = fuse4fs_check_from_dir_nlink(ff, from_ino, to_ino, from_dir_ino,
to_dir_ino);
if (ret)
- goto out2;
+ goto out;
/* If the target exists, unlink it first */
if (to_ino != 0) {
err = ext2fs_read_inode(fs, to_ino, &inode);
if (err) {
ret = translate_error(fs, to_ino, err);
- goto out2;
+ goto out;
}
- dbg_printf(ff, "%s: unlinking %s ino=%d\n", __func__,
- LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
- to_ino);
+ dbg_printf(ff, "%s: unlink dir=%d name='%s' child=%d\n",
+ __func__, to_dir_ino, to, to_ino);
if (LINUX_S_ISDIR(inode.i_mode))
- ret = __op_rmdir(ff, to);
+ ret = fuse4fs_rmdir(ff, to_dir_ino, to, to_ino);
else
- ret = __op_unlink(ff, to);
+ ret = fuse4fs_rmfile(ff, to_dir_ino, to, to_ino);
if (ret)
- goto out2;
+ goto out;
}
/* Get ready to do the move */
err = ext2fs_read_inode(fs, from_ino, &inode);
if (err) {
ret = translate_error(fs, from_ino, err);
- goto out2;
+ goto out;
}
/* Link in the new file */
- dbg_printf(ff, "%s: linking ino=%d/path=%s to dir=%d\n", __func__,
- from_ino, cp + 1, to_dir_ino);
- err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ dbg_printf(ff, "%s: link dir=%d name='%s' child=%d\n",
+ __func__, to_dir_ino, to, from_ino);
+ err = ext2fs_link(fs, to_dir_ino, to, from_ino,
ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
if (err) {
ret = translate_error(fs, to_dir_ino, err);
- goto out2;
+ goto out;
}
/* Update '..' pointer if dir */
- err = ext2fs_read_inode(fs, from_ino, &inode);
- if (err) {
- ret = translate_error(fs, from_ino, err);
- goto out2;
- }
-
if (LINUX_S_ISDIR(inode.i_mode)) {
ud.new_dotdot = to_dir_ino;
- dbg_printf(ff, "%s: updating .. entry for dir=%d\n", __func__,
- to_dir_ino);
+ dbg_printf(ff, "%s: updating .. entry for child=%d parent=%d\n",
+ __func__, from_ino, to_dir_ino);
err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
update_dotdot_helper, &ud);
if (err) {
ret = translate_error(fs, from_ino, err);
- goto out2;
+ goto out;
}
/* Decrease from_dir_ino's links_count */
@@ -2912,87 +2836,76 @@ static int op_rename(const char *from, const char *to,
err = ext2fs_read_inode(fs, from_dir_ino, &inode);
if (err) {
ret = translate_error(fs, from_dir_ino, err);
- goto out2;
+ goto out;
}
ext2fs_dec_nlink(&inode);
err = ext2fs_write_inode(fs, from_dir_ino, &inode);
if (err) {
ret = translate_error(fs, from_dir_ino, err);
- goto out2;
+ goto out;
}
/* Increase to_dir_ino's links_count */
err = ext2fs_read_inode(fs, to_dir_ino, &inode);
if (err) {
ret = translate_error(fs, to_dir_ino, err);
- goto out2;
+ goto out;
}
ext2fs_inc_nlink(fs, &inode);
err = ext2fs_write_inode(fs, to_dir_ino, &inode);
if (err) {
ret = translate_error(fs, to_dir_ino, err);
- goto out2;
+ goto out;
}
}
/* Update timestamps */
ret = update_ctime(fs, from_ino, NULL);
if (ret)
- goto out2;
+ goto out;
ret = update_mtime(fs, to_dir_ino, NULL);
if (ret)
- goto out2;
+ goto out;
/* Remove the old file */
- ret = fuse4fs_unlink(ff, from, NULL);
+ dbg_printf(ff, "%s: unlink dir=%d name='%s' child=%d\n",
+ __func__, from_dir_ino, from, from_ino);
+ ret = fuse4fs_unlink(ff, from_dir_ino, from, from_ino);
if (ret)
- goto out2;
+ goto out;
ret = fuse4fs_dirsync_flush(ff, from_dir_ino, &flushed);
if (ret)
- goto out2;
+ goto out;
if (from_dir_ino != to_dir_ino && !flushed) {
ret = fuse4fs_dirsync_flush(ff, to_dir_ino, NULL);
if (ret)
- goto out2;
+ goto out;
}
-out2:
- free(temp_from);
- free(temp_to);
out:
fuse4fs_finish(ff, ret);
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_link(const char *src, const char *dest)
+static void op_link(fuse_req_t req, fuse_ino_t child_fino,
+ fuse_ino_t parent_fino, const char *name)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct ext2_inode_large inode;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
- char *temp_path;
errcode_t err;
- char *node_name, a;
- ext2_ino_t parent, ino;
- struct ext2_inode_large inode;
+ ext2_ino_t parent, child;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: src=%s dest=%s\n", __func__, src, dest);
- temp_path = strdup(dest);
- if (!temp_path) {
- ret = -ENOMEM;
- goto out;
- }
- node_name = strrchr(temp_path, '/');
- if (!node_name) {
- ret = -ENOMEM;
- goto out;
- }
- node_name++;
- a = *node_name;
- *node_name = 0;
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, parent_fino);
+ FUSE4FS_CONVERT_FINO(req, &child, child_fino);
+ dbg_printf(ff, "%s: link dir=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 2)) {
@@ -3000,31 +2913,17 @@ static int op_link(const char *src, const char *dest)
goto out2;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &parent);
- *node_name = a;
- if (err) {
- err = -ENOENT;
- goto out2;
- }
-
- ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
if (ret)
goto out2;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
-
- err = fuse4fs_read_inode(fs, ino, &inode);
+ err = fuse4fs_read_inode(fs, child, &inode);
if (err) {
- ret = translate_error(fs, ino, err);
+ ret = translate_error(fs, child, err);
goto out2;
}
- ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, child, EXT2_INODE(&inode), W_OK);
if (ret)
goto out2;
@@ -3034,19 +2933,17 @@ static int op_link(const char *src, const char *dest)
}
ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
- ret = update_ctime(fs, ino, &inode);
+ ret = update_ctime(fs, child, &inode);
if (ret)
goto out2;
- err = fuse4fs_write_inode(fs, ino, &inode);
+ err = fuse4fs_write_inode(fs, child, &inode);
if (err) {
- ret = translate_error(fs, ino, err);
+ ret = translate_error(fs, child, err);
goto out2;
}
- dbg_printf(ff, "%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
- node_name, parent);
- err = ext2fs_link(fs, parent, node_name, ino,
+ err = ext2fs_link(fs, parent, name, child,
ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
if (err) {
ret = translate_error(fs, parent, err);
@@ -3063,13 +2960,12 @@ static int op_link(const char *src, const char *dest)
out2:
fuse4fs_finish(ff, ret);
-out:
- free(temp_path);
- return ret;
+ fuse4fs_reply_entry(req, child, &inode, ret);
}
/* Obtain group ids of the process that sent us a command(?) */
-static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
+static int fuse4fs_get_groups(struct fuse4fs *ff, fuse_req_t req, gid_t **gids,
+ size_t *nr_gids)
{
ext2_filsys fs = ff->fs;
errcode_t err;
@@ -3082,7 +2978,7 @@ static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
if (err)
return translate_error(fs, 0, err);
- ret = fuse_getgroups(nr, array);
+ ret = fuse_req_getgroups(req, nr, array);
if (ret < 0) {
/*
* If there's an error, we failed to find the group
@@ -3114,10 +3010,10 @@ static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
* that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative
* errno.
*/
-static int fuse4fs_in_file_group(struct fuse_context *ctxt,
+static int fuse4fs_in_file_group(struct fuse4fs *ff, fuse_req_t req,
const struct ext2_inode_large *inode)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
gid_t *gids = NULL;
size_t i, nr_gids = 0;
gid_t gid = inode_gid(*inode);
@@ -3127,7 +3023,7 @@ static int fuse4fs_in_file_group(struct fuse_context *ctxt,
if (ctxt->gid == gid)
return 1;
- ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
+ ret = fuse4fs_get_groups(ff, req, &gids, &nr_gids);
if (ret == -ENOENT) {
/* magic return code for "could not get caller group info" */
return 0;
@@ -3147,37 +3043,21 @@ static int fuse4fs_in_file_group(struct fuse_context *ctxt,
return ret;
}
-static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
+static int fuse4fs_chmod(struct fuse4fs *ff, fuse_req_t req, ext2_ino_t ino,
+ mode_t mode, struct ext2_inode_large *inode)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
- ext2_filsys fs;
- errcode_t err;
- ext2_ino_t ino;
- struct ext2_inode_large inode;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- fs = fuse4fs_start(ff);
- ret = fuse4fs_file_ino(ff, path, fi, &ino);
- if (ret)
- goto out;
- dbg_printf(ff, "%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
-
- err = fuse4fs_read_inode(fs, ino, &inode);
- if (err) {
- ret = translate_error(fs, ino, err);
- goto out;
- }
+ dbg_printf(ff, "%s: ino=%d mode=0%o\n", __func__, ino, mode);
- ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(inode), W_OK);
if (ret)
- goto out;
+ return ret;
- if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
- ret = -EPERM;
- goto out;
- }
+ if (fuse4fs_want_check_owner(ff, ctxt) &&
+ ctxt->uid != inode_uid(*inode))
+ return -EPERM;
/*
* XXX: We should really check that the inode gid is not in /any/
@@ -3185,100 +3065,60 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
* group.
*/
if (!fuse4fs_is_superuser(ff, ctxt)) {
- ret = fuse4fs_in_file_group(ctxt, &inode);
+ ret = fuse4fs_in_file_group(ff, req, inode);
if (ret < 0)
- goto out;
+ return ret;
if (!ret)
mode &= ~S_ISGID;
}
- inode.i_mode &= ~0xFFF;
- inode.i_mode |= mode & 0xFFF;
+ inode->i_mode &= ~0xFFF;
+ inode->i_mode |= mode & 0xFFF;
- dbg_printf(ff, "%s: path=%s new_mode=0%o ino=%d\n", __func__,
- path, inode.i_mode, ino);
+ dbg_printf(ff, "%s: ino=%d new_mode=0%o\n",
+ __func__, ino, inode->i_mode);
- ret = update_ctime(fs, ino, &inode);
- if (ret)
- goto out;
-
- err = fuse4fs_write_inode(fs, ino, &inode);
- if (err) {
- ret = translate_error(fs, ino, err);
- goto out;
- }
-
-out:
- fuse4fs_finish(ff, ret);
- return ret;
+ return 0;
}
-static int op_chown(const char *path, uid_t owner, gid_t group,
- struct fuse_file_info *fi)
+static int fuse4fs_chown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ ext2_ino_t ino, const int to_set,
+ const struct stat *attr,
+ struct ext2_inode_large *inode)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
- ext2_filsys fs;
- errcode_t err;
- ext2_ino_t ino;
- struct ext2_inode_large inode;
+ uid_t owner = (to_set & FUSE_SET_ATTR_UID) ? attr->st_uid : (uid_t)~0;
+ gid_t group = (to_set & FUSE_SET_ATTR_GID) ? attr->st_gid : (gid_t)~0;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- fs = fuse4fs_start(ff);
- ret = fuse4fs_file_ino(ff, path, fi, &ino);
- if (ret)
- goto out;
- dbg_printf(ff, "%s: path=%s owner=%d group=%d ino=%d\n", __func__,
- path, owner, group, ino);
-
- err = fuse4fs_read_inode(fs, ino, &inode);
- if (err) {
- ret = translate_error(fs, ino, err);
- goto out;
- }
+ dbg_printf(ff, "%s: ino=%d owner=%d group=%d\n",
+ __func__, ino, owner, group);
- ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+ ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(inode), W_OK);
if (ret)
- goto out;
+ return ret;
/* FUSE seems to feed us ~0 to mean "don't change" */
if (owner != (uid_t) ~0) {
/* Only root gets to change UID. */
if (fuse4fs_want_check_owner(ff, ctxt) &&
- !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
- ret = -EPERM;
- goto out;
- }
- fuse4fs_set_uid(&inode, owner);
+ !(inode_uid(*inode) == ctxt->uid && owner == ctxt->uid))
+ return -EPERM;
+
+ fuse4fs_set_uid(inode, owner);
}
if (group != (gid_t) ~0) {
/* Only root or the owner get to change GID. */
if (fuse4fs_want_check_owner(ff, ctxt) &&
- inode_uid(inode) != ctxt->uid) {
- ret = -EPERM;
- goto out;
- }
+ inode_uid(*inode) != ctxt->uid)
+ return -EPERM;
/* XXX: We /should/ check group membership but FUSE */
- fuse4fs_set_gid(&inode, group);
+ fuse4fs_set_gid(inode, group);
}
- ret = update_ctime(fs, ino, &inode);
- if (ret)
- goto out;
-
- err = fuse4fs_write_inode(fs, ino, &inode);
- if (err) {
- ret = translate_error(fs, ino, err);
- goto out;
- }
-
-out:
- fuse4fs_finish(ff, ret);
- return ret;
+ return 0;
}
static int fuse4fs_punch_posteof(struct fuse4fs *ff, ext2_ino_t ino,
@@ -3353,32 +3193,6 @@ static int fuse4fs_truncate(struct fuse4fs *ff, ext2_ino_t ino, off_t new_size)
return 0;
}
-static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
-{
- struct fuse4fs *ff = fuse4fs_get();
- ext2_ino_t ino;
- int ret = 0;
-
- FUSE4FS_CHECK_CONTEXT(ff);
- fuse4fs_start(ff);
- ret = fuse4fs_file_ino(ff, path, fi, &ino);
- if (ret)
- goto out;
- dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
-
- ret = fuse4fs_inum_access(ff, ino, W_OK);
- if (ret)
- goto out;
-
- ret = fuse4fs_truncate(ff, ino, len);
- if (ret)
- goto out;
-
-out:
- fuse4fs_finish(ff, ret);
- return ret;
-}
-
#ifdef __linux__
static void detect_linux_executable_open(int kernel_flags, int *access_check,
int *e2fs_open_flags)
@@ -3400,19 +3214,20 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
}
#endif /* __linux__ */
-static int __op_open(struct fuse4fs *ff, const char *path,
- struct fuse_file_info *fp)
+static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ ext2_ino_t ino, struct fuse_file_info *fp)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct fuse4fs_file_handle *file;
int check = 0, ret = 0;
- dbg_printf(ff, "%s: path=%s oflags=0o%o\n", __func__, path, fp->flags);
+ dbg_printf(ff, "%s: ino=%d oflags=0o%o\n", __func__, ino, fp->flags);
err = ext2fs_get_mem(sizeof(*file), &file);
if (err)
return translate_error(fs, 0, err);
file->magic = FUSE4FS_FILE_MAGIC;
+ file->ino = ino;
file->open_flags = 0;
switch (fp->flags & O_ACCMODE) {
@@ -3441,14 +3256,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
if (fp->flags & O_CREAT)
file->open_flags |= EXT2_FILE_CREATE;
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
- if (err || file->ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
-
- ret = fuse4fs_inum_access(ff, file->ino, check);
+ ret = fuse4fs_inum_access(ff, ctxt, file->ino, check);
if (ret) {
/*
* In a regular (Linux) fs driver, the kernel will open
@@ -3460,7 +3268,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
* also employ undocumented hacks (see above).
*/
if (check == R_OK) {
- ret = fuse4fs_inum_access(ff, file->ino, X_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, file->ino, X_OK);
if (ret)
goto out;
check = X_OK;
@@ -3483,34 +3291,48 @@ static int __op_open(struct fuse4fs *ff, const char *path,
return ret;
}
-static int op_open(const char *path, struct fuse_file_info *fp)
+static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_ino_t ino;
int ret;
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fuse4fs_start(ff);
- ret = __op_open(ff, path, fp);
+ ret = fuse4fs_open_file(ff, ctxt, ino, fp);
fuse4fs_finish(ff, ret);
- return ret;
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_open(req, fp);
}
-static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
- size_t len, off_t offset,
- struct fuse_file_info *fp)
+static void op_read(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ size_t len, off_t offset, struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ char *buf;
ext2_filsys fs;
ext2_file_t efp;
errcode_t err;
unsigned int got = 0;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ buf = calloc(len, sizeof(char));
+ if (!buf) {
+ fuse_reply_err(req, errno);
+ return;
+ }
+
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
(unsigned long long)offset, len);
+
fs = fuse4fs_start(ff);
err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
if (err) {
@@ -3546,14 +3368,18 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
}
out:
fuse4fs_finish(ff, ret);
- return got ? (int) got : ret;
+ if (got)
+ fuse_reply_buf(req, buf, got);
+ else
+ fuse_reply_err(req, -ret);
+ ext2fs_free_mem(&buf);
}
-static int op_write(const char *path EXT2FS_ATTR((unused)),
- const char *buf, size_t len, off_t offset,
- struct fuse_file_info *fp)
+static void op_write(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ const char *buf, size_t len, off_t offset,
+ struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
ext2_filsys fs;
ext2_file_t efp;
@@ -3561,8 +3387,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
unsigned int got = 0;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
(unsigned long long) offset, len);
fs = fuse4fs_start(ff);
@@ -3616,20 +3442,23 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
out:
fuse4fs_finish(ff, ret);
- return got ? (int) got : ret;
+ if (got)
+ fuse_reply_write(req, got);
+ else
+ fuse_reply_err(req, -ret);
}
-static int op_release(const char *path EXT2FS_ATTR((unused)),
- struct fuse_file_info *fp)
+static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
ext2_filsys fs;
errcode_t err;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
fs = fuse4fs_start(ff);
@@ -3646,21 +3475,21 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
ext2fs_free_mem(&fh);
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_fsync(const char *path EXT2FS_ATTR((unused)),
- int datasync EXT2FS_ATTR((unused)),
- struct fuse_file_info *fp)
+static void op_fsync(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ int datasync EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
ext2_filsys fs;
errcode_t err;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
fs = fuse4fs_start(ff);
/* For now, flush everything, even if it's slow */
@@ -3671,22 +3500,24 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
}
fuse4fs_finish(ff, ret);
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_statfs(const char *path EXT2FS_ATTR((unused)),
- struct statvfs *buf)
+static void op_statfs(fuse_req_t req, fuse_ino_t fino)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct statvfs buf;
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
uint64_t fsid, *f;
+ ext2_ino_t ino;
blk64_t overhead, reserved, free;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s\n", __func__, path);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
fs = fuse4fs_start(ff);
- buf->f_bsize = fs->blocksize;
- buf->f_frsize = 0;
+ buf.f_bsize = fs->blocksize;
+ buf.f_frsize = 0;
if (ff->minixdf)
overhead = 0;
@@ -3699,27 +3530,27 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
reserved = ext2fs_blocks_count(fs->super) / 10;
free = ext2fs_free_blocks_count(fs->super);
- buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
- buf->f_bfree = free;
+ buf.f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+ buf.f_bfree = free;
if (free < reserved)
- buf->f_bavail = 0;
+ buf.f_bavail = 0;
else
- buf->f_bavail = free - reserved;
- buf->f_files = fs->super->s_inodes_count;
- buf->f_ffree = fs->super->s_free_inodes_count;
- buf->f_favail = fs->super->s_free_inodes_count;
+ buf.f_bavail = free - reserved;
+ buf.f_files = fs->super->s_inodes_count;
+ buf.f_ffree = fs->super->s_free_inodes_count;
+ buf.f_favail = fs->super->s_free_inodes_count;
f = (uint64_t *)fs->super->s_uuid;
fsid = *f;
f++;
fsid ^= *f;
- buf->f_fsid = fsid;
- buf->f_flag = 0;
+ buf.f_fsid = fsid;
+ buf.f_flag = 0;
if (ff->opstate != F4OP_WRITABLE)
- buf->f_flag |= ST_RDONLY;
- buf->f_namemax = EXT2_NAME_LEN;
+ buf.f_flag |= ST_RDONLY;
+ buf.f_namemax = EXT2_NAME_LEN;
fuse4fs_finish(ff, 0);
- return 0;
+ fuse_reply_statfs(req, &buf);
}
static const char *valid_xattr_prefixes[] = {
@@ -3743,35 +3574,33 @@ static int validate_xattr_name(const char *name)
return 0;
}
-static int op_getxattr(const char *path, const char *key, char *value,
- size_t len)
+static void op_getxattr(fuse_req_t req, fuse_ino_t fino, const char *key,
+ size_t len)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
- void *ptr;
+ void *ptr = NULL;
size_t plen;
ext2_ino_t ino;
- errcode_t err;
int ret = 0;
- if (!validate_xattr_name(key))
- return -ENODATA;
+ if (!validate_xattr_name(key)) {
+ fuse_reply_err(req, ENODATA);
+ return;
+ }
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fs = fuse4fs_start(ff);
if (!ext2fs_has_feature_xattr(fs->super)) {
ret = -ENOTSUP;
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+ dbg_printf(ff, "%s: ino=%d name='%s'\n", __func__, ino, key);
- ret = fuse4fs_inum_access(ff, ino, R_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, R_OK);
if (ret)
goto out;
@@ -3780,19 +3609,26 @@ static int op_getxattr(const char *path, const char *key, char *value,
goto out;
if (!len) {
+ /* Just tell us the length */
ret = plen;
} else if (len < plen) {
+ /* Caller's buffer wasn't big enough */
ret = -ERANGE;
} else {
- memcpy(value, ptr, plen);
+ /* We have data */
ret = plen;
}
+out:
+ fuse4fs_finish(ff, ret);
+
+ if (ret < 0)
+ fuse_reply_err(req, -ret);
+ else if (!len)
+ fuse_reply_xattr(req, ret);
+ else
+ fuse_reply_buf(req, ptr, ret);
ext2fs_free_mem(&ptr);
-out:
- fuse4fs_finish(ff, ret);
-
- return ret;
}
static int count_buffer_space(char *name, char *value EXT2FS_ATTR((unused)),
@@ -3817,31 +3653,30 @@ static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
return 0;
}
-static int op_listxattr(const char *path, char *names, size_t len)
+static void op_listxattr(fuse_req_t req, fuse_ino_t fino, size_t len)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
struct ext2_xattr_handle *h;
+ char *names = NULL;
+ char *next_name;
unsigned int bufsz;
ext2_ino_t ino;
errcode_t err;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fs = fuse4fs_start(ff);
if (!ext2fs_has_feature_xattr(fs->super)) {
ret = -ENOTSUP;
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, ino, err);
- goto out;
- }
dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
- ret = fuse4fs_inum_access(ff, ino, R_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, R_OK);
if (ret)
goto out;
@@ -3866,21 +3701,28 @@ static int op_listxattr(const char *path, char *names, size_t len)
}
if (len == 0) {
- ret = bufsz;
+ /* Just tell us the length */
goto out2;
} else if (len < bufsz) {
+ /* Caller's buffer wasn't big enough */
ret = -ERANGE;
goto out2;
}
/* Copy names out */
- memset(names, 0, len);
- err = ext2fs_xattrs_iterate(h, copy_names, &names);
+ names = calloc(len, sizeof(char));
+ if (!names) {
+ ret = translate_error(fs, ino, errno);
+ goto out2;
+ }
+ next_name = names;
+
+ err = ext2fs_xattrs_iterate(h, copy_names, &next_name);
if (err) {
ret = translate_error(fs, ino, err);
goto out2;
}
- ret = bufsz;
+
out2:
err = ext2fs_xattrs_close(&h);
if (err && !ret)
@@ -3888,41 +3730,47 @@ static int op_listxattr(const char *path, char *names, size_t len)
out:
fuse4fs_finish(ff, ret);
- return ret;
+ if (ret < 0)
+ fuse_reply_err(req, -ret);
+ else if (names)
+ fuse_reply_buf(req, names, bufsz);
+ else
+ fuse_reply_xattr(req, bufsz);
+ free(names);
}
-static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
- const char *key, const char *value,
- size_t len, int flags)
+static void op_setxattr(fuse_req_t req, fuse_ino_t fino, const char *key,
+ const char *value, size_t len, int flags)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
struct ext2_xattr_handle *h;
ext2_ino_t ino;
errcode_t err;
int ret = 0;
- if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
- return -EOPNOTSUPP;
+ if (flags & ~(XATTR_CREATE | XATTR_REPLACE)) {
+ fuse_reply_err(req, EOPNOTSUPP);
+ return;
+ }
- if (!validate_xattr_name(key))
- return -EINVAL;
+ if (!validate_xattr_name(key)) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fs = fuse4fs_start(ff);
if (!ext2fs_has_feature_xattr(fs->super)) {
ret = -ENOTSUP;
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+ dbg_printf(ff, "%s: ino=%d name='%s'\n", __func__, ino, key);
- ret = fuse4fs_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
if (ret == -EACCES) {
ret = -EPERM;
goto out;
@@ -3979,13 +3827,13 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
ret = translate_error(fs, ino, err);
out:
fuse4fs_finish(ff, ret);
-
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_removexattr(const char *path, const char *key)
+static void op_removexattr(fuse_req_t req, fuse_ino_t fino, const char *key)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
struct ext2_xattr_handle *h;
void *buf;
@@ -3998,13 +3846,18 @@ static int op_removexattr(const char *path, const char *key)
* Once in a while libfuse gives us a no-name xattr to delete as part
* of clearing ACLs. Just pretend we cleared them.
*/
- if (key[0] == 0)
- return 0;
+ if (key[0] == 0) {
+ fuse_reply_err(req, 0);
+ return;
+ }
- if (!validate_xattr_name(key))
- return -ENODATA;
+ if (!validate_xattr_name(key)) {
+ fuse_reply_err(req, ENODATA);
+ return;
+ }
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fs = fuse4fs_start(ff);
if (!ext2fs_has_feature_xattr(fs->super)) {
ret = -ENOTSUP;
@@ -4016,14 +3869,9 @@ static int op_removexattr(const char *path, const char *key)
goto out;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
- ret = fuse4fs_inum_access(ff, ino, W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
if (ret)
goto out;
@@ -4073,24 +3921,26 @@ static int op_removexattr(const char *path, const char *key)
ret = translate_error(fs, ino, err);
out:
fuse4fs_finish(ff, ret);
-
- return ret;
+ fuse_reply_err(req, -ret);
}
struct readdir_iter {
void *buf;
- ext2_filsys fs;
- fuse_fill_dir_t func;
+ size_t bufsz;
+ size_t bufused;
+ ext2_filsys fs;
struct fuse4fs *ff;
- enum fuse_readdir_flags flags;
+ fuse_req_t req;
+
+ bool readdirplus;
unsigned int nr;
off_t startpos;
off_t dirpos;
};
static inline mode_t dirent_fmode(ext2_filsys fs,
- const struct ext2_dir_entry *dirent)
+ const struct ext2_dir_entry *dirent)
{
if (!ext2fs_has_feature_filetype(fs->super))
return 0;
@@ -4124,10 +3974,15 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
{
struct readdir_iter *i = data;
char namebuf[EXT2_NAME_LEN + 1];
- struct stat stat = {
- .st_ino = dirent->inode,
- .st_mode = dirent_fmode(i->fs, dirent),
+ struct fuse4fs_stat fstat = {
+ .entry = {
+ .attr = {
+ .st_ino = dirent->inode,
+ .st_mode = dirent_fmode(i->fs, dirent),
+ },
+ },
};
+ size_t entrysize;
int ret;
i->dirpos++;
@@ -4135,48 +3990,67 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
return 0;
dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
- i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+ i->readdirplus ? "PLUS" : "",
dir,
i->nr++,
(unsigned long long)i->dirpos);
- if (i->flags == FUSE_READDIR_PLUS) {
- ret = stat_inode(i->fs, dirent->inode, &stat);
+ if (i->readdirplus) {
+ ret = fuse4fs_stat_inode(i->ff, dirent->inode, NULL, &fstat);
if (ret)
return DIRENT_ABORT;
}
memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
namebuf[dirent->name_len & 0xFF] = 0;
- ret = i->func(i->buf, namebuf, &stat, i->dirpos , 0);
- if (ret)
+
+ if (i->readdirplus) {
+ entrysize = fuse_add_direntry_plus(i->req, i->buf + i->bufused,
+ i->bufsz - i->bufused,
+ namebuf, &fstat.entry,
+ i->dirpos);
+ } else {
+ entrysize = fuse_add_direntry(i->req, i->buf + i->bufused,
+ i->bufsz - i->bufused, namebuf,
+ &fstat.entry.attr, i->dirpos);
+ }
+ if (entrysize > i->bufsz - i->bufused) {
+ /* Buffer is full */
return DIRENT_ABORT;
+ }
+ i->bufused += entrysize;
return 0;
}
-static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
- fuse_fill_dir_t fill_func, off_t offset,
- struct fuse_file_info *fp, enum fuse_readdir_flags flags)
+static void __op_readdir(fuse_req_t req, fuse_ino_t fino, size_t size,
+ off_t offset, bool plus, struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
errcode_t err;
struct readdir_iter i = {
.ff = ff,
+ .req = req,
.dirpos = 0,
.startpos = offset,
- .flags = flags,
+ .readdirplus = plus,
+ .bufsz = size,
};
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
dbg_printf(ff, "%s: ino=%d offset=0x%llx\n", __func__, fh->ino,
(unsigned long long)offset);
+
+ err = ext2fs_get_mem(size, &i.buf);
+ if (err) {
+ ret = translate_error(i.fs, fh->ino, err);
+ goto out;
+ }
+
i.fs = fuse4fs_start(ff);
- i.buf = buf;
- i.func = fill_func;
err = ext2fs_dir_iterate2(i.fs, fh->ino, 0, NULL, op_readdir_iter, &i);
if (err) {
ret = translate_error(i.fs, fh->ino, err);
@@ -4190,64 +4064,66 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
}
out:
fuse4fs_finish(ff, ret);
- return ret;
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_buf(req, i.buf, i.bufused);
+
+ ext2fs_free_mem(&i.buf);
+}
+
+static void op_readdir(fuse_req_t req, fuse_ino_t fino, size_t size,
+ off_t offset, struct fuse_file_info *fp)
+{
+ __op_readdir(req, fino, size, offset, false, fp);
+}
+
+static void op_readdirplus(fuse_req_t req, fuse_ino_t fino, size_t size,
+ off_t offset, struct fuse_file_info *fp)
+{
+ __op_readdir(req, fino, size, offset, true, fp);
}
-static int op_access(const char *path, int mask)
+static void op_access(fuse_req_t req, fuse_ino_t fino, int mask)
{
- struct fuse4fs *ff = fuse4fs_get();
- ext2_filsys fs;
- errcode_t err;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_ino_t ino;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
- fs = fuse4fs_start(ff);
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err || ino == 0) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ dbg_printf(ff, "%s: ino=%d mask=0x%x\n",
+ __func__, ino, mask);
+ fuse4fs_start(ff);
- ret = fuse4fs_inum_access(ff, ino, mask);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, mask);
if (ret)
goto out;
out:
fuse4fs_finish(ff, ret);
- return ret;
+ fuse_reply_err(req, -ret);
}
-static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
+ mode_t mode, struct fuse_file_info *fp)
{
- struct fuse_context *ctxt = fuse_get_context();
- struct fuse4fs *ff = fuse4fs_get();
+ struct ext2_inode_large inode;
+ struct fuse4fs_stat fstat;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
ext2_ino_t parent, child;
- char *temp_path;
errcode_t err;
- char *node_name, a;
int filetype;
- struct ext2_inode_large inode;
gid_t gid;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
- temp_path = strdup(path);
- if (!temp_path) {
- ret = -ENOMEM;
- goto out;
- }
- node_name = strrchr(temp_path, '/');
- if (!node_name) {
- ret = -ENOMEM;
- goto out;
- }
- node_name++;
- a = *node_name;
- *node_name = 0;
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &parent, fino);
+ dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o\n",
+ __func__, parent, name, mode);
fs = fuse4fs_start(ff);
if (!fuse4fs_can_allocate(ff, 1)) {
@@ -4255,23 +4131,14 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
goto out2;
}
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
- &parent);
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out2;
- }
-
- ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+ ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
if (ret)
goto out2;
- err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+ err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
if (err)
goto out2;
- *node_name = a;
-
filetype = ext2_file_type(mode);
err = ext2fs_new_inode(fs, parent, mode, 0, &child);
@@ -4280,9 +4147,9 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
goto out2;
}
- dbg_printf(ff, "%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
- node_name, parent);
- err = ext2fs_link(fs, parent, node_name, child,
+ dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
+ err = ext2fs_link(fs, parent, name, child,
filetype | EXT2FS_LINK_EXPAND);
if (err) {
ret = translate_error(fs, parent, err);
@@ -4334,7 +4201,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
goto out2;
fp->flags &= ~O_TRUNC;
- ret = __op_open(ff, path, fp);
+ ret = fuse4fs_open_file(ff, ctxt, child, fp);
if (ret)
goto out2;
@@ -4342,44 +4209,152 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
if (ret)
goto out2;
+ ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
+ if (ret)
+ goto out2;
+
out2:
fuse4fs_finish(ff, ret);
-out:
- free(temp_path);
- return ret;
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_create(req, &fstat.entry, fp);
+}
+
+enum fuse4fs_time_action {
+ TA_NOW, /* set to current time */
+ TA_OMIT, /* do not set timestamp */
+ TA_THIS, /* set to specific timestamp */
+};
+
+static inline const char *
+fuse4fs_time_action_string(enum fuse4fs_time_action act)
+{
+ switch (act) {
+ case TA_NOW:
+ return "now";
+ case TA_OMIT:
+ return "omit";
+ case TA_THIS:
+ return "specific";
+ }
+ return NULL; /* shut up gcc */
}
-static int op_utimens(const char *path, const struct timespec ctv[2],
- struct fuse_file_info *fi)
+static int fuse4fs_utimens(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ ext2_ino_t ino, const int to_set,
+ const struct stat *attr,
+ struct ext2_inode_large *inode)
{
- struct fuse4fs *ff = fuse4fs_get();
- struct timespec tv[2];
- ext2_filsys fs;
- errcode_t err;
- ext2_ino_t ino;
- struct ext2_inode_large inode;
+ enum fuse4fs_time_action aact = TA_OMIT;
+ enum fuse4fs_time_action mact = TA_OMIT;
+ struct timespec atime = { };
+ struct timespec mtime = { };
+ struct timespec now = { };
int access = W_OK;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- fs = fuse4fs_start(ff);
- ret = fuse4fs_file_ino(ff, path, fi, &ino);
- if (ret)
- goto out;
- dbg_printf(ff, "%s: ino=%d atime=%lld.%ld mtime=%lld.%ld\n", __func__,
- ino,
- (long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
- (long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
+ if (to_set & (FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW))
+ get_now(&now);
+
+ if (to_set & FUSE_SET_ATTR_ATIME_NOW) {
+ atime = now;
+ aact = TA_NOW;
+ } else if (to_set & FUSE_SET_ATTR_ATIME) {
+#if HAVE_STRUCT_STAT_ST_ATIM
+ atime = attr->st_atim;
+#else
+ atime.tv_sec = attr->st_atime;
+#endif
+ aact = TA_THIS;
+ }
+
+ if (to_set & FUSE_SET_ATTR_MTIME_NOW) {
+ mtime = now;
+ mact = TA_NOW;
+ } else if (to_set & FUSE_SET_ATTR_MTIME) {
+#if HAVE_STRUCT_STAT_ST_ATIM
+ mtime = attr->st_mtim;
+#else
+ mtime.tv_sec = attr->st_mtime;
+#endif
+ mact = TA_THIS;
+ }
+
+ dbg_printf(ff, "%s: ino=%d atime=%s:%lld.%ld mtime=%s:%lld.%ld\n",
+ __func__, ino, fuse4fs_time_action_string(aact),
+ (long long int)atime.tv_sec, atime.tv_nsec,
+ fuse4fs_time_action_string(mact),
+ (long long int)mtime.tv_sec, mtime.tv_nsec);
/*
* ext4 allows timestamp updates of append-only files but only if we're
* setting to current time
*/
- if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
+ if (aact == TA_NOW && mact == TA_NOW)
access |= A_OK;
- ret = fuse4fs_inum_access(ff, ino, access);
+ ret = fuse4fs_inum_access(ff, ctxt, ino, access);
if (ret)
+ return ret;
+
+ if (aact != TA_OMIT)
+ EXT4_INODE_SET_XTIME(i_atime, &atime, inode);
+ if (mact != TA_OMIT)
+ EXT4_INODE_SET_XTIME(i_mtime, &mtime, inode);
+
+ return 0;
+}
+
+static int fuse4fs_setsize(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ ext2_ino_t ino, off_t new_size,
+ struct ext2_inode_large *inode)
+{
+ errcode_t err;
+ int ret;
+
+ /* Write inode because truncate makes its own copy */
+ err = fuse4fs_write_inode(ff->fs, ino, inode);
+ if (err)
+ return translate_error(ff->fs, ino, err);
+
+ ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
+ if (ret)
+ return ret;
+
+ ret = fuse4fs_truncate(ff, ino, new_size);
+ if (ret)
+ return ret;
+
+ /* Re-read inode after truncate */
+ err = fuse4fs_read_inode(ff->fs, ino, inode);
+ if (err)
+ return translate_error(ff->fs, ino, err);
+
+ return 0;
+}
+
+static void op_setattr(fuse_req_t req, fuse_ino_t fino, struct stat *attr,
+ int to_set, struct fuse_file_info *fi EXT2FS_ATTR((unused)))
+{
+ struct ext2_inode_large inode;
+ struct fuse4fs_stat fstat;
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ dbg_printf(ff, "%s: ino=%d to_set=0x%x\n", __func__, ino, to_set);
+ fs = fuse4fs_start(ff);
+
+ if (!fuse4fs_is_writeable(ff)) {
+ ret = -EROFS;
goto out;
+ }
err = fuse4fs_read_inode(fs, ino, &inode);
if (err) {
@@ -4387,20 +4362,35 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
goto out;
}
- tv[0] = ctv[0];
- tv[1] = ctv[1];
-#ifdef UTIME_NOW
- if (tv[0].tv_nsec == UTIME_NOW)
- get_now(tv);
- if (tv[1].tv_nsec == UTIME_NOW)
- get_now(tv + 1);
-#endif /* UTIME_NOW */
-#ifdef UTIME_OMIT
- if (tv[0].tv_nsec != UTIME_OMIT)
- EXT4_INODE_SET_XTIME(i_atime, &tv[0], &inode);
- if (tv[1].tv_nsec != UTIME_OMIT)
- EXT4_INODE_SET_XTIME(i_mtime, &tv[1], &inode);
-#endif /* UTIME_OMIT */
+ /* Handle mode change using helper */
+ if (to_set & FUSE_SET_ATTR_MODE) {
+ ret = fuse4fs_chmod(ff, req, ino, attr->st_mode, &inode);
+ if (ret)
+ goto out;
+ }
+
+ /* Handle owner/group change using helper */
+ if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+ ret = fuse4fs_chown(ff, ctxt, ino, to_set, attr, &inode);
+ if (ret)
+ goto out;
+ }
+
+ /* Handle size change using helper */
+ if (to_set & FUSE_SET_ATTR_SIZE) {
+ ret = fuse4fs_setsize(ff, ctxt, ino, attr->st_size, &inode);
+ if (ret)
+ goto out;
+ }
+
+ /* Handle time changes using helper */
+ if (to_set & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+ ret = fuse4fs_utimens(ff, ctxt, ino, to_set, attr, &inode);
+ if (ret)
+ goto out;
+ }
+
+ /* Update ctime for any attribute change */
ret = update_ctime(fs, ino, &inode);
if (ret)
goto out;
@@ -4411,9 +4401,17 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
goto out;
}
+ /* Get updated stat info to return */
+ ret = fuse4fs_stat_inode(ff, ino, &inode, &fstat);
+
out:
fuse4fs_finish(ff, ret);
- return ret;
+
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_attr(req, &fstat.entry.attr,
+ fstat.entry.attr_timeout);
}
#define FUSE4FS_MODIFIABLE_IFLAGS \
@@ -4432,32 +4430,38 @@ static inline int set_iflags(struct ext2_inode_large *inode, __u32 iflags)
#ifdef SUPPORT_I_FLAGS
static int ioctl_getflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+ __u32 *outdata, size_t *outsize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
+ if (*outsize < sizeof(__u32))
+ return -EFAULT;
+
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
return translate_error(fs, fh->ino, err);
- *(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+ *outdata = inode.i_flags & EXT2_FL_USER_VISIBLE;
+ *outsize = sizeof(__u32);
return 0;
}
-static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+static int ioctl_setflags(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ struct fuse4fs_file_handle *fh, const __u32 *indata,
+ size_t insize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
int ret;
- __u32 flags = *(__u32 *)data;
- struct fuse_context *ctxt = fuse_get_context();
- dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ if (insize < sizeof(__u32))
+ return -EFAULT;
+
+ dbg_printf(ff, "%s: ino=%d iflags=0x%x\n", __func__, fh->ino, *indata);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
return translate_error(fs, fh->ino, err);
@@ -4465,7 +4469,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
return -EPERM;
- ret = set_iflags(&inode, flags);
+ ret = set_iflags(&inode, *indata);
if (ret)
return ret;
@@ -4481,32 +4485,38 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
}
static int ioctl_getversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+ __u32 *outdata, size_t *outsize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
+ if (*outsize < sizeof(__u32))
+ return -EFAULT;
+
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
return translate_error(fs, fh->ino, err);
- *(__u32 *)data = inode.i_generation;
+ *outdata = inode.i_generation;
+ *outsize = sizeof(__u32);
return 0;
}
-static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+static int ioctl_setversion(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ struct fuse4fs_file_handle *fh, const __u32 *indata,
+ size_t insize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
int ret;
- __u32 generation = *(__u32 *)data;
- struct fuse_context *ctxt = fuse_get_context();
- dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ if (insize < sizeof(__u32))
+ return -EFAULT;
+
+ dbg_printf(ff, "%s: ino=%d generation=%d\n", __func__, fh->ino, *indata);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
return translate_error(fs, fh->ino, err);
@@ -4514,7 +4524,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
return -EPERM;
- inode.i_generation = generation;
+ inode.i_generation = *indata;
ret = update_ctime(fs, fh->ino, &inode);
if (ret)
@@ -4551,14 +4561,16 @@ static __u32 iflags_to_fsxflags(__u32 iflags)
}
static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+ struct fsxattr *fsx, size_t *outsize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
- struct fsxattr *fsx = data;
unsigned int inode_size;
+ if (*outsize < sizeof(struct fsxattr))
+ return -EFAULT;
+
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
@@ -4569,6 +4581,7 @@ static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
if (ext2fs_inode_includes(inode_size, i_projid))
fsx->fsx_projid = inode_projid(inode);
fsx->fsx_xflags = iflags_to_fsxflags(inode.i_flags);
+ *outsize = sizeof(struct fsxattr);
return 0;
}
@@ -4620,17 +4633,19 @@ static inline int set_xflags(struct ext2_inode_large *inode, __u32 xflags)
return 0;
}
-static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+static int ioctl_fssetxattr(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ struct fuse4fs_file_handle *fh,
+ const struct fsxattr *fsx, size_t insize)
{
ext2_filsys fs = ff->fs;
errcode_t err;
struct ext2_inode_large inode;
int ret;
- struct fuse_context *ctxt = fuse_get_context();
- struct fsxattr *fsx = data;
unsigned int inode_size;
+ if (insize < sizeof(struct fsxattr))
+ return -EFAULT;
+
dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
err = fuse4fs_read_inode(fs, fh->ino, &inode);
if (err)
@@ -4661,17 +4676,24 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
#ifdef FITRIM
static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+ const struct fstrim_range *fr_in, size_t insize,
+ struct fstrim_range *fr, size_t *outsize)
{
ext2_filsys fs = ff->fs;
- struct fstrim_range *fr = data;
blk64_t start, end, max_blocks, b, cleared, minlen;
blk64_t max_blks = ext2fs_blocks_count(fs->super);
errcode_t err = 0;
+ if (insize < sizeof(struct fstrim_range))
+ return -EFAULT;
+
+ if (*outsize < sizeof(struct fstrim_range))
+ return -EFAULT;
+
if (!fuse4fs_is_writeable(ff))
return -EROFS;
+ memcpy(fr, fr_in, sizeof(*fr));
start = FUSE4FS_B_TO_FSBT(ff, fr->start);
if (fr->len == -1ULL)
end = -1ULL;
@@ -4750,6 +4772,7 @@ static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
out:
fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+ *outsize = sizeof(struct fstrim_range);
dbg_printf(ff, "%s: len=%llu err=%ld\n", __func__, fr->len, err);
return err;
}
@@ -4759,10 +4782,10 @@ static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
# define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)
#endif
-static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
- void *data)
+static int ioctl_shutdown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+ struct fuse4fs_file_handle *fh, const void *indata,
+ size_t insize)
{
- struct fuse_context *ctxt = fuse_get_context();
ext2_filsys fs = ff->fs;
if (!fuse4fs_is_superuser(ff, ctxt))
@@ -4785,49 +4808,61 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
return 0;
}
-static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
- unsigned int cmd,
- void *arg EXT2FS_ATTR((unused)),
- struct fuse_file_info *fp,
- unsigned int flags EXT2FS_ATTR((unused)), void *data)
+static void op_ioctl(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ unsigned int cmd,
+ void *arg EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp,
+ unsigned int flags EXT2FS_ATTR((unused)),
+ const void *indata, size_t insize,
+ size_t outsize)
{
- struct fuse4fs *ff = fuse4fs_get();
+ const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ void *outdata = NULL;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ if (outsize > 0) {
+ outdata = calloc(outsize, sizeof(char));
+ if (!outdata) {
+ fuse_reply_err(req, errno);
+ return;
+ }
+ }
+
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
fuse4fs_start(ff);
switch ((unsigned long) cmd) {
#ifdef SUPPORT_I_FLAGS
case EXT2_IOC_GETFLAGS:
- ret = ioctl_getflags(ff, fh, data);
+ ret = ioctl_getflags(ff, fh, outdata, &outsize);
break;
case EXT2_IOC_SETFLAGS:
- ret = ioctl_setflags(ff, fh, data);
+ ret = ioctl_setflags(ff, ctxt, fh, indata, insize);
break;
case EXT2_IOC_GETVERSION:
- ret = ioctl_getversion(ff, fh, data);
+ ret = ioctl_getversion(ff, fh, outdata, &outsize);
break;
case EXT2_IOC_SETVERSION:
- ret = ioctl_setversion(ff, fh, data);
+ ret = ioctl_setversion(ff, ctxt, fh, indata, insize);
break;
#endif
#ifdef FS_IOC_FSGETXATTR
case FS_IOC_FSGETXATTR:
- ret = ioctl_fsgetxattr(ff, fh, data);
+ ret = ioctl_fsgetxattr(ff, fh, outdata, &outsize);
break;
case FS_IOC_FSSETXATTR:
- ret = ioctl_fssetxattr(ff, fh, data);
+ ret = ioctl_fssetxattr(ff, ctxt, fh, indata, insize);
break;
#endif
#ifdef FITRIM
case FITRIM:
- ret = ioctl_fitrim(ff, fh, data);
+ ret = ioctl_fitrim(ff, fh, indata, insize, outdata, &outsize);
break;
#endif
case EXT4_IOC_SHUTDOWN:
- ret = ioctl_shutdown(ff, fh, data);
+ ret = ioctl_shutdown(ff, ctxt, fh, indata, insize);
break;
default:
dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
@@ -4835,28 +4870,29 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
}
fuse4fs_finish(ff, ret);
- return ret;
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_ioctl(req, 0, outdata, outsize);
+ free(outdata);
}
-static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
- uint64_t *idx)
+static void op_bmap(fuse_req_t req, fuse_ino_t fino,
+ size_t blocksize EXT2FS_ATTR((unused)), uint64_t idx)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
ext2_filsys fs;
ext2_ino_t ino;
+ blk64_t blkno;
errcode_t err;
int ret = 0;
- FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CONVERT_FINO(req, &ino, fino);
fs = fuse4fs_start(ff);
- err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
- if (err) {
- ret = translate_error(fs, 0, err);
- goto out;
- }
- dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+ dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, idx);
- err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+ err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, idx, 0, &blkno);
if (err) {
ret = translate_error(fs, ino, err);
goto out;
@@ -4864,7 +4900,10 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
out:
fuse4fs_finish(ff, ret);
- return ret;
+ if (ret)
+ fuse_reply_err(req, -ret);
+ else
+ fuse_reply_bmap(req, blkno);
}
#ifdef SUPPORT_FALLOCATE
@@ -5107,20 +5146,22 @@ static int fuse4fs_zero_range(struct fuse4fs *ff,
return ret;
}
-static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
- off_t offset, off_t len,
- struct fuse_file_info *fp)
+static void op_fallocate(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+ int mode, off_t offset, off_t len,
+ struct fuse_file_info *fp)
{
- struct fuse4fs *ff = fuse4fs_get();
+ struct fuse4fs *ff = fuse4fs_get(req);
struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
int ret;
/* Catch unknown flags */
- if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
- return -EOPNOTSUPP;
+ if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG)) {
+ fuse_reply_err(req, EOPNOTSUPP);
+ return;
+ }
- FUSE4FS_CHECK_CONTEXT(ff);
- FUSE4FS_CHECK_HANDLE(ff, fh);
+ FUSE4FS_CHECK_CONTEXT(req);
+ FUSE4FS_CHECK_HANDLE(req, fh);
fuse4fs_start(ff);
if (!fuse4fs_is_writeable(ff)) {
ret = -EROFS;
@@ -5140,12 +5181,13 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
out:
fuse4fs_finish(ff, ret);
-
- return ret;
+ fuse_reply_err(req, -ret);
}
#endif /* SUPPORT_FALLOCATE */
-static struct fuse_operations fs_ops = {
+static struct fuse_lowlevel_ops fs_ops = {
+ .lookup = op_lookup,
+ .setattr = op_setattr,
.init = op_init,
.destroy = op_destroy,
.getattr = op_getattr,
@@ -5157,9 +5199,6 @@ static struct fuse_operations fs_ops = {
.symlink = op_symlink,
.rename = op_rename,
.link = op_link,
- .chmod = op_chmod,
- .chown = op_chown,
- .truncate = op_truncate,
.open = op_open,
.read = op_read,
.write = op_write,
@@ -5172,11 +5211,11 @@ static struct fuse_operations fs_ops = {
.removexattr = op_removexattr,
.opendir = op_open,
.readdir = op_readdir,
+ .readdirplus = op_readdirplus,
.releasedir = op_release,
.fsyncdir = op_fsync,
.access = op_access,
.create = op_create,
- .utimens = op_utimens,
.bmap = op_bmap,
#ifdef SUPERFLUOUS
.lock = op_lock,
@@ -5325,8 +5364,8 @@ static int fuse4fs_opt_proc(void *data, const char *arg,
"\n",
outargs->argv[0]);
if (key == FUSE4FS_HELPFULL) {
- fuse_opt_add_arg(outargs, "-h");
- fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ printf("FUSE options:\n");
+ fuse_cmdline_help();
} else {
fprintf(stderr, "Try --helpfull to get a list of "
"all flags, including the FUSE options.\n");
@@ -5336,8 +5375,7 @@ static int fuse4fs_opt_proc(void *data, const char *arg,
case FUSE4FS_VERSION:
fprintf(stderr, "fuse4fs %s (%s)\n", E2FSPROGS_VERSION,
E2FSPROGS_DATE);
- fuse_opt_add_arg(outargs, "--version");
- fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ fprintf(stderr, "FUSE library version %s\n", fuse_pkgversion());
exit(0);
}
return 1;
@@ -5367,8 +5405,7 @@ static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
char extra_args[BUFSIZ];
/* Set up default fuse parameters */
- snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
- "fsname=%s,attr_timeout=0",
+ snprintf(extra_args, BUFSIZ, "-osubtype=%s,fsname=%s",
get_subtype(argv0),
ff->device);
if (ff->no_default_opts == 0)
@@ -5396,14 +5433,6 @@ static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
"-oallow_other,default_permissions,suid,dev");
}
- /*
- * Since there's a Big Kernel Lock around all the libext2fs code, we
- * only need to start four threads -- one to decode a request, another
- * to do the filesystem work, a third to transmit the reply, and a
- * fourth to handle fuse notifications.
- */
- fuse_opt_insert_arg(args, 1, "-omax_threads=4");
-
if (ff->debug) {
int i;
@@ -5463,6 +5492,107 @@ static void fuse4fs_com_err_proc(const char *whoami, errcode_t code,
fflush(stderr);
}
+static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff)
+{
+ struct fuse_cmdline_opts opts;
+ struct fuse_session *se;
+ struct fuse_loop_config *loop_config = NULL;
+ int ret;
+
+ if (fuse_parse_cmdline(args, &opts) != 0) {
+ ret = 1;
+ goto out;
+ }
+
+ if (ff->debug)
+ opts.debug = true;
+
+ if (opts.show_help) {
+ fuse_cmdline_help();
+ ret = 0;
+ goto out_free_opts;
+ }
+
+ if (opts.show_version) {
+ printf("FUSE library version %s\n", fuse_pkgversion());
+ ret = 0;
+ goto out_free_opts;
+ }
+
+ if (!opts.mountpoint) {
+ fprintf(stderr, "error: no mountpoint specified\n");
+ ret = 2;
+ goto out_free_opts;
+ }
+
+ se = fuse_session_new(args, &fs_ops, sizeof(fs_ops), ff);
+ if (se == NULL) {
+ ret = 3;
+ goto out_free_opts;
+ }
+ ff->fuse = se;
+
+ if (fuse_session_mount(se, opts.mountpoint) != 0) {
+ ret = 4;
+ goto out_destroy_session;
+ }
+
+ if (fuse_daemonize(opts.foreground) != 0) {
+ ret = 5;
+ goto out_unmount;
+ }
+
+ /*
+ * Configure logging a second time, because libfuse might have
+ * redirected std{out,err} as part of daemonization. If this fails,
+ * give up and move on.
+ */
+ fuse4fs_setup_logging(ff);
+ if (ff->logfd >= 0)
+ close(ff->logfd);
+ ff->logfd = -1;
+
+ if (fuse_set_signal_handlers(se) != 0) {
+ ret = 6;
+ goto out_unmount;
+ }
+
+ loop_config = fuse_loop_cfg_create();
+ if (loop_config == NULL) {
+ ret = 7;
+ goto out_remove_signal_handlers;
+ }
+
+ /*
+ * Since there's a Big Kernel Lock around all the libext2fs code, we
+ * only need to start four threads -- one to decode a request, another
+ * to do the filesystem work, a third to transmit the reply, and a
+ * fourth to handle fuse notifications.
+ */
+ fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+ fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+ fuse_loop_cfg_set_max_threads(loop_config, 4);
+
+ if (fuse_session_loop_mt(se, loop_config) != 0) {
+ ret = 8;
+ goto out_loopcfg;
+ }
+
+out_loopcfg:
+ fuse_loop_cfg_destroy(loop_config);
+out_remove_signal_handlers:
+ fuse_remove_signal_handlers(se);
+out_unmount:
+ fuse_session_unmount(se);
+out_destroy_session:
+ ff->fuse = NULL;
+ fuse_session_destroy(se);
+out_free_opts:
+ free(opts.mountpoint);
+out:
+ return ret;
+}
+
int main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
@@ -5559,7 +5689,7 @@ int main(int argc, char *argv[])
fuse4fs_compute_libfuse_args(&fctx, &args, argv[0]);
- ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+ ret = fuse4fs_main(&args, &fctx);
switch(ret) {
case 0:
/* success */
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 06/23] libsupport: port the kernel list.h to libsupport
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (4 preceding siblings ...)
2025-11-06 22:44 ` [PATCH 05/23] fuse4fs: convert to low level API Darrick J. Wong
@ 2025-11-06 22:44 ` Darrick J. Wong
2025-11-06 22:44 ` [PATCH 07/23] libsupport: add a cache Darrick J. Wong
` (16 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
In the next patch, we're going to add the xfsprogs cache manager code to
e2fsprogs. That code is going into libsupport so that it doesn't become
part of the libext2fs ABI, and it depends on a richer set of list_head
helpers than what is in kernel-list.h, so port the Linux 6.17 list.h to
libsupport and drop the one in libext2fs.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/jfs_compat.h | 2
lib/ext2fs/kernel-list.h | 111 ------
lib/support/list.h | 894 ++++++++++++++++++++++++++++++++++++++++++++++
debugfs/Makefile.in | 12 -
e2fsck/Makefile.in | 56 +--
fuse4fs/Makefile.in | 6
lib/e2p/Makefile.in | 4
lib/ext2fs/Makefile.in | 14 -
misc/Makefile.in | 12 -
misc/tune2fs.c | 4
10 files changed, 947 insertions(+), 168 deletions(-)
delete mode 100644 lib/ext2fs/kernel-list.h
create mode 100644 lib/support/list.h
diff --git a/lib/ext2fs/jfs_compat.h b/lib/ext2fs/jfs_compat.h
index 30b05822b6fd4d..8e598bcfa73ef7 100644
--- a/lib/ext2fs/jfs_compat.h
+++ b/lib/ext2fs/jfs_compat.h
@@ -2,7 +2,7 @@
#ifndef _JFS_COMPAT_H
#define _JFS_COMPAT_H
-#include "kernel-list.h"
+#include "support/list.h"
#include <errno.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
diff --git a/lib/ext2fs/kernel-list.h b/lib/ext2fs/kernel-list.h
deleted file mode 100644
index dd7b8e07dd56c4..00000000000000
--- a/lib/ext2fs/kernel-list.h
+++ /dev/null
@@ -1,111 +0,0 @@
-#ifndef _LINUX_LIST_H
-#define _LINUX_LIST_H
-
-#include "compiler.h"
-
-/*
- * Simple doubly linked list implementation.
- *
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-struct list_head {
- struct list_head *next, *prev;
-};
-
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
-
-#define INIT_LIST_HEAD(ptr) do { \
- (ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
-
-#if (!defined(__GNUC__) && !defined(__WATCOMC__))
-#define __inline__
-#endif
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static __inline__ void __list_add(struct list_head * new,
- struct list_head * prev,
- struct list_head * next)
-{
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
-}
-
-/*
- * Insert a new entry after the specified head..
- */
-static __inline__ void list_add(struct list_head *new, struct list_head *head)
-{
- __list_add(new, head, head->next);
-}
-
-/*
- * Insert a new entry at the tail
- */
-static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
-{
- __list_add(new, head->prev, head);
-}
-
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static __inline__ void __list_del(struct list_head * prev,
- struct list_head * next)
-{
- next->prev = prev;
- prev->next = next;
-}
-
-static __inline__ void list_del(struct list_head *entry)
-{
- __list_del(entry->prev, entry->next);
-}
-
-static __inline__ int list_empty(struct list_head *head)
-{
- return head->next == head;
-}
-
-/*
- * Splice in "list" into "head"
- */
-static __inline__ void list_splice(struct list_head *list, struct list_head *head)
-{
- struct list_head *first = list->next;
-
- if (first != list) {
- struct list_head *last = list->prev;
- struct list_head *at = head->next;
-
- first->prev = head;
- head->next = first;
-
- last->next = at;
- at->prev = last;
- }
-}
-
-#define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
-
-#define list_for_each(pos, head) \
- for (pos = (head)->next; pos != (head); pos = pos->next)
-
-#endif
diff --git a/lib/support/list.h b/lib/support/list.h
new file mode 100644
index 00000000000000..df6c99708e4a8e
--- /dev/null
+++ b/lib/support/list.h
@@ -0,0 +1,894 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <stdbool.h>
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#ifdef __GNUC__
+#define container_of(ptr, type, member) ({ \
+ __typeof__( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+#else
+#define container_of(ptr, type, member) \
+ ((type *)((char *)(ptr) - offsetof(type, member)))
+#endif
+
+/*
+ * Circular doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+/**
+ * INIT_LIST_HEAD - Initialize a list_head structure
+ * @list: list_head structure to be initialized.
+ *
+ * Initializes the list_head to point to itself. If it is a list header,
+ * the result is an empty list.
+ */
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+#ifdef CONFIG_LIST_HARDENED
+
+#ifdef CONFIG_DEBUG_LIST
+# define __list_valid_slowpath
+#else
+# define __list_valid_slowpath __cold __preserve_most
+#endif
+
+/*
+ * Performs the full set of list corruption checks before __list_add().
+ * On list corruption reports a warning, and returns false.
+ */
+bool __list_valid_slowpath __list_add_valid_or_report(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next);
+
+/*
+ * Performs list corruption checks before __list_add(). Returns false if a
+ * corruption is detected, true otherwise.
+ *
+ * With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
+ * inline to catch non-faulting corruptions, and only if a corruption is
+ * detected calls the reporting function __list_add_valid_or_report().
+ */
+static __always_inline bool __list_add_valid(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ bool ret = true;
+
+ if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
+ /*
+ * With the hardening version, elide checking if next and prev
+ * are NULL, since the immediate dereference of them below would
+ * result in a fault if NULL.
+ *
+ * With the reduced set of checks, we can afford to inline the
+ * checks, which also gives the compiler a chance to elide some
+ * of them completely if they can be proven at compile-time. If
+ * one of the pre-conditions does not hold, the slow-path will
+ * show a report which pre-condition failed.
+ */
+ if (likely(next->prev == prev && prev->next == next && new != prev && new != next))
+ return true;
+ ret = false;
+ }
+
+ ret &= __list_add_valid_or_report(new, prev, next);
+ return ret;
+}
+
+/*
+ * Performs the full set of list corruption checks before __list_del_entry().
+ * On list corruption reports a warning, and returns false.
+ */
+bool __list_valid_slowpath __list_del_entry_valid_or_report(struct list_head *entry);
+
+/*
+ * Performs list corruption checks before __list_del_entry(). Returns false if a
+ * corruption is detected, true otherwise.
+ *
+ * With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
+ * inline to catch non-faulting corruptions, and only if a corruption is
+ * detected calls the reporting function __list_del_entry_valid_or_report().
+ */
+static __always_inline bool __list_del_entry_valid(struct list_head *entry)
+{
+ bool ret = true;
+
+ if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
+ struct list_head *prev = entry->prev;
+ struct list_head *next = entry->next;
+
+ /*
+ * With the hardening version, elide checking if next and prev
+ * are NULL, LIST_POISON1 or LIST_POISON2, since the immediate
+ * dereference of them below would result in a fault.
+ */
+ if (likely(prev->next == entry && next->prev == entry))
+ return true;
+ ret = false;
+ }
+
+ ret &= __list_del_entry_valid_or_report(entry);
+ return ret;
+}
+#else
+static inline bool __list_add_valid(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ return true;
+}
+static inline bool __list_del_entry_valid(struct list_head *entry)
+{
+ return true;
+}
+#endif
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ if (!__list_add_valid(new, prev, next))
+ return;
+
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/*
+ * Delete a list entry and clear the 'prev' pointer.
+ *
+ * This is a special-purpose list clearing method used in the networking code
+ * for lists allocated as per-cpu, where we don't want to incur the extra
+ * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
+ * needs to check the node 'prev' pointer instead of calling list_empty().
+ */
+static inline void __list_del_clearprev(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->prev = NULL;
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+ if (!__list_del_entry_valid(entry))
+ return;
+
+ __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del_entry(entry);
+ entry->next = NULL;
+ entry->prev = NULL;
+}
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+ struct list_head *new)
+{
+ new->next = old->next;
+ new->next->prev = new;
+ new->prev = old->prev;
+ new->prev->next = new;
+}
+
+/**
+ * list_replace_init - replace old entry by new one and initialize the old one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace_init(struct list_head *old,
+ struct list_head *new)
+{
+ list_replace(old, new);
+ INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
+ * @entry1: the location to place entry2
+ * @entry2: the location to place entry1
+ */
+static inline void list_swap(struct list_head *entry1,
+ struct list_head *entry2)
+{
+ struct list_head *pos = entry2->prev;
+
+ list_del(entry2);
+ list_replace(entry1, entry2);
+ if (pos == entry1)
+ pos = entry2;
+ list_add(entry1, pos);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del_entry(entry);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_bulk_move_tail - move a subsection of a list to its tail
+ * @head: the head that will follow our entry
+ * @first: first entry to move
+ * @last: last entry to move, can be the same as first
+ *
+ * Move all entries between @first and including @last before @head.
+ * All three entries must belong to the same linked list.
+ */
+static inline void list_bulk_move_tail(struct list_head *head,
+ struct list_head *first,
+ struct list_head *last)
+{
+ first->prev->next = last->next;
+ last->next->prev = first->prev;
+
+ head->prev->next = first;
+ first->prev = head->prev;
+
+ last->next = head;
+ head->prev = last;
+}
+
+/**
+ * list_is_first -- tests whether @list is the first entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_first(const struct list_head *list, const struct list_head *head)
+{
+ return list->prev == head;
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list, const struct list_head *head)
+{
+ return list->next == head;
+}
+
+/**
+ * list_is_head - tests whether @list is the list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_head(const struct list_head *list, const struct list_head *head)
+{
+ return list == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_rotate_left - rotate the list to the left
+ * @head: the head of the list
+ */
+static inline void list_rotate_left(struct list_head *head)
+{
+ struct list_head *first;
+
+ if (!list_empty(head)) {
+ first = head->next;
+ list_move_tail(first, head);
+ }
+}
+
+/**
+ * list_rotate_to_front() - Rotate list to specific item.
+ * @list: The desired new front of the list.
+ * @head: The head of the list.
+ *
+ * Rotates list so that @list becomes the new front of the list.
+ */
+static inline void list_rotate_to_front(struct list_head *list,
+ struct list_head *head)
+{
+ /*
+ * Deletes the list head from the list denoted by @head and
+ * places it as the tail of @list, this effectively rotates the
+ * list so that @list is at the front.
+ */
+ list_move_tail(head, list);
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+ return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ struct list_head *new_first = entry->next;
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry;
+ entry->next = list;
+ head->next = new_first;
+ new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ * and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ if (list_empty(head))
+ return;
+ if (list_is_singular(head) && !list_is_head(entry, head) && (entry != head->next))
+ return;
+ if (list_is_head(entry, head))
+ INIT_LIST_HEAD(list);
+ else
+ __list_cut_position(list, head, entry);
+}
+
+/**
+ * list_cut_before - cut a list into two, before given entry
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *
+ * This helper moves the initial part of @head, up to but
+ * excluding @entry, from @head to @list. You should pass
+ * in @entry an element you know is on @head. @list should
+ * be an empty list or a list you do not care about losing
+ * its data.
+ * If @entry == @head, all entries on @head are moved to
+ * @list.
+ */
+static inline void list_cut_before(struct list_head *list,
+ struct list_head *head,
+ struct list_head *entry)
+{
+ if (head->next == entry) {
+ INIT_LIST_HEAD(list);
+ return;
+ }
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry->prev;
+ list->prev->next = list;
+ head->next = entry;
+ entry->prev = head;
+}
+
+static inline void __list_splice(const struct list_head *list,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+
+ first->prev = prev;
+ prev->next = first;
+
+ last->next = next;
+ next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head, head->next);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head->prev, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_last_entry - get the last element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+ list_entry((ptr)->prev, type, member)
+
+/**
+ * list_first_entry_or_null - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_first_entry_or_null(ptr, type, member) ({ \
+ struct list_head *head__ = (ptr); \
+ struct list_head *pos__ = head__->next; \
+ pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+ list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_next_entry_circular - get the next element in list
+ * @pos: the type * to cursor.
+ * @head: the list head to take the element from.
+ * @member: the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the last element (return the first element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_next_entry_circular(pos, head, member) \
+ (list_is_last(&(pos)->member, head) ? \
+ list_first_entry(head, typeof(*(pos)), member) : list_next_entry(pos, member))
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+ list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry_circular - get the prev element in list
+ * @pos: the type * to cursor.
+ * @head: the list head to take the element from.
+ * @member: the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the first element (return the last element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_prev_entry_circular(pos, head, member) \
+ (list_is_first(&(pos)->member, head) ? \
+ list_last_entry(head, typeof(*(pos)), member) : list_prev_entry(pos, member))
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; !list_is_head(pos, (head)); pos = pos->next)
+
+/**
+ * list_for_each_rcu - Iterate over a list in an RCU-safe fashion
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_rcu(pos, head) \
+ for (pos = rcu_dereference((head)->next); \
+ !list_is_head(pos, (head)); \
+ pos = rcu_dereference(pos->next))
+
+/**
+ * list_for_each_continue - continue iteration over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ *
+ * Continue to iterate over a list, continuing after the current position.
+ */
+#define list_for_each_continue(pos, head) \
+ for (pos = pos->next; !list_is_head(pos, (head)); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; !list_is_head(pos, (head)); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; \
+ !list_is_head(pos, (head)); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+ for (pos = (head)->prev, n = pos->prev; \
+ !list_is_head(pos, (head)); \
+ pos = n, n = pos->prev)
+
+/**
+ * list_count_nodes - count nodes in the list
+ * @head: the head for your list.
+ */
+static inline size_t list_count_nodes(struct list_head *head)
+{
+ struct list_head *pos;
+ size_t count = 0;
+
+ list_for_each(pos, head)
+ count++;
+
+ return count;
+}
+
+/**
+ * list_entry_is_head - test if the entry points to the head of the list
+ * @pos: the type * to cursor
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_entry_is_head(pos, head, member) \
+ list_is_head(&pos->member, (head))
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_head within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_next_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member) \
+ for (pos = list_prev_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) \
+ for (; !list_entry_is_head(pos, head, member); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_from_reverse - iterate backwards over list of given type
+ * from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from_reverse(pos, head, member) \
+ for (; !list_entry_is_head(pos, head, member); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member), \
+ n = list_next_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) \
+ for (pos = list_next_entry(pos, member), \
+ n = list_next_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) \
+ for (n = list_next_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member), \
+ n = list_prev_entry(pos, member); \
+ !list_entry_is_head(pos, head, member); \
+ pos = n, n = list_prev_entry(n, member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos: the loop cursor used in the list_for_each_entry_safe loop
+ * @n: temporary storage used in list_for_each_entry_safe
+ * @member: the name of the list_head within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member) \
+ n = list_next_entry(pos, member)
+
+#endif
diff --git a/debugfs/Makefile.in b/debugfs/Makefile.in
index 689bf0c4a3c13d..700ae87418c268 100644
--- a/debugfs/Makefile.in
+++ b/debugfs/Makefile.in
@@ -195,7 +195,7 @@ debugfs.o: $(srcdir)/debugfs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/version.h \
$(srcdir)/../e2fsck/jfs_user.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/plausible.h
util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ss/ss.h \
@@ -287,7 +287,7 @@ logdump.o: $(srcdir)/logdump.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/../e2fsck/jfs_user.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h
htree.o: $(srcdir)/htree.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
@@ -408,7 +408,7 @@ journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h
revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -418,7 +418,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h
recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -428,7 +428,7 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h
do_journal.o: $(srcdir)/do_journal.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
@@ -442,7 +442,7 @@ do_journal.o: $(srcdir)/do_journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/journal.h $(srcdir)/../e2fsck/jfs_user.h
do_orphan.o: $(srcdir)/do_orphan.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
diff --git a/e2fsck/Makefile.in b/e2fsck/Makefile.in
index fbb7b156d5c759..52fad9cbfd2b23 100644
--- a/e2fsck/Makefile.in
+++ b/e2fsck/Makefile.in
@@ -282,7 +282,7 @@ e2fsck.o: $(srcdir)/e2fsck.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
super.o: $(srcdir)/super.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -296,7 +296,7 @@ super.o: $(srcdir)/super.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
pass1.o: $(srcdir)/pass1.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -310,7 +310,7 @@ pass1.o: $(srcdir)/pass1.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/e2p/e2p.h $(srcdir)/problem.h
pass1b.o: $(srcdir)/pass1b.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
@@ -324,7 +324,7 @@ pass1b.o: $(srcdir)/pass1b.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(top_srcdir)/lib/support/dict.h
pass2.o: $(srcdir)/pass2.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -338,7 +338,7 @@ pass2.o: $(srcdir)/pass2.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(top_srcdir)/lib/support/dict.h
pass3.o: $(srcdir)/pass3.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -352,7 +352,7 @@ pass3.o: $(srcdir)/pass3.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
pass4.o: $(srcdir)/pass4.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -366,7 +366,7 @@ pass4.o: $(srcdir)/pass4.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
pass5.o: $(srcdir)/pass5.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -380,7 +380,7 @@ pass5.o: $(srcdir)/pass5.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/jfs_user.h $(srcdir)/e2fsck.h \
@@ -394,7 +394,7 @@ journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(srcdir)/problem.h
recovery.o: $(srcdir)/recovery.c $(srcdir)/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -408,7 +408,7 @@ recovery.o: $(srcdir)/recovery.c $(srcdir)/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
revoke.o: $(srcdir)/revoke.c $(srcdir)/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -422,7 +422,7 @@ revoke.o: $(srcdir)/revoke.c $(srcdir)/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
@@ -436,7 +436,7 @@ badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -449,7 +449,7 @@ util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
unix.o: $(srcdir)/unix.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/e2p/e2p.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -463,7 +463,7 @@ unix.o: $(srcdir)/unix.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(srcdir)/jfs_user.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/version.h
dirinfo.o: $(srcdir)/dirinfo.c $(top_builddir)/lib/config.h \
@@ -478,7 +478,7 @@ dirinfo.o: $(srcdir)/dirinfo.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/tdb.h
dx_dirinfo.o: $(srcdir)/dx_dirinfo.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -492,7 +492,7 @@ dx_dirinfo.o: $(srcdir)/dx_dirinfo.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
ehandler.o: $(srcdir)/ehandler.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -505,7 +505,7 @@ ehandler.o: $(srcdir)/ehandler.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
problem.o: $(srcdir)/problem.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -518,7 +518,7 @@ problem.o: $(srcdir)/problem.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(srcdir)/problemP.h
message.o: $(srcdir)/message.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/support/quotaio.h \
@@ -531,7 +531,7 @@ message.o: $(srcdir)/message.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/support/profile.h $(top_builddir)/lib/support/prof_err.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
ea_refcount.o: $(srcdir)/ea_refcount.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -545,7 +545,7 @@ ea_refcount.o: $(srcdir)/ea_refcount.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
rehash.o: $(srcdir)/rehash.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -558,7 +558,7 @@ rehash.o: $(srcdir)/rehash.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(top_srcdir)/lib/support/sort_r.h
readahead.o: $(srcdir)/readahead.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -572,7 +572,7 @@ readahead.o: $(srcdir)/readahead.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
region.o: $(srcdir)/region.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -585,7 +585,7 @@ region.o: $(srcdir)/region.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
sigcatcher.o: $(srcdir)/sigcatcher.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -598,7 +598,7 @@ sigcatcher.o: $(srcdir)/sigcatcher.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
logfile.o: $(srcdir)/logfile.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -611,7 +611,7 @@ logfile.o: $(srcdir)/logfile.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
quota.o: $(srcdir)/quota.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -624,7 +624,7 @@ quota.o: $(srcdir)/quota.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
extents.o: $(srcdir)/extents.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -638,7 +638,7 @@ extents.o: $(srcdir)/extents.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h
encrypted_files.o: $(srcdir)/encrypted_files.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -652,5 +652,5 @@ encrypted_files.o: $(srcdir)/encrypted_files.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(srcdir)/problem.h $(top_srcdir)/lib/ext2fs/rbtree.h
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
index bc137a765ee2b7..6b41d1dd5ffe8d 100644
--- a/fuse4fs/Makefile.in
+++ b/fuse4fs/Makefile.in
@@ -160,7 +160,7 @@ journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -174,7 +174,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -188,5 +188,5 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/lib/e2p/Makefile.in b/lib/e2p/Makefile.in
index 92d9c018fe46c8..f642f5ec367c93 100644
--- a/lib/e2p/Makefile.in
+++ b/lib/e2p/Makefile.in
@@ -130,7 +130,7 @@ feature.o: $(srcdir)/feature.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h
fgetflags.o: $(srcdir)/fgetflags.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2p.h \
@@ -173,7 +173,7 @@ ljs.o: $(srcdir)/ljs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/e2p.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
mntopts.o: $(srcdir)/mntopts.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/e2p.h \
$(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h
diff --git a/lib/ext2fs/Makefile.in b/lib/ext2fs/Makefile.in
index e9a6ced244ea26..1d0991defff804 100644
--- a/lib/ext2fs/Makefile.in
+++ b/lib/ext2fs/Makefile.in
@@ -1032,7 +1032,7 @@ mkjournal.o: $(srcdir)/mkjournal.c $(top_builddir)/lib/config.h \
$(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h $(srcdir)/ext2_io.h \
$(top_builddir)/lib/ext2fs/ext2_err.h $(srcdir)/ext2_ext_attr.h \
$(srcdir)/hashmap.h $(srcdir)/bitops.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h
mmp.o: $(srcdir)/mmp.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/ext2_fs.h \
$(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/ext2fs.h \
@@ -1263,7 +1263,7 @@ debugfs.o: $(top_srcdir)/debugfs/debugfs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/debugfs/../version.h \
$(srcdir)/../../e2fsck/jfs_user.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h \
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h \
$(top_srcdir)/lib/support/plausible.h
util.o: $(top_srcdir)/debugfs/util.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ss/ss.h \
@@ -1353,7 +1353,7 @@ logdump.o: $(top_srcdir)/debugfs/logdump.c $(top_builddir)/lib/config.h \
$(top_srcdir)/debugfs/../misc/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \
$(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/../../e2fsck/jfs_user.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
$(srcdir)/compiler.h $(srcdir)/fast_commit.h
htree.o: $(top_srcdir)/debugfs/htree.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
@@ -1469,14 +1469,14 @@ journal.o: $(top_srcdir)/debugfs/journal.c $(top_builddir)/lib/config.h \
$(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h $(srcdir)/ext2_io.h \
$(top_builddir)/lib/ext2fs/ext2_err.h $(srcdir)/ext2_ext_attr.h \
$(srcdir)/hashmap.h $(srcdir)/bitops.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h
revoke.o: $(top_srcdir)/e2fsck/revoke.c $(top_srcdir)/e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
$(srcdir)/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
$(srcdir)/ext2fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
$(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
$(srcdir)/ext2_ext_attr.h $(srcdir)/hashmap.h $(srcdir)/bitops.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
$(srcdir)/compiler.h
recovery.o: $(top_srcdir)/e2fsck/recovery.c $(top_srcdir)/e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -1484,7 +1484,7 @@ recovery.o: $(top_srcdir)/e2fsck/recovery.c $(top_srcdir)/e2fsck/jfs_user.h \
$(srcdir)/ext2fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
$(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
$(srcdir)/ext2_ext_attr.h $(srcdir)/hashmap.h $(srcdir)/bitops.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
$(srcdir)/compiler.h
do_journal.o: $(top_srcdir)/debugfs/do_journal.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
@@ -1497,7 +1497,7 @@ do_journal.o: $(top_srcdir)/debugfs/do_journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/debugfs/../misc/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \
$(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h \
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h \
$(top_srcdir)/debugfs/journal.h $(srcdir)/../../e2fsck/jfs_user.h
do_orphan.o: $(top_srcdir)/debugfs/do_orphan.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
diff --git a/misc/Makefile.in b/misc/Makefile.in
index b63a0424b19fec..ec964688acd623 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -736,7 +736,7 @@ tune2fs.o: $(srcdir)/tune2fs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/plausible.h \
$(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/lib/support/devname.h \
@@ -789,7 +789,7 @@ dumpe2fs.o: $(srcdir)/dumpe2fs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/e2p/e2p.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/support/devname.h $(top_srcdir)/lib/support/nls-enable.h \
$(top_srcdir)/lib/support/plausible.h $(top_srcdir)/version.h
badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
@@ -812,7 +812,7 @@ util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/ext2fs/ext2_err.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
$(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/nls-enable.h \
$(top_srcdir)/lib/support/devname.h $(srcdir)/util.h
uuidgen.o: $(srcdir)/uuidgen.c $(top_builddir)/lib/config.h \
@@ -907,7 +907,7 @@ journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -921,7 +921,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -935,5 +935,5 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h \
$(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
$(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/misc/tune2fs.c b/misc/tune2fs.c
index 3db57632c88d42..ac440176351e83 100644
--- a/misc/tune2fs.c
+++ b/misc/tune2fs.c
@@ -2857,10 +2857,6 @@ static int expand_inode_table(ext2_filsys fs, unsigned long new_ino_size)
}
-#define list_for_each_safe(pos, pnext, head) \
- for (pos = (head)->next, pnext = pos->next; pos != (head); \
- pos = pnext, pnext = pos->next)
-
static void free_blk_move_list(void)
{
struct list_head *entry, *tmp;
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 07/23] libsupport: add a cache
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (5 preceding siblings ...)
2025-11-06 22:44 ` [PATCH 06/23] libsupport: port the kernel list.h to libsupport Darrick J. Wong
@ 2025-11-06 22:44 ` Darrick J. Wong
2025-11-06 22:45 ` [PATCH 08/23] cache: disable debugging Darrick J. Wong
` (15 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Reuse the cache code from xfsprogs.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 139 +++++++++
lib/support/list.h | 7
lib/support/xbitops.h | 128 ++++++++
lib/support/Makefile.in | 8 -
lib/support/cache.c | 739 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1019 insertions(+), 2 deletions(-)
create mode 100644 lib/support/cache.h
create mode 100644 lib/support/xbitops.h
create mode 100644 lib/support/cache.c
diff --git a/lib/support/cache.h b/lib/support/cache.h
new file mode 100644
index 00000000000000..16b17a9b7a1a51
--- /dev/null
+++ b/lib/support/cache.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2006 Silicon Graphics, Inc.
+ * All Rights Reserved.
+ */
+#ifndef __CACHE_H__
+#define __CACHE_H__
+
+/*
+ * initialisation flags
+ */
+/*
+ * xfs_db always writes changes immediately, and so we need to purge buffers
+ * when we get a buffer lookup mismatch due to reading the same block with a
+ * different buffer configuration.
+ */
+#define CACHE_MISCOMPARE_PURGE (1 << 0)
+
+/*
+ * cache object campare return values
+ */
+enum {
+ CACHE_HIT,
+ CACHE_MISS,
+ CACHE_PURGE,
+};
+
+#define HASH_CACHE_RATIO 8
+
+/*
+ * Cache priorities range from BASE to MAX.
+ *
+ * For prefetch support, the top half of the range starts at
+ * CACHE_PREFETCH_PRIORITY and everytime the buffer is fetched and is at or
+ * above this priority level, it is reduced to below this level (refer to
+ * libxfs_buf_get).
+ *
+ * If we have dirty nodes, we can't recycle them until they've been cleaned. To
+ * keep these out of the reclaimable lists (as there can be lots of them) give
+ * them their own priority that the shaker doesn't attempt to walk.
+ */
+
+#define CACHE_BASE_PRIORITY 0
+#define CACHE_PREFETCH_PRIORITY 8
+#define CACHE_MAX_PRIORITY 15
+#define CACHE_DIRTY_PRIORITY (CACHE_MAX_PRIORITY + 1)
+#define CACHE_NR_PRIORITIES CACHE_DIRTY_PRIORITY
+
+/*
+ * Simple, generic implementation of a cache (arbitrary data).
+ * Provides a hash table with a capped number of cache entries.
+ */
+
+struct cache;
+struct cache_node;
+
+typedef void *cache_key_t;
+
+typedef void (*cache_walk_t)(struct cache_node *);
+typedef struct cache_node * (*cache_node_alloc_t)(cache_key_t);
+typedef int (*cache_node_flush_t)(struct cache_node *);
+typedef void (*cache_node_relse_t)(struct cache_node *);
+typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
+ unsigned int);
+typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
+typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
+typedef int (*cache_node_get_t)(struct cache_node *);
+typedef void (*cache_node_put_t)(struct cache_node *);
+
+struct cache_operations {
+ cache_node_hash_t hash;
+ cache_node_alloc_t alloc;
+ cache_node_flush_t flush;
+ cache_node_relse_t relse;
+ cache_node_compare_t compare;
+ cache_bulk_relse_t bulkrelse; /* optional */
+ cache_node_get_t get; /* optional */
+ cache_node_put_t put; /* optional */
+};
+
+struct cache_hash {
+ struct list_head ch_list; /* hash chain head */
+ unsigned int ch_count; /* hash chain length */
+ pthread_mutex_t ch_mutex; /* hash chain mutex */
+};
+
+struct cache_mru {
+ struct list_head cm_list; /* MRU head */
+ unsigned int cm_count; /* MRU length */
+ pthread_mutex_t cm_mutex; /* MRU lock */
+};
+
+struct cache_node {
+ struct list_head cn_hash; /* hash chain */
+ struct list_head cn_mru; /* MRU chain */
+ unsigned int cn_count; /* reference count */
+ unsigned int cn_hashidx; /* hash chain index */
+ int cn_priority; /* priority, -1 = free list */
+ int cn_old_priority;/* saved pre-dirty prio */
+ pthread_mutex_t cn_mutex; /* node mutex */
+};
+
+struct cache {
+ int c_flags; /* behavioural flags */
+ unsigned int c_maxcount; /* max cache nodes */
+ unsigned int c_count; /* count of nodes */
+ pthread_mutex_t c_mutex; /* node count mutex */
+ cache_node_hash_t hash; /* node hash function */
+ cache_node_alloc_t alloc; /* allocation function */
+ cache_node_flush_t flush; /* flush dirty data function */
+ cache_node_relse_t relse; /* memory free function */
+ cache_node_compare_t compare; /* comparison routine */
+ cache_bulk_relse_t bulkrelse; /* bulk release routine */
+ cache_node_get_t get; /* prepare cache node after get */
+ cache_node_put_t put; /* prepare to put cache node */
+ unsigned int c_hashsize; /* hash bucket count */
+ unsigned int c_hashshift; /* hash key shift */
+ struct cache_hash *c_hash; /* hash table buckets */
+ struct cache_mru c_mrus[CACHE_DIRTY_PRIORITY + 1];
+ unsigned long long c_misses; /* cache misses */
+ unsigned long long c_hits; /* cache hits */
+ unsigned int c_max; /* max nodes ever used */
+};
+
+struct cache *cache_init(int, unsigned int, const struct cache_operations *);
+void cache_destroy(struct cache *);
+void cache_walk(struct cache *, cache_walk_t);
+void cache_purge(struct cache *);
+void cache_flush(struct cache *);
+
+int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
+void cache_node_put(struct cache *, struct cache_node *);
+void cache_node_set_priority(struct cache *, struct cache_node *, int);
+int cache_node_get_priority(struct cache_node *);
+int cache_node_purge(struct cache *, cache_key_t, struct cache_node *);
+void cache_report(FILE *fp, const char *, struct cache *);
+int cache_overflowed(struct cache *);
+
+#endif /* __CACHE_H__ */
diff --git a/lib/support/list.h b/lib/support/list.h
index df6c99708e4a8e..0e00e446dd7214 100644
--- a/lib/support/list.h
+++ b/lib/support/list.h
@@ -17,6 +17,13 @@ struct list_head {
((type *)((char *)(ptr) - offsetof(type, member)))
#endif
+static inline void list_head_destroy(struct list_head *list)
+{
+ list->next = list->prev = NULL;
+}
+
+#define list_head_init(list) INIT_LIST_HEAD(list)
+
/*
* Circular doubly linked list implementation.
*
diff --git a/lib/support/xbitops.h b/lib/support/xbitops.h
new file mode 100644
index 00000000000000..78a8f2a8545f4c
--- /dev/null
+++ b/lib/support/xbitops.h
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef __BITOPS_H__
+#define __BITOPS_H__
+
+/*
+ * fls: find last bit set.
+ */
+
+static inline int fls(int x)
+{
+ int r = 32;
+
+ if (!x)
+ return 0;
+ if (!(x & 0xffff0000u)) {
+ x = (x & 0xffffu) << 16;
+ r -= 16;
+ }
+ if (!(x & 0xff000000u)) {
+ x = (x & 0xffffffu) << 8;
+ r -= 8;
+ }
+ if (!(x & 0xf0000000u)) {
+ x = (x & 0xfffffffu) << 4;
+ r -= 4;
+ }
+ if (!(x & 0xc0000000u)) {
+ x = (x & 0x3fffffffu) << 2;
+ r -= 2;
+ }
+ if (!(x & 0x80000000u)) {
+ r -= 1;
+ }
+ return r;
+}
+
+static inline int fls64(uint64_t x)
+{
+ uint32_t h = x >> 32;
+ if (h)
+ return fls(h) + 32;
+ return fls(x);
+}
+
+static inline unsigned fls_long(unsigned long l)
+{
+ if (sizeof(l) == 4)
+ return fls(l);
+ return fls64(l);
+}
+
+/*
+ * ffz: find first zero bit.
+ * Result is undefined if no zero bit exists.
+ */
+#define ffz(x) ffs(~(x))
+
+/*
+ * XFS bit manipulation routines. Repeated here so that some programs
+ * don't have to link in all of libxfs just to have bit manipulation.
+ */
+
+/*
+ * masks with n high/low bits set, 64-bit values
+ */
+static inline uint64_t mask64hi(int n)
+{
+ return (uint64_t)-1 << (64 - (n));
+}
+static inline uint32_t mask32lo(int n)
+{
+ return ((uint32_t)1 << (n)) - 1;
+}
+static inline uint64_t mask64lo(int n)
+{
+ return ((uint64_t)1 << (n)) - 1;
+}
+
+/* Get high bit set out of 32-bit argument, -1 if none set */
+static inline int highbit32(uint32_t v)
+{
+ return fls(v) - 1;
+}
+
+/* Get high bit set out of 64-bit argument, -1 if none set */
+static inline int highbit64(uint64_t v)
+{
+ return fls64(v) - 1;
+}
+
+/* Get low bit set out of 32-bit argument, -1 if none set */
+static inline int lowbit32(uint32_t v)
+{
+ return ffs(v) - 1;
+}
+
+/* Get low bit set out of 64-bit argument, -1 if none set */
+static inline int lowbit64(uint64_t v)
+{
+ uint32_t w = (uint32_t)v;
+ int n = 0;
+
+ if (w) { /* lower bits */
+ n = ffs(w);
+ } else { /* upper bits */
+ w = (uint32_t)(v >> 32);
+ if (w) {
+ n = ffs(w);
+ if (n)
+ n += 32;
+ }
+ }
+ return n - 1;
+}
+
+/**
+ * __rounddown_pow_of_two() - round down to nearest power of two
+ * @n: value to round down
+ */
+static inline __attribute__((const))
+unsigned long __rounddown_pow_of_two(unsigned long n)
+{
+ return 1UL << (fls_long(n) - 1);
+}
+
+#define rounddown_pow_of_two(n) __rounddown_pow_of_two(n)
+
+#endif
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
index 6383816fd99cd4..a09814f574008c 100644
--- a/lib/support/Makefile.in
+++ b/lib/support/Makefile.in
@@ -26,7 +26,8 @@ OBJS= bthread.o \
quotaio_v2.o \
quotaio_tree.o \
dict.o \
- devname.o
+ devname.o \
+ cache.o
SRCS= $(srcdir)/argv_parse.c \
$(srcdir)/bthread.c \
@@ -42,7 +43,8 @@ SRCS= $(srcdir)/argv_parse.c \
$(srcdir)/quotaio_tree.c \
$(srcdir)/quotaio_v2.c \
$(srcdir)/dict.c \
- $(srcdir)/devname.c
+ $(srcdir)/devname.c \
+ $(srcdir)/cache.c
LIBRARY= libsupport
LIBDIR= support
@@ -187,3 +189,5 @@ dict.o: $(srcdir)/dict.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/dict.h
devname.o: $(srcdir)/devname.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/devname.h $(srcdir)/nls-enable.h
+cache.o: $(srcdir)/cache.c $(top_builddir)/lib/config.h \
+ $(srcdir)/cache.h $(srcdir)/list.h $(srcdir)/xbitops.h
diff --git a/lib/support/cache.c b/lib/support/cache.c
new file mode 100644
index 00000000000000..fe04f62f262aaa
--- /dev/null
+++ b/lib/support/cache.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2006 Silicon Graphics, Inc.
+ * All Rights Reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "list.h"
+#include "cache.h"
+#include "xbitops.h"
+
+#define CACHE_DEBUG 1
+#undef CACHE_DEBUG
+#define CACHE_DEBUG 1
+#undef CACHE_ABORT
+/* #define CACHE_ABORT 1 */
+
+#define CACHE_SHAKE_COUNT 64
+
+#ifdef CACHE_DEBUG
+# include <assert.h>
+# define ASSERT(x) assert(x)
+#endif
+
+static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
+
+struct cache *
+cache_init(
+ int flags,
+ unsigned int hashsize,
+ const struct cache_operations *cache_operations)
+{
+ struct cache * cache;
+ unsigned int i, maxcount;
+
+ maxcount = hashsize * HASH_CACHE_RATIO;
+
+ if (!(cache = malloc(sizeof(struct cache))))
+ return NULL;
+ if (!(cache->c_hash = calloc(hashsize, sizeof(struct cache_hash)))) {
+ free(cache);
+ return NULL;
+ }
+
+ cache->c_flags = flags;
+ cache->c_count = 0;
+ cache->c_max = 0;
+ cache->c_hits = 0;
+ cache->c_misses = 0;
+ cache->c_maxcount = maxcount;
+ cache->c_hashsize = hashsize;
+ cache->c_hashshift = fls(hashsize) - 1;
+ cache->hash = cache_operations->hash;
+ cache->alloc = cache_operations->alloc;
+ cache->flush = cache_operations->flush;
+ cache->relse = cache_operations->relse;
+ cache->compare = cache_operations->compare;
+ cache->bulkrelse = cache_operations->bulkrelse ?
+ cache_operations->bulkrelse : cache_generic_bulkrelse;
+ cache->get = cache_operations->get;
+ cache->put = cache_operations->put;
+ pthread_mutex_init(&cache->c_mutex, NULL);
+
+ for (i = 0; i < hashsize; i++) {
+ list_head_init(&cache->c_hash[i].ch_list);
+ cache->c_hash[i].ch_count = 0;
+ pthread_mutex_init(&cache->c_hash[i].ch_mutex, NULL);
+ }
+
+ for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+ list_head_init(&cache->c_mrus[i].cm_list);
+ cache->c_mrus[i].cm_count = 0;
+ pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
+ }
+ return cache;
+}
+
+static void
+cache_expand(
+ struct cache * cache)
+{
+ pthread_mutex_lock(&cache->c_mutex);
+#ifdef CACHE_DEBUG
+ fprintf(stderr, "doubling cache size to %d\n", 2 * cache->c_maxcount);
+#endif
+ cache->c_maxcount *= 2;
+ pthread_mutex_unlock(&cache->c_mutex);
+}
+
+void
+cache_walk(
+ struct cache * cache,
+ cache_walk_t visit)
+{
+ struct cache_hash * hash;
+ struct list_head * head;
+ struct list_head * pos;
+ unsigned int i;
+
+ for (i = 0; i < cache->c_hashsize; i++) {
+ hash = &cache->c_hash[i];
+ head = &hash->ch_list;
+ pthread_mutex_lock(&hash->ch_mutex);
+ for (pos = head->next; pos != head; pos = pos->next)
+ visit((struct cache_node *)pos);
+ pthread_mutex_unlock(&hash->ch_mutex);
+ }
+}
+
+#ifdef CACHE_ABORT
+#define cache_abort() abort()
+#else
+#define cache_abort() do { } while (0)
+#endif
+
+#ifdef CACHE_DEBUG
+static void
+cache_zero_check(
+ struct cache_node * node)
+{
+ if (node->cn_count > 0) {
+ fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
+ __FUNCTION__, node->cn_count, node);
+ cache_abort();
+ }
+}
+#define cache_destroy_check(c) cache_walk((c), cache_zero_check)
+#else
+#define cache_destroy_check(c) do { } while (0)
+#endif
+
+void
+cache_destroy(
+ struct cache * cache)
+{
+ unsigned int i;
+
+ cache_destroy_check(cache);
+ for (i = 0; i < cache->c_hashsize; i++) {
+ list_head_destroy(&cache->c_hash[i].ch_list);
+ pthread_mutex_destroy(&cache->c_hash[i].ch_mutex);
+ }
+ for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+ list_head_destroy(&cache->c_mrus[i].cm_list);
+ pthread_mutex_destroy(&cache->c_mrus[i].cm_mutex);
+ }
+ pthread_mutex_destroy(&cache->c_mutex);
+ free(cache->c_hash);
+ free(cache);
+}
+
+static unsigned int
+cache_generic_bulkrelse(
+ struct cache * cache,
+ struct list_head * list)
+{
+ struct cache_node * node;
+ unsigned int count = 0;
+
+ while (!list_empty(list)) {
+ node = list_entry(list->next, struct cache_node, cn_mru);
+ pthread_mutex_destroy(&node->cn_mutex);
+ list_del_init(&node->cn_mru);
+ cache->relse(node);
+ count++;
+ }
+
+ return count;
+}
+
+/*
+ * Park unflushable nodes on their own special MRU so that cache_shake() doesn't
+ * end up repeatedly scanning them in the futile attempt to clean them before
+ * reclaim.
+ */
+static void
+cache_add_to_dirty_mru(
+ struct cache *cache,
+ struct cache_node *node)
+{
+ struct cache_mru *mru = &cache->c_mrus[CACHE_DIRTY_PRIORITY];
+
+ pthread_mutex_lock(&mru->cm_mutex);
+ node->cn_old_priority = node->cn_priority;
+ node->cn_priority = CACHE_DIRTY_PRIORITY;
+ list_add(&node->cn_mru, &mru->cm_list);
+ mru->cm_count++;
+ pthread_mutex_unlock(&mru->cm_mutex);
+}
+
+/*
+ * We've hit the limit on cache size, so we need to start reclaiming nodes we've
+ * used. The MRU specified by the priority is shaken. Returns new priority at
+ * end of the call (in case we call again). We are not allowed to reclaim dirty
+ * objects, so we have to flush them first. If flushing fails, we move them to
+ * the "dirty, unreclaimable" list.
+ *
+ * Hence we skip priorities > CACHE_MAX_PRIORITY unless "purge" is set as we
+ * park unflushable (and hence unreclaimable) buffers at these priorities.
+ * Trying to shake unreclaimable buffer lists when there is memory pressure is a
+ * waste of time and CPU and greatly slows down cache node recycling operations.
+ * Hence we only try to free them if we are being asked to purge the cache of
+ * all entries.
+ */
+static unsigned int
+cache_shake(
+ struct cache * cache,
+ unsigned int priority,
+ bool purge)
+{
+ struct cache_mru *mru;
+ struct cache_hash * hash;
+ struct list_head temp;
+ struct list_head * head;
+ struct list_head * pos;
+ struct list_head * n;
+ struct cache_node * node;
+ unsigned int count;
+
+ ASSERT(priority <= CACHE_DIRTY_PRIORITY);
+ if (priority > CACHE_MAX_PRIORITY && !purge)
+ priority = 0;
+
+ mru = &cache->c_mrus[priority];
+ count = 0;
+ list_head_init(&temp);
+ head = &mru->cm_list;
+
+ pthread_mutex_lock(&mru->cm_mutex);
+ for (pos = head->prev, n = pos->prev; pos != head;
+ pos = n, n = pos->prev) {
+ node = list_entry(pos, struct cache_node, cn_mru);
+
+ if (pthread_mutex_trylock(&node->cn_mutex) != 0)
+ continue;
+
+ /* memory pressure is not allowed to release dirty objects */
+ if (cache->flush(node) && !purge) {
+ list_del(&node->cn_mru);
+ mru->cm_count--;
+ node->cn_priority = -1;
+ pthread_mutex_unlock(&node->cn_mutex);
+ cache_add_to_dirty_mru(cache, node);
+ continue;
+ }
+
+ hash = cache->c_hash + node->cn_hashidx;
+ if (pthread_mutex_trylock(&hash->ch_mutex) != 0) {
+ pthread_mutex_unlock(&node->cn_mutex);
+ continue;
+ }
+ ASSERT(node->cn_count == 0);
+ ASSERT(node->cn_priority == priority);
+ node->cn_priority = -1;
+
+ list_move(&node->cn_mru, &temp);
+ list_del_init(&node->cn_hash);
+ hash->ch_count--;
+ mru->cm_count--;
+ pthread_mutex_unlock(&hash->ch_mutex);
+ pthread_mutex_unlock(&node->cn_mutex);
+
+ count++;
+ if (!purge && count == CACHE_SHAKE_COUNT)
+ break;
+ }
+ pthread_mutex_unlock(&mru->cm_mutex);
+
+ if (count > 0) {
+ cache->bulkrelse(cache, &temp);
+
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_count -= count;
+ pthread_mutex_unlock(&cache->c_mutex);
+ }
+
+ return (count == CACHE_SHAKE_COUNT) ? priority : ++priority;
+}
+
+/*
+ * Allocate a new hash node (updating atomic counter in the process),
+ * unless doing so will push us over the maximum cache size.
+ */
+static struct cache_node *
+cache_node_allocate(
+ struct cache * cache,
+ cache_key_t key)
+{
+ unsigned int nodesfree;
+ struct cache_node * node;
+
+ pthread_mutex_lock(&cache->c_mutex);
+ nodesfree = (cache->c_count < cache->c_maxcount);
+ if (nodesfree) {
+ cache->c_count++;
+ if (cache->c_count > cache->c_max)
+ cache->c_max = cache->c_count;
+ }
+ cache->c_misses++;
+ pthread_mutex_unlock(&cache->c_mutex);
+ if (!nodesfree)
+ return NULL;
+ node = cache->alloc(key);
+ if (node == NULL) { /* uh-oh */
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_count--;
+ pthread_mutex_unlock(&cache->c_mutex);
+ return NULL;
+ }
+ pthread_mutex_init(&node->cn_mutex, NULL);
+ list_head_init(&node->cn_mru);
+ node->cn_count = 1;
+ node->cn_priority = 0;
+ node->cn_old_priority = -1;
+ return node;
+}
+
+int
+cache_overflowed(
+ struct cache * cache)
+{
+ return cache->c_maxcount == cache->c_max;
+}
+
+
+static int
+__cache_node_purge(
+ struct cache * cache,
+ struct cache_node * node)
+{
+ int count;
+ struct cache_mru * mru;
+
+ pthread_mutex_lock(&node->cn_mutex);
+ count = node->cn_count;
+ if (count != 0) {
+ pthread_mutex_unlock(&node->cn_mutex);
+ return count;
+ }
+
+ /* can't purge dirty objects */
+ if (cache->flush(node)) {
+ pthread_mutex_unlock(&node->cn_mutex);
+ return 1;
+ }
+
+ mru = &cache->c_mrus[node->cn_priority];
+ pthread_mutex_lock(&mru->cm_mutex);
+ list_del_init(&node->cn_mru);
+ mru->cm_count--;
+ pthread_mutex_unlock(&mru->cm_mutex);
+
+ pthread_mutex_unlock(&node->cn_mutex);
+ pthread_mutex_destroy(&node->cn_mutex);
+ list_del_init(&node->cn_hash);
+ cache->relse(node);
+ return 0;
+}
+
+/*
+ * Lookup in the cache hash table. With any luck we'll get a cache
+ * hit, in which case this will all be over quickly and painlessly.
+ * Otherwise, we allocate a new node, taking care not to expand the
+ * cache beyond the requested maximum size (shrink it if it would).
+ * Returns one if hit in cache, otherwise zero. A node is _always_
+ * returned, however.
+ */
+int
+cache_node_get(
+ struct cache * cache,
+ cache_key_t key,
+ struct cache_node ** nodep)
+{
+ struct cache_node * node = NULL;
+ struct cache_hash * hash;
+ struct cache_mru * mru;
+ struct list_head * head;
+ struct list_head * pos;
+ struct list_head * n;
+ unsigned int hashidx;
+ int priority = 0;
+ int purged = 0;
+
+ hashidx = cache->hash(key, cache->c_hashsize, cache->c_hashshift);
+ hash = cache->c_hash + hashidx;
+ head = &hash->ch_list;
+
+ for (;;) {
+ pthread_mutex_lock(&hash->ch_mutex);
+ for (pos = head->next, n = pos->next; pos != head;
+ pos = n, n = pos->next) {
+ int result;
+
+ node = list_entry(pos, struct cache_node, cn_hash);
+ result = cache->compare(node, key);
+ switch (result) {
+ case CACHE_HIT:
+ break;
+ case CACHE_PURGE:
+ if ((cache->c_flags & CACHE_MISCOMPARE_PURGE) &&
+ !__cache_node_purge(cache, node)) {
+ purged++;
+ hash->ch_count--;
+ }
+ /* FALL THROUGH */
+ case CACHE_MISS:
+ goto next_object;
+ }
+
+ /*
+ * node found, bump node's reference count, remove it
+ * from its MRU list, and update stats.
+ */
+ pthread_mutex_lock(&node->cn_mutex);
+
+ if (node->cn_count == 0 && cache->get) {
+ int err = cache->get(node);
+ if (err) {
+ pthread_mutex_unlock(&node->cn_mutex);
+ goto next_object;
+ }
+ }
+ if (node->cn_count == 0) {
+ ASSERT(node->cn_priority >= 0);
+ ASSERT(!list_empty(&node->cn_mru));
+ mru = &cache->c_mrus[node->cn_priority];
+ pthread_mutex_lock(&mru->cm_mutex);
+ mru->cm_count--;
+ list_del_init(&node->cn_mru);
+ pthread_mutex_unlock(&mru->cm_mutex);
+ if (node->cn_old_priority != -1) {
+ ASSERT(node->cn_priority ==
+ CACHE_DIRTY_PRIORITY);
+ node->cn_priority = node->cn_old_priority;
+ node->cn_old_priority = -1;
+ }
+ }
+ node->cn_count++;
+
+ pthread_mutex_unlock(&node->cn_mutex);
+ pthread_mutex_unlock(&hash->ch_mutex);
+
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_hits++;
+ pthread_mutex_unlock(&cache->c_mutex);
+
+ *nodep = node;
+ return 0;
+next_object:
+ continue; /* what the hell, gcc? */
+ }
+ pthread_mutex_unlock(&hash->ch_mutex);
+ /*
+ * not found, allocate a new entry
+ */
+ node = cache_node_allocate(cache, key);
+ if (node)
+ break;
+ priority = cache_shake(cache, priority, false);
+ /*
+ * We start at 0; if we free CACHE_SHAKE_COUNT we get
+ * back the same priority, if not we get back priority+1.
+ * If we exceed CACHE_MAX_PRIORITY all slots are full; grow it.
+ */
+ if (priority > CACHE_MAX_PRIORITY) {
+ priority = 0;
+ cache_expand(cache);
+ }
+ }
+
+ node->cn_hashidx = hashidx;
+
+ /* add new node to appropriate hash */
+ pthread_mutex_lock(&hash->ch_mutex);
+ hash->ch_count++;
+ list_add(&node->cn_hash, &hash->ch_list);
+ pthread_mutex_unlock(&hash->ch_mutex);
+
+ if (purged) {
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_count -= purged;
+ pthread_mutex_unlock(&cache->c_mutex);
+ }
+
+ *nodep = node;
+ return 1;
+}
+
+void
+cache_node_put(
+ struct cache * cache,
+ struct cache_node * node)
+{
+ struct cache_mru * mru;
+
+ pthread_mutex_lock(&node->cn_mutex);
+#ifdef CACHE_DEBUG
+ if (node->cn_count < 1) {
+ fprintf(stderr, "%s: node put on refcount %u (node=%p)\n",
+ __FUNCTION__, node->cn_count, node);
+ cache_abort();
+ }
+ if (!list_empty(&node->cn_mru)) {
+ fprintf(stderr, "%s: node put on node (%p) in MRU list\n",
+ __FUNCTION__, node);
+ cache_abort();
+ }
+#endif
+ node->cn_count--;
+
+ if (node->cn_count == 0 && cache->put)
+ cache->put(node);
+ if (node->cn_count == 0) {
+ /* add unreferenced node to appropriate MRU for shaker */
+ mru = &cache->c_mrus[node->cn_priority];
+ pthread_mutex_lock(&mru->cm_mutex);
+ mru->cm_count++;
+ list_add(&node->cn_mru, &mru->cm_list);
+ pthread_mutex_unlock(&mru->cm_mutex);
+ }
+
+ pthread_mutex_unlock(&node->cn_mutex);
+}
+
+void
+cache_node_set_priority(
+ struct cache * cache,
+ struct cache_node * node,
+ int priority)
+{
+ if (priority < 0)
+ priority = 0;
+ else if (priority > CACHE_MAX_PRIORITY)
+ priority = CACHE_MAX_PRIORITY;
+
+ pthread_mutex_lock(&node->cn_mutex);
+ ASSERT(node->cn_count > 0);
+ node->cn_priority = priority;
+ node->cn_old_priority = -1;
+ pthread_mutex_unlock(&node->cn_mutex);
+}
+
+int
+cache_node_get_priority(
+ struct cache_node * node)
+{
+ int priority;
+
+ pthread_mutex_lock(&node->cn_mutex);
+ priority = node->cn_priority;
+ pthread_mutex_unlock(&node->cn_mutex);
+
+ return priority;
+}
+
+
+/*
+ * Purge a specific node from the cache. Reference count must be zero.
+ */
+int
+cache_node_purge(
+ struct cache * cache,
+ cache_key_t key,
+ struct cache_node * node)
+{
+ struct list_head * head;
+ struct list_head * pos;
+ struct list_head * n;
+ struct cache_hash * hash;
+ int count = -1;
+
+ hash = cache->c_hash + cache->hash(key, cache->c_hashsize,
+ cache->c_hashshift);
+ head = &hash->ch_list;
+ pthread_mutex_lock(&hash->ch_mutex);
+ for (pos = head->next, n = pos->next; pos != head;
+ pos = n, n = pos->next) {
+ if ((struct cache_node *)pos != node)
+ continue;
+
+ count = __cache_node_purge(cache, node);
+ if (!count)
+ hash->ch_count--;
+ break;
+ }
+ pthread_mutex_unlock(&hash->ch_mutex);
+
+ if (count == 0) {
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_count--;
+ pthread_mutex_unlock(&cache->c_mutex);
+ }
+#ifdef CACHE_DEBUG
+ if (count >= 1) {
+ fprintf(stderr, "%s: refcount was %u, not zero (node=%p)\n",
+ __FUNCTION__, count, node);
+ cache_abort();
+ }
+ if (count == -1) {
+ fprintf(stderr, "%s: purge node not found! (node=%p)\n",
+ __FUNCTION__, node);
+ cache_abort();
+ }
+#endif
+ return count == 0;
+}
+
+/*
+ * Purge all nodes from the cache. All reference counts must be zero.
+ */
+void
+cache_purge(
+ struct cache * cache)
+{
+ int i;
+
+ for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++)
+ cache_shake(cache, i, true);
+
+#ifdef CACHE_DEBUG
+ if (cache->c_count != 0) {
+ /* flush referenced nodes to disk */
+ cache_flush(cache);
+ fprintf(stderr, "%s: shake on cache %p left %u nodes!?\n",
+ __FUNCTION__, cache, cache->c_count);
+ cache_abort();
+ }
+#endif
+}
+
+/*
+ * Flush all nodes in the cache to disk.
+ */
+void
+cache_flush(
+ struct cache * cache)
+{
+ struct cache_hash * hash;
+ struct list_head * head;
+ struct list_head * pos;
+ struct cache_node * node;
+ int i;
+
+ if (!cache->flush)
+ return;
+
+ for (i = 0; i < cache->c_hashsize; i++) {
+ hash = &cache->c_hash[i];
+
+ pthread_mutex_lock(&hash->ch_mutex);
+ head = &hash->ch_list;
+ for (pos = head->next; pos != head; pos = pos->next) {
+ node = (struct cache_node *)pos;
+ pthread_mutex_lock(&node->cn_mutex);
+ cache->flush(node);
+ pthread_mutex_unlock(&node->cn_mutex);
+ }
+ pthread_mutex_unlock(&hash->ch_mutex);
+ }
+}
+
+#define HASH_REPORT (3 * HASH_CACHE_RATIO)
+void
+cache_report(
+ FILE *fp,
+ const char *name,
+ struct cache *cache)
+{
+ int i;
+ unsigned long count, index, total;
+ unsigned long hash_bucket_lengths[HASH_REPORT + 2];
+
+ if ((cache->c_hits + cache->c_misses) == 0)
+ return;
+
+ /* report cache summary */
+ fprintf(fp, "%s: %p\n"
+ "Max supported entries = %u\n"
+ "Max utilized entries = %u\n"
+ "Active entries = %u\n"
+ "Hash table size = %u\n"
+ "Hits = %llu\n"
+ "Misses = %llu\n"
+ "Hit ratio = %5.2f\n",
+ name, cache,
+ cache->c_maxcount,
+ cache->c_max,
+ cache->c_count,
+ cache->c_hashsize,
+ cache->c_hits,
+ cache->c_misses,
+ (double)cache->c_hits * 100 /
+ (cache->c_hits + cache->c_misses)
+ );
+
+ for (i = 0; i <= CACHE_MAX_PRIORITY; i++)
+ fprintf(fp, "MRU %d entries = %6u (%3u%%)\n",
+ i, cache->c_mrus[i].cm_count,
+ cache->c_mrus[i].cm_count * 100 / cache->c_count);
+
+ i = CACHE_DIRTY_PRIORITY;
+ fprintf(fp, "Dirty MRU %d entries = %6u (%3u%%)\n",
+ i, cache->c_mrus[i].cm_count,
+ cache->c_mrus[i].cm_count * 100 / cache->c_count);
+
+ /* report hash bucket lengths */
+ bzero(hash_bucket_lengths, sizeof(hash_bucket_lengths));
+
+ for (i = 0; i < cache->c_hashsize; i++) {
+ count = cache->c_hash[i].ch_count;
+ if (count > HASH_REPORT)
+ index = HASH_REPORT + 1;
+ else
+ index = count;
+ hash_bucket_lengths[index]++;
+ }
+
+ total = 0;
+ for (i = 0; i < HASH_REPORT + 1; i++) {
+ total += i * hash_bucket_lengths[i];
+ if (hash_bucket_lengths[i] == 0)
+ continue;
+ fprintf(fp, "Hash buckets with %2d entries %6ld (%3ld%%)\n",
+ i, hash_bucket_lengths[i],
+ (i * hash_bucket_lengths[i] * 100) / cache->c_count);
+ }
+ if (hash_bucket_lengths[i]) /* last report bucket is the overflow bucket */
+ fprintf(fp, "Hash buckets with >%2d entries %6ld (%3ld%%)\n",
+ i - 1, hash_bucket_lengths[i],
+ ((cache->c_count - total) * 100) / cache->c_count);
+}
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 08/23] cache: disable debugging
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (6 preceding siblings ...)
2025-11-06 22:44 ` [PATCH 07/23] libsupport: add a cache Darrick J. Wong
@ 2025-11-06 22:45 ` Darrick J. Wong
2025-11-06 22:45 ` [PATCH 09/23] cache: use modern list iterator macros Darrick J. Wong
` (14 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Not sure why debugging is turned on by default in the xfsprogs cache
code, but let's turn it off.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/support/cache.c b/lib/support/cache.c
index fe04f62f262aaa..08e0b484cca298 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -17,9 +17,8 @@
#include "cache.h"
#include "xbitops.h"
-#define CACHE_DEBUG 1
#undef CACHE_DEBUG
-#define CACHE_DEBUG 1
+/* #define CACHE_DEBUG 1 */
#undef CACHE_ABORT
/* #define CACHE_ABORT 1 */
@@ -28,6 +27,8 @@
#ifdef CACHE_DEBUG
# include <assert.h>
# define ASSERT(x) assert(x)
+#else
+# define ASSERT(x) do { } while (0)
#endif
static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 09/23] cache: use modern list iterator macros
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (7 preceding siblings ...)
2025-11-06 22:45 ` [PATCH 08/23] cache: disable debugging Darrick J. Wong
@ 2025-11-06 22:45 ` Darrick J. Wong
2025-11-06 22:45 ` [PATCH 10/23] cache: embed struct cache in the owner Darrick J. Wong
` (13 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Use the list iterator macros from list.h.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.c | 71 +++++++++++++++++----------------------------------
1 file changed, 24 insertions(+), 47 deletions(-)
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 08e0b484cca298..d8f8231ac36d28 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -98,20 +98,18 @@ cache_expand(
void
cache_walk(
- struct cache * cache,
+ struct cache *cache,
cache_walk_t visit)
{
- struct cache_hash * hash;
- struct list_head * head;
- struct list_head * pos;
+ struct cache_hash *hash;
+ struct cache_node *pos;
unsigned int i;
for (i = 0; i < cache->c_hashsize; i++) {
hash = &cache->c_hash[i];
- head = &hash->ch_list;
pthread_mutex_lock(&hash->ch_mutex);
- for (pos = head->next; pos != head; pos = pos->next)
- visit((struct cache_node *)pos);
+ list_for_each_entry(pos, &hash->ch_list, cn_hash)
+ visit(pos);
pthread_mutex_unlock(&hash->ch_mutex);
}
}
@@ -218,12 +216,9 @@ cache_shake(
bool purge)
{
struct cache_mru *mru;
- struct cache_hash * hash;
+ struct cache_hash *hash;
struct list_head temp;
- struct list_head * head;
- struct list_head * pos;
- struct list_head * n;
- struct cache_node * node;
+ struct cache_node *node, *n;
unsigned int count;
ASSERT(priority <= CACHE_DIRTY_PRIORITY);
@@ -233,13 +228,9 @@ cache_shake(
mru = &cache->c_mrus[priority];
count = 0;
list_head_init(&temp);
- head = &mru->cm_list;
pthread_mutex_lock(&mru->cm_mutex);
- for (pos = head->prev, n = pos->prev; pos != head;
- pos = n, n = pos->prev) {
- node = list_entry(pos, struct cache_node, cn_mru);
-
+ list_for_each_entry_safe_reverse(node, n, &mru->cm_list, cn_mru) {
if (pthread_mutex_trylock(&node->cn_mutex) != 0)
continue;
@@ -376,31 +367,25 @@ __cache_node_purge(
*/
int
cache_node_get(
- struct cache * cache,
+ struct cache *cache,
cache_key_t key,
- struct cache_node ** nodep)
+ struct cache_node **nodep)
{
- struct cache_node * node = NULL;
- struct cache_hash * hash;
- struct cache_mru * mru;
- struct list_head * head;
- struct list_head * pos;
- struct list_head * n;
+ struct cache_hash *hash;
+ struct cache_mru *mru;
+ struct cache_node *node = NULL, *n;
unsigned int hashidx;
int priority = 0;
int purged = 0;
hashidx = cache->hash(key, cache->c_hashsize, cache->c_hashshift);
hash = cache->c_hash + hashidx;
- head = &hash->ch_list;
for (;;) {
pthread_mutex_lock(&hash->ch_mutex);
- for (pos = head->next, n = pos->next; pos != head;
- pos = n, n = pos->next) {
+ list_for_each_entry_safe(node, n, &hash->ch_list, cn_hash) {
int result;
- node = list_entry(pos, struct cache_node, cn_hash);
result = cache->compare(node, key);
switch (result) {
case CACHE_HIT:
@@ -568,23 +553,19 @@ cache_node_get_priority(
*/
int
cache_node_purge(
- struct cache * cache,
+ struct cache *cache,
cache_key_t key,
- struct cache_node * node)
+ struct cache_node *node)
{
- struct list_head * head;
- struct list_head * pos;
- struct list_head * n;
- struct cache_hash * hash;
+ struct cache_node *pos, *n;
+ struct cache_hash *hash;
int count = -1;
hash = cache->c_hash + cache->hash(key, cache->c_hashsize,
cache->c_hashshift);
- head = &hash->ch_list;
pthread_mutex_lock(&hash->ch_mutex);
- for (pos = head->next, n = pos->next; pos != head;
- pos = n, n = pos->next) {
- if ((struct cache_node *)pos != node)
+ list_for_each_entry_safe(pos, n, &hash->ch_list, cn_hash) {
+ if (pos != node)
continue;
count = __cache_node_purge(cache, node);
@@ -642,12 +623,10 @@ cache_purge(
*/
void
cache_flush(
- struct cache * cache)
+ struct cache *cache)
{
- struct cache_hash * hash;
- struct list_head * head;
- struct list_head * pos;
- struct cache_node * node;
+ struct cache_hash *hash;
+ struct cache_node *node;
int i;
if (!cache->flush)
@@ -657,9 +636,7 @@ cache_flush(
hash = &cache->c_hash[i];
pthread_mutex_lock(&hash->ch_mutex);
- head = &hash->ch_list;
- for (pos = head->next; pos != head; pos = pos->next) {
- node = (struct cache_node *)pos;
+ list_for_each_entry(node, &hash->ch_list, cn_hash) {
pthread_mutex_lock(&node->cn_mutex);
cache->flush(node);
pthread_mutex_unlock(&node->cn_mutex);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 10/23] cache: embed struct cache in the owner
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (8 preceding siblings ...)
2025-11-06 22:45 ` [PATCH 09/23] cache: use modern list iterator macros Darrick J. Wong
@ 2025-11-06 22:45 ` Darrick J. Wong
2025-11-06 22:45 ` [PATCH 11/23] cache: pass cache pointer to callbacks Darrick J. Wong
` (12 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
It'll be easier to embed a struct cache into the object that owns the
cache rather than passing pointers around. This is the prelude to the
next patch, which will enable cache functions to walk back to the owning
struct.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 10 ++++++++--
lib/support/cache.c | 38 ++++++++++++++++++++------------------
2 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 16b17a9b7a1a51..993f1385dedcee 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -122,8 +122,14 @@ struct cache {
unsigned int c_max; /* max nodes ever used */
};
-struct cache *cache_init(int, unsigned int, const struct cache_operations *);
-void cache_destroy(struct cache *);
+static inline bool cache_initialized(const struct cache *cache)
+{
+ return cache->hash != NULL;
+}
+
+int cache_init(int flags, unsigned int size,
+ const struct cache_operations *ops, struct cache *cache);
+void cache_destroy(struct cache *cache);
void cache_walk(struct cache *, cache_walk_t);
void cache_purge(struct cache *);
void cache_flush(struct cache *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index d8f8231ac36d28..8b4f9f03c3899b 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -12,6 +12,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <errno.h>
#include "list.h"
#include "cache.h"
@@ -33,23 +34,18 @@
static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
-struct cache *
+int
cache_init(
int flags,
unsigned int hashsize,
- const struct cache_operations *cache_operations)
+ const struct cache_operations *cache_operations,
+ struct cache *cache)
{
- struct cache * cache;
unsigned int i, maxcount;
maxcount = hashsize * HASH_CACHE_RATIO;
- if (!(cache = malloc(sizeof(struct cache))))
- return NULL;
- if (!(cache->c_hash = calloc(hashsize, sizeof(struct cache_hash)))) {
- free(cache);
- return NULL;
- }
+ memset(cache, 0, sizeof(*cache));
cache->c_flags = flags;
cache->c_count = 0;
@@ -57,8 +53,6 @@ cache_init(
cache->c_hits = 0;
cache->c_misses = 0;
cache->c_maxcount = maxcount;
- cache->c_hashsize = hashsize;
- cache->c_hashshift = fls(hashsize) - 1;
cache->hash = cache_operations->hash;
cache->alloc = cache_operations->alloc;
cache->flush = cache_operations->flush;
@@ -70,18 +64,26 @@ cache_init(
cache->put = cache_operations->put;
pthread_mutex_init(&cache->c_mutex, NULL);
+ for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+ list_head_init(&cache->c_mrus[i].cm_list);
+ cache->c_mrus[i].cm_count = 0;
+ pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
+ }
+
+ cache->c_hash = calloc(hashsize, sizeof(struct cache_hash));
+ if (!cache->c_hash)
+ return ENOMEM;
+
+ cache->c_hashsize = hashsize;
+ cache->c_hashshift = fls(hashsize) - 1;
+
for (i = 0; i < hashsize; i++) {
list_head_init(&cache->c_hash[i].ch_list);
cache->c_hash[i].ch_count = 0;
pthread_mutex_init(&cache->c_hash[i].ch_mutex, NULL);
}
- for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
- list_head_init(&cache->c_mrus[i].cm_list);
- cache->c_mrus[i].cm_count = 0;
- pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
- }
- return cache;
+ return 0;
}
static void
@@ -153,7 +155,7 @@ cache_destroy(
}
pthread_mutex_destroy(&cache->c_mutex);
free(cache->c_hash);
- free(cache);
+ memset(cache, 0, sizeof(*cache));
}
static unsigned int
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 11/23] cache: pass cache pointer to callbacks
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (9 preceding siblings ...)
2025-11-06 22:45 ` [PATCH 10/23] cache: embed struct cache in the owner Darrick J. Wong
@ 2025-11-06 22:45 ` Darrick J. Wong
2025-11-06 22:46 ` [PATCH 12/23] cache: pass a private data pointer through cache_walk Darrick J. Wong
` (11 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Pass the cache pointer to the cache node callbacks so that subsequent
patches don't have to waste memory putting pointers to struct fuse4fs in
the cached objects.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 12 ++++++------
lib/support/cache.c | 21 +++++++++++----------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 993f1385dedcee..0168fdca027896 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -56,16 +56,16 @@ struct cache_node;
typedef void *cache_key_t;
-typedef void (*cache_walk_t)(struct cache_node *);
-typedef struct cache_node * (*cache_node_alloc_t)(cache_key_t);
-typedef int (*cache_node_flush_t)(struct cache_node *);
-typedef void (*cache_node_relse_t)(struct cache_node *);
+typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn);
+typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
+typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
unsigned int);
typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
-typedef int (*cache_node_get_t)(struct cache_node *);
-typedef void (*cache_node_put_t)(struct cache_node *);
+typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
struct cache_operations {
cache_node_hash_t hash;
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 8b4f9f03c3899b..2e2e36ccc3ef78 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -111,7 +111,7 @@ cache_walk(
hash = &cache->c_hash[i];
pthread_mutex_lock(&hash->ch_mutex);
list_for_each_entry(pos, &hash->ch_list, cn_hash)
- visit(pos);
+ visit(cache, pos);
pthread_mutex_unlock(&hash->ch_mutex);
}
}
@@ -125,7 +125,8 @@ cache_walk(
#ifdef CACHE_DEBUG
static void
cache_zero_check(
- struct cache_node * node)
+ struct cache *cache,
+ struct cache_node *node)
{
if (node->cn_count > 0) {
fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
@@ -170,7 +171,7 @@ cache_generic_bulkrelse(
node = list_entry(list->next, struct cache_node, cn_mru);
pthread_mutex_destroy(&node->cn_mutex);
list_del_init(&node->cn_mru);
- cache->relse(node);
+ cache->relse(cache, node);
count++;
}
@@ -237,7 +238,7 @@ cache_shake(
continue;
/* memory pressure is not allowed to release dirty objects */
- if (cache->flush(node) && !purge) {
+ if (cache->flush(cache, node) && !purge) {
list_del(&node->cn_mru);
mru->cm_count--;
node->cn_priority = -1;
@@ -302,7 +303,7 @@ cache_node_allocate(
pthread_mutex_unlock(&cache->c_mutex);
if (!nodesfree)
return NULL;
- node = cache->alloc(key);
+ node = cache->alloc(cache, key);
if (node == NULL) { /* uh-oh */
pthread_mutex_lock(&cache->c_mutex);
cache->c_count--;
@@ -341,7 +342,7 @@ __cache_node_purge(
}
/* can't purge dirty objects */
- if (cache->flush(node)) {
+ if (cache->flush(cache, node)) {
pthread_mutex_unlock(&node->cn_mutex);
return 1;
}
@@ -355,7 +356,7 @@ __cache_node_purge(
pthread_mutex_unlock(&node->cn_mutex);
pthread_mutex_destroy(&node->cn_mutex);
list_del_init(&node->cn_hash);
- cache->relse(node);
+ cache->relse(cache, node);
return 0;
}
@@ -410,7 +411,7 @@ cache_node_get(
pthread_mutex_lock(&node->cn_mutex);
if (node->cn_count == 0 && cache->get) {
- int err = cache->get(node);
+ int err = cache->get(cache, node);
if (err) {
pthread_mutex_unlock(&node->cn_mutex);
goto next_object;
@@ -505,7 +506,7 @@ cache_node_put(
node->cn_count--;
if (node->cn_count == 0 && cache->put)
- cache->put(node);
+ cache->put(cache, node);
if (node->cn_count == 0) {
/* add unreferenced node to appropriate MRU for shaker */
mru = &cache->c_mrus[node->cn_priority];
@@ -640,7 +641,7 @@ cache_flush(
pthread_mutex_lock(&hash->ch_mutex);
list_for_each_entry(node, &hash->ch_list, cn_hash) {
pthread_mutex_lock(&node->cn_mutex);
- cache->flush(node);
+ cache->flush(cache, node);
pthread_mutex_unlock(&node->cn_mutex);
}
pthread_mutex_unlock(&hash->ch_mutex);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 12/23] cache: pass a private data pointer through cache_walk
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (10 preceding siblings ...)
2025-11-06 22:45 ` [PATCH 11/23] cache: pass cache pointer to callbacks Darrick J. Wong
@ 2025-11-06 22:46 ` Darrick J. Wong
2025-11-06 22:46 ` [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node Darrick J. Wong
` (10 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Allow cache_walk callers to pass a pointer to the callback function.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 4 ++--
lib/support/cache.c | 10 ++++++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 0168fdca027896..b18b6d3325e9ad 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -56,7 +56,7 @@ struct cache_node;
typedef void *cache_key_t;
-typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn, void *d);
typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
@@ -130,7 +130,7 @@ static inline bool cache_initialized(const struct cache *cache)
int cache_init(int flags, unsigned int size,
const struct cache_operations *ops, struct cache *cache);
void cache_destroy(struct cache *cache);
-void cache_walk(struct cache *, cache_walk_t);
+void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
void cache_purge(struct cache *);
void cache_flush(struct cache *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 2e2e36ccc3ef78..606acd5453cf10 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -101,7 +101,8 @@ cache_expand(
void
cache_walk(
struct cache *cache,
- cache_walk_t visit)
+ cache_walk_t visit,
+ void *data)
{
struct cache_hash *hash;
struct cache_node *pos;
@@ -111,7 +112,7 @@ cache_walk(
hash = &cache->c_hash[i];
pthread_mutex_lock(&hash->ch_mutex);
list_for_each_entry(pos, &hash->ch_list, cn_hash)
- visit(cache, pos);
+ visit(cache, pos, data);
pthread_mutex_unlock(&hash->ch_mutex);
}
}
@@ -126,7 +127,8 @@ cache_walk(
static void
cache_zero_check(
struct cache *cache,
- struct cache_node *node)
+ struct cache_node *node,
+ void *data)
{
if (node->cn_count > 0) {
fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
@@ -134,7 +136,7 @@ cache_zero_check(
cache_abort();
}
}
-#define cache_destroy_check(c) cache_walk((c), cache_zero_check)
+#define cache_destroy_check(c) cache_walk((c), cache_zero_check, NULL)
#else
#define cache_destroy_check(c) do { } while (0)
#endif
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (11 preceding siblings ...)
2025-11-06 22:46 ` [PATCH 12/23] cache: pass a private data pointer through cache_walk Darrick J. Wong
@ 2025-11-06 22:46 ` Darrick J. Wong
2025-11-06 22:46 ` [PATCH 14/23] cache: return results of a cache flush Darrick J. Wong
` (9 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Create a helper to bump the refcount of a cache node.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 1 +
lib/support/cache.c | 57 +++++++++++++++++++++++++++++----------------------
2 files changed, 33 insertions(+), 25 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index b18b6d3325e9ad..e8f1c82ef7869c 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -141,5 +141,6 @@ int cache_node_get_priority(struct cache_node *);
int cache_node_purge(struct cache *, cache_key_t, struct cache_node *);
void cache_report(FILE *fp, const char *, struct cache *);
int cache_overflowed(struct cache *);
+struct cache_node *cache_node_grab(struct cache *cache, struct cache_node *node);
#endif /* __CACHE_H__ */
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 606acd5453cf10..49568ffa6de2e4 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -362,6 +362,35 @@ __cache_node_purge(
return 0;
}
+/* Grab a new refcount to the cache node object. Caller must hold cn_mutex. */
+struct cache_node *cache_node_grab(struct cache *cache, struct cache_node *node)
+{
+ struct cache_mru *mru;
+
+ if (node->cn_count == 0 && cache->get) {
+ int err = cache->get(cache, node);
+ if (err)
+ return NULL;
+ }
+ if (node->cn_count == 0) {
+ ASSERT(node->cn_priority >= 0);
+ ASSERT(!list_empty(&node->cn_mru));
+ mru = &cache->c_mrus[node->cn_priority];
+ pthread_mutex_lock(&mru->cm_mutex);
+ mru->cm_count--;
+ list_del_init(&node->cn_mru);
+ pthread_mutex_unlock(&mru->cm_mutex);
+ if (node->cn_old_priority != -1) {
+ ASSERT(node->cn_priority ==
+ CACHE_DIRTY_PRIORITY);
+ node->cn_priority = node->cn_old_priority;
+ node->cn_old_priority = -1;
+ }
+ }
+ node->cn_count++;
+ return node;
+}
+
/*
* Lookup in the cache hash table. With any luck we'll get a cache
* hit, in which case this will all be over quickly and painlessly.
@@ -377,7 +406,6 @@ cache_node_get(
struct cache_node **nodep)
{
struct cache_hash *hash;
- struct cache_mru *mru;
struct cache_node *node = NULL, *n;
unsigned int hashidx;
int priority = 0;
@@ -411,31 +439,10 @@ cache_node_get(
* from its MRU list, and update stats.
*/
pthread_mutex_lock(&node->cn_mutex);
-
- if (node->cn_count == 0 && cache->get) {
- int err = cache->get(cache, node);
- if (err) {
- pthread_mutex_unlock(&node->cn_mutex);
- goto next_object;
- }
+ if (!cache_node_grab(cache, node)) {
+ pthread_mutex_unlock(&node->cn_mutex);
+ goto next_object;
}
- if (node->cn_count == 0) {
- ASSERT(node->cn_priority >= 0);
- ASSERT(!list_empty(&node->cn_mru));
- mru = &cache->c_mrus[node->cn_priority];
- pthread_mutex_lock(&mru->cm_mutex);
- mru->cm_count--;
- list_del_init(&node->cn_mru);
- pthread_mutex_unlock(&mru->cm_mutex);
- if (node->cn_old_priority != -1) {
- ASSERT(node->cn_priority ==
- CACHE_DIRTY_PRIORITY);
- node->cn_priority = node->cn_old_priority;
- node->cn_old_priority = -1;
- }
- }
- node->cn_count++;
-
pthread_mutex_unlock(&node->cn_mutex);
pthread_mutex_unlock(&hash->ch_mutex);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 14/23] cache: return results of a cache flush
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (12 preceding siblings ...)
2025-11-06 22:46 ` [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node Darrick J. Wong
@ 2025-11-06 22:46 ` Darrick J. Wong
2025-11-06 22:47 ` [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get Darrick J. Wong
` (8 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Modify cache_flush to return whether or not there were errors whilst
flushing the cache.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 4 ++--
lib/support/cache.c | 11 +++++++----
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index e8f1c82ef7869c..8d39ca5c02a285 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -58,7 +58,7 @@ typedef void *cache_key_t;
typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn, void *d);
typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
-typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
+typedef bool (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
unsigned int);
@@ -132,7 +132,7 @@ int cache_init(int flags, unsigned int size,
void cache_destroy(struct cache *cache);
void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
void cache_purge(struct cache *);
-void cache_flush(struct cache *);
+bool cache_flush(struct cache *cache);
int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
void cache_node_put(struct cache *, struct cache_node *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 49568ffa6de2e4..fa07b4ad8222d2 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -631,18 +631,19 @@ cache_purge(
}
/*
- * Flush all nodes in the cache to disk.
+ * Flush all nodes in the cache to disk. Returns true if the flush succeeded.
*/
-void
+bool
cache_flush(
struct cache *cache)
{
struct cache_hash *hash;
struct cache_node *node;
int i;
+ bool still_dirty = false;
if (!cache->flush)
- return;
+ return true;
for (i = 0; i < cache->c_hashsize; i++) {
hash = &cache->c_hash[i];
@@ -650,11 +651,13 @@ cache_flush(
pthread_mutex_lock(&hash->ch_mutex);
list_for_each_entry(node, &hash->ch_list, cn_hash) {
pthread_mutex_lock(&node->cn_mutex);
- cache->flush(cache, node);
+ still_dirty |= cache->flush(cache, node);
pthread_mutex_unlock(&node->cn_mutex);
}
pthread_mutex_unlock(&hash->ch_mutex);
}
+
+ return !still_dirty;
}
#define HASH_REPORT (3 * HASH_CACHE_RATIO)
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (13 preceding siblings ...)
2025-11-06 22:46 ` [PATCH 14/23] cache: return results of a cache flush Darrick J. Wong
@ 2025-11-06 22:47 ` Darrick J. Wong
2025-11-06 22:47 ` [PATCH 16/23] cache: support gradual expansion Darrick J. Wong
` (7 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Add a new flag to cache_node_get so that callers can specify that they
only want the cache to return an existing cache node, and not create a
new one.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 5 ++++-
lib/support/cache.c | 7 +++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 8d39ca5c02a285..98b2182d49a6e0 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -134,7 +134,10 @@ void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
void cache_purge(struct cache *);
bool cache_flush(struct cache *cache);
-int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
+/* don't allocate a new node */
+#define CACHE_GET_INCORE (1U << 0)
+int cache_node_get(struct cache *c, cache_key_t key, unsigned int cgflags,
+ struct cache_node **nodep);
void cache_node_put(struct cache *, struct cache_node *);
void cache_node_set_priority(struct cache *, struct cache_node *, int);
int cache_node_get_priority(struct cache_node *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index fa07b4ad8222d2..9da6c59b3b6391 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -403,6 +403,7 @@ int
cache_node_get(
struct cache *cache,
cache_key_t key,
+ unsigned int cgflags,
struct cache_node **nodep)
{
struct cache_hash *hash;
@@ -456,6 +457,12 @@ cache_node_get(
continue; /* what the hell, gcc? */
}
pthread_mutex_unlock(&hash->ch_mutex);
+
+ if (cgflags & CACHE_GET_INCORE) {
+ *nodep = NULL;
+ return 0;
+ }
+
/*
* not found, allocate a new entry
*/
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 16/23] cache: support gradual expansion
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (14 preceding siblings ...)
2025-11-06 22:47 ` [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get Darrick J. Wong
@ 2025-11-06 22:47 ` Darrick J. Wong
2025-11-06 22:47 ` [PATCH 17/23] cache: support updating maxcount and flags Darrick J. Wong
` (6 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
It's probably not a good idea to expand the cache size by powers of two
beyond some random limit, so let the users figure that out if they want
to.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 10 ++++++++++
lib/support/cache.c | 12 ++++++++++--
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 98b2182d49a6e0..ae37945c545f46 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -66,6 +66,14 @@ typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
+typedef unsigned int (*cache_node_resize_t)(const struct cache *c,
+ unsigned int curr_size);
+
+static inline unsigned int cache_gradual_resize(const struct cache *cache,
+ unsigned int curr_size)
+{
+ return curr_size * 5 / 4;
+}
struct cache_operations {
cache_node_hash_t hash;
@@ -76,6 +84,7 @@ struct cache_operations {
cache_bulk_relse_t bulkrelse; /* optional */
cache_node_get_t get; /* optional */
cache_node_put_t put; /* optional */
+ cache_node_resize_t resize; /* optional */
};
struct cache_hash {
@@ -113,6 +122,7 @@ struct cache {
cache_bulk_relse_t bulkrelse; /* bulk release routine */
cache_node_get_t get; /* prepare cache node after get */
cache_node_put_t put; /* prepare to put cache node */
+ cache_node_resize_t resize; /* compute new maxcount */
unsigned int c_hashsize; /* hash bucket count */
unsigned int c_hashshift; /* hash key shift */
struct cache_hash *c_hash; /* hash table buckets */
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 9da6c59b3b6391..dbaddc1bd36d3d 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -62,6 +62,7 @@ cache_init(
cache_operations->bulkrelse : cache_generic_bulkrelse;
cache->get = cache_operations->get;
cache->put = cache_operations->put;
+ cache->resize = cache_operations->resize;
pthread_mutex_init(&cache->c_mutex, NULL);
for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
@@ -90,11 +91,18 @@ static void
cache_expand(
struct cache * cache)
{
+ unsigned int new_size = 0;
+
pthread_mutex_lock(&cache->c_mutex);
+ if (cache->resize)
+ new_size = cache->resize(cache, cache->c_maxcount);
+ if (new_size <= cache->c_maxcount)
+ new_size = cache->c_maxcount * 2;
#ifdef CACHE_DEBUG
- fprintf(stderr, "doubling cache size to %d\n", 2 * cache->c_maxcount);
+ fprintf(stderr, "increasing cache max size from %u to %u\n",
+ cache->c_maxcount, new_size);
#endif
- cache->c_maxcount *= 2;
+ cache->c_maxcount = new_size;
pthread_mutex_unlock(&cache->c_mutex);
}
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 17/23] cache: support updating maxcount and flags
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (15 preceding siblings ...)
2025-11-06 22:47 ` [PATCH 16/23] cache: support gradual expansion Darrick J. Wong
@ 2025-11-06 22:47 ` Darrick J. Wong
2025-11-06 22:47 ` [PATCH 18/23] cache: support channging flags Darrick J. Wong
` (5 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
A future patchset will create a new io cache manager that uses the
hashtable in cache.c It's desirable for fuse4fs to be able to control
the maximum number of buffers in the IO cache, so add a simple API so
that cache users can set a new max item count.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 2 ++
lib/support/cache.c | 10 ++++++++++
2 files changed, 12 insertions(+)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index ae37945c545f46..75e61c92a1fd35 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -144,6 +144,8 @@ void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
void cache_purge(struct cache *);
bool cache_flush(struct cache *cache);
+void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
+
/* don't allocate a new node */
#define CACHE_GET_INCORE (1U << 0)
int cache_node_get(struct cache *c, cache_key_t key, unsigned int cgflags,
diff --git a/lib/support/cache.c b/lib/support/cache.c
index dbaddc1bd36d3d..e2f9e722eb2ef1 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -87,6 +87,16 @@ cache_init(
return 0;
}
+void
+cache_set_maxcount(
+ struct cache *cache,
+ unsigned int maxcount)
+{
+ pthread_mutex_lock(&cache->c_mutex);
+ cache->c_maxcount = maxcount;
+ pthread_mutex_unlock(&cache->c_mutex);
+}
+
static void
cache_expand(
struct cache * cache)
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 18/23] cache: support channging flags
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (16 preceding siblings ...)
2025-11-06 22:47 ` [PATCH 17/23] cache: support updating maxcount and flags Darrick J. Wong
@ 2025-11-06 22:47 ` Darrick J. Wong
2025-11-06 22:48 ` [PATCH 19/23] cache: implement automatic shrinking Darrick J. Wong
` (4 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Make it so that we can change the flags in use by a given cache.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 4 ++++
lib/support/cache.c | 18 ++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 75e61c92a1fd35..32b99b5fe733e3 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -16,6 +16,8 @@
*/
#define CACHE_MISCOMPARE_PURGE (1 << 0)
+#define CACHE_FLAGS_ALL (CACHE_MISCOMPARE_PURGE)
+
/*
* cache object campare return values
*/
@@ -145,6 +147,8 @@ void cache_purge(struct cache *);
bool cache_flush(struct cache *cache);
void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
+int cache_set_flag(struct cache *cache, int flags);
+int cache_clear_flag(struct cache *cache, int flags);
/* don't allocate a new node */
#define CACHE_GET_INCORE (1U << 0)
diff --git a/lib/support/cache.c b/lib/support/cache.c
index e2f9e722eb2ef1..99044248b85d38 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -97,6 +97,24 @@ cache_set_maxcount(
pthread_mutex_unlock(&cache->c_mutex);
}
+int
+cache_set_flag(
+ struct cache *cache,
+ int flags)
+{
+ cache->c_flags |= (flags & CACHE_FLAGS_ALL);
+ return 0;
+}
+
+int
+cache_clear_flag(
+ struct cache *cache,
+ int flags)
+{
+ cache->c_flags &= ~flags;
+ return 0;
+}
+
static void
cache_expand(
struct cache * cache)
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 19/23] cache: implement automatic shrinking
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (17 preceding siblings ...)
2025-11-06 22:47 ` [PATCH 18/23] cache: support channging flags Darrick J. Wong
@ 2025-11-06 22:48 ` Darrick J. Wong
2025-11-06 22:48 ` [PATCH 20/23] fuse4fs: add cache to track open files Darrick J. Wong
` (3 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Shrink the cache whenever maxcount has been expanded beyond its initial
value, we release a cached object to one of the mru lists and the number
of objects sitting on the mru is enough to drop the cache count down a
level. This enables a cache to reduce its memory consumption after a
spike in which reclamation wasn't possible.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 20 +++++++--
lib/support/cache.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 129 insertions(+), 10 deletions(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index 32b99b5fe733e3..c7c8298c115d50 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -16,7 +16,11 @@
*/
#define CACHE_MISCOMPARE_PURGE (1 << 0)
-#define CACHE_FLAGS_ALL (CACHE_MISCOMPARE_PURGE)
+/* Automatically shrink the cache's max_count when possible. */
+#define CACHE_AUTO_SHRINK (1 << 1)
+
+#define CACHE_FLAGS_ALL (CACHE_MISCOMPARE_PURGE | \
+ CACHE_AUTO_SHRINK)
/*
* cache object campare return values
@@ -69,12 +73,18 @@ typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
typedef unsigned int (*cache_node_resize_t)(const struct cache *c,
- unsigned int curr_size);
+ unsigned int curr_size,
+ int dir);
static inline unsigned int cache_gradual_resize(const struct cache *cache,
- unsigned int curr_size)
+ unsigned int curr_size,
+ int dir)
{
- return curr_size * 5 / 4;
+ if (dir < 0)
+ return curr_size * 9 / 10;
+ else if (dir > 0)
+ return curr_size * 5 / 4;
+ return curr_size;
}
struct cache_operations {
@@ -113,6 +123,7 @@ struct cache_node {
struct cache {
int c_flags; /* behavioural flags */
+ unsigned int c_orig_max; /* original max cache nodes */
unsigned int c_maxcount; /* max cache nodes */
unsigned int c_count; /* count of nodes */
pthread_mutex_t c_mutex; /* node count mutex */
@@ -145,6 +156,7 @@ void cache_destroy(struct cache *cache);
void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
void cache_purge(struct cache *);
bool cache_flush(struct cache *cache);
+void cache_shrink(struct cache *cache);
void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
int cache_set_flag(struct cache *cache, int flags);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 99044248b85d38..3a9e276f11af72 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -53,6 +53,7 @@ cache_init(
cache->c_hits = 0;
cache->c_misses = 0;
cache->c_maxcount = maxcount;
+ cache->c_orig_max = maxcount;
cache->hash = cache_operations->hash;
cache->alloc = cache_operations->alloc;
cache->flush = cache_operations->flush;
@@ -93,6 +94,7 @@ cache_set_maxcount(
unsigned int maxcount)
{
pthread_mutex_lock(&cache->c_mutex);
+ cache->c_orig_max = maxcount;
cache->c_maxcount = maxcount;
pthread_mutex_unlock(&cache->c_mutex);
}
@@ -123,7 +125,7 @@ cache_expand(
pthread_mutex_lock(&cache->c_mutex);
if (cache->resize)
- new_size = cache->resize(cache, cache->c_maxcount);
+ new_size = cache->resize(cache, cache->c_maxcount, 1);
if (new_size <= cache->c_maxcount)
new_size = cache->c_maxcount * 2;
#ifdef CACHE_DEBUG
@@ -254,7 +256,8 @@ static unsigned int
cache_shake(
struct cache * cache,
unsigned int priority,
- bool purge)
+ bool purge,
+ unsigned int nr_to_shake)
{
struct cache_mru *mru;
struct cache_hash *hash;
@@ -302,7 +305,7 @@ cache_shake(
pthread_mutex_unlock(&node->cn_mutex);
count++;
- if (!purge && count == CACHE_SHAKE_COUNT)
+ if (!purge && count == nr_to_shake)
break;
}
pthread_mutex_unlock(&mru->cm_mutex);
@@ -315,7 +318,7 @@ cache_shake(
pthread_mutex_unlock(&cache->c_mutex);
}
- return (count == CACHE_SHAKE_COUNT) ? priority : ++priority;
+ return (count == nr_to_shake) ? priority : ++priority;
}
/*
@@ -505,7 +508,7 @@ cache_node_get(
node = cache_node_allocate(cache, key);
if (node)
break;
- priority = cache_shake(cache, priority, false);
+ priority = cache_shake(cache, priority, false, CACHE_SHAKE_COUNT);
/*
* We start at 0; if we free CACHE_SHAKE_COUNT we get
* back the same priority, if not we get back priority+1.
@@ -535,12 +538,112 @@ cache_node_get(
return 1;
}
+static unsigned int cache_mru_count(const struct cache *cache)
+{
+ const struct cache_mru *mru = cache->c_mrus;
+ unsigned int mru_count = 0;
+ unsigned int i;
+
+ for (i = 0; i < CACHE_NR_PRIORITIES; i++, mru++)
+ mru_count += mru->cm_count;
+
+ return mru_count;
+}
+
+
+void cache_shrink(struct cache *cache)
+{
+ unsigned int mru_count = 0;
+ unsigned int threshold = 0;
+ unsigned int priority = 0;
+ unsigned int new_size;
+
+ pthread_mutex_lock(&cache->c_mutex);
+ /* Don't shrink below the original cache size */
+ if (cache->c_maxcount <= cache->c_orig_max)
+ goto out_unlock;
+
+ mru_count = cache_mru_count(cache);
+
+ /*
+ * If there's not even a batch of nodes on the MRU to try to free,
+ * don't bother with the rest.
+ */
+ if (mru_count < CACHE_SHAKE_COUNT)
+ goto out_unlock;
+
+ /*
+ * Figure out the next step down in size, but don't go below the
+ * original size.
+ */
+ if (cache->resize)
+ new_size = cache->resize(cache, cache->c_maxcount, -1);
+ else
+ new_size = cache->c_maxcount / 2;
+ if (new_size >= cache->c_maxcount)
+ goto out_unlock;
+ if (new_size < cache->c_orig_max)
+ new_size = cache->c_orig_max;
+
+ /*
+ * If we can't purge enough nodes to get the node count below new_size,
+ * don't resize the cache.
+ */
+ if (cache->c_count - mru_count >= new_size)
+ goto out_unlock;
+
+#ifdef CACHE_DEBUG
+ fprintf(stderr, "decreasing cache max size from %u to %u (currently %u)\n",
+ cache->c_maxcount, new_size, cache->c_count);
+#endif
+ cache->c_maxcount = new_size;
+
+ /* Try to reduce the number of cached objects. */
+ do {
+ unsigned int new_priority;
+
+ /*
+ * The threshold is the amount we need to purge to get c_count
+ * below the new maxcount. Try to free some objects off the
+ * MRU. Drop c_mutex because cache_shake will take it.
+ */
+ threshold = cache->c_count - new_size;
+ pthread_mutex_unlock(&cache->c_mutex);
+
+ new_priority = cache_shake(cache, priority, false, threshold);
+
+ /* Either we made no progress or we ran out of MRU levels */
+ if (new_priority == priority ||
+ new_priority > CACHE_MAX_PRIORITY)
+ return;
+ priority = new_priority;
+
+ pthread_mutex_lock(&cache->c_mutex);
+ /*
+ * Someone could have walked in and changed the cache maxsize
+ * again while we had the lock dropped. If that happened, stop
+ * clearing.
+ */
+ if (cache->c_maxcount != new_size)
+ goto out_unlock;
+
+ mru_count = cache_mru_count(cache);
+ if (cache->c_count - mru_count >= new_size)
+ goto out_unlock;
+ } while (1);
+
+out_unlock:
+ pthread_mutex_unlock(&cache->c_mutex);
+ return;
+}
+
void
cache_node_put(
struct cache * cache,
struct cache_node * node)
{
struct cache_mru * mru;
+ bool was_put = false;
pthread_mutex_lock(&node->cn_mutex);
#ifdef CACHE_DEBUG
@@ -556,6 +659,7 @@ cache_node_put(
}
#endif
node->cn_count--;
+ was_put = (node->cn_count == 0);
if (node->cn_count == 0 && cache->put)
cache->put(cache, node);
@@ -569,6 +673,9 @@ cache_node_put(
}
pthread_mutex_unlock(&node->cn_mutex);
+
+ if (was_put && (cache->c_flags & CACHE_AUTO_SHRINK))
+ cache_shrink(cache);
}
void
@@ -660,7 +767,7 @@ cache_purge(
int i;
for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++)
- cache_shake(cache, i, true);
+ cache_shake(cache, i, true, CACHE_SHAKE_COUNT);
#ifdef CACHE_DEBUG
if (cache->c_count != 0) {
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 20/23] fuse4fs: add cache to track open files
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (18 preceding siblings ...)
2025-11-06 22:48 ` [PATCH 19/23] cache: implement automatic shrinking Darrick J. Wong
@ 2025-11-06 22:48 ` Darrick J. Wong
2025-11-06 22:48 ` [PATCH 21/23] fuse4fs: use the orphaned inode list Darrick J. Wong
` (2 subsequent siblings)
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Add our own inode cache so that we can track open files.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/cache.h | 7 +++
fuse4fs/Makefile.in | 3 +
fuse4fs/fuse4fs.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 141 insertions(+), 1 deletion(-)
diff --git a/lib/support/cache.h b/lib/support/cache.h
index c7c8298c115d50..71fb9762f97866 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -6,6 +6,13 @@
#ifndef __CACHE_H__
#define __CACHE_H__
+/* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
+#define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL
+#ifndef CACHE_LINE_SIZE
+/* if the system didn't tell us, guess something reasonable */
+#define CACHE_LINE_SIZE 64
+#endif
+
/*
* initialisation flags
*/
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
index 6b41d1dd5ffe8d..9f3547c271638f 100644
--- a/fuse4fs/Makefile.in
+++ b/fuse4fs/Makefile.in
@@ -146,7 +146,8 @@ fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
$(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/version.h \
- $(top_srcdir)/lib/e2p/e2p.h
+ $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/cache.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/support/xbitops.h
journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/../debugfs/journal.h \
$(top_srcdir)/e2fsck/jfs_user.h $(top_srcdir)/e2fsck/e2fsck.h \
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 7585f1ff346d84..038d6126dbfde1 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -26,6 +26,7 @@
#include <sys/ioctl.h>
#include <unistd.h>
#include <ctype.h>
+#include <assert.h>
#define FUSE_DARWIN_ENABLE_EXTENSIONS 0
#ifdef __SET_FOB_FOR_FUSE
# error Do not set magic value __SET_FOB_FOR_FUSE!!!!
@@ -49,6 +50,8 @@
#include "ext2fs/ext2_fs.h"
#include "ext2fs/ext2fsP.h"
#include "support/bthread.h"
+#include "support/list.h"
+#include "support/cache.h"
#include "../version.h"
#include "uuid/uuid.h"
@@ -206,6 +209,7 @@ int journal_enable_debug = -1;
#define FUSE4FS_FILE_MAGIC (0xEF53DEAFUL)
struct fuse4fs_file_handle {
unsigned long magic;
+ struct fuse4fs_inode *fi;
ext2_ino_t ino;
int open_flags;
int check_flags;
@@ -261,6 +265,7 @@ struct fuse4fs {
int timing;
#endif
struct fuse_session *fuse;
+ struct cache inodes;
};
#define FUSE4FS_CHECK_HANDLE(req, fh) \
@@ -353,6 +358,115 @@ static inline int u_log2(unsigned int arg)
return l;
}
+struct fuse4fs_inode {
+ struct cache_node i_cnode;
+ ext2_ino_t i_ino;
+ unsigned int i_open_count;
+};
+
+struct fuse4fs_ikey {
+ ext2_ino_t i_ino;
+};
+
+#define ICKEY(key) ((struct fuse4fs_ikey *)(key))
+#define ICNODE(node) (container_of((node), struct fuse4fs_inode, i_cnode))
+
+static unsigned int
+icache_hash(cache_key_t key, unsigned int hashsize, unsigned int hashshift)
+{
+ uint64_t hashval = ICKEY(key)->i_ino;
+ uint64_t tmp;
+
+ tmp = hashval ^ (GOLDEN_RATIO_PRIME + hashval) / CACHE_LINE_SIZE;
+ tmp = tmp ^ ((tmp ^ GOLDEN_RATIO_PRIME) >> hashshift);
+ return tmp % hashsize;
+}
+
+static int icache_compare(struct cache_node *node, cache_key_t key)
+{
+ struct fuse4fs_inode *fi = ICNODE(node);
+ struct fuse4fs_ikey *ikey = ICKEY(key);
+
+ if (fi->i_ino == ikey->i_ino)
+ return CACHE_HIT;
+
+ return CACHE_MISS;
+}
+
+static struct cache_node *icache_alloc(struct cache *c, cache_key_t key)
+{
+ struct fuse4fs_ikey *ikey = ICKEY(key);
+ struct fuse4fs_inode *fi;
+
+ fi = calloc(1, sizeof(struct fuse4fs_inode));
+ if (!fi)
+ return NULL;
+
+ fi->i_ino = ikey->i_ino;
+ return &fi->i_cnode;
+}
+
+static bool icache_flush(struct cache *c, struct cache_node *node)
+{
+ return false;
+}
+
+static void icache_relse(struct cache *c, struct cache_node *node)
+{
+ struct fuse4fs_inode *fi = ICNODE(node);
+
+ assert(fi->i_open_count == 0);
+ free(fi);
+}
+
+static unsigned int icache_bulkrelse(struct cache *cache,
+ struct list_head *list)
+{
+ struct cache_node *cn, *n;
+ int count = 0;
+
+ if (list_empty(list))
+ return 0;
+
+ list_for_each_entry_safe(cn, n, list, cn_mru) {
+ icache_relse(cache, cn);
+ count++;
+ }
+
+ return count;
+}
+
+static const struct cache_operations icache_ops = {
+ .hash = icache_hash,
+ .alloc = icache_alloc,
+ .flush = icache_flush,
+ .relse = icache_relse,
+ .compare = icache_compare,
+ .bulkrelse = icache_bulkrelse,
+ .resize = cache_gradual_resize,
+};
+
+static errcode_t fuse4fs_iget(struct fuse4fs *ff, ext2_ino_t ino,
+ struct fuse4fs_inode **fip)
+{
+ struct fuse4fs_ikey ikey = {
+ .i_ino = ino,
+ };
+ struct cache_node *node = NULL;
+
+ cache_node_get(&ff->inodes, &ikey, 0, &node);
+ if (!node)
+ return ENOMEM;
+
+ *fip = ICNODE(node);
+ return 0;
+}
+
+static void fuse4fs_iput(struct fuse4fs *ff, struct fuse4fs_inode *fi)
+{
+ cache_node_put(&ff->inodes, &fi->i_cnode);
+}
+
static inline blk64_t FUSE4FS_B_TO_FSBT(const struct fuse4fs *ff, off_t pos)
{
return pos >> ff->blocklog;
@@ -1137,6 +1251,11 @@ static void fuse4fs_unmount(struct fuse4fs *ff)
errcode_t err;
if (ff->fs) {
+ if (cache_initialized(&ff->inodes)) {
+ cache_purge(&ff->inodes);
+ cache_destroy(&ff->inodes);
+ }
+
uuid_unparse(ff->fs->super->s_uuid, uuid);
err = ext2fs_close_free(&ff->fs);
if (err)
@@ -1268,6 +1387,10 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff)
log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
}
+ err = cache_init(CACHE_AUTO_SHRINK, 1U << 10, &icache_ops, &ff->inodes);
+ if (err)
+ return translate_error(ff->fs, 0, err);
+
ff->fs->priv_data = ff;
ff->blocklog = u_log2(ff->fs->blocksize);
ff->blockmask = ff->fs->blocksize - 1;
@@ -2326,6 +2449,7 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
if (inode.i_links_count)
goto write_out;
+
if (ext2fs_has_feature_ea_inode(fs->super)) {
ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
if (ret)
@@ -3282,6 +3406,13 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
goto out;
}
+ err = fuse4fs_iget(ff, file->ino, &file->fi);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ file->fi->i_open_count++;
+
file->check_flags = check;
fuse4fs_set_handle(fp, file);
@@ -3470,6 +3601,7 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
ret = translate_error(fs, fh->ino, err);
}
+ fuse4fs_iput(ff, fh->fi);
fp->fh = 0;
fuse4fs_finish(ff, ret);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 21/23] fuse4fs: use the orphaned inode list
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (19 preceding siblings ...)
2025-11-06 22:48 ` [PATCH 20/23] fuse4fs: add cache to track open files Darrick J. Wong
@ 2025-11-06 22:48 ` Darrick J. Wong
2025-11-06 22:48 ` [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE Darrick J. Wong
2025-11-06 22:49 ` [PATCH 23/23] fuse4fs: create incore reverse orphan list Darrick J. Wong
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Put open but unlinked files on the orphan list, and remove them when the
last open fd releases the inode.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fuse4fs/fuse4fs.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 178 insertions(+), 5 deletions(-)
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 038d6126dbfde1..fdeef157e732d4 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -1114,6 +1114,13 @@ static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
inode_uid(inode), inode_gid(inode),
ctxt->uid, ctxt->gid);
+ /* linked files cannot be on the unlinked list or deleted */
+ if (inode.i_dtime != 0) {
+ dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
+ __func__, ino, inode.i_dtime);
+ return -ENOENT;
+ }
+
/* existence check */
if (mask == 0)
return 0;
@@ -2404,9 +2411,80 @@ static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
return 0;
}
+static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ ext2_filsys fs = ff->fs;
+
+ dbg_printf(ff, "%s: orphan ino=%d dtime=%d next=%d\n",
+ __func__, ino, inode->i_dtime, fs->super->s_last_orphan);
+
+ inode->i_dtime = fs->super->s_last_orphan;
+ fs->super->s_last_orphan = ino;
+ ext2fs_mark_super_dirty(fs);
+
+ return 0;
+}
+
+static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t prev_orphan;
+ errcode_t err;
+
+ dbg_printf(ff, "%s: super=%d ino=%d next=%d\n",
+ __func__, fs->super->s_last_orphan, ino, inode->i_dtime);
+
+ /* If we're lucky, the ondisk superblock points to us */
+ if (fs->super->s_last_orphan == ino) {
+ dbg_printf(ff, "%s: superblock\n", __func__);
+
+ fs->super->s_last_orphan = inode->i_dtime;
+ inode->i_dtime = 0;
+ ext2fs_mark_super_dirty(fs);
+ return 0;
+ }
+
+ /* Otherwise walk the ondisk orphan list. */
+ prev_orphan = fs->super->s_last_orphan;
+ while (prev_orphan != 0) {
+ struct ext2_inode_large orphan;
+
+ err = fuse4fs_read_inode(fs, prev_orphan, &orphan);
+ if (err)
+ return translate_error(fs, prev_orphan, err);
+
+ if (orphan.i_dtime == prev_orphan)
+ return translate_error(fs, prev_orphan,
+ EXT2_ET_FILESYSTEM_CORRUPTED);
+
+ if (orphan.i_dtime == ino) {
+ dbg_printf(ff, "%s: prev=%d\n",
+ __func__, prev_orphan);
+
+ orphan.i_dtime = inode->i_dtime;
+ inode->i_dtime = 0;
+
+ err = fuse4fs_write_inode(fs, prev_orphan, &orphan);
+ if (err)
+ return translate_error(fs, prev_orphan, err);
+
+ return 0;
+ }
+
+ dbg_printf(ff, "%s: orphan=%d next=%d\n",
+ __func__, prev_orphan, orphan.i_dtime);
+ prev_orphan = orphan.i_dtime;
+ }
+
+ return translate_error(fs, ino, EXT2_ET_FILESYSTEM_CORRUPTED);
+}
+
static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
{
ext2_filsys fs = ff->fs;
+ struct fuse4fs_inode *fi;
errcode_t err;
struct ext2_inode_large inode;
int ret = 0;
@@ -2428,7 +2506,6 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
inode.i_links_count = 0;
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
} else {
/*
* Any other file type can be hardlinked, so all we need to do
@@ -2437,8 +2514,6 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
if (inode.i_links_count == 0)
return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
inode.i_links_count--;
- if (!inode.i_links_count)
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
}
ret = update_ctime(fs, ino, &inode);
@@ -2449,6 +2524,26 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
if (inode.i_links_count)
goto write_out;
+ err = fuse4fs_iget(ff, ino, &fi);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ dbg_printf(ff, "%s: put ino=%d opencount=%d\n", __func__, ino,
+ fi->i_open_count);
+
+ /*
+ * The file is unlinked but still open; add it to the orphan list and
+ * free it later.
+ */
+ if (fi->i_open_count > 0) {
+ fuse4fs_iput(ff, fi);
+ ret = fuse4fs_add_to_orphans(ff, ino, &inode);
+ if (ret)
+ return ret;
+
+ goto write_out;
+ }
+ fuse4fs_iput(ff, fi);
if (ext2fs_has_feature_ea_inode(fs->super)) {
ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
@@ -2468,6 +2563,7 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
return translate_error(fs, ino, err);
}
+ ext2fs_set_dtime(fs, EXT2_INODE(&inode));
ext2fs_inode_alloc_stats2(fs, ino, -1,
LINUX_S_ISDIR(inode.i_mode));
@@ -3056,6 +3152,16 @@ static void op_link(fuse_req_t req, fuse_ino_t child_fino,
goto out2;
}
+ /*
+ * Linking a file back into the filesystem requires removing it from
+ * the orphan list.
+ */
+ if (inode.i_links_count == 0) {
+ ret = fuse4fs_remove_from_orphans(ff, child, &inode);
+ if (ret)
+ goto out2;
+ }
+
ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
ret = update_ctime(fs, child, &inode);
if (ret)
@@ -3339,7 +3445,8 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
#endif /* __linux__ */
static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
- ext2_ino_t ino, struct fuse_file_info *fp)
+ ext2_ino_t ino,
+ struct fuse_file_info *fp)
{
ext2_filsys fs = ff->fs;
errcode_t err;
@@ -3415,6 +3522,8 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
file->check_flags = check;
fuse4fs_set_handle(fp, file);
+ dbg_printf(ff, "%s: ino=%d fh=%p opencount=%d\n", __func__, ino, file,
+ file->fi->i_open_count);
out:
if (ret)
@@ -3431,6 +3540,8 @@ static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
FUSE4FS_CHECK_CONTEXT(req);
FUSE4FS_CONVERT_FINO(req, &ino, fino);
+ dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
+
fuse4fs_start(ff);
ret = fuse4fs_open_file(ff, ctxt, ino, fp);
fuse4fs_finish(ff, ret);
@@ -3579,6 +3690,55 @@ static void op_write(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
fuse_reply_err(req, -ret);
}
+static int fuse4fs_free_unlinked(struct fuse4fs *ff, ext2_ino_t ino)
+{
+ struct ext2_inode_large inode;
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ int ret = 0;
+
+ err = fuse4fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ if (inode.i_links_count > 0)
+ return 0;
+
+ dbg_printf(ff, "%s: ino=%d links=%d\n", __func__, ino,
+ inode.i_links_count);
+
+ if (ext2fs_has_feature_ea_inode(fs->super)) {
+ ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
+ if (ret)
+ return ret;
+ }
+
+ /* Nobody holds this file; free its blocks! */
+ err = ext2fs_free_ext_attr(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(&inode))) {
+ err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), NULL,
+ 0, ~0ULL);
+ if (err)
+ return translate_error(fs, ino, err);
+ }
+
+ ret = fuse4fs_remove_from_orphans(ff, ino, &inode);
+ if (ret)
+ return ret;
+
+ ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
+
+ err = fuse4fs_write_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
struct fuse_file_info *fp)
{
@@ -3590,9 +3750,21 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
FUSE4FS_CHECK_CONTEXT(req);
FUSE4FS_CHECK_HANDLE(req, fh);
- dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+ dbg_printf(ff, "%s: ino=%d fh=%p opencount=%u\n",
+ __func__, fh->ino, fh, fh->fi->i_open_count);
+
fs = fuse4fs_start(ff);
+ /*
+ * If the file is no longer open and is unlinked, free it, which
+ * removes it from the ondisk list.
+ */
+ if (--fh->fi->i_open_count == 0) {
+ ret = fuse4fs_free_unlinked(ff, fh->ino);
+ if (ret)
+ goto out_iput;
+ }
+
if ((fp->flags & O_SYNC) &&
fuse4fs_is_writeable(ff) &&
(fh->open_flags & EXT2_FILE_WRITE)) {
@@ -3601,6 +3773,7 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
ret = translate_error(fs, fh->ino, err);
}
+out_iput:
fuse4fs_iput(ff, fh->fi);
fp->fh = 0;
fuse4fs_finish(ff, ret);
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (20 preceding siblings ...)
2025-11-06 22:48 ` [PATCH 21/23] fuse4fs: use the orphaned inode list Darrick J. Wong
@ 2025-11-06 22:48 ` Darrick J. Wong
2025-11-06 22:49 ` [PATCH 23/23] fuse4fs: create incore reverse orphan list Darrick J. Wong
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Allow creation of O_TMPFILE files now that we know how to use the
unlinked list.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fuse4fs/fuse4fs.c | 93 ++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 67 insertions(+), 26 deletions(-)
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index fdeef157e732d4..7db09ed93fdf1f 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -1056,22 +1056,25 @@ static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
/* Test for append permission */
#define A_OK 16
+/* Test for linked file */
+#define L_OK 32
static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
const struct ext2_inode *inode, int mask)
{
- EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
+ EXT2FS_BUILD_BUG_ON(((A_OK | L_OK) & (R_OK | W_OK | X_OK | F_OK)) != 0);
/* no writing or metadata changes to read-only or broken fs */
if ((mask & (W_OK | A_OK)) && !fuse4fs_is_writeable(ff))
return -EROFS;
- dbg_printf(ff, "access ino=%d mask=e%s%s%s%s iflags=0x%x\n",
+ dbg_printf(ff, "access ino=%d mask=e%s%s%s%s%s iflags=0x%x\n",
ino,
(mask & R_OK ? "r" : ""),
(mask & W_OK ? "w" : ""),
(mask & X_OK ? "x" : ""),
(mask & A_OK ? "a" : ""),
+ (mask & L_OK ? "l" : ""),
inode->i_flags);
/* is immutable? */
@@ -1104,21 +1107,31 @@ static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
return translate_error(fs, ino, err);
perms = inode.i_mode & 0777;
- dbg_printf(ff, "access ino=%d mask=e%s%s%s%s perms=0%o iflags=0x%x "
+ dbg_printf(ff, "access ino=%d mask=e%s%s%s%s%s perms=0%o iflags=0x%x "
"fuid=%d fgid=%d uid=%d gid=%d\n", ino,
(mask & R_OK ? "r" : ""),
(mask & W_OK ? "w" : ""),
(mask & X_OK ? "x" : ""),
(mask & A_OK ? "a" : ""),
+ (mask & L_OK ? "l" : ""),
perms, inode.i_flags,
inode_uid(inode), inode_gid(inode),
ctxt->uid, ctxt->gid);
- /* linked files cannot be on the unlinked list or deleted */
- if (inode.i_dtime != 0) {
- dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
- __func__, ino, inode.i_dtime);
- return -ENOENT;
+ if (mask & L_OK) {
+ /* linked files cannot be on the unlinked list or deleted */
+ if (inode.i_dtime != 0) {
+ dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
+ __func__, ino, inode.i_dtime);
+ return -ENOENT;
+ }
+ } else {
+ /* unlinked files cannot be deleted */
+ if (inode.i_dtime >= fs->super->s_inodes_count) {
+ dbg_printf(ff, "%s: deleted ino=%d dtime=0x%x\n",
+ __func__, ino, inode.i_dtime);
+ return -ENOENT;
+ }
}
/* existence check */
@@ -3445,7 +3458,7 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
#endif /* __linux__ */
static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
- ext2_ino_t ino,
+ ext2_ino_t ino, bool linked,
struct fuse_file_info *fp)
{
ext2_filsys fs = ff->fs;
@@ -3475,6 +3488,9 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
break;
}
+ if (linked)
+ check |= L_OK;
+
/*
* If the caller wants to truncate the file, we need to ask for full
* write access even if the caller claims to be appending.
@@ -3543,7 +3559,7 @@ static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
fuse4fs_start(ff);
- ret = fuse4fs_open_file(ff, ctxt, ino, fp);
+ ret = fuse4fs_open_file(ff, ctxt, ino, true, fp);
fuse4fs_finish(ff, ret);
if (ret)
@@ -4452,22 +4468,28 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
goto out2;
}
- dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
- __func__, parent, name, child);
- err = ext2fs_link(fs, parent, name, child,
- filetype | EXT2FS_LINK_EXPAND);
- if (err) {
- ret = translate_error(fs, parent, err);
- goto out2;
+ if (name) {
+ dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
+ __func__, parent, name, child);
+
+ err = ext2fs_link(fs, parent, name, child,
+ filetype | EXT2FS_LINK_EXPAND);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+ } else {
+ dbg_printf(ff, "%s: creating dir=%d tempfile=%d\n",
+ __func__, parent, child);
}
- ret = update_mtime(fs, parent, NULL);
- if (ret)
- goto out2;
-
memset(&inode, 0, sizeof(inode));
inode.i_mode = mode;
- inode.i_links_count = 1;
+ inode.i_links_count = name ? 1 : 0;
fuse4fs_set_extra_isize(ff, child, &inode);
fuse4fs_set_uid(&inode, ctxt->uid);
fuse4fs_set_gid(&inode, gid);
@@ -4485,6 +4507,12 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
ext2fs_extent_free(handle);
}
+ if (!name) {
+ ret = fuse4fs_add_to_orphans(ff, child, &inode);
+ if (ret)
+ goto out2;
+ }
+
err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
if (err) {
ret = translate_error(fs, child, err);
@@ -4506,13 +4534,15 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
goto out2;
fp->flags &= ~O_TRUNC;
- ret = fuse4fs_open_file(ff, ctxt, child, fp);
+ ret = fuse4fs_open_file(ff, ctxt, child, name != NULL, fp);
if (ret)
goto out2;
- ret = fuse4fs_dirsync_flush(ff, parent, NULL);
- if (ret)
- goto out2;
+ if (name) {
+ ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+ if (ret)
+ goto out2;
+ }
ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
if (ret)
@@ -4527,6 +4557,14 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
fuse_reply_create(req, &fstat.entry, fp);
}
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+static void op_tmpfile(fuse_req_t req, fuse_ino_t fino, mode_t mode,
+ struct fuse_file_info *fp)
+{
+ op_create(req, fino, NULL, mode, fp);
+}
+#endif
+
enum fuse4fs_time_action {
TA_NOW, /* set to current time */
TA_OMIT, /* do not set timestamp */
@@ -5521,6 +5559,9 @@ static struct fuse_lowlevel_ops fs_ops = {
.fsyncdir = op_fsync,
.access = op_access,
.create = op_create,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+ .tmpfile = op_tmpfile,
+#endif
.bmap = op_bmap,
#ifdef SUPERFLUOUS
.lock = op_lock,
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 23/23] fuse4fs: create incore reverse orphan list
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
` (21 preceding siblings ...)
2025-11-06 22:48 ` [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE Darrick J. Wong
@ 2025-11-06 22:49 ` Darrick J. Wong
22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:49 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
From: Darrick J. Wong <djwong@kernel.org>
Create an incore orphan list so that removing open unlinked inodes
doesn't take forever.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fuse4fs/fuse4fs.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 174 insertions(+), 4 deletions(-)
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 7db09ed93fdf1f..609e23bd916cc0 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -358,10 +358,20 @@ static inline int u_log2(unsigned int arg)
return l;
}
+/* inode is not on unlinked list */
+#define FUSE4FS_NULL_INO ((ext2_ino_t)~0ULL)
+
struct fuse4fs_inode {
struct cache_node i_cnode;
ext2_ino_t i_ino;
unsigned int i_open_count;
+
+ /*
+ * FUSE4FS_NULL_INO: inode is not on the orphan list
+ * 0: inode is the first on the orphan list
+ * otherwise: inode is in the middle of the list
+ */
+ ext2_ino_t i_prev_orphan;
};
struct fuse4fs_ikey {
@@ -403,12 +413,15 @@ static struct cache_node *icache_alloc(struct cache *c, cache_key_t key)
return NULL;
fi->i_ino = ikey->i_ino;
+ fi->i_prev_orphan = FUSE4FS_NULL_INO;
return &fi->i_cnode;
}
static bool icache_flush(struct cache *c, struct cache_node *node)
{
- return false;
+ struct fuse4fs_inode *fi = ICNODE(node);
+
+ return fi->i_prev_orphan != FUSE4FS_NULL_INO;
}
static void icache_relse(struct cache *c, struct cache_node *node)
@@ -2428,10 +2441,31 @@ static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
struct ext2_inode_large *inode)
{
ext2_filsys fs = ff->fs;
+ struct fuse4fs_inode *fi;
+ ext2_ino_t orphan_ino = fs->super->s_last_orphan;
+ errcode_t err;
dbg_printf(ff, "%s: orphan ino=%d dtime=%d next=%d\n",
__func__, ino, inode->i_dtime, fs->super->s_last_orphan);
+ /* Make the first orphan on the list point back to us */
+ if (orphan_ino != 0) {
+ err = fuse4fs_iget(ff, orphan_ino, &fi);
+ if (err)
+ return translate_error(fs, orphan_ino, err);
+
+ fi->i_prev_orphan = ino;
+ fuse4fs_iput(ff, fi);
+ }
+
+ /* Add ourselves to the head of the orphan list */
+ err = fuse4fs_iget(ff, ino, &fi);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ fi->i_prev_orphan = 0;
+ fuse4fs_iput(ff, fi);
+
inode->i_dtime = fs->super->s_last_orphan;
fs->super->s_last_orphan = ino;
ext2fs_mark_super_dirty(fs);
@@ -2439,24 +2473,158 @@ static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
return 0;
}
+/*
+ * Given the orphan list excerpt: prev_orphan -> ino -> next_orphan, set
+ * next_orphan's backpointer to ino's backpointer (prev_orphan), having removed
+ * ino from the orphan list.
+ */
+static int fuse4fs_update_next_orphan_backlink(struct fuse4fs *ff,
+ ext2_ino_t prev_orphan,
+ ext2_ino_t ino,
+ ext2_ino_t next_orphan)
+{
+ struct fuse4fs_inode *fi;
+ errcode_t err;
+ int ret = 0;
+
+ err = fuse4fs_iget(ff, next_orphan, &fi);
+ if (err)
+ return translate_error(ff->fs, next_orphan, err);
+
+ dbg_printf(ff, "%s: ino=%d cached next=%d nextprev=%d prev=%d\n",
+ __func__, ino, next_orphan, fi->i_prev_orphan,
+ prev_orphan);
+
+ if (fi->i_prev_orphan != ino) {
+ ret = translate_error(ff->fs, next_orphan,
+ EXT2_ET_FILESYSTEM_CORRUPTED);
+ goto out_iput;
+ }
+
+ fi->i_prev_orphan = prev_orphan;
+out_iput:
+ fuse4fs_iput(ff, fi);
+ return ret;
+}
+
+/*
+ * Remove ino from the orphan list the fast way. Returns 1 for success, 0 if
+ * it didn't do anything, or a negative errno.
+ */
+static int fuse4fs_fast_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+ struct ext2_inode_large *inode)
+{
+ struct ext2_inode_large orphan;
+ ext2_filsys fs = ff->fs;
+ struct fuse4fs_inode *fi;
+ ext2_ino_t prev_orphan;
+ ext2_ino_t next_orphan = 0;
+ errcode_t err;
+ int ret = 0;
+
+ err = fuse4fs_iget(ff, ino, &fi);
+ if (err)
+ return translate_error(fs, ino, err);
+
+ prev_orphan = fi->i_prev_orphan;
+ switch (prev_orphan) {
+ case 0:
+ /* First inode in the list */
+ dbg_printf(ff, "%s: ino=%d cached superblock\n", __func__, ino);
+
+ fs->super->s_last_orphan = inode->i_dtime;
+ next_orphan = inode->i_dtime;
+ inode->i_dtime = 0;
+ ext2fs_mark_super_dirty(fs);
+ fi->i_prev_orphan = FUSE4FS_NULL_INO;
+ break;
+ case FUSE4FS_NULL_INO:
+ /* unknown */
+ dbg_printf(ff, "%s: ino=%d broken list??\n", __func__, ino);
+ ret = 0;
+ goto out_iput;
+ default:
+ /* We're in the middle of the list */
+ err = fuse4fs_read_inode(fs, prev_orphan, &orphan);
+ if (err) {
+ ret = translate_error(fs, prev_orphan, err);
+ goto out_iput;
+ }
+
+ dbg_printf(ff,
+ "%s: ino=%d cached prev=%d prevnext=%d next=%d\n",
+ __func__, ino, prev_orphan, orphan.i_dtime,
+ inode->i_dtime);
+
+ if (orphan.i_dtime != ino) {
+ ret = translate_error(fs, prev_orphan,
+ EXT2_ET_FILESYSTEM_CORRUPTED);
+ goto out_iput;
+ }
+
+ fi->i_prev_orphan = FUSE4FS_NULL_INO;
+ orphan.i_dtime = inode->i_dtime;
+ next_orphan = inode->i_dtime;
+ inode->i_dtime = 0;
+
+ err = fuse4fs_write_inode(fs, prev_orphan, &orphan);
+ if (err) {
+ ret = translate_error(fs, prev_orphan, err);
+ goto out_iput;
+ }
+
+ break;
+ }
+
+ /*
+ * Make the next orphaned inode point back to the our own previous list
+ * entry
+ */
+ if (next_orphan != 0) {
+ ret = fuse4fs_update_next_orphan_backlink(ff, prev_orphan, ino,
+ next_orphan);
+ if (ret)
+ goto out_iput;
+ }
+ ret = 1;
+
+out_iput:
+ fuse4fs_iput(ff, fi);
+ return ret;
+}
+
static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
struct ext2_inode_large *inode)
{
ext2_filsys fs = ff->fs;
ext2_ino_t prev_orphan;
+ ext2_ino_t next_orphan;
errcode_t err;
+ int ret;
dbg_printf(ff, "%s: super=%d ino=%d next=%d\n",
__func__, fs->super->s_last_orphan, ino, inode->i_dtime);
- /* If we're lucky, the ondisk superblock points to us */
+ /*
+ * Fast way: use the incore list, which doesn't include any orphans
+ * that were already on the superblock when we mounted.
+ */
+ ret = fuse4fs_fast_remove_from_orphans(ff, ino, inode);
+ if (ret < 0)
+ return ret;
+ if (ret == 1)
+ return 0;
+
+ /* Slow way: If we're lucky, the ondisk superblock points to us */
if (fs->super->s_last_orphan == ino) {
dbg_printf(ff, "%s: superblock\n", __func__);
+ next_orphan = inode->i_dtime;
fs->super->s_last_orphan = inode->i_dtime;
inode->i_dtime = 0;
ext2fs_mark_super_dirty(fs);
- return 0;
+ return fuse4fs_update_next_orphan_backlink(ff, 0, ino,
+ next_orphan);
}
/* Otherwise walk the ondisk orphan list. */
@@ -2476,6 +2644,7 @@ static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
dbg_printf(ff, "%s: prev=%d\n",
__func__, prev_orphan);
+ next_orphan = inode->i_dtime;
orphan.i_dtime = inode->i_dtime;
inode->i_dtime = 0;
@@ -2483,7 +2652,8 @@ static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
if (err)
return translate_error(fs, prev_orphan, err);
- return 0;
+ return fuse4fs_update_next_orphan_backlink(ff,
+ prev_orphan, ino, next_orphan);
}
dbg_printf(ff, "%s: orphan=%d next=%d\n",
^ permalink raw reply related [flat|nested] 84+ messages in thread