[libvirt] [PATCH 0/2] Split out parts of qemu_driver.c into separate files

qemu_driver.c is starting to grow out of proportions. This series creates two(actualy four) files to split out some code from the main file. 1) qemu_util.c - various qemu-related tools and common code (~0.5k lines) 2) qemu_snapshot.c - snapshot related code (~1.7k lines) Apart from reformating function headers, no changes were made to the code. Peter Krempa (2): qemu: Split out various utility functions to qemu_util.c qemu: Split out snapshot related functions to qemu_snapshot.c po/POTFILES.in | 2 + src/Makefile.am | 2 + src/qemu/qemu_driver.c | 2191 +--------------------------------------------- src/qemu/qemu_snapshot.c | 1752 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_snapshot.h | 38 + src/qemu/qemu_util.c | 486 ++++++++++ src/qemu/qemu_util.h | 111 +++ 7 files changed, 2404 insertions(+), 2178 deletions(-) create mode 100644 src/qemu/qemu_snapshot.c create mode 100644 src/qemu/qemu_snapshot.h create mode 100644 src/qemu/qemu_util.c create mode 100644 src/qemu/qemu_util.h -- 1.8.0.2 e

--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 492 ++----------------------------------------------- src/qemu/qemu_util.c | 486 ++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_util.h | 111 +++++++++++ 5 files changed, 611 insertions(+), 480 deletions(-) create mode 100644 src/qemu/qemu_util.c create mode 100644 src/qemu/qemu_util.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 4d94799..4edacfa 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -101,6 +101,7 @@ src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c src/qemu/qemu_process.c +src/qemu/qemu_util.c src/remote/remote_client_bodies.h src/remote/remote_driver.c src/rpc/virkeepalive.c diff --git a/src/Makefile.am b/src/Makefile.am index f7a9b91..f76a2ea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -515,6 +515,7 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_conf.c qemu/qemu_conf.h \ qemu/qemu_process.c qemu/qemu_process.h \ qemu/qemu_migration.c qemu/qemu_migration.h \ + qemu/qemu_util.c qemu/qemu_util.h \ qemu/qemu_monitor.c qemu/qemu_monitor.h \ qemu/qemu_monitor_text.c \ qemu/qemu_monitor_text.h \ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 3a52b47..a9c03b6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1,7 +1,7 @@ /* * qemu_driver.c: core driver methods for managing qemu guests * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2013 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -57,6 +57,7 @@ #include "qemu_bridge_filter.h" #include "qemu_process.h" #include "qemu_migration.h" +#include "qemu_util.h" #include "virerror.h" #include "virlog.h" @@ -186,97 +187,6 @@ struct qemuAutostartData { virConnectPtr conn; }; -/** - * qemuDomObjFromDomainDriver: - * @domain: Domain pointer that has to be looked up - * @drv: Pointer to virQEMUDriverPtr to return the driver object - * - * This function looks up @domain in the domain list and returns the - * appropriate virDomainObjPtr. On successful lookup, both driver and domain - * object are returned locked. - * - * Returns the domain object if it's found and the driver. Both are locked. - * In case of failure NULL is returned and the driver isn't locked. - */ -static virDomainObjPtr -qemuDomObjFromDomainDriver(virDomainPtr domain, virQEMUDriverPtr *drv) -{ - virQEMUDriverPtr driver = domain->conn->privateData; - virDomainObjPtr vm; - char uuidstr[VIR_UUID_STRING_BUFLEN]; - - qemuDriverLock(driver); - vm = virDomainFindByUUID(&driver->domains, domain->uuid); - if (!vm) { - virUUIDFormat(domain->uuid, uuidstr); - virReportError(VIR_ERR_NO_DOMAIN, - _("no domain with matching uuid '%s'"), uuidstr); - qemuDriverUnlock(driver); - *drv = NULL; - return NULL; - } - - *drv = driver; - return vm; -} - -/** - * qemuDomObjFromDomain: - * @domain: Domain pointer that has to be looked up - * - * This function looks up @domain and returns the appropriate - * virDomainObjPtr. The driver is unlocked after the call. - * - * Returns the domain object which is locked on success, NULL - * otherwise. The driver remains unlocked after the call. - */ -static virDomainObjPtr -qemuDomObjFromDomain(virDomainPtr domain) -{ - virDomainObjPtr vm; - virQEMUDriverPtr driver; - - if (!(vm = qemuDomObjFromDomainDriver(domain, &driver))) - return NULL; - - qemuDriverUnlock(driver); - - return vm; -} - -/* Looks up the domain object from snapshot and unlocks the driver. The - * returned domain object is locked and the caller is responsible for - * unlocking it */ -static virDomainObjPtr -qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot) -{ - return qemuDomObjFromDomain(snapshot->domain); -} - - -/* Looks up snapshot object from VM and name */ -static virDomainSnapshotObjPtr -qemuSnapObjFromName(virDomainObjPtr vm, - const char *name) -{ - virDomainSnapshotObjPtr snap = NULL; - snap = virDomainSnapshotFindByName(vm->snapshots, name); - if (!snap) - virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, - _("no domain snapshot with matching name '%s'"), - name); - - return snap; -} - - -/* Looks up snapshot object from VM and snapshotPtr */ -static virDomainSnapshotObjPtr -qemuSnapObjFromSnapshot(virDomainObjPtr vm, - virDomainSnapshotPtr snapshot) -{ - return qemuSnapObjFromName(vm, snapshot->name); -} static void qemuAutostartDomain(void *payload, const void *name ATTRIBUTE_UNUSED, @@ -2583,336 +2493,8 @@ cleanup: } -/* 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 -verify(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") - -typedef struct _virQEMUSaveHeader virQEMUSaveHeader; -typedef virQEMUSaveHeader *virQEMUSaveHeaderPtr; -struct _virQEMUSaveHeader { - char magic[sizeof(QEMU_SAVE_MAGIC)-1]; - uint32_t version; - uint32_t xml_len; - uint32_t was_running; - uint32_t compressed; - uint32_t unused[15]; -}; - -static inline void -bswap_header(virQEMUSaveHeaderPtr hdr) { - hdr->version = bswap_32(hdr->version); - hdr->xml_len = bswap_32(hdr->xml_len); - hdr->was_running = bswap_32(hdr->was_running); - hdr->compressed = bswap_32(hdr->compressed); -} - - -/* return -errno on failure, or 0 on success */ -static int -qemuDomainSaveHeader(int fd, const char *path, const char *xml, - virQEMUSaveHeaderPtr header) -{ - int ret = 0; - - if (safewrite(fd, header, sizeof(*header)) != sizeof(*header)) { - ret = -errno; - virReportError(VIR_ERR_OPERATION_FAILED, - _("failed to write header to domain save file '%s'"), - path); - goto endjob; - } - - if (safewrite(fd, xml, header->xml_len) != header->xml_len) { - ret = -errno; - virReportError(VIR_ERR_OPERATION_FAILED, - _("failed to write xml to '%s'"), path); - goto endjob; - } -endjob: - return ret; -} - -/* Given a virQEMUSaveFormat compression level, return the name - * of the program to run, or NULL if no program is needed. */ -static const char * -qemuCompressProgramName(int compress) -{ - return (compress == QEMU_SAVE_FORMAT_RAW ? NULL : - qemuSaveCompressionTypeToString(compress)); -} - -/* Internal function to properly create or open existing files, with - * ownership affected by qemu driver setup. */ -static int -qemuOpenFile(virQEMUDriverPtr driver, const char *path, int oflags, - bool *needUnlink, bool *bypassSecurityDriver) -{ - struct stat sb; - bool is_reg = true; - bool need_unlink = false; - bool bypass_security = false; - unsigned int vfoflags = 0; - int fd = -1; - int path_shared = virStorageFileIsSharedFS(path); - uid_t uid = getuid(); - gid_t gid = getgid(); - - /* path might be a pre-existing block dev, in which case - * we need to skip the create step, and also avoid unlink - * in the failure case */ - if (oflags & O_CREAT) { - need_unlink = true; - - /* Don't force chown on network-shared FS - * as it is likely to fail. */ - if (path_shared <= 0 || driver->dynamicOwnership) - vfoflags |= VIR_FILE_OPEN_FORCE_OWNER; - - if (stat(path, &sb) == 0) { - is_reg = !!S_ISREG(sb.st_mode); - /* If the path is regular file which exists - * already and dynamic_ownership is off, we don't - * want to change it's ownership, just open it as-is */ - if (is_reg && !driver->dynamicOwnership) { - uid = sb.st_uid; - gid = sb.st_gid; - } - } - } - - /* First try creating the file as root */ - if (!is_reg) { - fd = open(path, oflags & ~O_CREAT); - if (fd < 0) { - virReportSystemError(errno, _("unable to open %s"), path); - goto cleanup; - } - } else { - if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, uid, gid, - vfoflags | VIR_FILE_OPEN_NOFORK)) < 0) { - /* If we failed as root, and the error was permission-denied - (EACCES or EPERM), assume it's on a network-connected share - where root access is restricted (eg, root-squashed NFS). If the - qemu user (driver->user) is non-root, just set a flag to - bypass security driver shenanigans, and retry the operation - after doing setuid to qemu user */ - if ((fd != -EACCES && fd != -EPERM) || - driver->user == getuid()) { - virReportSystemError(-fd, - _("Failed to create file '%s'"), - path); - goto cleanup; - } - - /* On Linux we can also verify the FS-type of the directory. */ - switch (path_shared) { - case 1: - /* it was on a network share, so we'll continue - * as outlined above - */ - break; - - case -1: - virReportSystemError(errno, - _("Failed to create file " - "'%s': couldn't determine fs type"), - path); - goto cleanup; - - case 0: - default: - /* local file - log the error returned by virFileOpenAs */ - virReportSystemError(-fd, - _("Failed to create file '%s'"), - path); - goto cleanup; - } - - /* Retry creating the file as driver->user */ - - if ((fd = virFileOpenAs(path, oflags, - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, - driver->user, driver->group, - vfoflags | VIR_FILE_OPEN_FORK)) < 0) { - virReportSystemError(-fd, - _("Error from child process creating '%s'"), - path); - goto cleanup; - } - - /* Since we had to setuid to create the file, and the fstype - is NFS, we assume it's a root-squashing NFS share, and that - the security driver stuff would have failed anyway */ - - bypass_security = true; - } - } -cleanup: - if (needUnlink) - *needUnlink = need_unlink; - if (bypassSecurityDriver) - *bypassSecurityDriver = bypass_security; - - return fd; -} - -/* 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, - const char *domXML, - int compressed, - bool was_running, - unsigned int flags, - enum qemuDomainAsyncJob asyncJob) -{ - virQEMUSaveHeader header; - bool bypassSecurityDriver = false; - bool needUnlink = false; - int ret = -1; - int fd = -1; - int directFlag = 0; - virFileWrapperFdPtr wrapperFd = NULL; - unsigned int wrapperFlags = VIR_FILE_WRAPPER_NON_BLOCKING; - unsigned long long pad; - unsigned long long offset; - size_t len; - char *xml = NULL; - - memset(&header, 0, sizeof(header)); - memcpy(header.magic, QEMU_SAVE_PARTIAL, sizeof(header.magic)); - header.version = QEMU_SAVE_VERSION; - header.was_running = was_running ? 1 : 0; - - header.compressed = compressed; - - len = strlen(domXML) + 1; - offset = sizeof(header) + len; - - /* Due to way we append QEMU state on our header with dd, - * we need to ensure there's a 512 byte boundary. Unfortunately - * we don't have an explicit offset in the header, so we fake - * it by padding the XML string with NUL bytes. Additionally, - * we want to ensure that virDomainSaveImageDefineXML can supply - * slightly larger XML, so we add a miminum padding prior to - * rounding out to page boundaries. - */ - pad = 1024; - pad += (QEMU_MONITOR_MIGRATE_TO_FILE_BS - - ((offset + pad) % QEMU_MONITOR_MIGRATE_TO_FILE_BS)); - if (VIR_ALLOC_N(xml, len + pad) < 0) { - virReportOOMError(); - goto cleanup; - } - strcpy(xml, domXML); - - offset += pad; - header.xml_len = len; - - /* 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 = qemuOpenFile(driver, path, O_WRONLY | O_TRUNC | O_CREAT | directFlag, - &needUnlink, &bypassSecurityDriver); - if (fd < 0) - goto cleanup; - - if (!(wrapperFd = virFileWrapperFdNew(&fd, path, wrapperFlags))) - goto cleanup; - - /* Write header to file, followed by XML */ - if (qemuDomainSaveHeader(fd, path, xml, &header) < 0) - goto cleanup; - - /* Perform the migration */ - if (qemuMigrationToFile(driver, vm, fd, offset, path, - qemuCompressProgramName(compressed), - bypassSecurityDriver, - 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 (virFileWrapperFdClose(wrapperFd) < 0) - goto cleanup; - - if ((fd = qemuOpenFile(driver, path, O_WRONLY, NULL, NULL)) < 0) - goto cleanup; - - memcpy(header.magic, QEMU_SAVE_MAGIC, sizeof(header.magic)); - - if (safewrite(fd, &header, sizeof(header)) != sizeof(header)) { - virReportSystemError(errno, _("unable to write %s"), path); - goto cleanup; - } - - if (VIR_CLOSE(fd) < 0) { - virReportSystemError(errno, _("unable to close %s"), path); - goto cleanup; - } - - ret = 0; - -cleanup: - VIR_FORCE_CLOSE(fd); - virFileWrapperFdCatchError(wrapperFd); - virFileWrapperFdFree(wrapperFd); - VIR_FREE(xml); - - if (ret != 0 && needUnlink) - unlink(path); - - return ret; -} /* This internal function expects the driver lock to already be held on * entry and the vm must be active + locked. Vm will be unlocked and @@ -4703,6 +4285,16 @@ cleanup: return ret; } + +static inline void +bswap_header(virQEMUSaveHeaderPtr hdr) { + hdr->version = bswap_32(hdr->version); + hdr->xml_len = bswap_32(hdr->xml_len); + hdr->was_running = bswap_32(hdr->was_running); + hdr->compressed = bswap_32(hdr->compressed); +} + + /* Return -1 on most failures after raising error, -2 if edit was specified * but xmlin and state (-1 for no change, 0 for paused, 1 for running) do * not represent any changes (no error raised), -3 if corrupt image was @@ -10329,66 +9921,6 @@ cleanup: return ret; } -typedef enum { - VIR_DISK_CHAIN_NO_ACCESS, - VIR_DISK_CHAIN_READ_ONLY, - VIR_DISK_CHAIN_READ_WRITE, -} qemuDomainDiskChainMode; - -/* Several operations end up adding or removing a single element of a - * disk backing file chain; this helper function ensures that the lock - * manager, cgroup device controller, and security manager labelling - * are all aware of each new file before it is added to a chain, and - * can revoke access to a file no longer needed in a chain. */ -static int -qemuDomainPrepareDiskChainElement(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virCgroupPtr cgroup, - virDomainDiskDefPtr disk, - const char *file, - qemuDomainDiskChainMode mode) -{ - /* The easiest way to label a single file with the same - * permissions it would have as if part of the disk chain is to - * temporarily modify the disk in place. */ - char *origsrc = disk->src; - int origformat = disk->format; - virStorageFileMetadataPtr origchain = disk->backingChain; - bool origreadonly = disk->readonly; - int ret = -1; - - disk->src = (char *) file; /* casting away const is safe here */ - disk->format = VIR_STORAGE_FILE_RAW; - disk->backingChain = NULL; - disk->readonly = mode == VIR_DISK_CHAIN_READ_ONLY; - - if (mode == VIR_DISK_CHAIN_NO_ACCESS) { - if (virSecurityManagerRestoreImageLabel(driver->securityManager, - vm->def, disk) < 0) - VIR_WARN("Unable to restore security label on %s", disk->src); - if (cgroup && qemuTeardownDiskCgroup(vm, cgroup, disk) < 0) - VIR_WARN("Failed to teardown cgroup for disk path %s", disk->src); - if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0) - VIR_WARN("Unable to release lock on %s", disk->src); - } else if (virDomainLockDiskAttach(driver->lockManager, driver->uri, - vm, disk) < 0 || - (cgroup && qemuSetupDiskCgroup(vm, cgroup, disk) < 0) || - virSecurityManagerSetImageLabel(driver->securityManager, - vm->def, disk) < 0) { - goto cleanup; - } - - ret = 0; - -cleanup: - disk->src = origsrc; - disk->format = origformat; - disk->backingChain = origchain; - disk->readonly = origreadonly; - return ret; -} - - /* this function expects the driver lock to be held by the caller */ static int diff --git a/src/qemu/qemu_util.c b/src/qemu/qemu_util.c new file mode 100644 index 0000000..bc958fc --- /dev/null +++ b/src/qemu/qemu_util.c @@ -0,0 +1,486 @@ +/* + * qemu_util.c: Various util functions for the QEMU driver + * + * Copyright (C) 2013 Red Hat, Inc. + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <byteswap.h> +#include <fcntl.h> + +#include "qemu_util.h" +#include "qemu_cgroup.h" +#include "qemu_migration.h" + +#include "internal.h" + +#include "virerror.h" +#include "domain_conf.h" +#include "locking/domain_lock.h" +#include "datatypes.h" +#include "virlog.h" +#include "viralloc.h" +#include "virfile.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_ENUM_IMPL(qemuSaveCompression, QEMU_SAVE_FORMAT_LAST, + "raw", + "gzip", + "bzip2", + "xz", + "lzop") + +/** + * qemuDomObjFromDomainDriver: + * @domain: Domain pointer that has to be looked up + * @drv: Pointer to virQEMUDriverPtr to return the driver object + * + * This function looks up @domain in the domain list and returns the + * appropriate virDomainObjPtr. On successful lookup, both driver and domain + * object are returned locked. + * + * Returns the domain object if it's found and the driver. Both are locked. + * In case of failure NULL is returned and the driver isn't locked. + */ +virDomainObjPtr +qemuDomObjFromDomainDriver(virDomainPtr domain, + virQEMUDriverPtr *drv) +{ + virQEMUDriverPtr driver = domain->conn->privateData; + virDomainObjPtr vm; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + virUUIDFormat(domain->uuid, uuidstr); + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + qemuDriverUnlock(driver); + *drv = NULL; + return NULL; + } + + *drv = driver; + return vm; +} + + +/** + * qemuDomObjFromDomain: + * @domain: Domain pointer that has to be looked up + * + * This function looks up @domain and returns the appropriate + * virDomainObjPtr. The driver is unlocked after the call. + * + * Returns the domain object which is locked on success, NULL + * otherwise. The driver remains unlocked after the call. + */ +virDomainObjPtr +qemuDomObjFromDomain(virDomainPtr domain) +{ + virDomainObjPtr vm; + virQEMUDriverPtr driver; + + if (!(vm = qemuDomObjFromDomainDriver(domain, &driver))) + return NULL; + + qemuDriverUnlock(driver); + + return vm; +} + + +/* Looks up the domain object from snapshot and unlocks the driver. The + * returned domain object is locked and the caller is responsible for + * unlocking it */ +virDomainObjPtr +qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot) +{ + return qemuDomObjFromDomain(snapshot->domain); +} + + +/* Looks up snapshot object from VM and name */ +virDomainSnapshotObjPtr +qemuSnapObjFromName(virDomainObjPtr vm, + const char *name) +{ + virDomainSnapshotObjPtr snap = NULL; + snap = virDomainSnapshotFindByName(vm->snapshots, name); + if (!snap) + virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + name); + + return snap; +} + + +/* Looks up snapshot object from VM and snapshotPtr */ +virDomainSnapshotObjPtr +qemuSnapObjFromSnapshot(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot) +{ + return qemuSnapObjFromName(vm, snapshot->name); +} + + +/* Internal function to properly create or open existing files, with + * ownership affected by qemu driver setup. */ +int +qemuOpenFile(virQEMUDriverPtr driver, + const char *path, + int oflags, + bool *needUnlink, + bool *bypassSecurityDriver) +{ + struct stat sb; + bool is_reg = true; + bool need_unlink = false; + bool bypass_security = false; + unsigned int vfoflags = 0; + int fd = -1; + int path_shared = virStorageFileIsSharedFS(path); + uid_t uid = getuid(); + gid_t gid = getgid(); + + /* path might be a pre-existing block dev, in which case + * we need to skip the create step, and also avoid unlink + * in the failure case */ + if (oflags & O_CREAT) { + need_unlink = true; + + /* Don't force chown on network-shared FS + * as it is likely to fail. */ + if (path_shared <= 0 || driver->dynamicOwnership) + vfoflags |= VIR_FILE_OPEN_FORCE_OWNER; + + if (stat(path, &sb) == 0) { + is_reg = !!S_ISREG(sb.st_mode); + /* If the path is regular file which exists + * already and dynamic_ownership is off, we don't + * want to change it's ownership, just open it as-is */ + if (is_reg && !driver->dynamicOwnership) { + uid = sb.st_uid; + gid = sb.st_gid; + } + } + } + + /* First try creating the file as root */ + if (!is_reg) { + fd = open(path, oflags & ~O_CREAT); + if (fd < 0) { + virReportSystemError(errno, _("unable to open %s"), path); + goto cleanup; + } + } else { + if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, uid, gid, + vfoflags | VIR_FILE_OPEN_NOFORK)) < 0) { + /* If we failed as root, and the error was permission-denied + (EACCES or EPERM), assume it's on a network-connected share + where root access is restricted (eg, root-squashed NFS). If the + qemu user (driver->user) is non-root, just set a flag to + bypass security driver shenanigans, and retry the operation + after doing setuid to qemu user */ + if ((fd != -EACCES && fd != -EPERM) || + driver->user == getuid()) { + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* On Linux we can also verify the FS-type of the directory. */ + switch (path_shared) { + case 1: + /* it was on a network share, so we'll continue + * as outlined above + */ + break; + + case -1: + virReportSystemError(errno, + _("Failed to create file " + "'%s': couldn't determine fs type"), + path); + goto cleanup; + + case 0: + default: + /* local file - log the error returned by virFileOpenAs */ + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* Retry creating the file as driver->user */ + + if ((fd = virFileOpenAs(path, oflags, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, + driver->user, driver->group, + vfoflags | VIR_FILE_OPEN_FORK)) < 0) { + virReportSystemError(-fd, + _("Error from child process creating '%s'"), + path); + goto cleanup; + } + + /* Since we had to setuid to create the file, and the fstype + is NFS, we assume it's a root-squashing NFS share, and that + the security driver stuff would have failed anyway */ + + bypass_security = true; + } + } +cleanup: + if (needUnlink) + *needUnlink = need_unlink; + if (bypassSecurityDriver) + *bypassSecurityDriver = bypass_security; + + return fd; +} + + +/* Several operations end up adding or removing a single element of a + * disk backing file chain; this helper function ensures that the lock + * manager, cgroup device controller, and security manager labelling + * are all aware of each new file before it is added to a chain, and + * can revoke access to a file no longer needed in a chain. */ +int +qemuDomainPrepareDiskChainElement(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainDiskDefPtr disk, + const char *file, + qemuDomainDiskChainMode mode) +{ + /* The easiest way to label a single file with the same + * permissions it would have as if part of the disk chain is to + * temporarily modify the disk in place. */ + char *origsrc = disk->src; + int origformat = disk->format; + virStorageFileMetadataPtr origchain = disk->backingChain; + bool origreadonly = disk->readonly; + int ret = -1; + + disk->src = (char *) file; /* casting away const is safe here */ + disk->format = VIR_STORAGE_FILE_RAW; + disk->backingChain = NULL; + disk->readonly = mode == VIR_DISK_CHAIN_READ_ONLY; + + if (mode == VIR_DISK_CHAIN_NO_ACCESS) { + if (virSecurityManagerRestoreImageLabel(driver->securityManager, + vm->def, disk) < 0) + VIR_WARN("Unable to restore security label on %s", disk->src); + if (cgroup && qemuTeardownDiskCgroup(vm, cgroup, disk) < 0) + VIR_WARN("Failed to teardown cgroup for disk path %s", disk->src); + if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0) + VIR_WARN("Unable to release lock on %s", disk->src); + } else if (virDomainLockDiskAttach(driver->lockManager, driver->uri, + vm, disk) < 0 || + (cgroup && qemuSetupDiskCgroup(vm, cgroup, disk) < 0) || + virSecurityManagerSetImageLabel(driver->securityManager, + vm->def, disk) < 0) { + goto cleanup; + } + + ret = 0; + +cleanup: + disk->src = origsrc; + disk->format = origformat; + disk->backingChain = origchain; + disk->readonly = origreadonly; + return ret; +} + + +/* return -errno on failure, or 0 on success */ +static int +qemuDomainSaveHeader(int fd, + const char *path, + const char *xml, + virQEMUSaveHeaderPtr header) +{ + int ret = 0; + + if (safewrite(fd, header, sizeof(*header)) != sizeof(*header)) { + ret = -errno; + virReportError(VIR_ERR_OPERATION_FAILED, + _("failed to write header to domain save file '%s'"), + path); + goto endjob; + } + + if (safewrite(fd, xml, header->xml_len) != header->xml_len) { + ret = -errno; + virReportError(VIR_ERR_OPERATION_FAILED, + _("failed to write xml to '%s'"), path); + goto endjob; + } +endjob: + return ret; +} + + +/* Given a virQEMUSaveFormat compression level, return the name + * of the program to run, or NULL if no program is needed. */ +const char * +qemuCompressProgramName(int compress) +{ + return (compress == QEMU_SAVE_FORMAT_RAW ? NULL : + qemuSaveCompressionTypeToString(compress)); +} + + +/* 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 +qemuDomainSaveMemory(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + const char *domXML, + int compressed, + bool was_running, + unsigned int flags, + enum qemuDomainAsyncJob asyncJob) +{ + virQEMUSaveHeader header; + bool bypassSecurityDriver = false; + bool needUnlink = false; + int ret = -1; + int fd = -1; + int directFlag = 0; + virFileWrapperFdPtr wrapperFd = NULL; + unsigned int wrapperFlags = VIR_FILE_WRAPPER_NON_BLOCKING; + unsigned long long pad; + unsigned long long offset; + size_t len; + char *xml = NULL; + + memset(&header, 0, sizeof(header)); + memcpy(header.magic, QEMU_SAVE_PARTIAL, sizeof(header.magic)); + header.version = QEMU_SAVE_VERSION; + header.was_running = was_running ? 1 : 0; + + header.compressed = compressed; + + len = strlen(domXML) + 1; + offset = sizeof(header) + len; + + /* Due to way we append QEMU state on our header with dd, + * we need to ensure there's a 512 byte boundary. Unfortunately + * we don't have an explicit offset in the header, so we fake + * it by padding the XML string with NUL bytes. Additionally, + * we want to ensure that virDomainSaveImageDefineXML can supply + * slightly larger XML, so we add a miminum padding prior to + * rounding out to page boundaries. + */ + pad = 1024; + pad += (QEMU_MONITOR_MIGRATE_TO_FILE_BS - + ((offset + pad) % QEMU_MONITOR_MIGRATE_TO_FILE_BS)); + if (VIR_ALLOC_N(xml, len + pad) < 0) { + virReportOOMError(); + goto cleanup; + } + strcpy(xml, domXML); + + offset += pad; + header.xml_len = len; + + /* 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 = qemuOpenFile(driver, path, O_WRONLY | O_TRUNC | O_CREAT | directFlag, + &needUnlink, &bypassSecurityDriver); + if (fd < 0) + goto cleanup; + + if (!(wrapperFd = virFileWrapperFdNew(&fd, path, wrapperFlags))) + goto cleanup; + + /* Write header to file, followed by XML */ + if (qemuDomainSaveHeader(fd, path, xml, &header) < 0) + goto cleanup; + + /* Perform the migration */ + if (qemuMigrationToFile(driver, vm, fd, offset, path, + qemuCompressProgramName(compressed), + bypassSecurityDriver, + 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 (virFileWrapperFdClose(wrapperFd) < 0) + goto cleanup; + + if ((fd = qemuOpenFile(driver, path, O_WRONLY, NULL, NULL)) < 0) + goto cleanup; + + memcpy(header.magic, QEMU_SAVE_MAGIC, sizeof(header.magic)); + + if (safewrite(fd, &header, sizeof(header)) != sizeof(header)) { + virReportSystemError(errno, _("unable to write %s"), path); + goto cleanup; + } + + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), path); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FORCE_CLOSE(fd); + virFileWrapperFdCatchError(wrapperFd); + virFileWrapperFdFree(wrapperFd); + VIR_FREE(xml); + + if (ret != 0 && needUnlink) + unlink(path); + + return ret; +} diff --git a/src/qemu/qemu_util.h b/src/qemu/qemu_util.h new file mode 100644 index 0000000..458af38 --- /dev/null +++ b/src/qemu/qemu_util.h @@ -0,0 +1,111 @@ +/* + * qemu_util.h: Various util functions for the QEMU driver + * + * Copyright (C) 2013 Red Hat, Inc. + * + * 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/>. + * + */ + +#ifndef __QEMU_UTIL_H__ +# define __QEMU_UTIL_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 + +verify(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 xml_len; + uint32_t was_running; + uint32_t compressed; + uint32_t unused[15]; +}; + + +typedef enum { + VIR_DISK_CHAIN_NO_ACCESS, + VIR_DISK_CHAIN_READ_ONLY, + VIR_DISK_CHAIN_READ_WRITE, +} qemuDomainDiskChainMode; + +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) + + +virDomainObjPtr qemuDomObjFromDomainDriver(virDomainPtr domain, + virQEMUDriverPtr *drv); + +virDomainObjPtr qemuDomObjFromDomain(virDomainPtr domain); + +virDomainObjPtr qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot); + +virDomainSnapshotObjPtr qemuSnapObjFromSnapshot(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot); + +virDomainSnapshotObjPtr qemuSnapObjFromName(virDomainObjPtr vm, + const char *name); + +int qemuOpenFile(virQEMUDriverPtr driver, + const char *path, + int oflags, + bool *needUnlink, + bool *bypassSecurityDriver); + +int qemuDomainPrepareDiskChainElement(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainDiskDefPtr disk, + const char *file, + qemuDomainDiskChainMode mode); + +int qemuDomainSaveMemory(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + const char *domXML, + int compressed, + bool was_running, + unsigned int flags, + enum qemuDomainAsyncJob asyncJob); + +const char *qemuCompressProgramName(int compress); + +#endif /* __QEMU_UTIL_H__ */ -- 1.8.0.2

On 01/03/2013 09:45 AM, Peter Krempa wrote:
--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 492 ++----------------------------------------------- src/qemu/qemu_util.c | 486 ++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_util.h | 111 +++++++++++ 5 files changed, 611 insertions(+), 480 deletions(-) create mode 100644 src/qemu/qemu_util.c create mode 100644 src/qemu/qemu_util.h
+ +static inline void +bswap_header(virQEMUSaveHeaderPtr hdr) {
It looks weird that you moved this function, but only to later in the same file rather than into a new file, and also weird that this function doesn't follow naming conventions. I would leave it untouched in this patch, and then do a followup patch that inlines the body of this function into its lone caller (qemuDomainSaveImageOpen); which will get rid of the bad naming at the same time.
+++ b/src/qemu/qemu_util.c @@ -0,0 +1,486 @@ +/* + * qemu_util.c: Various util functions for the QEMU driver + * + * Copyright (C) 2013 Red Hat, Inc.
Although this file only exists in 2013, it copies from other files with earlier copyrights. Please preserve all of the copyrights from qemu_driver.c (or, for bonus points, take the time to audit which year each individual function was added and later modified while it lived within qemu_driver.c). Same to the qemu_util.h file.
+ +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <byteswap.h> +#include <fcntl.h> + +#include "qemu_util.h" +#include "qemu_cgroup.h" +#include "qemu_migration.h" + +#include "internal.h" + +#include "virerror.h" +#include "domain_conf.h" +#include "locking/domain_lock.h" +#include "datatypes.h" +#include "virlog.h" +#include "viralloc.h" +#include "virfile.h"
Do we need all of these, or can it be trimmed a bit? Should we sort things?
+++ b/src/qemu/qemu_util.h @@ -0,0 +1,111 @@
+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 + */
This comment is safe to drop now. We have version control for a reason. ACK with those points addressed. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Jan 03, 2013 at 05:45:49PM +0100, Peter Krempa wrote:
--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 492 ++----------------------------------------------- src/qemu/qemu_util.c | 486 ++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_util.h | 111 +++++++++++ 5 files changed, 611 insertions(+), 480 deletions(-) create mode 100644 src/qemu/qemu_util.c create mode 100644 src/qemu/qemu_util.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 + +verify(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 xml_len; + uint32_t was_running; + uint32_t compressed; + uint32_t unused[15]; +}; + + +typedef enum { + VIR_DISK_CHAIN_NO_ACCESS, + VIR_DISK_CHAIN_READ_ONLY, + VIR_DISK_CHAIN_READ_WRITE, +} qemuDomainDiskChainMode; + +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) + + +virDomainObjPtr qemuDomObjFromDomainDriver(virDomainPtr domain, + virQEMUDriverPtr *drv); + +virDomainObjPtr qemuDomObjFromDomain(virDomainPtr domain); + +virDomainObjPtr qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot); + +virDomainSnapshotObjPtr qemuSnapObjFromSnapshot(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot); + +virDomainSnapshotObjPtr qemuSnapObjFromName(virDomainObjPtr vm, + const char *name);
All these should go in qemu_domain.{c,h}
+int qemuOpenFile(virQEMUDriverPtr driver, + const char *path, + int oflags, + bool *needUnlink, + bool *bypassSecurityDriver);
qemu_conf.c
+int qemuDomainPrepareDiskChainElement(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainDiskDefPtr disk, + const char *file, + qemuDomainDiskChainMode mode);
qemu_domain.h
+int qemuDomainSaveMemory(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + const char *domXML, + int compressed, + bool was_running, + unsigned int flags, + enum qemuDomainAsyncJob asyncJob);
Not convinced this needs to move at all.
+const char *qemuCompressProgramName(int compress);
qemu_domain.h In summary, I don't think we should create a qemu_util.{c,h} file - any file named util just ends up as a garbage dumping ground for code that should be better placed elsewhere. See also util/util.h which should be split up. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 01/03/13 18:37, Daniel P. Berrange wrote:
On Thu, Jan 03, 2013 at 05:45:49PM +0100, Peter Krempa wrote:
--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 492 ++----------------------------------------------- src/qemu/qemu_util.c | 486 ++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_util.h | 111 +++++++++++ 5 files changed, 611 insertions(+), 480 deletions(-) create mode 100644 src/qemu/qemu_util.c create mode 100644 src/qemu/qemu_util.h
...
All these should go in qemu_domain.{c,h}
Fair enough.
+int qemuOpenFile(virQEMUDriverPtr driver, + const char *path, + int oflags, + bool *needUnlink, + bool *bypassSecurityDriver);
qemu_conf.c
Hm, qemu_conf.c, okay it's used to open files honoring the config ...
+int qemuDomainPrepareDiskChainElement(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainDiskDefPtr disk, + const char *file, + qemuDomainDiskChainMode mode);
qemu_domain.h
+int qemuDomainSaveMemory(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + const char *domXML, + int compressed, + bool was_running, + unsigned int flags, + enum qemuDomainAsyncJob asyncJob);
Not convinced this needs to move at all.
This one needs to be included into qemu_snapshot.c that is created in 2/2 of this series. All functions split out in this patch were chosen to allow that.
+const char *qemuCompressProgramName(int compress);
qemu_domain.h
In summary, I don't think we should create a qemu_util.{c,h} file - any file named util just ends up as a garbage dumping ground for code that should be better placed elsewhere. See also util/util.h which should be split up.
Well, as you can see it will ultimately end up somewhere. Unfortunately it this case everything tends to end up in qemu_driver.c.
Daniel
Peter

--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 1699 +------------------------------------------- src/qemu/qemu_snapshot.c | 1752 ++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_snapshot.h | 38 + 5 files changed, 1793 insertions(+), 1698 deletions(-) create mode 100644 src/qemu/qemu_snapshot.c create mode 100644 src/qemu/qemu_snapshot.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 4edacfa..bc80df8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -101,6 +101,7 @@ src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c src/qemu/qemu_process.c +src/qemu/qemu_snapshot.c src/qemu/qemu_util.c src/remote/remote_client_bodies.h src/remote/remote_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index f76a2ea..5414799 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -516,6 +516,7 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_process.c qemu/qemu_process.h \ qemu/qemu_migration.c qemu/qemu_migration.h \ qemu/qemu_util.c qemu/qemu_util.h \ + qemu/qemu_snapshot.c qemu/qemu_snapshot.h \ qemu/qemu_monitor.c qemu/qemu_monitor.h \ qemu/qemu_monitor_text.c \ qemu/qemu_monitor_text.h \ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a9c03b6..d64c545 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -58,6 +58,7 @@ #include "qemu_process.h" #include "qemu_migration.h" #include "qemu_util.h" +#include "qemu_snapshot.h" #include "virerror.h" #include "virlog.h" @@ -1987,19 +1988,6 @@ cleanup: } -/* Count how many snapshots in a set are external snapshots or checkpoints. */ -static void -qemuDomainSnapshotCountExternal(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) -{ - virDomainSnapshotObjPtr snap = payload; - int *count = data; - - if (virDomainSnapshotIsExternal(snap)) - (*count)++; -} - static int qemuDomainDestroyFlags(virDomainPtr dom, unsigned int flags) @@ -9922,1193 +9910,6 @@ cleanup: } -/* this function expects the driver lock to be held by the caller */ -static int -qemuDomainSnapshotFSFreeze(virQEMUDriverPtr driver, - virDomainObjPtr vm) { - qemuDomainObjPrivatePtr priv = vm->privateData; - int freezed; - - if (priv->agentError) { - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("QEMU guest agent is not " - "available due to an error")); - return -1; - } - if (!priv->agent) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", - _("QEMU guest agent is not configured")); - return -1; - } - - qemuDomainObjEnterAgentWithDriver(driver, vm); - freezed = qemuAgentFSFreeze(priv->agent); - qemuDomainObjExitAgentWithDriver(driver, vm); - - return freezed; -} - -static int -qemuDomainSnapshotFSThaw(virQEMUDriverPtr driver, - virDomainObjPtr vm, bool report) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - int thawed; - virErrorPtr err = NULL; - - if (priv->agentError) { - if (report) - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("QEMU guest agent is not " - "available due to an error")); - return -1; - } - if (!priv->agent) { - if (report) - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", - _("QEMU guest agent is not configured")); - return -1; - } - - qemuDomainObjEnterAgent(driver, vm); - if (!report) - err = virSaveLastError(); - thawed = qemuAgentFSThaw(priv->agent); - if (!report) - virSetError(err); - qemuDomainObjExitAgent(driver, vm); - - virFreeError(err); - return thawed; -} - -/* The domain is expected to be locked and inactive. */ -static int -qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap) -{ - return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false); -} - -/* The domain is expected to be locked and inactive. */ -static int -qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap, - bool reuse) -{ - int i; - virDomainSnapshotDiskDefPtr snapdisk; - virDomainDiskDefPtr defdisk; - virCommandPtr cmd = NULL; - const char *qemuImgPath; - virBitmapPtr created; - - int ret = -1; - - if (!(qemuImgPath = qemuFindQemuImgBinary(driver))) - return -1; - - if (!(created = virBitmapNew(snap->def->ndisks))) { - virReportOOMError(); - return -1; - } - - /* If reuse is true, then qemuDomainSnapshotPrepare already - * ensured that the new files exist, and it was up to the user to - * create them correctly. */ - for (i = 0; i < snap->def->ndisks && !reuse; i++) { - snapdisk = &(snap->def->disks[i]); - defdisk = snap->def->dom->disks[snapdisk->index]; - if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) - continue; - - if (!snapdisk->format) - snapdisk->format = VIR_STORAGE_FILE_QCOW2; - - /* creates cmd line args: qemu-img create -f qcow2 -o */ - if (!(cmd = virCommandNewArgList(qemuImgPath, - "create", - "-f", - virStorageFileFormatTypeToString(snapdisk->format), - "-o", - NULL))) - goto cleanup; - - if (defdisk->format > 0) { - /* adds cmd line arg: backing_file=/path/to/backing/file,backing_fmd=format */ - virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s", - defdisk->src, - virStorageFileFormatTypeToString(defdisk->format)); - } else { - if (!driver->allowDiskFormatProbing) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unknown image format of '%s' and " - "format probing is disabled"), - defdisk->src); - goto cleanup; - } - - /* adds cmd line arg: backing_file=/path/to/backing/file */ - virCommandAddArgFormat(cmd, "backing_file=%s", defdisk->src); - } - - /* adds cmd line args: /path/to/target/file */ - virCommandAddArg(cmd, snapdisk->file); - - /* If the target does not exist, we're going to create it possibly */ - if (!virFileExists(snapdisk->file)) - ignore_value(virBitmapSetBit(created, i)); - - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - - virCommandFree(cmd); - cmd = NULL; - } - - /* update disk definitions */ - for (i = 0; i < snap->def->ndisks; i++) { - snapdisk = &(snap->def->disks[i]); - defdisk = vm->def->disks[snapdisk->index]; - - if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { - VIR_FREE(defdisk->src); - if (!(defdisk->src = strdup(snapdisk->file))) { - /* we cannot rollback here in a sane way */ - virReportOOMError(); - goto cleanup; - } - defdisk->format = snapdisk->format; - } - } - - ret = 0; - -cleanup: - virCommandFree(cmd); - - /* unlink images if creation has failed */ - if (ret < 0) { - ssize_t bit = -1; - while ((bit = virBitmapNextSetBit(created, bit)) >= 0) { - snapdisk = &(snap->def->disks[bit]); - if (unlink(snapdisk->file) < 0) - VIR_WARN("Failed to remove snapshot image '%s'", - snapdisk->file); - } - } - virBitmapFree(created); - - return ret; -} - - -/* The domain is expected to be locked and active. */ -static int -qemuDomainSnapshotCreateActiveInternal(virConnectPtr conn, - virQEMUDriverPtr driver, - virDomainObjPtr *vmptr, - virDomainSnapshotObjPtr snap, - unsigned int flags) -{ - virDomainObjPtr vm = *vmptr; - qemuDomainObjPrivatePtr priv = vm->privateData; - virDomainEventPtr event = NULL; - bool resume = false; - int ret = -1; - - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) - return -1; - - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is not running")); - goto endjob; - } - - if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { - /* savevm monitor command pauses the domain emitting an event which - * confuses libvirt since it's not notified when qemu resumes the - * domain. Thus we stop and start CPUs ourselves. - */ - if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE, - QEMU_ASYNC_JOB_NONE) < 0) - goto cleanup; - - resume = true; - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto cleanup; - } - } - - qemuDomainObjEnterMonitorWithDriver(driver, vm); - ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name); - qemuDomainObjExitMonitorWithDriver(driver, vm); - if (ret < 0) - goto cleanup; - - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { - event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, - VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - /* We already filtered the _HALT flag for persistent domains - * only, so this end job never drops the last reference. */ - ignore_value(qemuDomainObjEndJob(driver, vm)); - resume = false; - vm = NULL; - } - -cleanup: - if (resume && virDomainObjIsActive(vm) && - qemuProcessStartCPUs(driver, vm, conn, - VIR_DOMAIN_RUNNING_UNPAUSED, - QEMU_ASYNC_JOB_NONE) < 0) { - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR); - if (virGetLastError() == NULL) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("resuming after snapshot failed")); - } - } - -endjob: - if (vm && qemuDomainObjEndJob(driver, vm) == 0) { - /* Only possible if a transient vm quit while our locks were down, - * in which case we don't want to save snapshot metadata. */ - *vmptr = NULL; - ret = -1; - } - - if (event) - qemuDomainEventQueue(driver, event); - - return ret; -} - -static int -qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, - unsigned int *flags) -{ - int ret = -1; - int i; - bool active = virDomainObjIsActive(vm); - struct stat st; - bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0; - bool atomic = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC) != 0; - bool found_internal = false; - int external = 0; - qemuDomainObjPrivatePtr priv = vm->privateData; - - if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && - reuse && !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("reuse is not supported with this QEMU binary")); - goto cleanup; - } - - for (i = 0; i < def->ndisks; i++) { - virDomainSnapshotDiskDefPtr disk = &def->disks[i]; - virDomainDiskDefPtr dom_disk = vm->def->disks[i]; - - switch (disk->snapshot) { - case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: - if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && - dom_disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK && - (dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG || - dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD)) { - break; - } - if (vm->def->disks[i]->format > 0 && - vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("internal snapshot for disk %s unsupported " - "for storage type %s"), - disk->name, - virStorageFileFormatTypeToString( - vm->def->disks[i]->format)); - goto cleanup; - } - if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && active) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("active qemu domains require external disk " - "snapshots; disk %s requested internal"), - disk->name); - goto cleanup; - } - found_internal = true; - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: - if (!disk->format) { - disk->format = VIR_STORAGE_FILE_QCOW2; - } else if (disk->format != VIR_STORAGE_FILE_QCOW2 && - disk->format != VIR_STORAGE_FILE_QED) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("external snapshot format for disk %s " - "is unsupported: %s"), - disk->name, - virStorageFileFormatTypeToString(disk->format)); - goto cleanup; - } - if (stat(disk->file, &st) < 0) { - if (errno != ENOENT) { - virReportSystemError(errno, - _("unable to stat for disk %s: %s"), - disk->name, disk->file); - goto cleanup; - } else if (reuse) { - virReportSystemError(errno, - _("missing existing file for disk %s: %s"), - disk->name, disk->file); - goto cleanup; - } - } else if (!S_ISBLK(st.st_mode) && st.st_size && !reuse) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("external snapshot file for disk %s already " - "exists and is not a block device: %s"), - disk->name, disk->file); - goto cleanup; - } - external++; - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE: - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: - default: - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unexpected code path")); - goto cleanup; - } - } - - /* internal snapshot requires a disk image to store the memory image to */ - if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && - !found_internal) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("internal checkpoints require at least " - "one disk to be selected for snapshot")); - goto cleanup; - } - - /* disk snapshot requires at least one disk */ - if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && !external) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("disk-only snapshots require at least " - "one disk to be selected for snapshot")); - goto cleanup; - } - - /* For now, we don't allow mixing internal and external disks. - * XXX technically, we could mix internal and external disks for - * offline snapshots */ - if (found_internal && external) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("mixing internal and external snapshots is not " - "supported yet")); - goto cleanup; - } - - /* Alter flags to let later users know what we learned. */ - if (external && !active) - *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; - - if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && active) { - if (external == 1 || - qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { - *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC; - } else if (atomic && external > 1) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("atomic live snapshot of multiple disks " - "is unsupported")); - goto cleanup; - } - } - - ret = 0; - -cleanup: - return ret; -} - -/* The domain is expected to hold monitor lock. */ -static int -qemuDomainSnapshotCreateSingleDiskActive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virCgroupPtr cgroup, - virDomainSnapshotDiskDefPtr snap, - virDomainDiskDefPtr disk, - virDomainDiskDefPtr persistDisk, - virJSONValuePtr actions, - bool reuse) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - char *device = NULL; - char *source = NULL; - int format = snap->format; - const char *formatStr = NULL; - char *persistSource = NULL; - int ret = -1; - int fd = -1; - bool need_unlink = false; - - if (snap->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unexpected code path")); - return -1; - } - - if (virAsprintf(&device, "drive-%s", disk->info.alias) < 0 || - !(source = strdup(snap->file)) || - (persistDisk && - !(persistSource = strdup(source)))) { - virReportOOMError(); - goto cleanup; - } - - /* create the stub file and set selinux labels; manipulate disk in - * place, in a way that can be reverted on failure. */ - if (!reuse) { - fd = qemuOpenFile(driver, source, O_WRONLY | O_TRUNC | O_CREAT, - &need_unlink, NULL); - if (fd < 0) - goto cleanup; - VIR_FORCE_CLOSE(fd); - } - - /* XXX Here, we know we are about to alter disk->backingChain if - * successful, so we nuke the existing chain so that future - * commands will recompute it. Better would be storing the chain - * ourselves rather than reprobing, but this requires modifying - * domain_conf and our XML to fully track the chain across - * libvirtd restarts. */ - virStorageFileFreeMetadata(disk->backingChain); - disk->backingChain = NULL; - - if (qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source, - VIR_DISK_CHAIN_READ_WRITE) < 0) { - qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source, - VIR_DISK_CHAIN_NO_ACCESS); - goto cleanup; - } - - /* create the actual snapshot */ - if (snap->format) - formatStr = virStorageFileFormatTypeToString(snap->format); - ret = qemuMonitorDiskSnapshot(priv->mon, actions, device, source, - formatStr, reuse); - virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0); - if (ret < 0) - goto cleanup; - - /* Update vm in place to match changes. */ - need_unlink = false; - VIR_FREE(disk->src); - disk->src = source; - source = NULL; - disk->format = format; - if (persistDisk) { - VIR_FREE(persistDisk->src); - persistDisk->src = persistSource; - persistSource = NULL; - persistDisk->format = format; - } - -cleanup: - if (need_unlink && unlink(source)) - VIR_WARN("unable to unlink just-created %s", source); - VIR_FREE(device); - VIR_FREE(source); - VIR_FREE(persistSource); - return ret; -} - -/* The domain is expected to hold monitor lock. This is the - * counterpart to qemuDomainSnapshotCreateSingleDiskActive, called - * only on a failed transaction. */ -static void -qemuDomainSnapshotUndoSingleDiskActive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virCgroupPtr cgroup, - virDomainDiskDefPtr origdisk, - virDomainDiskDefPtr disk, - virDomainDiskDefPtr persistDisk, - bool need_unlink) -{ - char *source = NULL; - char *persistSource = NULL; - struct stat st; - - if (!(source = strdup(origdisk->src)) || - (persistDisk && - !(persistSource = strdup(source)))) { - virReportOOMError(); - goto cleanup; - } - - qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, origdisk->src, - VIR_DISK_CHAIN_NO_ACCESS); - if (need_unlink && stat(disk->src, &st) == 0 && - S_ISREG(st.st_mode) && unlink(disk->src) < 0) - VIR_WARN("Unable to remove just-created %s", disk->src); - - /* Update vm in place to match changes. */ - VIR_FREE(disk->src); - disk->src = source; - source = NULL; - disk->format = origdisk->format; - if (persistDisk) { - VIR_FREE(persistDisk->src); - persistDisk->src = persistSource; - persistSource = NULL; - persistDisk->format = origdisk->format; - } - -cleanup: - VIR_FREE(source); - VIR_FREE(persistSource); -} - -/* The domain is expected to be locked and active. */ -static int -qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap, - unsigned int flags, - enum qemuDomainAsyncJob asyncJob) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - virJSONValuePtr actions = NULL; - int ret = -1; - int i; - bool persist = false; - bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0; - virCgroupPtr cgroup = NULL; - - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is not running")); - goto cleanup; - } - - if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES) && - virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unable to find cgroup for %s"), - vm->def->name); - goto cleanup; - } - /* 'cgroup' is still NULL if cgroups are disabled. */ - - if (qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { - if (!(actions = virJSONValueNewArray())) { - virReportOOMError(); - goto cleanup; - } - } else if (!qemuCapsGet(priv->caps, QEMU_CAPS_DISK_SNAPSHOT)) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("live disk snapshot not supported with this " - "QEMU binary")); - goto cleanup; - } - - /* No way to roll back if first disk succeeds but later disks - * fail, unless we have transaction support. - * Based on earlier qemuDomainSnapshotPrepare, all - * disks in this list are now either SNAPSHOT_NO, or - * SNAPSHOT_EXTERNAL with a valid file name and qcow2 format. */ - if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) - goto cleanup; - - for (i = 0; i < snap->def->ndisks; i++) { - virDomainDiskDefPtr persistDisk = NULL; - - if (snap->def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) - continue; - if (vm->newDef) { - int indx = virDomainDiskIndexByName(vm->newDef, - vm->def->disks[i]->dst, - false); - if (indx >= 0) { - persistDisk = vm->newDef->disks[indx]; - persist = true; - } - } - - ret = qemuDomainSnapshotCreateSingleDiskActive(driver, vm, cgroup, - &snap->def->disks[i], - vm->def->disks[i], - persistDisk, actions, - reuse); - if (ret < 0) - break; - } - if (actions) { - if (ret == 0) - ret = qemuMonitorTransaction(priv->mon, actions); - virJSONValueFree(actions); - if (ret < 0) { - /* Transaction failed; undo the changes to vm. */ - bool need_unlink = !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT); - while (--i >= 0) { - virDomainDiskDefPtr persistDisk = NULL; - - if (snap->def->disks[i].snapshot == - VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) - continue; - if (vm->newDef) { - int indx = virDomainDiskIndexByName(vm->newDef, - vm->def->disks[i]->dst, - false); - if (indx >= 0) - persistDisk = vm->newDef->disks[indx]; - } - - qemuDomainSnapshotUndoSingleDiskActive(driver, vm, cgroup, - snap->def->dom->disks[i], - vm->def->disks[i], - persistDisk, - need_unlink); - } - } - } - qemuDomainObjExitMonitorWithDriver(driver, vm); - -cleanup: - virCgroupFree(&cgroup); - - if (ret == 0 || !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { - if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 || - (persist && virDomainSaveConfig(driver->configDir, vm->newDef) < 0)) - ret = -1; - } - - return ret; -} - - -static int -qemuDomainSnapshotCreateActiveExternal(virConnectPtr conn, - virQEMUDriverPtr driver, - virDomainObjPtr *vmptr, - virDomainSnapshotObjPtr snap, - unsigned int flags) -{ - bool resume = false; - int ret = -1; - virDomainObjPtr vm = *vmptr; - qemuDomainObjPrivatePtr priv = vm->privateData; - char *xml = NULL; - bool memory = snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - bool memory_unlink = false; - bool atomic = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC); - bool transaction = qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION); - int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */ - - if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) - goto cleanup; - - /* If quiesce was requested, then issue a freeze command, and a - * counterpart thaw command, no matter what. The command will - * fail if the guest is paused or the guest agent is not - * running. */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) { - if (qemuDomainSnapshotFSFreeze(driver, vm) < 0) { - /* helper reported the error */ - thaw = -1; - goto endjob; - } else { - thaw = 1; - } - } - - /* we need to resume the guest only if it was previously running */ - if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { - resume = true; - - /* For external checkpoints (those with memory), the guest - * must pause (either by libvirt up front, or by qemu after - * _LIVE converges). For disk-only snapshots with multiple - * disks, libvirt must pause externally to get all snapshots - * to be at the same point in time, unless qemu supports - * transactions. For a single disk, snapshot is atomic - * without requiring a pause. Thanks to - * qemuDomainSnapshotPrepare, if we got to this point, the - * atomic flag now says whether we need to pause, and a - * capability bit says whether to use transaction. - */ - if ((memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) || - (!memory && atomic && !transaction)) { - if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) - goto endjob; - - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto endjob; - } - } - } - - /* do the memory snapshot if necessary */ - if (memory) { - /* check if migration is possible */ - if (!qemuMigrationIsAllowed(driver, vm, vm->def, false)) - goto endjob; - - /* allow the migration job to be cancelled or the domain to be paused */ - qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK | - JOB_MASK(QEMU_JOB_SUSPEND) | - JOB_MASK(QEMU_JOB_MIGRATION_OP)); - - if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, false))) - goto endjob; - - if ((ret = qemuDomainSaveMemory(driver, vm, snap->def->file, - xml, QEMU_SAVE_FORMAT_RAW, - resume, 0, - QEMU_ASYNC_JOB_SNAPSHOT)) < 0) - goto endjob; - - /* the memory image was created, remove it on errors */ - memory_unlink = true; - - /* forbid any further manipulation */ - qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK); - } - - /* now the domain is now paused if: - * - if a memory snapshot was requested - * - an atomic snapshot was requested AND - * qemu does not support transactions - * - * Next we snapshot the disks. - */ - if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap, flags, - QEMU_ASYNC_JOB_SNAPSHOT)) < 0) - goto endjob; - - /* the snapshot is complete now */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { - virDomainEventPtr event; - - event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, - VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - /* We already filtered the _HALT flag for persistent domains - * only, so this end job never drops the last reference. */ - ignore_value(qemuDomainObjEndAsyncJob(driver, vm)); - resume = false; - thaw = 0; - vm = NULL; - if (event) - qemuDomainEventQueue(driver, event); - } - - ret = 0; - -endjob: - if (resume && vm && virDomainObjIsActive(vm) && - qemuProcessStartCPUs(driver, vm, conn, - VIR_DOMAIN_RUNNING_UNPAUSED, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) { - virDomainEventPtr event = NULL; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR); - if (event) - qemuDomainEventQueue(driver, event); - if (virGetLastError() == NULL) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("resuming after snapshot failed")); - } - - ret = -1; - goto cleanup; - } - if (vm && thaw != 0 && - qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) { - /* helper reported the error, if it was needed */ - if (thaw > 0) - ret = -1; - } - if (vm && !qemuDomainObjEndAsyncJob(driver, vm)) { - /* Only possible if a transient vm quit while our locks were down, - * in which case we don't want to save snapshot metadata. - */ - *vmptr = NULL; - ret = -1; - } - -cleanup: - VIR_FREE(xml); - if (memory_unlink && ret < 0) - unlink(snap->def->file); - - return ret; -} - - -static virDomainSnapshotPtr -qemuDomainSnapshotCreateXML(virDomainPtr domain, - const char *xmlDesc, - unsigned int flags) -{ - virQEMUDriverPtr driver = domain->conn->privateData; - virDomainObjPtr vm = NULL; - char *xml = NULL; - virDomainSnapshotObjPtr snap = NULL; - virDomainSnapshotPtr snapshot = NULL; - char uuidstr[VIR_UUID_STRING_BUFLEN]; - virDomainSnapshotDefPtr def = NULL; - bool update_current = true; - unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS; - virDomainSnapshotObjPtr other = NULL; - int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; - int align_match = true; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE | - VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT | - VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA | - VIR_DOMAIN_SNAPSHOT_CREATE_HALT | - VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY | - VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT | - VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE | - VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC | - VIR_DOMAIN_SNAPSHOT_CREATE_LIVE, NULL); - - if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) && - !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)) { - virReportError(VIR_ERR_OPERATION_INVALID, "%s", - _("quiesce requires disk-only")); - return NULL; - } - - if (((flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) && - !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) || - (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) - update_current = false; - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) - parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE; - - qemuDriverLock(driver); - virUUIDFormat(domain->uuid, uuidstr); - vm = virDomainFindByUUID(&driver->domains, domain->uuid); - if (!vm) { - virReportError(VIR_ERR_NO_DOMAIN, - _("no domain with matching uuid '%s'"), uuidstr); - goto cleanup; - } - - if (qemuProcessAutoDestroyActive(driver, vm)) { - virReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is marked for auto destroy")); - goto cleanup; - } - if (virDomainHasDiskMirror(vm)) { - virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s", - _("domain has active block copy job")); - goto cleanup; - } - - if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) { - virReportError(VIR_ERR_OPERATION_INVALID, "%s", - _("cannot halt after transient domain snapshot")); - goto cleanup; - } - if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) || - !virDomainObjIsActive(vm)) - parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE; - - if (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->caps, - QEMU_EXPECTED_VIRT_TYPES, - parse_flags))) - goto cleanup; - - /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE && - (!virDomainObjIsActive(vm) || - def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL || - flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("live snapshot creation is supported only " - "with external checkpoints")); - goto cleanup; - } - if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL || - def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL) && - flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("disk-only snapshot creation is not compatible with " - "memory snapshot")); - goto cleanup; - } - - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) { - /* Prevent circular chains */ - if (def->parent) { - if (STREQ(def->name, def->parent)) { - virReportError(VIR_ERR_INVALID_ARG, - _("cannot set snapshot %s as its own parent"), - def->name); - goto cleanup; - } - other = virDomainSnapshotFindByName(vm->snapshots, def->parent); - if (!other) { - virReportError(VIR_ERR_INVALID_ARG, - _("parent %s for snapshot %s not found"), - def->parent, def->name); - goto cleanup; - } - while (other->def->parent) { - if (STREQ(other->def->parent, def->name)) { - virReportError(VIR_ERR_INVALID_ARG, - _("parent %s would create cycle to %s"), - other->def->name, def->name); - goto cleanup; - } - other = virDomainSnapshotFindByName(vm->snapshots, - other->def->parent); - if (!other) { - VIR_WARN("snapshots are inconsistent for %s", - vm->def->name); - break; - } - } - } - - /* Check that any replacement is compatible */ - if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) && - def->state != VIR_DOMAIN_DISK_SNAPSHOT) { - virReportError(VIR_ERR_INVALID_ARG, - _("disk-only flag for snapshot %s requires " - "disk-snapshot state"), - def->name); - goto cleanup; - - } - - if (def->dom && - memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) { - virReportError(VIR_ERR_INVALID_ARG, - _("definition for snapshot %s must use uuid %s"), - def->name, uuidstr); - goto cleanup; - } - - other = virDomainSnapshotFindByName(vm->snapshots, def->name); - if (other) { - if ((other->def->state == VIR_DOMAIN_RUNNING || - other->def->state == VIR_DOMAIN_PAUSED) != - (def->state == VIR_DOMAIN_RUNNING || - def->state == VIR_DOMAIN_PAUSED)) { - virReportError(VIR_ERR_INVALID_ARG, - _("cannot change between online and offline " - "snapshot state in snapshot %s"), - def->name); - goto cleanup; - } - - if ((other->def->state == VIR_DOMAIN_DISK_SNAPSHOT) != - (def->state == VIR_DOMAIN_DISK_SNAPSHOT)) { - virReportError(VIR_ERR_INVALID_ARG, - _("cannot change between disk snapshot and " - "system checkpoint in snapshot %s"), - def->name); - goto cleanup; - } - - if (other->def->dom) { - if (def->dom) { - if (!virDomainDefCheckABIStability(other->def->dom, - def->dom)) - goto cleanup; - } else { - /* Transfer the domain def */ - def->dom = other->def->dom; - other->def->dom = NULL; - } - } - - if (def->dom) { - if (def->state == VIR_DOMAIN_DISK_SNAPSHOT || - virDomainSnapshotDefIsExternal(def)) { - align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match = false; - } - - if (virDomainSnapshotAlignDisks(def, align_location, - align_match) < 0) { - /* revert stealing of the snapshot domain definition */ - if (def->dom && !other->def->dom) { - other->def->dom = def->dom; - def->dom = NULL; - } - goto cleanup; - } - } - - if (other == vm->current_snapshot) { - update_current = true; - vm->current_snapshot = NULL; - } - - /* Drop and rebuild the parent relationship, but keep all - * child relations by reusing snap. */ - virDomainSnapshotDropParent(other); - virDomainSnapshotDefFree(other->def); - other->def = def; - def = NULL; - snap = other; - } else { - if (def->dom) { - if (def->state == VIR_DOMAIN_DISK_SNAPSHOT || - def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { - align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match = false; - } - if (virDomainSnapshotAlignDisks(def, align_location, - align_match) < 0) - goto cleanup; - } - } - } else { - /* Easiest way to clone inactive portion of vm->def is via - * conversion in and back out of xml. */ - if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, true)) || - !(def->dom = virDomainDefParseString(driver->caps, xml, - QEMU_EXPECTED_VIRT_TYPES, - VIR_DOMAIN_XML_INACTIVE))) - goto cleanup; - - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { - align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match = false; - if (virDomainObjIsActive(vm)) - def->state = VIR_DOMAIN_DISK_SNAPSHOT; - else - def->state = VIR_DOMAIN_SHUTOFF; - def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; - } else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { - def->state = virDomainObjGetState(vm, NULL); - align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match = false; - } else { - def->state = virDomainObjGetState(vm, NULL); - def->memory = (def->state == VIR_DOMAIN_SHUTOFF ? - VIR_DOMAIN_SNAPSHOT_LOCATION_NONE : - VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL); - } - if (virDomainSnapshotAlignDisks(def, align_location, - align_match) < 0 || - qemuDomainSnapshotPrepare(vm, def, &flags) < 0) - goto cleanup; - } - - if (!snap) { - if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def))) - goto cleanup; - - def = NULL; - } - - if (update_current) - snap->def->current = true; - if (vm->current_snapshot) { - if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) { - snap->def->parent = strdup(vm->current_snapshot->def->name); - if (snap->def->parent == NULL) { - virReportOOMError(); - goto cleanup; - } - } - if (update_current) { - vm->current_snapshot->def->current = false; - if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, - driver->snapshotDir) < 0) - goto cleanup; - vm->current_snapshot = NULL; - } - } - - /* actually do the snapshot */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) { - /* XXX Should we validate that the redefined snapshot even - * makes sense, such as checking that qemu-img recognizes the - * snapshot name in at least one of the domain's disks? */ - } else if (virDomainObjIsActive(vm)) { - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY || - snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { - /* external checkpoint or disk snapshot */ - if (qemuDomainSnapshotCreateActiveExternal(domain->conn, driver, - &vm, snap, flags) < 0) - goto cleanup; - } else { - /* internal checkpoint */ - if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver, - &vm, snap, flags) < 0) - goto cleanup; - } - } else { - /* inactive; qemuDomainSnapshotPrepare guaranteed that we - * aren't mixing internal and external, and altered flags to - * contain DISK_ONLY if there is an external disk. */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { - bool reuse = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT); - - if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap, - reuse) < 0) - goto cleanup; - } else { - if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap) < 0) - goto cleanup; - } - } - - /* If we fail after this point, there's not a whole lot we can - * do; we've successfully taken the snapshot, and we are now running - * on it, so we have to go forward the best we can - */ - snapshot = virGetDomainSnapshot(domain, snap->def->name); - -cleanup: - if (vm) { - if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->snapshotDir) < 0) { - VIR_WARN("unable to save metadata for snapshot %s", - snap->def->name); - } else { - if (update_current) - vm->current_snapshot = snap; - other = virDomainSnapshotFindByName(vm->snapshots, - snap->def->parent); - snap->parent = other; - other->nchildren++; - snap->sibling = other->first_child; - other->first_child = snap; - } - } else if (snap) { - virDomainSnapshotObjListRemove(vm->snapshots, snap); - } - virDomainObjUnlock(vm); - } - virDomainSnapshotDefFree(def); - VIR_FREE(xml); - qemuDriverUnlock(driver); - return snapshot; -} - static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, int nameslen, unsigned int flags) @@ -11430,504 +10231,6 @@ cleanup: return ret; } -/* The domain is expected to be locked and inactive. */ -static int -qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap) -{ - /* Try all disks, but report failure if we skipped any. */ - int ret = qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", true); - return ret > 0 ? -1 : ret; -} - -static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, - unsigned int flags) -{ - virQEMUDriverPtr driver = snapshot->domain->conn->privateData; - virDomainObjPtr vm = NULL; - int ret = -1; - virDomainSnapshotObjPtr snap = NULL; - char uuidstr[VIR_UUID_STRING_BUFLEN]; - virDomainEventPtr event = NULL; - virDomainEventPtr event2 = NULL; - int detail; - qemuDomainObjPrivatePtr priv; - int rc; - virDomainDefPtr config = NULL; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED | - VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1); - - /* We have the following transitions, which create the following events: - * 1. inactive -> inactive: none - * 2. inactive -> running: EVENT_STARTED - * 3. inactive -> paused: EVENT_STARTED, EVENT_PAUSED - * 4. running -> inactive: EVENT_STOPPED - * 5. running -> running: none - * 6. running -> paused: EVENT_PAUSED - * 7. paused -> inactive: EVENT_STOPPED - * 8. paused -> running: EVENT_RESUMED - * 9. paused -> paused: none - * Also, several transitions occur even if we fail partway through, - * and use of FORCE can cause multiple transitions. - */ - - qemuDriverLock(driver); - virUUIDFormat(snapshot->domain->uuid, uuidstr); - vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); - if (!vm) { - virReportError(VIR_ERR_NO_DOMAIN, - _("no domain with matching uuid '%s'"), uuidstr); - goto cleanup; - } - if (virDomainHasDiskMirror(vm)) { - virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s", - _("domain has active block copy job")); - goto cleanup; - } - - if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot))) - goto cleanup; - - if (!vm->persistent && - snap->def->state != VIR_DOMAIN_RUNNING && - snap->def->state != VIR_DOMAIN_PAUSED && - (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) { - virReportError(VIR_ERR_OPERATION_INVALID, "%s", - _("transient domain needs to request run or pause " - "to revert to inactive snapshot")); - goto cleanup; - } - if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("revert to external disk snapshot not supported " - "yet")); - goto cleanup; - } - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { - if (!snap->def->dom) { - virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, - _("snapshot '%s' lacks domain '%s' rollback info"), - snap->def->name, vm->def->name); - goto cleanup; - } - if (virDomainObjIsActive(vm) && - !(snap->def->state == VIR_DOMAIN_RUNNING - || snap->def->state == VIR_DOMAIN_PAUSED) && - (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { - virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", - _("must respawn qemu to start inactive snapshot")); - goto cleanup; - } - } - - - if (vm->current_snapshot) { - vm->current_snapshot->def->current = false; - if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, - driver->snapshotDir) < 0) - goto cleanup; - vm->current_snapshot = NULL; - /* XXX Should we restore vm->current_snapshot after this point - * in the failure cases where we know there was no change? */ - } - - /* Prepare to copy the snapshot inactive xml as the config of this - * domain. - * - * XXX Should domain snapshots track live xml rather - * than inactive xml? */ - snap->def->current = true; - if (snap->def->dom) { - config = virDomainDefCopy(driver->caps, snap->def->dom, true); - if (!config) - goto cleanup; - } - - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) - goto cleanup; - - if (snap->def->state == VIR_DOMAIN_RUNNING - || snap->def->state == VIR_DOMAIN_PAUSED) { - /* Transitions 2, 3, 5, 6, 8, 9 */ - bool was_running = false; - bool was_stopped = false; - - /* When using the loadvm monitor command, qemu does not know - * whether to pause or run the reverted domain, and just stays - * in the same state as before the monitor command, whether - * that is paused or running. We always pause before loadvm, - * to have finer control. */ - if (virDomainObjIsActive(vm)) { - /* Transitions 5, 6, 8, 9 */ - /* Check for ABI compatibility. */ - if (config && !virDomainDefCheckABIStability(vm->def, config)) { - virErrorPtr err = virGetLastError(); - - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { - /* Re-spawn error using correct category. */ - if (err->code == VIR_ERR_CONFIG_UNSUPPORTED) - virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", - err->str2); - goto endjob; - } - virResetError(err); - qemuProcessStop(driver, vm, - VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STOPPED, - detail); - if (event) - qemuDomainEventQueue(driver, event); - goto load; - } - - priv = vm->privateData; - if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { - /* Transitions 5, 6 */ - was_running = true; - if (qemuProcessStopCPUs(driver, vm, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_NONE) < 0) - goto endjob; - /* Create an event now in case the restore fails, so - * that user will be alerted that they are now paused. - * If restore later succeeds, we might replace this. */ - detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - detail); - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto endjob; - } - } - qemuDomainObjEnterMonitorWithDriver(driver, vm); - rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name); - qemuDomainObjExitMonitorWithDriver(driver, vm); - if (rc < 0) { - /* XXX resume domain if it was running before the - * failed loadvm attempt? */ - goto endjob; - } - if (config) - virDomainObjAssignDef(vm, config, false); - } else { - /* Transitions 2, 3 */ - load: - was_stopped = true; - if (config) - virDomainObjAssignDef(vm, config, false); - - rc = qemuProcessStart(snapshot->domain->conn, - driver, vm, NULL, -1, NULL, snap, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - VIR_QEMU_PROCESS_START_PAUSED); - virDomainAuditStart(vm, "from-snapshot", rc >= 0); - detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - if (rc < 0) - goto endjob; - } - - /* Touch up domain state. */ - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && - (snap->def->state == VIR_DOMAIN_PAUSED || - (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { - /* Transitions 3, 6, 9 */ - virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); - if (was_stopped) { - /* Transition 3, use event as-is and add event2 */ - detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; - event2 = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - detail); - } /* else transition 6 and 9 use event as-is */ - } else { - /* Transitions 2, 5, 8 */ - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto endjob; - } - rc = qemuProcessStartCPUs(driver, vm, snapshot->domain->conn, - VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_NONE); - if (rc < 0) - goto endjob; - virDomainEventFree(event); - event = NULL; - if (was_stopped) { - /* Transition 2 */ - detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - } else if (was_running) { - /* Transition 8 */ - detail = VIR_DOMAIN_EVENT_RESUMED; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_RESUMED, - detail); - } - } - } else { - /* Transitions 1, 4, 7 */ - /* Newer qemu -loadvm refuses to revert to the state of a snapshot - * created by qemu-img snapshot -c. If the domain is running, we - * must take it offline; then do the revert using qemu-img. - */ - - if (virDomainObjIsActive(vm)) { - /* Transitions 4, 7 */ - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STOPPED, - detail); - } - - if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) { - if (!vm->persistent) { - if (qemuDomainObjEndJob(driver, vm) > 0) - qemuDomainRemoveInactive(driver, vm); - vm = NULL; - goto cleanup; - } - goto endjob; - } - if (config) - virDomainObjAssignDef(vm, config, false); - - if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { - /* Flush first event, now do transition 2 or 3 */ - bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0; - unsigned int start_flags = 0; - - start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0; - - if (event) - qemuDomainEventQueue(driver, event); - rc = qemuProcessStart(snapshot->domain->conn, - driver, vm, NULL, -1, NULL, NULL, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - start_flags); - virDomainAuditStart(vm, "from-snapshot", rc >= 0); - if (rc < 0) { - if (!vm->persistent) { - if (qemuDomainObjEndJob(driver, vm) > 0) - qemuDomainRemoveInactive(driver, vm); - vm = NULL; - goto cleanup; - } - goto endjob; - } - detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - if (paused) { - detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; - event2 = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - detail); - } - } - } - - ret = 0; - -endjob: - if (vm && qemuDomainObjEndJob(driver, vm) == 0) - vm = NULL; - -cleanup: - if (vm && ret == 0) { - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->snapshotDir) < 0) - ret = -1; - else - vm->current_snapshot = snap; - } else if (snap) { - snap->def->current = false; - } - if (event) { - qemuDomainEventQueue(driver, event); - if (event2) - qemuDomainEventQueue(driver, event2); - } - if (vm) - virDomainObjUnlock(vm); - qemuDriverUnlock(driver); - - return ret; -} - - -typedef struct _virQEMUSnapReparent virQEMUSnapReparent; -typedef virQEMUSnapReparent *virQEMUSnapReparentPtr; -struct _virQEMUSnapReparent { - virQEMUDriverPtr driver; - virDomainSnapshotObjPtr parent; - virDomainObjPtr vm; - int err; - virDomainSnapshotObjPtr last; -}; - -static void -qemuDomainSnapshotReparentChildren(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) -{ - virDomainSnapshotObjPtr snap = payload; - virQEMUSnapReparentPtr rep = data; - - if (rep->err < 0) { - return; - } - - VIR_FREE(snap->def->parent); - snap->parent = rep->parent; - - if (rep->parent->def) { - snap->def->parent = strdup(rep->parent->def->name); - - if (snap->def->parent == NULL) { - virReportOOMError(); - rep->err = -1; - return; - } - } - - if (!snap->sibling) - rep->last = snap; - - rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap, - rep->driver->snapshotDir); -} - -static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, - unsigned int flags) -{ - virQEMUDriverPtr driver = snapshot->domain->conn->privateData; - virDomainObjPtr vm = NULL; - int ret = -1; - virDomainSnapshotObjPtr snap = NULL; - char uuidstr[VIR_UUID_STRING_BUFLEN]; - virQEMUSnapRemove rem; - virQEMUSnapReparent rep; - bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); - int external = 0; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); - - qemuDriverLock(driver); - virUUIDFormat(snapshot->domain->uuid, uuidstr); - vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); - if (!vm) { - virReportError(VIR_ERR_NO_DOMAIN, - _("no domain with matching uuid '%s'"), uuidstr); - goto cleanup; - } - - if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot))) - goto cleanup; - - if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)) { - if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) && - virDomainSnapshotIsExternal(snap)) - external++; - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) - virDomainSnapshotForEachDescendant(snap, - qemuDomainSnapshotCountExternal, - &external); - if (external) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("deletion of %d external disk snapshots not " - "supported yet"), external); - goto cleanup; - } - } - - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) - goto cleanup; - - if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { - rem.driver = driver; - rem.vm = vm; - rem.metadata_only = metadata_only; - rem.err = 0; - rem.current = false; - virDomainSnapshotForEachDescendant(snap, - qemuDomainSnapshotDiscardAll, - &rem); - if (rem.err < 0) - goto endjob; - if (rem.current) { - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { - snap->def->current = true; - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->snapshotDir) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("failed to set snapshot '%s' as current"), - snap->def->name); - snap->def->current = false; - goto endjob; - } - } - vm->current_snapshot = snap; - } - } else if (snap->nchildren) { - rep.driver = driver; - rep.parent = snap->parent; - rep.vm = vm; - rep.err = 0; - rep.last = NULL; - virDomainSnapshotForEachChild(snap, - qemuDomainSnapshotReparentChildren, - &rep); - if (rep.err < 0) - goto endjob; - /* Can't modify siblings during ForEachChild, so do it now. */ - snap->parent->nchildren += snap->nchildren; - rep.last->sibling = snap->parent->first_child; - snap->parent->first_child = snap->first_child; - } - - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { - snap->nchildren = 0; - snap->first_child = NULL; - ret = 0; - } else { - virDomainSnapshotDropParent(snap); - ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only); - } - -endjob: - if (qemuDomainObjEndJob(driver, vm) == 0) - vm = NULL; - -cleanup: - if (vm) - virDomainObjUnlock(vm); - qemuDriverUnlock(driver); - return ret; -} static int qemuDomainMonitorCommand(virDomainPtr domain, const char *cmd, char **result, unsigned int flags) diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c new file mode 100644 index 0000000..ac5c481 --- /dev/null +++ b/src/qemu/qemu_snapshot.c @@ -0,0 +1,1752 @@ +/* + * qemu_snapshot.c: QEMU snapshot handling + * + * Copyright (C) 2013 Red Hat, Inc. + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "qemu_snapshot.h" +#include "qemu_monitor.h" +#include "qemu_domain.h" +#include "qemu_process.h" +#include "qemu_capabilities.h" +#include "qemu_cgroup.h" +#include "qemu_util.h" +#include "qemu_migration.h" + +#include "internal.h" + +#include "domain_audit.h" +#include "virerror.h" +#include "virlog.h" +#include "virutil.h" +#include "viralloc.h" +#include "virfile.h" +#include "datatypes.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +/* Count how many snapshots in a set are external snapshots or checkpoints. */ +static void +qemuDomainSnapshotCountExternal(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + int *count = data; + + if (virDomainSnapshotIsExternal(snap)) + (*count)++; +} + + +/* this function expects the driver lock to be held by the caller */ +static int +qemuDomainSnapshotFSFreeze(virQEMUDriverPtr driver, + virDomainObjPtr vm) { + qemuDomainObjPrivatePtr priv = vm->privateData; + int freezed; + + if (priv->agentError) { + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("QEMU guest agent is not " + "available due to an error")); + return -1; + } + if (!priv->agent) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + return -1; + } + + qemuDomainObjEnterAgentWithDriver(driver, vm); + freezed = qemuAgentFSFreeze(priv->agent); + qemuDomainObjExitAgentWithDriver(driver, vm); + + return freezed; +} + +static int +qemuDomainSnapshotFSThaw(virQEMUDriverPtr driver, + virDomainObjPtr vm, bool report) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + int thawed; + virErrorPtr err = NULL; + + if (priv->agentError) { + if (report) + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("QEMU guest agent is not " + "available due to an error")); + return -1; + } + if (!priv->agent) { + if (report) + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + return -1; + } + + qemuDomainObjEnterAgent(driver, vm); + if (!report) + err = virSaveLastError(); + thawed = qemuAgentFSThaw(priv->agent); + if (!report) + virSetError(err); + qemuDomainObjExitAgent(driver, vm); + + virFreeError(err); + return thawed; +} + +/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false); +} + +/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap, + bool reuse) +{ + int i; + virDomainSnapshotDiskDefPtr snapdisk; + virDomainDiskDefPtr defdisk; + virCommandPtr cmd = NULL; + const char *qemuImgPath; + virBitmapPtr created; + + int ret = -1; + + if (!(qemuImgPath = qemuFindQemuImgBinary(driver))) + return -1; + + if (!(created = virBitmapNew(snap->def->ndisks))) { + virReportOOMError(); + return -1; + } + + /* If reuse is true, then qemuDomainSnapshotPrepare already + * ensured that the new files exist, and it was up to the user to + * create them correctly. */ + for (i = 0; i < snap->def->ndisks && !reuse; i++) { + snapdisk = &(snap->def->disks[i]); + defdisk = snap->def->dom->disks[snapdisk->index]; + if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + + if (!snapdisk->format) + snapdisk->format = VIR_STORAGE_FILE_QCOW2; + + /* creates cmd line args: qemu-img create -f qcow2 -o */ + if (!(cmd = virCommandNewArgList(qemuImgPath, + "create", + "-f", + virStorageFileFormatTypeToString(snapdisk->format), + "-o", + NULL))) + goto cleanup; + + if (defdisk->format > 0) { + /* adds cmd line arg: backing_file=/path/to/backing/file,backing_fmd=format */ + virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s", + defdisk->src, + virStorageFileFormatTypeToString(defdisk->format)); + } else { + if (!driver->allowDiskFormatProbing) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown image format of '%s' and " + "format probing is disabled"), + defdisk->src); + goto cleanup; + } + + /* adds cmd line arg: backing_file=/path/to/backing/file */ + virCommandAddArgFormat(cmd, "backing_file=%s", defdisk->src); + } + + /* adds cmd line args: /path/to/target/file */ + virCommandAddArg(cmd, snapdisk->file); + + /* If the target does not exist, we're going to create it possibly */ + if (!virFileExists(snapdisk->file)) + ignore_value(virBitmapSetBit(created, i)); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + virCommandFree(cmd); + cmd = NULL; + } + + /* update disk definitions */ + for (i = 0; i < snap->def->ndisks; i++) { + snapdisk = &(snap->def->disks[i]); + defdisk = vm->def->disks[snapdisk->index]; + + if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + VIR_FREE(defdisk->src); + if (!(defdisk->src = strdup(snapdisk->file))) { + /* we cannot rollback here in a sane way */ + virReportOOMError(); + goto cleanup; + } + defdisk->format = snapdisk->format; + } + } + + ret = 0; + +cleanup: + virCommandFree(cmd); + + /* unlink images if creation has failed */ + if (ret < 0) { + ssize_t bit = -1; + while ((bit = virBitmapNextSetBit(created, bit)) >= 0) { + snapdisk = &(snap->def->disks[bit]); + if (unlink(snapdisk->file) < 0) + VIR_WARN("Failed to remove snapshot image '%s'", + snapdisk->file); + } + } + virBitmapFree(created); + + return ret; +} + + +/* The domain is expected to be locked and active. */ +static int +qemuDomainSnapshotCreateActiveInternal(virConnectPtr conn, + virQEMUDriverPtr driver, + virDomainObjPtr *vmptr, + virDomainSnapshotObjPtr snap, + unsigned int flags) +{ + virDomainObjPtr vm = *vmptr; + qemuDomainObjPrivatePtr priv = vm->privateData; + virDomainEventPtr event = NULL; + bool resume = false; + int ret = -1; + + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) + return -1; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + /* savevm monitor command pauses the domain emitting an event which + * confuses libvirt since it's not notified when qemu resumes the + * domain. Thus we stop and start CPUs ourselves. + */ + if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE, + QEMU_ASYNC_JOB_NONE) < 0) + goto cleanup; + + resume = true; + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto cleanup; + } + } + + qemuDomainObjEnterMonitorWithDriver(driver, vm); + ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (ret < 0) + goto cleanup; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + /* We already filtered the _HALT flag for persistent domains + * only, so this end job never drops the last reference. */ + ignore_value(qemuDomainObjEndJob(driver, vm)); + resume = false; + vm = NULL; + } + +cleanup: + if (resume && virDomainObjIsActive(vm) && + qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED, + QEMU_ASYNC_JOB_NONE) < 0) { + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR); + if (virGetLastError() == NULL) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("resuming after snapshot failed")); + } + } + +endjob: + if (vm && qemuDomainObjEndJob(driver, vm) == 0) { + /* Only possible if a transient vm quit while our locks were down, + * in which case we don't want to save snapshot metadata. */ + *vmptr = NULL; + ret = -1; + } + + if (event) + qemuDomainEventQueue(driver, event); + + return ret; +} + +static int +qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, + unsigned int *flags) +{ + int ret = -1; + int i; + bool active = virDomainObjIsActive(vm); + struct stat st; + bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0; + bool atomic = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC) != 0; + bool found_internal = false; + int external = 0; + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && + reuse && !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("reuse is not supported with this QEMU binary")); + goto cleanup; + } + + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + virDomainDiskDefPtr dom_disk = vm->def->disks[i]; + + switch (disk->snapshot) { + case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: + if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && + dom_disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK && + (dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG || + dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD)) { + break; + } + if (vm->def->disks[i]->format > 0 && + vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("internal snapshot for disk %s unsupported " + "for storage type %s"), + disk->name, + virStorageFileFormatTypeToString( + vm->def->disks[i]->format)); + goto cleanup; + } + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && active) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("active qemu domains require external disk " + "snapshots; disk %s requested internal"), + disk->name); + goto cleanup; + } + found_internal = true; + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: + if (!disk->format) { + disk->format = VIR_STORAGE_FILE_QCOW2; + } else if (disk->format != VIR_STORAGE_FILE_QCOW2 && + disk->format != VIR_STORAGE_FILE_QED) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot format for disk %s " + "is unsupported: %s"), + disk->name, + virStorageFileFormatTypeToString(disk->format)); + goto cleanup; + } + if (stat(disk->file, &st) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("unable to stat for disk %s: %s"), + disk->name, disk->file); + goto cleanup; + } else if (reuse) { + virReportSystemError(errno, + _("missing existing file for disk %s: %s"), + disk->name, disk->file); + goto cleanup; + } + } else if (!S_ISBLK(st.st_mode) && st.st_size && !reuse) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot file for disk %s already " + "exists and is not a block device: %s"), + disk->name, disk->file); + goto cleanup; + } + external++; + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE: + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: + default: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected code path")); + goto cleanup; + } + } + + /* internal snapshot requires a disk image to store the memory image to */ + if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && + !found_internal) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("internal checkpoints require at least " + "one disk to be selected for snapshot")); + goto cleanup; + } + + /* disk snapshot requires at least one disk */ + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && !external) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk-only snapshots require at least " + "one disk to be selected for snapshot")); + goto cleanup; + } + + /* For now, we don't allow mixing internal and external disks. + * XXX technically, we could mix internal and external disks for + * offline snapshots */ + if (found_internal && external) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("mixing internal and external snapshots is not " + "supported yet")); + goto cleanup; + } + + /* Alter flags to let later users know what we learned. */ + if (external && !active) + *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; + + if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && active) { + if (external == 1 || + qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { + *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC; + } else if (atomic && external > 1) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("atomic live snapshot of multiple disks " + "is unsupported")); + goto cleanup; + } + } + + ret = 0; + +cleanup: + return ret; +} + +/* The domain is expected to hold monitor lock. */ +static int +qemuDomainSnapshotCreateSingleDiskActive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainSnapshotDiskDefPtr snap, + virDomainDiskDefPtr disk, + virDomainDiskDefPtr persistDisk, + virJSONValuePtr actions, + bool reuse) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + char *device = NULL; + char *source = NULL; + int format = snap->format; + const char *formatStr = NULL; + char *persistSource = NULL; + int ret = -1; + int fd = -1; + bool need_unlink = false; + + if (snap->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected code path")); + return -1; + } + + if (virAsprintf(&device, "drive-%s", disk->info.alias) < 0 || + !(source = strdup(snap->file)) || + (persistDisk && + !(persistSource = strdup(source)))) { + virReportOOMError(); + goto cleanup; + } + + /* create the stub file and set selinux labels; manipulate disk in + * place, in a way that can be reverted on failure. */ + if (!reuse) { + fd = qemuOpenFile(driver, source, O_WRONLY | O_TRUNC | O_CREAT, + &need_unlink, NULL); + if (fd < 0) + goto cleanup; + VIR_FORCE_CLOSE(fd); + } + + /* XXX Here, we know we are about to alter disk->backingChain if + * successful, so we nuke the existing chain so that future + * commands will recompute it. Better would be storing the chain + * ourselves rather than reprobing, but this requires modifying + * domain_conf and our XML to fully track the chain across + * libvirtd restarts. */ + virStorageFileFreeMetadata(disk->backingChain); + disk->backingChain = NULL; + + if (qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source, + VIR_DISK_CHAIN_READ_WRITE) < 0) { + qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source, + VIR_DISK_CHAIN_NO_ACCESS); + goto cleanup; + } + + /* create the actual snapshot */ + if (snap->format) + formatStr = virStorageFileFormatTypeToString(snap->format); + ret = qemuMonitorDiskSnapshot(priv->mon, actions, device, source, + formatStr, reuse); + virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0); + if (ret < 0) + goto cleanup; + + /* Update vm in place to match changes. */ + need_unlink = false; + VIR_FREE(disk->src); + disk->src = source; + source = NULL; + disk->format = format; + if (persistDisk) { + VIR_FREE(persistDisk->src); + persistDisk->src = persistSource; + persistSource = NULL; + persistDisk->format = format; + } + +cleanup: + if (need_unlink && unlink(source)) + VIR_WARN("unable to unlink just-created %s", source); + VIR_FREE(device); + VIR_FREE(source); + VIR_FREE(persistSource); + return ret; +} + +/* The domain is expected to hold monitor lock. This is the + * counterpart to qemuDomainSnapshotCreateSingleDiskActive, called + * only on a failed transaction. */ +static void +qemuDomainSnapshotUndoSingleDiskActive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCgroupPtr cgroup, + virDomainDiskDefPtr origdisk, + virDomainDiskDefPtr disk, + virDomainDiskDefPtr persistDisk, + bool need_unlink) +{ + char *source = NULL; + char *persistSource = NULL; + struct stat st; + + if (!(source = strdup(origdisk->src)) || + (persistDisk && + !(persistSource = strdup(source)))) { + virReportOOMError(); + goto cleanup; + } + + qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, origdisk->src, + VIR_DISK_CHAIN_NO_ACCESS); + if (need_unlink && stat(disk->src, &st) == 0 && + S_ISREG(st.st_mode) && unlink(disk->src) < 0) + VIR_WARN("Unable to remove just-created %s", disk->src); + + /* Update vm in place to match changes. */ + VIR_FREE(disk->src); + disk->src = source; + source = NULL; + disk->format = origdisk->format; + if (persistDisk) { + VIR_FREE(persistDisk->src); + persistDisk->src = persistSource; + persistSource = NULL; + persistDisk->format = origdisk->format; + } + +cleanup: + VIR_FREE(source); + VIR_FREE(persistSource); +} + +/* The domain is expected to be locked and active. */ +static int +qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap, + unsigned int flags, + enum qemuDomainAsyncJob asyncJob) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + virJSONValuePtr actions = NULL; + int ret = -1; + int i; + bool persist = false; + bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0; + virCgroupPtr cgroup = NULL; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + + if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES) && + virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to find cgroup for %s"), + vm->def->name); + goto cleanup; + } + /* 'cgroup' is still NULL if cgroups are disabled. */ + + if (qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { + if (!(actions = virJSONValueNewArray())) { + virReportOOMError(); + goto cleanup; + } + } else if (!qemuCapsGet(priv->caps, QEMU_CAPS_DISK_SNAPSHOT)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("live disk snapshot not supported with this " + "QEMU binary")); + goto cleanup; + } + + /* No way to roll back if first disk succeeds but later disks + * fail, unless we have transaction support. + * Based on earlier qemuDomainSnapshotPrepare, all + * disks in this list are now either SNAPSHOT_NO, or + * SNAPSHOT_EXTERNAL with a valid file name and qcow2 format. */ + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) + goto cleanup; + + for (i = 0; i < snap->def->ndisks; i++) { + virDomainDiskDefPtr persistDisk = NULL; + + if (snap->def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) + continue; + if (vm->newDef) { + int indx = virDomainDiskIndexByName(vm->newDef, + vm->def->disks[i]->dst, + false); + if (indx >= 0) { + persistDisk = vm->newDef->disks[indx]; + persist = true; + } + } + + ret = qemuDomainSnapshotCreateSingleDiskActive(driver, vm, cgroup, + &snap->def->disks[i], + vm->def->disks[i], + persistDisk, actions, + reuse); + if (ret < 0) + break; + } + if (actions) { + if (ret == 0) + ret = qemuMonitorTransaction(priv->mon, actions); + virJSONValueFree(actions); + if (ret < 0) { + /* Transaction failed; undo the changes to vm. */ + bool need_unlink = !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT); + while (--i >= 0) { + virDomainDiskDefPtr persistDisk = NULL; + + if (snap->def->disks[i].snapshot == + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) + continue; + if (vm->newDef) { + int indx = virDomainDiskIndexByName(vm->newDef, + vm->def->disks[i]->dst, + false); + if (indx >= 0) + persistDisk = vm->newDef->disks[indx]; + } + + qemuDomainSnapshotUndoSingleDiskActive(driver, vm, cgroup, + snap->def->dom->disks[i], + vm->def->disks[i], + persistDisk, + need_unlink); + } + } + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + +cleanup: + virCgroupFree(&cgroup); + + if (ret == 0 || !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) { + if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 || + (persist && virDomainSaveConfig(driver->configDir, vm->newDef) < 0)) + ret = -1; + } + + return ret; +} + + +static int +qemuDomainSnapshotCreateActiveExternal(virConnectPtr conn, + virQEMUDriverPtr driver, + virDomainObjPtr *vmptr, + virDomainSnapshotObjPtr snap, + unsigned int flags) +{ + bool resume = false; + int ret = -1; + virDomainObjPtr vm = *vmptr; + qemuDomainObjPrivatePtr priv = vm->privateData; + char *xml = NULL; + bool memory = snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + bool memory_unlink = false; + bool atomic = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC); + bool transaction = qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION); + int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */ + + if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) + goto cleanup; + + /* If quiesce was requested, then issue a freeze command, and a + * counterpart thaw command, no matter what. The command will + * fail if the guest is paused or the guest agent is not + * running. */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) { + if (qemuDomainSnapshotFSFreeze(driver, vm) < 0) { + /* helper reported the error */ + thaw = -1; + goto endjob; + } else { + thaw = 1; + } + } + + /* we need to resume the guest only if it was previously running */ + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + resume = true; + + /* For external checkpoints (those with memory), the guest + * must pause (either by libvirt up front, or by qemu after + * _LIVE converges). For disk-only snapshots with multiple + * disks, libvirt must pause externally to get all snapshots + * to be at the same point in time, unless qemu supports + * transactions. For a single disk, snapshot is atomic + * without requiring a pause. Thanks to + * qemuDomainSnapshotPrepare, if we got to this point, the + * atomic flag now says whether we need to pause, and a + * capability bit says whether to use transaction. + */ + if ((memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) || + (!memory && atomic && !transaction)) { + if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) + goto endjob; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + } + } + + /* do the memory snapshot if necessary */ + if (memory) { + /* check if migration is possible */ + if (!qemuMigrationIsAllowed(driver, vm, vm->def, false)) + goto endjob; + + /* allow the migration job to be cancelled or the domain to be paused */ + qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK | + JOB_MASK(QEMU_JOB_SUSPEND) | + JOB_MASK(QEMU_JOB_MIGRATION_OP)); + + if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, false))) + goto endjob; + + if ((ret = qemuDomainSaveMemory(driver, vm, snap->def->file, + xml, QEMU_SAVE_FORMAT_RAW, + resume, 0, + QEMU_ASYNC_JOB_SNAPSHOT)) < 0) + goto endjob; + + /* the memory image was created, remove it on errors */ + memory_unlink = true; + + /* forbid any further manipulation */ + qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK); + } + + /* now the domain is now paused if: + * - if a memory snapshot was requested + * - an atomic snapshot was requested AND + * qemu does not support transactions + * + * Next we snapshot the disks. + */ + if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap, flags, + QEMU_ASYNC_JOB_SNAPSHOT)) < 0) + goto endjob; + + /* the snapshot is complete now */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + virDomainEventPtr event; + + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + /* We already filtered the _HALT flag for persistent domains + * only, so this end job never drops the last reference. */ + ignore_value(qemuDomainObjEndAsyncJob(driver, vm)); + resume = false; + thaw = 0; + vm = NULL; + if (event) + qemuDomainEventQueue(driver, event); + } + + ret = 0; + +endjob: + if (resume && vm && virDomainObjIsActive(vm) && + qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) { + virDomainEventPtr event = NULL; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR); + if (event) + qemuDomainEventQueue(driver, event); + if (virGetLastError() == NULL) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("resuming after snapshot failed")); + } + + ret = -1; + goto cleanup; + } + if (vm && thaw != 0 && + qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) { + /* helper reported the error, if it was needed */ + if (thaw > 0) + ret = -1; + } + if (vm && !qemuDomainObjEndAsyncJob(driver, vm)) { + /* Only possible if a transient vm quit while our locks were down, + * in which case we don't want to save snapshot metadata. + */ + *vmptr = NULL; + ret = -1; + } + +cleanup: + VIR_FREE(xml); + if (memory_unlink && ret < 0) + unlink(snap->def->file); + + return ret; +} + + +virDomainSnapshotPtr +qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virQEMUDriverPtr driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + char *xml = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainSnapshotDefPtr def = NULL; + bool update_current = true; + unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS; + virDomainSnapshotObjPtr other = NULL; + int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; + int align_match = true; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE | + VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT | + VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA | + VIR_DOMAIN_SNAPSHOT_CREATE_HALT | + VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY | + VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT | + VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE | + VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC | + VIR_DOMAIN_SNAPSHOT_CREATE_LIVE, NULL); + + if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) && + !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("quiesce requires disk-only")); + return NULL; + } + + if (((flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) && + !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) || + (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) + update_current = false; + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) + parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE; + + qemuDriverLock(driver); + virUUIDFormat(domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (qemuProcessAutoDestroyActive(driver, vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is marked for auto destroy")); + goto cleanup; + } + if (virDomainHasDiskMirror(vm)) { + virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s", + _("domain has active block copy job")); + goto cleanup; + } + + if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot halt after transient domain snapshot")); + goto cleanup; + } + if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) || + !virDomainObjIsActive(vm)) + parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE; + + if (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->caps, + QEMU_EXPECTED_VIRT_TYPES, + parse_flags))) + goto cleanup; + + /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE && + (!virDomainObjIsActive(vm) || + def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL || + flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("live snapshot creation is supported only " + "with external checkpoints")); + goto cleanup; + } + if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL || + def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL) && + flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("disk-only snapshot creation is not compatible with " + "memory snapshot")); + goto cleanup; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) { + /* Prevent circular chains */ + if (def->parent) { + if (STREQ(def->name, def->parent)) { + virReportError(VIR_ERR_INVALID_ARG, + _("cannot set snapshot %s as its own parent"), + def->name); + goto cleanup; + } + other = virDomainSnapshotFindByName(vm->snapshots, def->parent); + if (!other) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s for snapshot %s not found"), + def->parent, def->name); + goto cleanup; + } + while (other->def->parent) { + if (STREQ(other->def->parent, def->name)) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s would create cycle to %s"), + other->def->name, def->name); + goto cleanup; + } + other = virDomainSnapshotFindByName(vm->snapshots, + other->def->parent); + if (!other) { + VIR_WARN("snapshots are inconsistent for %s", + vm->def->name); + break; + } + } + } + + /* Check that any replacement is compatible */ + if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) && + def->state != VIR_DOMAIN_DISK_SNAPSHOT) { + virReportError(VIR_ERR_INVALID_ARG, + _("disk-only flag for snapshot %s requires " + "disk-snapshot state"), + def->name); + goto cleanup; + + } + + if (def->dom && + memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) { + virReportError(VIR_ERR_INVALID_ARG, + _("definition for snapshot %s must use uuid %s"), + def->name, uuidstr); + goto cleanup; + } + + other = virDomainSnapshotFindByName(vm->snapshots, def->name); + if (other) { + if ((other->def->state == VIR_DOMAIN_RUNNING || + other->def->state == VIR_DOMAIN_PAUSED) != + (def->state == VIR_DOMAIN_RUNNING || + def->state == VIR_DOMAIN_PAUSED)) { + virReportError(VIR_ERR_INVALID_ARG, + _("cannot change between online and offline " + "snapshot state in snapshot %s"), + def->name); + goto cleanup; + } + + if ((other->def->state == VIR_DOMAIN_DISK_SNAPSHOT) != + (def->state == VIR_DOMAIN_DISK_SNAPSHOT)) { + virReportError(VIR_ERR_INVALID_ARG, + _("cannot change between disk snapshot and " + "system checkpoint in snapshot %s"), + def->name); + goto cleanup; + } + + if (other->def->dom) { + if (def->dom) { + if (!virDomainDefCheckABIStability(other->def->dom, + def->dom)) + goto cleanup; + } else { + /* Transfer the domain def */ + def->dom = other->def->dom; + other->def->dom = NULL; + } + } + + if (def->dom) { + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT || + virDomainSnapshotDefIsExternal(def)) { + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + } + + if (virDomainSnapshotAlignDisks(def, align_location, + align_match) < 0) { + /* revert stealing of the snapshot domain definition */ + if (def->dom && !other->def->dom) { + other->def->dom = def->dom; + def->dom = NULL; + } + goto cleanup; + } + } + + if (other == vm->current_snapshot) { + update_current = true; + vm->current_snapshot = NULL; + } + + /* Drop and rebuild the parent relationship, but keep all + * child relations by reusing snap. */ + virDomainSnapshotDropParent(other); + virDomainSnapshotDefFree(other->def); + other->def = def; + def = NULL; + snap = other; + } else { + if (def->dom) { + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT || + def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + } + if (virDomainSnapshotAlignDisks(def, align_location, + align_match) < 0) + goto cleanup; + } + } + } else { + /* Easiest way to clone inactive portion of vm->def is via + * conversion in and back out of xml. */ + if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, true)) || + !(def->dom = virDomainDefParseString(driver->caps, xml, + QEMU_EXPECTED_VIRT_TYPES, + VIR_DOMAIN_XML_INACTIVE))) + goto cleanup; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + if (virDomainObjIsActive(vm)) + def->state = VIR_DOMAIN_DISK_SNAPSHOT; + else + def->state = VIR_DOMAIN_SHUTOFF; + def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; + } else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + def->state = virDomainObjGetState(vm, NULL); + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + } else { + def->state = virDomainObjGetState(vm, NULL); + def->memory = (def->state == VIR_DOMAIN_SHUTOFF ? + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE : + VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL); + } + if (virDomainSnapshotAlignDisks(def, align_location, + align_match) < 0 || + qemuDomainSnapshotPrepare(vm, def, &flags) < 0) + goto cleanup; + } + + if (!snap) { + if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def))) + goto cleanup; + + def = NULL; + } + + if (update_current) + snap->def->current = true; + if (vm->current_snapshot) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) { + snap->def->parent = strdup(vm->current_snapshot->def->name); + if (snap->def->parent == NULL) { + virReportOOMError(); + goto cleanup; + } + } + if (update_current) { + vm->current_snapshot->def->current = false; + if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, + driver->snapshotDir) < 0) + goto cleanup; + vm->current_snapshot = NULL; + } + } + + /* actually do the snapshot */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) { + /* XXX Should we validate that the redefined snapshot even + * makes sense, such as checking that qemu-img recognizes the + * snapshot name in at least one of the domain's disks? */ + } else if (virDomainObjIsActive(vm)) { + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY || + snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + /* external checkpoint or disk snapshot */ + if (qemuDomainSnapshotCreateActiveExternal(domain->conn, driver, + &vm, snap, flags) < 0) + goto cleanup; + } else { + /* internal checkpoint */ + if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver, + &vm, snap, flags) < 0) + goto cleanup; + } + } else { + /* inactive; qemuDomainSnapshotPrepare guaranteed that we + * aren't mixing internal and external, and altered flags to + * contain DISK_ONLY if there is an external disk. */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + bool reuse = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT); + + if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap, + reuse) < 0) + goto cleanup; + } else { + if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap) < 0) + goto cleanup; + } + } + + /* If we fail after this point, there's not a whole lot we can + * do; we've successfully taken the snapshot, and we are now running + * on it, so we have to go forward the best we can + */ + snapshot = virGetDomainSnapshot(domain, snap->def->name); + +cleanup: + if (vm) { + if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->snapshotDir) < 0) { + VIR_WARN("unable to save metadata for snapshot %s", + snap->def->name); + } else { + if (update_current) + vm->current_snapshot = snap; + other = virDomainSnapshotFindByName(vm->snapshots, + snap->def->parent); + snap->parent = other; + other->nchildren++; + snap->sibling = other->first_child; + other->first_child = snap; + } + } else if (snap) { + virDomainSnapshotObjListRemove(vm->snapshots, snap); + } + virDomainObjUnlock(vm); + } + virDomainSnapshotDefFree(def); + VIR_FREE(xml); + qemuDriverUnlock(driver); + return snapshot; +} + + +/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + /* Try all disks, but report failure if we skipped any. */ + int ret = qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", true); + return ret > 0 ? -1 : ret; +} + +int +qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virQEMUDriverPtr driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainEventPtr event = NULL; + virDomainEventPtr event2 = NULL; + int detail; + qemuDomainObjPrivatePtr priv; + int rc; + virDomainDefPtr config = NULL; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED | + VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1); + + /* We have the following transitions, which create the following events: + * 1. inactive -> inactive: none + * 2. inactive -> running: EVENT_STARTED + * 3. inactive -> paused: EVENT_STARTED, EVENT_PAUSED + * 4. running -> inactive: EVENT_STOPPED + * 5. running -> running: none + * 6. running -> paused: EVENT_PAUSED + * 7. paused -> inactive: EVENT_STOPPED + * 8. paused -> running: EVENT_RESUMED + * 9. paused -> paused: none + * Also, several transitions occur even if we fail partway through, + * and use of FORCE can cause multiple transitions. + */ + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + if (virDomainHasDiskMirror(vm)) { + virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s", + _("domain has active block copy job")); + goto cleanup; + } + + if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot))) + goto cleanup; + + if (!vm->persistent && + snap->def->state != VIR_DOMAIN_RUNNING && + snap->def->state != VIR_DOMAIN_PAUSED && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient domain needs to request run or pause " + "to revert to inactive snapshot")); + goto cleanup; + } + if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("revert to external disk snapshot not supported " + "yet")); + goto cleanup; + } + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + if (!snap->def->dom) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, + _("snapshot '%s' lacks domain '%s' rollback info"), + snap->def->name, vm->def->name); + goto cleanup; + } + if (virDomainObjIsActive(vm) && + !(snap->def->state == VIR_DOMAIN_RUNNING + || snap->def->state == VIR_DOMAIN_PAUSED) && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", + _("must respawn qemu to start inactive snapshot")); + goto cleanup; + } + } + + + if (vm->current_snapshot) { + vm->current_snapshot->def->current = false; + if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, + driver->snapshotDir) < 0) + goto cleanup; + vm->current_snapshot = NULL; + /* XXX Should we restore vm->current_snapshot after this point + * in the failure cases where we know there was no change? */ + } + + /* Prepare to copy the snapshot inactive xml as the config of this + * domain. + * + * XXX Should domain snapshots track live xml rather + * than inactive xml? */ + snap->def->current = true; + if (snap->def->dom) { + config = virDomainDefCopy(driver->caps, snap->def->dom, true); + if (!config) + goto cleanup; + } + + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (snap->def->state == VIR_DOMAIN_RUNNING + || snap->def->state == VIR_DOMAIN_PAUSED) { + /* Transitions 2, 3, 5, 6, 8, 9 */ + bool was_running = false; + bool was_stopped = false; + + /* When using the loadvm monitor command, qemu does not know + * whether to pause or run the reverted domain, and just stays + * in the same state as before the monitor command, whether + * that is paused or running. We always pause before loadvm, + * to have finer control. */ + if (virDomainObjIsActive(vm)) { + /* Transitions 5, 6, 8, 9 */ + /* Check for ABI compatibility. */ + if (config && !virDomainDefCheckABIStability(vm->def, config)) { + virErrorPtr err = virGetLastError(); + + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + /* Re-spawn error using correct category. */ + if (err->code == VIR_ERR_CONFIG_UNSUPPORTED) + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", + err->str2); + goto endjob; + } + virResetError(err); + qemuProcessStop(driver, vm, + VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + detail); + if (event) + qemuDomainEventQueue(driver, event); + goto load; + } + + priv = vm->privateData; + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + /* Transitions 5, 6 */ + was_running = true; + if (qemuProcessStopCPUs(driver, vm, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE) < 0) + goto endjob; + /* Create an event now in case the restore fails, so + * that user will be alerted that they are now paused. + * If restore later succeeds, we might replace this. */ + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + } + qemuDomainObjEnterMonitorWithDriver(driver, vm); + rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (rc < 0) { + /* XXX resume domain if it was running before the + * failed loadvm attempt? */ + goto endjob; + } + if (config) + virDomainObjAssignDef(vm, config, false); + } else { + /* Transitions 2, 3 */ + load: + was_stopped = true; + if (config) + virDomainObjAssignDef(vm, config, false); + + rc = qemuProcessStart(snapshot->domain->conn, + driver, vm, NULL, -1, NULL, snap, + VIR_NETDEV_VPORT_PROFILE_OP_CREATE, + VIR_QEMU_PROCESS_START_PAUSED); + virDomainAuditStart(vm, "from-snapshot", rc >= 0); + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + if (rc < 0) + goto endjob; + } + + /* Touch up domain state. */ + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && + (snap->def->state == VIR_DOMAIN_PAUSED || + (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { + /* Transitions 3, 6, 9 */ + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + if (was_stopped) { + /* Transition 3, use event as-is and add event2 */ + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + } /* else transition 6 and 9 use event as-is */ + } else { + /* Transitions 2, 5, 8 */ + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + rc = qemuProcessStartCPUs(driver, vm, snapshot->domain->conn, + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE); + if (rc < 0) + goto endjob; + virDomainEventFree(event); + event = NULL; + if (was_stopped) { + /* Transition 2 */ + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + } else if (was_running) { + /* Transition 8 */ + detail = VIR_DOMAIN_EVENT_RESUMED; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + detail); + } + } + } else { + /* Transitions 1, 4, 7 */ + /* Newer qemu -loadvm refuses to revert to the state of a snapshot + * created by qemu-img snapshot -c. If the domain is running, we + * must take it offline; then do the revert using qemu-img. + */ + + if (virDomainObjIsActive(vm)) { + /* Transitions 4, 7 */ + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + detail); + } + + if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) { + if (!vm->persistent) { + if (qemuDomainObjEndJob(driver, vm) > 0) + qemuDomainRemoveInactive(driver, vm); + vm = NULL; + goto cleanup; + } + goto endjob; + } + if (config) + virDomainObjAssignDef(vm, config, false); + + if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { + /* Flush first event, now do transition 2 or 3 */ + bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0; + unsigned int start_flags = 0; + + start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0; + + if (event) + qemuDomainEventQueue(driver, event); + rc = qemuProcessStart(snapshot->domain->conn, + driver, vm, NULL, -1, NULL, NULL, + VIR_NETDEV_VPORT_PROFILE_OP_CREATE, + start_flags); + virDomainAuditStart(vm, "from-snapshot", rc >= 0); + if (rc < 0) { + if (!vm->persistent) { + if (qemuDomainObjEndJob(driver, vm) > 0) + qemuDomainRemoveInactive(driver, vm); + vm = NULL; + goto cleanup; + } + goto endjob; + } + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + if (paused) { + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + } + } + } + + ret = 0; + +endjob: + if (vm && qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; + +cleanup: + if (vm && ret == 0) { + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->snapshotDir) < 0) + ret = -1; + else + vm->current_snapshot = snap; + } else if (snap) { + snap->def->current = false; + } + if (event) { + qemuDomainEventQueue(driver, event); + if (event2) + qemuDomainEventQueue(driver, event2); + } + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + + return ret; +} + +typedef struct _virQEMUSnapReparent virQEMUSnapReparent; +typedef virQEMUSnapReparent *virQEMUSnapReparentPtr; +struct _virQEMUSnapReparent { + virQEMUDriverPtr driver; + virDomainSnapshotObjPtr parent; + virDomainObjPtr vm; + int err; + virDomainSnapshotObjPtr last; +}; + + +static void +qemuDomainSnapshotReparentChildren(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + virQEMUSnapReparentPtr rep = data; + + if (rep->err < 0) { + return; + } + + VIR_FREE(snap->def->parent); + snap->parent = rep->parent; + + if (rep->parent->def) { + snap->def->parent = strdup(rep->parent->def->name); + + if (snap->def->parent == NULL) { + virReportOOMError(); + rep->err = -1; + return; + } + } + + if (!snap->sibling) + rep->last = snap; + + rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap, + rep->driver->snapshotDir); +} + + +int +qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virQEMUDriverPtr driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virQEMUSnapRemove rem; + virQEMUSnapReparent rep; + bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); + int external = 0; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot))) + goto cleanup; + + if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) && + virDomainSnapshotIsExternal(snap)) + external++; + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) + virDomainSnapshotForEachDescendant(snap, + qemuDomainSnapshotCountExternal, + &external); + if (external) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("deletion of %d external disk snapshots not " + "supported yet"), external); + goto cleanup; + } + } + + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { + rem.driver = driver; + rem.vm = vm; + rem.metadata_only = metadata_only; + rem.err = 0; + rem.current = false; + virDomainSnapshotForEachDescendant(snap, + qemuDomainSnapshotDiscardAll, + &rem); + if (rem.err < 0) + goto endjob; + if (rem.current) { + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + snap->def->current = true; + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->snapshotDir) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to set snapshot '%s' as current"), + snap->def->name); + snap->def->current = false; + goto endjob; + } + } + vm->current_snapshot = snap; + } + } else if (snap->nchildren) { + rep.driver = driver; + rep.parent = snap->parent; + rep.vm = vm; + rep.err = 0; + rep.last = NULL; + virDomainSnapshotForEachChild(snap, + qemuDomainSnapshotReparentChildren, + &rep); + if (rep.err < 0) + goto endjob; + /* Can't modify siblings during ForEachChild, so do it now. */ + snap->parent->nchildren += snap->nchildren; + rep.last->sibling = snap->parent->first_child; + snap->parent->first_child = snap->first_child; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + snap->nchildren = 0; + snap->first_child = NULL; + ret = 0; + } else { + virDomainSnapshotDropParent(snap); + ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only); + } + +endjob: + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} diff --git a/src/qemu/qemu_snapshot.h b/src/qemu/qemu_snapshot.h new file mode 100644 index 0000000..eebf167 --- /dev/null +++ b/src/qemu/qemu_snapshot.h @@ -0,0 +1,38 @@ +/* + * qemu_snapshot.h: QEMU snapshot handling + * + * Copyright (C) 2013 Red Hat, Inc. + * + * 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/>. + * + */ + +#ifndef __QEMU_SNAPSHOT_H__ +# define __QEMU_SNAPSHOT_H__ + +# include "qemu_domain.h" + +virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +int qemuDomainRevertToSnapshot(virDomainSnapshotPtr, + unsigned int flags); + +int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); + + +#endif /* __QEMU_SNAPSHOT_H__ */ -- 1.8.0.2

On 01/03/2013 09:45 AM, Peter Krempa wrote:
--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 1699 +------------------------------------------- src/qemu/qemu_snapshot.c | 1752 ++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_snapshot.h | 38 + 5 files changed, 1793 insertions(+), 1698 deletions(-) create mode 100644 src/qemu/qemu_snapshot.c create mode 100644 src/qemu/qemu_snapshot.h
Now that I've seen Dan's arguments against 1/2, I tend to agree that qemu_util.c doesn't make much sense. But qemu_snapshot.c still makes sense, so I will still review this one.
+++ b/src/qemu/qemu_snapshot.c @@ -0,0 +1,1752 @@ +/* + * qemu_snapshot.c: QEMU snapshot handling + * + * Copyright (C) 2013 Red Hat, Inc.
Same comments as in 1/2 about copying over copyright years from the original location.
+ +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +
And same comment about sorting includes.
+#ifndef __QEMU_SNAPSHOT_H__ +# define __QEMU_SNAPSHOT_H__ + +# include "qemu_domain.h" + +virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +int qemuDomainRevertToSnapshot(virDomainSnapshotPtr, + unsigned int flags); + +int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); +
Interesting subset. Why not move any of these other snapshot-related driver callbacks? qemuDomainSnapshotGetXMLDesc, qemuDomainSnapshotNum, qemuDomainSnapshotListNames, qemuDomainListAllSnapshots, qemuDomainSnapshotNumChildren, qemuDomainSnapshotListChildrenNames, qemuDomainSnapshotListAllChildren, qemuDomainSnapshotLookupByName, qemuDomainSnapshotGetParent, qemuDomainSnapshotCurrent, qemuDomainSnapshotIsCurrent, qemuDomainSnapshotHasMetadata -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Jan 03, 2013 at 10:51:09AM -0700, Eric Blake wrote:
On 01/03/2013 09:45 AM, Peter Krempa wrote:
--- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_driver.c | 1699 +------------------------------------------- src/qemu/qemu_snapshot.c | 1752 ++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_snapshot.h | 38 + 5 files changed, 1793 insertions(+), 1698 deletions(-) create mode 100644 src/qemu/qemu_snapshot.c create mode 100644 src/qemu/qemu_snapshot.h
Now that I've seen Dan's arguments against 1/2, I tend to agree that qemu_util.c doesn't make much sense. But qemu_snapshot.c still makes sense, so I will still review this one.
+++ b/src/qemu/qemu_snapshot.c @@ -0,0 +1,1752 @@ +/* + * qemu_snapshot.c: QEMU snapshot handling + * + * Copyright (C) 2013 Red Hat, Inc.
Same comments as in 1/2 about copying over copyright years from the original location.
+ +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +
And same comment about sorting includes.
+#ifndef __QEMU_SNAPSHOT_H__ +# define __QEMU_SNAPSHOT_H__ + +# include "qemu_domain.h" + +virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +int qemuDomainRevertToSnapshot(virDomainSnapshotPtr, + unsigned int flags); + +int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); +
Interesting subset. Why not move any of these other snapshot-related driver callbacks? qemuDomainSnapshotGetXMLDesc, qemuDomainSnapshotNum, qemuDomainSnapshotListNames, qemuDomainListAllSnapshots, qemuDomainSnapshotNumChildren, qemuDomainSnapshotListChildrenNames, qemuDomainSnapshotListAllChildren, qemuDomainSnapshotLookupByName, qemuDomainSnapshotGetParent, qemuDomainSnapshotCurrent, qemuDomainSnapshotIsCurrent, qemuDomainSnapshotHasMetadata
IMHO all methods which are directly part of the public API driver struct should be in qemu_driver.c. If we want to split code, then there should be a set of internal helpers which those public API methods call. eg, see how it is done for migration. qemu_driver.c contains qemuDomainMigratePrepare2 which in turn calls qemuMigrationPrepareDirect in qemu_migration.c Basically all the qemu_driver.c code does is convert from the public API types (virDomainPtr) into the private API types (virDomainObjPtr) and then call the main impl code. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Peter Krempa