From: David Matlack <dmatlack@google.com>
To: kexec@lists.infradead.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-mm@kvack.org
Cc: Alexander Graf <graf@amazon.com>,
Andrew Morton <akpm@linux-foundation.org>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Christian Brauner <brauner@kernel.org>,
David Hildenbrand <david@kernel.org>,
Hugh Dickins <hughd@google.com>, Jonathan Corbet <corbet@lwn.net>,
"Liam R. Howlett" <liam@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>, Michal Hocko <mhocko@suse.com>,
Mike Rapoport <rppt@kernel.org>,
Pasha Tatashin <pasha.tatashin@soleen.com>,
Pratyush Yadav <pratyush@kernel.org>,
Randy Dunlap <rdunlap@infradead.org>,
Samiullah Khawaja <skhawaja@google.com>,
Shuah Khan <skhan@linuxfoundation.org>,
Suren Baghdasaryan <surenb@google.com>,
Vlastimil Babka <vbabka@kernel.org>,
David Matlack <dmatlack@google.com>
Subject: [PATCH v4 18/19] selftests/liveupdate: Test retrieving tmpfs files preserved read-only
Date: Wed, 11 Nov 2026 00:23:56 +0000 [thread overview]
Message-ID: <20261111002358.1842967-19-dmatlack@google.com> (raw)
In-Reply-To: <20261111002358.1842967-1-dmatlack@google.com>
Add test cases to luo_tmpfs for source files preserved through a
read-only fd, through a read-only mount, on a read-only superblock, and
immutable files. Each of these cases except the first also preserves a
sparse file, whose holes are filled when it is preserved. Check that
the files are retrieved with their contents. Also check that the size of
a preserved file is frozen, even when the file is opened by its path,
and that writes within the file are preserved.
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../testing/selftests/liveupdate/luo_tmpfs.c | 305 ++++++++++++++++++
1 file changed, 305 insertions(+)
diff --git a/tools/testing/selftests/liveupdate/luo_tmpfs.c b/tools/testing/selftests/liveupdate/luo_tmpfs.c
index 2d6dd8f0e138..0bd9b95f0575 100644
--- a/tools/testing/selftests/liveupdate/luo_tmpfs.c
+++ b/tools/testing/selftests/liveupdate/luo_tmpfs.c
@@ -451,6 +451,13 @@ static int preserve_memfd(struct test_case *tc, unsigned int flags,
return ret;
}
+/* Skip a case whose source file couldn't be preserved in stage 1. */
+static int skip_not_preserved(struct test_case *tc)
+{
+ ksft_print_msg("%s: The source file was not preserved\n", tc->name);
+ return KSFT_SKIP;
+}
+
static int drop_privileges(void)
{
CHECK_EQ(syscall(__NR_setgroups, 0, NULL), 0);
@@ -808,6 +815,175 @@ static int linked_target_verify(struct test_case *tc)
return KSFT_PASS;
}
+static int rdonly_source_preserve(struct test_case *tc)
+{
+ int fd, ret;
+
+ fd = create_source("src/rdonly_source", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+
+ fd = open("src/rdonly_source", O_RDONLY);
+ CHECK(fd >= 0);
+ ret = preserve_fd(fd, tc->pos, tc->token);
+ close(fd);
+
+ return ret;
+}
+
+/* Create a sparse file at @path, with a hole after the first page. */
+static int create_sparse_source(const char *path, __u64 token)
+{
+ int fd;
+
+ fd = create_source(path, page_size, token);
+ if (fd < 0)
+ return -1;
+
+ if (ftruncate(fd, 2 * page_size)) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+/* Open @path with O_RDONLY and try to preserve it with @token. */
+static int try_preserve_path(const char *path, __u64 token)
+{
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = luo_session_preserve_fd(session_fd, fd, token);
+ close(fd);
+ return ret;
+}
+
+/* Check that the file preserved with @token has the source of @tc. */
+static int check_preserved(struct test_case *tc, __u64 token, size_t size)
+{
+ int fd;
+
+ fd = retrieve_new(token, "dst");
+ CHECK(fd >= 0);
+ CHECK_EQ(file_size(fd), size);
+ TRY(check_contents(fd, size, tc->token));
+
+ close(fd);
+ return KSFT_PASS;
+}
+
+/* Check that the file preserved with @token is a create_sparse_source(). */
+static int check_sparse_preserved(struct test_case *tc, __u64 token)
+{
+ unsigned char *p;
+ int fd;
+
+ fd = retrieve_new(token, "dst");
+ CHECK(fd >= 0);
+ CHECK_EQ(file_size(fd), 2 * page_size);
+
+ /* Preserving the file filled the hole. */
+ CHECK_EQ(file_pages(fd), 2);
+ p = mmap(NULL, 2 * page_size, PROT_READ, MAP_SHARED, fd, 0);
+ CHECK(p != MAP_FAILED);
+ TRY(check_range(p, 0, page_size, tc->token));
+ TRY(check_range(p, page_size, 2 * page_size, 0));
+
+ munmap(p, 2 * page_size);
+ close(fd);
+ return KSFT_PASS;
+}
+
+static int rdonly_mount_source_preserve(struct test_case *tc)
+{
+ int fd;
+
+ fd = create_source("src/rdonly_mount_source", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+ fd = create_sparse_source("src/rdonly_mount_sparse", tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+
+ CHECK_EQ(mkdir("src_ro", 0755), 0);
+ CHECK_EQ(do_mount("src", "src_ro", NULL, MS_BIND, NULL), 0);
+ CHECK_EQ(do_mount("none", "src_ro", NULL,
+ MS_REMOUNT | MS_BIND | MS_RDONLY, NULL), 0);
+
+ /*
+ * Files can be preserved through a read-only mount. Their holes are
+ * filled as if by read faults, which work through read-only mounts too.
+ */
+ CHECK_EQ(try_preserve_path("src_ro/rdonly_mount_source", tc->token), 0);
+ CHECK_EQ(try_preserve_path("src_ro/rdonly_mount_sparse", tc->token + 1),
+ 0);
+ return KSFT_PASS;
+}
+
+static int rdonly_mount_source_verify(struct test_case *tc)
+{
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ TRY(check_preserved(tc, tc->token, tc->size));
+ TRY(check_sparse_preserved(tc, tc->token + 1));
+ return KSFT_PASS;
+}
+
+static int rdonly_sb_source_child(struct test_case *tc)
+{
+ TRY(drop_privileges());
+
+ /* The owner can preserve a file whose size can't change anyway... */
+ CHECK_EQ(try_preserve_path("src_rosb/own", tc->token + 1), 0);
+
+ /* ... but nobody else can. */
+ CHECK_EQ(try_preserve_path("src_rosb/other", tc->token + 2), -EROFS);
+ return KSFT_PASS;
+}
+
+static int rdonly_sb_source_preserve(struct test_case *tc)
+{
+ int fd;
+
+ CHECK_EQ(mount_tmpfs("src_rosb", 16, NULL), 0);
+ fd = create_source("src_rosb/root", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+ fd = create_source("src_rosb/own", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+ CHECK_EQ(chown("src_rosb/own", NOBODY, NOBODY), 0);
+ fd = create_source("src_rosb/other", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+ fd = create_sparse_source("src_rosb/sparse", tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+
+ CHECK_EQ(do_mount(NULL, "src_rosb", NULL, MS_REMOUNT | MS_RDONLY, NULL),
+ 0);
+
+ CHECK_EQ(try_preserve_path("src_rosb/root", tc->token), 0);
+ TRY(run_child(rdonly_sb_source_child, tc));
+
+ /* Holes are filled as if by read faults, even on a read-only sb. */
+ CHECK_EQ(try_preserve_path("src_rosb/sparse", tc->token + 3), 0);
+ return KSFT_PASS;
+}
+
+static int rdonly_sb_source_verify(struct test_case *tc)
+{
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ TRY(check_preserved(tc, tc->token, tc->size));
+ TRY(check_preserved(tc, tc->token + 1, tc->size));
+ CHECK_EQ(luo_session_retrieve_fd(session_fd, tc->token + 2), -ENOENT);
+ TRY(check_sparse_preserved(tc, tc->token + 3));
+ return KSFT_PASS;
+}
+
/* Set the inode flag @flag, e.g. FS_IMMUTABLE_FL, on the file at @path. */
static int set_inode_flag(const char *path, int flag)
{
@@ -829,6 +1005,104 @@ static int set_inode_flag(const char *path, int flag)
return ret;
}
+static int immutable_source_preserve(struct test_case *tc)
+{
+ int fd, ret;
+
+ fd = create_source("src/immutable_source", tc->size, tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+ fd = create_sparse_source("src/immutable_sparse", tc->token);
+ CHECK(fd >= 0);
+ close(fd);
+
+ ret = set_inode_flag("src/immutable_source", FS_IMMUTABLE_FL);
+ if (ret == -ENOTTY || ret == -EOPNOTSUPP) {
+ ksft_print_msg("%s: FS_IOC_SETFLAGS is not supported\n",
+ tc->name);
+ return KSFT_SKIP;
+ }
+ CHECK_EQ(ret, 0);
+ CHECK_EQ(set_inode_flag("src/immutable_sparse", FS_IMMUTABLE_FL), 0);
+
+ /*
+ * The size of an immutable file can't change anyway, so its owner can
+ * preserve it. Its holes are filled as if by read faults.
+ */
+ CHECK_EQ(try_preserve_path("src/immutable_source", tc->token), 0);
+ CHECK_EQ(try_preserve_path("src/immutable_sparse", tc->token + 1), 0);
+ return KSFT_PASS;
+}
+
+static int immutable_source_verify(struct test_case *tc)
+{
+ int fd;
+
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ fd = retrieve_new(tc->token, "dst");
+ if (fd == -ENOENT)
+ return skip_not_preserved(tc);
+ CHECK(fd >= 0);
+ CHECK_EQ(file_size(fd), tc->size);
+ TRY(check_contents(fd, tc->size, tc->token));
+ close(fd);
+
+ TRY(check_sparse_preserved(tc, tc->token + 1));
+ return KSFT_PASS;
+}
+
+#define FROZEN_OFFSET 100
+#define FROZEN_DATA "frozen"
+
+static int frozen_source_preserve(struct test_case *tc)
+{
+ size_t len = strlen(FROZEN_DATA);
+ int fd;
+
+ TRY(preserve_file(tc));
+
+ /* The size of a preserved file can't change, even through its path. */
+ fd = open("src/frozen_source", O_RDWR);
+ CHECK(fd >= 0);
+ CHECK_ERRNO(ftruncate(fd, tc->size / 2), EPERM);
+ CHECK_ERRNO(ftruncate(fd, tc->size * 2), EPERM);
+ CHECK_ERRNO(do_fallocate(fd, 0, 0, tc->size * 2), EPERM);
+ CHECK_ERRNO(do_fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ 0, page_size), EPERM);
+ CHECK_EQ(lseek(fd, tc->size, SEEK_SET), tc->size);
+ CHECK_ERRNO(write(fd, "x", 1), EPERM);
+ CHECK_EQ(file_size(fd), tc->size);
+
+ /* Writes within the file work, and the data is preserved. */
+ CHECK_EQ(lseek(fd, FROZEN_OFFSET, SEEK_SET), FROZEN_OFFSET);
+ CHECK_EQ(write(fd, FROZEN_DATA, len), len);
+
+ close(fd);
+ return KSFT_PASS;
+}
+
+static int frozen_source_verify(struct test_case *tc)
+{
+ size_t len = strlen(FROZEN_DATA);
+ unsigned char *p;
+ int fd;
+
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ fd = retrieve_new(tc->token, "dst");
+ CHECK(fd >= 0);
+ CHECK_EQ(file_size(fd), tc->size);
+
+ p = mmap(NULL, tc->size, PROT_READ, MAP_SHARED, fd, 0);
+ CHECK(p != MAP_FAILED);
+ TRY(check_range(p, 0, FROZEN_OFFSET, tc->token));
+ CHECK(!memcmp(p + FROZEN_OFFSET, FROZEN_DATA, len));
+ TRY(check_range(p, FROZEN_OFFSET + len, tc->size, tc->token));
+
+ munmap(p, tc->size);
+ close(fd);
+ return KSFT_PASS;
+}
+
static int target_rdonly_verify(struct test_case *tc)
{
int target, rd_fd;
@@ -1287,6 +1561,37 @@ static struct test_case test_cases[] = {
.size = SMALL_SIZE,
.verify = linked_target_verify,
},
+ /* Preserving files. */
+ {
+ .name = "rdonly_source",
+ .size = SMALL_SIZE,
+ .preserve = rdonly_source_preserve,
+ .verify = restore_verify,
+ },
+ {
+ .name = "rdonly_mount_source",
+ .size = SMALL_SIZE,
+ .preserve = rdonly_mount_source_preserve,
+ .verify = rdonly_mount_source_verify,
+ },
+ {
+ .name = "rdonly_sb_source",
+ .size = SMALL_SIZE,
+ .preserve = rdonly_sb_source_preserve,
+ .verify = rdonly_sb_source_verify,
+ },
+ {
+ .name = "immutable_source",
+ .size = SMALL_SIZE,
+ .preserve = immutable_source_preserve,
+ .verify = immutable_source_verify,
+ },
+ {
+ .name = "frozen_source",
+ .size = SMALL_SIZE,
+ .preserve = frozen_source_preserve,
+ .verify = frozen_source_verify,
+ },
/* The target file. */
{
.name = "target_rdonly",
--
2.56.0.542.g3ef89b7020-goog
next prev parent reply other threads:[~2026-11-11 0:24 UTC|newest]
Thread overview: 39+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 0:23 [PATCH v4 00/19] liveupdate: Preserve tmpfs files across Live Update David Matlack
2026-11-11 0:23 ` [PATCH v4 01/19] liveupdate: Remove the list of steps from luo_preserve_file()'s comment David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 02/19] liveupdate: Pass user argument to file preserve and retrieve David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 03/19] selftests/liveupdate: Add tests for preserve and retrieve user argument David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 04/19] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 05/19] mm: Rename memfd_luo to shmem_luo David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 06/19] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 07/19] liveupdate: Add helpers to track the files that LUO manages David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 08/19] liveupdate: Let file handlers retrieve into an existing file David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 09/19] mm/shmem_luo: Leave freeing the preserved state to finish() David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 10/19] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 11/19] mm/shmem_luo: Require permission to preserve a file David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 12/19] mm/shmem_luo: Retrieve tmpfs files into a file created by userspace David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 13/19] mm/shmem_luo: Allow preserving tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 14/19] selftests/liveupdate: Test which tmpfs files can be preserved David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 15/19] selftests/liveupdate: Add a test for retrieving tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 16/19] selftests/liveupdate: Test memfds and LUO interactions in luo_tmpfs David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 17/19] selftests/liveupdate: Test the targets of retrieved tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` David Matlack [this message]
2026-11-12 0:31 ` [PATCH v4 18/19] selftests/liveupdate: Test retrieving tmpfs files preserved read-only sashiko-bot
2026-11-11 0:23 ` [PATCH v4 19/19] selftests/liveupdate: Test block accounting of retrieved tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
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=20261111002358.1842967-19-dmatlack@google.com \
--to=dmatlack@google.com \
--cc=akpm@linux-foundation.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=brauner@kernel.org \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=graf@amazon.com \
--cc=hughd@google.com \
--cc=kexec@lists.infradead.org \
--cc=liam@infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=pasha.tatashin@soleen.com \
--cc=pratyush@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=skhawaja@google.com \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.