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 <[email protected]> --- 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
