--- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list