[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <175573712827.20753.8861438199765222529.stgit@frogsfrogsfrogs>
Date: Wed, 20 Aug 2025 18:08:08 -0700
From: "Darrick J. Wong" <djwong@...nel.org>
To: tytso@....edu
Cc: John@...ves.net, bernd@...ernd.com, linux-fsdevel@...r.kernel.org,
linux-ext4@...r.kernel.org, miklos@...redi.hu, amir73il@...il.com,
joannelkoong@...il.com, neal@...pa.dev
Subject: [PATCH 01/20] fuse2fs: port fuse2fs to lowlevel libfuse API
From: Darrick J. Wong <djwong@...nel.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@...nel.org>
---
configure | 50
configure.ac | 31
lib/config.h.in | 3
misc/Makefile.in | 22
misc/fuse4fs.c | 5607 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 5712 insertions(+), 1 deletion(-)
create mode 100644 misc/fuse4fs.c
diff --git a/configure b/configure
index 71750b1a8ee972..8afc53f89f2bf4 100755
--- a/configure
+++ b/configure
@@ -701,6 +701,7 @@ gcc_ranlib
gcc_ar
UNI_DIFF_OPTS
SEM_INIT_LIB
+FUSE4_CMT
FUSE_CMT
FUSE_LIB
fuse3_LIBS
@@ -14719,6 +14720,55 @@ elif test -n "$FUSE_LIB"
then
FUSE_USE_VERSION=29
fi
+
+FUSE4FS_CMT=
+if test "$FUSE_USE_VERSION" -ge 30
+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 30
+#include <fuse_lowlevel.h>
+
+int
+main (void)
+{
+
+struct fuse_lowlevel_ops fs_ops = {
+ .init = NULL,
+ .destroy = NULL,
+};
+
+ ;
+ 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
+if test "$have_fuse_lowlevel" = yes; then
+
+printf "%s\n" "#define HAVE_FUSE_LOWLEVEL 1" >>confdefs.h
+
+else
+ FUSE4FS_CMT="#"
+fi
+fi
+
+
if test -n "$FUSE_USE_VERSION"
then
diff --git a/configure.ac b/configure.ac
index 0591999b52b019..37dbfa0be4d7fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1447,6 +1447,37 @@ elif test -n "$FUSE_LIB"
then
FUSE_USE_VERSION=29
fi
+
+FUSE4FS_CMT=
+if test "$FUSE_USE_VERSION" -ge 30
+then
+dnl
+dnl see if fuse3 supports lowlevel interface
+dnl
+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 30
+#include <fuse_lowlevel.h>
+ ]], [[
+struct fuse_lowlevel_ops fs_ops = {
+ .init = NULL,
+ .destroy = NULL,
+};
+ ]])
+], have_fuse_lowlevel=yes
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no))
+if test "$have_fuse_lowlevel" = yes; then
+ AC_DEFINE(HAVE_FUSE_LOWLEVEL, 1, [Define to 1 if fuse supports lowlevel API])
+else
+ FUSE4FS_CMT="#"
+fi
+fi
+AC_SUBST(FUSE4_CMT)
+
if test -n "$FUSE_USE_VERSION"
then
AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
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
diff --git a/misc/Makefile.in b/misc/Makefile.in
index 0e3bed66dcb63d..7c6b33cb864204 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -35,6 +35,7 @@ MKDIR_P = @MKDIR_P@
@BLKID_CMT@...DFS_MAN= findfs.8
@FUSE_CMT@...E_PROG= fuse2fs
+@...E4_CMT@...E_PROG+=fuse4fs
SPROGS= mke2fs badblocks tune2fs dumpe2fs $(BLKID_PROG) logsave \
$(E2IMAGE_PROG) @FSCK_PROG@ e2undo
@@ -73,6 +74,7 @@ E4CRYPT_OBJS= e4crypt.o
E2FREEFRAG_OBJS= e2freefrag.o
E2FUZZ_OBJS= e2fuzz.o
FUSE2FS_OBJS= fuse2fs.o journal.o recovery.o revoke.o
+FUSE4FS_OBJS= fuse4fs.o journal.o recovery.o revoke.o
PROFILED_TUNE2FS_OBJS= profiled/tune2fs.o profiled/util.o profiled/journal.o \
profiled/recovery.o profiled/revoke.o
@@ -99,6 +101,8 @@ PROFILED_E4DEFRAG_OBJS= profiled/e4defrag.o
PROFILED_E4CRYPT_OBJS= profiled/e4crypt.o
PROFILED_FUSE2FS_OJBS= profiled/fuse2fs.o profiled/journal.o \
profiled/recovery.o profiled/revoke.o
+PROFILED_FUSE4FS_OJBS= profiled/fuse4fs.o profiled/journal.o \
+ profiled/recovery.o profiled/revoke.o
SRCS= $(srcdir)/tune2fs.c $(srcdir)/mklost+found.c $(srcdir)/mke2fs.c $(srcdir)/mk_hugefiles.c \
$(srcdir)/chattr.c $(srcdir)/lsattr.c $(srcdir)/dumpe2fs.c \
@@ -108,7 +112,7 @@ SRCS= $(srcdir)/tune2fs.c $(srcdir)/mklost+found.c $(srcdir)/mke2fs.c $(srcdir)/
$(srcdir)/ismounted.c $(srcdir)/e2undo.c \
$(srcdir)/e2freefrag.c $(srcdir)/create_inode.c \
$(srcdir)/create_inode_libarchive.c \
- $(srcdir)/fuse2fs.c $(srcdir)/e2fuzz.c \
+ $(srcdir)/fuse2fs.c $(srcdir)/fuse4fs.c $(srcdir)/e2fuzz.c \
$(srcdir)/check_fuzzer.c \
$(srcdir)/../debugfs/journal.c $(srcdir)/../e2fsck/revoke.c \
$(srcdir)/../e2fsck/recovery.c
@@ -429,6 +433,13 @@ fuse2fs: $(FUSE2FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \
$(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \
$(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P)
+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) \
@@ -881,6 +892,15 @@ fuse2fs.o: $(srcdir)/fuse2fs.c $(top_builddir)/lib/config.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
+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
e2fuzz.o: $(srcdir)/e2fuzz.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
$(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
diff --git a/misc/fuse4fs.c b/misc/fuse4fs.c
new file mode 100644
index 00000000000000..1b8240e56562d6
--- /dev/null
+++ b/misc/fuse4fs.c
@@ -0,0 +1,5607 @@
+/*
+ * 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>
+#include <stdbool.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"
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+# define FUSE_PLATFORM_OPTS ""
+#else
+# ifdef __linux__
+# define FUSE_PLATFORM_OPTS ",use_ino,big_writes"
+# else
+# define FUSE_PLATFORM_OPTS ",use_ino"
+# endif
+#endif
+
+#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; \
+ }
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+# ifdef _IOR
+# ifdef _IOW
+# define SUPPORT_I_FLAGS
+# endif
+# 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_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;
+};
+
+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;
+ uint8_t ro;
+ uint8_t debug;
+ uint8_t no_default_opts;
+ uint8_t errors_behavior; /* actually an enum */
+ uint8_t minixdf;
+ uint8_t fakeroot;
+ uint8_t alloc_all_blocks;
+ uint8_t norecovery;
+ uint8_t kernel;
+ uint8_t directio;
+ uint8_t acl;
+ uint8_t dirsync;
+ uint8_t unmount_in_destroy;
+ uint8_t noblkdev;
+
+ enum fuse4fs_opstate opstate;
+ int logfd;
+ int blocklog;
+ unsigned int blockmask;
+ unsigned long offset;
+ unsigned int next_generation;
+ unsigned long long cache_size;
+ char *lockfile;
+#ifdef HAVE_CLOCK_MONOTONIC
+ struct timespec lock_start_time;
+ struct timespec op_start_time;
+ uint8_t 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_RETURN(ff) \
+ __FUSE4FS_CHECK_CONTEXT((ff), return, return)
+#define FUSE4FS_CHECK_CONTEXT_ABORT(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;
+}
+
+#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 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;
+}
+
+#ifdef HAVE_CLOCK_MONOTONIC
+static inline ext2_filsys fuse4fs_start(struct fuse4fs *ff)
+{
+ struct timespec lock_time;
+ int ret;
+
+ if (ff->timing)
+ clock_gettime(CLOCK_MONOTONIC, &lock_time);
+
+ pthread_mutex_lock(&ff->bfl);
+ if (ff->timing) {
+ ret = clock_gettime(CLOCK_MONOTONIC, &ff->op_start_time);
+ if (ret)
+ ff->timing = 0;
+ ff->lock_start_time = lock_time;
+ }
+ return ff->fs;
+}
+
+static inline double ms_from_timespec(const struct timespec *ts)
+{
+ return ((double)ts->tv_sec * 1000) + ((double)ts->tv_nsec / 1000000);
+}
+
+static inline void fuse4fs_finish_timing(struct fuse4fs *ff, const char *func)
+{
+ struct timespec now;
+ double lockf, startf, nowf;
+ int ret;
+
+ if (!ff->timing)
+ return;
+
+ ret = clock_gettime(CLOCK_MONOTONIC, &now);
+ if (ret) {
+ ff->timing = 0;
+ return;
+ }
+
+ lockf = ms_from_timespec(&ff->lock_start_time);
+ startf = ms_from_timespec(&ff->op_start_time);
+ nowf = ms_from_timespec(&now);
+ timing_printf(ff, "%s: lock=%.2fms elapsed=%.2fms\n", func,
+ startf - lockf, nowf - startf);
+}
+#else
+static inline ext2_filsys fuse4fs_start(struct fuse4fs *ff)
+{
+ pthread_mutex_lock(&ff->bfl);
+ return ff->fs;
+}
+# define fuse4fs_finish_timing(...) ((void)0)
+#endif
+
+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__)
+
+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_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)
+{
+ errcode_t err;
+
+ if (!ff->fs)
+ return;
+
+ err = ext2fs_close(ff->fs);
+ if (err)
+ err_printf(ff, "%s\n", error_message(err));
+
+ ff->fs = NULL;
+
+ if (ff->lockfile)
+ fuse4fs_release_lockfile(ff);
+}
+
+static errcode_t fuse4fs_open(struct fuse4fs *ff, int libext2_flags)
+{
+ char options[128];
+ int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |
+ libext2_flags;
+ 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;
+
+ err = ext2fs_open2(ff->device, options, flags, 0, 0, unix_io_manager,
+ &ff->fs);
+ if (err == EPERM) {
+ err_printf(ff, "%s.\n",
+ _("read-only device, trying to mount norecovery"));
+ flags &= ~EXT2_FLAG_RW;
+ ff->ro = 1;
+ ff->norecovery = 1;
+ err = ext2fs_open2(ff->device, options, flags, 0, 0,
+ unix_io_manager, &ff->fs);
+ }
+ if (err) {
+ err_printf(ff, "%s.\n", error_message(err));
+ err_printf(ff, "%s\n", _("Please run e2fsck -fy."));
+ return err;
+ }
+
+ ff->fs->priv_data = ff;
+ ff->blocklog = u_log2(ff->fs->blocksize);
+ ff->blockmask = ff->fs->blocksize - 1;
+ return 0;
+}
+
+static inline bool fuse4fs_on_bdev(const struct fuse4fs *ff)
+{
+ return ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE;
+}
+
+static errcode_t fuse4fs_config_cache(struct fuse4fs *ff)
+{
+ char buf[128];
+ errcode_t err;
+
+ 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 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 int fuse4fs_check_norecovery(struct fuse4fs *ff)
+{
+ if (ext2fs_has_feature_journal_needs_recovery(ff->fs->super) &&
+ !ff->ro) {
+ log_printf(ff, "%s\n",
+ _("Required journal recovery suppressed and not mounted read-only."));
+ return 32;
+ }
+
+ /*
+ * Amazingly, norecovery allows a rw mount when there's a clean journal
+ * present.
+ */
+ return 0;
+}
+
+static errcode_t fuse4fs_mount(struct fuse4fs *ff)
+{
+ 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."));
+ } else {
+ log_printf(ff, "%s\n", _("Recovering journal."));
+ err = ext2fs_run_ext3_journal(&fs);
+ if (err) {
+ err_printf(ff, "%s.\n", error_message(err));
+ err_printf(ff, "%s\n",
+ _("Please run e2fsck -fy."));
+ return err;
+ }
+ ext2fs_clear_feature_journal_needs_recovery(fs->super);
+ ext2fs_mark_super_dirty(fs);
+
+ err = fuse4fs_check_support(ff);
+ if (err)
+ return 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"));
+ err = ext2fs_read_inode_bitmap(fs);
+ if (err) {
+ translate_error(fs, 0, err);
+ return err;
+ }
+ err = ext2fs_read_block_bitmap(fs);
+ if (err) {
+ translate_error(fs, 0, err);
+ return err;
+ }
+ 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_RETURN(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));
+ }
+
+ if (ff->kernel) {
+ char uuid[UUID_STR_SIZE];
+
+ uuid_unparse(fs->super->s_uuid, uuid);
+ log_printf(ff, "%s %s.\n", _("unmounting filesystem"), uuid);
+ }
+
+ if (ff->unmount_in_destroy)
+ fuse4fs_unmount(ff);
+
+ 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;
+}
+
+#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
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_config *cfg EXT2FS_ATTR((unused))
+#endif
+ )
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+
+ FUSE4FS_CHECK_CONTEXT_ABORT(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
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ conn->time_gran = 1;
+ cfg->use_ino = 1;
+ if (ff->debug)
+ cfg->debug = 1;
+ cfg->nullpath_ok = 1;
+#endif
+
+ if (ff->kernel) {
+ char uuid[UUID_STR_SIZE];
+
+ uuid_unparse(fs->super->s_uuid, uuid);
+ log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
+ }
+
+#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,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ struct fuse_file_info *fp EXT2FS_ATTR((unused)),
+#endif
+ ext2_ino_t *inop,
+ const char *func,
+ int line)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ 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;
+ }
+#endif
+ 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;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+# define fuse4fs_file_ino(ff, path, fp, inop) \
+ __fuse4fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
+#else
+# define fuse4fs_file_ino(ff, path, fp, inop) \
+ __fuse4fs_file_ino((ff), (path), NULL, (inop), __func__, __LINE__)
+#endif
+
+static int op_getattr(const char *path, struct stat *statbuf
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_file_info *fi
+#endif
+ )
+{
+ 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)
+{
+ void *def;
+ size_t deflen;
+ int ret;
+
+ if (!ff->acl)
+ 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);
+ 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);
+ 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);
+
+ switch (inode.i_links_count) {
+ case 0:
+ return 0; /* XXX: already done? */
+ case 1:
+ inode.i_links_count--;
+ ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ break;
+ default:
+ inode.i_links_count--;
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ return ret;
+
+ 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;
+ /* Directories have to be "removed" twice. */
+ ret = remove_inode(ff, child);
+ 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;
+ }
+ if (inode.i_links_count > 1)
+ inode.i_links_count--;
+ 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);
+ 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;
+}
+
+static int op_rename(const char *from, const char *to
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , unsigned int flags EXT2FS_ATTR((unused))
+#endif
+ )
+{
+ 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;
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ /* renameat2 is not supported */
+ if (flags)
+ return -ENOSYS;
+#endif
+
+ 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;
+
+ /* 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;
+ }
+ inode.i_links_count--;
+ 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;
+ }
+ inode.i_links_count++;
+ 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;
+
+ inode.i_links_count++;
+ 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;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+/* 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;
+
+ ret = get_req_groups(ff, &gids, &nr_gids);
+ if (ret == -ENOENT) {
+ /* magic return code for "could not get caller group info" */
+ return ctxt->gid == inode_gid(*inode);
+ }
+ 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;
+}
+#else
+static int in_file_group(struct fuse_context *ctxt,
+ const struct ext2_inode_large *inode)
+{
+ return ctxt->gid == inode_gid(*inode);
+}
+#endif
+
+static int op_chmod(const char *path, mode_t mode
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_file_info *fi
+#endif
+ )
+{
+ 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
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_file_info *fi
+#endif
+ )
+{
+ 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
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_file_info *fi
+#endif
+ )
+{
+ 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;
+ } else
+ goto out;
+ }
+
+ if (fp->flags & O_TRUNC) {
+ ret = fuse4fs_truncate(ff, file->ino, 0);
+ if (ret)
+ goto out;
+ }
+
+ 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 (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;
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ enum fuse_readdir_flags flags;
+#endif
+ 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",
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+#else
+ "",
+#endif
+ dir,
+ i->nr++,
+ (unsigned long long)i->dirpos);
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ if (i->flags == FUSE_READDIR_PLUS) {
+ ret = stat_inode(i->fs, dirent->inode, &stat);
+ if (ret)
+ return DIRENT_ABORT;
+ }
+#endif
+
+ memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
+ namebuf[dirent->name_len & 0xFF] = 0;
+ ret = i->func(i->buf, namebuf, &stat, i->dirpos
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , 0
+#endif
+ );
+ 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
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , enum fuse_readdir_flags flags
+#endif
+ )
+{
+ 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,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ .flags = flags,
+#endif
+ };
+ 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);
+ 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;
+}
+
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
+static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
+ off_t len, 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;
+ int ret = 0;
+
+ FUSE4FS_CHECK_CONTEXT(ff);
+ FUSE4FS_CHECK_HANDLE(ff, fh);
+ dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
+ (intmax_t) len);
+ fs = fuse4fs_start(ff);
+ if (!fuse4fs_is_writeable(ff)) {
+ ret = -EROFS;
+ 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_set_size2(efp, len);
+ 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;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ fuse4fs_finish(ff, ret);
+ return ret;
+}
+
+static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
+ struct stat *statbuf,
+ struct fuse_file_info *fp)
+{
+ struct fuse4fs *ff = fuse4fs_get();
+ ext2_filsys fs;
+ struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+ 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);
+ ret = stat_inode(fs, fh->ino, statbuf);
+ fuse4fs_finish(ff, ret);
+
+ return ret;
+}
+#endif /* FUSE_VERSION < FUSE_MAKE_VERSION(3, 0) */
+
+static int op_utimens(const char *path, const struct timespec ctv[2]
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ , struct fuse_file_info *fi
+#endif
+ )
+{
+ 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;
+}
+
+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;
+ __u32 flags = fsxflags_to_iflags(fsx->fsx_xflags);
+ 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_iflags(&inode, flags);
+ 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)
+ 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"));
+
+ /*
+ * 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;
+
+ return 0;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+ unsigned int cmd,
+#else
+ int cmd,
+#endif
+ 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;
+}
+#endif /* FUSE 28 */
+
+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;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+# 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 */
+#endif /* FUSE 29 */
+
+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,
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
+ .ftruncate = op_ftruncate,
+ .fgetattr = op_fgetattr,
+#endif
+ .utimens = op_utimens,
+#if (FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)) && (FUSE_VERSION < FUSE_MAKE_VERSION(3, 0))
+# if defined(UTIME_NOW) || defined(UTIME_OMIT)
+ .flag_utime_omit_ok = 1,
+# endif
+#endif
+ .bmap = op_bmap,
+#ifdef SUPERFLUOUS
+ .lock = op_lock,
+ .poll = op_poll,
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+ .ioctl = op_ioctl,
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
+ .flag_nullpath_ok = 1,
+#endif
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
+ .flag_nopath = 1,
+#endif
+# ifdef SUPPORT_FALLOCATE
+ .fallocate = op_fallocate,
+# endif
+#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("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
+ FUSE4FS_OPT("noblkdev", noblkdev, 1),
+
+ 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";
+}
+
+/* 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 inline bool fuse4fs_want_fuseblk(const struct fuse4fs *ff)
+{
+ if (ff->noblkdev)
+ return false;
+
+ /* libfuse won't let non-root do fuseblk mounts */
+ if (getuid() != 0)
+ return false;
+
+ return fuse4fs_on_bdev(ff);
+}
+
+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;
+ errcode_t err;
+ FILE *orig_stderr = stderr;
+ char extra_args[BUFSIZ];
+ int ret;
+
+ memset(&fctx, 0, sizeof(fctx));
+ fctx.magic = FUSE4FS_MAGIC;
+ fctx.logfd = -1;
+ fctx.opstate = F4OP_WRITABLE;
+
+ 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;
+ }
+
+#ifdef HAVE_PR_SET_IO_FLUSHER
+ /*
+ * Register as a filesystem I/O server process so that our memory
+ * allocations don't cause fs reclaim.
+ */
+ ret = prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0);
+ if (ret < 0) {
+ err_printf(&fctx, "%s: %s.\n",
+ _("Could not register as IO flusher thread"),
+ strerror(errno));
+ ret = 0;
+ }
+#endif
+
+ /* 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, EXT2_FLAG_EXCLUSIVE);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ if (fuse4fs_want_fuseblk(&fctx)) {
+ /*
+ * If this is a block device, we want to close the fs, reopen
+ * the block device in non-exclusive mode, and start the fuse
+ * driver in fuseblk mode (which will reopen the block device
+ * in exclusive mode) so that unmount will wait until
+ * op_destroy completes.
+ */
+ fuse4fs_unmount(&fctx);
+ err = fuse4fs_open(&fctx, 0);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ /* "blkdev" is the magic mount option for fuseblk mode */
+ snprintf(extra_args, BUFSIZ, "-oblkdev,blksize=%u",
+ fctx.fs->blocksize);
+ fuse_opt_add_arg(&args, extra_args);
+ fctx.unmount_in_destroy = 1;
+ }
+
+ if (!fctx.cache_size)
+ fctx.cache_size = default_cache_size();
+ if (fctx.cache_size) {
+ 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;
+
+ if (fctx.norecovery) {
+ ret = fuse4fs_check_norecovery(&fctx);
+ if (ret)
+ goto out;
+ }
+
+ err = fuse4fs_mount(&fctx);
+ if (err) {
+ ret = 32;
+ goto out;
+ }
+
+ /* Initialize generation counter */
+ get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
+
+ /* Set up default fuse parameters */
+ snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
+ "fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
+ get_subtype(argv[0]),
+ fctx.device);
+ if (fctx.no_default_opts == 0)
+ fuse_opt_add_arg(&args, extra_args);
+
+ if (fctx.ro)
+ fuse_opt_add_arg(&args, "-oro");
+
+ if (fctx.fakeroot) {
+#ifdef HAVE_MOUNT_NODEV
+ fuse_opt_add_arg(&args,"-onodev");
+#endif
+#ifdef HAVE_MOUNT_NOSUID
+ fuse_opt_add_arg(&args,"-onosuid");
+#endif
+ }
+
+ if (fctx.kernel) {
+ /*
+ * ACLs are always enforced when kernel mode is enabled, to
+ * match the kernel ext4 driver which always enables ACLs.
+ */
+ fctx.acl = 1;
+ fuse_opt_insert_arg(&args, 1,
+ "-oallow_other,default_permissions,suid,dev");
+ }
+
+ if (fctx.debug) {
+ int i;
+
+ printf("FUSE4FS (%s): fuse arguments:", fctx.shortdev);
+ for (i = 0; i < args.argc; i++)
+ printf(" '%s'", args.argv[i]);
+ printf("\n");
+ fflush(stdout);
+ }
+
+ pthread_mutex_init(&fctx.bfl, NULL);
+ ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+ pthread_mutex_destroy(&fctx.bfl);
+
+ 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_unmount(&fctx);
+ reset_com_err_hook();
+ err_shortdev = NULL;
+ if (fctx.device)
+ free(fctx.device);
+ 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_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;
+ }
+ 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;
+}
Powered by blists - more mailing lists