Linux-mtd Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org, Jan Kara <jack@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>,
	ecryptfs@vger.kernel.org,  linux-ext4@vger.kernel.org,
	jfs-discussion@lists.sourceforge.net,
	 linux-nilfs@vger.kernel.org, exfat@lists.linux.dev,
	linux-mm@kvack.org,  ntfs@lists.linux.dev,
	ocfs2-devel@lists.linux.dev,  linux-btrfs@vger.kernel.org,
	gfs2@lists.linux.dev,  linux-efi@vger.kernel.org,
	linux-mtd@lists.infradead.org,  linux-xfs@vger.kernel.org,
	 "Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH v2 32/34] selftests/filesystems: test ext4 remounts in the emergency state
Date: Thu, 29 Oct 2026 17:07:23 +0100	[thread overview]
Message-ID: <20261029-work-fs-sb_rdonly-v2-32-2f44f64f0b66@kernel.org> (raw)
In-Reply-To: <20261029-work-fs-sb_rdonly-v2-0-2f44f64f0b66@kernel.org>

Add tests for remounting ext4 after FS_IOC_SHUTDOWN with the default and
the nologflush flags and after an error with and without a journal:

- option-only remount doesn't make it read-only, the error state alone
  reports ST_RDONLY
- remount to read-only works and writes nothing
- remount to read-write fails with EROFS

The tests need root, loop devices and mkfs.ext4.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 .../selftests/filesystems/sb_rdonly/.gitignore     |   1 +
 .../selftests/filesystems/sb_rdonly/Makefile       |   2 +-
 .../testing/selftests/filesystems/sb_rdonly/config |   1 +
 .../sb_rdonly/ext4_emergency_remount_test.c        | 305 +++++++++++++++++++++
 4 files changed, 308 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/filesystems/sb_rdonly/.gitignore b/tools/testing/selftests/filesystems/sb_rdonly/.gitignore
index 96daf47a494f..7445dbe15229 100644
--- a/tools/testing/selftests/filesystems/sb_rdonly/.gitignore
+++ b/tools/testing/selftests/filesystems/sb_rdonly/.gitignore
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 emergency_ro_test
 gfs2_withdraw_test
+ext4_emergency_remount_test
diff --git a/tools/testing/selftests/filesystems/sb_rdonly/Makefile b/tools/testing/selftests/filesystems/sb_rdonly/Makefile
index 89d72fcf8600..acd1d70d0fe2 100644
--- a/tools/testing/selftests/filesystems/sb_rdonly/Makefile
+++ b/tools/testing/selftests/filesystems/sb_rdonly/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-TEST_GEN_PROGS := emergency_ro_test gfs2_withdraw_test
+TEST_GEN_PROGS := emergency_ro_test gfs2_withdraw_test ext4_emergency_remount_test
 
 CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
 
diff --git a/tools/testing/selftests/filesystems/sb_rdonly/config b/tools/testing/selftests/filesystems/sb_rdonly/config
index ab660b588e6d..d98bd667314b 100644
--- a/tools/testing/selftests/filesystems/sb_rdonly/config
+++ b/tools/testing/selftests/filesystems/sb_rdonly/config
@@ -1,5 +1,6 @@
 CONFIG_BLK_DEV_LOOP=y
 CONFIG_EXT2_FS=y
+CONFIG_EXT4_FS=y
 CONFIG_GFS2_FS=y
 CONFIG_JFS_FS=y
 CONFIG_NILFS2_FS=y
diff --git a/tools/testing/selftests/filesystems/sb_rdonly/ext4_emergency_remount_test.c b/tools/testing/selftests/filesystems/sb_rdonly/ext4_emergency_remount_test.c
new file mode 100644
index 000000000000..5c541bb68ef7
--- /dev/null
+++ b/tools/testing/selftests/filesystems/sb_rdonly/ext4_emergency_remount_test.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ext4 refused every change of the read-only state in its emergency state and
+ * emergency remount (sysrq u) WARNed. Check that after a shutdown or an error
+ * a remount can go read-only without writing anything, not back to read-write,
+ * and that option-only remounts work. sysrq u itself can't be tested here, the
+ * plain remount goes through the same path minus SB_FORCE.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <linux/loop.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+
+#include "../../kselftest_harness.h"
+
+#define IMG_SIZE	(128 << 20)
+
+#ifndef FS_IOC_SHUTDOWN
+#define FS_IOC_SHUTDOWN _IOR('X', 125, __u32)
+#endif
+#ifndef FS_SHUTDOWN_FLAGS_DEFAULT
+#define FS_SHUTDOWN_FLAGS_DEFAULT 0x0
+#endif
+#ifndef FS_SHUTDOWN_FLAGS_NOLOGFLUSH
+#define FS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2
+#endif
+
+enum how { HOW_SHUTDOWN, HOW_ERROR };
+
+FIXTURE(ext4_emergency) {
+	char dir[64];
+	char img[96];
+	char mnt[96];
+	char dev[32];
+	int loop;
+	int loop_fd;
+	bool mounted;
+};
+
+FIXTURE_VARIANT(ext4_emergency) {
+	const char *mkfs;
+	enum how how;
+	__u32 shutdown_flags;
+	const char *state;	/* the state option shown in the mount options */
+};
+
+FIXTURE_VARIANT_ADD(ext4_emergency, shutdown) {
+	.mkfs = "mkfs.ext4 -q -F -b 4096",
+	.how = HOW_SHUTDOWN,
+	.shutdown_flags = FS_SHUTDOWN_FLAGS_NOLOGFLUSH,
+	.state = "shutdown",
+};
+
+/* the journal stays live, going read-only must not write it */
+FIXTURE_VARIANT_ADD(ext4_emergency, shutdown_default) {
+	.mkfs = "mkfs.ext4 -q -F -b 4096",
+	.how = HOW_SHUTDOWN,
+	.shutdown_flags = FS_SHUTDOWN_FLAGS_DEFAULT,
+	.state = "shutdown",
+};
+
+FIXTURE_VARIANT_ADD(ext4_emergency, error) {
+	.mkfs = "mkfs.ext4 -q -F -b 4096",
+	.how = HOW_ERROR,
+	.state = "emergency_ro",
+};
+
+FIXTURE_VARIANT_ADD(ext4_emergency, error_nojournal) {
+	.mkfs = "mkfs.ext4 -q -F -b 4096 -O ^has_journal",
+	.how = HOW_ERROR,
+	.state = "emergency_ro",
+};
+
+static bool mount_option(const char *mnt, const char *opt)
+{
+	char line[4096], needle[128];
+	bool found = false;
+	FILE *f;
+
+	f = fopen("/proc/self/mountinfo", "r");
+	if (!f)
+		return false;
+	snprintf(needle, sizeof(needle), " %s ", mnt);
+	while (fgets(line, sizeof(line), f)) {
+		char *super;
+
+		if (!strstr(line, needle))
+			continue;
+		/* the superblock options follow the separator */
+		super = strstr(line, " - ");
+		if (super && strstr(super, opt))
+			found = true;
+	}
+	fclose(f);
+	return found;
+}
+
+/* The superblock options start with its own "ro" or "rw". */
+static bool super_rdonly(const char *mnt)
+{
+	char line[4096], needle[128], fstype[64], source[256], opts[2048];
+	bool ro = false;
+	FILE *f;
+
+	f = fopen("/proc/self/mountinfo", "r");
+	if (!f)
+		return false;
+	snprintf(needle, sizeof(needle), " %s ", mnt);
+	while (fgets(line, sizeof(line), f)) {
+		char *super;
+
+		if (!strstr(line, needle))
+			continue;
+		super = strstr(line, " - ");
+		if (super && sscanf(super, " - %63s %255s %2047s",
+				    fstype, source, opts) == 3)
+			ro = !strncmp(opts, "ro", 2) && (opts[2] == ',' || !opts[2]);
+	}
+	fclose(f);
+	return ro;
+}
+
+/* Sectors written to the loop device so far, -1 on error. */
+static long long written(int loop)
+{
+	unsigned long long v[7];
+	char path[64];
+	FILE *f;
+	int n;
+
+	snprintf(path, sizeof(path), "/sys/block/loop%d/stat", loop);
+	f = fopen(path, "r");
+	if (!f)
+		return -1;
+	n = fscanf(f, "%llu %llu %llu %llu %llu %llu %llu",
+		   &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6]);
+	fclose(f);
+	return n == 7 ? (long long)v[6] : -1;
+}
+
+static int enter_emergency(FIXTURE_DATA(ext4_emergency) *self,
+			   const FIXTURE_VARIANT(ext4_emergency) *variant)
+{
+	char path[128];
+	int fd, ret;
+
+	if (variant->how == HOW_SHUTDOWN) {
+		__u32 flags = variant->shutdown_flags;
+
+		fd = open(self->mnt, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+		if (fd < 0)
+			return -1;
+		ret = ioctl(fd, FS_IOC_SHUTDOWN, &flags);
+		close(fd);
+		return ret;
+	}
+
+	/* ext4_error() through the test knob, errors=remount-ro reacts */
+	snprintf(path, sizeof(path), "/sys/fs/ext4/loop%d/trigger_fs_error", self->loop);
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -1;
+	ret = write(fd, "selftest", 8) == 8 ? 0 : -1;
+	close(fd);
+	return ret;
+}
+
+static void ext4_emergency_cleanup(FIXTURE_DATA(ext4_emergency) *self)
+{
+	if (self->mounted)
+		umount2(self->mnt, MNT_DETACH);
+	if (self->loop_fd >= 0)
+		close(self->loop_fd);	/* LO_FLAGS_AUTOCLEAR detaches it */
+	unlink(self->img);
+	rmdir(self->mnt);
+	rmdir(self->dir);
+}
+
+FIXTURE_SETUP(ext4_emergency)
+{
+	struct loop_config cfg = { .info.lo_flags = LO_FLAGS_AUTOCLEAR };
+	char cmd[256], path[128];
+	int ctl, fd, ret;
+
+	self->loop = -1;
+	self->loop_fd = -1;
+	self->mounted = false;
+
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+	if (access("/dev/loop-control", R_OK | W_OK))
+		SKIP(return, "test requires loop devices");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(self->dir, sizeof(self->dir), "/tmp/ext4_emergency.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	snprintf(self->img, sizeof(self->img), "%s/img", self->dir);
+	snprintf(self->mnt, sizeof(self->mnt), "%s/mnt", self->dir);
+	ASSERT_EQ(mkdir(self->mnt, 0700), 0);
+
+	fd = open(self->img, O_RDWR | O_CREAT | O_CLOEXEC, 0600);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(ftruncate(fd, IMG_SIZE), 0);
+	snprintf(cmd, sizeof(cmd), "%s %s >/dev/null 2>&1", variant->mkfs, self->img);
+	if (system(cmd)) {
+		close(fd);
+		ext4_emergency_cleanup(self);
+		SKIP(return, "mkfs.ext4 failed or is not available");
+	}
+
+	ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
+	ASSERT_GE(ctl, 0);
+	cfg.fd = fd;
+	/* another user can grab the free device first */
+	for (int tries = 0;; tries++) {
+		self->loop = ioctl(ctl, LOOP_CTL_GET_FREE);
+		ASSERT_GE(self->loop, 0);
+		snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", self->loop);
+		self->loop_fd = open(self->dev, O_RDWR | O_CLOEXEC);
+		ASSERT_GE(self->loop_fd, 0);
+		if (!ioctl(self->loop_fd, LOOP_CONFIGURE, &cfg))
+			break;
+		ASSERT_EQ(errno, EBUSY);
+		ASSERT_LT(tries, 10);
+		close(self->loop_fd);
+		self->loop_fd = -1;
+	}
+	close(ctl);
+	close(fd);
+
+	ret = mount(self->dev, self->mnt, "ext4", 0, "errors=remount-ro");
+	if (ret && errno == ENODEV) {
+		ext4_emergency_cleanup(self);
+		SKIP(return, "kernel lacks ext4");
+	}
+	ASSERT_EQ(ret, 0);
+	self->mounted = true;
+
+	snprintf(path, sizeof(path), "%s/f", self->mnt);
+	fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(write(fd, "data\n", 5), 5);
+	ASSERT_EQ(fsync(fd), 0);
+	close(fd);
+}
+
+FIXTURE_TEARDOWN(ext4_emergency)
+{
+	ext4_emergency_cleanup(self);
+}
+
+TEST_F_TIMEOUT(ext4_emergency, remount, 60)
+{
+	long long w0, w1;
+	struct statvfs sv;
+	int ret;
+
+	ASSERT_EQ(enter_emergency(self, variant), 0);
+	ASSERT_TRUE(mount_option(self->mnt, variant->state));
+
+	/* nothing below may write, get whatever is still dirty out first */
+	sync();
+	w0 = written(self->loop);
+	ASSERT_GE(w0, 0);
+
+	/* only options change, the error state alone reports ST_RDONLY */
+	ASSERT_EQ(mount(NULL, self->mnt, NULL, MS_REMOUNT, "errors=remount-ro"), 0);
+	ASSERT_EQ(statvfs(self->mnt, &sv), 0);
+	ASSERT_EQ(!!(sv.f_flag & ST_RDONLY), variant->how == HOW_ERROR);
+
+	/* going read-only works */
+	ret = mount(NULL, self->mnt, NULL, MS_REMOUNT | MS_RDONLY, NULL);
+	ASSERT_EQ(ret, 0) TH_LOG("remount read-only: %s", strerror(errno));
+	ASSERT_TRUE(super_rdonly(self->mnt));
+	ASSERT_TRUE(mount_option(self->mnt, variant->state));
+
+	/* without writing anything */
+	w1 = written(self->loop);
+	ASSERT_EQ(w1, w0) TH_LOG("%lld sectors written by the remounts", w1 - w0);
+
+	/* going back to read-write doesn't */
+	ret = mount(NULL, self->mnt, NULL, MS_REMOUNT, NULL);
+	ASSERT_EQ(ret, -1);
+	ASSERT_EQ(errno, EROFS);
+	ASSERT_TRUE(super_rdonly(self->mnt));
+
+	ASSERT_EQ(umount(self->mnt), 0);
+	self->mounted = false;
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

  parent reply	other threads:[~2026-10-29 16:09 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 16:06 [PATCH v2 00/34] fs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:06 ` [PATCH v2 01/34] ecryptfs: don't leak the inode when a lookup can't open the lower file Christian Brauner
2026-10-29 16:06 ` [PATCH v2 02/34] ecryptfs: fall back to a read-only " Christian Brauner
2026-10-29 16:06 ` [PATCH v2 03/34] fs: add a generic emergency read-only state Christian Brauner
2026-10-29 16:06 ` [PATCH v2 04/34] ext2: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:06 ` [PATCH v2 05/34] jfs: " Christian Brauner
2026-10-29 16:06 ` [PATCH v2 06/34] nilfs2: " Christian Brauner
2026-10-29 16:06 ` [PATCH v2 07/34] fat: " Christian Brauner
2026-10-29 16:06 ` [PATCH v2 08/34] exfat: keep the current mount options on remount Christian Brauner
2026-10-29 16:07 ` [PATCH v2 09/34] exfat: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:07 ` [PATCH v2 10/34] exfat: use the generic emergency read-only state Christian Brauner
2026-10-29 16:07 ` [PATCH v2 11/34] hfs: don't set SB_RDONLY after MDB write errors Christian Brauner
2026-10-29 16:07 ` [PATCH v2 12/34] hfs: use the generic emergency read-only state Christian Brauner
2026-10-29 16:07 ` [PATCH v2 13/34] hfsplus: don't set SB_RDONLY when creating the attributes tree Christian Brauner
2026-10-29 16:07 ` [PATCH v2 14/34] affs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:07 ` [PATCH v2 15/34] ufs: " Christian Brauner
2026-10-29 16:07 ` [PATCH v2 16/34] hpfs: " Christian Brauner
2026-10-29 16:07 ` [PATCH v2 17/34] ntfs: " Christian Brauner
2026-10-29 16:07 ` [PATCH v2 18/34] ntfs: use the generic emergency read-only state Christian Brauner
2026-10-29 16:07 ` [PATCH v2 19/34] ocfs2: keep the current mount options on remount Christian Brauner
2026-10-30  2:58   ` Joseph Qi
2026-10-29 16:07 ` [PATCH v2 20/34] ocfs2: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30  3:01   ` Joseph Qi
2026-10-29 16:07 ` [PATCH v2 21/34] zonefs: don't set SB_RDONLY after I/O errors Christian Brauner
2026-10-29 16:07 ` [PATCH v2 22/34] zonefs: use the generic emergency read-only state Christian Brauner
2026-10-29 16:07 ` [PATCH v2 23/34] btrfs: check BTRFS_FS_STATE_RO instead of SB_RDONLY at runtime Christian Brauner
2026-10-29 16:07 ` [PATCH v2 24/34] btrfs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:07 ` [PATCH v2 25/34] gfs2: wait for a pending withdraw before remounting read-only Christian Brauner
2026-10-29 16:07 ` [PATCH v2 26/34] gfs2: don't set SB_RDONLY on withdraw Christian Brauner
2026-10-29 16:07 ` [PATCH v2 27/34] efivarfs: stop using SB_RDONLY for read-only efivar ops Christian Brauner
2026-10-29 16:07 ` [PATCH v2 28/34] ubifs: don't set SB_RDONLY after errors Christian Brauner
2026-10-30  3:42   ` Zhihao Cheng
2026-10-29 16:07 ` [PATCH v2 29/34] selftests/filesystems: test filesystem errors while frozen Christian Brauner
2026-10-29 16:07 ` [PATCH v2 30/34] ext4: allow remounting read-only in the emergency state Christian Brauner
2026-10-29 16:07 ` [PATCH v2 31/34] ext4: use the generic emergency read-only state Christian Brauner
2026-10-29 16:07 ` Christian Brauner [this message]
2026-10-29 16:07 ` [PATCH v2 33/34] xfs: allow remounting read-only after a shutdown Christian Brauner
2026-10-29 16:07 ` [PATCH v2 34/34] selftests/filesystems: test xfs remounts " Christian Brauner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261029-work-fs-sb_rdonly-v2-32-2f44f64f0b66@kernel.org \
    --to=brauner@kernel.org \
    --cc=ecryptfs@vger.kernel.org \
    --cc=exfat@lists.linux.dev \
    --cc=gfs2@lists.linux.dev \
    --cc=jack@suse.com \
    --cc=jfs-discussion@lists.sourceforge.net \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=linux-efi@vger.kernel.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-nilfs@vger.kernel.org \
    --cc=linux-xfs@vger.kernel.org \
    --cc=ntfs@lists.linux.dev \
    --cc=ocfs2-devel@lists.linux.dev \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox