CPR with x-ignore-shared can skip migration of shared RAM pages, so only
vmstate is transferred. This relies on guest RAM being externally managed
and re-used in place.
Allow users to pass a pre-opened RAM backing FD via -add-fd and attach it
with memory-backend-file mem-path=/dev/fdset/<id>. File-backed RAM used
plain open()/creat(), so /dev/fdset/<id> could not be resolved through the
fdset mechanism. Switch to qemu_open()/qemu_create(), return -EINVAL when
creation is attempted, and add a hint for missing -add-fd. Add a qtest that
creates a memory-backend-file object from /dev/fdset/<id>.
Signed-off-by: Li Chen <me@linux.beauty>
---
system/physmem.c | 17 ++++++++++++--
tests/qtest/migration/cpr-tests.c | 38 ++++++++++++++++++++++++++++++-
2 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/system/physmem.c b/system/physmem.c
index c9869e4049..7717de5e7b 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -1650,10 +1650,14 @@ static int file_ram_open(const char *path,
char *sanitized_name;
char *c;
int fd = -1;
+ const char *tmp;
+ const bool is_fdset = strstart(path, "/dev/fdset/", &tmp);
*created = false;
for (;;) {
- fd = open(path, readonly ? O_RDONLY : O_RDWR);
+ g_autoptr(Error) local_err = NULL;
+
+ fd = qemu_open(path, readonly ? O_RDONLY : O_RDWR, &local_err);
if (fd >= 0) {
/*
* open(O_RDONLY) won't fail with EISDIR. Check manually if we
@@ -1682,8 +1686,13 @@ static int file_ram_open(const char *path,
/* Refuse to create new, readonly files. */
return -ENOENT;
}
+ if (is_fdset) {
+ /* /dev/fdset/N is a QEMU fdset handle and cannot be created. */
+ return -EINVAL;
+ }
/* @path names a file that doesn't exist, create it */
- fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0644);
+ local_err = NULL;
+ fd = qemu_create(path, O_RDWR | O_EXCL, 0644, &local_err);
if (fd >= 0) {
*created = true;
break;
@@ -2410,6 +2419,10 @@ RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr,
if (fd < 0) {
error_setg_errno(errp, -fd, "can't open backing store %s for guest RAM",
mem_path);
+ if (g_str_has_prefix(mem_path, "/dev/fdset/")) {
+ error_append_hint(errp, "Did you forget to pass the backing FD via"
+ " '-add-fd fd=<n>,set=<id>'?\n");
+ }
if (!(ram_flags & RAM_READONLY_FD) && !(ram_flags & RAM_SHARED) &&
fd == -EACCES) {
/*
diff --git a/tests/qtest/migration/cpr-tests.c b/tests/qtest/migration/cpr-tests.c
index 2a186c6f35..5eafc4d678 100644
--- a/tests/qtest/migration/cpr-tests.c
+++ b/tests/qtest/migration/cpr-tests.c
@@ -19,6 +19,39 @@
static char *tmpfs;
+#ifndef _WIN32
+static void test_mem_backend_file_fdset(void)
+{
+ const uint64_t size = 8 * 1024 * 1024;
+ g_autofree char *file = g_strdup_printf("%s/fdset-ram.bin", tmpfs);
+ QTestState *qts;
+ int fd;
+
+ fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ g_assert_cmpint(fd, >=, 0);
+ g_assert_cmpint(ftruncate(fd, size), ==, 0);
+
+ qts = qtest_init("-machine none -nodefaults");
+ qtest_qmp_fds_assert_success(qts, &fd, 1,
+ "{'execute': 'add-fd',"
+ " 'arguments': { 'fdset-id': 1 } }");
+ close(fd);
+
+ qtest_qmp_assert_success(qts, "{ 'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'memory-backend-file',"
+ " 'id': 'ram0',"
+ " 'mem-path': '/dev/fdset/1',"
+ " 'size': %" PRIu64 " } }",
+ size);
+ qtest_qmp_assert_success(qts, "{ 'execute': 'object-del',"
+ " 'arguments': { 'id': 'ram0' } }");
+ qtest_quit(qts);
+
+ unlink(file);
+}
+#endif
+
static void *migrate_hook_start_mode_reboot(QTestState *from, QTestState *to)
{
migrate_set_parameter_str(from, "mode", "cpr-reboot");
@@ -247,7 +280,10 @@ void migration_test_add_cpr(MigrationTestEnv *env)
{
tmpfs = env->tmpfs;
- /* no tests in the smoke set for now */
+#ifndef _WIN32
+ migration_test_add("/migration/fdset/mem-backend-file",
+ test_mem_backend_file_fdset);
+#endif
if (!env->full_set) {
return;
--
2.52.0
© 2016 - 2026 Red Hat, Inc.