[PATCH 3/3] fuse2fs: enable the shutdown ioctl

Darrick J. Wong
2025-11-06T22:42:00Z

Implement a bastardized version of EXT4_IOC_SHUTDOWN, because the people
who invented the ioctl got the direction wrong, so we can't actually
read the flags. So all we do is flush the filesystem, clear the
writable flags, and hope that's what the user wanted.

Since we change __FUSE2FS_CHECK_CONTEXT to return an error code on a
shut down filesystem, remove FUSE2FS_CHECK_CONTEXT_RETURN so that
op_destroy always tears everything down and logs the unmount message.
Later on in the iomap patchset this will become critical because we need
to try to close the block device before unmount completes to avoid
problems with fstests.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
misc/fuse2fs.c | 45 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 41 insertions(+), 4 deletions(-)

diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 8d5b705280b72f..51e6b3b1969d62 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -222,6 +222,7 @@ struct fuse2fs_file_handle {
enum fuse2fs_opstate {
F2OP_READONLY,
F2OP_WRITABLE,
+ F2OP_SHUTDOWN,
};

/* Main program context */
@@ -280,7 +281,7 @@ struct fuse2fs {
} \
} while (0)

-#define __FUSE2FS_CHECK_CONTEXT(ff, retcode) \
+#define __FUSE2FS_CHECK_CONTEXT(ff, retcode, shutcode) \
do { \
if ((ff) == NULL || (ff)->magic != FUSE2FS_MAGIC) { \
fprintf(stderr, \
@@ -289,14 +290,17 @@ struct fuse2fs {
fflush(stderr); \
retcode; \
} \
+ if ((ff)->opstate == F2OP_SHUTDOWN) { \
+ shutcode; \
+ } \
} while (0)

#define FUSE2FS_CHECK_CONTEXT(ff) \
- __FUSE2FS_CHECK_CONTEXT((ff), return -EUCLEAN)
+ __FUSE2FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
#define FUSE2FS_CHECK_CONTEXT_DESTROY(ff) \
- __FUSE2FS_CHECK_CONTEXT((ff), return)
+ __FUSE2FS_CHECK_CONTEXT((ff), return, /* do not return */)
#define FUSE2FS_CHECK_CONTEXT_INIT(ff) \
- __FUSE2FS_CHECK_CONTEXT((ff), abort())
+ __FUSE2FS_CHECK_CONTEXT((ff), abort(), abort())

static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
const char *func, int line);
@@ -4863,6 +4867,36 @@ static int ioctl_fitrim(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
}
#endif /* FITRIM */

+#ifndef EXT4_IOC_SHUTDOWN
+# define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)
+#endif
+
+static int ioctl_shutdown(struct fuse2fs *ff, struct fuse2fs_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"));
+
+ fuse2fs_mmp_cancel(ff);
+
+ /*
+ * EXT4_IOC_SHUTDOWN inherited the inverted polarity on the ioctl
+ * direction from XFS. Unfortunately, that means we can't implement
+ * any of the flags. Flush whatever is dirty and shut down.
+ */
+ if (ff->opstate == F2OP_WRITABLE)
+ ext2fs_flush2(fs, 0);
+ ff->opstate = F2OP_SHUTDOWN;
+ fs->flags &= ~EXT2_FLAG_RW;
+
+ 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)
@@ -4909,6 +4943,9 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
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;