fuse2fs: flush dirty metadata periodically
Flush dirty metadata out to disk periodically like the kernel, to reduce
the potential for data loss if userspace doesn't explicitly fsync.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 3b65aef..45b4004 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -28,6 +28,7 @@
#include <unistd.h>
#include <ctype.h>
#include <assert.h>
+#include <limits.h>
#ifdef HAVE_FUSE_LOOPDEV
# include <fuse_loopdev.h>
#endif
@@ -332,6 +333,10 @@
#endif
struct psi *mem_psi;
struct psi_handler *mem_psi_handler;
+
+ struct bthread *flush_thread;
+ unsigned int flush_interval;
+ double last_flush;
};
#ifdef HAVE_FUSE_SERVICE
@@ -1005,6 +1010,71 @@
fp->keep_cache = 1;
}
+static errcode_t fuse4fs_flush(struct fuse4fs *ff, int flags)
+{
+ double last_flush = gettime_monotonic();
+ errcode_t err;
+
+ err = ext2fs_flush2(ff->fs, flags);
+ if (err)
+ return err;
+
+ ff->last_flush = last_flush;
+ return 0;
+}
+
+static inline int fuse4fs_flush_wanted(struct fuse4fs *ff)
+{
+ return ff->fs != NULL && ff->opstate == F4OP_WRITABLE &&
+ ff->last_flush + ff->flush_interval <= gettime_monotonic();
+}
+
+static void fuse4fs_flush_bthread(void *data)
+{
+ struct fuse4fs *ff = data;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ fs = fuse4fs_start(ff);
+ if (fuse4fs_flush_wanted(ff) && !bthread_cancelled(ff->flush_thread)) {
+ err = fuse4fs_flush(ff, 0);
+ if (err)
+ ret = translate_error(fs, 0, err);
+ }
+ fuse4fs_finish(ff, ret);
+}
+
+static void fuse4fs_flush_start(struct fuse4fs *ff)
+{
+ int ret;
+
+ if (!ff->flush_interval)
+ return;
+
+ ret = bthread_create("fuse4fs_flush", fuse4fs_flush_bthread, ff,
+ ff->flush_interval, &ff->flush_thread);
+ if (ret) {
+ err_printf(ff, "flusher: %s.\n", error_message(ret));
+ return;
+ }
+
+ ret = bthread_start(ff->flush_thread);
+ if (ret)
+ err_printf(ff, "flusher: %s.\n", error_message(ret));
+}
+
+static void fuse4fs_flush_cancel(struct fuse4fs *ff)
+{
+ if (ff->flush_thread)
+ bthread_cancel(ff->flush_thread);
+}
+
+static void fuse4fs_flush_destroy(struct fuse4fs *ff)
+{
+ bthread_destroy(&ff->flush_thread);
+}
+
#ifdef HAVE_FUSE_IOMAP
static inline int fuse4fs_iomap_enabled(const struct fuse4fs *ff)
{
@@ -2244,7 +2314,7 @@
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);
+ err = fuse4fs_flush(ff, 0);
if (err)
return translate_error(fs, 0, err);
}
@@ -2274,7 +2344,7 @@
translate_error(fs, 0, err);
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse4fs_flush(ff, 0);
if (err)
translate_error(fs, 0, err);
}
@@ -2297,6 +2367,7 @@
* that the block device will be released before umount(2) returns.
*/
if (ff->iomap_state == IOMAP_ENABLED) {
+ fuse4fs_flush_cancel(ff);
fuse4fs_mmp_cancel(ff);
fuse4fs_unmount(ff);
}
@@ -2502,6 +2573,7 @@
*/
fuse4fs_mmp_start(ff);
fuse4fs_psi_start(ff);
+ fuse4fs_flush_start(ff);
#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
/*
@@ -3059,7 +3131,7 @@
*flushed = 0;
return 0;
flush:
- err = ext2fs_flush2(fs, 0);
+ err = fuse4fs_flush(ff, 0);
if (err)
return translate_error(fs, 0, err);
@@ -4888,7 +4960,7 @@
if ((fp->flags & O_SYNC) &&
fuse4fs_is_writeable(ff) &&
(fh->open_flags & EXT2_FILE_WRITE)) {
- err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+ err = fuse4fs_flush(ff, EXT2_FLAG_FLUSH_NO_SYNC);
if (err)
ret = translate_error(fs, fh->ino, err);
}
@@ -4919,7 +4991,7 @@
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);
+ err = fuse4fs_flush(ff, 0);
if (err)
ret = translate_error(fs, fh->ino, err);
}
@@ -6246,6 +6318,7 @@
err_printf(ff, "%s.\n", _("shut down requested"));
+ fuse4fs_flush_cancel(ff);
fuse4fs_mmp_cancel(ff);
/*
@@ -6254,7 +6327,7 @@
* any of the flags. Flush whatever is dirty and shut down.
*/
if (ff->opstate == F4OP_WRITABLE)
- ext2fs_flush2(fs, 0);
+ fuse4fs_flush(ff, 0);
ff->opstate = F4OP_SHUTDOWN;
fs->flags &= ~EXT2_FLAG_RW;
@@ -6669,7 +6742,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse4fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -6705,7 +6778,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse4fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -6749,7 +6822,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse4fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -8146,6 +8219,7 @@
FUSE4FS_CACHE_SIZE,
FUSE4FS_DIRSYNC,
FUSE4FS_ERRORS_BEHAVIOR,
+ FUSE4FS_FLUSH_INTERVAL,
#ifdef HAVE_FUSE_IOMAP
FUSE4FS_IOMAP,
FUSE4FS_IOMAP_PASSTHROUGH,
@@ -8174,6 +8248,7 @@
#ifdef HAVE_CLOCK_MONOTONIC
FUSE4FS_OPT("timing", timing, 1),
#endif
+ FUSE_OPT_KEY("flush_interval=%s", FUSE4FS_FLUSH_INTERVAL),
#ifdef HAVE_FUSE_IOMAP
FUSE4FS_OPT("iomap_cache", iomap_cache, 1),
FUSE4FS_OPT("noiomap_cache", iomap_cache, 0),
@@ -8257,6 +8332,21 @@
/* do not pass through to libfuse */
return 0;
+ case FUSE4FS_FLUSH_INTERVAL:
+ char *p;
+ unsigned long val;
+
+ errno = 0;
+ val = strtoul(arg + 15, &p, 0);
+ if (p != arg + strlen(arg) || errno || val > UINT_MAX) {
+ fprintf(stderr, "%s: %s.\n", arg,
+ _("Unrecognized flush interval"));
+ return -1;
+ }
+
+ /* do not pass through to libfuse */
+ ff->flush_interval = val;
+ return 0;
#ifdef HAVE_FUSE_IOMAP
case FUSE4FS_IOMAP:
if (strcmp(arg, "iomap") == 0 || strcmp(arg + 6, "1") == 0)
@@ -8304,6 +8394,7 @@
#ifdef HAVE_FUSE_IOMAP
" -o iomap= 0 to disable iomap, 1 to enable iomap\n"
#endif
+ " -o flush=<time> flush dirty metadata on this interval\n"
"\n",
outargs->argv[0]);
if (key == FUSE4FS_HELPFULL) {
@@ -8586,6 +8677,7 @@
#endif
.translate_inums = 1,
.write_gdt_on_destroy = 1,
+ .flush_interval = 30,
#ifdef HAVE_FUSE_SERVICE
.bdev_fd = -1,
.fusedev_fd = -1,
@@ -8762,6 +8854,7 @@
_("Mount failed while opening filesystem. Check dmesg(1) for details."));
fflush(orig_stderr);
}
+ fuse4fs_flush_destroy(&fctx);
fuse4fs_psi_destroy(&fctx);
fuse4fs_mmp_destroy(&fctx);
fuse4fs_unmount(&fctx);
@@ -8940,6 +9033,7 @@
_("Remounting read-only due to errors."));
ff->opstate = F4OP_READONLY;
}
+ fuse4fs_flush_cancel(ff);
fuse4fs_mmp_cancel(ff);
fs->flags &= ~EXT2_FLAG_RW;
break;
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 7af7ccc..8e646f2 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -26,6 +26,7 @@
#include <sys/sysmacros.h>
#include <unistd.h>
#include <ctype.h>
+#include <limits.h>
#ifdef HAVE_FUSE_LOOPDEV
# include <fuse_loopdev.h>
#endif
@@ -310,6 +311,10 @@
#endif
struct psi *mem_psi;
struct psi_handler *mem_psi_handler;
+
+ struct bthread *flush_thread;
+ unsigned int flush_interval;
+ double last_flush;
};
#define FUSE2FS_CHECK_HANDLE(ff, fh) \
@@ -813,6 +818,71 @@
fp->fh = (uintptr_t)fh;
}
+static errcode_t fuse2fs_flush(struct fuse2fs *ff, int flags)
+{
+ double last_flush = gettime_monotonic();
+ errcode_t err;
+
+ err = ext2fs_flush2(ff->fs, flags);
+ if (err)
+ return err;
+
+ ff->last_flush = last_flush;
+ return 0;
+}
+
+static inline int fuse2fs_flush_wanted(struct fuse2fs *ff)
+{
+ return ff->fs != NULL && ff->opstate == F2OP_WRITABLE &&
+ ff->last_flush + ff->flush_interval <= gettime_monotonic();
+}
+
+static void fuse2fs_flush_bthread(void *data)
+{
+ struct fuse2fs *ff = data;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ fs = fuse2fs_start(ff);
+ if (fuse2fs_flush_wanted(ff) && !bthread_cancelled(ff->flush_thread)) {
+ err = fuse2fs_flush(ff, 0);
+ if (err)
+ ret = translate_error(fs, 0, err);
+ }
+ fuse2fs_finish(ff, ret);
+}
+
+static void fuse2fs_flush_start(struct fuse2fs *ff)
+{
+ int ret;
+
+ if (!ff->flush_interval)
+ return;
+
+ ret = bthread_create("fuse2fs_flush", fuse2fs_flush_bthread, ff,
+ ff->flush_interval, &ff->flush_thread);
+ if (ret) {
+ err_printf(ff, "flusher: %s.\n", error_message(ret));
+ return;
+ }
+
+ ret = bthread_start(ff->flush_thread);
+ if (ret)
+ err_printf(ff, "flusher: %s.\n", error_message(ret));
+}
+
+static void fuse2fs_flush_cancel(struct fuse2fs *ff)
+{
+ if (ff->flush_thread)
+ bthread_cancel(ff->flush_thread);
+}
+
+static void fuse2fs_flush_destroy(struct fuse2fs *ff)
+{
+ bthread_destroy(&ff->flush_thread);
+}
+
#ifdef HAVE_FUSE_IOMAP
static inline int fuse2fs_iomap_enabled(const struct fuse2fs *ff)
{
@@ -1730,7 +1800,7 @@
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);
+ err = fuse2fs_flush(ff, 0);
if (err)
return translate_error(fs, 0, err);
}
@@ -1760,7 +1830,7 @@
translate_error(fs, 0, err);
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err)
translate_error(fs, 0, err);
}
@@ -1783,6 +1853,7 @@
* that the block device will be released before umount(2) returns.
*/
if (ff->iomap_state == IOMAP_ENABLED) {
+ fuse2fs_flush_cancel(ff);
fuse2fs_mmp_cancel(ff);
fuse2fs_unmount(ff);
}
@@ -2009,6 +2080,7 @@
*/
fuse2fs_mmp_start(ff);
fuse2fs_psi_start(ff);
+ fuse2fs_flush_start(ff);
#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
/*
@@ -2539,7 +2611,7 @@
*flushed = 0;
return 0;
flush:
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err)
return translate_error(fs, 0, err);
@@ -4338,7 +4410,7 @@
if ((fp->flags & O_SYNC) &&
fuse2fs_is_writeable(ff) &&
(fh->open_flags & EXT2_FILE_WRITE)) {
- err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+ err = fuse2fs_flush(ff, EXT2_FLAG_FLUSH_NO_SYNC);
if (err)
ret = translate_error(fs, fh->ino, err);
}
@@ -4367,7 +4439,7 @@
fs = fuse2fs_start(ff);
/* For now, flush everything, even if it's slow */
if (fuse2fs_is_writeable(ff) && fh->open_flags & EXT2_FILE_WRITE) {
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err)
ret = translate_error(fs, fh->ino, err);
}
@@ -5477,6 +5549,7 @@
err_printf(ff, "%s.\n", _("shut down requested"));
+ fuse2fs_flush_cancel(ff);
fuse2fs_mmp_cancel(ff);
/*
@@ -5485,7 +5558,7 @@
* any of the flags. Flush whatever is dirty and shut down.
*/
if (ff->opstate == F2OP_WRITABLE)
- ext2fs_flush2(fs, 0);
+ fuse2fs_flush(ff, 0);
ff->opstate = F2OP_SHUTDOWN;
fs->flags &= ~EXT2_FLAG_RW;
@@ -5883,7 +5956,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -5918,7 +5991,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -5960,7 +6033,7 @@
goto out_unlock;
}
- err = ext2fs_flush2(fs, 0);
+ err = fuse2fs_flush(ff, 0);
if (err) {
ret = translate_error(fs, 0, err);
goto out_unlock;
@@ -7339,6 +7412,7 @@
FUSE2FS_CACHE_SIZE,
FUSE2FS_DIRSYNC,
FUSE2FS_ERRORS_BEHAVIOR,
+ FUSE2FS_FLUSH_INTERVAL,
#ifdef HAVE_FUSE_IOMAP
FUSE2FS_IOMAP,
FUSE2FS_IOMAP_PASSTHROUGH,
@@ -7367,6 +7441,7 @@
#ifdef HAVE_CLOCK_MONOTONIC
FUSE2FS_OPT("timing", timing, 1),
#endif
+ FUSE_OPT_KEY("flush_interval=%s", FUSE2FS_FLUSH_INTERVAL),
#ifdef HAVE_FUSE_IOMAP
FUSE2FS_OPT("iomap_cache", iomap_cache, 1),
FUSE2FS_OPT("noiomap_cache", iomap_cache, 0),
@@ -7450,6 +7525,21 @@
/* do not pass through to libfuse */
return 0;
+ case FUSE2FS_FLUSH_INTERVAL:
+ char *p;
+ unsigned long val;
+
+ errno = 0;
+ val = strtoul(arg + 15, &p, 0);
+ if (p != arg + strlen(arg) || errno || val > UINT_MAX) {
+ fprintf(stderr, "%s: %s.\n", arg,
+ _("Unrecognized flush interval"));
+ return -1;
+ }
+
+ /* do not pass through to libfuse */
+ ff->flush_interval = val;
+ return 0;
#ifdef HAVE_FUSE_IOMAP
case FUSE2FS_IOMAP:
if (strcmp(arg, "iomap") == 0 || strcmp(arg + 6, "1") == 0)
@@ -7497,6 +7587,7 @@
#ifdef HAVE_FUSE_IOMAP
" -o iomap= 0 to disable iomap, 1 to enable iomap\n"
#endif
+ " -o flush=<time> flush dirty metadata on this interval\n"
"\n",
outargs->argv[0]);
if (key == FUSE2FS_HELPFULL) {
@@ -7657,6 +7748,7 @@
.loop_fd = -1,
#endif
.write_gdt_on_destroy = 1,
+ .flush_interval = 30,
};
errcode_t err;
FILE *orig_stderr = stderr;
@@ -7790,6 +7882,7 @@
_("Mount failed while opening filesystem. Check dmesg(1) for details."));
fflush(orig_stderr);
}
+ fuse2fs_flush_destroy(&fctx);
fuse2fs_psi_destroy(&fctx);
fuse2fs_mmp_destroy(&fctx);
fuse2fs_unmount(&fctx);
@@ -7967,6 +8060,7 @@
_("Remounting read-only due to errors."));
ff->opstate = F2OP_READONLY;
}
+ fuse2fs_flush_cancel(ff);
fuse2fs_mmp_cancel(ff);
fs->flags &= ~EXT2_FLAG_RW;
break;