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@xxxxxxxxxx> --- 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@src/qemu/qemu_namespace.c @SRCDIR@src/qemu/qemu_process.c @SRCDIR@src/qemu/qemu_qapi.c +@SRCDIR@src/qemu/qemu_saveimage.c @SRCDIR@src/qemu/qemu_slirp.c @SRCDIR@src/qemu/qemu_tpm.c @SRCDIR@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