* [PATCH RFC fstests] generic: add a test for multigrain timestamps
@ 2026-10-27 13:58 Jeff Layton
0 siblings, 0 replies; only message in thread
From: Jeff Layton @ 2026-10-27 13:58 UTC (permalink / raw)
To: Zorro Lang; +Cc: fstests, linux-fsdevel, Jeff Layton
Ensure that the mtime and ctime apparently change, even when there are
multiple writes in quick succession. Since this is opt-in for in-kernel
filesystems and there is no way to test for this feature in userland,
limit it to just ext4, f2fs, btrfs, tmpfs and xfs for now.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
I'd like to start testing this to make sure it doesn't break, but it's
not universally supported on all filesystems. Is an explicit allowlist
like this the right approach?
---
src/Makefile | 2 +-
src/mgtime.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++
tests/generic/999 | 35 ++++++++++++++++
tests/generic/999.out | 1 +
4 files changed, 145 insertions(+), 1 deletion(-)
diff --git a/src/Makefile b/src/Makefile
index 0ff607ae1593..07bf749a1b8c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -36,7 +36,7 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
detached_mounts_propagation ext4_resize t_readdir_3 splice2pipe \
uuid_ioctl t_snapshot_deleted_subvolume fiemap-fault min_dio_alignment \
- rw_hint btrfs_ioctl
+ rw_hint btrfs_ioctl mgtime
EXTRA_EXECS = dmerror fill2attr fill2fs fill2fs_check scaleread.sh \
btrfs_crc32c_forged_name.py popdir.pl popattr.py \
diff --git a/src/mgtime.c b/src/mgtime.c
new file mode 100644
index 000000000000..4ff7c206d31f
--- /dev/null
+++ b/src/mgtime.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Older Linux kernels always use coarse-grained timestamps, with a
+ * resolution of around 1 jiffy. Writes that are done in quick succession
+ * on those kernels apparent change to the ctime or mtime.
+ *
+ * Newer kernels attempt to ensure that fine-grained timestamps are used
+ * when the mtime or ctime are queried from userland.
+ *
+ * Open a file and do a 1 byte write to it and then statx the mtime and ctime.
+ * Do that in a loop 1000 times and ensure that the value is different from
+ * the last.
+ *
+ * Copyright (c) 2023: Jeff Layton <jlayton@kernel.org>
+ */
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdbool.h>
+
+#define NUM_WRITES 1000
+
+static int64_t statx_ts_cmp(struct statx_timestamp *ts1, struct statx_timestamp *ts2)
+{
+ int64_t ret = ts2->tv_sec - ts1->tv_sec;
+
+ if (ret)
+ return ret;
+
+ ret = ts2->tv_nsec;
+ ret -= ts1->tv_nsec;
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int ret, fd, i;
+ struct statx stx = { };
+ struct statx_timestamp ctime, mtime;
+ bool verbose = false;
+
+ while ((i = getopt(argc, argv, "v")) != -1) {
+ switch (i) {
+ case 'v':
+ verbose = true;
+ break;
+ }
+ }
+
+ if (argc < 2) {
+ errno = -EINVAL;
+ perror("usage");
+ }
+
+ fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+
+ ret = statx(fd, "", AT_EMPTY_PATH, STATX_MTIME|STATX_CTIME, &stx);
+ if (ret < 0) {
+ perror("stat");
+ return 1;
+ }
+
+ ctime = stx.stx_ctime;
+ mtime = stx.stx_mtime;
+
+ for (i = 0; i < NUM_WRITES; ++i) {
+ ssize_t written;
+
+ written = write(fd, "a", 1);
+ if (written < 0) {
+ perror("write");
+ return 1;
+ }
+
+ ret = statx(fd, "", AT_EMPTY_PATH, STATX_MTIME|STATX_CTIME, &stx);
+ if (ret < 0) {
+ perror("stat");
+ return 1;
+ }
+
+ if (verbose)
+ printf("%d: %llu.%u\n", i, stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec);
+
+ if (!statx_ts_cmp(&ctime, &stx.stx_ctime)) {
+ printf("Duplicate ctime after write!\n");
+ return 1;
+ }
+
+ if (!statx_ts_cmp(&mtime, &stx.stx_mtime)) {
+ printf("Duplicate mtime after write!\n");
+ return 1;
+ }
+
+ ctime = stx.stx_ctime;
+ mtime = stx.stx_mtime;
+ }
+ return 0;
+}
diff --git a/tests/generic/999 b/tests/generic/999
new file mode 100755
index 000000000000..878247515e71
--- /dev/null
+++ b/tests/generic/999
@@ -0,0 +1,35 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026, Jeff Layton <jlayton@kernel.org>
+#
+# FS QA Test No. 999
+#
+# Multigrain time test
+#
+# Open a file, and do 1 byte writes to it, and statx the file for
+# the ctime and mtime after each write. Ensure that they have changed
+# since the previous write.
+#
+. ./common/preamble
+_begin_fstest auto quick
+
+# Override the default cleanup function.
+_require_test_program mgtime
+
+# Multigrain timestamps are opt-in per filesystem; only allowlist the ones
+# known to support them.
+case "$FSTYP" in
+ext4|btrfs|tmpfs|xfs|f2fs)
+ ;;
+*)
+ _notrun "multigrain timestamps not supported on $FSTYP"
+ ;;
+esac
+
+testfile="$TEST_DIR/test_mgtime_file.$$"
+
+$here/src/mgtime $testfile
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/999.out b/tests/generic/999.out
new file mode 100644
index 000000000000..7fbc67688211
--- /dev/null
+++ b/tests/generic/999.out
@@ -0,0 +1 @@
+QA output created by 999
---
base-commit: 333c067ccf2f389f5558860336a7fa64157cd46e
change-id: 20261027-mgtime-64194b4e22b4
Best regards,
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-27 13:59 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 13:58 [PATCH RFC fstests] generic: add a test for multigrain timestamps Jeff Layton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox