Migration/HMP/Virtio pull 2022-03-02

A bit of a mix this time:
   * Minor fixes from myself, Hanna, and Jack
   * VNC password rework by Stefan and Fabian
   * Postcopy changes from Peter X that are
     the start of a larger series to come
   * Removing the prehistoic load_state_old
     code from Peter M
 
 Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEERfXHG0oMt/uXep+pBRYzHrxb/ecFAmIftogACgkQBRYzHrxb
 /ed19xAApta5EZm7X28mDz6uWKK0Rwwgwn195QGPz3LR/VbG69iYRAhVsD417u6j
 D1T4E5l+uY8T6gSEMPsEd3NYF79Au0HkE7Oo2YDZLKheWOIt+bsGUUDp6SpNPm85
 +MiwfqjN+yhzexrck1MSfOGZyGSv9EF+4iYODOA0Q79x8E4vztgCqJqjUDjm1j7S
 DrR10znaXNNAdC9ZtkacwF8q9LglzQLWJmkNfSLTp3IJVSeLqokSG2aFM0tLA1rA
 EGqJKUOtULcyzYxTt+V4FTAFOea4P14riAcMWEpQGdU7td7fKOmjcSJoKEDA5pdI
 wHzSQ3jxm4iXFrfIJAz5r9EzNoRaiMYO1AyApAnFXr638mDHdeIWCKfwOC08pS7+
 Bq1+bdHqkFQUpjHUfrcEnFCs/KD6rEmLahJCHDzMzaHxxxEKpZyXSVT51TVI3X/5
 HKrz4xeGetQHLPi1jV9157fJHzGFTTpMVbr77wcc+KqJ1KhAJT+neIyaeDlMvIX7
 iw219JkavhVqK6NXvwo5m75wr4kVyuUfw6HaXn2y9NWe75lo4uuCoRGz8bd/pXZK
 grBSxTTwZUItQtG+x3CIuqfAByyvAtV454cuQLyusR4P3GbDi3DwK6MIsa9qfVS5
 FV3F0FWXxPPGa5NuRRvTY8f7WyKziq5y/DRckUTB1QH3ct2GTdk=
 =L5uY
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/dgilbert-gitlab/tags/pull-migration-20220302b' into staging

Migration/HMP/Virtio pull 2022-03-02

A bit of a mix this time:
  * Minor fixes from myself, Hanna, and Jack
  * VNC password rework by Stefan and Fabian
  * Postcopy changes from Peter X that are
    the start of a larger series to come
  * Removing the prehistoic load_state_old
    code from Peter M

Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>

# gpg: Signature made Wed 02 Mar 2022 18:25:12 GMT
# gpg:                using RSA key 45F5C71B4A0CB7FB977A9FA90516331EBC5BFDE7
# gpg: Good signature from "Dr. David Alan Gilbert (RH2) <dgilbert@redhat.com>" [full]
# Primary key fingerprint: 45F5 C71B 4A0C B7FB 977A  9FA9 0516 331E BC5B FDE7

* remotes/dgilbert-gitlab/tags/pull-migration-20220302b:
  migration: Remove load_state_old and minimum_version_id_old
  tests: Pass in MigrateStart** into test_migrate_start()
  migration: Add migration_incoming_transport_cleanup()
  migration: postcopy_pause_fault_thread() never fails
  migration: Enlarge postcopy recovery to capture !-EIO too
  migration: Move static var in ram_block_from_stream() into global
  migration: Add postcopy_thread_create()
  migration: Dump ramblock and offset too when non-same-page detected
  migration: Introduce postcopy channels on dest node
  migration: Tracepoint change in postcopy-run bottom half
  migration: Finer grained tracepoints for POSTCOPY_LISTEN
  migration: Dump sub-cmd name in loadvm_process_command tp
  migration/rdma: set the REUSEADDR option for destination
  qapi/monitor: allow VNC display id in set/expire_password
  qapi/monitor: refactor set/expire_password with enums
  monitor/hmp: add support for flag argument with value
  virtiofsd: Let meson check for statx.stx_mnt_id
  clock-vmstate: Add missing END_OF_LIST

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2022-03-02 20:55:48 +00:00
commit 36eae3a732
22 changed files with 433 additions and 199 deletions

View file

@ -389,19 +389,13 @@ Each version is associated with a series of fields saved. The ``save_state`` al
the state as the newer version. But ``load_state`` sometimes is able to
load state from an older version.
You can see that there are several version fields:
You can see that there are two version fields:
- ``version_id``: the maximum version_id supported by VMState for that device.
- ``minimum_version_id``: the minimum version_id that VMState is able to understand
for that device.
- ``minimum_version_id_old``: For devices that were not able to port to vmstate, we can
assign a function that knows how to read this old state. This field is
ignored if there is no ``load_state_old`` handler.
VMState is able to read versions from minimum_version_id to
version_id. And the function ``load_state_old()`` (if present) is able to
load state from minimum_version_id_old to minimum_version_id. This
function is deprecated and will be removed when no more users are left.
VMState is able to read versions from minimum_version_id to version_id.
There are *_V* forms of many ``VMSTATE_`` macros to load fields for version dependent fields,
e.g.

View file

@ -1514,33 +1514,35 @@ ERST
{
.name = "set_password",
.args_type = "protocol:s,password:s,connected:s?",
.params = "protocol password action-if-connected",
.args_type = "protocol:s,password:s,display:-ds,connected:s?",
.params = "protocol password [-d display] [action-if-connected]",
.help = "set spice/vnc password",
.cmd = hmp_set_password,
},
SRST
``set_password [ vnc | spice ] password [ action-if-connected ]``
Change spice/vnc password. *action-if-connected* specifies what
should happen in case a connection is established: *fail* makes the
password change fail. *disconnect* changes the password and
``set_password [ vnc | spice ] password [ -d display ] [ action-if-connected ]``
Change spice/vnc password. *display* can be used with 'vnc' to specify
which display to set the password on. *action-if-connected* specifies
what should happen in case a connection is established: *fail* makes
the password change fail. *disconnect* changes the password and
disconnects the client. *keep* changes the password and keeps the
connection up. *keep* is the default.
ERST
{
.name = "expire_password",
.args_type = "protocol:s,time:s",
.params = "protocol time",
.args_type = "protocol:s,time:s,display:-ds",
.params = "protocol time [-d display]",
.help = "set spice/vnc password expire-time",
.cmd = hmp_expire_password,
},
SRST
``expire_password [ vnc | spice ]`` *expire-time*
Specify when a password for spice/vnc becomes
invalid. *expire-time* accepts:
``expire_password [ vnc | spice ] expire-time [ -d display ]``
Specify when a password for spice/vnc becomes invalid.
*display* behaves the same as in ``set_password``.
*expire-time* accepts:
``now``
Invalidate password instantly.

View file

@ -44,6 +44,7 @@ const VMStateDescription vmstate_muldiv = {
.fields = (VMStateField[]) {
VMSTATE_UINT32(multiplier, Clock),
VMSTATE_UINT32(divider, Clock),
VMSTATE_END_OF_LIST()
},
};

View file

@ -1800,7 +1800,6 @@ static const VMStateDescription vmstate_xlnx_versal_ospi = {
.name = TYPE_XILINX_VERSAL_OSPI,
.version_id = 1,
.minimum_version_id = 1,
.minimum_version_id_old = 1,
.fields = (VMStateField[]) {
VMSTATE_FIFO8(rx_fifo, XlnxVersalOspi),
VMSTATE_FIFO8(tx_fifo, XlnxVersalOspi),

View file

@ -181,9 +181,7 @@ struct VMStateDescription {
int unmigratable;
int version_id;
int minimum_version_id;
int minimum_version_id_old;
MigrationPriority priority;
LoadStateHandler *load_state_old;
int (*pre_load)(void *opaque);
int (*post_load)(void *opaque, int version_id);
int (*pre_save)(void *opaque);

View file

@ -1306,6 +1306,18 @@ statx_test = gnu_source_prefix + '''
has_statx = cc.links(statx_test)
# Check whether statx() provides mount ID information
statx_mnt_id_test = gnu_source_prefix + '''
#include <sys/stat.h>
int main(void) {
struct statx statxbuf;
statx(0, "", 0, STATX_BASIC_STATS | STATX_MNT_ID, &statxbuf);
return statxbuf.stx_mnt_id;
}'''
has_statx_mnt_id = cc.links(statx_mnt_id_test)
have_vhost_user_blk_server = get_option('vhost_user_blk_server') \
.require(targetos == 'linux',
error_message: 'vhost_user_blk_server requires linux') \
@ -1553,6 +1565,7 @@ config_host_data.set('CONFIG_NETTLE', nettle.found())
config_host_data.set('CONFIG_QEMU_PRIVATE_XTS', xts == 'private')
config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim)
config_host_data.set('CONFIG_STATX', has_statx)
config_host_data.set('CONFIG_STATX_MNT_ID', has_statx_mnt_id)
config_host_data.set('CONFIG_ZSTD', zstd.found())
config_host_data.set('CONFIG_FUSE', fuse.found())
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())

View file

@ -267,6 +267,19 @@ MigrationIncomingState *migration_incoming_get_current(void)
return current_incoming;
}
void migration_incoming_transport_cleanup(MigrationIncomingState *mis)
{
if (mis->socket_address_list) {
qapi_free_SocketAddressList(mis->socket_address_list);
mis->socket_address_list = NULL;
}
if (mis->transport_cleanup) {
mis->transport_cleanup(mis->transport_data);
mis->transport_data = mis->transport_cleanup = NULL;
}
}
void migration_incoming_state_destroy(void)
{
struct MigrationIncomingState *mis = migration_incoming_get_current();
@ -287,10 +300,8 @@ void migration_incoming_state_destroy(void)
g_array_free(mis->postcopy_remote_fds, TRUE);
mis->postcopy_remote_fds = NULL;
}
if (mis->transport_cleanup) {
mis->transport_cleanup(mis->transport_data);
}
migration_incoming_transport_cleanup(mis);
qemu_event_reset(&mis->main_thread_load_event);
if (mis->page_requested) {
@ -298,11 +309,6 @@ void migration_incoming_state_destroy(void)
mis->page_requested = NULL;
}
if (mis->socket_address_list) {
qapi_free_SocketAddressList(mis->socket_address_list);
mis->socket_address_list = NULL;
}
yank_unregister_instance(MIGRATION_YANK_INSTANCE);
}
@ -2865,7 +2871,7 @@ retry:
out:
res = qemu_file_get_error(rp);
if (res) {
if (res == -EIO && migration_in_postcopy()) {
if (res && migration_in_postcopy()) {
/*
* Maybe there is something we can do: it looks like a
* network down issue, and we pause for a recovery.
@ -3466,7 +3472,7 @@ static MigThrError migration_detect_error(MigrationState *s)
error_free(local_error);
}
if (state == MIGRATION_STATUS_POSTCOPY_ACTIVE && ret == -EIO) {
if (state == MIGRATION_STATUS_POSTCOPY_ACTIVE && ret) {
/*
* For postcopy, we allow the network to be down for a
* while. After that, it can be continued by a

View file

@ -45,14 +45,37 @@ struct PostcopyBlocktimeContext;
*/
#define CLEAR_BITMAP_SHIFT_MAX 31
/* This is an abstraction of a "temp huge page" for postcopy's purpose */
typedef struct {
/*
* This points to a temporary huge page as a buffer for UFFDIO_COPY. It's
* mmap()ed and needs to be freed when cleanup.
*/
void *tmp_huge_page;
/*
* This points to the host page we're going to install for this temp page.
* It tells us after we've received the whole page, where we should put it.
*/
void *host_addr;
/* Number of small pages copied (in size of TARGET_PAGE_SIZE) */
unsigned int target_pages;
/* Whether this page contains all zeros */
bool all_zero;
} PostcopyTmpPage;
/* State for the incoming migration */
struct MigrationIncomingState {
QEMUFile *from_src_file;
/* Previously received RAM's RAMBlock pointer */
RAMBlock *last_recv_block;
/* A hook to allow cleanup at the end of incoming migration */
void *transport_data;
void (*transport_cleanup)(void *data);
/*
* Used to sync thread creations. Note that we can't create threads in
* parallel with this sem.
*/
QemuSemaphore thread_sync_sem;
/*
* Free at the start of the main state load, set as the main thread finishes
* loading state.
@ -65,13 +88,11 @@ struct MigrationIncomingState {
size_t largest_page_size;
bool have_fault_thread;
QemuThread fault_thread;
QemuSemaphore fault_thread_sem;
/* Set this when we want the fault thread to quit */
bool fault_thread_quit;
bool have_listen_thread;
QemuThread listen_thread;
QemuSemaphore listen_thread_sem;
/* For the kernel to send us notifications */
int userfault_fd;
@ -81,7 +102,22 @@ struct MigrationIncomingState {
QemuMutex rp_mutex; /* We send replies from multiple threads */
/* RAMBlock of last request sent to source */
RAMBlock *last_rb;
void *postcopy_tmp_page;
/*
* Number of postcopy channels including the default precopy channel, so
* vanilla postcopy will only contain one channel which contain both
* precopy and postcopy streams.
*
* This is calculated when the src requests to enable postcopy but before
* it starts. Its value can depend on e.g. whether postcopy preemption is
* enabled.
*/
unsigned int postcopy_channels;
/*
* An array of temp host huge pages to be used, one for each postcopy
* channel.
*/
PostcopyTmpPage *postcopy_tmp_pages;
/* This is shared for all postcopy channels */
void *postcopy_tmp_zero_page;
/* PostCopyFD's for external userfaultfds & handlers of shared memory */
GArray *postcopy_remote_fds;
@ -130,6 +166,7 @@ struct MigrationIncomingState {
MigrationIncomingState *migration_incoming_get_current(void);
void migration_incoming_state_destroy(void);
void migration_incoming_transport_cleanup(MigrationIncomingState *mis);
/*
* Functions to work with blocktime context
*/
@ -391,5 +428,6 @@ bool migration_rate_limit(void);
void migration_cancel(const Error *error);
void populate_vfio_info(MigrationInfo *info);
void postcopy_temp_page_reset(PostcopyTmpPage *tmp_page);
#endif

View file

@ -78,6 +78,20 @@ int postcopy_notify(enum PostcopyNotifyReason reason, Error **errp)
&pnd);
}
/*
* NOTE: this routine is not thread safe, we can't call it concurrently. But it
* should be good enough for migration's purposes.
*/
void postcopy_thread_create(MigrationIncomingState *mis,
QemuThread *thread, const char *name,
void *(*fn)(void *), int joinable)
{
qemu_sem_init(&mis->thread_sync_sem, 0);
qemu_thread_create(thread, name, fn, mis, joinable);
qemu_sem_wait(&mis->thread_sync_sem);
qemu_sem_destroy(&mis->thread_sync_sem);
}
/* Postcopy needs to detect accesses to pages that haven't yet been copied
* across, and efficiently map new pages in, the techniques for doing this
* are target OS specific.
@ -526,9 +540,18 @@ int postcopy_ram_incoming_init(MigrationIncomingState *mis)
static void postcopy_temp_pages_cleanup(MigrationIncomingState *mis)
{
if (mis->postcopy_tmp_page) {
munmap(mis->postcopy_tmp_page, mis->largest_page_size);
mis->postcopy_tmp_page = NULL;
int i;
if (mis->postcopy_tmp_pages) {
for (i = 0; i < mis->postcopy_channels; i++) {
if (mis->postcopy_tmp_pages[i].tmp_huge_page) {
munmap(mis->postcopy_tmp_pages[i].tmp_huge_page,
mis->largest_page_size);
mis->postcopy_tmp_pages[i].tmp_huge_page = NULL;
}
}
g_free(mis->postcopy_tmp_pages);
mis->postcopy_tmp_pages = NULL;
}
if (mis->postcopy_tmp_zero_page) {
@ -868,15 +891,11 @@ static void mark_postcopy_blocktime_end(uintptr_t addr)
affected_cpu);
}
static bool postcopy_pause_fault_thread(MigrationIncomingState *mis)
static void postcopy_pause_fault_thread(MigrationIncomingState *mis)
{
trace_postcopy_pause_fault_thread();
qemu_sem_wait(&mis->postcopy_pause_sem_fault);
trace_postcopy_pause_fault_thread_continued();
return true;
}
/*
@ -893,7 +912,7 @@ static void *postcopy_ram_fault_thread(void *opaque)
trace_postcopy_ram_fault_thread_entry();
rcu_register_thread();
mis->last_rb = NULL; /* last RAMBlock we sent part of */
qemu_sem_post(&mis->fault_thread_sem);
qemu_sem_post(&mis->thread_sync_sem);
struct pollfd *pfd;
size_t pfd_len = 2 + mis->postcopy_remote_fds->len;
@ -936,13 +955,7 @@ static void *postcopy_ram_fault_thread(void *opaque)
* broken already using the event. We should hold until
* the channel is rebuilt.
*/
if (postcopy_pause_fault_thread(mis)) {
/* Continue to read the userfaultfd */
} else {
error_report("%s: paused but don't allow to continue",
__func__);
break;
}
postcopy_pause_fault_thread(mis);
}
if (pfd[1].revents) {
@ -1016,15 +1029,8 @@ retry:
msg.arg.pagefault.address);
if (ret) {
/* May be network failure, try to wait for recovery */
if (ret == -EIO && postcopy_pause_fault_thread(mis)) {
/* We got reconnected somehow, try to continue */
goto retry;
} else {
/* This is a unavoidable fault */
error_report("%s: postcopy_request_page() get %d",
__func__, ret);
break;
}
postcopy_pause_fault_thread(mis);
goto retry;
}
}
@ -1092,17 +1098,30 @@ retry:
static int postcopy_temp_pages_setup(MigrationIncomingState *mis)
{
int err;
PostcopyTmpPage *tmp_page;
int err, i, channels;
void *temp_page;
mis->postcopy_tmp_page = mmap(NULL, mis->largest_page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mis->postcopy_tmp_page == MAP_FAILED) {
err = errno;
mis->postcopy_tmp_page = NULL;
error_report("%s: Failed to map postcopy_tmp_page %s",
__func__, strerror(err));
return -err;
/* TODO: will be boosted when enable postcopy preemption */
mis->postcopy_channels = 1;
channels = mis->postcopy_channels;
mis->postcopy_tmp_pages = g_malloc0_n(sizeof(PostcopyTmpPage), channels);
for (i = 0; i < channels; i++) {
tmp_page = &mis->postcopy_tmp_pages[i];
temp_page = mmap(NULL, mis->largest_page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (temp_page == MAP_FAILED) {
err = errno;
error_report("%s: Failed to map postcopy_tmp_pages[%d]: %s",
__func__, i, strerror(err));
/* Clean up will be done later */
return -err;
}
tmp_page->tmp_huge_page = temp_page;
/* Initialize default states for each tmp page */
postcopy_temp_page_reset(tmp_page);
}
/*
@ -1151,11 +1170,8 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis)
return -1;
}
qemu_sem_init(&mis->fault_thread_sem, 0);
qemu_thread_create(&mis->fault_thread, "postcopy/fault",
postcopy_ram_fault_thread, mis, QEMU_THREAD_JOINABLE);
qemu_sem_wait(&mis->fault_thread_sem);
qemu_sem_destroy(&mis->fault_thread_sem);
postcopy_thread_create(mis, &mis->fault_thread, "postcopy/fault",
postcopy_ram_fault_thread, QEMU_THREAD_JOINABLE);
mis->have_fault_thread = true;
/* Mark so that we get notified of accesses to unwritten areas */
@ -1352,6 +1368,16 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
#endif
/* ------------------------------------------------------------------------- */
void postcopy_temp_page_reset(PostcopyTmpPage *tmp_page)
{
tmp_page->target_pages = 0;
tmp_page->host_addr = NULL;
/*
* This is set to true when reset, and cleared as long as we received any
* of the non-zero small page within this huge page.
*/
tmp_page->all_zero = true;
}
void postcopy_fault_thread_notify(MigrationIncomingState *mis)
{

View file

@ -135,6 +135,10 @@ void postcopy_remove_notifier(NotifierWithReturn *n);
/* Call the notifier list set by postcopy_add_start_notifier */
int postcopy_notify(enum PostcopyNotifyReason reason, Error **errp);
void postcopy_thread_create(MigrationIncomingState *mis,
QemuThread *thread, const char *name,
void *(*fn)(void *), int joinable);
struct PostCopyFD;
/* ufd is a pointer to the struct uffd_msg *TODO: more Portable! */

View file

@ -3185,12 +3185,14 @@ static int load_xbzrle(QEMUFile *f, ram_addr_t addr, void *host)
*
* Returns a pointer from within the RCU-protected ram_list.
*
* @mis: the migration incoming state pointer
* @f: QEMUFile where to read the data from
* @flags: Page flags (mostly to see if it's a continuation of previous block)
*/
static inline RAMBlock *ram_block_from_stream(QEMUFile *f, int flags)
static inline RAMBlock *ram_block_from_stream(MigrationIncomingState *mis,
QEMUFile *f, int flags)
{
static RAMBlock *block;
RAMBlock *block = mis->last_recv_block;
char id[256];
uint8_t len;
@ -3217,6 +3219,8 @@ static inline RAMBlock *ram_block_from_stream(QEMUFile *f, int flags)
return NULL;
}
mis->last_recv_block = block;
return block;
}
@ -3641,11 +3645,8 @@ static int ram_load_postcopy(QEMUFile *f)
bool place_needed = false;
bool matches_target_page_size = false;
MigrationIncomingState *mis = migration_incoming_get_current();
/* Temporary page that is later 'placed' */
void *postcopy_host_page = mis->postcopy_tmp_page;
void *host_page = NULL;
bool all_zero = true;
int target_pages = 0;
/* Currently we only use channel 0. TODO: use all the channels */
PostcopyTmpPage *tmp_page = &mis->postcopy_tmp_pages[0];
while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) {
ram_addr_t addr;
@ -3672,7 +3673,7 @@ static int ram_load_postcopy(QEMUFile *f)
trace_ram_load_postcopy_loop((uint64_t)addr, flags);
if (flags & (RAM_SAVE_FLAG_ZERO | RAM_SAVE_FLAG_PAGE |
RAM_SAVE_FLAG_COMPRESS_PAGE)) {
block = ram_block_from_stream(f, flags);
block = ram_block_from_stream(mis, f, flags);
if (!block) {
ret = -EINVAL;
break;
@ -3689,7 +3690,7 @@ static int ram_load_postcopy(QEMUFile *f)
ret = -EINVAL;
break;
}
target_pages++;
tmp_page->target_pages++;
matches_target_page_size = block->page_size == TARGET_PAGE_SIZE;
/*
* Postcopy requires that we place whole host pages atomically;
@ -3701,16 +3702,21 @@ static int ram_load_postcopy(QEMUFile *f)
* however the source ensures it always sends all the components
* of a host page in one chunk.
*/
page_buffer = postcopy_host_page +
page_buffer = tmp_page->tmp_huge_page +
host_page_offset_from_ram_block_offset(block, addr);
/* If all TP are zero then we can optimise the place */
if (target_pages == 1) {
host_page = host_page_from_ram_block_offset(block, addr);
} else if (host_page != host_page_from_ram_block_offset(block,
addr)) {
if (tmp_page->target_pages == 1) {
tmp_page->host_addr =
host_page_from_ram_block_offset(block, addr);
} else if (tmp_page->host_addr !=
host_page_from_ram_block_offset(block, addr)) {
/* not the 1st TP within the HP */
error_report("Non-same host page %p/%p", host_page,
host_page_from_ram_block_offset(block, addr));
error_report("Non-same host page detected. "
"Target host page %p, received host page %p "
"(rb %s offset 0x"RAM_ADDR_FMT" target_pages %d)",
tmp_page->host_addr,
host_page_from_ram_block_offset(block, addr),
block->idstr, addr, tmp_page->target_pages);
ret = -EINVAL;
break;
}
@ -3719,10 +3725,11 @@ static int ram_load_postcopy(QEMUFile *f)
* If it's the last part of a host page then we place the host
* page
*/
if (target_pages == (block->page_size / TARGET_PAGE_SIZE)) {
if (tmp_page->target_pages ==
(block->page_size / TARGET_PAGE_SIZE)) {
place_needed = true;
}
place_source = postcopy_host_page;
place_source = tmp_page->tmp_huge_page;
}
switch (flags & ~RAM_SAVE_FLAG_CONTINUE) {
@ -3736,12 +3743,12 @@ static int ram_load_postcopy(QEMUFile *f)
memset(page_buffer, ch, TARGET_PAGE_SIZE);
}
if (ch) {
all_zero = false;
tmp_page->all_zero = false;
}
break;
case RAM_SAVE_FLAG_PAGE:
all_zero = false;
tmp_page->all_zero = false;
if (!matches_target_page_size) {
/* For huge pages, we always use temporary buffer */
qemu_get_buffer(f, page_buffer, TARGET_PAGE_SIZE);
@ -3759,7 +3766,7 @@ static int ram_load_postcopy(QEMUFile *f)
}
break;
case RAM_SAVE_FLAG_COMPRESS_PAGE:
all_zero = false;
tmp_page->all_zero = false;
len = qemu_get_be32(f);
if (len < 0 || len > compressBound(TARGET_PAGE_SIZE)) {
error_report("Invalid compressed data length: %d", len);
@ -3791,16 +3798,14 @@ static int ram_load_postcopy(QEMUFile *f)
}
if (!ret && place_needed) {
if (all_zero) {
ret = postcopy_place_page_zero(mis, host_page, block);
if (tmp_page->all_zero) {
ret = postcopy_place_page_zero(mis, tmp_page->host_addr, block);
} else {
ret = postcopy_place_page(mis, host_page, place_source,
block);
ret = postcopy_place_page(mis, tmp_page->host_addr,
place_source, block);
}
place_needed = false;
target_pages = 0;
/* Assume we have a zero page until we detect something different */
all_zero = true;
postcopy_temp_page_reset(tmp_page);
}
}
@ -3880,6 +3885,7 @@ void colo_flush_ram_cache(void)
*/
static int ram_load_precopy(QEMUFile *f)
{
MigrationIncomingState *mis = migration_incoming_get_current();
int flags = 0, ret = 0, invalid_flags = 0, len = 0, i = 0;
/* ADVISE is earlier, it shows the source has the postcopy capability on */
bool postcopy_advised = postcopy_is_advised();
@ -3918,7 +3924,7 @@ static int ram_load_precopy(QEMUFile *f)
if (flags & (RAM_SAVE_FLAG_ZERO | RAM_SAVE_FLAG_PAGE |
RAM_SAVE_FLAG_COMPRESS_PAGE | RAM_SAVE_FLAG_XBZRLE)) {
RAMBlock *block = ram_block_from_stream(f, flags);
RAMBlock *block = ram_block_from_stream(mis, f, flags);
host = host_from_ram_block_offset(block, addr);
/*

View file

@ -2705,6 +2705,7 @@ static int qemu_rdma_dest_init(RDMAContext *rdma, Error **errp)
char ip[40] = "unknown";
struct rdma_addrinfo *res, *e;
char port_str[16];
int reuse = 1;
for (idx = 0; idx < RDMA_WRID_MAX; idx++) {
rdma->wr_data[idx].control_len = 0;
@ -2740,6 +2741,12 @@ static int qemu_rdma_dest_init(RDMAContext *rdma, Error **errp)
goto err_dest_init_bind_addr;
}
ret = rdma_set_option(listen_id, RDMA_OPTION_ID, RDMA_OPTION_ID_REUSEADDR,
&reuse, sizeof reuse);
if (ret) {
ERROR(errp, "Error: could not set REUSEADDR option");
goto err_dest_init_bind_addr;
}
for (e = res; e != NULL; e = e->ai_next) {
inet_ntop(e->ai_family,
&((struct sockaddr_in *) e->ai_dst_addr)->sin_addr, ip, sizeof ip);

View file

@ -1863,7 +1863,7 @@ static void *postcopy_ram_listen_thread(void *opaque)
migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
MIGRATION_STATUS_POSTCOPY_ACTIVE);
qemu_sem_post(&mis->listen_thread_sem);
qemu_sem_post(&mis->thread_sync_sem);
trace_postcopy_ram_listen_thread_start();
rcu_register_thread();
@ -1948,9 +1948,10 @@ static void *postcopy_ram_listen_thread(void *opaque)
static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
{
PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
trace_loadvm_postcopy_handle_listen();
Error *local_err = NULL;
trace_loadvm_postcopy_handle_listen("enter");
if (ps != POSTCOPY_INCOMING_ADVISE && ps != POSTCOPY_INCOMING_DISCARD) {
error_report("CMD_POSTCOPY_LISTEN in wrong postcopy state (%d)", ps);
return -1;
@ -1965,6 +1966,8 @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
}
}
trace_loadvm_postcopy_handle_listen("after discard");
/*
* Sensitise RAM - can now generate requests for blocks that don't exist
* However, at this point the CPU shouldn't be running, and the IO
@ -1977,19 +1980,17 @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
}
}
trace_loadvm_postcopy_handle_listen("after uffd");
if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, &local_err)) {
error_report_err(local_err);
return -1;
}
mis->have_listen_thread = true;
/* Start up the listening thread and wait for it to signal ready */
qemu_sem_init(&mis->listen_thread_sem, 0);
qemu_thread_create(&mis->listen_thread, "postcopy/listen",
postcopy_ram_listen_thread, NULL,
QEMU_THREAD_DETACHED);
qemu_sem_wait(&mis->listen_thread_sem);
qemu_sem_destroy(&mis->listen_thread_sem);
postcopy_thread_create(mis, &mis->listen_thread, "postcopy/listen",
postcopy_ram_listen_thread, QEMU_THREAD_DETACHED);
trace_loadvm_postcopy_handle_listen("return");
return 0;
}
@ -1999,13 +2000,19 @@ static void loadvm_postcopy_handle_run_bh(void *opaque)
Error *local_err = NULL;
MigrationIncomingState *mis = opaque;
trace_loadvm_postcopy_handle_run_bh("enter");
/* TODO we should move all of this lot into postcopy_ram.c or a shared code
* in migration.c
*/
cpu_synchronize_all_post_init();
trace_loadvm_postcopy_handle_run_bh("after cpu sync");
qemu_announce_self(&mis->announce_timer, migrate_announce_params());
trace_loadvm_postcopy_handle_run_bh("after announce");
/* Make sure all file formats flush their mutable metadata.
* If we get an error here, just don't restart the VM yet. */
bdrv_invalidate_cache_all(&local_err);
@ -2015,9 +2022,7 @@ static void loadvm_postcopy_handle_run_bh(void *opaque)
autostart = false;
}
trace_loadvm_postcopy_handle_run_cpu_sync();
trace_loadvm_postcopy_handle_run_vmstart();
trace_loadvm_postcopy_handle_run_bh("after invalidate cache");
dirty_bitmap_mig_before_vm_start();
@ -2030,6 +2035,8 @@ static void loadvm_postcopy_handle_run_bh(void *opaque)
}
qemu_bh_delete(mis->bh);
trace_loadvm_postcopy_handle_run_bh("return");
}
/* After all discards we can start running and asking for pages */
@ -2273,12 +2280,13 @@ static int loadvm_process_command(QEMUFile *f)
return qemu_file_get_error(f);
}
trace_loadvm_process_command(cmd, len);
if (cmd >= MIG_CMD_MAX || cmd == MIG_CMD_INVALID) {
error_report("MIG_CMD 0x%x unknown (len 0x%x)", cmd, len);
return -EINVAL;
}
trace_loadvm_process_command(mig_cmd_args[cmd].name, len);
if (mig_cmd_args[cmd].len != -1 && mig_cmd_args[cmd].len != len) {
error_report("%s received with bad length - expecting %zu, got %d",
mig_cmd_args[cmd].name,
@ -2565,6 +2573,18 @@ void qemu_loadvm_state_cleanup(void)
/* Return true if we should continue the migration, or false. */
static bool postcopy_pause_incoming(MigrationIncomingState *mis)
{
int i;
/*
* If network is interrupted, any temp page we received will be useless
* because we didn't mark them as "received" in receivedmap. After a
* proper recovery later (which will sync src dirty bitmap with receivedmap
* on dest) these cached small pages will be resent again.
*/
for (i = 0; i < mis->postcopy_channels; i++) {
postcopy_temp_page_reset(&mis->postcopy_tmp_pages[i]);
}
trace_postcopy_pause_incoming();
assert(migrate_postcopy_ram());

View file

@ -14,15 +14,14 @@ loadvm_handle_cmd_packaged_main(int ret) "%d"
loadvm_handle_cmd_packaged_received(int ret) "%d"
loadvm_handle_recv_bitmap(char *s) "%s"
loadvm_postcopy_handle_advise(void) ""
loadvm_postcopy_handle_listen(void) ""
loadvm_postcopy_handle_listen(const char *str) "%s"
loadvm_postcopy_handle_run(void) ""
loadvm_postcopy_handle_run_cpu_sync(void) ""
loadvm_postcopy_handle_run_vmstart(void) ""
loadvm_postcopy_handle_run_bh(const char *str) "%s"
loadvm_postcopy_handle_resume(void) ""
loadvm_postcopy_ram_handle_discard(void) ""
loadvm_postcopy_ram_handle_discard_end(void) ""
loadvm_postcopy_ram_handle_discard_header(const char *ramid, uint16_t len) "%s: %ud"
loadvm_process_command(uint16_t com, uint16_t len) "com=0x%x len=%d"
loadvm_process_command(const char *s, uint16_t len) "com=%s len=%d"
loadvm_process_command_ping(uint32_t val) "0x%x"
postcopy_ram_listen_thread_exit(void) ""
postcopy_ram_listen_thread_start(void) ""

View file

@ -90,12 +90,6 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
return -EINVAL;
}
if (version_id < vmsd->minimum_version_id) {
if (vmsd->load_state_old &&
version_id >= vmsd->minimum_version_id_old) {
ret = vmsd->load_state_old(f, opaque, version_id);
trace_vmstate_load_state_end(vmsd->name, "old path", ret);
return ret;
}
error_report("%s: incoming version_id %d is too old "
"for local minimum version_id %d",
vmsd->name, version_id, vmsd->minimum_version_id);

View file

@ -1396,10 +1396,35 @@ void hmp_set_password(Monitor *mon, const QDict *qdict)
{
const char *protocol = qdict_get_str(qdict, "protocol");
const char *password = qdict_get_str(qdict, "password");
const char *display = qdict_get_try_str(qdict, "display");
const char *connected = qdict_get_try_str(qdict, "connected");
Error *err = NULL;
qmp_set_password(protocol, password, !!connected, connected, &err);
SetPasswordOptions opts = {
.password = (char *)password,
.has_connected = !!connected,
};
opts.connected = qapi_enum_parse(&SetPasswordAction_lookup, connected,
SET_PASSWORD_ACTION_KEEP, &err);
if (err) {
goto out;
}
opts.protocol = qapi_enum_parse(&DisplayProtocol_lookup, protocol,
DISPLAY_PROTOCOL_VNC, &err);
if (err) {
goto out;
}
if (opts.protocol == DISPLAY_PROTOCOL_VNC) {
opts.u.vnc.has_display = !!display;
opts.u.vnc.display = (char *)display;
}
qmp_set_password(&opts, &err);
out:
hmp_handle_error(mon, err);
}
@ -1407,9 +1432,27 @@ void hmp_expire_password(Monitor *mon, const QDict *qdict)
{
const char *protocol = qdict_get_str(qdict, "protocol");
const char *whenstr = qdict_get_str(qdict, "time");
const char *display = qdict_get_try_str(qdict, "display");
Error *err = NULL;
qmp_expire_password(protocol, whenstr, &err);
ExpirePasswordOptions opts = {
.time = (char *)whenstr,
};
opts.protocol = qapi_enum_parse(&DisplayProtocol_lookup, protocol,
DISPLAY_PROTOCOL_VNC, &err);
if (err) {
goto out;
}
if (opts.protocol == DISPLAY_PROTOCOL_VNC) {
opts.u.vnc.has_display = !!display;
opts.u.vnc.display = (char *)display;
}
qmp_expire_password(&opts, &err);
out:
hmp_handle_error(mon, err);
}

View file

@ -981,6 +981,7 @@ static QDict *monitor_parse_arguments(Monitor *mon,
{
const char *tmp = p;
int skip_key = 0;
int ret;
/* option */
c = *typestr++;
@ -1003,11 +1004,27 @@ static QDict *monitor_parse_arguments(Monitor *mon,
}
if (skip_key) {
p = tmp;
} else if (*typestr == 's') {
/* has option with string value */
typestr++;
tmp = p++;
while (qemu_isspace(*p)) {
p++;
}
ret = get_str(buf, sizeof(buf), &p);
if (ret < 0) {
monitor_printf(mon, "%s: value expected for -%c\n",
cmd->name, *tmp);
goto fail;
}
qdict_put_str(qdict, key, buf);
} else {
/* has option */
/* has boolean option */
p++;
qdict_put_bool(qdict, key, true);
}
} else if (*typestr == 's') {
typestr++;
}
}
break;

View file

@ -63,7 +63,8 @@
* '.' other form of optional type (for 'i' and 'l')
* 'b' boolean
* user mode accepts "on" or "off"
* '-' optional parameter (eg. '-f')
* '-' optional parameter (eg. '-f'); if followed by a 's', it
* specifies an optional string param (e.g. '-fs' allows '-f foo')
*
*/

View file

@ -168,45 +168,27 @@ void qmp_system_wakeup(Error **errp)
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, errp);
}
void qmp_set_password(const char *protocol, const char *password,
bool has_connected, const char *connected, Error **errp)
void qmp_set_password(SetPasswordOptions *opts, Error **errp)
{
int disconnect_if_connected = 0;
int fail_if_connected = 0;
int rc;
if (has_connected) {
if (strcmp(connected, "fail") == 0) {
fail_if_connected = 1;
} else if (strcmp(connected, "disconnect") == 0) {
disconnect_if_connected = 1;
} else if (strcmp(connected, "keep") == 0) {
/* nothing */
} else {
error_setg(errp, QERR_INVALID_PARAMETER, "connected");
return;
}
}
if (strcmp(protocol, "spice") == 0) {
if (opts->protocol == DISPLAY_PROTOCOL_SPICE) {
if (!qemu_using_spice(errp)) {
return;
}
rc = qemu_spice.set_passwd(password, fail_if_connected,
disconnect_if_connected);
} else if (strcmp(protocol, "vnc") == 0) {
if (fail_if_connected || disconnect_if_connected) {
rc = qemu_spice.set_passwd(opts->password,
opts->connected == SET_PASSWORD_ACTION_FAIL,
opts->connected == SET_PASSWORD_ACTION_DISCONNECT);
} else {
assert(opts->protocol == DISPLAY_PROTOCOL_VNC);
if (opts->connected != SET_PASSWORD_ACTION_KEEP) {
/* vnc supports "connected=keep" only */
error_setg(errp, QERR_INVALID_PARAMETER, "connected");
return;
}
/* Note that setting an empty password will not disable login through
* this interface. */
rc = vnc_display_password(NULL, password);
} else {
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol",
"'vnc' or 'spice'");
return;
rc = vnc_display_password(opts->u.vnc.display, opts->password);
}
if (rc != 0) {
@ -214,11 +196,11 @@ void qmp_set_password(const char *protocol, const char *password,
}
}
void qmp_expire_password(const char *protocol, const char *whenstr,
Error **errp)
void qmp_expire_password(ExpirePasswordOptions *opts, Error **errp)
{
time_t when;
int rc;
const char *whenstr = opts->time;
if (strcmp(whenstr, "now") == 0) {
when = 0;
@ -230,17 +212,14 @@ void qmp_expire_password(const char *protocol, const char *whenstr,
when = strtoull(whenstr, NULL, 10);
}
if (strcmp(protocol, "spice") == 0) {
if (opts->protocol == DISPLAY_PROTOCOL_SPICE) {
if (!qemu_using_spice(errp)) {
return;
}
rc = qemu_spice.set_pw_expire(when);
} else if (strcmp(protocol, "vnc") == 0) {
rc = vnc_display_pw_expire(NULL, when);
} else {
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol",
"'vnc' or 'spice'");
return;
assert(opts->protocol == DISPLAY_PROTOCOL_VNC);
rc = vnc_display_pw_expire(opts->u.vnc.display, when);
}
if (rc != 0) {

View file

@ -10,20 +10,75 @@
{ 'include': 'sockets.json' }
##
# @set_password:
# @DisplayProtocol:
#
# Sets the password of a remote display session.
# Display protocols which support changing password options.
#
# Since: 7.0
#
##
{ 'enum': 'DisplayProtocol',
'data': [ 'vnc', 'spice' ] }
##
# @SetPasswordAction:
#
# An action to take on changing a password on a connection with active clients.
#
# @keep: maintain existing clients
#
# @fail: fail the command if clients are connected
#
# @disconnect: disconnect existing clients
#
# Since: 7.0
#
##
{ 'enum': 'SetPasswordAction',
'data': [ 'keep', 'fail', 'disconnect' ] }
##
# @SetPasswordOptions:
#
# Options for set_password.
#
# @protocol: - 'vnc' to modify the VNC server password
# - 'spice' to modify the Spice server password
#
# @password: the new password
#
# @connected: how to handle existing clients when changing the
# password. If nothing is specified, defaults to 'keep'
# 'fail' to fail the command if clients are connected
# 'disconnect' to disconnect existing clients
# 'keep' to maintain existing clients
# @connected: How to handle existing clients when changing the
# password. If nothing is specified, defaults to 'keep'.
# For VNC, only 'keep' is currently implemented.
#
# Since: 7.0
#
##
{ 'union': 'SetPasswordOptions',
'base': { 'protocol': 'DisplayProtocol',
'password': 'str',
'*connected': 'SetPasswordAction' },
'discriminator': 'protocol',
'data': { 'vnc': 'SetPasswordOptionsVnc' } }
##
# @SetPasswordOptionsVnc:
#
# Options for set_password specific to the VNC procotol.
#
# @display: The id of the display where the password should be changed.
# Defaults to the first.
#
# Since: 7.0
#
##
{ 'struct': 'SetPasswordOptionsVnc',
'data': { '*display': 'str' } }
##
# @set_password:
#
# Set the password of a remote display server.
#
# Returns: - Nothing on success
# - If Spice is not enabled, DeviceNotFound
@ -37,15 +92,15 @@
# <- { "return": {} }
#
##
{ 'command': 'set_password',
'data': {'protocol': 'str', 'password': 'str', '*connected': 'str'} }
{ 'command': 'set_password', 'boxed': true, 'data': 'SetPasswordOptions' }
##
# @expire_password:
# @ExpirePasswordOptions:
#
# Expire the password of a remote display server.
# General options for expire_password.
#
# @protocol: the name of the remote display protocol 'vnc' or 'spice'
# @protocol: - 'vnc' to modify the VNC server expiration
# - 'spice' to modify the Spice server expiration
#
# @time: when to expire the password.
#
@ -54,16 +109,45 @@
# - '+INT' where INT is the number of seconds from now (integer)
# - 'INT' where INT is the absolute time in seconds
#
# Returns: - Nothing on success
# - If @protocol is 'spice' and Spice is not active, DeviceNotFound
#
# Since: 0.14
#
# Notes: Time is relative to the server and currently there is no way to
# coordinate server time with client time. It is not recommended to
# use the absolute time version of the @time parameter unless you're
# sure you are on the same machine as the QEMU instance.
#
# Since: 7.0
#
##
{ 'union': 'ExpirePasswordOptions',
'base': { 'protocol': 'DisplayProtocol',
'time': 'str' },
'discriminator': 'protocol',
'data': { 'vnc': 'ExpirePasswordOptionsVnc' } }
##
# @ExpirePasswordOptionsVnc:
#
# Options for expire_password specific to the VNC procotol.
#
# @display: The id of the display where the expiration should be changed.
# Defaults to the first.
#
# Since: 7.0
#
##
{ 'struct': 'ExpirePasswordOptionsVnc',
'data': { '*display': 'str' } }
##
# @expire_password:
#
# Expire the password of a remote display server.
#
# Returns: - Nothing on success
# - If @protocol is 'spice' and Spice is not active, DeviceNotFound
#
# Since: 0.14
#
# Example:
#
# -> { "execute": "expire_password", "arguments": { "protocol": "vnc",
@ -71,7 +155,7 @@
# <- { "return": {} }
#
##
{ 'command': 'expire_password', 'data': {'protocol': 'str', 'time': 'str'} }
{ 'command': 'expire_password', 'boxed': true, 'data': 'ExpirePasswordOptions' }
##
# @screendump:

View file

@ -495,7 +495,7 @@ static void migrate_start_destroy(MigrateStart *args)
}
static int test_migrate_start(QTestState **from, QTestState **to,
const char *uri, MigrateStart *args)
const char *uri, MigrateStart **pargs)
{
g_autofree gchar *arch_source = NULL;
g_autofree gchar *arch_target = NULL;
@ -507,6 +507,7 @@ static int test_migrate_start(QTestState **from, QTestState **to,
g_autofree char *shmem_path = NULL;
const char *arch = qtest_get_arch();
const char *machine_opts = NULL;
MigrateStart *args = *pargs;
const char *memory_size;
int ret = 0;
@ -621,6 +622,8 @@ static int test_migrate_start(QTestState **from, QTestState **to,
out:
migrate_start_destroy(args);
/* This tells the caller that this structure is gone */
*pargs = NULL;
return ret;
}
@ -665,7 +668,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
QTestState *from, *to;
if (test_migrate_start(&from, &to, uri, args)) {
if (test_migrate_start(&from, &to, uri, &args)) {
return -1;
}
@ -788,7 +791,7 @@ static void test_baddest(void)
args->hide_stderr = true;
if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", args)) {
if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", &args)) {
return;
}
migrate_qmp(from, "tcp:127.0.0.1:0", "{}");
@ -804,7 +807,7 @@ static void test_precopy_unix_common(bool dirty_ring)
args->use_dirty_ring = dirty_ring;
if (test_migrate_start(&from, &to, uri, args)) {
if (test_migrate_start(&from, &to, uri, &args)) {
return;
}
@ -892,7 +895,7 @@ static void test_xbzrle(const char *uri)
MigrateStart *args = migrate_start_new();
QTestState *from, *to;
if (test_migrate_start(&from, &to, uri, args)) {
if (test_migrate_start(&from, &to, uri, &args)) {
return;
}
@ -946,7 +949,7 @@ static void test_precopy_tcp(void)
g_autofree char *uri = NULL;
QTestState *from, *to;
if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", args)) {
if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", &args)) {
return;
}
@ -991,7 +994,7 @@ static void test_migrate_fd_proto(void)
QDict *rsp;
const char *error_desc;
if (test_migrate_start(&from, &to, "defer", args)) {
if (test_migrate_start(&from, &to, "defer", &args)) {
return;
}
@ -1071,7 +1074,7 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
QTestState *from, *to;
if (test_migrate_start(&from, &to, uri, args)) {
if (test_migrate_start(&from, &to, uri, &args)) {
return;
}
@ -1163,7 +1166,7 @@ static void test_migrate_auto_converge(void)
*/
const int64_t expected_threshold = max_bandwidth * downtime_limit / 1000;
if (test_migrate_start(&from, &to, uri, args)) {
if (test_migrate_start(&from, &to, uri, &args)) {
return;
}
@ -1232,7 +1235,7 @@ static void test_multifd_tcp(const char *method)
QDict *rsp;
g_autofree char *uri = NULL;
if (test_migrate_start(&from, &to, "defer", args)) {
if (test_migrate_start(&from, &to, "defer", &args)) {
return;
}
@ -1318,7 +1321,7 @@ static void test_multifd_tcp_cancel(void)
args->hide_stderr = true;
if (test_migrate_start(&from, &to, "defer", args)) {
if (test_migrate_start(&from, &to, "defer", &args)) {
return;
}
@ -1357,7 +1360,7 @@ static void test_multifd_tcp_cancel(void)
args = migrate_start_new();
args->only_target = true;
if (test_migrate_start(&from, &to2, "defer", args)) {
if (test_migrate_start(&from, &to2, "defer", &args)) {
return;
}

View file

@ -1039,7 +1039,7 @@ static int do_statx(struct lo_data *lo, int dirfd, const char *pathname,
{
int res;
#if defined(CONFIG_STATX) && defined(STATX_MNT_ID)
#if defined(CONFIG_STATX) && defined(CONFIG_STATX_MNT_ID)
if (lo->use_statx) {
struct statx statxbuf;