[LTP] [PATCH v13] fsconfig04: Check FSCONFIG_SET_PATH
Wei Gao
wegao@suse.com
Thu Aug 13 07:28:40 CEST 2026
The fsconfig01.c does not test if FSCONFIG_SET_PATH has any effect;
most of the calls there just set a dummy "sync" parameter. This test
case aims to verify if the FSCONFIG_SET_PATH operation can be used
to dynamically change the external journal device of an ext3 or ext4
filesystem.
Closes: https://github.com/linux-test-project/ltp/issues/1169
Signed-off-by: Wei Gao <wegao@suse.com>
Reviewed-by: Petr Vorel <pvorel@suse.cz>
---
v12-v13:
- Uses bool found with standard false/true logic.
- Resolves device node numbers portably using standard SAFE_STAT and major/minor macros on st_rdev.
- Fixes dangling stack pointer bugs across loop runs.
- Moves formatting of all loop devices into run() to guarantee full test idempotency.
- Removes redundant .needs_tmpdir = 1.
runtest/syscalls | 1 +
testcases/kernel/syscalls/fsconfig/.gitignore | 1 +
.../kernel/syscalls/fsconfig/fsconfig04.c | 174 ++++++++++++++++++
3 files changed, 176 insertions(+)
create mode 100644 testcases/kernel/syscalls/fsconfig/fsconfig04.c
diff --git a/runtest/syscalls b/runtest/syscalls
index 65fb4d1ce..13bb59cde 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -437,6 +437,7 @@ fremovexattr02 fremovexattr02
fsconfig01 fsconfig01
fsconfig02 fsconfig02
fsconfig03 fsconfig03
+fsconfig04 fsconfig04
fsmount01 fsmount01
fsmount02 fsmount02
diff --git a/testcases/kernel/syscalls/fsconfig/.gitignore b/testcases/kernel/syscalls/fsconfig/.gitignore
index cfedae5f7..bd3754c34 100644
--- a/testcases/kernel/syscalls/fsconfig/.gitignore
+++ b/testcases/kernel/syscalls/fsconfig/.gitignore
@@ -1,3 +1,4 @@
/fsconfig01
/fsconfig02
/fsconfig03
+/fsconfig04
diff --git a/testcases/kernel/syscalls/fsconfig/fsconfig04.c b/testcases/kernel/syscalls/fsconfig/fsconfig04.c
new file mode 100644
index 000000000..00468ac65
--- /dev/null
+++ b/testcases/kernel/syscalls/fsconfig/fsconfig04.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Wei Gao <wegao@suse.com>
+ */
+
+/*\
+ * This test aims to validate :manpage:`fsconfig(2)` with the
+ * ``FSCONFIG_SET_PATH`` operation in dynamically altering the external
+ * journal device of an ext3 or ext4 filesystem.
+ *
+ * Root privileges are required because the test creates and formats loop
+ * devices, and configures filesystems.
+ *
+ * [Algorithm]
+ *
+ * - Acquire three loop devices (``dev0`` from framework, ``dev1``, ``dev2``).
+ * - Format ``dev1`` and ``dev2`` as external journal devices with the same UUID
+ * (``-O journal_dev -U <uuid>``).
+ * - Format ``dev0`` three times to cycle journal associations:
+ * ``dev1`` -> ``dev2`` -> ``dev1``, so both journal superblocks are consistent.
+ * - Open ``dev0`` via :manpage:`fsopen(2)` and set the source and journal_path using
+ * :manpage:`fsconfig(2)` (``FSCONFIG_SET_STRING``) and
+ * :manpage:`fsconfig(2)` (``FSCONFIG_SET_PATH``).
+ * - Apply ``FSCONFIG_CMD_CREATE`` and verify with ``tune2fs`` that ``dev0``'s
+ * journal device is now ``dev2``.
+ *
+ * Implementation notes:
+ *
+ * - To avoid ``journal UUID does not match`` error when switching external
+ * journal device, we have to assign the same UUID to ``dev1``/``dev2``.
+ * - Before the :manpage:`fsconfig(2)` test, we have to format ``dev0`` associating to
+ * ``dev1`` -> ``dev2`` -> ``dev1``. This ensures that both ``dev1``/``dev2`` superblocks contain
+ * correct content. Otherwise, you will encounter errors such as
+ * ``EXT4-fs (loop0): External journal has more than one user (unsupported)``
+ * when switching the external journal device using :manpage:`fsconfig(2)`.
+ */
+
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+#include "lapi/fsmount.h"
+
+#define MNTPOINT "mntpoint"
+#define LOOP_DEV_SIZE 10
+#define UUID "d73c9e5e-97e4-4a9c-b17e-75a931b02660"
+
+static int fd = -1;
+static char dev0[PATH_MAX];
+static char dev1[PATH_MAX];
+static char dev2[PATH_MAX];
+
+static char device_option_dev1[PATH_MAX + 16];
+static char device_option_dev2[PATH_MAX + 16];
+static const char *const mkfs_opts_journal_dev1[] = {"-F", "-J", device_option_dev1, NULL};
+static const char *const mkfs_opts_journal_dev2[] = {"-F", "-J", device_option_dev2, NULL};
+static const char *const mkfs_opts_set_UUID[] = {"-F", "-U", UUID, "-O", "journal_dev", NULL};
+
+static void cleanup(void)
+{
+ if (fd != -1)
+ SAFE_CLOSE(fd);
+
+ if (dev1[0])
+ tst_detach_device(dev1);
+
+ if (dev2[0])
+ tst_detach_device(dev2);
+}
+
+static void create_and_attach_loopdev(const char *filename, char *dev_path, size_t dev_path_len)
+{
+ if (tst_prealloc_file(filename, 1024 * 1024, LOOP_DEV_SIZE))
+ tst_brk(TBROK, "Failed to create %s", filename);
+
+ if (tst_find_free_loopdev(dev_path, dev_path_len) == -1)
+ tst_brk(TBROK, "No free loop device found for %s", filename);
+
+ if (tst_attach_device(dev_path, filename))
+ tst_brk(TBROK, "Failed to attach %s to %s", filename, dev_path);
+}
+
+static void setup(void)
+{
+ fsopen_supported_by_kernel();
+
+ strcpy(dev0, tst_device->dev);
+
+ create_and_attach_loopdev("dev1_file", dev1, sizeof(dev1));
+ create_and_attach_loopdev("dev2_file", dev2, sizeof(dev2));
+
+ snprintf(device_option_dev1, sizeof(device_option_dev1), "device=%s", dev1);
+ snprintf(device_option_dev2, sizeof(device_option_dev2), "device=%s", dev2);
+}
+
+static void run(void)
+{
+ /* Reset dev1, dev2 and dev0 superblocks before each test run iteration */
+ SAFE_MKFS(dev1, tst_device->fs_type, mkfs_opts_set_UUID, NULL);
+ SAFE_MKFS(dev2, tst_device->fs_type, mkfs_opts_set_UUID, NULL);
+ SAFE_MKFS(dev0, tst_device->fs_type, mkfs_opts_journal_dev1, NULL);
+ SAFE_MKFS(dev0, tst_device->fs_type, mkfs_opts_journal_dev2, NULL);
+ SAFE_MKFS(dev0, tst_device->fs_type, mkfs_opts_journal_dev1, NULL);
+
+ TEST(fd = fsopen(tst_device->fs_type, 0));
+ if (fd == -1)
+ tst_brk(TBROK | TTERRNO, "fsopen() failed");
+
+ TEST(fsconfig(fd, FSCONFIG_SET_STRING, "source", dev0, 0));
+ if (TST_RET == -1)
+ tst_brk(TBROK | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
+
+ TEST(fsconfig(fd, FSCONFIG_SET_PATH, "journal_path", dev2, 0));
+ if (TST_RET == -1) {
+ if (TST_ERR == EOPNOTSUPP)
+ tst_brk(TCONF, "fsconfig(FSCONFIG_SET_PATH) not supported");
+ else
+ tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_PATH) failed");
+ }
+
+ TEST(fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
+ if (TST_RET == -1)
+ tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_CMD_CREATE) failed");
+
+ char path[PATH_MAX + 32];
+ char device_str[NAME_MAX];
+ bool found = false;
+ struct stat st;
+
+ SAFE_STAT(dev2, &st);
+ unsigned int device_num = (minor(st.st_rdev) & 0xff) | (major(st.st_rdev) << 8) |
+ ((minor(st.st_rdev) & ~0xff) << 12);
+ snprintf(device_str, sizeof(device_str), "0x%04x", device_num);
+
+ char line[PATH_MAX];
+ FILE *tune2fs;
+
+ snprintf(path, sizeof(path), "tune2fs -l %s 2>&1", dev0);
+ tune2fs = SAFE_POPEN(path, "r");
+
+ while (fgets(line, PATH_MAX, tune2fs)) {
+ if (*line && strstr(line, "Journal device:") && strstr(line, device_str)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ tst_res(TPASS, "Device found in journal");
+ else
+ tst_res(TFAIL, "Device not found in journal");
+
+ pclose(tune2fs);
+ SAFE_CLOSE(fd);
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_root = 1,
+ .needs_device = 1,
+ .mntpoint = MNTPOINT,
+ .needs_cmds = (struct tst_cmd []) {
+ {.cmd = "tune2fs"},
+ {}
+ },
+ .filesystems = (struct tst_fs []) {
+ {.type = "ext3"},
+ {.type = "ext4"},
+ {}
+ },
+};
--
2.54.0
More information about the ltp
mailing list