[PATCH 06/28] fuse: flush events and send FUSE_SYNCFS and FUSE_DESTROY on unmount

Darrick J. Wong
2025-09-16T00:29:32Z

At unmount time, there are a few things that we need to ask the fuse
server to do.

First, we need to flush queued events to userspace to give the fuse
server a chance to process the events. This is how we make sure that
the server processes FUSE_RELEASE events before the connection goes
down.

Second, to ensure that all those metadata updates are persisted to disk
before tell the fuse server to destroy itself, send FUSE_SYNCFS after
waiting for the queued events.

Finally, we need to send FUSE_DESTROY to the fuse server so that it
closes the filesystem and the device fds before unmount returns. That
way, a script that does something like "umount /dev/sda ; e2fsck -fn
/dev/sda" will not fail the e2fsck because the fd closure races with
e2fsck startup. Obviously, we need to wait for FUSE_SYNCFS.

This is a major behavior change and who knows what might break existing
code, so we hide it behind iomap mode.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/fuse/fuse_i.h | 8 ++++++++
fs/fuse/file_iomap.c | 29 +++++++++++++++++++++++++++++
fs/fuse/inode.c | 9 +++++++--
3 files changed, 44 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 791f210c13a876..3cda9bc6af23fe 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1432,6 +1432,9 @@ int fuse_init_fs_context_submount(struct fs_context *fsc);
*/
void fuse_conn_destroy(struct fuse_mount *fm);

+/* Send the FUSE_DESTROY command. */
+void fuse_send_destroy(struct fuse_mount *fm);
+
/* Drop the connection and free the fuse mount */
void fuse_mount_destroy(struct fuse_mount *fm);

@@ -1709,9 +1712,14 @@ static inline bool fuse_has_iomap(const struct inode *inode)
}

extern const struct fuse_backing_ops fuse_iomap_backing_ops;
+
+void fuse_iomap_mount(struct fuse_mount *fm);
+void fuse_iomap_unmount(struct fuse_mount *fm);
#else
# define fuse_iomap_enabled(...) (false)
# define fuse_has_iomap(...) (false)
+# define fuse_iomap_mount(...) ((void)0)
+# define fuse_iomap_unmount(...) ((void)0)
#endif

#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c
index 3a4161633add0e..75e6f668baa9ef 100644
--- a/fs/fuse/file_iomap.c
+++ b/fs/fuse/file_iomap.c
@@ -604,3 +604,32 @@ const struct fuse_backing_ops fuse_iomap_backing_ops = {
.may_close = fuse_iomap_may_close,
.post_open = fuse_iomap_post_open,
};
+
+void fuse_iomap_mount(struct fuse_mount *fm)
+{
+ struct fuse_conn *fc = fm->fc;
+
+ /*
+ * Enable syncfs for iomap fuse servers so that we can send a final
+ * flush at unmount time. This also means that we can support
+ * freeze/thaw properly.
+ */
+ fc->sync_fs = true;
+}
+
+void fuse_iomap_unmount(struct fuse_mount *fm)
+{
+ struct fuse_conn *fc = fm->fc;
+
+ /*
+ * Flush all pending commands, then issue a syncfs, flush the syncfs,
+ * and send a destroy command. This gives the fuse server a chance to
+ * process all the pending releases, write the last bits of metadata
+ * changes to disk, and close the iomap block devices before we return
+ * from the umount call.
+ */
+ fuse_flush_requests_and_wait(fc);
+ sync_filesystem(fm->sb);
+ fuse_flush_requests_and_wait(fc);
+ fuse_send_destroy(fm);
+}
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 0d39e1dcec308d..7cb1426ca3e767 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -623,7 +623,7 @@ static void fuse_umount_begin(struct super_block *sb)
retire_super(sb);
}

-static void fuse_send_destroy(struct fuse_mount *fm)
+void fuse_send_destroy(struct fuse_mount *fm)
{
if (fm->fc->conn_init) {
FUSE_ARGS(args);
@@ -1463,6 +1463,9 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,

init_server_timeout(fc, timeout);

+ if (fc->iomap)
+ fuse_iomap_mount(fm);
+
fm->sb->s_bdi->ra_pages =
min(fm->sb->s_bdi->ra_pages, ra_pages);
fc->minor = arg->minor;
@@ -2101,7 +2104,9 @@ void fuse_conn_destroy(struct fuse_mount *fm)
{
struct fuse_conn *fc = fm->fc;

- if (fc->destroy) {
+ if (fc->iomap) {
+ fuse_iomap_unmount(fm);
+ } else if (fc->destroy) {
/*
* Flush all pending requests (most of which will be
* FUSE_RELEASE) before sending FUSE_DESTROY, because the fuse