Compare commits
	
		
			38 Commits
		
	
	
		
			factory-tm
			...
			multifd-fi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c9c26e7fb3 | ||
| 
						 | 
					8273a30944 | ||
| 
						 | 
					32f31a3e72 | ||
| 
						 | 
					d99189f004 | ||
| 
						 | 
					be0f98336a | ||
| 
						 | 
					f61b1859ee | ||
| 
						 | 
					524467bb35 | ||
| 
						 | 
					febc85d9e5 | ||
| 
						 | 
					6eca765641 | ||
| 
						 | 
					cd21625b2a | ||
| 
						 | 
					9b7ee75ae6 | ||
| 
						 | 
					003ffce566 | ||
| 
						 | 
					b0f65b8f02 | ||
| 
						 | 
					17daea7522 | ||
| 
						 | 
					f633005048 | ||
| 
						 | 
					2ac6a9b07f | ||
| 
						 | 
					8869d53021 | ||
| 
						 | 
					179dc3526e | ||
| 
						 | 
					ed1216ec30 | ||
| 
						 | 
					17c943d294 | ||
| 
						 | 
					419987eb70 | ||
| 
						 | 
					f4c7b51194 | ||
| 
						 | 
					ee8b73bde7 | ||
| 
						 | 
					84c303ea47 | ||
| 
						 | 
					a27192203a | ||
| 
						 | 
					b546d4aa07 | ||
| 
						 | 
					d6047bf97e | ||
| 
						 | 
					267071860c | ||
| 
						 | 
					2eafe91834 | ||
| 
						 | 
					db2dc58c74 | ||
| 
						 | 
					8f83c78aa5 | ||
| 
						 | 
					4d52d894e4 | ||
| 
						 | 
					985f44ddf0 | ||
| 
						 | 
					6982a253d8 | ||
| 
						 | 
					e9c03af1e1 | ||
| 
						 | 
					df17dcf6a8 | ||
| 
						 | 
					64a491f495 | ||
| 
						 | 
					06f21266b9 | 
@@ -41,6 +41,28 @@ over any transport.
 | 
			
		||||
- exec migration: do the migration using the stdin/stdout through a process.
 | 
			
		||||
- fd migration: do the migration using a file descriptor that is
 | 
			
		||||
  passed to QEMU.  QEMU doesn't care how this file descriptor is opened.
 | 
			
		||||
- file migration: do the migration using a file that is passed to QEMU
 | 
			
		||||
  by path. A file offset option is supported to allow a management
 | 
			
		||||
  application to add its own metadata to the start of the file without
 | 
			
		||||
  QEMU interference.
 | 
			
		||||
 | 
			
		||||
  The file migration also supports using a file that has already been
 | 
			
		||||
  opened. A set of file descriptors is passed to QEMU via an "fdset"
 | 
			
		||||
  (see add-fd QMP command documentation). This method allows a
 | 
			
		||||
  management application to have control over the migration file
 | 
			
		||||
  opening operation. There are, however, strict requirements to this
 | 
			
		||||
  interface:
 | 
			
		||||
 | 
			
		||||
  On the migration source side:
 | 
			
		||||
    - the fdset must contain two file descriptors that are not
 | 
			
		||||
      duplicates between themselves;
 | 
			
		||||
    - if the direct-io capability is to be used, exactly one of the
 | 
			
		||||
      file descriptors must have the O_DIRECT flag set;
 | 
			
		||||
    - the file must be opened with WRONLY both times.
 | 
			
		||||
 | 
			
		||||
  On the migration destination side:
 | 
			
		||||
    - the fdset must contain one file descriptor;
 | 
			
		||||
    - the file must be opened with RDONLY.
 | 
			
		||||
 | 
			
		||||
In addition, support is included for migration using RDMA, which
 | 
			
		||||
transports the page data using ``RDMA``, where the hardware takes care of
 | 
			
		||||
@@ -572,6 +594,27 @@ Others (especially either older devices or system devices which for
 | 
			
		||||
some reason don't have a bus concept) make use of the ``instance id``
 | 
			
		||||
for otherwise identically named devices.
 | 
			
		||||
 | 
			
		||||
Fixed-ram format
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
When the ``fixed-ram`` capability is enabled, a slightly different
 | 
			
		||||
stream format is used for the RAM section. Instead of having a
 | 
			
		||||
sequential stream of pages that follow the RAMBlock headers, the dirty
 | 
			
		||||
pages for a RAMBlock follow its header. This ensures that each RAM
 | 
			
		||||
page has a fixed offset in the resulting migration file.
 | 
			
		||||
 | 
			
		||||
The ``fixed-ram`` capability must be enabled in both source and
 | 
			
		||||
destination with:
 | 
			
		||||
 | 
			
		||||
    ``migrate_set_capability fixed-ram on``
 | 
			
		||||
 | 
			
		||||
Since pages are written to their relative offsets and out of order
 | 
			
		||||
(due to the memory dirtying patterns), streaming channels such as
 | 
			
		||||
sockets are not supported. A seekable channel such as a file is
 | 
			
		||||
required. This can be verified in the QIOChannel by the presence of
 | 
			
		||||
the QIO_CHANNEL_FEATURE_SEEKABLE. In more practical terms, this
 | 
			
		||||
migration format requires the ``file:`` URI when migrating.
 | 
			
		||||
 | 
			
		||||
Return path
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,14 @@ struct RAMBlock {
 | 
			
		||||
    size_t page_size;
 | 
			
		||||
    /* dirty bitmap used during migration */
 | 
			
		||||
    unsigned long *bmap;
 | 
			
		||||
    /* shadow dirty bitmap used when migrating to a file */
 | 
			
		||||
    unsigned long *shadow_bmap;
 | 
			
		||||
    /*
 | 
			
		||||
     * offset in the file pages belonging to this ramblock are saved,
 | 
			
		||||
     * used only during migration to a file.
 | 
			
		||||
     */
 | 
			
		||||
    off_t bitmap_offset;
 | 
			
		||||
    uint64_t pages_offset;
 | 
			
		||||
    /* bitmap of already received pages in postcopy */
 | 
			
		||||
    unsigned long *receivedmap;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -89,4 +89,11 @@ qio_channel_file_new_path(const char *path,
 | 
			
		||||
                          mode_t mode,
 | 
			
		||||
                          Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_file_set_direct_io:
 | 
			
		||||
 * @ioc: the QIOChannel object
 | 
			
		||||
 * @enabled: the desired state of the O_DIRECT flag
 | 
			
		||||
 * @errp: pointer to initialized error object
 | 
			
		||||
 */
 | 
			
		||||
void qio_channel_file_set_direct_io(QIOChannel *ioc, bool enable, Error **errp);
 | 
			
		||||
#endif /* QIO_CHANNEL_FILE_H */
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ enum QIOChannelFeature {
 | 
			
		||||
    QIO_CHANNEL_FEATURE_LISTEN,
 | 
			
		||||
    QIO_CHANNEL_FEATURE_WRITE_ZERO_COPY,
 | 
			
		||||
    QIO_CHANNEL_FEATURE_READ_MSG_PEEK,
 | 
			
		||||
    QIO_CHANNEL_FEATURE_SEEKABLE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -130,6 +131,16 @@ struct QIOChannelClass {
 | 
			
		||||
                           Error **errp);
 | 
			
		||||
 | 
			
		||||
    /* Optional callbacks */
 | 
			
		||||
    ssize_t (*io_pwritev)(QIOChannel *ioc,
 | 
			
		||||
                          const struct iovec *iov,
 | 
			
		||||
                          size_t niov,
 | 
			
		||||
                          off_t offset,
 | 
			
		||||
                          Error **errp);
 | 
			
		||||
    ssize_t (*io_preadv)(QIOChannel *ioc,
 | 
			
		||||
                         const struct iovec *iov,
 | 
			
		||||
                         size_t niov,
 | 
			
		||||
                         off_t offset,
 | 
			
		||||
                         Error **errp);
 | 
			
		||||
    int (*io_shutdown)(QIOChannel *ioc,
 | 
			
		||||
                       QIOChannelShutdown how,
 | 
			
		||||
                       Error **errp);
 | 
			
		||||
@@ -528,6 +539,104 @@ void qio_channel_set_follow_coroutine_ctx(QIOChannel *ioc, bool enabled);
 | 
			
		||||
int qio_channel_close(QIOChannel *ioc,
 | 
			
		||||
                      Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_pwritev
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @iov: the array of memory regions to write data from
 | 
			
		||||
 * @niov: the length of the @iov array
 | 
			
		||||
 * @offset: offset in the channel where writes should begin
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Not all implementations will support this facility, so may report
 | 
			
		||||
 * an error. To avoid errors, the caller may check for the feature
 | 
			
		||||
 * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
 | 
			
		||||
 *
 | 
			
		||||
 * Behaves as qio_channel_writev_full, apart from not supporting
 | 
			
		||||
 * sending of file handles as well as beginning the write at the
 | 
			
		||||
 * passed @offset
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                            size_t niov, off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_pwritev_all:
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @iov: the array of memory regions to write data from
 | 
			
		||||
 * @niov: the length of the @iov array
 | 
			
		||||
 * @offset: the iovec offset in the file where to write the data
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Returns: 0 if all bytes were written, or -1 on error
 | 
			
		||||
 */
 | 
			
		||||
int qio_channel_pwritev_all(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                            size_t niov, off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_pwrite
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @buf: the memory region to write data into
 | 
			
		||||
 * @buflen: the number of bytes to @buf
 | 
			
		||||
 * @offset: offset in the channel where writes should begin
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Not all implementations will support this facility, so may report
 | 
			
		||||
 * an error. To avoid errors, the caller may check for the feature
 | 
			
		||||
 * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
 | 
			
		||||
                           off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_preadv
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @iov: the array of memory regions to read data into
 | 
			
		||||
 * @niov: the length of the @iov array
 | 
			
		||||
 * @offset: offset in the channel where writes should begin
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Not all implementations will support this facility, so may report
 | 
			
		||||
 * an error.  To avoid errors, the caller may check for the feature
 | 
			
		||||
 * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
 | 
			
		||||
 *
 | 
			
		||||
 * Behaves as qio_channel_readv_full, apart from not supporting
 | 
			
		||||
 * receiving of file handles as well as beginning the read at the
 | 
			
		||||
 * passed @offset
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                           size_t niov, off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_preadv_all:
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @iov: the array of memory regions to read data to
 | 
			
		||||
 * @niov: the length of the @iov array
 | 
			
		||||
 * @offset: the iovec offset in the file from where to read the data
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Returns: 0 if all bytes were read, or -1 on error
 | 
			
		||||
 */
 | 
			
		||||
int qio_channel_preadv_all(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                           size_t niov, off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_pread
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 * @buf: the memory region to write data into
 | 
			
		||||
 * @buflen: the number of bytes to @buf
 | 
			
		||||
 * @offset: offset in the channel where writes should begin
 | 
			
		||||
 * @errp: pointer to a NULL-initialized error object
 | 
			
		||||
 *
 | 
			
		||||
 * Not all implementations will support this facility, so may report
 | 
			
		||||
 * an error.  To avoid errors, the caller may check for the feature
 | 
			
		||||
 * flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
 | 
			
		||||
                          off_t offset, Error **errp);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * qio_channel_shutdown:
 | 
			
		||||
 * @ioc: the channel object
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,8 @@ unsigned int qemu_get_be16(QEMUFile *f);
 | 
			
		||||
unsigned int qemu_get_be32(QEMUFile *f);
 | 
			
		||||
uint64_t qemu_get_be64(QEMUFile *f);
 | 
			
		||||
 | 
			
		||||
bool qemu_file_is_seekable(QEMUFile *f);
 | 
			
		||||
 | 
			
		||||
static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv)
 | 
			
		||||
{
 | 
			
		||||
    qemu_put_be64(f, *pv);
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,19 @@ static inline void clear_bit(long nr, unsigned long *addr)
 | 
			
		||||
    *p &= ~mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * clear_bit_atomic - Clears a bit in memory atomically
 | 
			
		||||
 * @nr: Bit to clear
 | 
			
		||||
 * @addr: Address to start counting from
 | 
			
		||||
 */
 | 
			
		||||
static inline void clear_bit_atomic(long nr, unsigned long *addr)
 | 
			
		||||
{
 | 
			
		||||
    unsigned long mask = BIT_MASK(nr);
 | 
			
		||||
    unsigned long *p = addr + BIT_WORD(nr);
 | 
			
		||||
 | 
			
		||||
    return qatomic_and(p, ~mask);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * change_bit - Toggle a bit in memory
 | 
			
		||||
 * @nr: Bit to change
 | 
			
		||||
 
 | 
			
		||||
@@ -597,6 +597,8 @@ int qemu_lock_fd_test(int fd, int64_t start, int64_t len, bool exclusive);
 | 
			
		||||
bool qemu_has_ofd_lock(void);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool qemu_has_direct_io(void);
 | 
			
		||||
 | 
			
		||||
#if defined(__HAIKU__) && defined(__i386__)
 | 
			
		||||
#define FMT_pid "%ld"
 | 
			
		||||
#elif defined(WIN64)
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,10 @@ qio_channel_file_new_fd(int fd)
 | 
			
		||||
 | 
			
		||||
    ioc->fd = fd;
 | 
			
		||||
 | 
			
		||||
    if (lseek(fd, 0, SEEK_CUR) != (off_t)-1) {
 | 
			
		||||
        qio_channel_set_feature(QIO_CHANNEL(ioc), QIO_CHANNEL_FEATURE_SEEKABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    trace_qio_channel_file_new_fd(ioc, fd);
 | 
			
		||||
 | 
			
		||||
    return ioc;
 | 
			
		||||
@@ -60,6 +64,10 @@ qio_channel_file_new_path(const char *path,
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (lseek(ioc->fd, 0, SEEK_CUR) != (off_t)-1) {
 | 
			
		||||
        qio_channel_set_feature(QIO_CHANNEL(ioc), QIO_CHANNEL_FEATURE_SEEKABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    trace_qio_channel_file_new_path(ioc, path, flags, mode, ioc->fd);
 | 
			
		||||
 | 
			
		||||
    return ioc;
 | 
			
		||||
@@ -138,6 +146,58 @@ static ssize_t qio_channel_file_writev(QIOChannel *ioc,
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_PREADV
 | 
			
		||||
static ssize_t qio_channel_file_preadv(QIOChannel *ioc,
 | 
			
		||||
                                       const struct iovec *iov,
 | 
			
		||||
                                       size_t niov,
 | 
			
		||||
                                       off_t offset,
 | 
			
		||||
                                       Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
 | 
			
		||||
 retry:
 | 
			
		||||
    ret = preadv(fioc->fd, iov, niov, offset);
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
        if (errno == EAGAIN) {
 | 
			
		||||
            return QIO_CHANNEL_ERR_BLOCK;
 | 
			
		||||
        }
 | 
			
		||||
        if (errno == EINTR) {
 | 
			
		||||
            goto retry;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        error_setg_errno(errp, errno, "Unable to read from file");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ssize_t qio_channel_file_pwritev(QIOChannel *ioc,
 | 
			
		||||
                                        const struct iovec *iov,
 | 
			
		||||
                                        size_t niov,
 | 
			
		||||
                                        off_t offset,
 | 
			
		||||
                                        Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
 | 
			
		||||
 retry:
 | 
			
		||||
    ret = pwritev(fioc->fd, iov, niov, offset);
 | 
			
		||||
    if (ret <= 0) {
 | 
			
		||||
        if (errno == EAGAIN) {
 | 
			
		||||
            return QIO_CHANNEL_ERR_BLOCK;
 | 
			
		||||
        }
 | 
			
		||||
        if (errno == EINTR) {
 | 
			
		||||
            goto retry;
 | 
			
		||||
        }
 | 
			
		||||
        error_setg_errno(errp, errno, "Unable to write to file");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
#endif /* CONFIG_PREADV */
 | 
			
		||||
 | 
			
		||||
static int qio_channel_file_set_blocking(QIOChannel *ioc,
 | 
			
		||||
                                         bool enabled,
 | 
			
		||||
                                         Error **errp)
 | 
			
		||||
@@ -157,6 +217,31 @@ static int qio_channel_file_set_blocking(QIOChannel *ioc,
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void qio_channel_file_set_direct_io(QIOChannel *ioc, bool enabled, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
 | 
			
		||||
    int flags = fcntl(fioc->fd, F_GETFL);
 | 
			
		||||
 | 
			
		||||
    if (flags == -1) {
 | 
			
		||||
        error_setg_errno(errp, errno, "Unable to read file descriptor flags");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
        flags |= O_DIRECT;
 | 
			
		||||
    } else {
 | 
			
		||||
        flags &= ~O_DIRECT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fcntl(fioc->fd, F_SETFL, flags | O_DIRECT) == -1) {
 | 
			
		||||
        error_setg_errno(errp, errno, "Unable to set file descriptor flags");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    error_setg(&errp, "System does not support O_DIRECT");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static off_t qio_channel_file_seek(QIOChannel *ioc,
 | 
			
		||||
                                   off_t offset,
 | 
			
		||||
@@ -182,6 +267,11 @@ static int qio_channel_file_close(QIOChannel *ioc,
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
 | 
			
		||||
 | 
			
		||||
    if (qemu_fdatasync(fioc->fd) < 0) {
 | 
			
		||||
        error_setg_errno(errp, errno,
 | 
			
		||||
                         "Unable to synchronize file data with storage device");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
    if (qemu_close(fioc->fd) < 0) {
 | 
			
		||||
        error_setg_errno(errp, errno,
 | 
			
		||||
                         "Unable to close file");
 | 
			
		||||
@@ -223,6 +313,10 @@ static void qio_channel_file_class_init(ObjectClass *klass,
 | 
			
		||||
    ioc_klass->io_writev = qio_channel_file_writev;
 | 
			
		||||
    ioc_klass->io_readv = qio_channel_file_readv;
 | 
			
		||||
    ioc_klass->io_set_blocking = qio_channel_file_set_blocking;
 | 
			
		||||
#ifdef CONFIG_PREADV
 | 
			
		||||
    ioc_klass->io_pwritev = qio_channel_file_pwritev;
 | 
			
		||||
    ioc_klass->io_preadv = qio_channel_file_preadv;
 | 
			
		||||
#endif
 | 
			
		||||
    ioc_klass->io_seek = qio_channel_file_seek;
 | 
			
		||||
    ioc_klass->io_close = qio_channel_file_close;
 | 
			
		||||
    ioc_klass->io_create_watch = qio_channel_file_create_watch;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								io/channel.c
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								io/channel.c
									
									
									
									
									
								
							@@ -454,6 +454,134 @@ GSource *qio_channel_add_watch_source(QIOChannel *ioc,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                            size_t niov, off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
 | 
			
		||||
 | 
			
		||||
    if (!klass->io_pwritev) {
 | 
			
		||||
        error_setg(errp, "Channel does not support pwritev");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_SEEKABLE)) {
 | 
			
		||||
        error_setg_errno(errp, EINVAL, "Requested channel is not seekable");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return klass->io_pwritev(ioc, iov, niov, offset, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int qio_channel_preadv_pwritev_contiguous(QIOChannel *ioc,
 | 
			
		||||
                                                 const struct iovec *iov,
 | 
			
		||||
                                                 size_t niov, off_t offset,
 | 
			
		||||
                                                 bool is_write, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t ret = -1;
 | 
			
		||||
    int i, slice_idx, slice_num;
 | 
			
		||||
    uintptr_t base, next, file_offset;
 | 
			
		||||
    size_t len;
 | 
			
		||||
 | 
			
		||||
    slice_idx = 0;
 | 
			
		||||
    slice_num = 1;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * If the iov array doesn't have contiguous elements, we need to
 | 
			
		||||
     * split it in slices because we only have one (file) 'offset' for
 | 
			
		||||
     * the whole iov. Do this here so callers don't need to break the
 | 
			
		||||
     * iov array themselves.
 | 
			
		||||
     */
 | 
			
		||||
    for (i = 0; i < niov; i++, slice_num++) {
 | 
			
		||||
        base = (uintptr_t) iov[i].iov_base;
 | 
			
		||||
 | 
			
		||||
        if (i != niov - 1) {
 | 
			
		||||
            len = iov[i].iov_len;
 | 
			
		||||
            next = (uintptr_t) iov[i + 1].iov_base;
 | 
			
		||||
 | 
			
		||||
            if (base + len == next) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * Use the offset of the first element of the segment that
 | 
			
		||||
         * we're sending.
 | 
			
		||||
         */
 | 
			
		||||
        file_offset = offset + (uintptr_t) iov[slice_idx].iov_base;
 | 
			
		||||
 | 
			
		||||
        if (is_write) {
 | 
			
		||||
            ret = qio_channel_pwritev(ioc, &iov[slice_idx], slice_num,
 | 
			
		||||
                                      file_offset, errp);
 | 
			
		||||
        } else {
 | 
			
		||||
            ret = qio_channel_preadv(ioc, &iov[slice_idx], slice_num,
 | 
			
		||||
                                     file_offset, errp);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ret < 0) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        slice_idx += slice_num;
 | 
			
		||||
        slice_num = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (ret < 0) ? -1 : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int qio_channel_pwritev_all(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                            size_t niov, off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    return qio_channel_preadv_pwritev_contiguous(ioc, iov, niov,
 | 
			
		||||
                                                 offset, true, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
 | 
			
		||||
                           off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct iovec iov = {
 | 
			
		||||
        .iov_base = buf,
 | 
			
		||||
        .iov_len = buflen
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return qio_channel_pwritev(ioc, &iov, 1, offset, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                           size_t niov, off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
 | 
			
		||||
 | 
			
		||||
    if (!klass->io_preadv) {
 | 
			
		||||
        error_setg(errp, "Channel does not support preadv");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_SEEKABLE)) {
 | 
			
		||||
        error_setg_errno(errp, EINVAL, "Requested channel is not seekable");
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return klass->io_preadv(ioc, iov, niov, offset, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int qio_channel_preadv_all(QIOChannel *ioc, const struct iovec *iov,
 | 
			
		||||
                           size_t niov, off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    return qio_channel_preadv_pwritev_contiguous(ioc, iov, niov,
 | 
			
		||||
                                                 offset, false, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
 | 
			
		||||
                          off_t offset, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct iovec iov = {
 | 
			
		||||
        .iov_base = buf,
 | 
			
		||||
        .iov_len = buflen
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return qio_channel_preadv(ioc, &iov, 1, offset, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int qio_channel_shutdown(QIOChannel *ioc,
 | 
			
		||||
                         QIOChannelShutdown how,
 | 
			
		||||
                         Error **errp)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,14 +19,28 @@
 | 
			
		||||
#include "fd.h"
 | 
			
		||||
#include "migration.h"
 | 
			
		||||
#include "monitor/monitor.h"
 | 
			
		||||
#include "io/channel-file.h"
 | 
			
		||||
#include "io/channel-util.h"
 | 
			
		||||
#include "options.h"
 | 
			
		||||
#include "trace.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct FdOutgoingArgs {
 | 
			
		||||
    int fd;
 | 
			
		||||
} outgoing_args;
 | 
			
		||||
 | 
			
		||||
int fd_args_get_fd(void)
 | 
			
		||||
{
 | 
			
		||||
    return outgoing_args.fd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannel *ioc;
 | 
			
		||||
    int fd = monitor_get_fd(monitor_cur(), fdname, errp);
 | 
			
		||||
 | 
			
		||||
    outgoing_args.fd = -1;
 | 
			
		||||
 | 
			
		||||
    if (fd == -1) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -38,6 +52,8 @@ void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error **
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outgoing_args.fd = fd;
 | 
			
		||||
 | 
			
		||||
    qio_channel_set_name(ioc, "migration-fd-outgoing");
 | 
			
		||||
    migration_channel_connect(s, ioc, NULL, NULL);
 | 
			
		||||
    object_unref(OBJECT(ioc));
 | 
			
		||||
@@ -73,4 +89,18 @@ void fd_start_incoming_migration(const char *fdname, Error **errp)
 | 
			
		||||
                               fd_accept_incoming_migration,
 | 
			
		||||
                               NULL, NULL,
 | 
			
		||||
                               g_main_context_get_thread_default());
 | 
			
		||||
 | 
			
		||||
    if (migrate_multifd()) {
 | 
			
		||||
        int channels = migrate_multifd_channels();
 | 
			
		||||
 | 
			
		||||
        while (channels--) {
 | 
			
		||||
            ioc = QIO_CHANNEL(qio_channel_file_new_fd(fd));
 | 
			
		||||
 | 
			
		||||
            qio_channel_set_name(ioc, "migration-fd-incoming");
 | 
			
		||||
            qio_channel_add_watch_full(ioc, G_IO_IN,
 | 
			
		||||
                                       fd_accept_incoming_migration,
 | 
			
		||||
                                       NULL, NULL,
 | 
			
		||||
                                       g_main_context_get_thread_default());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,4 +20,5 @@ void fd_start_incoming_migration(const char *fdname, Error **errp);
 | 
			
		||||
 | 
			
		||||
void fd_start_outgoing_migration(MigrationState *s, const char *fdname,
 | 
			
		||||
                                 Error **errp);
 | 
			
		||||
int fd_args_get_fd(void);
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										197
									
								
								migration/file.c
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								migration/file.c
									
									
									
									
									
								
							@@ -7,16 +7,26 @@
 | 
			
		||||
 | 
			
		||||
#include "qemu/osdep.h"
 | 
			
		||||
#include "qemu/cutils.h"
 | 
			
		||||
#include "qemu/error-report.h"
 | 
			
		||||
#include "qapi/error.h"
 | 
			
		||||
#include "qapi/qapi-commands-misc.h"
 | 
			
		||||
#include "channel.h"
 | 
			
		||||
#include "fd.h"
 | 
			
		||||
#include "file.h"
 | 
			
		||||
#include "migration.h"
 | 
			
		||||
#include "io/channel-file.h"
 | 
			
		||||
#include "io/channel-util.h"
 | 
			
		||||
#include "monitor/monitor.h"
 | 
			
		||||
#include "options.h"
 | 
			
		||||
#include "trace.h"
 | 
			
		||||
 | 
			
		||||
#define OFFSET_OPTION ",offset="
 | 
			
		||||
 | 
			
		||||
static struct FileOutgoingArgs {
 | 
			
		||||
    char *fname;
 | 
			
		||||
    int64_t fdset_id;
 | 
			
		||||
} outgoing_args;
 | 
			
		||||
 | 
			
		||||
/* Remove the offset option from @filespec and return it in @offsetp. */
 | 
			
		||||
 | 
			
		||||
int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp)
 | 
			
		||||
@@ -36,6 +46,145 @@ int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp)
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * If the open flags and file status flags from the file descriptors
 | 
			
		||||
 * in the fdset don't match what QEMU expects, errno gets set to
 | 
			
		||||
 * EACCES. Let's provide a more user-friendly message.
 | 
			
		||||
 */
 | 
			
		||||
static void file_fdset_error(int flags, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    ERRP_GUARD();
 | 
			
		||||
 | 
			
		||||
    if (errno == EACCES) {
 | 
			
		||||
        /* ditch the previous error */
 | 
			
		||||
        error_free(*errp);
 | 
			
		||||
        *errp = NULL;
 | 
			
		||||
 | 
			
		||||
        error_setg(errp, "Fdset is missing a file descriptor with flags: 0x%x",
 | 
			
		||||
                   flags);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void file_remove_fdset(void)
 | 
			
		||||
{
 | 
			
		||||
    if (outgoing_args.fdset_id != -1) {
 | 
			
		||||
        qmp_remove_fd(outgoing_args.fdset_id, false, -1, NULL);
 | 
			
		||||
        outgoing_args.fdset_id = -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Due to the behavior of the dup() system call, we need the fdset to
 | 
			
		||||
 * have two non-duplicate fds so we can enable direct IO in the
 | 
			
		||||
 * secondary channels without affecting the main channel.
 | 
			
		||||
 */
 | 
			
		||||
static bool file_parse_fdset(const char *filename, int64_t *fdset_id,
 | 
			
		||||
                             Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    FdsetInfoList *fds_info;
 | 
			
		||||
    FdsetFdInfoList *fd_info;
 | 
			
		||||
    const char *fdset_id_str;
 | 
			
		||||
    int nfds = 0;
 | 
			
		||||
 | 
			
		||||
    *fdset_id = -1;
 | 
			
		||||
 | 
			
		||||
    if (!strstart(filename, "/dev/fdset/", &fdset_id_str)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd()) {
 | 
			
		||||
        error_setg(errp, "fdset is only supported with multifd");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    *fdset_id = qemu_parse_fd(fdset_id_str);
 | 
			
		||||
 | 
			
		||||
    for (fds_info = qmp_query_fdsets(NULL); fds_info;
 | 
			
		||||
         fds_info = fds_info->next) {
 | 
			
		||||
 | 
			
		||||
        if (*fdset_id != fds_info->value->fdset_id) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (fd_info = fds_info->value->fds; fd_info; fd_info = fd_info->next) {
 | 
			
		||||
            if (nfds++ > 2) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (nfds != 2) {
 | 
			
		||||
        error_setg(errp, "Outgoing migration needs two fds in the fdset, "
 | 
			
		||||
                   "got %d", nfds);
 | 
			
		||||
        qmp_remove_fd(*fdset_id, false, -1, NULL);
 | 
			
		||||
        *fdset_id = -1;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void qio_channel_file_connect_worker(QIOTask *task, gpointer opaque)
 | 
			
		||||
{
 | 
			
		||||
    /* noop */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int file_send_channel_destroy(QIOChannel *ioc)
 | 
			
		||||
{
 | 
			
		||||
    if (ioc) {
 | 
			
		||||
        qio_channel_close(ioc, NULL);
 | 
			
		||||
        object_unref(OBJECT(ioc));
 | 
			
		||||
    }
 | 
			
		||||
    g_free(outgoing_args.fname);
 | 
			
		||||
    outgoing_args.fname = NULL;
 | 
			
		||||
 | 
			
		||||
    file_remove_fdset();
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void file_send_channel_create(QIOTaskFunc f, void *data)
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelFile *ioc = NULL;
 | 
			
		||||
    QIOTask *task;
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
    int flags = O_WRONLY;
 | 
			
		||||
    int fd = fd_args_get_fd();
 | 
			
		||||
 | 
			
		||||
    if (migrate_direct_io()) {
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
        /*
 | 
			
		||||
         * Enable O_DIRECT for the secondary channels. These are used
 | 
			
		||||
         * for sending ram pages and writes should be guaranteed to be
 | 
			
		||||
         * aligned to at least page size.
 | 
			
		||||
         */
 | 
			
		||||
        flags |= O_DIRECT;
 | 
			
		||||
#else
 | 
			
		||||
        error_setg(&err, "System does not support O_DIRECT");
 | 
			
		||||
        error_append_hint(&err,
 | 
			
		||||
                          "Try disabling direct-io migration capability\n");
 | 
			
		||||
        /* errors are propagated through the qio_task below */
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!err) {
 | 
			
		||||
        if (fd && fd != -1) {
 | 
			
		||||
            ioc = qio_channel_file_new_fd(fd);
 | 
			
		||||
        } else {
 | 
			
		||||
            ioc = qio_channel_file_new_path(outgoing_args.fname, flags, 0, &err);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    task = qio_task_new(OBJECT(ioc), f, (gpointer)data, NULL);
 | 
			
		||||
    if (!ioc) {
 | 
			
		||||
        file_fdset_error(flags, &err);
 | 
			
		||||
        qio_task_set_error(task, err);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qio_task_run_in_thread(task, qio_channel_file_connect_worker,
 | 
			
		||||
                           (gpointer)data, NULL, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void file_start_outgoing_migration(MigrationState *s,
 | 
			
		||||
                                   FileMigrationArgs *file_args, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
@@ -43,12 +192,20 @@ void file_start_outgoing_migration(MigrationState *s,
 | 
			
		||||
    g_autofree char *filename = g_strdup(file_args->filename);
 | 
			
		||||
    uint64_t offset = file_args->offset;
 | 
			
		||||
    QIOChannel *ioc;
 | 
			
		||||
    int flags = O_CREAT | O_TRUNC | O_WRONLY;
 | 
			
		||||
    mode_t mode = 0660;
 | 
			
		||||
 | 
			
		||||
    trace_migration_file_outgoing(filename);
 | 
			
		||||
 | 
			
		||||
    fioc = qio_channel_file_new_path(filename, O_CREAT | O_WRONLY | O_TRUNC,
 | 
			
		||||
                                     0600, errp);
 | 
			
		||||
    if (!file_parse_fdset(filename, &outgoing_args.fdset_id, errp)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outgoing_args.fname = g_strdup(filename);
 | 
			
		||||
 | 
			
		||||
    fioc = qio_channel_file_new_path(filename, flags, mode, errp);
 | 
			
		||||
    if (!fioc) {
 | 
			
		||||
        file_fdset_error(flags, errp);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -74,22 +231,40 @@ void file_start_incoming_migration(FileMigrationArgs *file_args, Error **errp)
 | 
			
		||||
    g_autofree char *filename = g_strdup(file_args->filename);
 | 
			
		||||
    QIOChannelFile *fioc = NULL;
 | 
			
		||||
    uint64_t offset = file_args->offset;
 | 
			
		||||
    QIOChannel *ioc;
 | 
			
		||||
    int channels = 1;
 | 
			
		||||
    int i = 0, fd, flags = O_RDONLY;
 | 
			
		||||
 | 
			
		||||
    trace_migration_file_incoming(filename);
 | 
			
		||||
 | 
			
		||||
    fioc = qio_channel_file_new_path(filename, O_RDONLY, 0, errp);
 | 
			
		||||
    fioc = qio_channel_file_new_path(filename, flags, 0, errp);
 | 
			
		||||
    if (!fioc) {
 | 
			
		||||
        file_fdset_error(flags, errp);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ioc = QIO_CHANNEL(fioc);
 | 
			
		||||
    if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
 | 
			
		||||
    if (offset &&
 | 
			
		||||
        qio_channel_io_seek(QIO_CHANNEL(fioc), offset, SEEK_SET, errp) < 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migrate_multifd()) {
 | 
			
		||||
        channels += migrate_multifd_channels();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fd = fioc->fd;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        QIOChannel *ioc = QIO_CHANNEL(fioc);
 | 
			
		||||
 | 
			
		||||
        qio_channel_set_name(ioc, "migration-file-incoming");
 | 
			
		||||
        qio_channel_add_watch_full(ioc, G_IO_IN,
 | 
			
		||||
                                   file_accept_incoming_migration,
 | 
			
		||||
                                   NULL, NULL,
 | 
			
		||||
                                   g_main_context_get_thread_default());
 | 
			
		||||
    } while (++i < channels && (fioc = qio_channel_file_new_fd(fd)));
 | 
			
		||||
 | 
			
		||||
    if (!fioc) {
 | 
			
		||||
        error_setg(errp, "Error creating migration incoming channel");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    qio_channel_set_name(QIO_CHANNEL(ioc), "migration-file-incoming");
 | 
			
		||||
    qio_channel_add_watch_full(ioc, G_IO_IN,
 | 
			
		||||
                               file_accept_incoming_migration,
 | 
			
		||||
                               NULL, NULL,
 | 
			
		||||
                               g_main_context_get_thread_default());
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,15 @@
 | 
			
		||||
#define QEMU_MIGRATION_FILE_H
 | 
			
		||||
 | 
			
		||||
#include "qapi/qapi-types-migration.h"
 | 
			
		||||
#include "io/task.h"
 | 
			
		||||
#include "channel.h"
 | 
			
		||||
 | 
			
		||||
void file_start_incoming_migration(FileMigrationArgs *file_args, Error **errp);
 | 
			
		||||
 | 
			
		||||
void file_start_outgoing_migration(MigrationState *s,
 | 
			
		||||
                                   FileMigrationArgs *file_args, Error **errp);
 | 
			
		||||
int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp);
 | 
			
		||||
 | 
			
		||||
void file_send_channel_create(QIOTaskFunc f, void *data);
 | 
			
		||||
int file_send_channel_destroy(QIOChannel *ioc);
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -392,6 +392,13 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
 | 
			
		||||
        monitor_printf(mon, "%s: %s\n",
 | 
			
		||||
            MigrationParameter_str(MIGRATION_PARAMETER_MODE),
 | 
			
		||||
            qapi_enum_lookup(&MigMode_lookup, params->mode));
 | 
			
		||||
 | 
			
		||||
        if (params->has_direct_io) {
 | 
			
		||||
            monitor_printf(mon, "%s: %s\n",
 | 
			
		||||
                           MigrationParameter_str(
 | 
			
		||||
                               MIGRATION_PARAMETER_DIRECT_IO),
 | 
			
		||||
                           params->direct_io ? "on" : "off");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qapi_free_MigrationParameters(params);
 | 
			
		||||
@@ -679,6 +686,10 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict)
 | 
			
		||||
        p->has_mode = true;
 | 
			
		||||
        visit_type_MigMode(v, param, &p->mode, &err);
 | 
			
		||||
        break;
 | 
			
		||||
    case MIGRATION_PARAMETER_DIRECT_IO:
 | 
			
		||||
        p->has_direct_io = true;
 | 
			
		||||
        visit_type_bool(v, param, &p->direct_io, &err);
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        assert(0);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -128,24 +128,88 @@ static bool migration_needs_multiple_sockets(void)
 | 
			
		||||
    return migrate_multifd() || migrate_postcopy_preempt();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool transport_supports_multi_channels(SocketAddress *saddr)
 | 
			
		||||
static bool transport_supports_multi_channels(MigrationAddress *addr)
 | 
			
		||||
{
 | 
			
		||||
    return saddr->type == SOCKET_ADDRESS_TYPE_INET ||
 | 
			
		||||
           saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
 | 
			
		||||
           saddr->type == SOCKET_ADDRESS_TYPE_VSOCK;
 | 
			
		||||
    if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
 | 
			
		||||
        SocketAddress *saddr = &addr->u.socket;
 | 
			
		||||
 | 
			
		||||
        if (saddr->type == SOCKET_ADDRESS_TYPE_FD) {
 | 
			
		||||
            return migrate_fixed_ram();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (saddr->type == SOCKET_ADDRESS_TYPE_INET ||
 | 
			
		||||
                saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
 | 
			
		||||
                saddr->type == SOCKET_ADDRESS_TYPE_VSOCK);
 | 
			
		||||
    } else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
 | 
			
		||||
        return migrate_fixed_ram();
 | 
			
		||||
    } else {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool migration_needs_seekable_channel(void)
 | 
			
		||||
{
 | 
			
		||||
    return migrate_fixed_ram();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool migration_needs_multiple_fds(void)
 | 
			
		||||
{
 | 
			
		||||
    /*
 | 
			
		||||
     * When doing direct-io, multifd requires two different,
 | 
			
		||||
     * non-duplicated file descriptors so we can use one of them for
 | 
			
		||||
     * unaligned IO.
 | 
			
		||||
     */
 | 
			
		||||
    return migrate_multifd() && migrate_direct_io();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool transport_supports_seeking(MigrationAddress *addr)
 | 
			
		||||
{
 | 
			
		||||
    if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * At this point, the user might not yet have passed the file
 | 
			
		||||
     * descriptor to QEMU, so we cannot know for sure whether it
 | 
			
		||||
     * refers to a plain file or a socket. Let it through anyway.
 | 
			
		||||
     */
 | 
			
		||||
    if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
 | 
			
		||||
        return addr->u.socket.type == SOCKET_ADDRESS_TYPE_FD;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool transport_supports_multiple_fds(MigrationAddress *addr)
 | 
			
		||||
{
 | 
			
		||||
    /* file: works because QEMU can open it multiple times */
 | 
			
		||||
    return addr->transport == MIGRATION_ADDRESS_TYPE_FILE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
migration_channels_and_transport_compatible(MigrationAddress *addr,
 | 
			
		||||
                                            Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    if (migration_needs_seekable_channel() &&
 | 
			
		||||
        !transport_supports_seeking(addr)) {
 | 
			
		||||
        error_setg(errp, "Migration requires seekable transport (e.g. file)");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migration_needs_multiple_sockets() &&
 | 
			
		||||
        (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) &&
 | 
			
		||||
        !transport_supports_multi_channels(&addr->u.socket)) {
 | 
			
		||||
        !transport_supports_multi_channels(addr)) {
 | 
			
		||||
        error_setg(errp, "Migration requires multi-channel URIs (e.g. tcp)");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migration_needs_multiple_fds() &&
 | 
			
		||||
        !transport_supports_multiple_fds(addr)) {
 | 
			
		||||
        error_setg(errp,
 | 
			
		||||
                   "Migration with direct-io is incompatible with the fd: URI,"
 | 
			
		||||
                   " use file: instead");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -314,6 +378,46 @@ static void migrate_generate_event(int new_state)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void migration_direct_io_start(QEMUFile *file, off_t align)
 | 
			
		||||
{
 | 
			
		||||
    off_t base;
 | 
			
		||||
 | 
			
		||||
    if (!migrate_direct_io() || migrate_multifd()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    base = qemu_get_offset(file);
 | 
			
		||||
    if (base < 0 || qemu_file_get_error(file)) {
 | 
			
		||||
        goto err_align;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qemu_set_offset(file, ROUND_UP(base, align), SEEK_SET);
 | 
			
		||||
 | 
			
		||||
    if (qemu_file_get_error(file)) {
 | 
			
		||||
        goto err_align;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!qemu_file_set_direct_io(file, true)) {
 | 
			
		||||
        error_report("Failed to enable direct-io");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
err_align:
 | 
			
		||||
    error_report("Failed to align stream for direct-io");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void migration_direct_io_finish(QEMUFile *file)
 | 
			
		||||
{
 | 
			
		||||
    if (!migrate_direct_io() || migrate_multifd()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!qemu_file_set_direct_io(file, false)) {
 | 
			
		||||
        error_report("Failed to disable direct-io");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Send a message on the return channel back to the source
 | 
			
		||||
 * of the migration.
 | 
			
		||||
@@ -822,7 +926,8 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
 | 
			
		||||
    uint32_t channel_magic = 0;
 | 
			
		||||
    int ret = 0;
 | 
			
		||||
 | 
			
		||||
    if (migrate_multifd() && !migrate_postcopy_ram() &&
 | 
			
		||||
    if (migrate_multifd() && migrate_multifd_packets() &&
 | 
			
		||||
        !migrate_postcopy_ram() &&
 | 
			
		||||
        qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK)) {
 | 
			
		||||
        /*
 | 
			
		||||
         * With multiple channels, it is possible that we receive channels
 | 
			
		||||
 
 | 
			
		||||
@@ -550,4 +550,7 @@ void migration_rp_kick(MigrationState *s);
 | 
			
		||||
 | 
			
		||||
int migration_stop_vm(RunState state);
 | 
			
		||||
 | 
			
		||||
void migration_direct_io_start(QEMUFile *file, off_t align);
 | 
			
		||||
void migration_direct_io_finish(QEMUFile *file);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ static int zlib_send_setup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
        err_msg = "out of memory for buf";
 | 
			
		||||
        goto err_free_zbuff;
 | 
			
		||||
    }
 | 
			
		||||
    p->data = z;
 | 
			
		||||
    p->compress_data = z;
 | 
			
		||||
    return 0;
 | 
			
		||||
 | 
			
		||||
err_free_zbuff:
 | 
			
		||||
@@ -92,15 +92,15 @@ err_free_z:
 | 
			
		||||
 */
 | 
			
		||||
static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct zlib_data *z = p->data;
 | 
			
		||||
    struct zlib_data *z = p->compress_data;
 | 
			
		||||
 | 
			
		||||
    deflateEnd(&z->zs);
 | 
			
		||||
    g_free(z->zbuff);
 | 
			
		||||
    z->zbuff = NULL;
 | 
			
		||||
    g_free(z->buf);
 | 
			
		||||
    z->buf = NULL;
 | 
			
		||||
    g_free(p->data);
 | 
			
		||||
    p->data = NULL;
 | 
			
		||||
    g_free(p->compress_data);
 | 
			
		||||
    p->compress_data = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -116,7 +116,7 @@ static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
 */
 | 
			
		||||
static int zlib_send_prepare(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct zlib_data *z = p->data;
 | 
			
		||||
    struct zlib_data *z = p->compress_data;
 | 
			
		||||
    z_stream *zs = &z->zs;
 | 
			
		||||
    uint32_t out_size = 0;
 | 
			
		||||
    int ret;
 | 
			
		||||
@@ -189,7 +189,7 @@ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
    struct zlib_data *z = g_new0(struct zlib_data, 1);
 | 
			
		||||
    z_stream *zs = &z->zs;
 | 
			
		||||
 | 
			
		||||
    p->data = z;
 | 
			
		||||
    p->compress_data = z;
 | 
			
		||||
    zs->zalloc = Z_NULL;
 | 
			
		||||
    zs->zfree = Z_NULL;
 | 
			
		||||
    zs->opaque = Z_NULL;
 | 
			
		||||
@@ -219,13 +219,13 @@ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
 */
 | 
			
		||||
static void zlib_recv_cleanup(MultiFDRecvParams *p)
 | 
			
		||||
{
 | 
			
		||||
    struct zlib_data *z = p->data;
 | 
			
		||||
    struct zlib_data *z = p->compress_data;
 | 
			
		||||
 | 
			
		||||
    inflateEnd(&z->zs);
 | 
			
		||||
    g_free(z->zbuff);
 | 
			
		||||
    z->zbuff = NULL;
 | 
			
		||||
    g_free(p->data);
 | 
			
		||||
    p->data = NULL;
 | 
			
		||||
    g_free(p->compress_data);
 | 
			
		||||
    p->compress_data = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -241,7 +241,7 @@ static void zlib_recv_cleanup(MultiFDRecvParams *p)
 | 
			
		||||
 */
 | 
			
		||||
static int zlib_recv_pages(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct zlib_data *z = p->data;
 | 
			
		||||
    struct zlib_data *z = p->compress_data;
 | 
			
		||||
    z_stream *zs = &z->zs;
 | 
			
		||||
    uint32_t in_size = p->next_packet_size;
 | 
			
		||||
    /* we measure the change of total_out */
 | 
			
		||||
@@ -314,7 +314,7 @@ static MultiFDMethods multifd_zlib_ops = {
 | 
			
		||||
    .send_prepare = zlib_send_prepare,
 | 
			
		||||
    .recv_setup = zlib_recv_setup,
 | 
			
		||||
    .recv_cleanup = zlib_recv_cleanup,
 | 
			
		||||
    .recv_pages = zlib_recv_pages
 | 
			
		||||
    .recv_data = zlib_recv_pages
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void multifd_zlib_register(void)
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
    struct zstd_data *z = g_new0(struct zstd_data, 1);
 | 
			
		||||
    int res;
 | 
			
		||||
 | 
			
		||||
    p->data = z;
 | 
			
		||||
    p->compress_data = z;
 | 
			
		||||
    z->zcs = ZSTD_createCStream();
 | 
			
		||||
    if (!z->zcs) {
 | 
			
		||||
        g_free(z);
 | 
			
		||||
@@ -90,14 +90,14 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
 */
 | 
			
		||||
static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct zstd_data *z = p->data;
 | 
			
		||||
    struct zstd_data *z = p->compress_data;
 | 
			
		||||
 | 
			
		||||
    ZSTD_freeCStream(z->zcs);
 | 
			
		||||
    z->zcs = NULL;
 | 
			
		||||
    g_free(z->zbuff);
 | 
			
		||||
    z->zbuff = NULL;
 | 
			
		||||
    g_free(p->data);
 | 
			
		||||
    p->data = NULL;
 | 
			
		||||
    g_free(p->compress_data);
 | 
			
		||||
    p->compress_data = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -113,7 +113,7 @@ static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
 */
 | 
			
		||||
static int zstd_send_prepare(MultiFDSendParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    struct zstd_data *z = p->data;
 | 
			
		||||
    struct zstd_data *z = p->compress_data;
 | 
			
		||||
    int ret;
 | 
			
		||||
    uint32_t i;
 | 
			
		||||
 | 
			
		||||
@@ -178,7 +178,7 @@ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
    struct zstd_data *z = g_new0(struct zstd_data, 1);
 | 
			
		||||
    int ret;
 | 
			
		||||
 | 
			
		||||
    p->data = z;
 | 
			
		||||
    p->compress_data = z;
 | 
			
		||||
    z->zds = ZSTD_createDStream();
 | 
			
		||||
    if (!z->zds) {
 | 
			
		||||
        g_free(z);
 | 
			
		||||
@@ -216,14 +216,14 @@ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
 */
 | 
			
		||||
static void zstd_recv_cleanup(MultiFDRecvParams *p)
 | 
			
		||||
{
 | 
			
		||||
    struct zstd_data *z = p->data;
 | 
			
		||||
    struct zstd_data *z = p->compress_data;
 | 
			
		||||
 | 
			
		||||
    ZSTD_freeDStream(z->zds);
 | 
			
		||||
    z->zds = NULL;
 | 
			
		||||
    g_free(z->zbuff);
 | 
			
		||||
    z->zbuff = NULL;
 | 
			
		||||
    g_free(p->data);
 | 
			
		||||
    p->data = NULL;
 | 
			
		||||
    g_free(p->compress_data);
 | 
			
		||||
    p->compress_data = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -243,7 +243,7 @@ static int zstd_recv_pages(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
    uint32_t out_size = 0;
 | 
			
		||||
    uint32_t expected_size = p->normal_num * p->page_size;
 | 
			
		||||
    uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK;
 | 
			
		||||
    struct zstd_data *z = p->data;
 | 
			
		||||
    struct zstd_data *z = p->compress_data;
 | 
			
		||||
    int ret;
 | 
			
		||||
    int i;
 | 
			
		||||
 | 
			
		||||
@@ -305,7 +305,7 @@ static MultiFDMethods multifd_zstd_ops = {
 | 
			
		||||
    .send_prepare = zstd_send_prepare,
 | 
			
		||||
    .recv_setup = zstd_recv_setup,
 | 
			
		||||
    .recv_cleanup = zstd_recv_cleanup,
 | 
			
		||||
    .recv_pages = zstd_recv_pages
 | 
			
		||||
    .recv_data = zstd_recv_pages
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void multifd_zstd_register(void)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
#include "exec/ramblock.h"
 | 
			
		||||
#include "qemu/error-report.h"
 | 
			
		||||
#include "qapi/error.h"
 | 
			
		||||
#include "file.h"
 | 
			
		||||
#include "ram.h"
 | 
			
		||||
#include "migration.h"
 | 
			
		||||
#include "migration-stats.h"
 | 
			
		||||
@@ -28,6 +29,7 @@
 | 
			
		||||
#include "threadinfo.h"
 | 
			
		||||
#include "options.h"
 | 
			
		||||
#include "qemu/yank.h"
 | 
			
		||||
#include "io/channel-file.h"
 | 
			
		||||
#include "io/channel-socket.h"
 | 
			
		||||
#include "yank_functions.h"
 | 
			
		||||
 | 
			
		||||
@@ -128,7 +130,7 @@ static void nocomp_recv_cleanup(MultiFDRecvParams *p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * nocomp_recv_pages: read the data from the channel into actual pages
 | 
			
		||||
 * nocomp_recv_data: read the data from the channel
 | 
			
		||||
 *
 | 
			
		||||
 * For no compression we just need to read things into the correct place.
 | 
			
		||||
 *
 | 
			
		||||
@@ -137,20 +139,39 @@ static void nocomp_recv_cleanup(MultiFDRecvParams *p)
 | 
			
		||||
 * @p: Params for the channel that we are using
 | 
			
		||||
 * @errp: pointer to an error
 | 
			
		||||
 */
 | 
			
		||||
static int nocomp_recv_pages(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
static int nocomp_recv_data(MultiFDRecvParams *p, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK;
 | 
			
		||||
    ERRP_GUARD();
 | 
			
		||||
 | 
			
		||||
    if (flags != MULTIFD_FLAG_NOCOMP) {
 | 
			
		||||
        error_setg(errp, "multifd %u: flags received %x flags expected %x",
 | 
			
		||||
                   p->id, flags, MULTIFD_FLAG_NOCOMP);
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
    for (int i = 0; i < p->normal_num; i++) {
 | 
			
		||||
        p->iov[i].iov_base = p->host + p->normal[i];
 | 
			
		||||
        p->iov[i].iov_len = p->page_size;
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd_packets()) {
 | 
			
		||||
        MultiFDRecvData *data = p->data;
 | 
			
		||||
        size_t ret;
 | 
			
		||||
 | 
			
		||||
        ret = qio_channel_pread(p->c, (char *) data->opaque,
 | 
			
		||||
                                data->size, data->file_offset, errp);
 | 
			
		||||
        if (ret != data->size) {
 | 
			
		||||
            error_prepend(errp,
 | 
			
		||||
                          "multifd recv (%u): read 0x%zx, expected 0x%zx",
 | 
			
		||||
                          p->id, ret, data->size);
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    } else {
 | 
			
		||||
        for (int i = 0; i < p->normal_num; i++) {
 | 
			
		||||
            p->iov[i].iov_base = p->host + p->normal[i];
 | 
			
		||||
            p->iov[i].iov_len = p->page_size;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return qio_channel_readv_all(p->c, p->iov, p->normal_num, errp);
 | 
			
		||||
    }
 | 
			
		||||
    return qio_channel_readv_all(p->c, p->iov, p->normal_num, errp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static MultiFDMethods multifd_nocomp_ops = {
 | 
			
		||||
@@ -159,7 +180,7 @@ static MultiFDMethods multifd_nocomp_ops = {
 | 
			
		||||
    .send_prepare = nocomp_send_prepare,
 | 
			
		||||
    .recv_setup = nocomp_recv_setup,
 | 
			
		||||
    .recv_cleanup = nocomp_recv_cleanup,
 | 
			
		||||
    .recv_pages = nocomp_recv_pages
 | 
			
		||||
    .recv_data = nocomp_recv_data
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static MultiFDMethods *multifd_ops[MULTIFD_COMPRESSION__MAX] = {
 | 
			
		||||
@@ -257,6 +278,17 @@ static void multifd_pages_clear(MultiFDPages_t *pages)
 | 
			
		||||
    g_free(pages);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_set_file_bitmap(MultiFDSendParams *p)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDPages_t *pages = p->pages;
 | 
			
		||||
 | 
			
		||||
    assert(pages->block);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < p->normal_num; i++) {
 | 
			
		||||
        ramblock_set_shadow_bmap_atomic(pages->block, pages->offset[i]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_send_fill_packet(MultiFDSendParams *p)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDPacket_t *packet = p->packet;
 | 
			
		||||
@@ -511,7 +543,11 @@ static void multifd_send_terminate_threads(Error *err)
 | 
			
		||||
 | 
			
		||||
static int multifd_send_channel_destroy(QIOChannel *send)
 | 
			
		||||
{
 | 
			
		||||
    return socket_send_channel_destroy(send);
 | 
			
		||||
    if (migrate_to_file()) {
 | 
			
		||||
        return file_send_channel_destroy(send);
 | 
			
		||||
    } else {
 | 
			
		||||
        return socket_send_channel_destroy(send);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void multifd_save_cleanup(void)
 | 
			
		||||
@@ -599,6 +635,34 @@ int multifd_send_sync_main(QEMUFile *f)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd_packets()) {
 | 
			
		||||
        /*
 | 
			
		||||
         * There's no sync packet to send. Just make sure the sending
 | 
			
		||||
         * above has finished.
 | 
			
		||||
         */
 | 
			
		||||
        for (i = 0; i < migrate_multifd_channels(); i++) {
 | 
			
		||||
            qemu_sem_wait(&multifd_send_state->channels_ready);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* sanity check and release the channels */
 | 
			
		||||
        for (i = 0; i < migrate_multifd_channels(); i++) {
 | 
			
		||||
            MultiFDSendParams *p = &multifd_send_state->params[i];
 | 
			
		||||
 | 
			
		||||
            qemu_mutex_lock(&p->mutex);
 | 
			
		||||
            if (p->quit) {
 | 
			
		||||
                error_report("%s: channel %d has already quit!", __func__, i);
 | 
			
		||||
                qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
                return -1;
 | 
			
		||||
            }
 | 
			
		||||
            assert(!p->pending_job);
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
 | 
			
		||||
            qemu_sem_post(&p->sem);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * When using zero-copy, it's necessary to flush the pages before any of
 | 
			
		||||
     * the pages can be sent again, so we'll make sure the new version of the
 | 
			
		||||
@@ -654,18 +718,22 @@ static void *multifd_send_thread(void *opaque)
 | 
			
		||||
    Error *local_err = NULL;
 | 
			
		||||
    int ret = 0;
 | 
			
		||||
    bool use_zero_copy_send = migrate_zero_copy_send();
 | 
			
		||||
    bool use_packets = migrate_multifd_packets();
 | 
			
		||||
 | 
			
		||||
    thread = migration_threads_add(p->name, qemu_get_thread_id());
 | 
			
		||||
 | 
			
		||||
    trace_multifd_send_thread_start(p->id);
 | 
			
		||||
    rcu_register_thread();
 | 
			
		||||
 | 
			
		||||
    if (multifd_send_initial_packet(p, &local_err) < 0) {
 | 
			
		||||
        ret = -1;
 | 
			
		||||
        goto out;
 | 
			
		||||
    if (use_packets) {
 | 
			
		||||
        if (multifd_send_initial_packet(p, &local_err) < 0) {
 | 
			
		||||
            ret = -1;
 | 
			
		||||
            goto out;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* initial packet */
 | 
			
		||||
        p->num_packets = 1;
 | 
			
		||||
    }
 | 
			
		||||
    /* initial packet */
 | 
			
		||||
    p->num_packets = 1;
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
        qemu_sem_post(&multifd_send_state->channels_ready);
 | 
			
		||||
@@ -677,11 +745,12 @@ static void *multifd_send_thread(void *opaque)
 | 
			
		||||
        qemu_mutex_lock(&p->mutex);
 | 
			
		||||
 | 
			
		||||
        if (p->pending_job) {
 | 
			
		||||
            uint64_t packet_num = p->packet_num;
 | 
			
		||||
            uint32_t flags;
 | 
			
		||||
            uintptr_t write_base;
 | 
			
		||||
 | 
			
		||||
            p->normal_num = 0;
 | 
			
		||||
 | 
			
		||||
            if (use_zero_copy_send) {
 | 
			
		||||
            if (!use_packets || use_zero_copy_send) {
 | 
			
		||||
                p->iovs_num = 0;
 | 
			
		||||
            } else {
 | 
			
		||||
                p->iovs_num = 1;
 | 
			
		||||
@@ -699,33 +768,53 @@ static void *multifd_send_thread(void *opaque)
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            multifd_send_fill_packet(p);
 | 
			
		||||
 | 
			
		||||
            if (use_packets) {
 | 
			
		||||
                multifd_send_fill_packet(p);
 | 
			
		||||
                p->num_packets++;
 | 
			
		||||
            } else {
 | 
			
		||||
                multifd_set_file_bitmap(p);
 | 
			
		||||
 | 
			
		||||
                /*
 | 
			
		||||
                 * If we subtract the host page now, we don't need to
 | 
			
		||||
                 * pass it into qio_channel_pwritev_all() below.
 | 
			
		||||
                 */
 | 
			
		||||
                write_base = p->pages->block->pages_offset -
 | 
			
		||||
                    (uintptr_t)p->pages->block->host;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            flags = p->flags;
 | 
			
		||||
            p->flags = 0;
 | 
			
		||||
            p->num_packets++;
 | 
			
		||||
            p->total_normal_pages += p->normal_num;
 | 
			
		||||
            p->pages->num = 0;
 | 
			
		||||
            p->pages->block = NULL;
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
 | 
			
		||||
            trace_multifd_send(p->id, packet_num, p->normal_num, flags,
 | 
			
		||||
            trace_multifd_send(p->id, p->packet_num, p->normal_num, flags,
 | 
			
		||||
                               p->next_packet_size);
 | 
			
		||||
 | 
			
		||||
            if (use_zero_copy_send) {
 | 
			
		||||
                /* Send header first, without zerocopy */
 | 
			
		||||
                ret = qio_channel_write_all(p->c, (void *)p->packet,
 | 
			
		||||
                                            p->packet_len, &local_err);
 | 
			
		||||
                if (ret != 0) {
 | 
			
		||||
                    break;
 | 
			
		||||
            if (use_packets) {
 | 
			
		||||
                if (use_zero_copy_send) {
 | 
			
		||||
                    /* Send header first, without zerocopy */
 | 
			
		||||
                    ret = qio_channel_write_all(p->c, (void *)p->packet,
 | 
			
		||||
                                                p->packet_len, &local_err);
 | 
			
		||||
                    if (ret != 0) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    /* Send header using the same writev call */
 | 
			
		||||
                    p->iov[0].iov_len = p->packet_len;
 | 
			
		||||
                    p->iov[0].iov_base = p->packet;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ret = qio_channel_writev_full_all(p->c, p->iov, p->iovs_num,
 | 
			
		||||
                                                  NULL, 0, p->write_flags,
 | 
			
		||||
                                                  &local_err);
 | 
			
		||||
            } else {
 | 
			
		||||
                /* Send header using the same writev call */
 | 
			
		||||
                p->iov[0].iov_len = p->packet_len;
 | 
			
		||||
                p->iov[0].iov_base = p->packet;
 | 
			
		||||
                ret = qio_channel_pwritev_all(p->c, p->iov, p->iovs_num,
 | 
			
		||||
                                              write_base, &local_err);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ret = qio_channel_writev_full_all(p->c, p->iov, p->iovs_num, NULL,
 | 
			
		||||
                                              0, p->write_flags, &local_err);
 | 
			
		||||
            if (ret != 0) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
@@ -858,8 +947,7 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_new_send_channel_cleanup(MultiFDSendParams *p,
 | 
			
		||||
                                             QIOChannel *ioc, Error *err)
 | 
			
		||||
static void multifd_new_send_channel_cleanup(MultiFDSendParams *p, Error *err)
 | 
			
		||||
{
 | 
			
		||||
     migrate_set_error(migrate_get_current(), err);
 | 
			
		||||
     /* Error happen, we need to tell who pay attention to me */
 | 
			
		||||
@@ -871,20 +959,20 @@ static void multifd_new_send_channel_cleanup(MultiFDSendParams *p,
 | 
			
		||||
      * its status.
 | 
			
		||||
      */
 | 
			
		||||
     p->quit = true;
 | 
			
		||||
     object_unref(OBJECT(ioc));
 | 
			
		||||
     error_free(err);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDSendParams *p = opaque;
 | 
			
		||||
    QIOChannel *ioc = QIO_CHANNEL(qio_task_get_source(task));
 | 
			
		||||
    Object *obj = qio_task_get_source(task);
 | 
			
		||||
    Error *local_err = NULL;
 | 
			
		||||
 | 
			
		||||
    trace_multifd_new_send_channel_async(p->id);
 | 
			
		||||
    if (!qio_task_propagate_error(task, &local_err)) {
 | 
			
		||||
        p->c = ioc;
 | 
			
		||||
        qio_channel_set_delay(p->c, false);
 | 
			
		||||
        QIOChannel *ioc = QIO_CHANNEL(obj);
 | 
			
		||||
 | 
			
		||||
        qio_channel_set_delay(ioc, false);
 | 
			
		||||
        p->running = true;
 | 
			
		||||
        if (multifd_channel_connect(p, ioc, &local_err)) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -892,18 +980,24 @@ static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    trace_multifd_new_send_channel_async_error(p->id, local_err);
 | 
			
		||||
    multifd_new_send_channel_cleanup(p, ioc, local_err);
 | 
			
		||||
    multifd_new_send_channel_cleanup(p, local_err);
 | 
			
		||||
    object_unref(obj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_new_send_channel_create(gpointer opaque)
 | 
			
		||||
{
 | 
			
		||||
    socket_send_channel_create(multifd_new_send_channel_async, opaque);
 | 
			
		||||
    if (migrate_to_file()) {
 | 
			
		||||
        file_send_channel_create(multifd_new_send_channel_async, opaque);
 | 
			
		||||
    } else {
 | 
			
		||||
        socket_send_channel_create(multifd_new_send_channel_async, opaque);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int multifd_save_setup(Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    int thread_count;
 | 
			
		||||
    uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size();
 | 
			
		||||
    bool use_packets = migrate_multifd_packets();
 | 
			
		||||
    uint8_t i;
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd()) {
 | 
			
		||||
@@ -928,14 +1022,20 @@ int multifd_save_setup(Error **errp)
 | 
			
		||||
        p->pending_job = 0;
 | 
			
		||||
        p->id = i;
 | 
			
		||||
        p->pages = multifd_pages_init(page_count);
 | 
			
		||||
        p->packet_len = sizeof(MultiFDPacket_t)
 | 
			
		||||
                      + sizeof(uint64_t) * page_count;
 | 
			
		||||
        p->packet = g_malloc0(p->packet_len);
 | 
			
		||||
        p->packet->magic = cpu_to_be32(MULTIFD_MAGIC);
 | 
			
		||||
        p->packet->version = cpu_to_be32(MULTIFD_VERSION);
 | 
			
		||||
 | 
			
		||||
        if (use_packets) {
 | 
			
		||||
            p->packet_len = sizeof(MultiFDPacket_t)
 | 
			
		||||
                          + sizeof(uint64_t) * page_count;
 | 
			
		||||
            p->packet = g_malloc0(p->packet_len);
 | 
			
		||||
            p->packet->magic = cpu_to_be32(MULTIFD_MAGIC);
 | 
			
		||||
            p->packet->version = cpu_to_be32(MULTIFD_VERSION);
 | 
			
		||||
 | 
			
		||||
            /* We need one extra place for the packet header */
 | 
			
		||||
            p->iov = g_new0(struct iovec, page_count + 1);
 | 
			
		||||
        } else {
 | 
			
		||||
            p->iov = g_new0(struct iovec, page_count);
 | 
			
		||||
        }
 | 
			
		||||
        p->name = g_strdup_printf("multifdsend_%d", i);
 | 
			
		||||
        /* We need one extra place for the packet header */
 | 
			
		||||
        p->iov = g_new0(struct iovec, page_count + 1);
 | 
			
		||||
        p->normal = g_new0(ram_addr_t, page_count);
 | 
			
		||||
        p->page_size = qemu_target_page_size();
 | 
			
		||||
        p->page_count = page_count;
 | 
			
		||||
@@ -965,6 +1065,7 @@ int multifd_save_setup(Error **errp)
 | 
			
		||||
 | 
			
		||||
struct {
 | 
			
		||||
    MultiFDRecvParams *params;
 | 
			
		||||
    MultiFDRecvData *data;
 | 
			
		||||
    /* number of created threads */
 | 
			
		||||
    int count;
 | 
			
		||||
    /* syncs main thread and channels */
 | 
			
		||||
@@ -975,6 +1076,49 @@ struct {
 | 
			
		||||
    MultiFDMethods *ops;
 | 
			
		||||
} *multifd_recv_state;
 | 
			
		||||
 | 
			
		||||
int multifd_recv(void)
 | 
			
		||||
{
 | 
			
		||||
    int i;
 | 
			
		||||
    static int next_recv_channel;
 | 
			
		||||
    MultiFDRecvParams *p = NULL;
 | 
			
		||||
    MultiFDRecvData *data = multifd_recv_state->data;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * next_channel can remain from a previous migration that was
 | 
			
		||||
     * using more channels, so ensure it doesn't overflow if the
 | 
			
		||||
     * limit is lower now.
 | 
			
		||||
     */
 | 
			
		||||
    next_recv_channel %= migrate_multifd_channels();
 | 
			
		||||
    for (i = next_recv_channel;; i = (i + 1) % migrate_multifd_channels()) {
 | 
			
		||||
        p = &multifd_recv_state->params[i];
 | 
			
		||||
 | 
			
		||||
        qemu_mutex_lock(&p->mutex);
 | 
			
		||||
        if (p->quit) {
 | 
			
		||||
            error_report("%s: channel %d has already quit!", __func__, i);
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
        if (!p->pending_job) {
 | 
			
		||||
            p->pending_job++;
 | 
			
		||||
            next_recv_channel = (i + 1) % migrate_multifd_channels();
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
    }
 | 
			
		||||
    assert(p->data->size == 0);
 | 
			
		||||
    multifd_recv_state->data = p->data;
 | 
			
		||||
    p->data = data;
 | 
			
		||||
    qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
    qemu_sem_post(&p->sem);
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MultiFDRecvData *multifd_get_recv_data(void)
 | 
			
		||||
{
 | 
			
		||||
    return multifd_recv_state->data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void multifd_recv_terminate_threads(Error *err)
 | 
			
		||||
{
 | 
			
		||||
    int i;
 | 
			
		||||
@@ -996,6 +1140,7 @@ static void multifd_recv_terminate_threads(Error *err)
 | 
			
		||||
 | 
			
		||||
        qemu_mutex_lock(&p->mutex);
 | 
			
		||||
        p->quit = true;
 | 
			
		||||
        qemu_sem_post(&p->sem);
 | 
			
		||||
        /*
 | 
			
		||||
         * We could arrive here for two reasons:
 | 
			
		||||
         *  - normal quit, i.e. everything went fine, just finished
 | 
			
		||||
@@ -1045,6 +1190,7 @@ void multifd_load_cleanup(void)
 | 
			
		||||
        p->c = NULL;
 | 
			
		||||
        qemu_mutex_destroy(&p->mutex);
 | 
			
		||||
        qemu_sem_destroy(&p->sem_sync);
 | 
			
		||||
        qemu_sem_destroy(&p->sem);
 | 
			
		||||
        g_free(p->name);
 | 
			
		||||
        p->name = NULL;
 | 
			
		||||
        p->packet_len = 0;
 | 
			
		||||
@@ -1059,6 +1205,8 @@ void multifd_load_cleanup(void)
 | 
			
		||||
    qemu_sem_destroy(&multifd_recv_state->sem_sync);
 | 
			
		||||
    g_free(multifd_recv_state->params);
 | 
			
		||||
    multifd_recv_state->params = NULL;
 | 
			
		||||
    g_free(multifd_recv_state->data);
 | 
			
		||||
    multifd_recv_state->data = NULL;
 | 
			
		||||
    g_free(multifd_recv_state);
 | 
			
		||||
    multifd_recv_state = NULL;
 | 
			
		||||
}
 | 
			
		||||
@@ -1067,9 +1215,24 @@ void multifd_recv_sync_main(void)
 | 
			
		||||
{
 | 
			
		||||
    int i;
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd()) {
 | 
			
		||||
    if (!migrate_multifd() || !migrate_multifd_packets()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!migrate_multifd_packets()) {
 | 
			
		||||
        for (i = 0; i < migrate_multifd_channels(); i++) {
 | 
			
		||||
            MultiFDRecvParams *p = &multifd_recv_state->params[i];
 | 
			
		||||
 | 
			
		||||
            qemu_sem_post(&p->sem);
 | 
			
		||||
            qemu_sem_wait(&p->sem_sync);
 | 
			
		||||
 | 
			
		||||
            qemu_mutex_lock(&p->mutex);
 | 
			
		||||
            assert(!p->pending_job || p->quit);
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < migrate_multifd_channels(); i++) {
 | 
			
		||||
        MultiFDRecvParams *p = &multifd_recv_state->params[i];
 | 
			
		||||
 | 
			
		||||
@@ -1094,51 +1257,82 @@ static void *multifd_recv_thread(void *opaque)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDRecvParams *p = opaque;
 | 
			
		||||
    Error *local_err = NULL;
 | 
			
		||||
    bool use_packets = migrate_multifd_packets();
 | 
			
		||||
    int ret;
 | 
			
		||||
 | 
			
		||||
    trace_multifd_recv_thread_start(p->id);
 | 
			
		||||
    rcu_register_thread();
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
        uint32_t flags;
 | 
			
		||||
        uint32_t flags = 0;
 | 
			
		||||
        bool has_data = false;
 | 
			
		||||
        p->normal_num = 0;
 | 
			
		||||
 | 
			
		||||
        if (p->quit) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ret = qio_channel_read_all_eof(p->c, (void *)p->packet,
 | 
			
		||||
                                       p->packet_len, &local_err);
 | 
			
		||||
        if (ret == 0 || ret == -1) {   /* 0: EOF  -1: Error */
 | 
			
		||||
            break;
 | 
			
		||||
        if (use_packets) {
 | 
			
		||||
            ret = qio_channel_read_all_eof(p->c, (void *)p->packet,
 | 
			
		||||
                                           p->packet_len, &local_err);
 | 
			
		||||
            if (ret == 0 || ret == -1) {   /* 0: EOF  -1: Error */
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            qemu_mutex_lock(&p->mutex);
 | 
			
		||||
            ret = multifd_recv_unfill_packet(p, &local_err);
 | 
			
		||||
            if (ret) {
 | 
			
		||||
                qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            p->num_packets++;
 | 
			
		||||
 | 
			
		||||
            flags = p->flags;
 | 
			
		||||
            /* recv methods don't know how to handle the SYNC flag */
 | 
			
		||||
            p->flags &= ~MULTIFD_FLAG_SYNC;
 | 
			
		||||
            trace_multifd_recv(p->id, p->packet_num, p->normal_num, flags,
 | 
			
		||||
                               p->next_packet_size);
 | 
			
		||||
 | 
			
		||||
            p->total_normal_pages += p->normal_num;
 | 
			
		||||
            has_data = !!p->normal_num;
 | 
			
		||||
        } else {
 | 
			
		||||
            /*
 | 
			
		||||
             * No packets, so we need to wait for the vmstate code to
 | 
			
		||||
             * give us work.
 | 
			
		||||
             */
 | 
			
		||||
            qemu_sem_wait(&p->sem);
 | 
			
		||||
            qemu_mutex_lock(&p->mutex);
 | 
			
		||||
            if (!p->pending_job) {
 | 
			
		||||
                qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            has_data = !!p->data->size;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        qemu_mutex_lock(&p->mutex);
 | 
			
		||||
        ret = multifd_recv_unfill_packet(p, &local_err);
 | 
			
		||||
        if (ret) {
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        flags = p->flags;
 | 
			
		||||
        /* recv methods don't know how to handle the SYNC flag */
 | 
			
		||||
        p->flags &= ~MULTIFD_FLAG_SYNC;
 | 
			
		||||
        trace_multifd_recv(p->id, p->packet_num, p->normal_num, flags,
 | 
			
		||||
                           p->next_packet_size);
 | 
			
		||||
        p->num_packets++;
 | 
			
		||||
        p->total_normal_pages += p->normal_num;
 | 
			
		||||
        qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
 | 
			
		||||
        if (p->normal_num) {
 | 
			
		||||
            ret = multifd_recv_state->ops->recv_pages(p, &local_err);
 | 
			
		||||
        if (has_data) {
 | 
			
		||||
            ret = multifd_recv_state->ops->recv_data(p, &local_err);
 | 
			
		||||
            if (ret != 0) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (flags & MULTIFD_FLAG_SYNC) {
 | 
			
		||||
        if (use_packets && (flags & MULTIFD_FLAG_SYNC)) {
 | 
			
		||||
            qemu_sem_post(&multifd_recv_state->sem_sync);
 | 
			
		||||
            qemu_sem_wait(&p->sem_sync);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!use_packets) {
 | 
			
		||||
            qemu_mutex_lock(&p->mutex);
 | 
			
		||||
            p->data->size = 0;
 | 
			
		||||
            p->pending_job--;
 | 
			
		||||
            qemu_mutex_unlock(&p->mutex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!use_packets) {
 | 
			
		||||
        qemu_sem_post(&p->sem_sync);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (local_err) {
 | 
			
		||||
@@ -1159,6 +1353,7 @@ int multifd_load_setup(Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    int thread_count;
 | 
			
		||||
    uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size();
 | 
			
		||||
    bool use_packets = migrate_multifd_packets();
 | 
			
		||||
    uint8_t i;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
@@ -1172,6 +1367,10 @@ int multifd_load_setup(Error **errp)
 | 
			
		||||
    thread_count = migrate_multifd_channels();
 | 
			
		||||
    multifd_recv_state = g_malloc0(sizeof(*multifd_recv_state));
 | 
			
		||||
    multifd_recv_state->params = g_new0(MultiFDRecvParams, thread_count);
 | 
			
		||||
 | 
			
		||||
    multifd_recv_state->data = g_new0(MultiFDRecvData, 1);
 | 
			
		||||
    multifd_recv_state->data->size = 0;
 | 
			
		||||
 | 
			
		||||
    qatomic_set(&multifd_recv_state->count, 0);
 | 
			
		||||
    qemu_sem_init(&multifd_recv_state->sem_sync, 0);
 | 
			
		||||
    multifd_recv_state->ops = multifd_ops[migrate_multifd_compression()];
 | 
			
		||||
@@ -1181,11 +1380,19 @@ int multifd_load_setup(Error **errp)
 | 
			
		||||
 | 
			
		||||
        qemu_mutex_init(&p->mutex);
 | 
			
		||||
        qemu_sem_init(&p->sem_sync, 0);
 | 
			
		||||
        qemu_sem_init(&p->sem, 0);
 | 
			
		||||
        p->quit = false;
 | 
			
		||||
        p->pending_job = 0;
 | 
			
		||||
        p->id = i;
 | 
			
		||||
        p->packet_len = sizeof(MultiFDPacket_t)
 | 
			
		||||
                      + sizeof(uint64_t) * page_count;
 | 
			
		||||
        p->packet = g_malloc0(p->packet_len);
 | 
			
		||||
 | 
			
		||||
        p->data = g_new0(MultiFDRecvData, 1);
 | 
			
		||||
        p->data->size = 0;
 | 
			
		||||
 | 
			
		||||
        if (use_packets) {
 | 
			
		||||
            p->packet_len = sizeof(MultiFDPacket_t)
 | 
			
		||||
                + sizeof(uint64_t) * page_count;
 | 
			
		||||
            p->packet = g_malloc0(p->packet_len);
 | 
			
		||||
        }
 | 
			
		||||
        p->name = g_strdup_printf("multifdrecv_%d", i);
 | 
			
		||||
        p->iov = g_new0(struct iovec, page_count);
 | 
			
		||||
        p->normal = g_new0(ram_addr_t, page_count);
 | 
			
		||||
@@ -1231,18 +1438,26 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDRecvParams *p;
 | 
			
		||||
    Error *local_err = NULL;
 | 
			
		||||
    int id;
 | 
			
		||||
    bool use_packets = migrate_multifd_packets();
 | 
			
		||||
    int id, num_packets = 0;
 | 
			
		||||
 | 
			
		||||
    id = multifd_recv_initial_packet(ioc, &local_err);
 | 
			
		||||
    if (id < 0) {
 | 
			
		||||
        multifd_recv_terminate_threads(local_err);
 | 
			
		||||
        error_propagate_prepend(errp, local_err,
 | 
			
		||||
                                "failed to receive packet"
 | 
			
		||||
                                " via multifd channel %d: ",
 | 
			
		||||
                                qatomic_read(&multifd_recv_state->count));
 | 
			
		||||
        return;
 | 
			
		||||
    if (use_packets) {
 | 
			
		||||
        id = multifd_recv_initial_packet(ioc, &local_err);
 | 
			
		||||
        if (id < 0) {
 | 
			
		||||
            multifd_recv_terminate_threads(local_err);
 | 
			
		||||
            error_propagate_prepend(errp, local_err,
 | 
			
		||||
                                    "failed to receive packet"
 | 
			
		||||
                                    " via multifd channel %d: ",
 | 
			
		||||
                                    qatomic_read(&multifd_recv_state->count));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        trace_multifd_recv_new_channel(id);
 | 
			
		||||
 | 
			
		||||
        /* initial packet */
 | 
			
		||||
        num_packets = 1;
 | 
			
		||||
    } else {
 | 
			
		||||
        id = qatomic_read(&multifd_recv_state->count);
 | 
			
		||||
    }
 | 
			
		||||
    trace_multifd_recv_new_channel(id);
 | 
			
		||||
 | 
			
		||||
    p = &multifd_recv_state->params[id];
 | 
			
		||||
    if (p->c != NULL) {
 | 
			
		||||
@@ -1253,9 +1468,8 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    p->c = ioc;
 | 
			
		||||
    p->num_packets = num_packets;
 | 
			
		||||
    object_ref(OBJECT(ioc));
 | 
			
		||||
    /* initial packet */
 | 
			
		||||
    p->num_packets = 1;
 | 
			
		||||
 | 
			
		||||
    p->running = true;
 | 
			
		||||
    qemu_thread_create(&p->thread, p->name, multifd_recv_thread, p,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,16 +13,21 @@
 | 
			
		||||
#ifndef QEMU_MIGRATION_MULTIFD_H
 | 
			
		||||
#define QEMU_MIGRATION_MULTIFD_H
 | 
			
		||||
 | 
			
		||||
typedef struct MultiFDRecvData MultiFDRecvData;
 | 
			
		||||
 | 
			
		||||
int multifd_save_setup(Error **errp);
 | 
			
		||||
void multifd_save_cleanup(void);
 | 
			
		||||
int multifd_load_setup(Error **errp);
 | 
			
		||||
void multifd_load_cleanup(void);
 | 
			
		||||
void multifd_load_shutdown(void);
 | 
			
		||||
bool multifd_recv_first_channel(void);
 | 
			
		||||
bool multifd_recv_all_channels_created(void);
 | 
			
		||||
void multifd_recv_new_channel(QIOChannel *ioc, Error **errp);
 | 
			
		||||
void multifd_recv_sync_main(void);
 | 
			
		||||
int multifd_send_sync_main(QEMUFile *f);
 | 
			
		||||
int multifd_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset);
 | 
			
		||||
int multifd_recv(void);
 | 
			
		||||
MultiFDRecvData *multifd_get_recv_data(void);
 | 
			
		||||
 | 
			
		||||
/* Multifd Compression flags */
 | 
			
		||||
#define MULTIFD_FLAG_SYNC (1 << 0)
 | 
			
		||||
@@ -65,6 +70,13 @@ typedef struct {
 | 
			
		||||
    RAMBlock *block;
 | 
			
		||||
} MultiFDPages_t;
 | 
			
		||||
 | 
			
		||||
struct MultiFDRecvData {
 | 
			
		||||
    void *opaque;
 | 
			
		||||
    size_t size;
 | 
			
		||||
    /* for preadv */
 | 
			
		||||
    off_t file_offset;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    /* Fields are only written at creating/deletion time */
 | 
			
		||||
    /* No lock required for them, they are read only */
 | 
			
		||||
@@ -131,7 +143,7 @@ typedef struct {
 | 
			
		||||
    /* num of non zero pages */
 | 
			
		||||
    uint32_t normal_num;
 | 
			
		||||
    /* used for compression methods */
 | 
			
		||||
    void *data;
 | 
			
		||||
    void *compress_data;
 | 
			
		||||
}  MultiFDSendParams;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
@@ -155,6 +167,8 @@ typedef struct {
 | 
			
		||||
 | 
			
		||||
    /* syncs main thread and channels */
 | 
			
		||||
    QemuSemaphore sem_sync;
 | 
			
		||||
    /* sem where to wait for more work */
 | 
			
		||||
    QemuSemaphore sem;
 | 
			
		||||
 | 
			
		||||
    /* this mutex protects the following parameters */
 | 
			
		||||
    QemuMutex mutex;
 | 
			
		||||
@@ -166,6 +180,13 @@ typedef struct {
 | 
			
		||||
    uint32_t flags;
 | 
			
		||||
    /* global number of generated multifd packets */
 | 
			
		||||
    uint64_t packet_num;
 | 
			
		||||
    int pending_job;
 | 
			
		||||
    /*
 | 
			
		||||
     * The owner of 'data' depends of 'pending_job' value:
 | 
			
		||||
     * pending_job == 0 -> migration_thread can use it.
 | 
			
		||||
     * pending_job != 0 -> multifd_channel can use it.
 | 
			
		||||
     */
 | 
			
		||||
    MultiFDRecvData *data;
 | 
			
		||||
 | 
			
		||||
    /* thread local variables. No locking required */
 | 
			
		||||
 | 
			
		||||
@@ -188,7 +209,7 @@ typedef struct {
 | 
			
		||||
    /* num of non zero pages */
 | 
			
		||||
    uint32_t normal_num;
 | 
			
		||||
    /* used for de-compression methods */
 | 
			
		||||
    void *data;
 | 
			
		||||
    void *compress_data;
 | 
			
		||||
} MultiFDRecvParams;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
@@ -202,11 +223,10 @@ typedef struct {
 | 
			
		||||
    int (*recv_setup)(MultiFDRecvParams *p, Error **errp);
 | 
			
		||||
    /* Cleanup for receiving side */
 | 
			
		||||
    void (*recv_cleanup)(MultiFDRecvParams *p);
 | 
			
		||||
    /* Read all pages */
 | 
			
		||||
    int (*recv_pages)(MultiFDRecvParams *p, Error **errp);
 | 
			
		||||
    /* Read all data */
 | 
			
		||||
    int (*recv_data)(MultiFDRecvParams *p, Error **errp);
 | 
			
		||||
} MultiFDMethods;
 | 
			
		||||
 | 
			
		||||
void multifd_register_ops(int method, MultiFDMethods *ops);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -204,6 +204,7 @@ Property migration_properties[] = {
 | 
			
		||||
    DEFINE_PROP_MIG_CAP("x-switchover-ack",
 | 
			
		||||
                        MIGRATION_CAPABILITY_SWITCHOVER_ACK),
 | 
			
		||||
    DEFINE_PROP_MIG_CAP("x-dirty-limit", MIGRATION_CAPABILITY_DIRTY_LIMIT),
 | 
			
		||||
    DEFINE_PROP_MIG_CAP("x-fixed-ram", MIGRATION_CAPABILITY_FIXED_RAM),
 | 
			
		||||
    DEFINE_PROP_END_OF_LIST(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -263,6 +264,13 @@ bool migrate_events(void)
 | 
			
		||||
    return s->capabilities[MIGRATION_CAPABILITY_EVENTS];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_fixed_ram(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrationState *s = migrate_get_current();
 | 
			
		||||
 | 
			
		||||
    return s->capabilities[MIGRATION_CAPABILITY_FIXED_RAM];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_ignore_shared(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrationState *s = migrate_get_current();
 | 
			
		||||
@@ -377,6 +385,11 @@ bool migrate_multifd_flush_after_each_section(void)
 | 
			
		||||
    return s->multifd_flush_after_each_section;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_multifd_packets(void)
 | 
			
		||||
{
 | 
			
		||||
    return !migrate_fixed_ram();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_postcopy(void)
 | 
			
		||||
{
 | 
			
		||||
    return migrate_postcopy_ram() || migrate_dirty_bitmaps();
 | 
			
		||||
@@ -396,6 +409,13 @@ bool migrate_tls(void)
 | 
			
		||||
    return s->parameters.tls_creds && *s->parameters.tls_creds;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_to_file(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrationState *s = migrate_get_current();
 | 
			
		||||
 | 
			
		||||
    return qemu_file_is_seekable(s->to_dst_file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef enum WriteTrackingSupport {
 | 
			
		||||
    WT_SUPPORT_UNKNOWN = 0,
 | 
			
		||||
    WT_SUPPORT_ABSENT,
 | 
			
		||||
@@ -645,6 +665,26 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (new_caps[MIGRATION_CAPABILITY_FIXED_RAM]) {
 | 
			
		||||
        if (new_caps[MIGRATION_CAPABILITY_XBZRLE]) {
 | 
			
		||||
            error_setg(errp,
 | 
			
		||||
                       "Fixed-ram migration is incompatible with xbzrle");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (new_caps[MIGRATION_CAPABILITY_COMPRESS]) {
 | 
			
		||||
            error_setg(errp,
 | 
			
		||||
                       "Fixed-ram migration is incompatible with compression");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
 | 
			
		||||
            error_setg(errp,
 | 
			
		||||
                       "Fixed-ram migration is incompatible with postcopy ram");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -795,6 +835,22 @@ int migrate_decompress_threads(void)
 | 
			
		||||
    return s->parameters.decompress_threads;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool migrate_direct_io(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrationState *s = migrate_get_current();
 | 
			
		||||
 | 
			
		||||
    /* For now O_DIRECT is only supported with fixed-ram */
 | 
			
		||||
    if (!s->capabilities[MIGRATION_CAPABILITY_FIXED_RAM]) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (s->parameters.has_direct_io) {
 | 
			
		||||
        return s->parameters.direct_io;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t migrate_downtime_limit(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrationState *s = migrate_get_current();
 | 
			
		||||
@@ -1012,6 +1068,11 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
 | 
			
		||||
    params->has_mode = true;
 | 
			
		||||
    params->mode = s->parameters.mode;
 | 
			
		||||
 | 
			
		||||
    if (s->parameters.has_direct_io) {
 | 
			
		||||
        params->has_direct_io = true;
 | 
			
		||||
        params->direct_io = s->parameters.direct_io;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1047,6 +1108,7 @@ void migrate_params_init(MigrationParameters *params)
 | 
			
		||||
    params->has_x_vcpu_dirty_limit_period = true;
 | 
			
		||||
    params->has_vcpu_dirty_limit = true;
 | 
			
		||||
    params->has_mode = true;
 | 
			
		||||
    params->has_direct_io = qemu_has_direct_io();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -1348,6 +1410,10 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
 | 
			
		||||
    if (params->has_mode) {
 | 
			
		||||
        dest->mode = params->mode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (params->has_direct_io) {
 | 
			
		||||
        dest->direct_io = params->direct_io;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
 | 
			
		||||
@@ -1492,6 +1558,10 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
 | 
			
		||||
    if (params->has_mode) {
 | 
			
		||||
        s->parameters.mode = params->mode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (params->has_direct_io) {
 | 
			
		||||
        s->parameters.direct_io = params->direct_io;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ bool migrate_compress(void);
 | 
			
		||||
bool migrate_dirty_bitmaps(void);
 | 
			
		||||
bool migrate_dirty_limit(void);
 | 
			
		||||
bool migrate_events(void);
 | 
			
		||||
bool migrate_fixed_ram(void);
 | 
			
		||||
bool migrate_ignore_shared(void);
 | 
			
		||||
bool migrate_late_block_activate(void);
 | 
			
		||||
bool migrate_multifd(void);
 | 
			
		||||
@@ -55,9 +56,11 @@ bool migrate_zero_copy_send(void);
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
bool migrate_multifd_flush_after_each_section(void);
 | 
			
		||||
bool migrate_multifd_packets(void);
 | 
			
		||||
bool migrate_postcopy(void);
 | 
			
		||||
bool migrate_rdma(void);
 | 
			
		||||
bool migrate_tls(void);
 | 
			
		||||
bool migrate_to_file(void);
 | 
			
		||||
 | 
			
		||||
/* capabilities helpers */
 | 
			
		||||
 | 
			
		||||
@@ -78,6 +81,7 @@ uint8_t migrate_cpu_throttle_increment(void);
 | 
			
		||||
uint8_t migrate_cpu_throttle_initial(void);
 | 
			
		||||
bool migrate_cpu_throttle_tailslow(void);
 | 
			
		||||
int migrate_decompress_threads(void);
 | 
			
		||||
bool migrate_direct_io(void);
 | 
			
		||||
uint64_t migrate_downtime_limit(void);
 | 
			
		||||
uint8_t migrate_max_cpu_throttle(void);
 | 
			
		||||
uint64_t migrate_max_bandwidth(void);
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@
 | 
			
		||||
#include "options.h"
 | 
			
		||||
#include "qapi/error.h"
 | 
			
		||||
#include "rdma.h"
 | 
			
		||||
#include "io/channel-file.h"
 | 
			
		||||
 | 
			
		||||
#define IO_BUF_SIZE 32768
 | 
			
		||||
#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 64)
 | 
			
		||||
@@ -255,6 +256,10 @@ static void qemu_iovec_release_ram(QEMUFile *f)
 | 
			
		||||
    memset(f->may_free, 0, sizeof(f->may_free));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool qemu_file_is_seekable(QEMUFile *f)
 | 
			
		||||
{
 | 
			
		||||
    return qio_channel_has_feature(f->ioc, QIO_CHANNEL_FEATURE_SEEKABLE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Flushes QEMUFile buffer
 | 
			
		||||
@@ -447,6 +452,83 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
 | 
			
		||||
                        off_t pos)
 | 
			
		||||
{
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
 | 
			
		||||
    if (f->last_error) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qemu_fflush(f);
 | 
			
		||||
    qio_channel_pwrite(f->ioc, (char *)buf, buflen, pos, &err);
 | 
			
		||||
 | 
			
		||||
    if (err) {
 | 
			
		||||
        qemu_file_set_error_obj(f, -EIO, err);
 | 
			
		||||
    } else {
 | 
			
		||||
        stat64_add(&mig_stats.qemu_file_transferred, buflen);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
 | 
			
		||||
                          off_t pos)
 | 
			
		||||
{
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
 | 
			
		||||
    if (f->last_error) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ret = qio_channel_pread(f->ioc, (char *)buf, buflen, pos, &err);
 | 
			
		||||
    if (ret == -1 || err) {
 | 
			
		||||
        goto error;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (size_t)ret;
 | 
			
		||||
 | 
			
		||||
 error:
 | 
			
		||||
    qemu_file_set_error_obj(f, -EIO, err);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void qemu_set_offset(QEMUFile *f, off_t off, int whence)
 | 
			
		||||
{
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
    off_t ret;
 | 
			
		||||
 | 
			
		||||
    qemu_fflush(f);
 | 
			
		||||
 | 
			
		||||
    if (!qemu_file_is_writable(f)) {
 | 
			
		||||
        f->buf_index = 0;
 | 
			
		||||
        f->buf_size = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ret = qio_channel_io_seek(f->ioc, off, whence, &err);
 | 
			
		||||
    if (ret == (off_t)-1) {
 | 
			
		||||
        qemu_file_set_error_obj(f, -EIO, err);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
off_t qemu_get_offset(QEMUFile *f)
 | 
			
		||||
{
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
    off_t ret;
 | 
			
		||||
 | 
			
		||||
    qemu_fflush(f);
 | 
			
		||||
 | 
			
		||||
    ret = qio_channel_io_seek(f->ioc, 0, SEEK_CUR, &err);
 | 
			
		||||
    if (ret == (off_t)-1) {
 | 
			
		||||
        qemu_file_set_error_obj(f, -EIO, err);
 | 
			
		||||
    }
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void qemu_put_byte(QEMUFile *f, int v)
 | 
			
		||||
{
 | 
			
		||||
    if (f->last_error) {
 | 
			
		||||
@@ -834,3 +916,15 @@ int qemu_file_get_to_fd(QEMUFile *f, int fd, size_t size)
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool qemu_file_set_direct_io(QEMUFile *f, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
    Error *local_err = NULL;
 | 
			
		||||
 | 
			
		||||
    qio_channel_file_set_direct_io(f->ioc, enabled, &local_err);
 | 
			
		||||
    if (local_err) {
 | 
			
		||||
        qemu_file_set_error_obj(f, -EINVAL, local_err);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,13 @@ QEMUFile *qemu_file_get_return_path(QEMUFile *f);
 | 
			
		||||
int qemu_fflush(QEMUFile *f);
 | 
			
		||||
void qemu_file_set_blocking(QEMUFile *f, bool block);
 | 
			
		||||
int qemu_file_get_to_fd(QEMUFile *f, int fd, size_t size);
 | 
			
		||||
void qemu_set_offset(QEMUFile *f, off_t off, int whence);
 | 
			
		||||
off_t qemu_get_offset(QEMUFile *f);
 | 
			
		||||
void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
 | 
			
		||||
                        off_t pos);
 | 
			
		||||
size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
 | 
			
		||||
                          off_t pos);
 | 
			
		||||
 | 
			
		||||
QIOChannel *qemu_file_get_ioc(QEMUFile *file);
 | 
			
		||||
 | 
			
		||||
bool qemu_file_set_direct_io(QEMUFile *f, bool enabled);
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										296
									
								
								migration/ram.c
									
									
									
									
									
								
							
							
						
						
									
										296
									
								
								migration/ram.c
									
									
									
									
									
								
							@@ -94,6 +94,25 @@
 | 
			
		||||
#define RAM_SAVE_FLAG_MULTIFD_FLUSH    0x200
 | 
			
		||||
/* We can't use any flag that is bigger than 0x200 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * fixed-ram migration supports O_DIRECT, so we need to make sure the
 | 
			
		||||
 * userspace buffer, the IO operation size and the file offset are
 | 
			
		||||
 * aligned according to the underlying device's block size. The first
 | 
			
		||||
 * two are already aligned to page size, but we need to add padding to
 | 
			
		||||
 * the file to align the offset.  We cannot read the block size
 | 
			
		||||
 * dynamically because the migration file can be moved between
 | 
			
		||||
 * different systems, so use 1M to cover most block sizes and to keep
 | 
			
		||||
 * the file offset aligned at page size as well.
 | 
			
		||||
 */
 | 
			
		||||
#define FIXED_RAM_FILE_OFFSET_ALIGNMENT 0x100000
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * When doing fixed-ram migration, this is the amount we read from the
 | 
			
		||||
 * pages region in the migration file at a time.
 | 
			
		||||
 */
 | 
			
		||||
#define FIXED_RAM_LOAD_BUF_SIZE 0x100000
 | 
			
		||||
#define FIXED_RAM_MULTIFD_LOAD_BUF_SIZE 0x100000
 | 
			
		||||
 | 
			
		||||
XBZRLECacheStats xbzrle_counters;
 | 
			
		||||
 | 
			
		||||
/* used by the search for pages to send */
 | 
			
		||||
@@ -1127,12 +1146,18 @@ static int save_zero_page(RAMState *rs, PageSearchStatus *pss,
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stat64_add(&mig_stats.zero_pages, 1);
 | 
			
		||||
 | 
			
		||||
    if (migrate_fixed_ram()) {
 | 
			
		||||
        /* zero pages are not transferred with fixed-ram */
 | 
			
		||||
        clear_bit_atomic(offset >> TARGET_PAGE_BITS, pss->block->shadow_bmap);
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    len += save_page_header(pss, file, pss->block, offset | RAM_SAVE_FLAG_ZERO);
 | 
			
		||||
    qemu_put_byte(file, 0);
 | 
			
		||||
    len += 1;
 | 
			
		||||
    ram_release_page(pss->block->idstr, offset);
 | 
			
		||||
 | 
			
		||||
    stat64_add(&mig_stats.zero_pages, 1);
 | 
			
		||||
    ram_transferred_add(len);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
@@ -1190,14 +1215,20 @@ static int save_normal_page(PageSearchStatus *pss, RAMBlock *block,
 | 
			
		||||
{
 | 
			
		||||
    QEMUFile *file = pss->pss_channel;
 | 
			
		||||
 | 
			
		||||
    ram_transferred_add(save_page_header(pss, pss->pss_channel, block,
 | 
			
		||||
                                         offset | RAM_SAVE_FLAG_PAGE));
 | 
			
		||||
    if (async) {
 | 
			
		||||
        qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE,
 | 
			
		||||
                              migrate_release_ram() &&
 | 
			
		||||
                              migration_in_postcopy());
 | 
			
		||||
    if (migrate_fixed_ram()) {
 | 
			
		||||
        qemu_put_buffer_at(file, buf, TARGET_PAGE_SIZE,
 | 
			
		||||
                           block->pages_offset + offset);
 | 
			
		||||
        set_bit(offset >> TARGET_PAGE_BITS, block->shadow_bmap);
 | 
			
		||||
    } else {
 | 
			
		||||
        qemu_put_buffer(file, buf, TARGET_PAGE_SIZE);
 | 
			
		||||
        ram_transferred_add(save_page_header(pss, pss->pss_channel, block,
 | 
			
		||||
                                             offset | RAM_SAVE_FLAG_PAGE));
 | 
			
		||||
        if (async) {
 | 
			
		||||
            qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE,
 | 
			
		||||
                                  migrate_release_ram() &&
 | 
			
		||||
                                  migration_in_postcopy());
 | 
			
		||||
        } else {
 | 
			
		||||
            qemu_put_buffer(file, buf, TARGET_PAGE_SIZE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    ram_transferred_add(TARGET_PAGE_SIZE);
 | 
			
		||||
    stat64_add(&mig_stats.normal_pages, 1);
 | 
			
		||||
@@ -1333,7 +1364,7 @@ static int find_dirty_block(RAMState *rs, PageSearchStatus *pss)
 | 
			
		||||
        pss->page = 0;
 | 
			
		||||
        pss->block = QLIST_NEXT_RCU(pss->block, next);
 | 
			
		||||
        if (!pss->block) {
 | 
			
		||||
            if (migrate_multifd() &&
 | 
			
		||||
            if (migrate_multifd() && !migrate_fixed_ram() &&
 | 
			
		||||
                !migrate_multifd_flush_after_each_section()) {
 | 
			
		||||
                QEMUFile *f = rs->pss[RAM_CHANNEL_PRECOPY].pss_channel;
 | 
			
		||||
                int ret = multifd_send_sync_main(f);
 | 
			
		||||
@@ -2215,6 +2246,9 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss)
 | 
			
		||||
    /* Update host page boundary information */
 | 
			
		||||
    pss_host_page_prepare(pss);
 | 
			
		||||
 | 
			
		||||
    migration_direct_io_start(pss->pss_channel,
 | 
			
		||||
                              FIXED_RAM_FILE_OFFSET_ALIGNMENT);
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        page_dirty = migration_bitmap_clear_dirty(rs, pss->block, pss->page);
 | 
			
		||||
 | 
			
		||||
@@ -2248,6 +2282,7 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss)
 | 
			
		||||
 | 
			
		||||
        if (tmppages < 0) {
 | 
			
		||||
            pss_host_page_finish(pss);
 | 
			
		||||
            migration_direct_io_finish(pss->pss_channel);
 | 
			
		||||
            return tmppages;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -2255,6 +2290,7 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss)
 | 
			
		||||
    } while (pss_within_range(pss));
 | 
			
		||||
 | 
			
		||||
    pss_host_page_finish(pss);
 | 
			
		||||
    migration_direct_io_finish(pss->pss_channel);
 | 
			
		||||
 | 
			
		||||
    res = ram_save_release_protection(rs, pss, start_page);
 | 
			
		||||
    return (res < 0 ? res : pages);
 | 
			
		||||
@@ -2780,6 +2816,7 @@ static void ram_list_init_bitmaps(void)
 | 
			
		||||
             */
 | 
			
		||||
            block->bmap = bitmap_new(pages);
 | 
			
		||||
            bitmap_set(block->bmap, 0, pages);
 | 
			
		||||
            block->shadow_bmap = bitmap_new(pages);
 | 
			
		||||
            block->clear_bmap_shift = shift;
 | 
			
		||||
            block->clear_bmap = bitmap_new(clear_bmap_size(pages, shift));
 | 
			
		||||
        }
 | 
			
		||||
@@ -2917,6 +2954,87 @@ void qemu_guest_free_page_hint(void *addr, size_t len)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define FIXED_RAM_HDR_VERSION 1
 | 
			
		||||
struct FixedRamHeader {
 | 
			
		||||
    uint32_t version;
 | 
			
		||||
    /*
 | 
			
		||||
     * The target's page size, so we know how many pages are in the
 | 
			
		||||
     * bitmap.
 | 
			
		||||
     */
 | 
			
		||||
    uint64_t page_size;
 | 
			
		||||
    /*
 | 
			
		||||
     * The offset in the migration file where the pages bitmap is
 | 
			
		||||
     * found.
 | 
			
		||||
     */
 | 
			
		||||
    uint64_t bitmap_offset;
 | 
			
		||||
    /*
 | 
			
		||||
     * The offset in the migration file where the actual pages (data)
 | 
			
		||||
     * are found.
 | 
			
		||||
     */
 | 
			
		||||
    uint64_t pages_offset;
 | 
			
		||||
    /* end of v1 */
 | 
			
		||||
} QEMU_PACKED;
 | 
			
		||||
typedef struct FixedRamHeader FixedRamHeader;
 | 
			
		||||
 | 
			
		||||
static void fixed_ram_insert_header(QEMUFile *file, RAMBlock *block)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree FixedRamHeader *header;
 | 
			
		||||
    size_t header_size, bitmap_size;
 | 
			
		||||
    long num_pages;
 | 
			
		||||
 | 
			
		||||
    header = g_new0(FixedRamHeader, 1);
 | 
			
		||||
    header_size = sizeof(FixedRamHeader);
 | 
			
		||||
 | 
			
		||||
    num_pages = block->used_length >> TARGET_PAGE_BITS;
 | 
			
		||||
    bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Save the file offsets of where the bitmap and the pages should
 | 
			
		||||
     * go as they are written at the end of migration and during the
 | 
			
		||||
     * iterative phase, respectively.
 | 
			
		||||
     */
 | 
			
		||||
    block->bitmap_offset = qemu_get_offset(file) + header_size;
 | 
			
		||||
    block->pages_offset = ROUND_UP(block->bitmap_offset +
 | 
			
		||||
                                   bitmap_size,
 | 
			
		||||
                                   FIXED_RAM_FILE_OFFSET_ALIGNMENT);
 | 
			
		||||
 | 
			
		||||
    header->version = cpu_to_be32(FIXED_RAM_HDR_VERSION);
 | 
			
		||||
    header->page_size = cpu_to_be64(TARGET_PAGE_SIZE);
 | 
			
		||||
    header->bitmap_offset = cpu_to_be64(block->bitmap_offset);
 | 
			
		||||
    header->pages_offset = cpu_to_be64(block->pages_offset);
 | 
			
		||||
 | 
			
		||||
    qemu_put_buffer(file, (uint8_t *) header, header_size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool fixed_ram_read_header(QEMUFile *file, FixedRamHeader *header,
 | 
			
		||||
                                  Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    size_t ret, header_size = sizeof(FixedRamHeader);
 | 
			
		||||
 | 
			
		||||
    ret = qemu_get_buffer(file, (uint8_t *)header, header_size);
 | 
			
		||||
    if (ret != header_size) {
 | 
			
		||||
        error_setg(errp, "Could not read whole fixed-ram migration header "
 | 
			
		||||
                   "(expected %zd, got %zd bytes)", header_size, ret);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* migration stream is big-endian */
 | 
			
		||||
    header->version = be32_to_cpu(header->version);
 | 
			
		||||
 | 
			
		||||
    if (header->version > FIXED_RAM_HDR_VERSION) {
 | 
			
		||||
        error_setg(errp, "Migration fixed-ram capability version mismatch "
 | 
			
		||||
                   "(expected %d, got %d)", FIXED_RAM_HDR_VERSION,
 | 
			
		||||
                   header->version);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    header->page_size = be64_to_cpu(header->page_size);
 | 
			
		||||
    header->bitmap_offset = be64_to_cpu(header->bitmap_offset);
 | 
			
		||||
    header->pages_offset = be64_to_cpu(header->pages_offset);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Each of ram_save_setup, ram_save_iterate and ram_save_complete has
 | 
			
		||||
 * long-running RCU critical section.  When rcu-reclaims in the code
 | 
			
		||||
@@ -2966,6 +3084,13 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
 | 
			
		||||
            if (migrate_ignore_shared()) {
 | 
			
		||||
                qemu_put_be64(f, block->mr->addr);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (migrate_fixed_ram()) {
 | 
			
		||||
                fixed_ram_insert_header(f, block);
 | 
			
		||||
                /* prepare offset for next ramblock */
 | 
			
		||||
                qemu_set_offset(f, block->pages_offset + block->used_length,
 | 
			
		||||
                                SEEK_SET);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -2991,7 +3116,8 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migrate_multifd() && !migrate_multifd_flush_after_each_section()) {
 | 
			
		||||
    if (migrate_multifd() && !migrate_multifd_flush_after_each_section()
 | 
			
		||||
        && !migrate_fixed_ram()) {
 | 
			
		||||
        qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -2999,6 +3125,32 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
 | 
			
		||||
    return qemu_fflush(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ram_save_shadow_bmap(QEMUFile *f)
 | 
			
		||||
{
 | 
			
		||||
    RAMBlock *block;
 | 
			
		||||
 | 
			
		||||
    RAMBLOCK_FOREACH_MIGRATABLE(block) {
 | 
			
		||||
        long num_pages = block->used_length >> TARGET_PAGE_BITS;
 | 
			
		||||
        long bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
 | 
			
		||||
        qemu_put_buffer_at(f, (uint8_t *)block->shadow_bmap, bitmap_size,
 | 
			
		||||
                           block->bitmap_offset);
 | 
			
		||||
        ram_transferred_add(bitmap_size);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * Free the bitmap here to catch any synchronization issues
 | 
			
		||||
         * with multifd channels. No channels should be sending pages
 | 
			
		||||
         * after we've written the bitmap to file.
 | 
			
		||||
         */
 | 
			
		||||
        g_free(block->shadow_bmap);
 | 
			
		||||
        block->shadow_bmap = NULL;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ramblock_set_shadow_bmap_atomic(RAMBlock *block, ram_addr_t offset)
 | 
			
		||||
{
 | 
			
		||||
    set_bit_atomic(offset >> TARGET_PAGE_BITS, block->shadow_bmap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ram_save_iterate: iterative stage for migration
 | 
			
		||||
 *
 | 
			
		||||
@@ -3108,8 +3260,11 @@ static int ram_save_iterate(QEMUFile *f, void *opaque)
 | 
			
		||||
out:
 | 
			
		||||
    if (ret >= 0
 | 
			
		||||
        && migration_is_setup_or_active(migrate_get_current()->state)) {
 | 
			
		||||
        if (migrate_multifd() && migrate_multifd_flush_after_each_section()) {
 | 
			
		||||
            ret = multifd_send_sync_main(rs->pss[RAM_CHANNEL_PRECOPY].pss_channel);
 | 
			
		||||
        if (migrate_multifd() &&
 | 
			
		||||
            (migrate_multifd_flush_after_each_section() ||
 | 
			
		||||
             migrate_fixed_ram())) {
 | 
			
		||||
            ret = multifd_send_sync_main(
 | 
			
		||||
                rs->pss[RAM_CHANNEL_PRECOPY].pss_channel);
 | 
			
		||||
            if (ret < 0) {
 | 
			
		||||
                return ret;
 | 
			
		||||
            }
 | 
			
		||||
@@ -3188,6 +3343,10 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migrate_fixed_ram()) {
 | 
			
		||||
        ram_save_shadow_bmap(f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (migrate_multifd() && !migrate_multifd_flush_after_each_section()) {
 | 
			
		||||
        qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH);
 | 
			
		||||
    }
 | 
			
		||||
@@ -3789,6 +3948,107 @@ void colo_flush_ram_cache(void)
 | 
			
		||||
    trace_colo_flush_ram_cache_end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static size_t ram_load_multifd_pages(RAMBlock *block, ram_addr_t start_offset,
 | 
			
		||||
                                     size_t size)
 | 
			
		||||
{
 | 
			
		||||
    MultiFDRecvData *data = multifd_get_recv_data();
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Pointing the opaque directly to the host buffer, no
 | 
			
		||||
     * preprocessing needed.
 | 
			
		||||
     */
 | 
			
		||||
    data->opaque = block->host + start_offset;
 | 
			
		||||
 | 
			
		||||
    data->file_offset = block->pages_offset + start_offset;
 | 
			
		||||
    data->size = size;
 | 
			
		||||
 | 
			
		||||
    if (multifd_recv() < 0) {
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void read_ramblock_fixed_ram(QEMUFile *f, RAMBlock *block,
 | 
			
		||||
                                    long num_pages, unsigned long *bitmap)
 | 
			
		||||
{
 | 
			
		||||
    unsigned long set_bit_idx, clear_bit_idx;
 | 
			
		||||
    ram_addr_t offset;
 | 
			
		||||
    void *host;
 | 
			
		||||
    size_t read, unread, size;
 | 
			
		||||
    size_t buf_size = (migrate_multifd() ? FIXED_RAM_MULTIFD_LOAD_BUF_SIZE :
 | 
			
		||||
                       FIXED_RAM_LOAD_BUF_SIZE);
 | 
			
		||||
 | 
			
		||||
    for (set_bit_idx = find_first_bit(bitmap, num_pages);
 | 
			
		||||
         set_bit_idx < num_pages;
 | 
			
		||||
         set_bit_idx = find_next_bit(bitmap, num_pages, clear_bit_idx + 1)) {
 | 
			
		||||
 | 
			
		||||
        clear_bit_idx = find_next_zero_bit(bitmap, num_pages, set_bit_idx + 1);
 | 
			
		||||
 | 
			
		||||
        unread = TARGET_PAGE_SIZE * (clear_bit_idx - set_bit_idx);
 | 
			
		||||
        offset = set_bit_idx << TARGET_PAGE_BITS;
 | 
			
		||||
 | 
			
		||||
        while (unread > 0) {
 | 
			
		||||
            host = host_from_ram_block_offset(block, offset);
 | 
			
		||||
            size = MIN(unread, buf_size);
 | 
			
		||||
 | 
			
		||||
            if (migrate_multifd()) {
 | 
			
		||||
                read = ram_load_multifd_pages(block, offset, size);
 | 
			
		||||
            } else {
 | 
			
		||||
                read = qemu_get_buffer_at(f, host, size,
 | 
			
		||||
                                          block->pages_offset + offset);
 | 
			
		||||
            }
 | 
			
		||||
            offset += read;
 | 
			
		||||
            unread -= read;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int parse_ramblock_fixed_ram(QEMUFile *f, RAMBlock *block,
 | 
			
		||||
                                    ram_addr_t length, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree unsigned long *bitmap = NULL;
 | 
			
		||||
    FixedRamHeader header;
 | 
			
		||||
    size_t bitmap_size;
 | 
			
		||||
    long num_pages;
 | 
			
		||||
 | 
			
		||||
    if (!fixed_ram_read_header(f, &header, errp)) {
 | 
			
		||||
        return -EINVAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    block->pages_offset = header.pages_offset;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Check the alignment of the file region that contains pages. We
 | 
			
		||||
     * don't enforce FIXED_RAM_FILE_OFFSET_ALIGNMENT to allow that
 | 
			
		||||
     * value to change in the future. Do only a sanity check with page
 | 
			
		||||
     * size alignment.
 | 
			
		||||
     */
 | 
			
		||||
    if (!QEMU_IS_ALIGNED(block->pages_offset, TARGET_PAGE_SIZE)) {
 | 
			
		||||
        error_setg(errp,
 | 
			
		||||
                   "Error reading ramblock %s pages, region has bad alignment",
 | 
			
		||||
                   block->idstr);
 | 
			
		||||
        return -EINVAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    num_pages = length / header.page_size;
 | 
			
		||||
    bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
 | 
			
		||||
 | 
			
		||||
    bitmap = g_malloc0(bitmap_size);
 | 
			
		||||
    if (qemu_get_buffer_at(f, (uint8_t *)bitmap, bitmap_size,
 | 
			
		||||
                           header.bitmap_offset) != bitmap_size) {
 | 
			
		||||
        error_setg(errp, "Error reading dirty bitmap");
 | 
			
		||||
        return -EINVAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    read_ramblock_fixed_ram(f, block, num_pages, bitmap);
 | 
			
		||||
 | 
			
		||||
    /* Skip pages array */
 | 
			
		||||
    qemu_set_offset(f, block->pages_offset + length, SEEK_SET);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int parse_ramblock(QEMUFile *f, RAMBlock *block, ram_addr_t length)
 | 
			
		||||
{
 | 
			
		||||
    int ret = 0;
 | 
			
		||||
@@ -3797,6 +4057,16 @@ static int parse_ramblock(QEMUFile *f, RAMBlock *block, ram_addr_t length)
 | 
			
		||||
 | 
			
		||||
    assert(block);
 | 
			
		||||
 | 
			
		||||
    if (migrate_fixed_ram()) {
 | 
			
		||||
        Error *local_err = NULL;
 | 
			
		||||
 | 
			
		||||
        ret = parse_ramblock_fixed_ram(f, block, length, &local_err);
 | 
			
		||||
        if (local_err) {
 | 
			
		||||
            error_report_err(local_err);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!qemu_ram_is_migratable(block)) {
 | 
			
		||||
        error_report("block %s should not be migrated !", block->idstr);
 | 
			
		||||
        return -EINVAL;
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,7 @@ bool ram_dirty_bitmap_reload(MigrationState *s, RAMBlock *rb, Error **errp);
 | 
			
		||||
bool ramblock_page_is_discarded(RAMBlock *rb, ram_addr_t start);
 | 
			
		||||
void postcopy_preempt_shutdown_file(MigrationState *s);
 | 
			
		||||
void *postcopy_preempt_thread(void *opaque);
 | 
			
		||||
void ramblock_set_shadow_bmap_atomic(RAMBlock *block, ram_addr_t offset);
 | 
			
		||||
 | 
			
		||||
/* ram cache */
 | 
			
		||||
int colo_init_ram_cache(void);
 | 
			
		||||
 
 | 
			
		||||
@@ -245,6 +245,7 @@ static bool should_validate_capability(int capability)
 | 
			
		||||
    /* Validate only new capabilities to keep compatibility. */
 | 
			
		||||
    switch (capability) {
 | 
			
		||||
    case MIGRATION_CAPABILITY_X_IGNORE_SHARED:
 | 
			
		||||
    case MIGRATION_CAPABILITY_FIXED_RAM:
 | 
			
		||||
        return true;
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -173,9 +173,9 @@ static void monitor_fdset_cleanup(MonFdset *mon_fdset)
 | 
			
		||||
    MonFdsetFd *mon_fdset_fd_next;
 | 
			
		||||
 | 
			
		||||
    QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) {
 | 
			
		||||
        if ((mon_fdset_fd->removed ||
 | 
			
		||||
                (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) &&
 | 
			
		||||
                runstate_is_running()) {
 | 
			
		||||
        if (mon_fdset_fd->removed ||
 | 
			
		||||
            (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0 &&
 | 
			
		||||
             runstate_is_running())) {
 | 
			
		||||
            close(mon_fdset_fd->fd);
 | 
			
		||||
            g_free(mon_fdset_fd->opaque);
 | 
			
		||||
            QLIST_REMOVE(mon_fdset_fd, next);
 | 
			
		||||
@@ -406,6 +406,25 @@ AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id,
 | 
			
		||||
    return fdinfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
static bool monitor_fdset_flags_match(int flags, int fd_flags)
 | 
			
		||||
{
 | 
			
		||||
    bool match = false;
 | 
			
		||||
 | 
			
		||||
    if ((flags & O_ACCMODE) == (fd_flags & O_ACCMODE)) {
 | 
			
		||||
        match = true;
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
        if ((flags & O_DIRECT) != (fd_flags & O_DIRECT)) {
 | 
			
		||||
            match = false;
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return match;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags)
 | 
			
		||||
{
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
@@ -431,7 +450,7 @@ int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags)
 | 
			
		||||
                return -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) {
 | 
			
		||||
            if (monitor_fdset_flags_match(flags, mon_fd_flags)) {
 | 
			
		||||
                fd = mon_fdset_fd->fd;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -531,6 +531,10 @@
 | 
			
		||||
#     and can result in more stable read performance.  Requires KVM
 | 
			
		||||
#     with accelerator property "dirty-ring-size" set.  (Since 8.1)
 | 
			
		||||
#
 | 
			
		||||
# @fixed-ram: Migrate using fixed offsets for each RAM page.  Requires
 | 
			
		||||
#     a migration URI that supports seeking, such as a file.  (since
 | 
			
		||||
#     8.2)
 | 
			
		||||
#
 | 
			
		||||
# Features:
 | 
			
		||||
#
 | 
			
		||||
# @deprecated: Member @block is deprecated.  Use blockdev-mirror with
 | 
			
		||||
@@ -555,7 +559,7 @@
 | 
			
		||||
           { 'name': 'x-ignore-shared', 'features': [ 'unstable' ] },
 | 
			
		||||
           'validate-uuid', 'background-snapshot',
 | 
			
		||||
           'zero-copy-send', 'postcopy-preempt', 'switchover-ack',
 | 
			
		||||
           'dirty-limit'] }
 | 
			
		||||
           'dirty-limit', 'fixed-ram'] }
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
# @MigrationCapabilityStatus:
 | 
			
		||||
@@ -874,6 +878,9 @@
 | 
			
		||||
# @mode: Migration mode. See description in @MigMode. Default is 'normal'.
 | 
			
		||||
#        (Since 8.2)
 | 
			
		||||
#
 | 
			
		||||
# @direct-io: Open migration files with O_DIRECT when possible. This
 | 
			
		||||
#     requires that the 'fixed-ram' capability is enabled. (since 9.0)
 | 
			
		||||
#
 | 
			
		||||
# Features:
 | 
			
		||||
#
 | 
			
		||||
# @deprecated: Member @block-incremental is deprecated.  Use
 | 
			
		||||
@@ -907,7 +914,8 @@
 | 
			
		||||
           'block-bitmap-mapping',
 | 
			
		||||
           { 'name': 'x-vcpu-dirty-limit-period', 'features': ['unstable'] },
 | 
			
		||||
           'vcpu-dirty-limit',
 | 
			
		||||
           'mode'] }
 | 
			
		||||
           'mode',
 | 
			
		||||
           'direct-io'] }
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
# @MigrateSetParameters:
 | 
			
		||||
@@ -1062,6 +1070,9 @@
 | 
			
		||||
# @mode: Migration mode. See description in @MigMode. Default is 'normal'.
 | 
			
		||||
#        (Since 8.2)
 | 
			
		||||
#
 | 
			
		||||
# @direct-io: Open migration files with O_DIRECT when possible. This
 | 
			
		||||
#     requires that the 'fixed-ram' capability is enabled. (since 9.0)
 | 
			
		||||
#
 | 
			
		||||
# Features:
 | 
			
		||||
#
 | 
			
		||||
# @deprecated: Member @block-incremental is deprecated.  Use
 | 
			
		||||
@@ -1115,7 +1126,8 @@
 | 
			
		||||
            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
 | 
			
		||||
                                            'features': [ 'unstable' ] },
 | 
			
		||||
            '*vcpu-dirty-limit': 'uint64',
 | 
			
		||||
            '*mode': 'MigMode'} }
 | 
			
		||||
            '*mode': 'MigMode',
 | 
			
		||||
            '*direct-io': 'bool' } }
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
# @migrate-set-parameters:
 | 
			
		||||
@@ -1290,6 +1302,9 @@
 | 
			
		||||
# @mode: Migration mode. See description in @MigMode. Default is 'normal'.
 | 
			
		||||
#        (Since 8.2)
 | 
			
		||||
#
 | 
			
		||||
# @direct-io: Open migration files with O_DIRECT when possible. This
 | 
			
		||||
#     requires that the 'fixed-ram' capability is enabled. (since 9.0)
 | 
			
		||||
#
 | 
			
		||||
# Features:
 | 
			
		||||
#
 | 
			
		||||
# @deprecated: Member @block-incremental is deprecated.  Use
 | 
			
		||||
@@ -1340,7 +1355,8 @@
 | 
			
		||||
            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
 | 
			
		||||
                                            'features': [ 'unstable' ] },
 | 
			
		||||
            '*vcpu-dirty-limit': 'uint64',
 | 
			
		||||
            '*mode': 'MigMode'} }
 | 
			
		||||
            '*mode': 'MigMode',
 | 
			
		||||
            '*direct-io': 'bool' } }
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
# @query-migrate-parameters:
 | 
			
		||||
 
 | 
			
		||||
@@ -118,6 +118,12 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...)
 | 
			
		||||
 | 
			
		||||
    rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
 | 
			
		||||
                    args);
 | 
			
		||||
 | 
			
		||||
    if (!qdict_haskey(rsp, "return")) {
 | 
			
		||||
        g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(rsp), true);
 | 
			
		||||
        g_test_message("%s", s->str);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    g_assert(qdict_haskey(rsp, "return"));
 | 
			
		||||
    qobject_unref(rsp);
 | 
			
		||||
 | 
			
		||||
@@ -292,3 +298,45 @@ char *resolve_machine_version(const char *alias, const char *var1,
 | 
			
		||||
 | 
			
		||||
    return find_common_machine_version(machine_name, var1, var2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
/*
 | 
			
		||||
 * Probe for O_DIRECT support on the filesystem. Since this is used
 | 
			
		||||
 * for tests, be conservative, if anything fails, assume it's
 | 
			
		||||
 * unsupported.
 | 
			
		||||
 */
 | 
			
		||||
bool probe_o_direct_support(const char *tmpfs)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *filename = g_strdup_printf("%s/probe-o-direct", tmpfs);
 | 
			
		||||
    int fd, flags = O_CREAT | O_RDWR | O_TRUNC | O_DIRECT;
 | 
			
		||||
    void *buf;
 | 
			
		||||
    ssize_t ret, len;
 | 
			
		||||
    uint64_t offset;
 | 
			
		||||
 | 
			
		||||
    fd = open(filename, flags, 0660);
 | 
			
		||||
    if (fd < 0) {
 | 
			
		||||
        unlink(filename);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Assuming 4k should be enough to satisfy O_DIRECT alignment
 | 
			
		||||
     * requirements. The migration code uses 1M to be conservative.
 | 
			
		||||
     */
 | 
			
		||||
    len = 0x100000;
 | 
			
		||||
    offset = 0x100000;
 | 
			
		||||
 | 
			
		||||
    buf = aligned_alloc(len, len);
 | 
			
		||||
    g_assert(buf);
 | 
			
		||||
 | 
			
		||||
    ret = pwrite(fd, buf, len, offset);
 | 
			
		||||
    unlink(filename);
 | 
			
		||||
    g_free(buf);
 | 
			
		||||
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -47,4 +47,5 @@ char *find_common_machine_version(const char *mtype, const char *var1,
 | 
			
		||||
                                  const char *var2);
 | 
			
		||||
char *resolve_machine_version(const char *alias, const char *var1,
 | 
			
		||||
                              const char *var2);
 | 
			
		||||
bool probe_o_direct_support(const char *tmpfs);
 | 
			
		||||
#endif /* MIGRATION_HELPERS_H */
 | 
			
		||||
 
 | 
			
		||||
@@ -2135,6 +2135,14 @@ static void *test_mode_reboot_start(QTestState *from, QTestState *to)
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_fixed_ram_start(QTestState *from, QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_set_capability(from, "fixed-ram", true);
 | 
			
		||||
    migrate_set_capability(to, "fixed-ram", true);
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_mode_reboot(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
@@ -2149,6 +2157,202 @@ static void test_mode_reboot(void)
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_precopy_file_fixed_ram_live(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_fixed_ram_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_precopy_file_fixed_ram(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_fixed_ram_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_multifd_fixed_ram_start(QTestState *from, QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_fixed_ram_start(from, to);
 | 
			
		||||
 | 
			
		||||
    migrate_set_parameter_int(from, "multifd-channels", 4);
 | 
			
		||||
    migrate_set_parameter_int(to, "multifd-channels", 4);
 | 
			
		||||
 | 
			
		||||
    migrate_set_capability(from, "multifd", true);
 | 
			
		||||
    migrate_set_capability(to, "multifd", true);
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_multifd_file_fixed_ram_live(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_multifd_fixed_ram_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_multifd_file_fixed_ram(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_multifd_fixed_ram_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
static void *migrate_fixed_ram_dio_start(QTestState *from,
 | 
			
		||||
                                                 QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_set_parameter_bool(from, "direct-io", true);
 | 
			
		||||
    migrate_set_parameter_bool(to, "direct-io", true);
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_multifd_fixed_ram_dio_start(QTestState *from,
 | 
			
		||||
                                                 QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_multifd_fixed_ram_start(from, to);
 | 
			
		||||
    return migrate_fixed_ram_dio_start(from, to);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_multifd_file_fixed_ram_dio(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_multifd_fixed_ram_dio_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!probe_o_direct_support(tmpfs)) {
 | 
			
		||||
        g_test_skip("Filesystem does not support O_DIRECT");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_precopy_file_fixed_ram_dio(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
 | 
			
		||||
                                           FILE_TEST_FILENAME);
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_fixed_ram_dio_start,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void migrate_multifd_fixed_ram_fdset_dio_end(QTestState *from,
 | 
			
		||||
                                                    QTestState *to,
 | 
			
		||||
                                                    void *opaque)
 | 
			
		||||
{
 | 
			
		||||
    QDict *resp;
 | 
			
		||||
    QList *fdsets;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Check that we removed the fdsets after migration, otherwise a
 | 
			
		||||
     * second migration would fail due to too many fdsets.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    resp = qtest_qmp(from, "{'execute': 'query-fdsets', "
 | 
			
		||||
                     "'arguments': {}}");
 | 
			
		||||
    g_assert(qdict_haskey(resp, "return"));
 | 
			
		||||
    fdsets = qdict_get_qlist(resp, "return");
 | 
			
		||||
    g_assert(fdsets && qlist_empty(fdsets));
 | 
			
		||||
}
 | 
			
		||||
#endif /* O_DIRECT */
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
static void *migrate_multifd_fixed_ram_fdset(QTestState *from, QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
 | 
			
		||||
    int fds[3];
 | 
			
		||||
    int src_flags = O_CREAT | O_WRONLY;
 | 
			
		||||
    int dst_flags = O_CREAT | O_RDONLY;
 | 
			
		||||
 | 
			
		||||
    /* main outgoing channel: no O_DIRECT */
 | 
			
		||||
    fds[0] = open(file, src_flags, 0660);
 | 
			
		||||
    assert(fds[0] != -1);
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
    src_flags |= O_DIRECT;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    /* secondary outgoing channels */
 | 
			
		||||
    fds[1] = open(file, src_flags, 0660);
 | 
			
		||||
    assert(fds[1] != -1);
 | 
			
		||||
 | 
			
		||||
    qtest_qmp_fds_assert_success(from, &fds[0], 1, "{'execute': 'add-fd', "
 | 
			
		||||
                                 "'arguments': {'fdset-id': 1}}");
 | 
			
		||||
 | 
			
		||||
    qtest_qmp_fds_assert_success(from, &fds[1], 1, "{'execute': 'add-fd', "
 | 
			
		||||
                                 "'arguments': {'fdset-id': 1}}");
 | 
			
		||||
 | 
			
		||||
    /* incoming channel */
 | 
			
		||||
    fds[2] = open(file, dst_flags, 0660);
 | 
			
		||||
    assert(fds[2] != -1);
 | 
			
		||||
 | 
			
		||||
    qtest_qmp_fds_assert_success(to, &fds[2], 1, "{'execute': 'add-fd', "
 | 
			
		||||
                                 "'arguments': {'fdset-id': 1}}");
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
        migrate_multifd_fixed_ram_dio_start(from, to);
 | 
			
		||||
#else
 | 
			
		||||
        migrate_multifd_fixed_ram_start(from, to);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_multifd_file_fixed_ram_fdset(void)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=0x100");
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = uri,
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_multifd_fixed_ram_fdset,
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
        .finish_hook = migrate_multifd_fixed_ram_fdset_dio_end,
 | 
			
		||||
#endif
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!probe_o_direct_support(tmpfs)) {
 | 
			
		||||
        g_test_skip("Filesystem does not support O_DIRECT");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
#endif /* _WIN32 */
 | 
			
		||||
 | 
			
		||||
static void test_precopy_tcp_plain(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
@@ -2358,7 +2562,7 @@ static void test_migrate_fd_finish_hook(QTestState *from,
 | 
			
		||||
    qobject_unref(rsp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_migrate_fd_proto(void)
 | 
			
		||||
static void test_migrate_precopy_fd_socket(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
@@ -2368,6 +2572,82 @@ static void test_migrate_fd_proto(void)
 | 
			
		||||
    };
 | 
			
		||||
    test_precopy_common(&args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_precopy_fd_file_start(QTestState *from, QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
 | 
			
		||||
    int src_flags = O_CREAT | O_RDWR;
 | 
			
		||||
    int dst_flags = O_CREAT | O_RDWR;
 | 
			
		||||
    int fds[2];
 | 
			
		||||
 | 
			
		||||
    fds[0] = open(file, src_flags, 0660);
 | 
			
		||||
    assert(fds[0] != -1);
 | 
			
		||||
 | 
			
		||||
    fds[1] = open(file, dst_flags, 0660);
 | 
			
		||||
    assert(fds[1] != -1);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    qtest_qmp_fds_assert_success(to, &fds[0], 1,
 | 
			
		||||
                                 "{ 'execute': 'getfd',"
 | 
			
		||||
                                 "  'arguments': { 'fdname': 'fd-mig' }}");
 | 
			
		||||
 | 
			
		||||
    qtest_qmp_fds_assert_success(from, &fds[1], 1,
 | 
			
		||||
                                 "{ 'execute': 'getfd',"
 | 
			
		||||
                                 "  'arguments': { 'fdname': 'fd-mig' }}");
 | 
			
		||||
 | 
			
		||||
    close(fds[0]);
 | 
			
		||||
    close(fds[1]);
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_fd_file_fixed_ram_start(QTestState *from, QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_fixed_ram_start(from, to);
 | 
			
		||||
 | 
			
		||||
    return migrate_precopy_fd_file_start(from, to);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_migrate_precopy_fd_file(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .connect_uri = "fd:fd-mig",
 | 
			
		||||
        .start_hook = migrate_precopy_fd_file_start,
 | 
			
		||||
        .finish_hook = test_migrate_fd_finish_hook
 | 
			
		||||
    };
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_migrate_precopy_fd_file_fixed_ram(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .connect_uri = "fd:fd-mig",
 | 
			
		||||
        .start_hook = migrate_fd_file_fixed_ram_start,
 | 
			
		||||
        .finish_hook = test_migrate_fd_finish_hook
 | 
			
		||||
    };
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *migrate_multifd_fd_fixed_ram_start(QTestState *from,
 | 
			
		||||
                                                QTestState *to)
 | 
			
		||||
{
 | 
			
		||||
    migrate_multifd_fixed_ram_start(from, to);
 | 
			
		||||
    return migrate_precopy_fd_file_start(from, to);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_multifd_fd_fixed_ram(void)
 | 
			
		||||
{
 | 
			
		||||
    MigrateCommon args = {
 | 
			
		||||
        .connect_uri = "fd:fd-mig",
 | 
			
		||||
        .listen_uri = "defer",
 | 
			
		||||
        .start_hook = migrate_multifd_fd_fixed_ram_start,
 | 
			
		||||
        .finish_hook = test_migrate_fd_finish_hook
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    test_file_common(&args, true);
 | 
			
		||||
}
 | 
			
		||||
#endif /* _WIN32 */
 | 
			
		||||
 | 
			
		||||
static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
 | 
			
		||||
@@ -3392,6 +3672,32 @@ int main(int argc, char **argv)
 | 
			
		||||
        qtest_add_func("/migration/mode/reboot", test_mode_reboot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qtest_add_func("/migration/precopy/file/fixed-ram",
 | 
			
		||||
                   test_precopy_file_fixed_ram);
 | 
			
		||||
    qtest_add_func("/migration/precopy/file/fixed-ram/live",
 | 
			
		||||
                   test_precopy_file_fixed_ram_live);
 | 
			
		||||
 | 
			
		||||
    qtest_add_func("/migration/multifd/file/fixed-ram",
 | 
			
		||||
                   test_multifd_file_fixed_ram);
 | 
			
		||||
    qtest_add_func("/migration/multifd/file/fixed-ram/live",
 | 
			
		||||
                   test_multifd_file_fixed_ram_live);
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
    qtest_add_func("/migration/multifd/fd/fixed-ram",
 | 
			
		||||
                   test_multifd_fd_fixed_ram);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
    qtest_add_func("/migration/precopy/file/fixed-ram/dio",
 | 
			
		||||
                   test_precopy_file_fixed_ram_dio);
 | 
			
		||||
    qtest_add_func("/migration/multifd/file/fixed-ram/dio",
 | 
			
		||||
                   test_multifd_file_fixed_ram_dio);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
    qtest_add_func("/migration/multifd/file/fixed-ram/fdset",
 | 
			
		||||
                   test_multifd_file_fixed_ram_fdset);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_GNUTLS
 | 
			
		||||
    qtest_add_func("/migration/precopy/unix/tls/psk",
 | 
			
		||||
                   test_precopy_unix_tls_psk);
 | 
			
		||||
@@ -3448,7 +3754,10 @@ int main(int argc, char **argv)
 | 
			
		||||
 | 
			
		||||
    /* qtest_add_func("/migration/ignore_shared", test_ignore_shared); */
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
    qtest_add_func("/migration/fd_proto", test_migrate_fd_proto);
 | 
			
		||||
    qtest_add_func("/migration/precopy/fd/tcp", test_migrate_precopy_fd_socket);
 | 
			
		||||
    qtest_add_func("/migration/precopy/fd/file", test_migrate_precopy_fd_file);
 | 
			
		||||
    qtest_add_func("/migration/precopy/fd/file/fixed-ram",
 | 
			
		||||
                   test_migrate_precopy_fd_file_fixed_ram);
 | 
			
		||||
#endif
 | 
			
		||||
    qtest_add_func("/migration/validate_uuid", test_validate_uuid);
 | 
			
		||||
    qtest_add_func("/migration/validate_uuid_error", test_validate_uuid_error);
 | 
			
		||||
 
 | 
			
		||||
@@ -277,6 +277,15 @@ int qemu_lock_fd_test(int fd, int64_t start, int64_t len, bool exclusive)
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool qemu_has_direct_io(void)
 | 
			
		||||
{
 | 
			
		||||
#ifdef O_DIRECT
 | 
			
		||||
    return true;
 | 
			
		||||
#else
 | 
			
		||||
    return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int qemu_open_cloexec(const char *name, int flags, mode_t mode)
 | 
			
		||||
{
 | 
			
		||||
    int ret;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user