[PATCH 4/5] qemu: Split of code related to handling of the save image file

Peter Krempa pkrempa at redhat.com
Thu Aug 6 09:55:15 UTC 2020


There's a lot of helper code related to the save image handling. Extract
it to qemu_saveimage.c/h.

Signed-off-by: Peter Krempa <pkrempa at redhat.com>
---
 po/POTFILES.in            |   1 +
 src/qemu/meson.build      |   1 +
 src/qemu/qemu_driver.c    | 843 +++-----------------------------------
 src/qemu/qemu_saveimage.c | 764 ++++++++++++++++++++++++++++++++++
 src/qemu/qemu_saveimage.h | 116 ++++++
 5 files changed, 928 insertions(+), 797 deletions(-)
 create mode 100644 src/qemu/qemu_saveimage.c
 create mode 100644 src/qemu/qemu_saveimage.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index c5b43df7b5..6f47371b01 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -170,6 +170,7 @@
 @SRCDIR at src/qemu/qemu_namespace.c
 @SRCDIR at src/qemu/qemu_process.c
 @SRCDIR at src/qemu/qemu_qapi.c
+ at SRCDIR@src/qemu/qemu_saveimage.c
 @SRCDIR at src/qemu/qemu_slirp.c
 @SRCDIR at src/qemu/qemu_tpm.c
 @SRCDIR at src/qemu/qemu_validate.c
diff --git a/src/qemu/meson.build b/src/qemu/meson.build
index 7faba16049..7d5249978a 100644
--- a/src/qemu/meson.build
+++ b/src/qemu/meson.build
@@ -29,6 +29,7 @@ qemu_driver_sources = [
   'qemu_namespace.c',
   'qemu_process.c',
   'qemu_qapi.c',
+  'qemu_saveimage.c',
   'qemu_security.c',
   'qemu_slirp.c',
   'qemu_tpm.c',
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 8f61759f53..a6b8c79168 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -51,6 +51,7 @@
 #include "qemu_checkpoint.h"
 #include "qemu_backup.h"
 #include "qemu_namespace.h"
+#include "qemu_saveimage.h"

 #include "virerror.h"
 #include "virlog.h"
@@ -135,6 +136,16 @@ VIR_LOG_INIT("qemu.qemu_driver");

 #define QEMU_NB_BANDWIDTH_PARAM 7

+VIR_ENUM_DECL(qemuDumpFormat);
+VIR_ENUM_IMPL(qemuDumpFormat,
+              VIR_DOMAIN_CORE_DUMP_FORMAT_LAST,
+              "elf",
+              "kdump-zlib",
+              "kdump-lzo",
+              "kdump-snappy",
+);
+
+
 static void qemuProcessEventHandler(void *data, void *opaque);

 static int qemuStateCleanup(void);
@@ -2771,339 +2782,6 @@ qemuDomainGetControlInfo(virDomainPtr dom,
 }


-/* It would be nice to replace 'Qemud' with 'Qemu' but
- * this magic string is ABI, so it can't be changed
- */
-#define QEMU_SAVE_MAGIC   "LibvirtQemudSave"
-#define QEMU_SAVE_PARTIAL "LibvirtQemudPart"
-#define QEMU_SAVE_VERSION 2
-
-G_STATIC_ASSERT(sizeof(QEMU_SAVE_MAGIC) == sizeof(QEMU_SAVE_PARTIAL));
-
-typedef enum {
-    QEMU_SAVE_FORMAT_RAW = 0,
-    QEMU_SAVE_FORMAT_GZIP = 1,
-    QEMU_SAVE_FORMAT_BZIP2 = 2,
-    /*
-     * Deprecated by xz and never used as part of a release
-     * QEMU_SAVE_FORMAT_LZMA
-     */
-    QEMU_SAVE_FORMAT_XZ = 3,
-    QEMU_SAVE_FORMAT_LZOP = 4,
-    /* Note: add new members only at the end.
-       These values are used in the on-disk format.
-       Do not change or re-use numbers. */
-
-    QEMU_SAVE_FORMAT_LAST
-} virQEMUSaveFormat;
-
-VIR_ENUM_DECL(qemuSaveCompression);
-VIR_ENUM_IMPL(qemuSaveCompression,
-              QEMU_SAVE_FORMAT_LAST,
-              "raw",
-              "gzip",
-              "bzip2",
-              "xz",
-              "lzop",
-);
-
-VIR_ENUM_DECL(qemuDumpFormat);
-VIR_ENUM_IMPL(qemuDumpFormat,
-              VIR_DOMAIN_CORE_DUMP_FORMAT_LAST,
-              "elf",
-              "kdump-zlib",
-              "kdump-lzo",
-              "kdump-snappy",
-);
-
-typedef struct _virQEMUSaveHeader virQEMUSaveHeader;
-typedef virQEMUSaveHeader *virQEMUSaveHeaderPtr;
-struct _virQEMUSaveHeader {
-    char magic[sizeof(QEMU_SAVE_MAGIC)-1];
-    uint32_t version;
-    uint32_t data_len;
-    uint32_t was_running;
-    uint32_t compressed;
-    uint32_t cookieOffset;
-    uint32_t unused[14];
-};
-
-typedef struct _virQEMUSaveData virQEMUSaveData;
-typedef virQEMUSaveData *virQEMUSaveDataPtr;
-struct _virQEMUSaveData {
-    virQEMUSaveHeader header;
-    char *xml;
-    char *cookie;
-};
-
-
-static inline void
-bswap_header(virQEMUSaveHeaderPtr hdr)
-{
-    hdr->version = GUINT32_SWAP_LE_BE(hdr->version);
-    hdr->data_len = GUINT32_SWAP_LE_BE(hdr->data_len);
-    hdr->was_running = GUINT32_SWAP_LE_BE(hdr->was_running);
-    hdr->compressed = GUINT32_SWAP_LE_BE(hdr->compressed);
-    hdr->cookieOffset = GUINT32_SWAP_LE_BE(hdr->cookieOffset);
-}
-
-
-static void
-virQEMUSaveDataFree(virQEMUSaveDataPtr data)
-{
-    if (!data)
-        return;
-
-    VIR_FREE(data->xml);
-    VIR_FREE(data->cookie);
-    VIR_FREE(data);
-}
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(virQEMUSaveData, virQEMUSaveDataFree);
-
-/**
- * This function steals @domXML on success.
- */
-static virQEMUSaveDataPtr
-virQEMUSaveDataNew(char *domXML,
-                   qemuDomainSaveCookiePtr cookieObj,
-                   bool running,
-                   int compressed,
-                   virDomainXMLOptionPtr xmlopt)
-{
-    virQEMUSaveDataPtr data = NULL;
-    virQEMUSaveHeaderPtr header;
-
-    if (VIR_ALLOC(data) < 0)
-        return NULL;
-
-    data->xml = g_steal_pointer(&domXML);
-
-    if (cookieObj &&
-        !(data->cookie = virSaveCookieFormat((virObjectPtr) cookieObj,
-                                             virDomainXMLOptionGetSaveCookie(xmlopt))))
-        goto error;
-
-    header = &data->header;
-    memcpy(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic));
-    header->version = QEMU_SAVE_VERSION;
-    header->was_running = running ? 1 : 0;
-    header->compressed = compressed;
-
-    return data;
-
- error:
-    virQEMUSaveDataFree(data);
-    return NULL;
-}
-
-
-/* virQEMUSaveDataWrite:
- *
- * Writes libvirt's header (including domain XML) into a saved image of a
- * running domain. If @header has data_len filled in (because it was previously
- * read from the file), the function will make sure the new data will fit
- * within data_len.
- *
- * Returns -1 on failure, or 0 on success.
- */
-static int
-virQEMUSaveDataWrite(virQEMUSaveDataPtr data,
-                     int fd,
-                     const char *path)
-{
-    virQEMUSaveHeaderPtr header = &data->header;
-    size_t len;
-    size_t xml_len;
-    size_t cookie_len = 0;
-    size_t zerosLen = 0;
-    g_autofree char *zeros = NULL;
-
-    xml_len = strlen(data->xml) + 1;
-    if (data->cookie)
-        cookie_len = strlen(data->cookie) + 1;
-
-    len = xml_len + cookie_len;
-
-    if (header->data_len == 0) {
-        /* This 64kb padding allows the user to edit the XML in
-         * a saved state image and have the new XML be larger
-         * that what was originally saved
-         */
-        header->data_len = len + (64 * 1024);
-    } else {
-        if (len > header->data_len) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("new xml too large to fit in file"));
-            return -1;
-        }
-    }
-
-    zerosLen = header->data_len - len;
-    zeros = g_new0(char, zerosLen);
-
-    if (data->cookie)
-        header->cookieOffset = xml_len;
-
-    if (safewrite(fd, header, sizeof(*header)) != sizeof(*header)) {
-        virReportSystemError(errno,
-                             _("failed to write header to domain save file '%s'"),
-                             path);
-        return -1;
-    }
-
-    if (safewrite(fd, data->xml, xml_len) != xml_len) {
-        virReportSystemError(errno,
-                             _("failed to write domain xml to '%s'"),
-                             path);
-        return -1;
-    }
-
-    if (data->cookie &&
-        safewrite(fd, data->cookie, cookie_len) != cookie_len) {
-        virReportSystemError(errno,
-                             _("failed to write cookie to '%s'"),
-                             path);
-        return -1;
-    }
-
-    if (safewrite(fd, zeros, zerosLen) != zerosLen) {
-        virReportSystemError(errno,
-                             _("failed to write padding to '%s'"),
-                             path);
-        return -1;
-    }
-
-    return 0;
-}
-
-
-static int
-virQEMUSaveDataFinish(virQEMUSaveDataPtr data,
-                      int *fd,
-                      const char *path)
-{
-    virQEMUSaveHeaderPtr header = &data->header;
-
-    memcpy(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic));
-
-    if (safewrite(*fd, header, sizeof(*header)) != sizeof(*header) ||
-        VIR_CLOSE(*fd) < 0) {
-        virReportSystemError(errno,
-                             _("failed to write header to domain save file '%s'"),
-                             path);
-        return -1;
-    }
-
-    return 0;
-}
-
-
-static virCommandPtr
-qemuCompressGetCommand(virQEMUSaveFormat compression)
-{
-    virCommandPtr ret = NULL;
-    const char *prog = qemuSaveCompressionTypeToString(compression);
-
-    if (!prog) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("Invalid compressed save format %d"),
-                       compression);
-        return NULL;
-    }
-
-    ret = virCommandNew(prog);
-    virCommandAddArg(ret, "-dc");
-
-    if (compression == QEMU_SAVE_FORMAT_LZOP)
-        virCommandAddArg(ret, "--ignore-warn");
-
-    return ret;
-}
-
-
-/* Helper function to execute a migration to file with a correct save header
- * the caller needs to make sure that the processors are stopped and do all other
- * actions besides saving memory */
-static int
-qemuDomainSaveMemory(virQEMUDriverPtr driver,
-                     virDomainObjPtr vm,
-                     const char *path,
-                     virQEMUSaveDataPtr data,
-                     virCommandPtr compressor,
-                     unsigned int flags,
-                     qemuDomainAsyncJob asyncJob)
-{
-    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
-    bool needUnlink = false;
-    int ret = -1;
-    int fd = -1;
-    int directFlag = 0;
-    virFileWrapperFdPtr wrapperFd = NULL;
-    unsigned int wrapperFlags = VIR_FILE_WRAPPER_NON_BLOCKING;
-
-    /* Obtain the file handle.  */
-    if ((flags & VIR_DOMAIN_SAVE_BYPASS_CACHE)) {
-        wrapperFlags |= VIR_FILE_WRAPPER_BYPASS_CACHE;
-        directFlag = virFileDirectFdFlag();
-        if (directFlag < 0) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("bypass cache unsupported by this system"));
-            goto cleanup;
-        }
-    }
-
-    fd = virQEMUFileOpenAs(cfg->user, cfg->group, false, path,
-                           O_WRONLY | O_TRUNC | O_CREAT | directFlag,
-                           &needUnlink);
-    if (fd < 0)
-        goto cleanup;
-
-    if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, fd) < 0)
-        goto cleanup;
-
-    if (!(wrapperFd = virFileWrapperFdNew(&fd, path, wrapperFlags)))
-        goto cleanup;
-
-    if (virQEMUSaveDataWrite(data, fd, path) < 0)
-        goto cleanup;
-
-    /* Perform the migration */
-    if (qemuMigrationSrcToFile(driver, vm, fd, compressor, asyncJob) < 0)
-        goto cleanup;
-
-    /* Touch up file header to mark image complete. */
-
-    /* Reopen the file to touch up the header, since we aren't set
-     * up to seek backwards on wrapperFd.  The reopened fd will
-     * trigger a single page of file system cache pollution, but
-     * that's acceptable.  */
-    if (VIR_CLOSE(fd) < 0) {
-        virReportSystemError(errno, _("unable to close %s"), path);
-        goto cleanup;
-    }
-
-    if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0)
-        goto cleanup;
-
-    if ((fd = qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 ||
-        virQEMUSaveDataFinish(data, &fd, path) < 0)
-        goto cleanup;
-
-    ret = 0;
-
- cleanup:
-    VIR_FORCE_CLOSE(fd);
-    if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0)
-        ret = -1;
-    virFileWrapperFdFree(wrapperFd);
-
-    if (ret < 0 && needUnlink)
-        unlink(path);
-
-    return ret;
-}
-
 /* The vm must be active + locked. Vm will be unlocked and
  * potentially free'd after this returns (eg transient VMs are freed
  * shutdown). So 'vm' must not be referenced by the caller after
@@ -3192,8 +2870,8 @@ qemuDomainSaveInternal(virQEMUDriverPtr driver,
         goto endjob;
     xml = NULL;

-    ret = qemuDomainSaveMemory(driver, vm, path, data, compressor,
-                               flags, QEMU_ASYNC_JOB_SAVE);
+    ret = qemuSaveImageCreate(driver, vm, path, data, compressor,
+                              flags, QEMU_ASYNC_JOB_SAVE);
     if (ret < 0)
         goto endjob;

@@ -3231,87 +2909,6 @@ qemuDomainSaveInternal(virQEMUDriverPtr driver,
 }


-/* qemuGetCompressionProgram:
- * @imageFormat: String representation from qemu.conf for the compression
- *               image format being used (dump, save, or snapshot).
- * @compresspath: Pointer to a character string to store the fully qualified
- *                path from virFindFileInPath.
- * @styleFormat: String representing the style of format (dump, save, snapshot)
- * @use_raw_on_fail: Boolean indicating how to handle the error path. For
- *                   callers that are OK with invalid data or inability to
- *                   find the compression program, just return a raw format
- *                   and let the path remain as NULL.
- *
- * Returns:
- *    virQEMUSaveFormat    - Integer representation of the compression
- *                           program to be used for particular style
- *                           (e.g. dump, save, or snapshot).
- *    QEMU_SAVE_FORMAT_RAW - If there is no qemu.conf imageFormat value or
- *                           no there was an error, then just return RAW
- *                           indicating none.
- */
-static int ATTRIBUTE_NONNULL(2)
-qemuGetCompressionProgram(const char *imageFormat,
-                          virCommandPtr *compressor,
-                          const char *styleFormat,
-                          bool use_raw_on_fail)
-{
-    int ret;
-    const char *prog;
-
-    *compressor = NULL;
-
-    if (!imageFormat)
-        return QEMU_SAVE_FORMAT_RAW;
-
-    if ((ret = qemuSaveCompressionTypeFromString(imageFormat)) < 0)
-        goto error;
-
-    if (ret == QEMU_SAVE_FORMAT_RAW)
-        return QEMU_SAVE_FORMAT_RAW;
-
-    if (!(prog = virFindFileInPath(imageFormat)))
-        goto error;
-
-    *compressor = virCommandNew(prog);
-    virCommandAddArg(*compressor, "-c");
-    if (ret == QEMU_SAVE_FORMAT_XZ)
-        virCommandAddArg(*compressor, "-3");
-
-    return ret;
-
- error:
-    if (ret < 0) {
-        if (use_raw_on_fail)
-            VIR_WARN("Invalid %s image format specified in "
-                     "configuration file, using raw",
-                     styleFormat);
-        else
-            virReportError(VIR_ERR_OPERATION_FAILED,
-                           _("Invalid %s image format specified "
-                             "in configuration file"),
-                           styleFormat);
-    } else {
-        if (use_raw_on_fail)
-            VIR_WARN("Compression program for %s image format in "
-                     "configuration file isn't available, using raw",
-                     styleFormat);
-        else
-            virReportError(VIR_ERR_OPERATION_FAILED,
-                           _("Compression program for %s image format "
-                             "in configuration file isn't available"),
-                           styleFormat);
-    }
-
-    /* Use "raw" as the format if the specified format is not valid,
-     * or the compress program is not available. */
-    if (use_raw_on_fail)
-        return QEMU_SAVE_FORMAT_RAW;
-
-    return -1;
-}
-
-
 static int
 qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml,
                     unsigned int flags)
@@ -3328,9 +2925,9 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml,
                   VIR_DOMAIN_SAVE_PAUSED, -1);

     cfg = virQEMUDriverGetConfig(driver);
-    if ((compressed = qemuGetCompressionProgram(cfg->saveImageFormat,
-                                                &compressor,
-                                                "save", false)) < 0)
+    if ((compressed = qemuSaveImageGetCompressionProgram(cfg->saveImageFormat,
+                                                         &compressor,
+                                                         "save", false)) < 0)
         goto cleanup;

     if (!(vm = qemuDomainObjFromDomain(dom)))
@@ -3399,9 +2996,9 @@ qemuDomainManagedSave(virDomainPtr dom, unsigned int flags)
     }

     cfg = virQEMUDriverGetConfig(driver);
-    if ((compressed = qemuGetCompressionProgram(cfg->saveImageFormat,
-                                                &compressor,
-                                                "save", false)) < 0)
+    if ((compressed = qemuSaveImageGetCompressionProgram(cfg->saveImageFormat,
+                                                         &compressor,
+                                                         "save", false)) < 0)
         goto cleanup;

     if (!(name = qemuDomainManagedSavePath(driver, vm)))
@@ -3613,9 +3210,9 @@ doCoreDump(virQEMUDriverPtr driver,
      * format in "save" and "dump". This path doesn't need the compression
      * program to exist and can ignore the return value - it only cares to
      * get the compressor */
-    ignore_value(qemuGetCompressionProgram(cfg->dumpImageFormat,
-                                           &compressor,
-                                           "dump", true));
+    ignore_value(qemuSaveImageGetCompressionProgram(cfg->dumpImageFormat,
+                                                    &compressor,
+                                                    "dump", true));

     /* Create an empty file with appropriate ownership.  */
     if (dump_flags & VIR_DUMP_BYPASS_CACHE) {
@@ -6420,354 +6017,6 @@ static int qemuNodeGetSecurityModel(virConnectPtr conn,
 }


-/**
- * qemuDomainSaveImageUpdateDef:
- * @driver: qemu driver data
- * @def: def of the domain from the save image
- * @newxml: user provided replacement XML
- *
- * Returns the new domain definition in case @newxml is ABI compatible with the
- * guest.
- */
-static virDomainDefPtr
-qemuDomainSaveImageUpdateDef(virQEMUDriverPtr driver,
-                             virDomainDefPtr def,
-                             const char *newxml)
-{
-    virDomainDefPtr ret = NULL;
-    virDomainDefPtr newdef_migr = NULL;
-    virDomainDefPtr newdef = NULL;
-
-    if (!(newdef = virDomainDefParseString(newxml, driver->xmlopt, NULL,
-                                           VIR_DOMAIN_DEF_PARSE_INACTIVE)))
-        goto cleanup;
-
-    if (!(newdef_migr = qemuDomainDefCopy(driver, NULL,
-                                          newdef,
-                                          QEMU_DOMAIN_FORMAT_LIVE_FLAGS |
-                                          VIR_DOMAIN_XML_MIGRATABLE)))
-        goto cleanup;
-
-    if (!virDomainDefCheckABIStability(def, newdef_migr, driver->xmlopt)) {
-        virErrorPtr save_err;
-
-        virErrorPreserveLast(&save_err);
-
-        /* Due to a bug in older version of external snapshot creation
-         * code, the XML saved in the save image was not a migratable
-         * XML. To ensure backwards compatibility with the change of the
-         * saved XML type, we need to check the ABI compatibility against
-         * the user provided XML if the check against the migratable XML
-         * fails. Snapshots created prior to v1.1.3 have this issue. */
-        if (!virDomainDefCheckABIStability(def, newdef, driver->xmlopt)) {
-            virErrorRestore(&save_err);
-            goto cleanup;
-        }
-        virFreeError(save_err);
-
-        /* use the user provided XML */
-        ret = g_steal_pointer(&newdef);
-    } else {
-        ret = g_steal_pointer(&newdef_migr);
-    }
-
- cleanup:
-    virDomainDefFree(newdef);
-    virDomainDefFree(newdef_migr);
-
-    return ret;
-}
-
-
-/**
- * qemuDomainSaveImageOpen:
- * @driver: qemu driver data
- * @qemuCaps: pointer to qemuCaps if the domain is running or NULL
- * @path: path of the save image
- * @ret_def: returns domain definition created from the XML stored in the image
- * @ret_data: returns structure filled with data from the image header
- * @bypass_cache: bypass cache when opening the file
- * @wrapperFd: returns the file wrapper structure
- * @open_write: open the file for writing (for updates)
- * @unlink_corrupt: remove the image file if it is corrupted
- *
- * Returns the opened fd of the save image file and fills the appropriate fields
- * on success. On error returns -1 on most failures, -3 if corrupt image was
- * unlinked (no error raised).
- */
-static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
-qemuDomainSaveImageOpen(virQEMUDriverPtr driver,
-                        virQEMUCapsPtr qemuCaps,
-                        const char *path,
-                        virDomainDefPtr *ret_def,
-                        virQEMUSaveDataPtr *ret_data,
-                        bool bypass_cache,
-                        virFileWrapperFdPtr *wrapperFd,
-                        bool open_write,
-                        bool unlink_corrupt)
-{
-    VIR_AUTOCLOSE fd = -1;
-    int ret = -1;
-    g_autoptr(virQEMUSaveData) data = NULL;
-    virQEMUSaveHeaderPtr header;
-    g_autoptr(virDomainDef) def = NULL;
-    int oflags = open_write ? O_RDWR : O_RDONLY;
-    size_t xml_len;
-    size_t cookie_len;
-
-    if (bypass_cache) {
-        int directFlag = virFileDirectFdFlag();
-        if (directFlag < 0) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("bypass cache unsupported by this system"));
-            return -1;
-        }
-        oflags |= directFlag;
-    }
-
-    if ((fd = qemuDomainOpenFile(driver, NULL, path, oflags, NULL)) < 0)
-        return -1;
-
-    if (bypass_cache &&
-        !(*wrapperFd = virFileWrapperFdNew(&fd, path,
-                                           VIR_FILE_WRAPPER_BYPASS_CACHE)))
-        return -1;
-
-    data = g_new0(virQEMUSaveData, 1);
-
-    header = &data->header;
-    if (saferead(fd, header, sizeof(*header)) != sizeof(*header)) {
-        if (unlink_corrupt) {
-            if (unlink(path) < 0) {
-                virReportSystemError(errno,
-                                     _("cannot remove corrupt file: %s"),
-                                     path);
-                return -1;
-            } else {
-                return -3;
-            }
-        }
-
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       "%s", _("failed to read qemu header"));
-        return -1;
-    }
-
-    if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) != 0) {
-        if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)) == 0) {
-            if (unlink_corrupt) {
-                if (unlink(path) < 0) {
-                    virReportSystemError(errno,
-                                         _("cannot remove corrupt file: %s"),
-                                         path);
-                    return -1;
-                } else {
-                    return -3;
-                }
-            }
-
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("save image is incomplete"));
-            return -1;
-        }
-
-        virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                       _("image magic is incorrect"));
-        return -1;
-    }
-
-    if (header->version > QEMU_SAVE_VERSION) {
-        /* convert endianness and try again */
-        bswap_header(header);
-    }
-
-    if (header->version > QEMU_SAVE_VERSION) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("image version is not supported (%d > %d)"),
-                       header->version, QEMU_SAVE_VERSION);
-        return -1;
-    }
-
-    if (header->data_len <= 0) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("invalid header data length: %d"), header->data_len);
-        return -1;
-    }
-
-    if (header->cookieOffset)
-        xml_len = header->cookieOffset;
-    else
-        xml_len = header->data_len;
-
-    cookie_len = header->data_len - xml_len;
-
-    data->xml = g_new0(char, xml_len);
-
-    if (saferead(fd, data->xml, xml_len) != xml_len) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       "%s", _("failed to read domain XML"));
-        return -1;
-    }
-
-    if (cookie_len > 0) {
-        data->cookie = g_new0(char, cookie_len);
-
-        if (saferead(fd, data->cookie, cookie_len) != cookie_len) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("failed to read cookie"));
-            return -1;
-        }
-    }
-
-    /* Create a domain from this XML */
-    if (!(def = virDomainDefParseString(data->xml, driver->xmlopt, qemuCaps,
-                                        VIR_DOMAIN_DEF_PARSE_INACTIVE |
-                                        VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
-        return -1;
-
-    *ret_def = g_steal_pointer(&def);
-    *ret_data = g_steal_pointer(&data);
-
-    ret = fd;
-    fd = -1;
-
-    return ret;
-}
-
-static int ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6)
-qemuDomainSaveImageStartVM(virConnectPtr conn,
-                           virQEMUDriverPtr driver,
-                           virDomainObjPtr vm,
-                           int *fd,
-                           virQEMUSaveDataPtr data,
-                           const char *path,
-                           bool start_paused,
-                           qemuDomainAsyncJob asyncJob)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    int ret = -1;
-    bool started = false;
-    virObjectEventPtr event;
-    VIR_AUTOCLOSE intermediatefd = -1;
-    g_autoptr(virCommand) cmd = NULL;
-    g_autofree char *errbuf = NULL;
-    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
-    virQEMUSaveHeaderPtr header = &data->header;
-    g_autoptr(qemuDomainSaveCookie) cookie = NULL;
-    int rc = 0;
-
-    if (virSaveCookieParseString(data->cookie, (virObjectPtr *)&cookie,
-                                 virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0)
-        goto cleanup;
-
-    if ((header->version == 2) &&
-        (header->compressed != QEMU_SAVE_FORMAT_RAW)) {
-        if (!(cmd = qemuCompressGetCommand(header->compressed)))
-            goto cleanup;
-
-        intermediatefd = *fd;
-        *fd = -1;
-
-        virCommandSetInputFD(cmd, intermediatefd);
-        virCommandSetOutputFD(cmd, fd);
-        virCommandSetErrorBuffer(cmd, &errbuf);
-        virCommandDoAsyncIO(cmd);
-
-        if (virCommandRunAsync(cmd, NULL) < 0) {
-            *fd = intermediatefd;
-            intermediatefd = -1;
-            goto cleanup;
-        }
-    }
-
-    /* No cookie means libvirt which saved the domain was too old to mess up
-     * the CPU definitions.
-     */
-    if (cookie &&
-        qemuDomainFixupCPUs(vm, &cookie->cpu) < 0)
-        goto cleanup;
-
-    if (cookie && !cookie->slirpHelper)
-        priv->disableSlirp = true;
-
-    if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL,
-                         asyncJob, "stdio", *fd, path, NULL,
-                         VIR_NETDEV_VPORT_PROFILE_OP_RESTORE,
-                         VIR_QEMU_PROCESS_START_PAUSED |
-                         VIR_QEMU_PROCESS_START_GEN_VMID) == 0)
-        started = true;
-
-    if (intermediatefd != -1) {
-        virErrorPtr orig_err = NULL;
-
-        if (!started) {
-            /* if there was an error setting up qemu, the intermediate
-             * process will wait forever to write to stdout, so we
-             * must manually kill it and ignore any error related to
-             * the process
-             */
-            virErrorPreserveLast(&orig_err);
-            VIR_FORCE_CLOSE(intermediatefd);
-            VIR_FORCE_CLOSE(*fd);
-        }
-
-        rc = virCommandWait(cmd, NULL);
-        VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf));
-        virErrorRestore(&orig_err);
-    }
-    if (VIR_CLOSE(*fd) < 0) {
-        virReportSystemError(errno, _("cannot close file: %s"), path);
-        rc = -1;
-    }
-
-    virDomainAuditStart(vm, "restored", started);
-    if (!started || rc < 0)
-        goto cleanup;
-
-    /* qemuProcessStart doesn't unset the qemu error reporting infrastructure
-     * in case of migration (which is used in this case) so we need to reset it
-     * so that the handle to virtlogd is not held open unnecessarily */
-    qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL);
-
-    event = virDomainEventLifecycleNewFromObj(vm,
-                                     VIR_DOMAIN_EVENT_STARTED,
-                                     VIR_DOMAIN_EVENT_STARTED_RESTORED);
-    virObjectEventStateQueue(driver->domainEventState, event);
-
-
-    /* If it was running before, resume it now unless caller requested pause. */
-    if (header->was_running && !start_paused) {
-        if (qemuProcessStartCPUs(driver, vm,
-                                 VIR_DOMAIN_RUNNING_RESTORED,
-                                 asyncJob) < 0) {
-            if (virGetLastErrorCode() == VIR_ERR_OK)
-                virReportError(VIR_ERR_OPERATION_FAILED,
-                               "%s", _("failed to resume domain"));
-            goto cleanup;
-        }
-        if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) {
-            VIR_WARN("Failed to save status on vm %s", vm->def->name);
-            goto cleanup;
-        }
-    } else {
-        int detail = (start_paused ? VIR_DOMAIN_EVENT_SUSPENDED_PAUSED :
-                      VIR_DOMAIN_EVENT_SUSPENDED_RESTORED);
-        event = virDomainEventLifecycleNewFromObj(vm,
-                                         VIR_DOMAIN_EVENT_SUSPENDED,
-                                         detail);
-        virObjectEventStateQueue(driver->domainEventState, event);
-    }
-
-    ret = 0;
-
- cleanup:
-    if (ret < 0 && started) {
-        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED,
-                        asyncJob, VIR_QEMU_PROCESS_STOP_MIGRATED);
-    }
-    return ret;
-}
-
 static int
 qemuDomainRestoreFlags(virConnectPtr conn,
                        const char *path,
@@ -6793,9 +6042,9 @@ qemuDomainRestoreFlags(virConnectPtr conn,

     virNWFilterReadLockFilterUpdates();

-    fd = qemuDomainSaveImageOpen(driver, NULL, path, &def, &data,
-                                 (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0,
-                                 &wrapperFd, false, false);
+    fd = qemuSaveImageOpen(driver, NULL, path, &def, &data,
+                           (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0,
+                           &wrapperFd, false, false);
     if (fd < 0)
         goto cleanup;

@@ -6822,7 +6071,7 @@ qemuDomainRestoreFlags(virConnectPtr conn,

     if (newxml) {
         virDomainDefPtr tmp;
-        if (!(tmp = qemuDomainSaveImageUpdateDef(driver, def, newxml)))
+        if (!(tmp = qemuSaveImageUpdateDef(driver, def, newxml)))
             goto cleanup;

         virDomainDefFree(def);
@@ -6851,8 +6100,8 @@ qemuDomainRestoreFlags(virConnectPtr conn,
                             flags) < 0)
         goto cleanup;

-    ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, data, path,
-                                     false, QEMU_ASYNC_JOB_START);
+    ret = qemuSaveImageStartVM(conn, driver, vm, &fd, data, path,
+                               false, QEMU_ASYNC_JOB_START);

     qemuProcessEndJob(driver, vm);

@@ -6889,8 +6138,8 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path,

     virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL);

-    fd = qemuDomainSaveImageOpen(driver, NULL, path, &def, &data,
-                                 false, NULL, false, false);
+    fd = qemuSaveImageOpen(driver, NULL, path, &def, &data,
+                           false, NULL, false, false);

     if (fd < 0)
         goto cleanup;
@@ -6927,8 +6176,8 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path,
     else if (flags & VIR_DOMAIN_SAVE_PAUSED)
         state = 0;

-    fd = qemuDomainSaveImageOpen(driver, NULL, path, &def, &data,
-                                 false, NULL, true, false);
+    fd = qemuSaveImageOpen(driver, NULL, path, &def, &data,
+                           false, NULL, true, false);

     if (fd < 0)
         goto cleanup;
@@ -6946,7 +6195,7 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path,
     if (state >= 0)
         data->header.was_running = state;

-    if (!(newdef = qemuDomainSaveImageUpdateDef(driver, def, dxml)))
+    if (!(newdef = qemuSaveImageUpdateDef(driver, def, dxml)))
         goto cleanup;

     VIR_FREE(data->xml);
@@ -7011,8 +6260,8 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags)
         goto cleanup;
     }

-    if ((fd = qemuDomainSaveImageOpen(driver, priv->qemuCaps, path, &def, &data,
-                                      false, NULL, false, false)) < 0)
+    if ((fd = qemuSaveImageOpen(driver, priv->qemuCaps, path, &def, &data,
+                                false, NULL, false, false)) < 0)
         goto cleanup;

     ret = qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags);
@@ -7076,8 +6325,8 @@ qemuDomainObjRestore(virConnectPtr conn,
     virQEMUSaveDataPtr data = NULL;
     virFileWrapperFdPtr wrapperFd = NULL;

-    fd = qemuDomainSaveImageOpen(driver, NULL, path, &def, &data,
-                                 bypass_cache, &wrapperFd, false, true);
+    fd = qemuSaveImageOpen(driver, NULL, path, &def, &data,
+                           bypass_cache, &wrapperFd, false, true);
     if (fd < 0) {
         if (fd == -3)
             ret = 1;
@@ -7098,7 +6347,7 @@ qemuDomainObjRestore(virConnectPtr conn,

             VIR_DEBUG("Using hook-filtered domain XML: %s", xmlout);

-            if (!(tmp = qemuDomainSaveImageUpdateDef(driver, def, xmlout)))
+            if (!(tmp = qemuSaveImageUpdateDef(driver, def, xmlout)))
                 goto cleanup;

             virDomainDefFree(def);
@@ -7124,8 +6373,8 @@ qemuDomainObjRestore(virConnectPtr conn,
     virDomainObjAssignDef(vm, def, true, NULL);
     def = NULL;

-    ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, data, path,
-                                     start_paused, asyncJob);
+    ret = qemuSaveImageStartVM(conn, driver, vm, &fd, data, path,
+                               start_paused, asyncJob);

  cleanup:
     virQEMUSaveDataFree(data);
@@ -15333,9 +14582,9 @@ qemuDomainSnapshotCreateActiveExternal(virQEMUDriverPtr driver,
                                           JOB_MASK(QEMU_JOB_SUSPEND) |
                                           JOB_MASK(QEMU_JOB_MIGRATION_OP)));

-        if ((compressed = qemuGetCompressionProgram(cfg->snapshotImageFormat,
-                                                    &compressor,
-                                                    "snapshot", false)) < 0)
+        if ((compressed = qemuSaveImageGetCompressionProgram(cfg->snapshotImageFormat,
+                                                             &compressor,
+                                                             "snapshot", false)) < 0)
             goto cleanup;

         if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
@@ -15350,9 +14599,9 @@ qemuDomainSnapshotCreateActiveExternal(virQEMUDriverPtr driver,
             goto cleanup;
         xml = NULL;

-        if ((ret = qemuDomainSaveMemory(driver, vm, snapdef->file, data,
-                                        compressor, 0,
-                                        QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+        if ((ret = qemuSaveImageCreate(driver, vm, snapdef->file, data,
+                                      compressor, 0,
+                                      QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
             goto cleanup;

         /* the memory image was created, remove it on errors */
diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c
new file mode 100644
index 0000000000..52468056ad
--- /dev/null
+++ b/src/qemu/qemu_saveimage.c
@@ -0,0 +1,764 @@
+/*
+ * qemu_saveimage.c: Infrastructure for saving qemu state to a file
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "qemu_saveimage.h"
+#include "qemu_domain.h"
+#include "qemu_migration.h"
+#include "qemu_process.h"
+#include "qemu_security.h"
+
+#include "domain_audit.h"
+
+#include "virerror.h"
+#include "virlog.h"
+#include "viralloc.h"
+#include "virqemu.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_saveimage");
+
+typedef enum {
+    QEMU_SAVE_FORMAT_RAW = 0,
+    QEMU_SAVE_FORMAT_GZIP = 1,
+    QEMU_SAVE_FORMAT_BZIP2 = 2,
+    /*
+     * Deprecated by xz and never used as part of a release
+     * QEMU_SAVE_FORMAT_LZMA
+     */
+    QEMU_SAVE_FORMAT_XZ = 3,
+    QEMU_SAVE_FORMAT_LZOP = 4,
+    /* Note: add new members only at the end.
+       These values are used in the on-disk format.
+       Do not change or re-use numbers. */
+
+    QEMU_SAVE_FORMAT_LAST
+} virQEMUSaveFormat;
+
+VIR_ENUM_DECL(qemuSaveCompression);
+VIR_ENUM_IMPL(qemuSaveCompression,
+              QEMU_SAVE_FORMAT_LAST,
+              "raw",
+              "gzip",
+              "bzip2",
+              "xz",
+              "lzop",
+);
+
+static inline void
+qemuSaveImageBswapHeader(virQEMUSaveHeaderPtr hdr)
+{
+    hdr->version = GUINT32_SWAP_LE_BE(hdr->version);
+    hdr->data_len = GUINT32_SWAP_LE_BE(hdr->data_len);
+    hdr->was_running = GUINT32_SWAP_LE_BE(hdr->was_running);
+    hdr->compressed = GUINT32_SWAP_LE_BE(hdr->compressed);
+    hdr->cookieOffset = GUINT32_SWAP_LE_BE(hdr->cookieOffset);
+}
+
+
+void
+virQEMUSaveDataFree(virQEMUSaveDataPtr data)
+{
+    if (!data)
+        return;
+
+    VIR_FREE(data->xml);
+    VIR_FREE(data->cookie);
+    VIR_FREE(data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virQEMUSaveData, virQEMUSaveDataFree);
+
+/**
+ * This function steals @domXML on success.
+ */
+virQEMUSaveDataPtr
+virQEMUSaveDataNew(char *domXML,
+                   qemuDomainSaveCookiePtr cookieObj,
+                   bool running,
+                   int compressed,
+                   virDomainXMLOptionPtr xmlopt)
+{
+    virQEMUSaveDataPtr data = NULL;
+    virQEMUSaveHeaderPtr header;
+
+    if (VIR_ALLOC(data) < 0)
+        return NULL;
+
+    data->xml = g_steal_pointer(&domXML);
+
+    if (cookieObj &&
+        !(data->cookie = virSaveCookieFormat((virObjectPtr) cookieObj,
+                                             virDomainXMLOptionGetSaveCookie(xmlopt))))
+        goto error;
+
+    header = &data->header;
+    memcpy(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic));
+    header->version = QEMU_SAVE_VERSION;
+    header->was_running = running ? 1 : 0;
+    header->compressed = compressed;
+
+    return data;
+
+ error:
+    virQEMUSaveDataFree(data);
+    return NULL;
+}
+
+
+/* virQEMUSaveDataWrite:
+ *
+ * Writes libvirt's header (including domain XML) into a saved image of a
+ * running domain. If @header has data_len filled in (because it was previously
+ * read from the file), the function will make sure the new data will fit
+ * within data_len.
+ *
+ * Returns -1 on failure, or 0 on success.
+ */
+int
+virQEMUSaveDataWrite(virQEMUSaveDataPtr data,
+                     int fd,
+                     const char *path)
+{
+    virQEMUSaveHeaderPtr header = &data->header;
+    size_t len;
+    size_t xml_len;
+    size_t cookie_len = 0;
+    size_t zerosLen = 0;
+    g_autofree char *zeros = NULL;
+
+    xml_len = strlen(data->xml) + 1;
+    if (data->cookie)
+        cookie_len = strlen(data->cookie) + 1;
+
+    len = xml_len + cookie_len;
+
+    if (header->data_len == 0) {
+        /* This 64kb padding allows the user to edit the XML in
+         * a saved state image and have the new XML be larger
+         * that what was originally saved
+         */
+        header->data_len = len + (64 * 1024);
+    } else {
+        if (len > header->data_len) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("new xml too large to fit in file"));
+            return -1;
+        }
+    }
+
+    zerosLen = header->data_len - len;
+    zeros = g_new0(char, zerosLen);
+
+    if (data->cookie)
+        header->cookieOffset = xml_len;
+
+    if (safewrite(fd, header, sizeof(*header)) != sizeof(*header)) {
+        virReportSystemError(errno,
+                             _("failed to write header to domain save file '%s'"),
+                             path);
+        return -1;
+    }
+
+    if (safewrite(fd, data->xml, xml_len) != xml_len) {
+        virReportSystemError(errno,
+                             _("failed to write domain xml to '%s'"),
+                             path);
+        return -1;
+    }
+
+    if (data->cookie &&
+        safewrite(fd, data->cookie, cookie_len) != cookie_len) {
+        virReportSystemError(errno,
+                             _("failed to write cookie to '%s'"),
+                             path);
+        return -1;
+    }
+
+    if (safewrite(fd, zeros, zerosLen) != zerosLen) {
+        virReportSystemError(errno,
+                             _("failed to write padding to '%s'"),
+                             path);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+virQEMUSaveDataFinish(virQEMUSaveDataPtr data,
+                      int *fd,
+                      const char *path)
+{
+    virQEMUSaveHeaderPtr header = &data->header;
+
+    memcpy(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic));
+
+    if (safewrite(*fd, header, sizeof(*header)) != sizeof(*header) ||
+        VIR_CLOSE(*fd) < 0) {
+        virReportSystemError(errno,
+                             _("failed to write header to domain save file '%s'"),
+                             path);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static virCommandPtr
+qemuSaveImageGetCompressionCommand(virQEMUSaveFormat compression)
+{
+    virCommandPtr ret = NULL;
+    const char *prog = qemuSaveCompressionTypeToString(compression);
+
+    if (!prog) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       _("Invalid compressed save format %d"),
+                       compression);
+        return NULL;
+    }
+
+    ret = virCommandNew(prog);
+    virCommandAddArg(ret, "-dc");
+
+    if (compression == QEMU_SAVE_FORMAT_LZOP)
+        virCommandAddArg(ret, "--ignore-warn");
+
+    return ret;
+}
+
+
+/* Helper function to execute a migration to file with a correct save header
+ * the caller needs to make sure that the processors are stopped and do all other
+ * actions besides saving memory */
+int
+qemuSaveImageCreate(virQEMUDriverPtr driver,
+                    virDomainObjPtr vm,
+                    const char *path,
+                    virQEMUSaveDataPtr data,
+                    virCommandPtr compressor,
+                    unsigned int flags,
+                    qemuDomainAsyncJob asyncJob)
+{
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+    bool needUnlink = false;
+    int ret = -1;
+    int fd = -1;
+    int directFlag = 0;
+    virFileWrapperFdPtr wrapperFd = NULL;
+    unsigned int wrapperFlags = VIR_FILE_WRAPPER_NON_BLOCKING;
+
+    /* Obtain the file handle.  */
+    if ((flags & VIR_DOMAIN_SAVE_BYPASS_CACHE)) {
+        wrapperFlags |= VIR_FILE_WRAPPER_BYPASS_CACHE;
+        directFlag = virFileDirectFdFlag();
+        if (directFlag < 0) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("bypass cache unsupported by this system"));
+            goto cleanup;
+        }
+    }
+
+    fd = virQEMUFileOpenAs(cfg->user, cfg->group, false, path,
+                           O_WRONLY | O_TRUNC | O_CREAT | directFlag,
+                           &needUnlink);
+    if (fd < 0)
+        goto cleanup;
+
+    if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, fd) < 0)
+        goto cleanup;
+
+    if (!(wrapperFd = virFileWrapperFdNew(&fd, path, wrapperFlags)))
+        goto cleanup;
+
+    if (virQEMUSaveDataWrite(data, fd, path) < 0)
+        goto cleanup;
+
+    /* Perform the migration */
+    if (qemuMigrationSrcToFile(driver, vm, fd, compressor, asyncJob) < 0)
+        goto cleanup;
+
+    /* Touch up file header to mark image complete. */
+
+    /* Reopen the file to touch up the header, since we aren't set
+     * up to seek backwards on wrapperFd.  The reopened fd will
+     * trigger a single page of file system cache pollution, but
+     * that's acceptable.  */
+    if (VIR_CLOSE(fd) < 0) {
+        virReportSystemError(errno, _("unable to close %s"), path);
+        goto cleanup;
+    }
+
+    if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0)
+        goto cleanup;
+
+    if ((fd = qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 ||
+        virQEMUSaveDataFinish(data, &fd, path) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    VIR_FORCE_CLOSE(fd);
+    if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0)
+        ret = -1;
+    virFileWrapperFdFree(wrapperFd);
+
+    if (ret < 0 && needUnlink)
+        unlink(path);
+
+    return ret;
+}
+
+
+/* qemuSaveImageGetCompressionProgram:
+ * @imageFormat: String representation from qemu.conf for the compression
+ *               image format being used (dump, save, or snapshot).
+ * @compresspath: Pointer to a character string to store the fully qualified
+ *                path from virFindFileInPath.
+ * @styleFormat: String representing the style of format (dump, save, snapshot)
+ * @use_raw_on_fail: Boolean indicating how to handle the error path. For
+ *                   callers that are OK with invalid data or inability to
+ *                   find the compression program, just return a raw format
+ *                   and let the path remain as NULL.
+ *
+ * Returns:
+ *    virQEMUSaveFormat    - Integer representation of the compression
+ *                           program to be used for particular style
+ *                           (e.g. dump, save, or snapshot).
+ *    QEMU_SAVE_FORMAT_RAW - If there is no qemu.conf imageFormat value or
+ *                           no there was an error, then just return RAW
+ *                           indicating none.
+ */
+int
+qemuSaveImageGetCompressionProgram(const char *imageFormat,
+                                   virCommandPtr *compressor,
+                                   const char *styleFormat,
+                                   bool use_raw_on_fail)
+{
+    int ret;
+    const char *prog;
+
+    *compressor = NULL;
+
+    if (!imageFormat)
+        return QEMU_SAVE_FORMAT_RAW;
+
+    if ((ret = qemuSaveCompressionTypeFromString(imageFormat)) < 0)
+        goto error;
+
+    if (ret == QEMU_SAVE_FORMAT_RAW)
+        return QEMU_SAVE_FORMAT_RAW;
+
+    if (!(prog = virFindFileInPath(imageFormat)))
+        goto error;
+
+    *compressor = virCommandNew(prog);
+    virCommandAddArg(*compressor, "-c");
+    if (ret == QEMU_SAVE_FORMAT_XZ)
+        virCommandAddArg(*compressor, "-3");
+
+    return ret;
+
+ error:
+    if (ret < 0) {
+        if (use_raw_on_fail)
+            VIR_WARN("Invalid %s image format specified in "
+                     "configuration file, using raw",
+                     styleFormat);
+        else
+            virReportError(VIR_ERR_OPERATION_FAILED,
+                           _("Invalid %s image format specified "
+                             "in configuration file"),
+                           styleFormat);
+    } else {
+        if (use_raw_on_fail)
+            VIR_WARN("Compression program for %s image format in "
+                     "configuration file isn't available, using raw",
+                     styleFormat);
+        else
+            virReportError(VIR_ERR_OPERATION_FAILED,
+                           _("Compression program for %s image format "
+                             "in configuration file isn't available"),
+                           styleFormat);
+    }
+
+    /* Use "raw" as the format if the specified format is not valid,
+     * or the compress program is not available. */
+    if (use_raw_on_fail)
+        return QEMU_SAVE_FORMAT_RAW;
+
+    return -1;
+}
+
+
+/**
+ * qemuSaveImageOpen:
+ * @driver: qemu driver data
+ * @qemuCaps: pointer to qemuCaps if the domain is running or NULL
+ * @path: path of the save image
+ * @ret_def: returns domain definition created from the XML stored in the image
+ * @ret_data: returns structure filled with data from the image header
+ * @bypass_cache: bypass cache when opening the file
+ * @wrapperFd: returns the file wrapper structure
+ * @open_write: open the file for writing (for updates)
+ * @unlink_corrupt: remove the image file if it is corrupted
+ *
+ * Returns the opened fd of the save image file and fills the appropriate fields
+ * on success. On error returns -1 on most failures, -3 if corrupt image was
+ * unlinked (no error raised).
+ */
+int
+qemuSaveImageOpen(virQEMUDriverPtr driver,
+                  virQEMUCapsPtr qemuCaps,
+                  const char *path,
+                  virDomainDefPtr *ret_def,
+                  virQEMUSaveDataPtr *ret_data,
+                  bool bypass_cache,
+                  virFileWrapperFdPtr *wrapperFd,
+                  bool open_write,
+                  bool unlink_corrupt)
+{
+    VIR_AUTOCLOSE fd = -1;
+    int ret = -1;
+    g_autoptr(virQEMUSaveData) data = NULL;
+    virQEMUSaveHeaderPtr header;
+    g_autoptr(virDomainDef) def = NULL;
+    int oflags = open_write ? O_RDWR : O_RDONLY;
+    size_t xml_len;
+    size_t cookie_len;
+
+    if (bypass_cache) {
+        int directFlag = virFileDirectFdFlag();
+        if (directFlag < 0) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("bypass cache unsupported by this system"));
+            return -1;
+        }
+        oflags |= directFlag;
+    }
+
+    if ((fd = qemuDomainOpenFile(driver, NULL, path, oflags, NULL)) < 0)
+        return -1;
+
+    if (bypass_cache &&
+        !(*wrapperFd = virFileWrapperFdNew(&fd, path,
+                                           VIR_FILE_WRAPPER_BYPASS_CACHE)))
+        return -1;
+
+    data = g_new0(virQEMUSaveData, 1);
+
+    header = &data->header;
+    if (saferead(fd, header, sizeof(*header)) != sizeof(*header)) {
+        if (unlink_corrupt) {
+            if (unlink(path) < 0) {
+                virReportSystemError(errno,
+                                     _("cannot remove corrupt file: %s"),
+                                     path);
+                return -1;
+            } else {
+                return -3;
+            }
+        }
+
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       "%s", _("failed to read qemu header"));
+        return -1;
+    }
+
+    if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) != 0) {
+        if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)) == 0) {
+            if (unlink_corrupt) {
+                if (unlink(path) < 0) {
+                    virReportSystemError(errno,
+                                         _("cannot remove corrupt file: %s"),
+                                         path);
+                    return -1;
+                } else {
+                    return -3;
+                }
+            }
+
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("save image is incomplete"));
+            return -1;
+        }
+
+        virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                       _("image magic is incorrect"));
+        return -1;
+    }
+
+    if (header->version > QEMU_SAVE_VERSION) {
+        /* convert endianness and try again */
+        qemuSaveImageBswapHeader(header);
+    }
+
+    if (header->version > QEMU_SAVE_VERSION) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       _("image version is not supported (%d > %d)"),
+                       header->version, QEMU_SAVE_VERSION);
+        return -1;
+    }
+
+    if (header->data_len <= 0) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       _("invalid header data length: %d"), header->data_len);
+        return -1;
+    }
+
+    if (header->cookieOffset)
+        xml_len = header->cookieOffset;
+    else
+        xml_len = header->data_len;
+
+    cookie_len = header->data_len - xml_len;
+
+    data->xml = g_new0(char, xml_len);
+
+    if (saferead(fd, data->xml, xml_len) != xml_len) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       "%s", _("failed to read domain XML"));
+        return -1;
+    }
+
+    if (cookie_len > 0) {
+        data->cookie = g_new0(char, cookie_len);
+
+        if (saferead(fd, data->cookie, cookie_len) != cookie_len) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("failed to read cookie"));
+            return -1;
+        }
+    }
+
+    /* Create a domain from this XML */
+    if (!(def = virDomainDefParseString(data->xml, driver->xmlopt, qemuCaps,
+                                        VIR_DOMAIN_DEF_PARSE_INACTIVE |
+                                        VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
+        return -1;
+
+    *ret_def = g_steal_pointer(&def);
+    *ret_data = g_steal_pointer(&data);
+
+    ret = fd;
+    fd = -1;
+
+    return ret;
+}
+
+int
+qemuSaveImageStartVM(virConnectPtr conn,
+                     virQEMUDriverPtr driver,
+                     virDomainObjPtr vm,
+                     int *fd,
+                     virQEMUSaveDataPtr data,
+                     const char *path,
+                     bool start_paused,
+                     qemuDomainAsyncJob asyncJob)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    int ret = -1;
+    bool started = false;
+    virObjectEventPtr event;
+    VIR_AUTOCLOSE intermediatefd = -1;
+    g_autoptr(virCommand) cmd = NULL;
+    g_autofree char *errbuf = NULL;
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+    virQEMUSaveHeaderPtr header = &data->header;
+    g_autoptr(qemuDomainSaveCookie) cookie = NULL;
+    int rc = 0;
+
+    if (virSaveCookieParseString(data->cookie, (virObjectPtr *)&cookie,
+                                 virDomainXMLOptionGetSaveCookie(driver->xmlopt)) < 0)
+        goto cleanup;
+
+    if ((header->version == 2) &&
+        (header->compressed != QEMU_SAVE_FORMAT_RAW)) {
+        if (!(cmd = qemuSaveImageGetCompressionCommand(header->compressed)))
+            goto cleanup;
+
+        intermediatefd = *fd;
+        *fd = -1;
+
+        virCommandSetInputFD(cmd, intermediatefd);
+        virCommandSetOutputFD(cmd, fd);
+        virCommandSetErrorBuffer(cmd, &errbuf);
+        virCommandDoAsyncIO(cmd);
+
+        if (virCommandRunAsync(cmd, NULL) < 0) {
+            *fd = intermediatefd;
+            intermediatefd = -1;
+            goto cleanup;
+        }
+    }
+
+    /* No cookie means libvirt which saved the domain was too old to mess up
+     * the CPU definitions.
+     */
+    if (cookie &&
+        qemuDomainFixupCPUs(vm, &cookie->cpu) < 0)
+        goto cleanup;
+
+    if (cookie && !cookie->slirpHelper)
+        priv->disableSlirp = true;
+
+    if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL,
+                         asyncJob, "stdio", *fd, path, NULL,
+                         VIR_NETDEV_VPORT_PROFILE_OP_RESTORE,
+                         VIR_QEMU_PROCESS_START_PAUSED |
+                         VIR_QEMU_PROCESS_START_GEN_VMID) == 0)
+        started = true;
+
+    if (intermediatefd != -1) {
+        virErrorPtr orig_err = NULL;
+
+        if (!started) {
+            /* if there was an error setting up qemu, the intermediate
+             * process will wait forever to write to stdout, so we
+             * must manually kill it and ignore any error related to
+             * the process
+             */
+            virErrorPreserveLast(&orig_err);
+            VIR_FORCE_CLOSE(intermediatefd);
+            VIR_FORCE_CLOSE(*fd);
+        }
+
+        rc = virCommandWait(cmd, NULL);
+        VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf));
+        virErrorRestore(&orig_err);
+    }
+    if (VIR_CLOSE(*fd) < 0) {
+        virReportSystemError(errno, _("cannot close file: %s"), path);
+        rc = -1;
+    }
+
+    virDomainAuditStart(vm, "restored", started);
+    if (!started || rc < 0)
+        goto cleanup;
+
+    /* qemuProcessStart doesn't unset the qemu error reporting infrastructure
+     * in case of migration (which is used in this case) so we need to reset it
+     * so that the handle to virtlogd is not held open unnecessarily */
+    qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL);
+
+    event = virDomainEventLifecycleNewFromObj(vm,
+                                     VIR_DOMAIN_EVENT_STARTED,
+                                     VIR_DOMAIN_EVENT_STARTED_RESTORED);
+    virObjectEventStateQueue(driver->domainEventState, event);
+
+
+    /* If it was running before, resume it now unless caller requested pause. */
+    if (header->was_running && !start_paused) {
+        if (qemuProcessStartCPUs(driver, vm,
+                                 VIR_DOMAIN_RUNNING_RESTORED,
+                                 asyncJob) < 0) {
+            if (virGetLastErrorCode() == VIR_ERR_OK)
+                virReportError(VIR_ERR_OPERATION_FAILED,
+                               "%s", _("failed to resume domain"));
+            goto cleanup;
+        }
+        if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) {
+            VIR_WARN("Failed to save status on vm %s", vm->def->name);
+            goto cleanup;
+        }
+    } else {
+        int detail = (start_paused ? VIR_DOMAIN_EVENT_SUSPENDED_PAUSED :
+                      VIR_DOMAIN_EVENT_SUSPENDED_RESTORED);
+        event = virDomainEventLifecycleNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         detail);
+        virObjectEventStateQueue(driver->domainEventState, event);
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (ret < 0 && started) {
+        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED,
+                        asyncJob, VIR_QEMU_PROCESS_STOP_MIGRATED);
+    }
+    return ret;
+}
+
+
+/**
+ * qemuSaveImageUpdateDef:
+ * @driver: qemu driver data
+ * @def: def of the domain from the save image
+ * @newxml: user provided replacement XML
+ *
+ * Returns the new domain definition in case @newxml is ABI compatible with the
+ * guest.
+ */
+virDomainDefPtr
+qemuSaveImageUpdateDef(virQEMUDriverPtr driver,
+                       virDomainDefPtr def,
+                       const char *newxml)
+{
+    virDomainDefPtr ret = NULL;
+    virDomainDefPtr newdef_migr = NULL;
+    virDomainDefPtr newdef = NULL;
+
+    if (!(newdef = virDomainDefParseString(newxml, driver->xmlopt, NULL,
+                                           VIR_DOMAIN_DEF_PARSE_INACTIVE)))
+        goto cleanup;
+
+    if (!(newdef_migr = qemuDomainDefCopy(driver, NULL,
+                                          newdef,
+                                          QEMU_DOMAIN_FORMAT_LIVE_FLAGS |
+                                          VIR_DOMAIN_XML_MIGRATABLE)))
+        goto cleanup;
+
+    if (!virDomainDefCheckABIStability(def, newdef_migr, driver->xmlopt)) {
+        virErrorPtr save_err;
+
+        virErrorPreserveLast(&save_err);
+
+        /* Due to a bug in older version of external snapshot creation
+         * code, the XML saved in the save image was not a migratable
+         * XML. To ensure backwards compatibility with the change of the
+         * saved XML type, we need to check the ABI compatibility against
+         * the user provided XML if the check against the migratable XML
+         * fails. Snapshots created prior to v1.1.3 have this issue. */
+        if (!virDomainDefCheckABIStability(def, newdef, driver->xmlopt)) {
+            virErrorRestore(&save_err);
+            goto cleanup;
+        }
+        virFreeError(save_err);
+
+        /* use the user provided XML */
+        ret = g_steal_pointer(&newdef);
+    } else {
+        ret = g_steal_pointer(&newdef_migr);
+    }
+
+ cleanup:
+    virDomainDefFree(newdef);
+    virDomainDefFree(newdef_migr);
+
+    return ret;
+}
diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h
new file mode 100644
index 0000000000..f9fecbcc46
--- /dev/null
+++ b/src/qemu/qemu_saveimage.h
@@ -0,0 +1,116 @@
+/*
+ * qemu_saveimage.h: Infrastructure for saving qemu state to a file
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "virconftypes.h"
+#include "datatypes.h"
+
+#include "qemu_conf.h"
+#include "qemu_domainjob.h"
+#include "qemu_domain.h"
+
+/* It would be nice to replace 'Qemud' with 'Qemu' but
+ * this magic string is ABI, so it can't be changed
+ */
+#define QEMU_SAVE_MAGIC   "LibvirtQemudSave"
+#define QEMU_SAVE_PARTIAL "LibvirtQemudPart"
+#define QEMU_SAVE_VERSION 2
+
+G_STATIC_ASSERT(sizeof(QEMU_SAVE_MAGIC) == sizeof(QEMU_SAVE_PARTIAL));
+
+typedef struct _virQEMUSaveHeader virQEMUSaveHeader;
+typedef virQEMUSaveHeader *virQEMUSaveHeaderPtr;
+struct _virQEMUSaveHeader {
+    char magic[sizeof(QEMU_SAVE_MAGIC)-1];
+    uint32_t version;
+    uint32_t data_len;
+    uint32_t was_running;
+    uint32_t compressed;
+    uint32_t cookieOffset;
+    uint32_t unused[14];
+};
+
+
+typedef struct _virQEMUSaveData virQEMUSaveData;
+typedef virQEMUSaveData *virQEMUSaveDataPtr;
+struct _virQEMUSaveData {
+    virQEMUSaveHeader header;
+    char *xml;
+    char *cookie;
+};
+
+
+virDomainDefPtr
+qemuSaveImageUpdateDef(virQEMUDriverPtr driver,
+                       virDomainDefPtr def,
+                       const char *newxml);
+
+int
+qemuSaveImageStartVM(virConnectPtr conn,
+                     virQEMUDriverPtr driver,
+                     virDomainObjPtr vm,
+                     int *fd,
+                     virQEMUSaveDataPtr data,
+                     const char *path,
+                     bool start_paused,
+                     qemuDomainAsyncJob asyncJob)
+    ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6);
+
+int
+qemuSaveImageOpen(virQEMUDriverPtr driver,
+                  virQEMUCapsPtr qemuCaps,
+                  const char *path,
+                  virDomainDefPtr *ret_def,
+                  virQEMUSaveDataPtr *ret_data,
+                  bool bypass_cache,
+                  virFileWrapperFdPtr *wrapperFd,
+                  bool open_write,
+                  bool unlink_corrupt)
+    ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4);
+
+int
+qemuSaveImageGetCompressionProgram(const char *imageFormat,
+                                   virCommandPtr *compressor,
+                                   const char *styleFormat,
+                                   bool use_raw_on_fail)
+    ATTRIBUTE_NONNULL(2);
+
+int
+qemuSaveImageCreate(virQEMUDriverPtr driver,
+                    virDomainObjPtr vm,
+                    const char *path,
+                    virQEMUSaveDataPtr data,
+                    virCommandPtr compressor,
+                    unsigned int flags,
+                    qemuDomainAsyncJob asyncJob);
+
+int
+virQEMUSaveDataWrite(virQEMUSaveDataPtr data,
+                     int fd,
+                     const char *path);
+
+virQEMUSaveDataPtr
+virQEMUSaveDataNew(char *domXML,
+                   qemuDomainSaveCookiePtr cookieObj,
+                   bool running,
+                   int compressed,
+                   virDomainXMLOptionPtr xmlopt);
+
+void
+virQEMUSaveDataFree(virQEMUSaveDataPtr data);
-- 
2.26.2




More information about the libvir-list mailing list