From nobody Fri May 3 10:12:47 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) client-ip=207.211.31.120; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-1.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1596707736; cv=none; d=zohomail.com; s=zohoarc; b=dcy5/zKbUnlEo+nW5lUPmQOd+dqrqdclph9se/eVU01l/oTbAFIW0PvyBnEB6KAWKJHosXEz7YDbmhuhLcsurPGG68XKA3YogQZ0GaCdEyFsinVmsd83J1dW3YHYLSfr0LH0HbhOueOg78WXMMUod26e01lcklmJNmUI7b4m1qY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1596707736; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=dEOAn9ec+ZZNb3B0n4744kQfEHdOj9PPNhawdVyvmNE=; b=MieOU2gHbW533LUVfIonv1cGMnTWh9E/H87CDGNphoYThzuqwlWsSQ5zO2spoWo43cP2+o39rWdMbK9Linl63h57cb0OoVipIw1RPHuNf+rFiTlQ72ib5aSlV53Gy4qLeXm7885D1MTlLfYoorZMfoDP2wddMY6KtIZounhO1U0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) by mx.zohomail.com with SMTPS id 1596707736920803.7135375610995; Thu, 6 Aug 2020 02:55:36 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-505-K52mS5jINMivjbisquNMbA-1; Thu, 06 Aug 2020 05:55:33 -0400 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A8010100CCC0; Thu, 6 Aug 2020 09:55:27 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 7630865C7C; Thu, 6 Aug 2020 09:55:27 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id C61B99692A; Thu, 6 Aug 2020 09:55:24 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0769tMRJ024668 for ; Thu, 6 Aug 2020 05:55:22 -0400 Received: by smtp.corp.redhat.com (Postfix) id E120860E1C; Thu, 6 Aug 2020 09:55:22 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.38]) by smtp.corp.redhat.com (Postfix) with ESMTP id 03A6460BF3 for ; Thu, 6 Aug 2020 09:55:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1596707735; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=dEOAn9ec+ZZNb3B0n4744kQfEHdOj9PPNhawdVyvmNE=; b=JUc/c8kXgRa49eMMJrJL/ZPJz+qlKSyc6Grb4hT5NKkDU1YLHcgaaA8yoKmJF/ef3HRU87 vaHGGGGaZMNLt3s4RbiM3Z6WSOypedwIODGTGqAuSbUvLPXSugG/TjlKb95PFGpIQ+jBh/ mW586ZI0+bkWSMWPFWOPfKiyeidZq78= X-MC-Unique: K52mS5jINMivjbisquNMbA-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 1/5] qemuOpenFileAs: Move into util/virqemu.c Date: Thu, 6 Aug 2020 11:55:12 +0200 Message-Id: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 138 ++------------------------------------- src/util/virqemu.c | 130 ++++++++++++++++++++++++++++++++++++ src/util/virqemu.h | 7 ++ 4 files changed, 144 insertions(+), 132 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 01c2e710cd..d737e4d9e4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2939,6 +2939,7 @@ virQEMUBuildDriveCommandlineFromJSON; virQEMUBuildNetdevCommandlineFromJSON; virQEMUBuildObjectCommandlineFromJSON; virQEMUBuildQemuImgKeySecretOpts; +virQEMUFileOpenAs; # util/virrandom.h diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0f98243fe4..a667eb21bf 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -148,11 +148,6 @@ static int qemuDomainObjStart(virConnectPtr conn, static int qemuDomainManagedSaveLoad(virDomainObjPtr vm, void *opaque); -static int qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid, - bool dynamicOwnership, - const char *path, int oflags, - bool *needUnlink); - static virQEMUDriverPtr qemu_driver; /* Looks up the domain object from snapshot and unlocks the @@ -3062,129 +3057,8 @@ qemuOpenFile(virQEMUDriverPtr driver, (virParseOwnershipIds(seclabel->label, &user, &group) < 0)) return -1; - return qemuOpenFileAs(user, group, dynamicOwnership, - path, oflags, needUnlink); -} - -static int -qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid, - bool dynamicOwnership, - const char *path, int oflags, - bool *needUnlink) -{ - struct stat sb; - bool is_reg =3D true; - bool need_unlink =3D false; - unsigned int vfoflags =3D 0; - int fd =3D -1; - int path_shared =3D virFileIsSharedFS(path); - uid_t uid =3D geteuid(); - gid_t gid =3D getegid(); - - /* 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 =3D true; - - /* Don't force chown on network-shared FS - * as it is likely to fail. */ - if (path_shared <=3D 0 || dynamicOwnership) - vfoflags |=3D VIR_FILE_OPEN_FORCE_OWNER; - - if (stat(path, &sb) =3D=3D 0) { - /* It already exists, we don't want to delete it on error */ - need_unlink =3D false; - - is_reg =3D !!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 its ownership, just open it as-is */ - if (is_reg && !dynamicOwnership) { - uid =3D sb.st_uid; - gid =3D sb.st_gid; - } - } - } - - /* First try creating the file as root */ - if (!is_reg) { - if ((fd =3D open(path, oflags & ~O_CREAT)) < 0) { - fd =3D -errno; - goto error; - } - } else { - if ((fd =3D virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, uid, gi= d, - 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 is non-root, just set a flag to - bypass security driver shenanigans, and retry the operation - after doing setuid to qemu user */ - if ((fd !=3D -EACCES && fd !=3D -EPERM) || fallback_uid =3D=3D= geteuid()) - goto error; - - /* 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(-fd, oflags & O_CREAT - ? _("Failed to create file " - "'%s': couldn't determine fs = type") - : _("Failed to open file " - "'%s': couldn't determine fs = type"), - path); - goto cleanup; - - case 0: - default: - /* local file - log the error returned by virFileOpenA= s */ - goto error; - } - - /* If we created the file above, then we need to remove it; - * otherwise, the next attempt to create will fail. If the - * file had already existed before we got here, then we also - * don't want to delete it and allow the following to succeed - * or fail based on existing protections - */ - if (need_unlink) - unlink(path); - - /* Retry creating the file as qemu user */ - - /* Since we're passing different modes... */ - vfoflags |=3D VIR_FILE_OPEN_FORCE_MODE; - - if ((fd =3D virFileOpenAs(path, oflags, - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, - fallback_uid, fallback_gid, - vfoflags | VIR_FILE_OPEN_FORK)) < 0) { - virReportSystemError(-fd, oflags & O_CREAT - ? _("Error from child process creatin= g '%s'") - : _("Error from child process opening= '%s'"), - path); - goto cleanup; - } - } - } - cleanup: - if (needUnlink) - *needUnlink =3D need_unlink; - return fd; - - error: - virReportSystemError(-fd, oflags & O_CREAT - ? _("Failed to create file '%s'") - : _("Failed to open file '%s'"), - path); - goto cleanup; + return virQEMUFileOpenAs(user, group, dynamicOwnership, + path, oflags, needUnlink); } @@ -3247,9 +3121,9 @@ qemuDomainSaveMemory(virQEMUDriverPtr driver, } } - fd =3D qemuOpenFileAs(cfg->user, cfg->group, false, path, - O_WRONLY | O_TRUNC | O_CREAT | directFlag, - &needUnlink); + fd =3D virQEMUFileOpenAs(cfg->user, cfg->group, false, path, + O_WRONLY | O_TRUNC | O_CREAT | directFlag, + &needUnlink); if (fd < 0) goto cleanup; @@ -3824,7 +3698,7 @@ doCoreDump(virQEMUDriverPtr driver, /* Core dumps usually imply last-ditch analysis efforts are * desired, so we intentionally do not unlink even if a file was * created. */ - if ((fd =3D qemuOpenFileAs(cfg->user, cfg->group, false, path, + if ((fd =3D virQEMUFileOpenAs(cfg->user, cfg->group, false, path, O_CREAT | O_TRUNC | O_WRONLY | directFlag, NULL)) < 0) goto cleanup; diff --git a/src/util/virqemu.c b/src/util/virqemu.c index 20cb09d878..e1c8673390 100644 --- a/src/util/virqemu.c +++ b/src/util/virqemu.c @@ -22,12 +22,18 @@ #include +#include +#include +#include +#include + #include "virbuffer.h" #include "virerror.h" #include "virlog.h" #include "virqemu.h" #include "virstring.h" #include "viralloc.h" +#include "virfile.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -441,3 +447,127 @@ virQEMUBuildQemuImgKeySecretOpts(virBufferPtr buf, virBufferAddLit(buf, ","); } } + + +int +virQEMUFileOpenAs(uid_t fallback_uid, + gid_t fallback_gid, + bool dynamicOwnership, + const char *path, + int oflags, + bool *needUnlink) +{ + struct stat sb; + bool is_reg =3D true; + bool need_unlink =3D false; + unsigned int vfoflags =3D 0; + int fd =3D -1; + int path_shared =3D virFileIsSharedFS(path); + uid_t uid =3D geteuid(); + gid_t gid =3D getegid(); + + /* 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 =3D true; + + /* Don't force chown on network-shared FS + * as it is likely to fail. */ + if (path_shared <=3D 0 || dynamicOwnership) + vfoflags |=3D VIR_FILE_OPEN_FORCE_OWNER; + + if (stat(path, &sb) =3D=3D 0) { + /* It already exists, we don't want to delete it on error */ + need_unlink =3D false; + + is_reg =3D !!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 its ownership, just open it as-is */ + if (is_reg && !dynamicOwnership) { + uid =3D sb.st_uid; + gid =3D sb.st_gid; + } + } + } + + /* First try creating the file as root */ + if (!is_reg) { + if ((fd =3D open(path, oflags & ~O_CREAT)) < 0) { + fd =3D -errno; + goto error; + } + } else { + if ((fd =3D virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, uid, gi= d, + 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 is non-root, just set a flag to + bypass security driver shenanigans, and retry the operation + after doing setuid to qemu user */ + if ((fd !=3D -EACCES && fd !=3D -EPERM) || fallback_uid =3D=3D= geteuid()) + goto error; + + /* 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(-fd, oflags & O_CREAT + ? _("Failed to create file " + "'%s': couldn't determine fs = type") + : _("Failed to open file " + "'%s': couldn't determine fs = type"), + path); + goto cleanup; + + case 0: + default: + /* local file - log the error returned by virFileOpenA= s */ + goto error; + } + + /* If we created the file above, then we need to remove it; + * otherwise, the next attempt to create will fail. If the + * file had already existed before we got here, then we also + * don't want to delete it and allow the following to succeed + * or fail based on existing protections + */ + if (need_unlink) + unlink(path); + + /* Retry creating the file as qemu user */ + + /* Since we're passing different modes... */ + vfoflags |=3D VIR_FILE_OPEN_FORCE_MODE; + + if ((fd =3D virFileOpenAs(path, oflags, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, + fallback_uid, fallback_gid, + vfoflags | VIR_FILE_OPEN_FORK)) < 0) { + virReportSystemError(-fd, oflags & O_CREAT + ? _("Error from child process creatin= g '%s'") + : _("Error from child process opening= '%s'"), + path); + goto cleanup; + } + } + } + cleanup: + if (needUnlink) + *needUnlink =3D need_unlink; + return fd; + + error: + virReportSystemError(-fd, oflags & O_CREAT + ? _("Failed to create file '%s'") + : _("Failed to open file '%s'"), + path); + goto cleanup; +} diff --git a/src/util/virqemu.h b/src/util/virqemu.h index b1296cb657..e4e071f7c5 100644 --- a/src/util/virqemu.h +++ b/src/util/virqemu.h @@ -63,3 +63,10 @@ void virQEMUBuildQemuImgKeySecretOpts(virBufferPtr buf, virStorageEncryptionInfoDefPtr enc, const char *alias) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + +int virQEMUFileOpenAs(uid_t fallback_uid, + gid_t fallback_gid, + bool dynamicOwnership, + const char *path, + int oflags, + bool *needUnlink); --=20 2.26.2 From nobody Fri May 3 10:12:47 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 205.139.110.120 as permitted sender) client-ip=205.139.110.120; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-1.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 205.139.110.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1596707756; cv=none; d=zohomail.com; s=zohoarc; b=TJvo2/yswCt2d2eZLGa68FGkHUDDKpL7NupO1Z+F4grbEDIa+CHEPVaclKJ0Z1p+uEHW8QCGyty2I6iI8AxoGYLWYlEpUekNouh+P0kW/uZr0feVdxCDnbw/8RG8k++B1JRTrcjLfPbAq7eHSw2USiGIVRPuurQHXXcZh/5D7IE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1596707756; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=L0/kM5lpUpeML5hTvMMJZOuZ1gUu3HypwuYM8fiit38=; b=a4YbIqV96JUEArJv1Vp96IxIAc4l0ejBlgPzsoD91eofMarl38ucaBvXtnpvhTQNtuIjHe88lmaFVxMFXbJX6ESofvrPgzne6JCfIdxv6tgMSVaD/ilEn7EXJqtxze/Y83u67EWK8zYHVQerjkTnkCr4gGP5yyrzX5MfwKLAZVI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 205.139.110.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) by mx.zohomail.com with SMTPS id 1596707756866129.1446278628265; Thu, 6 Aug 2020 02:55:56 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-102-KWLQPRJ7ONCl1w671U8hgA-1; Thu, 06 Aug 2020 05:55:53 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id C802B100CCC0; Thu, 6 Aug 2020 09:55:47 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A64631001B2C; Thu, 6 Aug 2020 09:55:47 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 731B6180530B; Thu, 6 Aug 2020 09:55:47 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0769tNO9024675 for ; Thu, 6 Aug 2020 05:55:23 -0400 Received: by smtp.corp.redhat.com (Postfix) id EC37A60E1C; Thu, 6 Aug 2020 09:55:23 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.38]) by smtp.corp.redhat.com (Postfix) with ESMTP id 4E2DE60BF3 for ; Thu, 6 Aug 2020 09:55:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1596707755; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=L0/kM5lpUpeML5hTvMMJZOuZ1gUu3HypwuYM8fiit38=; b=eNQpYkjjGL0lXGnjEZtEqdoDDiSbvW1qPcpuaa+tkDLhBK4k8fXlSeSOqo3CJEGHhlOqj9 iHdVrnWSagZVkgaQ5d/EqCenYvmaV1wSDGfDK5z9FRgCeBs/m0SNAXfhNsHl0M7Aqu65O0 58EeohP3DiBxAbl05yogqFX8+2tgf9M= X-MC-Unique: KWLQPRJ7ONCl1w671U8hgA-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 2/5] qemuOpenFile: Move to qemu_domain.c Date: Thu, 6 Aug 2020 11:55:13 +0200 Message-Id: <3e8edade52db373f1b8cb192dcf8d1a3a6a2983d.1596707671.git.pkrempa@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" Move the code to qemu_domain.c so that it can be reused in other parts of the qemu driver. 'qemu_domain' was chosen as the permissions are based on the domain configuration. Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- src/qemu/qemu_domain.c | 42 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 7 ++++++ src/qemu/qemu_driver.c | 50 +++++------------------------------------- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c440c79e1d..670db6ebfb 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -64,6 +64,7 @@ #include "virdomaincheckpointobjlist.h" #include "backup_conf.h" #include "virutil.h" +#include "virqemu.h" #include #include @@ -10679,3 +10680,44 @@ qemuDomainDiskBlockJobIsSupported(virDomainObjPtr = vm, return true; } + + +/** + * qemuDomainOpenFile: + * @driver: driver object + * @vm: domain object + * @path: path to file to open + * @oflags: flags for opening/creation of the file + * @needUnlink: set to true if file was created by this function + * + * Internal function to properly create or open existing files, with + * ownership affected by qemu driver setup and domain DAC label. + * + * Returns the file descriptor on success and negative errno on failure. + * + * This function should not be used on storage sources. Use + * qemuDomainStorageFileInit and storage driver APIs if possible. + **/ +int +qemuDomainOpenFile(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + int oflags, + bool *needUnlink) +{ + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + uid_t user =3D cfg->user; + gid_t group =3D cfg->group; + bool dynamicOwnership =3D cfg->dynamicOwnership; + virSecurityLabelDefPtr seclabel; + + /* TODO: Take imagelabel into account? */ + if (vm && + (seclabel =3D virDomainDefGetSecurityLabelDef(vm->def, "dac")) != =3D NULL && + seclabel->label !=3D NULL && + (virParseOwnershipIds(seclabel->label, &user, &group) < 0)) + return -1; + + return virQEMUFileOpenAs(user, group, dynamicOwnership, + path, oflags, needUnlink); +} diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 3a1bcbbfa3..ef03702fa1 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1017,3 +1017,10 @@ qemuDomainDiskBlockJobIsSupported(virDomainObjPtr vm, int qemuDomainDefNumaCPUsRectify(virDomainDefPtr def, virQEMUCapsPtr qemuCaps); + +int +qemuDomainOpenFile(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + int oflags, + bool *needUnlink); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a667eb21bf..0bc7eebe9a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -3021,46 +3021,6 @@ qemuCompressGetCommand(virQEMUSaveFormat compression) return ret; } -/** - * qemuOpenFile: - * @driver: driver object - * @vm: domain object - * @path: path to file to open - * @oflags: flags for opening/creation of the file - * @needUnlink: set to true if file was created by this function - * - * Internal function to properly create or open existing files, with - * ownership affected by qemu driver setup and domain DAC label. - * - * Returns the file descriptor on success and negative errno on failure. - * - * This function should not be used on storage sources. Use - * qemuDomainStorageFileInit and storage driver APIs if possible. - **/ -static int -qemuOpenFile(virQEMUDriverPtr driver, - virDomainObjPtr vm, - const char *path, - int oflags, - bool *needUnlink) -{ - g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); - uid_t user =3D cfg->user; - gid_t group =3D cfg->group; - bool dynamicOwnership =3D cfg->dynamicOwnership; - virSecurityLabelDefPtr seclabel; - - /* TODO: Take imagelabel into account? */ - if (vm && - (seclabel =3D virDomainDefGetSecurityLabelDef(vm->def, "dac")) != =3D NULL && - seclabel->label !=3D NULL && - (virParseOwnershipIds(seclabel->label, &user, &group) < 0)) - return -1; - - return virQEMUFileOpenAs(user, group, dynamicOwnership, - path, oflags, needUnlink); -} - static int qemuFileWrapperFDClose(virDomainObjPtr vm, @@ -3154,7 +3114,7 @@ qemuDomainSaveMemory(virQEMUDriverPtr driver, if (qemuFileWrapperFDClose(vm, wrapperFd) < 0) goto cleanup; - if ((fd =3D qemuOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 || + if ((fd =3D qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 = || virQEMUSaveDataFinish(data, &fd, path) < 0) goto cleanup; @@ -6593,7 +6553,7 @@ qemuDomainSaveImageOpen(virQEMUDriverPtr driver, oflags |=3D directFlag; } - if ((fd =3D qemuOpenFile(driver, NULL, path, oflags, NULL)) < 0) + if ((fd =3D qemuDomainOpenFile(driver, NULL, path, oflags, NULL)) < 0) return -1; if (bypass_cache && @@ -11593,7 +11553,7 @@ qemuDomainMemoryPeek(virDomainPtr dom, * @ret_sb: pointer to return stat buffer (local or remote) * @skipInaccessible: Don't report error if files are not accessible * - * For local storage, open the file using qemuOpenFile and then use + * For local storage, open the file using qemuDomainOpenFile and then use * fstat() to grab the stat struct data for the caller. * * For remote storage, attempt to access the file and grab the stat @@ -11616,8 +11576,8 @@ qemuDomainStorageOpenStat(virQEMUDriverPtr driver, if (skipInaccessible && !virFileExists(src->path)) return 0; - if ((*ret_fd =3D qemuOpenFile(driver, vm, src->path, O_RDONLY, - NULL)) < 0) + if ((*ret_fd =3D qemuDomainOpenFile(driver, vm, src->path, O_RDONL= Y, + NULL)) < 0) return -1; if (fstat(*ret_fd, ret_sb) < 0) { --=20 2.26.2 From nobody Fri May 3 10:12:47 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) client-ip=207.211.31.120; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-1.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1596707737; cv=none; d=zohomail.com; s=zohoarc; b=JqKDtvYRIZqubeSc7LWUUrIiK2QNsApag8eQqhJdTFwNmkhvwvklRpTpo2tZqgnLscI29H0SPSCVOXpOxMm2B9lBoGbEPTAoj77eZGlxfDaI/FY7Hsmx7CeFchXzJZDi0feKrEo12gGcqO9eJ8BjGSyAai9UYXE8cLzqxx6hGOA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1596707737; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=7g29VaOQk2iBF0BIptjYu4USm22PzRccS0fcJIeNiuc=; b=fw+t6kRpzTK2xA3BEGc2YMAgiSpwQX1aoUNhjzztHI+u4qkqtKh66PYqXojjOXZLo0dKOWZMxVXuSl8PN3i6qJULZFRcvHRqe0OlIl++DYEou2QyYQagBn2WrNo/cb5JSLfeQthxMjQQ9GICn6Esj/KKdTczPXlUmu8m5sIo1yc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) by mx.zohomail.com with SMTPS id 1596707737137567.7533371645582; Thu, 6 Aug 2020 02:55:37 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-23-lJh9Mip7MsOxV0GF_gKrvw-1; Thu, 06 Aug 2020 05:55:33 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 8D233100AA22; Thu, 6 Aug 2020 09:55:28 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 690FB1001B2C; Thu, 6 Aug 2020 09:55:28 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 0B5F09692F; Thu, 6 Aug 2020 09:55:27 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0769tPxm024685 for ; Thu, 6 Aug 2020 05:55:25 -0400 Received: by smtp.corp.redhat.com (Postfix) id 0DAEC60BF3; Thu, 6 Aug 2020 09:55:25 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.38]) by smtp.corp.redhat.com (Postfix) with ESMTP id 601F870105 for ; Thu, 6 Aug 2020 09:55:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1596707736; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=7g29VaOQk2iBF0BIptjYu4USm22PzRccS0fcJIeNiuc=; b=NUHSXHa6b3oLWkscoZS/Pj7nQaurssn5Ixm9DR1qpSKy6ToNmPde9r1gKA9zTLNN1Lhv4Z 0jgD8BS7dYNuJDj6ITHrAeoDQ9et7o/dTm+U9n51jwXpSAtFm1+Wh/mPbLOt05uKeBNoAs +JnLID5mYU9AEeD1F4ST9rHNz3vyW/k= X-MC-Unique: lJh9Mip7MsOxV0GF_gKrvw-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 3/5] qemuFileWrapperFDClose: move to qemu_domain.c Date: Thu, 6 Aug 2020 11:55:14 +0200 Message-Id: <8a91bed9c9a784df0c68400735f30f52d8b2da16.1596707671.git.pkrempa@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" Move the code to qemu_domain.c so that it can be reused in other parts of the qemu driver. 'qemu_domain' was chosen as we check the domain state after closing the wrapper. Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- src/qemu/qemu_domain.c | 28 ++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 4 ++++ src/qemu/qemu_driver.c | 36 ++++-------------------------------- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 670db6ebfb..e28f704dba 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10721,3 +10721,31 @@ qemuDomainOpenFile(virQEMUDriverPtr driver, return virQEMUFileOpenAs(user, group, dynamicOwnership, path, oflags, needUnlink); } + + +int +qemuDomainFileWrapperFDClose(virDomainObjPtr vm, + virFileWrapperFdPtr fd) +{ + int ret; + + /* virFileWrapperFd uses iohelper to write data onto disk. + * However, iohelper calls fdatasync() which may take ages to + * finish. Therefore, we shouldn't be waiting with the domain + * object locked. */ + + /* XXX Currently, this function is intended for *Save() only + * as restore needs some reworking before it's ready for + * this. */ + + virObjectUnlock(vm); + ret =3D virFileWrapperFdClose(fd); + virObjectLock(vm); + if (!virDomainObjIsActive(vm)) { + if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("domain is no longer running")); + ret =3D -1; + } + return ret; +} diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index ef03702fa1..e4c22864dc 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1024,3 +1024,7 @@ qemuDomainOpenFile(virQEMUDriverPtr driver, const char *path, int oflags, bool *needUnlink); + +int +qemuDomainFileWrapperFDClose(virDomainObjPtr vm, + virFileWrapperFdPtr fd); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0bc7eebe9a..8f61759f53 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -3022,34 +3022,6 @@ qemuCompressGetCommand(virQEMUSaveFormat compression) } -static int -qemuFileWrapperFDClose(virDomainObjPtr vm, - virFileWrapperFdPtr fd) -{ - int ret; - - /* virFileWrapperFd uses iohelper to write data onto disk. - * However, iohelper calls fdatasync() which may take ages to - * finish. Therefore, we shouldn't be waiting with the domain - * object locked. */ - - /* XXX Currently, this function is intended for *Save() only - * as restore needs some reworking before it's ready for - * this. */ - - virObjectUnlock(vm); - ret =3D virFileWrapperFdClose(fd); - virObjectLock(vm); - if (!virDomainObjIsActive(vm)) { - if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("domain is no longer running")); - ret =3D -1; - } - return ret; -} - - /* Helper function to execute a migration to file with a correct save head= er * the caller needs to make sure that the processors are stopped and do al= l other * actions besides saving memory */ @@ -3111,7 +3083,7 @@ qemuDomainSaveMemory(virQEMUDriverPtr driver, goto cleanup; } - if (qemuFileWrapperFDClose(vm, wrapperFd) < 0) + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) goto cleanup; if ((fd =3D qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 = || @@ -3122,7 +3094,7 @@ qemuDomainSaveMemory(virQEMUDriverPtr driver, cleanup: VIR_FORCE_CLOSE(fd); - if (qemuFileWrapperFDClose(vm, wrapperFd) < 0) + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) ret =3D -1; virFileWrapperFdFree(wrapperFd); @@ -3703,14 +3675,14 @@ doCoreDump(virQEMUDriverPtr driver, path); goto cleanup; } - if (qemuFileWrapperFDClose(vm, wrapperFd) < 0) + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) goto cleanup; ret =3D 0; cleanup: VIR_FORCE_CLOSE(fd); - if (qemuFileWrapperFDClose(vm, wrapperFd) < 0) + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) ret =3D -1; virFileWrapperFdFree(wrapperFd); if (ret !=3D 0) --=20 2.26.2 From nobody Fri May 3 10:12:47 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) client-ip=207.211.31.120; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-1.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1596707753; cv=none; d=zohomail.com; s=zohoarc; b=d8s1DsS+MG1K8wozC+9xkHo0izqtm5bb1tKQO+LpAnn1LxyKs9grZp5y+e8CHzAsoi5se9h9h5F8wI9vYhNbawtcFRmdWN8jv3VLkm9ACqGiXtrlTi/vCenWqJKXJpyPQopaT9/xmQuCJH+VdobuTPOWJ0cJzF1HVX69n2sFvZI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1596707753; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=FiW7gzoRbRDkjuvWhQTJ0Zfm5QJZo9wYqt/R4FYWYh8=; b=SvKPfNRf8u7wsa71vulSK3X5fiJUeti4T4gY0c6RisEkFwHR4HwzlCr3jvZA2ldJAQnb1Lqo6d0n5teP6ty4oubYiAEfMZ8tnK2qFASlOB6fdOgPkyIsqxGGBpYcb4k3lNueOeqEsQFtPh5nxnn5QfIMryNbuIGkb8qq4SJJR+I= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.120 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) by mx.zohomail.com with SMTPS id 1596707753501571.4115147831302; Thu, 6 Aug 2020 02:55:53 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-485-82TG4cXyNIGs8l81OQPhKA-1; Thu, 06 Aug 2020 05:55:48 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 849FA19200C0; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6322B5DE91; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 2C1221809563; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0769tQGh024693 for ; Thu, 6 Aug 2020 05:55:26 -0400 Received: by smtp.corp.redhat.com (Postfix) id 8F3BC60E1C; Thu, 6 Aug 2020 09:55:26 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.38]) by smtp.corp.redhat.com (Postfix) with ESMTP id 800C660BF3 for ; Thu, 6 Aug 2020 09:55:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1596707752; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=FiW7gzoRbRDkjuvWhQTJ0Zfm5QJZo9wYqt/R4FYWYh8=; b=Y4xz/4QG5lVfqehXovtyK6tgUzPHmyQ/dtROBRwAcpCh6JpCx6IjldSHL4EsYQFtGZDfZb 18atQYM3+qNf6PLaZBRDL5PTuCEUzVxef0iYMPpKKPzTKhLfnWPt+XsUfSvuZWtqahi0eq COvFxIoIBMehcFNecmUPU8AIvJG1Jcg= X-MC-Unique: 82TG4cXyNIGs8l81OQPhKA-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 4/5] qemu: Split of code related to handling of the save image file Date: Thu, 6 Aug 2020 11:55:15 +0200 Message-Id: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" There's a lot of helper code related to the save image handling. Extract it to qemu_saveimage.c/h. Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- po/POTFILES.in | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_driver.c | 843 +++----------------------------------- src/qemu/qemu_saveimage.c | 764 ++++++++++++++++++++++++++++++++++ src/qemu/qemu_saveimage.h | 116 ++++++ 5 files changed, 928 insertions(+), 797 deletions(-) create mode 100644 src/qemu/qemu_saveimage.c create mode 100644 src/qemu/qemu_saveimage.h diff --git a/po/POTFILES.in b/po/POTFILES.in index c5b43df7b5..6f47371b01 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -170,6 +170,7 @@ @SRCDIR@src/qemu/qemu_namespace.c @SRCDIR@src/qemu/qemu_process.c @SRCDIR@src/qemu/qemu_qapi.c +@SRCDIR@src/qemu/qemu_saveimage.c @SRCDIR@src/qemu/qemu_slirp.c @SRCDIR@src/qemu/qemu_tpm.c @SRCDIR@src/qemu/qemu_validate.c diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 7faba16049..7d5249978a 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -29,6 +29,7 @@ qemu_driver_sources =3D [ 'qemu_namespace.c', 'qemu_process.c', 'qemu_qapi.c', + 'qemu_saveimage.c', 'qemu_security.c', 'qemu_slirp.c', 'qemu_tpm.c', diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8f61759f53..a6b8c79168 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -51,6 +51,7 @@ #include "qemu_checkpoint.h" #include "qemu_backup.h" #include "qemu_namespace.h" +#include "qemu_saveimage.h" #include "virerror.h" #include "virlog.h" @@ -135,6 +136,16 @@ VIR_LOG_INIT("qemu.qemu_driver"); #define QEMU_NB_BANDWIDTH_PARAM 7 +VIR_ENUM_DECL(qemuDumpFormat); +VIR_ENUM_IMPL(qemuDumpFormat, + VIR_DOMAIN_CORE_DUMP_FORMAT_LAST, + "elf", + "kdump-zlib", + "kdump-lzo", + "kdump-snappy", +); + + static void qemuProcessEventHandler(void *data, void *opaque); static int qemuStateCleanup(void); @@ -2771,339 +2782,6 @@ qemuDomainGetControlInfo(virDomainPtr dom, } -/* It would be nice to replace 'Qemud' with 'Qemu' but - * this magic string is ABI, so it can't be changed - */ -#define QEMU_SAVE_MAGIC "LibvirtQemudSave" -#define QEMU_SAVE_PARTIAL "LibvirtQemudPart" -#define QEMU_SAVE_VERSION 2 - -G_STATIC_ASSERT(sizeof(QEMU_SAVE_MAGIC) =3D=3D sizeof(QEMU_SAVE_PARTIAL)); - -typedef enum { - QEMU_SAVE_FORMAT_RAW =3D 0, - QEMU_SAVE_FORMAT_GZIP =3D 1, - QEMU_SAVE_FORMAT_BZIP2 =3D 2, - /* - * Deprecated by xz and never used as part of a release - * QEMU_SAVE_FORMAT_LZMA - */ - QEMU_SAVE_FORMAT_XZ =3D 3, - QEMU_SAVE_FORMAT_LZOP =3D 4, - /* Note: add new members only at the end. - These values are used in the on-disk format. - Do not change or re-use numbers. */ - - QEMU_SAVE_FORMAT_LAST -} virQEMUSaveFormat; - -VIR_ENUM_DECL(qemuSaveCompression); -VIR_ENUM_IMPL(qemuSaveCompression, - QEMU_SAVE_FORMAT_LAST, - "raw", - "gzip", - "bzip2", - "xz", - "lzop", -); - -VIR_ENUM_DECL(qemuDumpFormat); -VIR_ENUM_IMPL(qemuDumpFormat, - VIR_DOMAIN_CORE_DUMP_FORMAT_LAST, - "elf", - "kdump-zlib", - "kdump-lzo", - "kdump-snappy", -); - -typedef struct _virQEMUSaveHeader virQEMUSaveHeader; -typedef virQEMUSaveHeader *virQEMUSaveHeaderPtr; -struct _virQEMUSaveHeader { - char magic[sizeof(QEMU_SAVE_MAGIC)-1]; - uint32_t version; - uint32_t data_len; - uint32_t was_running; - uint32_t compressed; - uint32_t cookieOffset; - uint32_t unused[14]; -}; - -typedef struct _virQEMUSaveData virQEMUSaveData; -typedef virQEMUSaveData *virQEMUSaveDataPtr; -struct _virQEMUSaveData { - virQEMUSaveHeader header; - char *xml; - char *cookie; -}; - - -static inline void -bswap_header(virQEMUSaveHeaderPtr hdr) -{ - hdr->version =3D GUINT32_SWAP_LE_BE(hdr->version); - hdr->data_len =3D GUINT32_SWAP_LE_BE(hdr->data_len); - hdr->was_running =3D GUINT32_SWAP_LE_BE(hdr->was_running); - hdr->compressed =3D GUINT32_SWAP_LE_BE(hdr->compressed); - hdr->cookieOffset =3D GUINT32_SWAP_LE_BE(hdr->cookieOffset); -} - - -static void -virQEMUSaveDataFree(virQEMUSaveDataPtr data) -{ - if (!data) - return; - - VIR_FREE(data->xml); - VIR_FREE(data->cookie); - VIR_FREE(data); -} - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(virQEMUSaveData, virQEMUSaveDataFree); - -/** - * This function steals @domXML on success. - */ -static virQEMUSaveDataPtr -virQEMUSaveDataNew(char *domXML, - qemuDomainSaveCookiePtr cookieObj, - bool running, - int compressed, - virDomainXMLOptionPtr xmlopt) -{ - virQEMUSaveDataPtr data =3D NULL; - virQEMUSaveHeaderPtr header; - - if (VIR_ALLOC(data) < 0) - return NULL; - - data->xml =3D g_steal_pointer(&domXML); - - if (cookieObj && - !(data->cookie =3D virSaveCookieFormat((virObjectPtr) cookieObj, - virDomainXMLOptionGetSaveCook= ie(xmlopt)))) - goto error; - - header =3D &data->header; - memcpy(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)); - header->version =3D QEMU_SAVE_VERSION; - header->was_running =3D running ? 1 : 0; - header->compressed =3D compressed; - - return data; - - error: - virQEMUSaveDataFree(data); - return NULL; -} - - -/* virQEMUSaveDataWrite: - * - * Writes libvirt's header (including domain XML) into a saved image of a - * running domain. If @header has data_len filled in (because it was previ= ously - * read from the file), the function will make sure the new data will fit - * within data_len. - * - * Returns -1 on failure, or 0 on success. - */ -static int -virQEMUSaveDataWrite(virQEMUSaveDataPtr data, - int fd, - const char *path) -{ - virQEMUSaveHeaderPtr header =3D &data->header; - size_t len; - size_t xml_len; - size_t cookie_len =3D 0; - size_t zerosLen =3D 0; - g_autofree char *zeros =3D NULL; - - xml_len =3D strlen(data->xml) + 1; - if (data->cookie) - cookie_len =3D strlen(data->cookie) + 1; - - len =3D xml_len + cookie_len; - - if (header->data_len =3D=3D 0) { - /* This 64kb padding allows the user to edit the XML in - * a saved state image and have the new XML be larger - * that what was originally saved - */ - header->data_len =3D len + (64 * 1024); - } else { - if (len > header->data_len) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("new xml too large to fit in file")); - return -1; - } - } - - zerosLen =3D header->data_len - len; - zeros =3D g_new0(char, zerosLen); - - if (data->cookie) - header->cookieOffset =3D xml_len; - - if (safewrite(fd, header, sizeof(*header)) !=3D sizeof(*header)) { - virReportSystemError(errno, - _("failed to write header to domain save file= '%s'"), - path); - return -1; - } - - if (safewrite(fd, data->xml, xml_len) !=3D xml_len) { - virReportSystemError(errno, - _("failed to write domain xml to '%s'"), - path); - return -1; - } - - if (data->cookie && - safewrite(fd, data->cookie, cookie_len) !=3D cookie_len) { - virReportSystemError(errno, - _("failed to write cookie to '%s'"), - path); - return -1; - } - - if (safewrite(fd, zeros, zerosLen) !=3D zerosLen) { - virReportSystemError(errno, - _("failed to write padding to '%s'"), - path); - return -1; - } - - return 0; -} - - -static int -virQEMUSaveDataFinish(virQEMUSaveDataPtr data, - int *fd, - const char *path) -{ - virQEMUSaveHeaderPtr header =3D &data->header; - - memcpy(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)); - - if (safewrite(*fd, header, sizeof(*header)) !=3D sizeof(*header) || - VIR_CLOSE(*fd) < 0) { - virReportSystemError(errno, - _("failed to write header to domain save file= '%s'"), - path); - return -1; - } - - return 0; -} - - -static virCommandPtr -qemuCompressGetCommand(virQEMUSaveFormat compression) -{ - virCommandPtr ret =3D NULL; - const char *prog =3D qemuSaveCompressionTypeToString(compression); - - if (!prog) { - virReportError(VIR_ERR_OPERATION_FAILED, - _("Invalid compressed save format %d"), - compression); - return NULL; - } - - ret =3D virCommandNew(prog); - virCommandAddArg(ret, "-dc"); - - if (compression =3D=3D QEMU_SAVE_FORMAT_LZOP) - virCommandAddArg(ret, "--ignore-warn"); - - return ret; -} - - -/* Helper function to execute a migration to file with a correct save head= er - * the caller needs to make sure that the processors are stopped and do al= l other - * actions besides saving memory */ -static int -qemuDomainSaveMemory(virQEMUDriverPtr driver, - virDomainObjPtr vm, - const char *path, - virQEMUSaveDataPtr data, - virCommandPtr compressor, - unsigned int flags, - qemuDomainAsyncJob asyncJob) -{ - g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); - bool needUnlink =3D false; - int ret =3D -1; - int fd =3D -1; - int directFlag =3D 0; - virFileWrapperFdPtr wrapperFd =3D NULL; - unsigned int wrapperFlags =3D VIR_FILE_WRAPPER_NON_BLOCKING; - - /* Obtain the file handle. */ - if ((flags & VIR_DOMAIN_SAVE_BYPASS_CACHE)) { - wrapperFlags |=3D VIR_FILE_WRAPPER_BYPASS_CACHE; - directFlag =3D virFileDirectFdFlag(); - if (directFlag < 0) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("bypass cache unsupported by this system")); - goto cleanup; - } - } - - fd =3D virQEMUFileOpenAs(cfg->user, cfg->group, false, path, - O_WRONLY | O_TRUNC | O_CREAT | directFlag, - &needUnlink); - if (fd < 0) - goto cleanup; - - if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, fd) = < 0) - goto cleanup; - - if (!(wrapperFd =3D virFileWrapperFdNew(&fd, path, wrapperFlags))) - goto cleanup; - - if (virQEMUSaveDataWrite(data, fd, path) < 0) - goto cleanup; - - /* Perform the migration */ - if (qemuMigrationSrcToFile(driver, vm, fd, compressor, asyncJob) < 0) - goto cleanup; - - /* Touch up file header to mark image complete. */ - - /* Reopen the file to touch up the header, since we aren't set - * up to seek backwards on wrapperFd. The reopened fd will - * trigger a single page of file system cache pollution, but - * that's acceptable. */ - if (VIR_CLOSE(fd) < 0) { - virReportSystemError(errno, _("unable to close %s"), path); - goto cleanup; - } - - if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) - goto cleanup; - - if ((fd =3D qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 = || - virQEMUSaveDataFinish(data, &fd, path) < 0) - goto cleanup; - - ret =3D 0; - - cleanup: - VIR_FORCE_CLOSE(fd); - if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) - ret =3D -1; - virFileWrapperFdFree(wrapperFd); - - if (ret < 0 && needUnlink) - unlink(path); - - return ret; -} - /* The vm must be active + locked. Vm will be unlocked and * potentially free'd after this returns (eg transient VMs are freed * shutdown). So 'vm' must not be referenced by the caller after @@ -3192,8 +2870,8 @@ qemuDomainSaveInternal(virQEMUDriverPtr driver, goto endjob; xml =3D NULL; - ret =3D qemuDomainSaveMemory(driver, vm, path, data, compressor, - flags, QEMU_ASYNC_JOB_SAVE); + ret =3D qemuSaveImageCreate(driver, vm, path, data, compressor, + flags, QEMU_ASYNC_JOB_SAVE); if (ret < 0) goto endjob; @@ -3231,87 +2909,6 @@ qemuDomainSaveInternal(virQEMUDriverPtr driver, } -/* qemuGetCompressionProgram: - * @imageFormat: String representation from qemu.conf for the compression - * image format being used (dump, save, or snapshot). - * @compresspath: Pointer to a character string to store the fully qualifi= ed - * path from virFindFileInPath. - * @styleFormat: String representing the style of format (dump, save, snap= shot) - * @use_raw_on_fail: Boolean indicating how to handle the error path. For - * callers that are OK with invalid data or inability to - * find the compression program, just return a raw format - * and let the path remain as NULL. - * - * Returns: - * virQEMUSaveFormat - Integer representation of the compression - * program to be used for particular style - * (e.g. dump, save, or snapshot). - * QEMU_SAVE_FORMAT_RAW - If there is no qemu.conf imageFormat value or - * no there was an error, then just return RAW - * indicating none. - */ -static int ATTRIBUTE_NONNULL(2) -qemuGetCompressionProgram(const char *imageFormat, - virCommandPtr *compressor, - const char *styleFormat, - bool use_raw_on_fail) -{ - int ret; - const char *prog; - - *compressor =3D NULL; - - if (!imageFormat) - return QEMU_SAVE_FORMAT_RAW; - - if ((ret =3D qemuSaveCompressionTypeFromString(imageFormat)) < 0) - goto error; - - if (ret =3D=3D QEMU_SAVE_FORMAT_RAW) - return QEMU_SAVE_FORMAT_RAW; - - if (!(prog =3D virFindFileInPath(imageFormat))) - goto error; - - *compressor =3D virCommandNew(prog); - virCommandAddArg(*compressor, "-c"); - if (ret =3D=3D QEMU_SAVE_FORMAT_XZ) - virCommandAddArg(*compressor, "-3"); - - return ret; - - error: - if (ret < 0) { - if (use_raw_on_fail) - VIR_WARN("Invalid %s image format specified in " - "configuration file, using raw", - styleFormat); - else - virReportError(VIR_ERR_OPERATION_FAILED, - _("Invalid %s image format specified " - "in configuration file"), - styleFormat); - } else { - if (use_raw_on_fail) - VIR_WARN("Compression program for %s image format in " - "configuration file isn't available, using raw", - styleFormat); - else - virReportError(VIR_ERR_OPERATION_FAILED, - _("Compression program for %s image format " - "in configuration file isn't available"), - styleFormat); - } - - /* Use "raw" as the format if the specified format is not valid, - * or the compress program is not available. */ - if (use_raw_on_fail) - return QEMU_SAVE_FORMAT_RAW; - - return -1; -} - - static int qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml, unsigned int flags) @@ -3328,9 +2925,9 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *pat= h, const char *dxml, VIR_DOMAIN_SAVE_PAUSED, -1); cfg =3D virQEMUDriverGetConfig(driver); - if ((compressed =3D qemuGetCompressionProgram(cfg->saveImageFormat, - &compressor, - "save", false)) < 0) + if ((compressed =3D qemuSaveImageGetCompressionProgram(cfg->saveImageF= ormat, + &compressor, + "save", false)) <= 0) goto cleanup; if (!(vm =3D qemuDomainObjFromDomain(dom))) @@ -3399,9 +2996,9 @@ qemuDomainManagedSave(virDomainPtr dom, unsigned int = flags) } cfg =3D virQEMUDriverGetConfig(driver); - if ((compressed =3D qemuGetCompressionProgram(cfg->saveImageFormat, - &compressor, - "save", false)) < 0) + if ((compressed =3D qemuSaveImageGetCompressionProgram(cfg->saveImageF= ormat, + &compressor, + "save", false)) <= 0) goto cleanup; if (!(name =3D qemuDomainManagedSavePath(driver, vm))) @@ -3613,9 +3210,9 @@ doCoreDump(virQEMUDriverPtr driver, * format in "save" and "dump". This path doesn't need the compression * program to exist and can ignore the return value - it only cares to * get the compressor */ - ignore_value(qemuGetCompressionProgram(cfg->dumpImageFormat, - &compressor, - "dump", true)); + ignore_value(qemuSaveImageGetCompressionProgram(cfg->dumpImageFormat, + &compressor, + "dump", true)); /* Create an empty file with appropriate ownership. */ if (dump_flags & VIR_DUMP_BYPASS_CACHE) { @@ -6420,354 +6017,6 @@ static int qemuNodeGetSecurityModel(virConnectPtr c= onn, } -/** - * qemuDomainSaveImageUpdateDef: - * @driver: qemu driver data - * @def: def of the domain from the save image - * @newxml: user provided replacement XML - * - * Returns the new domain definition in case @newxml is ABI compatible wit= h the - * guest. - */ -static virDomainDefPtr -qemuDomainSaveImageUpdateDef(virQEMUDriverPtr driver, - virDomainDefPtr def, - const char *newxml) -{ - virDomainDefPtr ret =3D NULL; - virDomainDefPtr newdef_migr =3D NULL; - virDomainDefPtr newdef =3D NULL; - - if (!(newdef =3D virDomainDefParseString(newxml, driver->xmlopt, NULL, - VIR_DOMAIN_DEF_PARSE_INACTIVE))) - goto cleanup; - - if (!(newdef_migr =3D qemuDomainDefCopy(driver, NULL, - newdef, - QEMU_DOMAIN_FORMAT_LIVE_FLAGS | - VIR_DOMAIN_XML_MIGRATABLE))) - goto cleanup; - - if (!virDomainDefCheckABIStability(def, newdef_migr, driver->xmlopt)) { - virErrorPtr save_err; - - virErrorPreserveLast(&save_err); - - /* Due to a bug in older version of external snapshot creation - * code, the XML saved in the save image was not a migratable - * XML. To ensure backwards compatibility with the change of the - * saved XML type, we need to check the ABI compatibility against - * the user provided XML if the check against the migratable XML - * fails. Snapshots created prior to v1.1.3 have this issue. */ - if (!virDomainDefCheckABIStability(def, newdef, driver->xmlopt)) { - virErrorRestore(&save_err); - goto cleanup; - } - virFreeError(save_err); - - /* use the user provided XML */ - ret =3D g_steal_pointer(&newdef); - } else { - ret =3D g_steal_pointer(&newdef_migr); - } - - cleanup: - virDomainDefFree(newdef); - virDomainDefFree(newdef_migr); - - return ret; -} - - -/** - * qemuDomainSaveImageOpen: - * @driver: qemu driver data - * @qemuCaps: pointer to qemuCaps if the domain is running or NULL - * @path: path of the save image - * @ret_def: returns domain definition created from the XML stored in the = image - * @ret_data: returns structure filled with data from the image header - * @bypass_cache: bypass cache when opening the file - * @wrapperFd: returns the file wrapper structure - * @open_write: open the file for writing (for updates) - * @unlink_corrupt: remove the image file if it is corrupted - * - * Returns the opened fd of the save image file and fills the appropriate = fields - * on success. On error returns -1 on most failures, -3 if corrupt image w= as - * unlinked (no error raised). - */ -static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4) -qemuDomainSaveImageOpen(virQEMUDriverPtr driver, - virQEMUCapsPtr qemuCaps, - const char *path, - virDomainDefPtr *ret_def, - virQEMUSaveDataPtr *ret_data, - bool bypass_cache, - virFileWrapperFdPtr *wrapperFd, - bool open_write, - bool unlink_corrupt) -{ - VIR_AUTOCLOSE fd =3D -1; - int ret =3D -1; - g_autoptr(virQEMUSaveData) data =3D NULL; - virQEMUSaveHeaderPtr header; - g_autoptr(virDomainDef) def =3D NULL; - int oflags =3D open_write ? O_RDWR : O_RDONLY; - size_t xml_len; - size_t cookie_len; - - if (bypass_cache) { - int directFlag =3D virFileDirectFdFlag(); - if (directFlag < 0) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("bypass cache unsupported by this system")); - return -1; - } - oflags |=3D directFlag; - } - - if ((fd =3D qemuDomainOpenFile(driver, NULL, path, oflags, NULL)) < 0) - return -1; - - if (bypass_cache && - !(*wrapperFd =3D virFileWrapperFdNew(&fd, path, - VIR_FILE_WRAPPER_BYPASS_CACHE))) - return -1; - - data =3D g_new0(virQEMUSaveData, 1); - - header =3D &data->header; - if (saferead(fd, header, sizeof(*header)) !=3D sizeof(*header)) { - if (unlink_corrupt) { - if (unlink(path) < 0) { - virReportSystemError(errno, - _("cannot remove corrupt file: %s"), - path); - return -1; - } else { - return -3; - } - } - - virReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("failed to read qemu header")); - return -1; - } - - if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) !=3D= 0) { - if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)= ) =3D=3D 0) { - if (unlink_corrupt) { - if (unlink(path) < 0) { - virReportSystemError(errno, - _("cannot remove corrupt file: %s= "), - path); - return -1; - } else { - return -3; - } - } - - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("save image is incomplete")); - return -1; - } - - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("image magic is incorrect")); - return -1; - } - - if (header->version > QEMU_SAVE_VERSION) { - /* convert endianness and try again */ - bswap_header(header); - } - - if (header->version > QEMU_SAVE_VERSION) { - virReportError(VIR_ERR_OPERATION_FAILED, - _("image version is not supported (%d > %d)"), - header->version, QEMU_SAVE_VERSION); - return -1; - } - - if (header->data_len <=3D 0) { - virReportError(VIR_ERR_OPERATION_FAILED, - _("invalid header data length: %d"), header->data_l= en); - return -1; - } - - if (header->cookieOffset) - xml_len =3D header->cookieOffset; - else - xml_len =3D header->data_len; - - cookie_len =3D header->data_len - xml_len; - - data->xml =3D g_new0(char, xml_len); - - if (saferead(fd, data->xml, xml_len) !=3D xml_len) { - virReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("failed to read domain XML")); - return -1; - } - - if (cookie_len > 0) { - data->cookie =3D g_new0(char, cookie_len); - - if (saferead(fd, data->cookie, cookie_len) !=3D cookie_len) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("failed to read cookie")); - return -1; - } - } - - /* Create a domain from this XML */ - if (!(def =3D virDomainDefParseString(data->xml, driver->xmlopt, qemuC= aps, - VIR_DOMAIN_DEF_PARSE_INACTIVE | - VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE= ))) - return -1; - - *ret_def =3D g_steal_pointer(&def); - *ret_data =3D g_steal_pointer(&data); - - ret =3D fd; - fd =3D -1; - - return ret; -} - -static int ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6) -qemuDomainSaveImageStartVM(virConnectPtr conn, - virQEMUDriverPtr driver, - virDomainObjPtr vm, - int *fd, - virQEMUSaveDataPtr data, - const char *path, - bool start_paused, - qemuDomainAsyncJob asyncJob) -{ - qemuDomainObjPrivatePtr priv =3D vm->privateData; - int ret =3D -1; - bool started =3D false; - virObjectEventPtr event; - VIR_AUTOCLOSE intermediatefd =3D -1; - g_autoptr(virCommand) cmd =3D NULL; - g_autofree char *errbuf =3D NULL; - g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); - virQEMUSaveHeaderPtr header =3D &data->header; - g_autoptr(qemuDomainSaveCookie) cookie =3D NULL; - int rc =3D 0; - - if (virSaveCookieParseString(data->cookie, (virObjectPtr *)&cookie, - virDomainXMLOptionGetSaveCookie(driver->x= mlopt)) < 0) - goto cleanup; - - if ((header->version =3D=3D 2) && - (header->compressed !=3D QEMU_SAVE_FORMAT_RAW)) { - if (!(cmd =3D qemuCompressGetCommand(header->compressed))) - goto cleanup; - - intermediatefd =3D *fd; - *fd =3D -1; - - virCommandSetInputFD(cmd, intermediatefd); - virCommandSetOutputFD(cmd, fd); - virCommandSetErrorBuffer(cmd, &errbuf); - virCommandDoAsyncIO(cmd); - - if (virCommandRunAsync(cmd, NULL) < 0) { - *fd =3D intermediatefd; - intermediatefd =3D -1; - goto cleanup; - } - } - - /* No cookie means libvirt which saved the domain was too old to mess = up - * the CPU definitions. - */ - if (cookie && - qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) - goto cleanup; - - if (cookie && !cookie->slirpHelper) - priv->disableSlirp =3D true; - - if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, - asyncJob, "stdio", *fd, path, NULL, - VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, - VIR_QEMU_PROCESS_START_PAUSED | - VIR_QEMU_PROCESS_START_GEN_VMID) =3D=3D 0) - started =3D true; - - if (intermediatefd !=3D -1) { - virErrorPtr orig_err =3D NULL; - - if (!started) { - /* if there was an error setting up qemu, the intermediate - * process will wait forever to write to stdout, so we - * must manually kill it and ignore any error related to - * the process - */ - virErrorPreserveLast(&orig_err); - VIR_FORCE_CLOSE(intermediatefd); - VIR_FORCE_CLOSE(*fd); - } - - rc =3D virCommandWait(cmd, NULL); - VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf)); - virErrorRestore(&orig_err); - } - if (VIR_CLOSE(*fd) < 0) { - virReportSystemError(errno, _("cannot close file: %s"), path); - rc =3D -1; - } - - virDomainAuditStart(vm, "restored", started); - if (!started || rc < 0) - goto cleanup; - - /* qemuProcessStart doesn't unset the qemu error reporting infrastruct= ure - * in case of migration (which is used in this case) so we need to res= et it - * so that the handle to virtlogd is not held open unnecessarily */ - qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); - - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - VIR_DOMAIN_EVENT_STARTED_RESTORED); - virObjectEventStateQueue(driver->domainEventState, event); - - - /* If it was running before, resume it now unless caller requested pau= se. */ - if (header->was_running && !start_paused) { - if (qemuProcessStartCPUs(driver, vm, - VIR_DOMAIN_RUNNING_RESTORED, - asyncJob) < 0) { - if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) - virReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("failed to resume domain")); - goto cleanup; - } - if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) { - VIR_WARN("Failed to save status on vm %s", vm->def->name); - goto cleanup; - } - } else { - int detail =3D (start_paused ? VIR_DOMAIN_EVENT_SUSPENDED_PAUSED : - VIR_DOMAIN_EVENT_SUSPENDED_RESTORED); - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - detail); - virObjectEventStateQueue(driver->domainEventState, event); - } - - ret =3D 0; - - cleanup: - if (ret < 0 && started) { - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, - asyncJob, VIR_QEMU_PROCESS_STOP_MIGRATED); - } - return ret; -} - static int qemuDomainRestoreFlags(virConnectPtr conn, const char *path, @@ -6793,9 +6042,9 @@ qemuDomainRestoreFlags(virConnectPtr conn, virNWFilterReadLockFilterUpdates(); - fd =3D qemuDomainSaveImageOpen(driver, NULL, path, &def, &data, - (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != =3D 0, - &wrapperFd, false, false); + fd =3D qemuSaveImageOpen(driver, NULL, path, &def, &data, + (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) !=3D 0, + &wrapperFd, false, false); if (fd < 0) goto cleanup; @@ -6822,7 +6071,7 @@ qemuDomainRestoreFlags(virConnectPtr conn, if (newxml) { virDomainDefPtr tmp; - if (!(tmp =3D qemuDomainSaveImageUpdateDef(driver, def, newxml))) + if (!(tmp =3D qemuSaveImageUpdateDef(driver, def, newxml))) goto cleanup; virDomainDefFree(def); @@ -6851,8 +6100,8 @@ qemuDomainRestoreFlags(virConnectPtr conn, flags) < 0) goto cleanup; - ret =3D qemuDomainSaveImageStartVM(conn, driver, vm, &fd, data, path, - false, QEMU_ASYNC_JOB_START); + ret =3D qemuSaveImageStartVM(conn, driver, vm, &fd, data, path, + false, QEMU_ASYNC_JOB_START); qemuProcessEndJob(driver, vm); @@ -6889,8 +6138,8 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, con= st char *path, virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); - fd =3D qemuDomainSaveImageOpen(driver, NULL, path, &def, &data, - false, NULL, false, false); + fd =3D qemuSaveImageOpen(driver, NULL, path, &def, &data, + false, NULL, false, false); if (fd < 0) goto cleanup; @@ -6927,8 +6176,8 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, cons= t char *path, else if (flags & VIR_DOMAIN_SAVE_PAUSED) state =3D 0; - fd =3D qemuDomainSaveImageOpen(driver, NULL, path, &def, &data, - false, NULL, true, false); + fd =3D qemuSaveImageOpen(driver, NULL, path, &def, &data, + false, NULL, true, false); if (fd < 0) goto cleanup; @@ -6946,7 +6195,7 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, cons= t char *path, if (state >=3D 0) data->header.was_running =3D state; - if (!(newdef =3D qemuDomainSaveImageUpdateDef(driver, def, dxml))) + if (!(newdef =3D qemuSaveImageUpdateDef(driver, def, dxml))) goto cleanup; VIR_FREE(data->xml); @@ -7011,8 +6260,8 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, uns= igned int flags) goto cleanup; } - if ((fd =3D qemuDomainSaveImageOpen(driver, priv->qemuCaps, path, &def= , &data, - false, NULL, false, false)) < 0) + if ((fd =3D qemuSaveImageOpen(driver, priv->qemuCaps, path, &def, &dat= a, + false, NULL, false, false)) < 0) goto cleanup; ret =3D qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags); @@ -7076,8 +6325,8 @@ qemuDomainObjRestore(virConnectPtr conn, virQEMUSaveDataPtr data =3D NULL; virFileWrapperFdPtr wrapperFd =3D NULL; - fd =3D qemuDomainSaveImageOpen(driver, NULL, path, &def, &data, - bypass_cache, &wrapperFd, false, true); + fd =3D qemuSaveImageOpen(driver, NULL, path, &def, &data, + bypass_cache, &wrapperFd, false, true); if (fd < 0) { if (fd =3D=3D -3) ret =3D 1; @@ -7098,7 +6347,7 @@ qemuDomainObjRestore(virConnectPtr conn, VIR_DEBUG("Using hook-filtered domain XML: %s", xmlout); - if (!(tmp =3D qemuDomainSaveImageUpdateDef(driver, def, xmlout= ))) + if (!(tmp =3D qemuSaveImageUpdateDef(driver, def, xmlout))) goto cleanup; virDomainDefFree(def); @@ -7124,8 +6373,8 @@ qemuDomainObjRestore(virConnectPtr conn, virDomainObjAssignDef(vm, def, true, NULL); def =3D NULL; - ret =3D qemuDomainSaveImageStartVM(conn, driver, vm, &fd, data, path, - start_paused, asyncJob); + ret =3D qemuSaveImageStartVM(conn, driver, vm, &fd, data, path, + start_paused, asyncJob); cleanup: virQEMUSaveDataFree(data); @@ -15333,9 +14582,9 @@ qemuDomainSnapshotCreateActiveExternal(virQEMUDrive= rPtr driver, JOB_MASK(QEMU_JOB_SUSPEND) | JOB_MASK(QEMU_JOB_MIGRATION_OP))= ); - if ((compressed =3D qemuGetCompressionProgram(cfg->snapshotImageFo= rmat, - &compressor, - "snapshot", false)) < = 0) + if ((compressed =3D qemuSaveImageGetCompressionProgram(cfg->snapsh= otImageFormat, + &compressor, + "snapshot", f= alse)) < 0) goto cleanup; if (!(xml =3D qemuDomainDefFormatLive(driver, priv->qemuCaps, @@ -15350,9 +14599,9 @@ qemuDomainSnapshotCreateActiveExternal(virQEMUDrive= rPtr driver, goto cleanup; xml =3D NULL; - if ((ret =3D qemuDomainSaveMemory(driver, vm, snapdef->file, data, - compressor, 0, - QEMU_ASYNC_JOB_SNAPSHOT)) < 0) + if ((ret =3D qemuSaveImageCreate(driver, vm, snapdef->file, data, + compressor, 0, + QEMU_ASYNC_JOB_SNAPSHOT)) < 0) goto cleanup; /* the memory image was created, remove it on errors */ diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c new file mode 100644 index 0000000000..52468056ad --- /dev/null +++ b/src/qemu/qemu_saveimage.c @@ -0,0 +1,764 @@ +/* + * qemu_saveimage.c: Infrastructure for saving qemu state to a file + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#include + +#include "qemu_saveimage.h" +#include "qemu_domain.h" +#include "qemu_migration.h" +#include "qemu_process.h" +#include "qemu_security.h" + +#include "domain_audit.h" + +#include "virerror.h" +#include "virlog.h" +#include "viralloc.h" +#include "virqemu.h" + +#include +#include +#include + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.qemu_saveimage"); + +typedef enum { + QEMU_SAVE_FORMAT_RAW =3D 0, + QEMU_SAVE_FORMAT_GZIP =3D 1, + QEMU_SAVE_FORMAT_BZIP2 =3D 2, + /* + * Deprecated by xz and never used as part of a release + * QEMU_SAVE_FORMAT_LZMA + */ + QEMU_SAVE_FORMAT_XZ =3D 3, + QEMU_SAVE_FORMAT_LZOP =3D 4, + /* Note: add new members only at the end. + These values are used in the on-disk format. + Do not change or re-use numbers. */ + + QEMU_SAVE_FORMAT_LAST +} virQEMUSaveFormat; + +VIR_ENUM_DECL(qemuSaveCompression); +VIR_ENUM_IMPL(qemuSaveCompression, + QEMU_SAVE_FORMAT_LAST, + "raw", + "gzip", + "bzip2", + "xz", + "lzop", +); + +static inline void +qemuSaveImageBswapHeader(virQEMUSaveHeaderPtr hdr) +{ + hdr->version =3D GUINT32_SWAP_LE_BE(hdr->version); + hdr->data_len =3D GUINT32_SWAP_LE_BE(hdr->data_len); + hdr->was_running =3D GUINT32_SWAP_LE_BE(hdr->was_running); + hdr->compressed =3D GUINT32_SWAP_LE_BE(hdr->compressed); + hdr->cookieOffset =3D GUINT32_SWAP_LE_BE(hdr->cookieOffset); +} + + +void +virQEMUSaveDataFree(virQEMUSaveDataPtr data) +{ + if (!data) + return; + + VIR_FREE(data->xml); + VIR_FREE(data->cookie); + VIR_FREE(data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virQEMUSaveData, virQEMUSaveDataFree); + +/** + * This function steals @domXML on success. + */ +virQEMUSaveDataPtr +virQEMUSaveDataNew(char *domXML, + qemuDomainSaveCookiePtr cookieObj, + bool running, + int compressed, + virDomainXMLOptionPtr xmlopt) +{ + virQEMUSaveDataPtr data =3D NULL; + virQEMUSaveHeaderPtr header; + + if (VIR_ALLOC(data) < 0) + return NULL; + + data->xml =3D g_steal_pointer(&domXML); + + if (cookieObj && + !(data->cookie =3D virSaveCookieFormat((virObjectPtr) cookieObj, + virDomainXMLOptionGetSaveCook= ie(xmlopt)))) + goto error; + + header =3D &data->header; + memcpy(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)); + header->version =3D QEMU_SAVE_VERSION; + header->was_running =3D running ? 1 : 0; + header->compressed =3D compressed; + + return data; + + error: + virQEMUSaveDataFree(data); + return NULL; +} + + +/* virQEMUSaveDataWrite: + * + * Writes libvirt's header (including domain XML) into a saved image of a + * running domain. If @header has data_len filled in (because it was previ= ously + * read from the file), the function will make sure the new data will fit + * within data_len. + * + * Returns -1 on failure, or 0 on success. + */ +int +virQEMUSaveDataWrite(virQEMUSaveDataPtr data, + int fd, + const char *path) +{ + virQEMUSaveHeaderPtr header =3D &data->header; + size_t len; + size_t xml_len; + size_t cookie_len =3D 0; + size_t zerosLen =3D 0; + g_autofree char *zeros =3D NULL; + + xml_len =3D strlen(data->xml) + 1; + if (data->cookie) + cookie_len =3D strlen(data->cookie) + 1; + + len =3D xml_len + cookie_len; + + if (header->data_len =3D=3D 0) { + /* This 64kb padding allows the user to edit the XML in + * a saved state image and have the new XML be larger + * that what was originally saved + */ + header->data_len =3D len + (64 * 1024); + } else { + if (len > header->data_len) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("new xml too large to fit in file")); + return -1; + } + } + + zerosLen =3D header->data_len - len; + zeros =3D g_new0(char, zerosLen); + + if (data->cookie) + header->cookieOffset =3D xml_len; + + if (safewrite(fd, header, sizeof(*header)) !=3D sizeof(*header)) { + virReportSystemError(errno, + _("failed to write header to domain save file= '%s'"), + path); + return -1; + } + + if (safewrite(fd, data->xml, xml_len) !=3D xml_len) { + virReportSystemError(errno, + _("failed to write domain xml to '%s'"), + path); + return -1; + } + + if (data->cookie && + safewrite(fd, data->cookie, cookie_len) !=3D cookie_len) { + virReportSystemError(errno, + _("failed to write cookie to '%s'"), + path); + return -1; + } + + if (safewrite(fd, zeros, zerosLen) !=3D zerosLen) { + virReportSystemError(errno, + _("failed to write padding to '%s'"), + path); + return -1; + } + + return 0; +} + + +static int +virQEMUSaveDataFinish(virQEMUSaveDataPtr data, + int *fd, + const char *path) +{ + virQEMUSaveHeaderPtr header =3D &data->header; + + memcpy(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)); + + if (safewrite(*fd, header, sizeof(*header)) !=3D sizeof(*header) || + VIR_CLOSE(*fd) < 0) { + virReportSystemError(errno, + _("failed to write header to domain save file= '%s'"), + path); + return -1; + } + + return 0; +} + + +static virCommandPtr +qemuSaveImageGetCompressionCommand(virQEMUSaveFormat compression) +{ + virCommandPtr ret =3D NULL; + const char *prog =3D qemuSaveCompressionTypeToString(compression); + + if (!prog) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Invalid compressed save format %d"), + compression); + return NULL; + } + + ret =3D virCommandNew(prog); + virCommandAddArg(ret, "-dc"); + + if (compression =3D=3D QEMU_SAVE_FORMAT_LZOP) + virCommandAddArg(ret, "--ignore-warn"); + + return ret; +} + + +/* Helper function to execute a migration to file with a correct save head= er + * the caller needs to make sure that the processors are stopped and do al= l other + * actions besides saving memory */ +int +qemuSaveImageCreate(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + virQEMUSaveDataPtr data, + virCommandPtr compressor, + unsigned int flags, + qemuDomainAsyncJob asyncJob) +{ + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + bool needUnlink =3D false; + int ret =3D -1; + int fd =3D -1; + int directFlag =3D 0; + virFileWrapperFdPtr wrapperFd =3D NULL; + unsigned int wrapperFlags =3D VIR_FILE_WRAPPER_NON_BLOCKING; + + /* Obtain the file handle. */ + if ((flags & VIR_DOMAIN_SAVE_BYPASS_CACHE)) { + wrapperFlags |=3D VIR_FILE_WRAPPER_BYPASS_CACHE; + directFlag =3D virFileDirectFdFlag(); + if (directFlag < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("bypass cache unsupported by this system")); + goto cleanup; + } + } + + fd =3D virQEMUFileOpenAs(cfg->user, cfg->group, false, path, + O_WRONLY | O_TRUNC | O_CREAT | directFlag, + &needUnlink); + if (fd < 0) + goto cleanup; + + if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, fd) = < 0) + goto cleanup; + + if (!(wrapperFd =3D virFileWrapperFdNew(&fd, path, wrapperFlags))) + goto cleanup; + + if (virQEMUSaveDataWrite(data, fd, path) < 0) + goto cleanup; + + /* Perform the migration */ + if (qemuMigrationSrcToFile(driver, vm, fd, compressor, asyncJob) < 0) + goto cleanup; + + /* Touch up file header to mark image complete. */ + + /* Reopen the file to touch up the header, since we aren't set + * up to seek backwards on wrapperFd. The reopened fd will + * trigger a single page of file system cache pollution, but + * that's acceptable. */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, _("unable to close %s"), path); + goto cleanup; + } + + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) + goto cleanup; + + if ((fd =3D qemuDomainOpenFile(driver, vm, path, O_WRONLY, NULL)) < 0 = || + virQEMUSaveDataFinish(data, &fd, path) < 0) + goto cleanup; + + ret =3D 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + if (qemuDomainFileWrapperFDClose(vm, wrapperFd) < 0) + ret =3D -1; + virFileWrapperFdFree(wrapperFd); + + if (ret < 0 && needUnlink) + unlink(path); + + return ret; +} + + +/* qemuSaveImageGetCompressionProgram: + * @imageFormat: String representation from qemu.conf for the compression + * image format being used (dump, save, or snapshot). + * @compresspath: Pointer to a character string to store the fully qualifi= ed + * path from virFindFileInPath. + * @styleFormat: String representing the style of format (dump, save, snap= shot) + * @use_raw_on_fail: Boolean indicating how to handle the error path. For + * callers that are OK with invalid data or inability to + * find the compression program, just return a raw format + * and let the path remain as NULL. + * + * Returns: + * virQEMUSaveFormat - Integer representation of the compression + * program to be used for particular style + * (e.g. dump, save, or snapshot). + * QEMU_SAVE_FORMAT_RAW - If there is no qemu.conf imageFormat value or + * no there was an error, then just return RAW + * indicating none. + */ +int +qemuSaveImageGetCompressionProgram(const char *imageFormat, + virCommandPtr *compressor, + const char *styleFormat, + bool use_raw_on_fail) +{ + int ret; + const char *prog; + + *compressor =3D NULL; + + if (!imageFormat) + return QEMU_SAVE_FORMAT_RAW; + + if ((ret =3D qemuSaveCompressionTypeFromString(imageFormat)) < 0) + goto error; + + if (ret =3D=3D QEMU_SAVE_FORMAT_RAW) + return QEMU_SAVE_FORMAT_RAW; + + if (!(prog =3D virFindFileInPath(imageFormat))) + goto error; + + *compressor =3D virCommandNew(prog); + virCommandAddArg(*compressor, "-c"); + if (ret =3D=3D QEMU_SAVE_FORMAT_XZ) + virCommandAddArg(*compressor, "-3"); + + return ret; + + error: + if (ret < 0) { + if (use_raw_on_fail) + VIR_WARN("Invalid %s image format specified in " + "configuration file, using raw", + styleFormat); + else + virReportError(VIR_ERR_OPERATION_FAILED, + _("Invalid %s image format specified " + "in configuration file"), + styleFormat); + } else { + if (use_raw_on_fail) + VIR_WARN("Compression program for %s image format in " + "configuration file isn't available, using raw", + styleFormat); + else + virReportError(VIR_ERR_OPERATION_FAILED, + _("Compression program for %s image format " + "in configuration file isn't available"), + styleFormat); + } + + /* Use "raw" as the format if the specified format is not valid, + * or the compress program is not available. */ + if (use_raw_on_fail) + return QEMU_SAVE_FORMAT_RAW; + + return -1; +} + + +/** + * qemuSaveImageOpen: + * @driver: qemu driver data + * @qemuCaps: pointer to qemuCaps if the domain is running or NULL + * @path: path of the save image + * @ret_def: returns domain definition created from the XML stored in the = image + * @ret_data: returns structure filled with data from the image header + * @bypass_cache: bypass cache when opening the file + * @wrapperFd: returns the file wrapper structure + * @open_write: open the file for writing (for updates) + * @unlink_corrupt: remove the image file if it is corrupted + * + * Returns the opened fd of the save image file and fills the appropriate = fields + * on success. On error returns -1 on most failures, -3 if corrupt image w= as + * unlinked (no error raised). + */ +int +qemuSaveImageOpen(virQEMUDriverPtr driver, + virQEMUCapsPtr qemuCaps, + const char *path, + virDomainDefPtr *ret_def, + virQEMUSaveDataPtr *ret_data, + bool bypass_cache, + virFileWrapperFdPtr *wrapperFd, + bool open_write, + bool unlink_corrupt) +{ + VIR_AUTOCLOSE fd =3D -1; + int ret =3D -1; + g_autoptr(virQEMUSaveData) data =3D NULL; + virQEMUSaveHeaderPtr header; + g_autoptr(virDomainDef) def =3D NULL; + int oflags =3D open_write ? O_RDWR : O_RDONLY; + size_t xml_len; + size_t cookie_len; + + if (bypass_cache) { + int directFlag =3D virFileDirectFdFlag(); + if (directFlag < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("bypass cache unsupported by this system")); + return -1; + } + oflags |=3D directFlag; + } + + if ((fd =3D qemuDomainOpenFile(driver, NULL, path, oflags, NULL)) < 0) + return -1; + + if (bypass_cache && + !(*wrapperFd =3D virFileWrapperFdNew(&fd, path, + VIR_FILE_WRAPPER_BYPASS_CACHE))) + return -1; + + data =3D g_new0(virQEMUSaveData, 1); + + header =3D &data->header; + if (saferead(fd, header, sizeof(*header)) !=3D sizeof(*header)) { + if (unlink_corrupt) { + if (unlink(path) < 0) { + virReportSystemError(errno, + _("cannot remove corrupt file: %s"), + path); + return -1; + } else { + return -3; + } + } + + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("failed to read qemu header")); + return -1; + } + + if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) !=3D= 0) { + if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)= ) =3D=3D 0) { + if (unlink_corrupt) { + if (unlink(path) < 0) { + virReportSystemError(errno, + _("cannot remove corrupt file: %s= "), + path); + return -1; + } else { + return -3; + } + } + + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("save image is incomplete")); + return -1; + } + + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("image magic is incorrect")); + return -1; + } + + if (header->version > QEMU_SAVE_VERSION) { + /* convert endianness and try again */ + qemuSaveImageBswapHeader(header); + } + + if (header->version > QEMU_SAVE_VERSION) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("image version is not supported (%d > %d)"), + header->version, QEMU_SAVE_VERSION); + return -1; + } + + if (header->data_len <=3D 0) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("invalid header data length: %d"), header->data_l= en); + return -1; + } + + if (header->cookieOffset) + xml_len =3D header->cookieOffset; + else + xml_len =3D header->data_len; + + cookie_len =3D header->data_len - xml_len; + + data->xml =3D g_new0(char, xml_len); + + if (saferead(fd, data->xml, xml_len) !=3D xml_len) { + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("failed to read domain XML")); + return -1; + } + + if (cookie_len > 0) { + data->cookie =3D g_new0(char, cookie_len); + + if (saferead(fd, data->cookie, cookie_len) !=3D cookie_len) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("failed to read cookie")); + return -1; + } + } + + /* Create a domain from this XML */ + if (!(def =3D virDomainDefParseString(data->xml, driver->xmlopt, qemuC= aps, + VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE= ))) + return -1; + + *ret_def =3D g_steal_pointer(&def); + *ret_data =3D g_steal_pointer(&data); + + ret =3D fd; + fd =3D -1; + + return ret; +} + +int +qemuSaveImageStartVM(virConnectPtr conn, + virQEMUDriverPtr driver, + virDomainObjPtr vm, + int *fd, + virQEMUSaveDataPtr data, + const char *path, + bool start_paused, + qemuDomainAsyncJob asyncJob) +{ + qemuDomainObjPrivatePtr priv =3D vm->privateData; + int ret =3D -1; + bool started =3D false; + virObjectEventPtr event; + VIR_AUTOCLOSE intermediatefd =3D -1; + g_autoptr(virCommand) cmd =3D NULL; + g_autofree char *errbuf =3D NULL; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + virQEMUSaveHeaderPtr header =3D &data->header; + g_autoptr(qemuDomainSaveCookie) cookie =3D NULL; + int rc =3D 0; + + if (virSaveCookieParseString(data->cookie, (virObjectPtr *)&cookie, + virDomainXMLOptionGetSaveCookie(driver->x= mlopt)) < 0) + goto cleanup; + + if ((header->version =3D=3D 2) && + (header->compressed !=3D QEMU_SAVE_FORMAT_RAW)) { + if (!(cmd =3D qemuSaveImageGetCompressionCommand(header->compresse= d))) + goto cleanup; + + intermediatefd =3D *fd; + *fd =3D -1; + + virCommandSetInputFD(cmd, intermediatefd); + virCommandSetOutputFD(cmd, fd); + virCommandSetErrorBuffer(cmd, &errbuf); + virCommandDoAsyncIO(cmd); + + if (virCommandRunAsync(cmd, NULL) < 0) { + *fd =3D intermediatefd; + intermediatefd =3D -1; + goto cleanup; + } + } + + /* No cookie means libvirt which saved the domain was too old to mess = up + * the CPU definitions. + */ + if (cookie && + qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) + goto cleanup; + + if (cookie && !cookie->slirpHelper) + priv->disableSlirp =3D true; + + if (qemuProcessStart(conn, driver, vm, cookie ? cookie->cpu : NULL, + asyncJob, "stdio", *fd, path, NULL, + VIR_NETDEV_VPORT_PROFILE_OP_RESTORE, + VIR_QEMU_PROCESS_START_PAUSED | + VIR_QEMU_PROCESS_START_GEN_VMID) =3D=3D 0) + started =3D true; + + if (intermediatefd !=3D -1) { + virErrorPtr orig_err =3D NULL; + + if (!started) { + /* if there was an error setting up qemu, the intermediate + * process will wait forever to write to stdout, so we + * must manually kill it and ignore any error related to + * the process + */ + virErrorPreserveLast(&orig_err); + VIR_FORCE_CLOSE(intermediatefd); + VIR_FORCE_CLOSE(*fd); + } + + rc =3D virCommandWait(cmd, NULL); + VIR_DEBUG("Decompression binary stderr: %s", NULLSTR(errbuf)); + virErrorRestore(&orig_err); + } + if (VIR_CLOSE(*fd) < 0) { + virReportSystemError(errno, _("cannot close file: %s"), path); + rc =3D -1; + } + + virDomainAuditStart(vm, "restored", started); + if (!started || rc < 0) + goto cleanup; + + /* qemuProcessStart doesn't unset the qemu error reporting infrastruct= ure + * in case of migration (which is used in this case) so we need to res= et it + * so that the handle to virtlogd is not held open unnecessarily */ + qemuMonitorSetDomainLog(qemuDomainGetMonitor(vm), NULL, NULL, NULL); + + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_RESTORED); + virObjectEventStateQueue(driver->domainEventState, event); + + + /* If it was running before, resume it now unless caller requested pau= se. */ + if (header->was_running && !start_paused) { + if (qemuProcessStartCPUs(driver, vm, + VIR_DOMAIN_RUNNING_RESTORED, + asyncJob) < 0) { + if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("failed to resume domain")); + goto cleanup; + } + if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) { + VIR_WARN("Failed to save status on vm %s", vm->def->name); + goto cleanup; + } + } else { + int detail =3D (start_paused ? VIR_DOMAIN_EVENT_SUSPENDED_PAUSED : + VIR_DOMAIN_EVENT_SUSPENDED_RESTORED); + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + virObjectEventStateQueue(driver->domainEventState, event); + } + + ret =3D 0; + + cleanup: + if (ret < 0 && started) { + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, + asyncJob, VIR_QEMU_PROCESS_STOP_MIGRATED); + } + return ret; +} + + +/** + * qemuSaveImageUpdateDef: + * @driver: qemu driver data + * @def: def of the domain from the save image + * @newxml: user provided replacement XML + * + * Returns the new domain definition in case @newxml is ABI compatible wit= h the + * guest. + */ +virDomainDefPtr +qemuSaveImageUpdateDef(virQEMUDriverPtr driver, + virDomainDefPtr def, + const char *newxml) +{ + virDomainDefPtr ret =3D NULL; + virDomainDefPtr newdef_migr =3D NULL; + virDomainDefPtr newdef =3D NULL; + + if (!(newdef =3D virDomainDefParseString(newxml, driver->xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) + goto cleanup; + + if (!(newdef_migr =3D qemuDomainDefCopy(driver, NULL, + newdef, + QEMU_DOMAIN_FORMAT_LIVE_FLAGS | + VIR_DOMAIN_XML_MIGRATABLE))) + goto cleanup; + + if (!virDomainDefCheckABIStability(def, newdef_migr, driver->xmlopt)) { + virErrorPtr save_err; + + virErrorPreserveLast(&save_err); + + /* Due to a bug in older version of external snapshot creation + * code, the XML saved in the save image was not a migratable + * XML. To ensure backwards compatibility with the change of the + * saved XML type, we need to check the ABI compatibility against + * the user provided XML if the check against the migratable XML + * fails. Snapshots created prior to v1.1.3 have this issue. */ + if (!virDomainDefCheckABIStability(def, newdef, driver->xmlopt)) { + virErrorRestore(&save_err); + goto cleanup; + } + virFreeError(save_err); + + /* use the user provided XML */ + ret =3D g_steal_pointer(&newdef); + } else { + ret =3D g_steal_pointer(&newdef_migr); + } + + cleanup: + virDomainDefFree(newdef); + virDomainDefFree(newdef_migr); + + return ret; +} diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h new file mode 100644 index 0000000000..f9fecbcc46 --- /dev/null +++ b/src/qemu/qemu_saveimage.h @@ -0,0 +1,116 @@ +/* + * qemu_saveimage.h: Infrastructure for saving qemu state to a file + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#pragma once + +#include "virconftypes.h" +#include "datatypes.h" + +#include "qemu_conf.h" +#include "qemu_domainjob.h" +#include "qemu_domain.h" + +/* It would be nice to replace 'Qemud' with 'Qemu' but + * this magic string is ABI, so it can't be changed + */ +#define QEMU_SAVE_MAGIC "LibvirtQemudSave" +#define QEMU_SAVE_PARTIAL "LibvirtQemudPart" +#define QEMU_SAVE_VERSION 2 + +G_STATIC_ASSERT(sizeof(QEMU_SAVE_MAGIC) =3D=3D sizeof(QEMU_SAVE_PARTIAL)); + +typedef struct _virQEMUSaveHeader virQEMUSaveHeader; +typedef virQEMUSaveHeader *virQEMUSaveHeaderPtr; +struct _virQEMUSaveHeader { + char magic[sizeof(QEMU_SAVE_MAGIC)-1]; + uint32_t version; + uint32_t data_len; + uint32_t was_running; + uint32_t compressed; + uint32_t cookieOffset; + uint32_t unused[14]; +}; + + +typedef struct _virQEMUSaveData virQEMUSaveData; +typedef virQEMUSaveData *virQEMUSaveDataPtr; +struct _virQEMUSaveData { + virQEMUSaveHeader header; + char *xml; + char *cookie; +}; + + +virDomainDefPtr +qemuSaveImageUpdateDef(virQEMUDriverPtr driver, + virDomainDefPtr def, + const char *newxml); + +int +qemuSaveImageStartVM(virConnectPtr conn, + virQEMUDriverPtr driver, + virDomainObjPtr vm, + int *fd, + virQEMUSaveDataPtr data, + const char *path, + bool start_paused, + qemuDomainAsyncJob asyncJob) + ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6); + +int +qemuSaveImageOpen(virQEMUDriverPtr driver, + virQEMUCapsPtr qemuCaps, + const char *path, + virDomainDefPtr *ret_def, + virQEMUSaveDataPtr *ret_data, + bool bypass_cache, + virFileWrapperFdPtr *wrapperFd, + bool open_write, + bool unlink_corrupt) + ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); + +int +qemuSaveImageGetCompressionProgram(const char *imageFormat, + virCommandPtr *compressor, + const char *styleFormat, + bool use_raw_on_fail) + ATTRIBUTE_NONNULL(2); + +int +qemuSaveImageCreate(virQEMUDriverPtr driver, + virDomainObjPtr vm, + const char *path, + virQEMUSaveDataPtr data, + virCommandPtr compressor, + unsigned int flags, + qemuDomainAsyncJob asyncJob); + +int +virQEMUSaveDataWrite(virQEMUSaveDataPtr data, + int fd, + const char *path); + +virQEMUSaveDataPtr +virQEMUSaveDataNew(char *domXML, + qemuDomainSaveCookiePtr cookieObj, + bool running, + int compressed, + virDomainXMLOptionPtr xmlopt); + +void +virQEMUSaveDataFree(virQEMUSaveDataPtr data); --=20 2.26.2 From nobody Fri May 3 10:12:47 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 207.211.31.81 as permitted sender) client-ip=207.211.31.81; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-1.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.81 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1596707753; cv=none; d=zohomail.com; s=zohoarc; b=TgS3KXxhI0AxJ+oUhJT53yQd7LXuqY825IL8x376xho3/9NmJw1WVYtBIgdQbTK/7ijyeWNIIKw8c9SttBI1auS5yUN9o2Sv94FSbm+wr78nqxV1SqTIgRdLO42ECiXyDQR0UOKbZCg6pwQQ4gmPITL8VMsecXcska9m1cBjH9A= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1596707753; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=/8WGT/w6vfoO7vtNwr8iovkP7woEavDVkCNTfVjI7tI=; b=JZgz9WAtbj+LsYmDBOq1NiI/Lt0a/h9uQ4lniyXbyAAadDAoT59nqBCHMJJwlM0MFalgP6H8NL1Zt3N7wdLeE1OEyp4mn7WFiH6MGui64q7w3/y60VIiD8V2kKsiLJ4i+f7HXy0KsXIimCD43wVNXCb8Dq/a2v8DVyALRpMP2Tk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 207.211.31.81 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-delivery-1.mimecast.com (us-smtp-1.mimecast.com [207.211.31.81]) by mx.zohomail.com with SMTPS id 15967077529791009.6199364883569; Thu, 6 Aug 2020 02:55:52 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-271-H7roPLTfMJemAJ3bPsZQ6Q-1; Thu, 06 Aug 2020 05:55:48 -0400 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 91504800473; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6BD6E1755E; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 396CF1809547; Thu, 6 Aug 2020 09:55:43 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 0769tTDN024715 for ; Thu, 6 Aug 2020 05:55:29 -0400 Received: by smtp.corp.redhat.com (Postfix) id 24D296842F; Thu, 6 Aug 2020 09:55:29 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.38]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0280360BF3 for ; Thu, 6 Aug 2020 09:55:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1596707751; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=/8WGT/w6vfoO7vtNwr8iovkP7woEavDVkCNTfVjI7tI=; b=ad5IgHL5PRlsEApb3XQYNwuWmEMBi99VMOMeFEfFM03vzcLhglO3xzsvNmwOj444RgaT3l oZwRU6ilCsRmB9+h4A5sjHGKXL+w+UX+TIILEbF11qfe867uo5tdkKmMtCdfjuysz0EMRi f85RIYusXTRiU2KYe5DLho7QpEtOAZU= X-MC-Unique: H7roPLTfMJemAJ3bPsZQ6Q-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 5/5] qemu: Extract snapshot related code to a separate file Date: Thu, 6 Aug 2020 11:55:16 +0200 Message-Id: <734beeaff98b010ab5ffceb5ad185a7ef8d8aada.1596707671.git.pkrempa@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" We've dumped all the snapshot helpers and related code into qemu_driver.c. It accounted for ~10% of overal size of qemu_driver.c. Separate the code to qemu_snapshot.c/h. Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- po/POTFILES.in | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_driver.c | 2487 +++----------------------------------- src/qemu/qemu_snapshot.c | 2266 ++++++++++++++++++++++++++++++++++ src/qemu/qemu_snapshot.h | 55 + 5 files changed, 2475 insertions(+), 2335 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 6f47371b01..3d6c20c55f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -172,6 +172,7 @@ @SRCDIR@src/qemu/qemu_qapi.c @SRCDIR@src/qemu/qemu_saveimage.c @SRCDIR@src/qemu/qemu_slirp.c +@SRCDIR@src/qemu/qemu_snapshot.c @SRCDIR@src/qemu/qemu_tpm.c @SRCDIR@src/qemu/qemu_validate.c @SRCDIR@src/qemu/qemu_vhost_user.c diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 7d5249978a..85d020465f 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -31,6 +31,7 @@ qemu_driver_sources =3D [ 'qemu_qapi.c', 'qemu_saveimage.c', 'qemu_security.c', + 'qemu_snapshot.c', 'qemu_slirp.c', 'qemu_tpm.c', 'qemu_validate.c', diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a6b8c79168..bc2879dee4 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -52,6 +52,7 @@ #include "qemu_backup.h" #include "qemu_namespace.h" #include "qemu_saveimage.h" +#include "qemu_snapshot.h" #include "virerror.h" #include "virlog.h" @@ -171,29 +172,6 @@ qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot) } -/* Looks up snapshot object from VM and name */ -static virDomainMomentObjPtr -qemuSnapObjFromName(virDomainObjPtr vm, - const char *name) -{ - virDomainMomentObjPtr snap =3D NULL; - snap =3D 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 virDomainMomentObjPtr -qemuSnapObjFromSnapshot(virDomainObjPtr vm, - virDomainSnapshotPtr snapshot) -{ - return qemuSnapObjFromName(vm, snapshot->name); -} static int @@ -2218,20 +2196,6 @@ qemuDomainReset(virDomainPtr dom, unsigned int flags) } -/* Count how many snapshots in a set are external snapshots. */ -static int -qemuDomainSnapshotCountExternal(void *payload, - const void *name G_GNUC_UNUSED, - void *data) -{ - virDomainMomentObjPtr snap =3D payload; - int *count =3D data; - - if (virDomainSnapshotIsExternal(snap)) - (*count)++; - return 0; -} - static int qemuDomainDestroyFlags(virDomainPtr dom, unsigned int flags) @@ -13353,1820 +13317,230 @@ qemuDomainMigrateStartPostCopy(virDomainPtr do= m, } -/* Return -1 if request is not sent to agent due to misconfig, -2 if reque= st - * is sent but failed, and number of frozen filesystems on success. If -2 = is - * returned, FSThaw should be called revert the quiesced status. */ -static int -qemuDomainSnapshotFSFreeze(virDomainObjPtr vm, - const char **mountpoints, - unsigned int nmountpoints) +static virDomainSnapshotPtr +qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) { - qemuAgentPtr agent; - int frozen; + virDomainObjPtr vm =3D NULL; + virDomainSnapshotPtr snapshot =3D NULL; - if (!qemuDomainAgentAvailable(vm, true)) - return -1; + if (!(vm =3D qemuDomainObjFromDomain(domain))) + goto cleanup; - agent =3D qemuDomainObjEnterAgent(vm); - frozen =3D qemuAgentFSFreeze(agent, mountpoints, nmountpoints); - qemuDomainObjExitAgent(vm, agent); - return frozen < 0 ? -2 : frozen; + if (virDomainSnapshotCreateXMLEnsureACL(domain->conn, vm->def, flags) = < 0) + goto cleanup; + + snapshot =3D qemuSnapshotCreateXML(domain, vm, xmlDesc, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return snapshot; } -/* Return -1 on error, otherwise number of thawed filesystems. */ static int -qemuDomainSnapshotFSThaw(virDomainObjPtr vm, - bool report) +qemuDomainSnapshotListNames(virDomainPtr domain, + char **names, + int nameslen, + unsigned int flags) { - qemuAgentPtr agent; - int thawed; - virErrorPtr err =3D NULL; + virDomainObjPtr vm =3D NULL; + int n =3D -1; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - if (!qemuDomainAgentAvailable(vm, report)) + if (!(vm =3D qemuDomainObjFromDomain(domain))) return -1; - agent =3D qemuDomainObjEnterAgent(vm); - if (!report) - virErrorPreserveLast(&err); - thawed =3D qemuAgentFSThaw(agent); - qemuDomainObjExitAgent(vm, agent); + if (virDomainSnapshotListNamesEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; - virErrorRestore(&err); + n =3D virDomainSnapshotObjListGetNames(vm->snapshots, NULL, names, nam= eslen, + flags); - return thawed; + cleanup: + virDomainObjEndAPI(&vm); + return n; } -/* The domain is expected to be locked and inactive. */ static int -qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap) +qemuDomainSnapshotNum(virDomainPtr domain, + unsigned int flags) { - return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false); -} - + virDomainObjPtr vm =3D NULL; + int n =3D -1; -/* The domain is expected to be locked and inactive. */ -static int -qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap, - bool reuse) -{ - size_t i; - virDomainSnapshotDiskDefPtr snapdisk; - virDomainDiskDefPtr defdisk; - virCommandPtr cmd =3D NULL; - const char *qemuImgPath; - virBitmapPtr created =3D NULL; - g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); - int ret =3D -1; - g_auto(virBuffer) buf =3D VIR_BUFFER_INITIALIZER; - virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - if (!(qemuImgPath =3D qemuFindQemuImgBinary(driver))) - goto cleanup; + if (!(vm =3D qemuDomainObjFromDomain(domain))) + return -1; - if (!(created =3D virBitmapNew(snapdef->ndisks))) + if (virDomainSnapshotNumEnsureACL(domain->conn, vm->def) < 0) goto cleanup; - /* 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 =3D 0; i < snapdef->ndisks && !reuse; i++) { - snapdisk =3D &(snapdef->disks[i]); - defdisk =3D snapdef->parent.dom->disks[snapdisk->idx]; - if (snapdisk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) - continue; - - if (!snapdisk->src->format) - snapdisk->src->format =3D VIR_STORAGE_FILE_QCOW2; - - if (qemuDomainStorageSourceValidateDepth(defdisk->src, 1, defdisk-= >dst) < 0) - goto cleanup; + n =3D virDomainSnapshotObjListNum(vm->snapshots, NULL, flags); - /* creates cmd line args: qemu-img create -f qcow2 -o */ - if (!(cmd =3D virCommandNewArgList(qemuImgPath, - "create", - "-f", - virStorageFileFormatTypeToString(= snapdisk->src->format), - "-o", - NULL))) - goto cleanup; + cleanup: + virDomainObjEndAPI(&vm); + return n; +} - /* adds cmd line arg: backing_fmt=3Dformat,backing_file=3D/path/to= /backing/file */ - virBufferAsprintf(&buf, "backing_fmt=3D%s,backing_file=3D", - virStorageFileFormatTypeToString(defdisk->src->f= ormat)); - virQEMUBuildBufferEscapeComma(&buf, defdisk->src->path); - virCommandAddArgBuffer(cmd, &buf); - /* adds cmd line args: /path/to/target/file */ - virQEMUBuildBufferEscapeComma(&buf, snapdisk->src->path); - virCommandAddArgBuffer(cmd, &buf); +static int +qemuDomainListAllSnapshots(virDomainPtr domain, + virDomainSnapshotPtr **snaps, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + int n =3D -1; - /* If the target does not exist, we're going to create it possibly= */ - if (!virFileExists(snapdisk->src->path)) - ignore_value(virBitmapSetBit(created, i)); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; + if (!(vm =3D qemuDomainObjFromDomain(domain))) + return -1; - virCommandFree(cmd); - cmd =3D NULL; - } + if (virDomainListAllSnapshotsEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; - /* update disk definitions */ - for (i =3D 0; i < snapdef->ndisks; i++) { - g_autoptr(virStorageSource) newsrc =3D NULL; + n =3D virDomainListSnapshots(vm->snapshots, NULL, domain, snaps, flags= ); - snapdisk =3D &(snapdef->disks[i]); - defdisk =3D vm->def->disks[snapdisk->idx]; + cleanup: + virDomainObjEndAPI(&vm); + return n; +} - if (snapdisk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) - continue; - if (!(newsrc =3D virStorageSourceCopy(snapdisk->src, false))) - goto cleanup; +static int +qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot, + char **names, + int nameslen, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + virDomainMomentObjPtr snap =3D NULL; + int n =3D -1; - if (virStorageSourceInitChainElement(newsrc, defdisk->src, false) = < 0) - goto cleanup; + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - if (!reuse && - virStorageSourceHasBacking(defdisk->src)) { - defdisk->src->readonly =3D true; - newsrc->backingStore =3D g_steal_pointer(&defdisk->src); - } else { - virObjectUnref(defdisk->src); - } + if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) + return -1; - defdisk->src =3D g_steal_pointer(&newsrc); - } + if (virDomainSnapshotListChildrenNamesEnsureACL(snapshot->domain->conn= , vm->def) < 0) + goto cleanup; - if (virDomainDefSave(vm->def, driver->xmlopt, cfg->configDir) < 0) + if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) goto cleanup; - ret =3D 0; + n =3D virDomainSnapshotObjListGetNames(vm->snapshots, snap, names, nam= eslen, + flags); cleanup: - virCommandFree(cmd); - - /* unlink images if creation has failed */ - if (ret < 0 && created) { - ssize_t bit =3D -1; - while ((bit =3D virBitmapNextSetBit(created, bit)) >=3D 0) { - snapdisk =3D &(snapdef->disks[bit]); - if (unlink(snapdisk->src->path) < 0) - VIR_WARN("Failed to remove snapshot image '%s'", - snapdisk->src->path); - } - } - virBitmapFree(created); - - return ret; + virDomainObjEndAPI(&vm); + return n; } -/* The domain is expected to be locked and active. */ static int -qemuDomainSnapshotCreateActiveInternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap, - unsigned int flags) +qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot, + unsigned int flags) { - qemuDomainObjPrivatePtr priv =3D vm->privateData; - virObjectEventPtr event =3D NULL; - bool resume =3D false; - virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); - int ret =3D -1; - - if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0)) - goto cleanup; - - if (virDomainObjGetState(vm, NULL) =3D=3D 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_SNAPSHOT) < 0) - goto cleanup; + virDomainObjPtr vm =3D NULL; + virDomainMomentObjPtr snap =3D NULL; + int n =3D -1; - resume =3D true; - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto cleanup; - } - } + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - if (qemuDomainObjEnterMonitorAsync(driver, vm, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) { - resume =3D false; - goto cleanup; - } + if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) + return -1; - ret =3D qemuMonitorCreateSnapshot(priv->mon, snap->def->name); - if (qemuDomainObjExitMonitor(driver, vm) < 0) - ret =3D -1; - if (ret < 0) + if (virDomainSnapshotNumChildrenEnsureACL(snapshot->domain->conn, vm->= def) < 0) goto cleanup; - if (!(snapdef->cookie =3D (virObjectPtr) qemuDomainSaveCookieNew(vm))) + if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) goto cleanup; - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { - event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= TOPPED, - VIR_DOMAIN_EVENT_STOPPED_FROM_SNA= PSHOT); - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - resume =3D false; - } + n =3D virDomainSnapshotObjListNum(vm->snapshots, snap, flags); cleanup: - if (resume && virDomainObjIsActive(vm) && - qemuProcessStartCPUs(driver, vm, - VIR_DOMAIN_RUNNING_UNPAUSED, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) { - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_API_ER= ROR); - if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("resuming after snapshot failed")); - } - } - - virObjectEventStateQueue(driver->domainEventState, event); - - return ret; + virDomainObjEndAPI(&vm); + return n; } static int -qemuDomainSnapshotPrepareDiskShared(virDomainSnapshotDiskDefPtr snapdisk, - virDomainDiskDefPtr domdisk) +qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot, + virDomainSnapshotPtr **snaps, + unsigned int flags) { - if (!domdisk->src->shared || domdisk->src->readonly) - return 0; + virDomainObjPtr vm =3D NULL; + virDomainMomentObjPtr snap =3D NULL; + int n =3D -1; - if (!qemuBlockStorageSourceSupportsConcurrentAccess(snapdisk->src)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("shared access for disk '%s' requires use of " - "supported storage format"), domdisk->dst); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | + VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | + VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); + + if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) return -1; - } - return 0; + if (virDomainSnapshotListAllChildrenEnsureACL(snapshot->domain->conn, = vm->def) < 0) + goto cleanup; + + if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) + goto cleanup; + + n =3D virDomainListSnapshots(vm->snapshots, snap, snapshot->domain, sn= aps, + flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; } -static int -qemuDomainSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDefPtr = snapdisk, - virDomainDiskDefPtr domdisk) +static virDomainSnapshotPtr +qemuDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) { - int domDiskType =3D virStorageSourceGetActualType(domdisk->src); - int snapDiskType =3D virStorageSourceGetActualType(snapdisk->src); - - switch ((virStorageType)domDiskType) { - case VIR_STORAGE_TYPE_BLOCK: - case VIR_STORAGE_TYPE_FILE: - break; + virDomainObjPtr vm; + virDomainMomentObjPtr snap =3D NULL; + virDomainSnapshotPtr snapshot =3D NULL; - case VIR_STORAGE_TYPE_NETWORK: - switch ((virStorageNetProtocol) domdisk->src->protocol) { - case VIR_STORAGE_NET_PROTOCOL_NONE: - case VIR_STORAGE_NET_PROTOCOL_NBD: - case VIR_STORAGE_NET_PROTOCOL_RBD: - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - case VIR_STORAGE_NET_PROTOCOL_SSH: - case VIR_STORAGE_NET_PROTOCOL_VXHS: - case VIR_STORAGE_NET_PROTOCOL_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("external inactive snapshots are not supporte= d on " - "'network' disks using '%s' protocol"), - virStorageNetProtocolTypeToString(domdisk->src-= >protocol)); - return -1; - } - break; + virCheckFlags(0, NULL); - case VIR_STORAGE_TYPE_DIR: - case VIR_STORAGE_TYPE_VOLUME: - case VIR_STORAGE_TYPE_NVME: - case VIR_STORAGE_TYPE_NONE: - case VIR_STORAGE_TYPE_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("external inactive snapshots are not supported on= " - "'%s' disks"), virStorageTypeToString(domDiskType= )); - return -1; - } + if (!(vm =3D qemuDomainObjFromDomain(domain))) + return NULL; - switch ((virStorageType)snapDiskType) { - case VIR_STORAGE_TYPE_BLOCK: - case VIR_STORAGE_TYPE_FILE: - break; + if (virDomainSnapshotLookupByNameEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; - case VIR_STORAGE_TYPE_NETWORK: - case VIR_STORAGE_TYPE_DIR: - case VIR_STORAGE_TYPE_VOLUME: - case VIR_STORAGE_TYPE_NVME: - case VIR_STORAGE_TYPE_NONE: - case VIR_STORAGE_TYPE_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("external inactive snapshots are not supported on= " - "'%s' disks"), virStorageTypeToString(snapDiskTyp= e)); - return -1; - } + if (!(snap =3D qemuSnapObjFromName(vm, name))) + goto cleanup; - if (qemuDomainSnapshotPrepareDiskShared(snapdisk, domdisk) < 0) - return -1; + snapshot =3D virGetDomainSnapshot(domain, snap->def->name); - return 0; + cleanup: + virDomainObjEndAPI(&vm); + return snapshot; } static int -qemuDomainSnapshotPrepareDiskExternalActive(virDomainObjPtr vm, - virDomainSnapshotDiskDefPtr sn= apdisk, - virDomainDiskDefPtr domdisk, - bool blockdev) -{ - int actualType =3D virStorageSourceGetActualType(snapdisk->src); - - if (domdisk->device =3D=3D VIR_DOMAIN_DISK_DEVICE_LUN) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("external active snapshots are not supported on s= csi " - "passthrough devices")); - return -1; - } - - if (!qemuDomainDiskBlockJobIsSupported(vm, domdisk)) - return -1; - - switch ((virStorageType)actualType) { - case VIR_STORAGE_TYPE_BLOCK: - case VIR_STORAGE_TYPE_FILE: - break; - - case VIR_STORAGE_TYPE_NETWORK: - /* defer all of the checking to either qemu or libvirt's blockdev = code */ - if (blockdev) - break; - - switch ((virStorageNetProtocol) snapdisk->src->protocol) { - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - break; - - case VIR_STORAGE_NET_PROTOCOL_NONE: - case VIR_STORAGE_NET_PROTOCOL_NBD: - case VIR_STORAGE_NET_PROTOCOL_RBD: - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - case VIR_STORAGE_NET_PROTOCOL_SSH: - case VIR_STORAGE_NET_PROTOCOL_VXHS: - case VIR_STORAGE_NET_PROTOCOL_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("external active snapshots are not supported = on " - "'network' disks using '%s' protocol"), - virStorageNetProtocolTypeToString(snapdisk->src= ->protocol)); - return -1; - - } - break; - - case VIR_STORAGE_TYPE_DIR: - case VIR_STORAGE_TYPE_VOLUME: - case VIR_STORAGE_TYPE_NVME: - case VIR_STORAGE_TYPE_NONE: - case VIR_STORAGE_TYPE_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("external active snapshots are not supported on " - "'%s' disks"), virStorageTypeToString(actualType)= ); - return -1; - } - - if (qemuDomainSnapshotPrepareDiskShared(snapdisk, domdisk) < 0) - return -1; - - return 0; -} - - -static int -qemuDomainSnapshotPrepareDiskExternal(virDomainObjPtr vm, - virDomainDiskDefPtr disk, - virDomainSnapshotDiskDefPtr snapdisk, - bool active, - bool reuse, - bool blockdev) -{ - struct stat st; - int err; - int rc; - - if (disk->src->readonly && !(reuse || blockdev)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("external snapshot for readonly disk %s " - "is not supported"), disk->dst); - return -1; - } - - if (qemuTranslateSnapshotDiskSourcePool(snapdisk) < 0) - return -1; - - if (!active) { - if (virDomainDiskTranslateSourcePool(disk) < 0) - return -1; - - if (qemuDomainSnapshotPrepareDiskExternalInactive(snapdisk, disk) = < 0) - return -1; - } else { - if (qemuDomainSnapshotPrepareDiskExternalActive(vm, snapdisk, disk= , blockdev) < 0) - return -1; - } - - if (virStorageSourceIsLocalStorage(snapdisk->src)) { - if (virStorageFileInit(snapdisk->src) < 0) - return -1; - - rc =3D virStorageFileStat(snapdisk->src, &st); - err =3D errno; - - virStorageFileDeinit(snapdisk->src); - - if (rc < 0) { - if (err !=3D ENOENT) { - virReportSystemError(err, - _("unable to stat for disk %s: %s"), - snapdisk->name, snapdisk->src->path); - return -1; - } else if (reuse) { - virReportSystemError(err, - _("missing existing file for disk %s:= %s"), - snapdisk->name, snapdisk->src->path); - return -1; - } - } 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"), - snapdisk->name, snapdisk->src->path); - return -1; - } - } - - return 0; -} - - -static int -qemuDomainSnapshotPrepareDiskInternal(virDomainDiskDefPtr disk, - bool active) -{ - int actualType; - - /* active disks are handled by qemu itself so no need to worry about t= hose */ - if (active) - return 0; - - if (virDomainDiskTranslateSourcePool(disk) < 0) - return -1; - - actualType =3D virStorageSourceGetActualType(disk->src); - - switch ((virStorageType)actualType) { - case VIR_STORAGE_TYPE_BLOCK: - case VIR_STORAGE_TYPE_FILE: - return 0; - - case VIR_STORAGE_TYPE_NETWORK: - switch ((virStorageNetProtocol) disk->src->protocol) { - case VIR_STORAGE_NET_PROTOCOL_NONE: - case VIR_STORAGE_NET_PROTOCOL_NBD: - case VIR_STORAGE_NET_PROTOCOL_RBD: - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - case VIR_STORAGE_NET_PROTOCOL_SSH: - case VIR_STORAGE_NET_PROTOCOL_VXHS: - case VIR_STORAGE_NET_PROTOCOL_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("internal inactive snapshots are not supporte= d on " - "'network' disks using '%s' protocol"), - virStorageNetProtocolTypeToString(disk->src->pr= otocol)); - return -1; - } - break; - - case VIR_STORAGE_TYPE_DIR: - case VIR_STORAGE_TYPE_VOLUME: - case VIR_STORAGE_TYPE_NVME: - case VIR_STORAGE_TYPE_NONE: - case VIR_STORAGE_TYPE_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("internal inactive snapshots are not supported on= " - "'%s' disks"), virStorageTypeToString(actualType)= ); - return -1; - } - - return 0; -} - - -static int -qemuDomainSnapshotPrepare(virDomainObjPtr vm, - virDomainSnapshotDefPtr def, - unsigned int *flags) -{ - qemuDomainObjPrivatePtr priv =3D vm->privateData; - bool blockdev =3D virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); - size_t i; - bool active =3D virDomainObjIsActive(vm); - bool reuse =3D (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) !=3D 0; - bool found_internal =3D false; - bool forbid_internal =3D false; - int external =3D 0; - - for (i =3D 0; i < def->ndisks; i++) { - virDomainSnapshotDiskDefPtr disk =3D &def->disks[i]; - virDomainDiskDefPtr dom_disk =3D vm->def->disks[i]; - - if (disk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE && - qemuDomainDiskBlockJobIsActive(dom_disk)) - return -1; - - switch ((virDomainSnapshotLocation) disk->snapshot) { - case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: - found_internal =3D true; - - if (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && act= ive) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("active qemu domains require external dis= k " - "snapshots; disk %s requested internal"), - disk->name); - return -1; - } - - if (qemuDomainSnapshotPrepareDiskInternal(dom_disk, - active) < 0) - return -1; - - if (dom_disk->src->format > 0 && - dom_disk->src->format !=3D VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("internal snapshot for disk %s unsupporte= d " - "for storage type %s"), - disk->name, - virStorageFileFormatTypeToString(dom_disk->= src->format)); - return -1; - } - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: - if (!disk->src->format) { - disk->src->format =3D VIR_STORAGE_FILE_QCOW2; - } else if (disk->src->format !=3D VIR_STORAGE_FILE_QCOW2 && - disk->src->format !=3D VIR_STORAGE_FILE_QED) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("external snapshot format for disk %s " - "is unsupported: %s"), - disk->name, - virStorageFileFormatTypeToString(disk->src-= >format)); - return -1; - } - - if (qemuDomainSnapshotPrepareDiskExternal(vm, dom_disk, disk, - active, reuse, block= dev) < 0) - return -1; - - external++; - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE: - /* Remember seeing a disk that has snapshot disabled */ - if (!virStorageSourceIsEmpty(dom_disk->src) && - !dom_disk->src->readonly) - forbid_internal =3D true; - break; - - case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: - case VIR_DOMAIN_SNAPSHOT_LOCATION_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unexpected code path")); - return -1; - } - } - - if (!found_internal && !external && - def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("nothing selected for snapshot")); - return -1; - } - - /* internal snapshot requires a disk image to store the memory image t= o, and - * also disks can't be excluded from an internal snapshot */ - if ((def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && !foun= d_internal) || - (found_internal && forbid_internal)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("internal and full system snapshots require all " - "disks to be selected for snapshot")); - return -1; - } - - /* disk snapshot requires at least one disk */ - if (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("disk-only snapshots require at least " - "one disk to be selected for snapshot")); - return -1; - } - - /* 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) || - (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && exte= rnal) || - (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL && foun= d_internal)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("mixing internal and external targets for a snaps= hot " - "is not yet supported")); - return -1; - } - - /* internal snapshots + pflash based loader have the following problem= s: - * - if the variable store is raw, the snapshot fails - * - allowing a qcow2 image as the varstore would make it eligible to = receive - * the vmstate dump, which would make it huge - * - offline snapshot would not snapshot the varstore at all - * - * Avoid the issues by forbidding internal snapshot with pflash comple= tely. - */ - if (found_internal && - virDomainDefHasOldStyleUEFI(vm->def)) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("internal snapshots of a VM with pflash based " - "firmware are not supported")); - return -1; - } - - /* Alter flags to let later users know what we learned. */ - if (external && !active) - *flags |=3D VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; - - return 0; -} - - -struct _qemuDomainSnapshotDiskData { - virStorageSourcePtr src; - bool initialized; /* @src was initialized in the storage driver */ - bool created; /* @src was created by the snapshot code */ - bool prepared; /* @src was prepared using qemuDomainStorageSourceAcces= sAllow */ - virDomainDiskDefPtr disk; - char *relPath; /* relative path component to fill into original disk */ - qemuBlockStorageSourceChainDataPtr crdata; - bool blockdevadded; - - virStorageSourcePtr persistsrc; - virDomainDiskDefPtr persistdisk; -}; - -typedef struct _qemuDomainSnapshotDiskData qemuDomainSnapshotDiskData; -typedef qemuDomainSnapshotDiskData *qemuDomainSnapshotDiskDataPtr; - - -static void -qemuDomainSnapshotDiskCleanup(qemuDomainSnapshotDiskDataPtr data, - size_t ndata, - virQEMUDriverPtr driver, - virDomainObjPtr vm, - qemuDomainAsyncJob asyncJob) -{ - virErrorPtr orig_err; - size_t i; - - if (!data) - return; - - virErrorPreserveLast(&orig_err); - - for (i =3D 0; i < ndata; i++) { - /* on success of the snapshot the 'src' and 'persistsrc' propertie= s will - * be set to NULL by qemuDomainSnapshotDiskUpdateSource */ - if (data[i].src) { - if (data[i].blockdevadded) { - if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) = =3D=3D 0) { - - qemuBlockStorageSourceAttachRollback(qemuDomainGetMoni= tor(vm), - data[i].crdata->s= rcdata[0]); - ignore_value(qemuDomainObjExitMonitor(driver, vm)); - } - } - - if (data[i].created && - virStorageFileUnlink(data[i].src) < 0) { - VIR_WARN("Unable to remove just-created %s", - NULLSTR(data[i].src->path)); - } - - if (data[i].initialized) - virStorageFileDeinit(data[i].src); - - if (data[i].prepared) - qemuDomainStorageSourceAccessRevoke(driver, vm, data[i].sr= c); - - virObjectUnref(data[i].src); - } - virObjectUnref(data[i].persistsrc); - VIR_FREE(data[i].relPath); - qemuBlockStorageSourceChainDataFree(data[i].crdata); - } - - VIR_FREE(data); - virErrorRestore(&orig_err); -} - - -/** - * qemuDomainSnapshotDiskBitmapsPropagate: - * - * This function propagates any active persistent bitmap present in the or= iginal - * image into the new snapshot. This is necessary to keep tracking the cha= nged - * blocks in the active bitmaps as the backing file will become read-only. - * We leave the original bitmap active as in cases when the overlay is - * discarded (snapshot revert with abandoning the history) everything work= s as - * expected. - */ -static int -qemuDomainSnapshotDiskBitmapsPropagate(qemuDomainSnapshotDiskDataPtr dd, - virJSONValuePtr actions, - virHashTablePtr blockNamedNodeData) -{ - qemuBlockNamedNodeDataPtr entry; - size_t i; - - if (!(entry =3D virHashLookup(blockNamedNodeData, dd->disk->src->nodef= ormat))) - return 0; - - for (i =3D 0; i < entry->nbitmaps; i++) { - qemuBlockNamedNodeDataBitmapPtr bitmap =3D entry->bitmaps[i]; - - /* we don't care about temporary, inconsistent, or disabled bitmap= s */ - if (!bitmap->persistent || !bitmap->recording || bitmap->inconsist= ent) - continue; - - if (qemuMonitorTransactionBitmapAdd(actions, dd->src->nodeformat, - bitmap->name, true, false, - bitmap->granularity) < 0) - return -1; - } - - return 0; -} - - -static int -qemuDomainSnapshotDiskPrepareOneBlockdev(virQEMUDriverPtr driver, - virDomainObjPtr vm, - qemuDomainSnapshotDiskDataPtr dd, - virQEMUDriverConfigPtr cfg, - bool reuse, - virHashTablePtr blockNamedNodeDat= a, - qemuDomainAsyncJob asyncJob) -{ - qemuDomainObjPrivatePtr priv =3D vm->privateData; - g_autoptr(virStorageSource) terminator =3D NULL; - int rc; - - /* create a terminator for the snapshot disks so that qemu does not try - * to open them at first */ - if (!(terminator =3D virStorageSourceNew())) - return -1; - - if (qemuDomainPrepareStorageSourceBlockdev(dd->disk, dd->src, - priv, cfg) < 0) - return -1; - - if (!(dd->crdata =3D qemuBuildStorageSourceChainAttachPrepareBlockdevT= op(dd->src, - = terminator, - = priv->qemuCaps))) - return -1; - - if (reuse) { - if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) - return -1; - - rc =3D qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm), - dd->crdata->srcdata[0]); - - if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) - return -1; - } else { - if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, - dd->src, dd->disk->src)= < 0) - return -1; - - if (qemuBlockStorageSourceCreate(vm, dd->src, dd->disk->src, - NULL, dd->crdata->srcdata[0], - asyncJob) < 0) - return -1; - } - - dd->blockdevadded =3D true; - return 0; -} - - -static int -qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virQEMUDriverConfigPtr cfg, - virDomainDiskDefPtr disk, - virDomainSnapshotDiskDefPtr snapdisk, - qemuDomainSnapshotDiskDataPtr dd, - virHashTablePtr blockNamedNodeData, - bool reuse, - bool blockdev, - qemuDomainAsyncJob asyncJob, - virJSONValuePtr actions) -{ - virDomainDiskDefPtr persistdisk; - bool supportsCreate; - bool updateRelativeBacking =3D false; - - dd->disk =3D disk; - - if (qemuDomainStorageSourceValidateDepth(disk->src, 1, disk->dst) < 0) - return -1; - - if (!(dd->src =3D virStorageSourceCopy(snapdisk->src, false))) - return -1; - - if (virStorageSourceInitChainElement(dd->src, dd->disk->src, false) < = 0) - return -1; - - /* modify disk in persistent definition only when the source is the sa= me */ - if (vm->newDef && - (persistdisk =3D virDomainDiskByTarget(vm->newDef, dd->disk->dst))= && - virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) { - - dd->persistdisk =3D persistdisk; - - if (!(dd->persistsrc =3D virStorageSourceCopy(dd->src, false))) - return -1; - - if (virStorageSourceInitChainElement(dd->persistsrc, - dd->persistdisk->src, false) = < 0) - return -1; - } - - supportsCreate =3D virStorageFileSupportsCreate(dd->src); - - /* relative backing store paths need to be updated so that relative - * block commit still works. With blockdev we must update it when doing - * commit anyways so it's skipped here */ - if (!blockdev && - virStorageFileSupportsBackingChainTraversal(dd->src)) - updateRelativeBacking =3D true; - - if (supportsCreate || updateRelativeBacking) { - if (qemuDomainStorageFileInit(driver, vm, dd->src, NULL) < 0) - return -1; - - dd->initialized =3D true; - - if (reuse) { - if (updateRelativeBacking) { - g_autofree char *backingStoreStr =3D NULL; - - if (virStorageFileGetBackingStoreStr(dd->src, &backingStor= eStr) < 0) - return -1; - if (backingStoreStr !=3D NULL) { - if (virStorageIsRelative(backingStoreStr)) - dd->relPath =3D g_steal_pointer(&backingStoreStr); - } - } - } else { - /* pre-create the image file so that we can label it before ha= nding it to qemu */ - if (supportsCreate && dd->src->type !=3D VIR_STORAGE_TYPE_BLOC= K) { - if (virStorageFileCreate(dd->src) < 0) { - virReportSystemError(errno, _("failed to create image = file '%s'"), - NULLSTR(dd->src->path)); - return -1; - } - dd->created =3D true; - } - } - } - - /* set correct security, cgroup and locking options on the new image */ - if (qemuDomainStorageSourceAccessAllow(driver, vm, dd->src, - false, true, true) < 0) - return -1; - - dd->prepared =3D true; - - if (blockdev) { - if (qemuDomainSnapshotDiskPrepareOneBlockdev(driver, vm, dd, cfg, = reuse, - blockNamedNodeData, a= syncJob) < 0) - return -1; - - if (qemuDomainSnapshotDiskBitmapsPropagate(dd, actions, blockNamed= NodeData) < 0) - return -1; - - if (qemuBlockSnapshotAddBlockdev(actions, dd->disk, dd->src) < 0) - return -1; - } else { - if (qemuBlockSnapshotAddLegacy(actions, dd->disk, dd->src, reuse) = < 0) - return -1; - } - - return 0; -} - - -/** - * qemuDomainSnapshotDiskPrepare: - * - * Collects and prepares a list of structures that hold information about = disks - * that are selected for the snapshot. - */ -static int -qemuDomainSnapshotDiskPrepare(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap, - virQEMUDriverConfigPtr cfg, - bool reuse, - bool blockdev, - virHashTablePtr blockNamedNodeData, - qemuDomainAsyncJob asyncJob, - qemuDomainSnapshotDiskDataPtr *rdata, - size_t *rndata, - virJSONValuePtr actions) -{ - size_t i; - qemuDomainSnapshotDiskDataPtr data; - size_t ndata =3D 0; - virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); - int ret =3D -1; - - if (VIR_ALLOC_N(data, snapdef->ndisks) < 0) - return -1; - - for (i =3D 0; i < snapdef->ndisks; i++) { - if (snapdef->disks[i].snapshot =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION= _NONE) - continue; - - if (qemuDomainSnapshotDiskPrepareOne(driver, vm, cfg, vm->def->dis= ks[i], - snapdef->disks + i, - data + ndata++, - blockNamedNodeData, - reuse, blockdev, - asyncJob, - actions) < 0) - goto cleanup; - } - - *rdata =3D g_steal_pointer(&data); - *rndata =3D ndata; - ret =3D 0; - - cleanup: - qemuDomainSnapshotDiskCleanup(data, ndata, driver, vm, asyncJob); - return ret; -} - - -static void -qemuDomainSnapshotDiskUpdateSourceRenumber(virStorageSourcePtr src) -{ - virStorageSourcePtr next; - unsigned int idx =3D 1; - - for (next =3D src->backingStore; virStorageSourceIsBacking(next); next= =3D next->backingStore) - next->id =3D idx++; -} - - -/** - * qemuDomainSnapshotDiskUpdateSource: - * @driver: QEMU driver - * @vm: domain object - * @dd: snapshot disk data object - * @blockdev: -blockdev is in use for the VM - * - * Updates disk definition after a successful snapshot. - */ -static void -qemuDomainSnapshotDiskUpdateSource(virQEMUDriverPtr driver, - virDomainObjPtr vm, - qemuDomainSnapshotDiskDataPtr dd, - bool blockdev) -{ - /* storage driver access won'd be needed */ - if (dd->initialized) - virStorageFileDeinit(dd->src); - - if (qemuSecurityMoveImageMetadata(driver, vm, dd->disk->src, dd->src) = < 0) - VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name); - - /* unlock the write lock on the original image as qemu will no longer = write to it */ - virDomainLockImageDetach(driver->lockManager, vm, dd->disk->src); - - /* unlock also the new image if the VM is paused to follow the locking= semantics */ - if (virDomainObjGetState(vm, NULL) !=3D VIR_DOMAIN_RUNNING) - virDomainLockImageDetach(driver->lockManager, vm, dd->src); - - /* the old disk image is now readonly */ - dd->disk->src->readonly =3D true; - - dd->disk->src->relPath =3D g_steal_pointer(&dd->relPath); - dd->src->backingStore =3D g_steal_pointer(&dd->disk->src); - dd->disk->src =3D g_steal_pointer(&dd->src); - - /* fix numbering of disks */ - if (!blockdev) - qemuDomainSnapshotDiskUpdateSourceRenumber(dd->disk->src); - - if (dd->persistdisk) { - dd->persistdisk->src->readonly =3D true; - dd->persistsrc->backingStore =3D g_steal_pointer(&dd->persistdisk-= >src); - dd->persistdisk->src =3D g_steal_pointer(&dd->persistsrc); - } -} - - -/* The domain is expected to be locked and active. */ -static int -qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap, - virHashTablePtr blockNamedNodeData, - unsigned int flags, - virQEMUDriverConfigPtr cfg, - qemuDomainAsyncJob asyncJob) -{ - qemuDomainObjPrivatePtr priv =3D vm->privateData; - g_autoptr(virJSONValue) actions =3D NULL; - int rc; - int ret =3D -1; - size_t i; - bool reuse =3D (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) !=3D 0; - qemuDomainSnapshotDiskDataPtr diskdata =3D NULL; - size_t ndiskdata =3D 0; - bool blockdev =3D virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); - - if (virDomainObjCheckActive(vm) < 0) - return -1; - - actions =3D virJSONValueNewArray(); - - /* prepare a list of objects to use in the vm definition so that we do= n't - * have to roll back later */ - if (qemuDomainSnapshotDiskPrepare(driver, vm, snap, cfg, reuse, blockd= ev, - blockNamedNodeData, asyncJob, - &diskdata, &ndiskdata, actions) < 0) - goto cleanup; - - /* check whether there's anything to do */ - if (ndiskdata =3D=3D 0) { - ret =3D 0; - goto cleanup; - } - - if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) - goto cleanup; - - rc =3D qemuMonitorTransaction(priv->mon, &actions); - - if (qemuDomainObjExitMonitor(driver, vm) < 0) - rc =3D -1; - - for (i =3D 0; i < ndiskdata; i++) { - qemuDomainSnapshotDiskDataPtr dd =3D &diskdata[i]; - - virDomainAuditDisk(vm, dd->disk->src, dd->src, "snapshot", rc >=3D= 0); - - if (rc =3D=3D 0) - qemuDomainSnapshotDiskUpdateSource(driver, vm, dd, blockdev); - } - - if (rc < 0) - goto cleanup; - - if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0 || - (vm->newDef && virDomainDefSave(vm->newDef, driver->xmlopt, - cfg->configDir) < 0)) - goto cleanup; - - ret =3D 0; - - cleanup: - qemuDomainSnapshotDiskCleanup(diskdata, ndiskdata, driver, vm, asyncJo= b); - return ret; -} - - -static int -qemuDomainSnapshotCreateActiveExternal(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap, - virQEMUDriverConfigPtr cfg, - unsigned int flags) -{ - virObjectEventPtr event; - bool resume =3D false; - int ret =3D -1; - qemuDomainObjPrivatePtr priv =3D vm->privateData; - g_autofree char *xml =3D NULL; - virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); - bool memory =3D snapdef->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EX= TERNAL; - bool memory_unlink =3D false; - int thaw =3D 0; /* 1 if freeze succeeded, -1 if freeze failed */ - bool pmsuspended =3D false; - int compressed; - g_autoptr(virCommand) compressor =3D NULL; - virQEMUSaveDataPtr data =3D NULL; - g_autoptr(virHashTable) blockNamedNodeData =3D NULL; - - /* If quiesce was requested, then issue a freeze command, and a - * counterpart thaw command when it is actually sent to agent. - * The command will fail if the guest is paused or the guest agent - * is not running, or is already quiesced. */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) { - int freeze; - - if (qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) = < 0) - goto cleanup; - - if (virDomainObjCheckActive(vm) < 0) { - qemuDomainObjEndAgentJob(vm); - goto cleanup; - } - - freeze =3D qemuDomainSnapshotFSFreeze(vm, NULL, 0); - qemuDomainObjEndAgentJob(vm); - - if (freeze < 0) { - /* the helper reported the error */ - if (freeze =3D=3D -2) - thaw =3D -1; /* the command is sent but agent failed */ - goto cleanup; - } - thaw =3D 1; - } - - /* We need to track what state the guest is in, since taking the - * snapshot may alter that state and we must restore it later. */ - if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_PMSUSPENDED) { - pmsuspended =3D true; - } else if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_RUNNING) { - /* For full system external snapshots (those with memory), the gue= st - * must pause (either by libvirt up front, or by qemu after - * _LIVE converges). */ - if (memory) - resume =3D true; - - if (memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) { - if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) - goto cleanup; - - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto cleanup; - } - - resume =3D true; - } - } - - /* We need to collect reply from 'query-named-block-nodes' prior to the - * migration step as qemu deactivates bitmaps after migration so the r= esult - * would be wrong */ - if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && - !(blockNamedNodeData =3D qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_= JOB_SNAPSHOT))) - goto cleanup; - - /* do the memory snapshot if necessary */ - if (memory) { - /* check if migration is possible */ - if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0)) - goto cleanup; - - priv->job.current->statsType =3D QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDU= MP; - - /* allow the migration job to be cancelled or the domain to be pau= sed */ - qemuDomainObjSetAsyncJobMask(vm, (QEMU_JOB_DEFAULT_MASK | - JOB_MASK(QEMU_JOB_SUSPEND) | - JOB_MASK(QEMU_JOB_MIGRATION_OP))= ); - - if ((compressed =3D qemuSaveImageGetCompressionProgram(cfg->snapsh= otImageFormat, - &compressor, - "snapshot", f= alse)) < 0) - goto cleanup; - - if (!(xml =3D qemuDomainDefFormatLive(driver, priv->qemuCaps, - vm->def, priv->origCPU, - true, true)) || - !(snapdef->cookie =3D (virObjectPtr) qemuDomainSaveCookieNew(v= m))) - goto cleanup; - - if (!(data =3D virQEMUSaveDataNew(xml, - (qemuDomainSaveCookiePtr) snapdef-= >cookie, - resume, compressed, driver->xmlopt= ))) - goto cleanup; - xml =3D NULL; - - if ((ret =3D qemuSaveImageCreate(driver, vm, snapdef->file, data, - compressor, 0, - QEMU_ASYNC_JOB_SNAPSHOT)) < 0) - goto cleanup; - - /* the memory image was created, remove it on errors */ - memory_unlink =3D true; - - /* forbid any further manipulation */ - qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_DEFAULT_MASK); - } - - /* the domain is now paused if a memory snapshot was requested */ - - if ((ret =3D qemuDomainSnapshotCreateDiskActive(driver, vm, snap, - blockNamedNodeData, flag= s, cfg, - QEMU_ASYNC_JOB_SNAPSHOT)= ) < 0) - goto cleanup; - - /* the snapshot is complete now */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { - event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= TOPPED, - VIR_DOMAIN_EVENT_STOPPED_FROM_SNA= PSHOT); - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_SNAPSHOT, 0); - virDomainAuditStop(vm, "from-snapshot"); - resume =3D false; - thaw =3D 0; - virObjectEventStateQueue(driver->domainEventState, event); - } else if (memory && pmsuspended) { - /* qemu 1.3 is unable to save a domain in pm-suspended (S3) - * state; so we must emit an event stating that it was - * converted to paused. */ - virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); - event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= USPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_FROM_S= NAPSHOT); - virObjectEventStateQueue(driver->domainEventState, event); - } - - ret =3D 0; - - cleanup: - if (resume && virDomainObjIsActive(vm) && - qemuProcessStartCPUs(driver, vm, - VIR_DOMAIN_RUNNING_UNPAUSED, - QEMU_ASYNC_JOB_SNAPSHOT) < 0) { - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_API_ER= ROR); - virObjectEventStateQueue(driver->domainEventState, event); - if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) { - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("resuming after snapshot failed")); - } - - ret =3D -1; - } - - if (thaw !=3D 0 && - qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) >=3D= 0 && - virDomainObjIsActive(vm)) { - if (qemuDomainSnapshotFSThaw(vm, ret =3D=3D 0 && thaw > 0) < 0) { - /* helper reported the error, if it was needed */ - if (thaw > 0) - ret =3D -1; - } - - qemuDomainObjEndAgentJob(vm); - } - - virQEMUSaveDataFree(data); - if (memory_unlink && ret < 0) - unlink(snapdef->file); - - return ret; -} - - -static virDomainSnapshotPtr -qemuDomainSnapshotCreateXML(virDomainPtr domain, - const char *xmlDesc, - unsigned int flags) -{ - virQEMUDriverPtr driver =3D domain->conn->privateData; - virDomainObjPtr vm =3D NULL; - g_autofree char *xml =3D NULL; - virDomainMomentObjPtr snap =3D NULL; - virDomainSnapshotPtr snapshot =3D NULL; - virDomainMomentObjPtr current =3D NULL; - bool update_current =3D true; - bool redefine =3D flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE; - unsigned int parse_flags =3D VIR_DOMAIN_SNAPSHOT_PARSE_DISKS; - int align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; - bool align_match =3D true; - g_autoptr(virQEMUDriverConfig) cfg =3D NULL; - qemuDomainObjPrivatePtr priv; - virDomainSnapshotState state; - g_autoptr(virDomainSnapshotDef) def =3D NULL; - - 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 | - VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE, NULL); - - VIR_REQUIRE_FLAG_RET(VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE, - VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY, - NULL); - VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_SNAPSHOT_CREATE_LIVE, - VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE, - NULL); - - if ((redefine && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) || - (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) - update_current =3D false; - if (redefine) - parse_flags |=3D VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE; - - if (!(vm =3D qemuDomainObjFromDomain(domain))) - goto cleanup; - - priv =3D vm->privateData; - cfg =3D virQEMUDriverGetConfig(driver); - - if (virDomainSnapshotCreateXMLEnsureACL(domain->conn, vm->def, flags) = < 0) - goto cleanup; - - if (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0) - 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 |=3D VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE; - - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE) - parse_flags |=3D VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE; - - if (!(def =3D virDomainSnapshotDefParseString(xmlDesc, driver->xmlopt, - priv->qemuCaps, NULL, pars= e_flags))) - goto cleanup; - - /* reject snapshot names containing slashes or starting with dot as - * snapshot definitions are saved in files named by the snapshot name = */ - if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { - if (strchr(def->parent.name, '/')) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid snapshot name '%s': " - "name can't contain '/'"), - def->parent.name); - goto cleanup; - } - - if (def->parent.name[0] =3D=3D '.') { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid snapshot name '%s': " - "name can't start with '.'"), - def->parent.name); - goto cleanup; - } - } - - /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported= */ - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE && - (!virDomainObjIsActive(vm) || - def->memory !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("live snapshot creation is supported only " - "during full system snapshots")); - goto cleanup; - } - - /* allow snapshots only in certain states */ - state =3D redefine ? def->state : vm->state.state; - switch (state) { - /* valid states */ - case VIR_DOMAIN_SNAPSHOT_RUNNING: - case VIR_DOMAIN_SNAPSHOT_PAUSED: - case VIR_DOMAIN_SNAPSHOT_SHUTDOWN: - case VIR_DOMAIN_SNAPSHOT_SHUTOFF: - case VIR_DOMAIN_SNAPSHOT_CRASHED: - break; - - case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT: - if (!redefine) { - virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state= %s"), - virDomainSnapshotStateTypeToString(state)); - goto cleanup; - } - break; - - case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED: - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("qemu doesn't support taking snapshots of " - "PMSUSPENDED guests")); - goto cleanup; - - /* invalid states */ - case VIR_DOMAIN_SNAPSHOT_NOSTATE: - case VIR_DOMAIN_SNAPSHOT_BLOCKED: /* invalid state, unused in qemu */ - case VIR_DOMAIN_SNAPSHOT_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"= ), - virDomainSnapshotStateTypeToString(state)); - goto cleanup; - } - - /* We are going to modify the domain below. Internal snapshots would u= se - * a regular job, so we need to set the job mask to disallow query as - * 'savevm' blocks the monitor. External snapshot will then modify the - * job mask appropriately. */ - if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_SNAPSHOT, - VIR_DOMAIN_JOB_OPERATION_SNAPSHOT, flag= s) < 0) - goto cleanup; - - qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); - - if (redefine) { - if (virDomainSnapshotRedefinePrep(vm, &def, &snap, - driver->xmlopt, - flags) < 0) - goto endjob; - } else { - /* Easiest way to clone inactive portion of vm->def is via - * conversion in and back out of xml. */ - if (!(xml =3D qemuDomainDefFormatLive(driver, priv->qemuCaps, - vm->def, priv->origCPU, - true, true)) || - !(def->parent.dom =3D virDomainDefParseString(xml, driver->xml= opt, - priv->qemuCaps, - VIR_DOMAIN_DEF_PAR= SE_INACTIVE | - VIR_DOMAIN_DEF_PAR= SE_SKIP_VALIDATE))) - goto endjob; - - if (vm->newDef) { - def->parent.inactiveDom =3D virDomainDefCopy(vm->newDef, - driver->xmlopt, pri= v->qemuCaps, true); - if (!def->parent.inactiveDom) - goto endjob; - } - - if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { - align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match =3D false; - if (virDomainObjIsActive(vm)) - def->state =3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT; - else - def->state =3D VIR_DOMAIN_SNAPSHOT_SHUTOFF; - def->memory =3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; - } else if (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNA= L) { - def->state =3D virDomainObjGetState(vm, NULL); - align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; - align_match =3D false; - } else { - def->state =3D virDomainObjGetState(vm, NULL); - - if (virDomainObjIsActive(vm) && - def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("internal snapshot of a running VM " - "must include the memory state")); - goto endjob; - } - - def->memory =3D (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_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 endjob; - } - - if (!snap) { - if (!(snap =3D virDomainSnapshotAssignDef(vm->snapshots, def))) - goto endjob; - - def =3D NULL; - } - - current =3D virDomainSnapshotGetCurrent(vm->snapshots); - if (current) { - if (!redefine) - snap->def->parent_name =3D g_strdup(current->def->name); - } - - /* actually do the snapshot */ - if (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 || - virDomainSnapshotObjGetDef(snap)->memory =3D=3D VIR_DOMAIN_SNA= PSHOT_LOCATION_EXTERNAL) { - /* external full system or disk snapshot */ - if (qemuDomainSnapshotCreateActiveExternal(driver, - vm, snap, cfg, flag= s) < 0) - goto endjob; - } else { - /* internal full system */ - if (qemuDomainSnapshotCreateActiveInternal(driver, - vm, snap, flags) < = 0) - goto endjob; - } - } 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 =3D !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT= ); - - if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap, - reuse) < 0) - goto endjob; - } else { - if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap)= < 0) - goto endjob; - } - } - - /* 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 =3D virGetDomainSnapshot(domain, snap->def->name); - - endjob: - if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { - if (update_current) - virDomainSnapshotSetCurrent(vm->snapshots, snap); - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->xmlopt, - cfg->snapshotDir) < 0) { - /* if writing of metadata fails, error out rather than trying - * to silently carry on without completing the snapshot */ - virObjectUnref(snapshot); - snapshot =3D NULL; - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to save metadata for snapshot %s"), - snap->def->name); - virDomainSnapshotObjListRemove(vm->snapshots, snap); - } else { - virDomainSnapshotLinkParent(vm->snapshots, snap); - } - } else if (snap) { - virDomainSnapshotObjListRemove(vm->snapshots, snap); - } - - qemuDomainObjEndAsyncJob(driver, vm); - - cleanup: - virDomainObjEndAPI(&vm); - return snapshot; -} - - -static int -qemuDomainSnapshotListNames(virDomainPtr domain, - char **names, - int nameslen, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomainObjFromDomain(domain))) - return -1; - - if (virDomainSnapshotListNamesEnsureACL(domain->conn, vm->def) < 0) - goto cleanup; - - n =3D virDomainSnapshotObjListGetNames(vm->snapshots, NULL, names, nam= eslen, - flags); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static int -qemuDomainSnapshotNum(virDomainPtr domain, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomainObjFromDomain(domain))) - return -1; - - if (virDomainSnapshotNumEnsureACL(domain->conn, vm->def) < 0) - goto cleanup; - - n =3D virDomainSnapshotObjListNum(vm->snapshots, NULL, flags); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static int -qemuDomainListAllSnapshots(virDomainPtr domain, - virDomainSnapshotPtr **snaps, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomainObjFromDomain(domain))) - return -1; - - if (virDomainListAllSnapshotsEnsureACL(domain->conn, vm->def) < 0) - goto cleanup; - - n =3D virDomainListSnapshots(vm->snapshots, NULL, domain, snaps, flags= ); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static int -qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot, - char **names, - int nameslen, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - virDomainMomentObjPtr snap =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) - return -1; - - if (virDomainSnapshotListChildrenNamesEnsureACL(snapshot->domain->conn= , vm->def) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) - goto cleanup; - - n =3D virDomainSnapshotObjListGetNames(vm->snapshots, snap, names, nam= eslen, - flags); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static int -qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - virDomainMomentObjPtr snap =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) - return -1; - - if (virDomainSnapshotNumChildrenEnsureACL(snapshot->domain->conn, vm->= def) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) - goto cleanup; - - n =3D virDomainSnapshotObjListNum(vm->snapshots, snap, flags); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static int -qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot, - virDomainSnapshotPtr **snaps, - unsigned int flags) -{ - virDomainObjPtr vm =3D NULL; - virDomainMomentObjPtr snap =3D NULL; - int n =3D -1; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS | - VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL | - VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1); - - if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) - return -1; - - if (virDomainSnapshotListAllChildrenEnsureACL(snapshot->domain->conn, = vm->def) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) - goto cleanup; - - n =3D virDomainListSnapshots(vm->snapshots, snap, snapshot->domain, sn= aps, - flags); - - cleanup: - virDomainObjEndAPI(&vm); - return n; -} - - -static virDomainSnapshotPtr -qemuDomainSnapshotLookupByName(virDomainPtr domain, - const char *name, - unsigned int flags) -{ - virDomainObjPtr vm; - virDomainMomentObjPtr snap =3D NULL; - virDomainSnapshotPtr snapshot =3D NULL; - - virCheckFlags(0, NULL); - - if (!(vm =3D qemuDomainObjFromDomain(domain))) - return NULL; - - if (virDomainSnapshotLookupByNameEnsureACL(domain->conn, vm->def) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromName(vm, name))) - goto cleanup; - - snapshot =3D virGetDomainSnapshot(domain, snap->def->name); - - cleanup: - virDomainObjEndAPI(&vm); - return snapshot; -} - - -static int -qemuDomainHasCurrentSnapshot(virDomainPtr domain, - unsigned int flags) +qemuDomainHasCurrentSnapshot(virDomainPtr domain, + unsigned int flags) { virDomainObjPtr vm; int ret =3D -1; @@ -15342,601 +13716,44 @@ qemuDomainSnapshotHasMetadata(virDomainSnapshotP= tr snapshot, } -/* The domain is expected to be locked and inactive. */ -static int -qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virDomainMomentObjPtr snap) -{ - /* Try all disks, but report failure if we skipped any. */ - int ret =3D qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", tru= e); - return ret > 0 ? -1 : ret; -} - - static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, unsigned int flags) { - virQEMUDriverPtr driver =3D snapshot->domain->conn->privateData; virDomainObjPtr vm =3D NULL; int ret =3D -1; - virDomainMomentObjPtr snap =3D NULL; - virDomainSnapshotDefPtr snapdef; - virObjectEventPtr event =3D NULL; - virObjectEventPtr event2 =3D NULL; - int detail; - qemuDomainObjPrivatePtr priv; - int rc; - virDomainDefPtr config =3D NULL; - virDomainDefPtr inactiveConfig =3D NULL; - g_autoptr(virQEMUDriverConfig) cfg =3D NULL; - bool was_stopped =3D false; - qemuDomainSaveCookiePtr cookie; - virCPUDefPtr origCPU =3D NULL; - unsigned int start_flags =3D VIR_QEMU_PROCESS_START_GEN_VMID; - qemuDomainAsyncJob jobType =3D QEMU_ASYNC_JOB_START; - bool defined =3D false; - - 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 event= s: - * 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. - */ virNWFilterReadLockFilterUpdates(); if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) goto cleanup; - priv =3D vm->privateData; - cfg =3D virQEMUDriverGetConfig(driver); - if (virDomainRevertToSnapshotEnsureACL(snapshot->domain->conn, vm->def= ) < 0) goto cleanup; - if (qemuDomainHasBlockjob(vm, false)) { - virReportError(VIR_ERR_OPERATION_INVALID, "%s", - _("domain has active block job")); - goto cleanup; - } - - if (qemuProcessBeginJob(driver, vm, - VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT, - flags) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) - goto endjob; - snapdef =3D virDomainSnapshotObjGetDef(snap); - - if (!vm->persistent && - snapdef->state !=3D VIR_DOMAIN_SNAPSHOT_RUNNING && - snapdef->state !=3D VIR_DOMAIN_SNAPSHOT_PAUSED && - (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) =3D=3D 0) { - virReportError(VIR_ERR_OPERATION_INVALID, "%s", - _("transient domain needs to request run or pause " - "to revert to inactive snapshot")); - goto endjob; - } - - if (virDomainSnapshotIsExternal(snap)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("revert to external snapshot not supported yet")); - goto endjob; - } - - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { - if (!snap->def->dom) { - virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, - _("snapshot '%s' lacks domain '%s' rollback inf= o"), - snap->def->name, vm->def->name); - goto endjob; - } - if (virDomainObjIsActive(vm) && - !(snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || - snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_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 endjob; - } - if (vm->hasManagedSave && - !(snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || - snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_PAUSED)) { - virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", - _("snapshot without memory state, removal of " - "existing managed saved state strongly " - "recommended to avoid corruption")); - goto endjob; - } - } - - if (snap->def->dom) { - config =3D virDomainDefCopy(snap->def->dom, - driver->xmlopt, priv->qemuCaps, true); - if (!config) - goto endjob; - } - - if (snap->def->inactiveDom) { - inactiveConfig =3D virDomainDefCopy(snap->def->inactiveDom, - driver->xmlopt, priv->qemuCaps, = true); - if (!inactiveConfig) - goto endjob; - } else { - /* Inactive domain definition is missing: - * - either this is an old active snapshot and we need to copy the - * active definition as an inactive one - * - or this is an inactive snapshot which means config contains t= he - * inactive definition. - */ - if (snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || - snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_PAUSED) { - inactiveConfig =3D virDomainDefCopy(snap->def->dom, - driver->xmlopt, priv->qemuCa= ps, true); - if (!inactiveConfig) - goto endjob; - } else { - inactiveConfig =3D g_steal_pointer(&config); - } - } - - cookie =3D (qemuDomainSaveCookiePtr) snapdef->cookie; - - switch ((virDomainSnapshotState) snapdef->state) { - case VIR_DOMAIN_SNAPSHOT_RUNNING: - case VIR_DOMAIN_SNAPSHOT_PAUSED: - start_flags |=3D VIR_QEMU_PROCESS_START_PAUSED; - - /* Transitions 2, 3, 5, 6, 8, 9 */ - /* 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. We need to do this check again= st - * the migratable XML or it will always fail otherwise */ - if (config) { - bool compatible; - - /* Replace the CPU in config and put the original one in p= riv - * once we're done. When we have the updated CPU def in the - * cookie, we don't want to replace the CPU in migratable = def - * when doing ABI checks to make sure the current CPU exac= tly - * matches the one used at the time the snapshot was taken. - */ - if (cookie && cookie->cpu && config->cpu) { - origCPU =3D config->cpu; - if (!(config->cpu =3D virCPUDefCopy(cookie->cpu))) - goto endjob; - - compatible =3D qemuDomainDefCheckABIStability(driver, - priv->qemu= Caps, - vm->def, - config); - } else { - compatible =3D qemuDomainCheckABIStability(driver, vm,= config); - } - - /* If using VM GenID, there is no way currently to change - * the genid for the running guest, so set an error, - * mark as incompatible, and don't allow change of genid - * if the revert force flag would start the guest again. */ - if (compatible && config->genidRequested) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("domain genid update requires restart= ")); - compatible =3D false; - start_flags &=3D ~VIR_QEMU_PROCESS_START_GEN_VMID; - } - - if (!compatible) { - virErrorPtr err =3D virGetLastError(); - - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { - /* Re-spawn error using correct category. */ - if (err->code =3D=3D 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, - QEMU_ASYNC_JOB_START, 0); - virDomainAuditStop(vm, "from-snapshot"); - detail =3D VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STOP= PED, - detail); - virObjectEventStateQueue(driver->domainEventState, eve= nt); - /* Start after stop won't be an async start job, so - * reset to none */ - jobType =3D QEMU_ASYNC_JOB_NONE; - goto load; - } - } - - if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_RUNNING) { - /* Transitions 5, 6 */ - if (qemuProcessStopCPUs(driver, vm, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_START) < 0) - goto endjob; - if (!virDomainObjIsActive(vm)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto endjob; - } - } - - if (qemuDomainObjEnterMonitorAsync(driver, vm, - QEMU_ASYNC_JOB_START) < 0) - goto endjob; - rc =3D qemuMonitorLoadSnapshot(priv->mon, snap->def->name); - if (qemuDomainObjExitMonitor(driver, vm) < 0) - goto endjob; - if (rc < 0) { - /* XXX resume domain if it was running before the - * failed loadvm attempt? */ - goto endjob; - } - if (config) { - virCPUDefFree(priv->origCPU); - priv->origCPU =3D g_steal_pointer(&origCPU); - } - - if (cookie && !cookie->slirpHelper) - priv->disableSlirp =3D true; - - if (inactiveConfig) { - virDomainObjAssignDef(vm, inactiveConfig, false, NULL); - inactiveConfig =3D NULL; - defined =3D true; - } - } else { - /* Transitions 2, 3 */ - load: - was_stopped =3D true; - - if (inactiveConfig) { - virDomainObjAssignDef(vm, inactiveConfig, false, NULL); - inactiveConfig =3D NULL; - defined =3D true; - } - - if (config) { - virDomainObjAssignDef(vm, config, true, NULL); - config =3D NULL; - } - - /* No cookie means libvirt which saved the domain was too old = to - * mess up the CPU definitions. - */ - if (cookie && - qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) - goto cleanup; - - rc =3D qemuProcessStart(snapshot->domain->conn, driver, vm, - cookie ? cookie->cpu : NULL, - jobType, NULL, -1, NULL, snap, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - start_flags); - virDomainAuditStart(vm, "from-snapshot", rc >=3D 0); - detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - if (rc < 0) - goto endjob; - } - - /* Touch up domain state. */ - if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && - (snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_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 =3D VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; - event2 =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPEND= ED, - 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 =3D qemuProcessStartCPUs(driver, vm, - VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, - jobType); - if (rc < 0) - goto endjob; - virObjectUnref(event); - event =3D NULL; - if (was_stopped) { - /* Transition 2 */ - detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - } - } - break; - - case VIR_DOMAIN_SNAPSHOT_SHUTDOWN: - case VIR_DOMAIN_SNAPSHOT_SHUTOFF: - case VIR_DOMAIN_SNAPSHOT_CRASHED: - /* 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, - QEMU_ASYNC_JOB_START, 0); - virDomainAuditStop(vm, "from-snapshot"); - detail =3D VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STOPPED, - detail); - } - - if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) { - qemuDomainRemoveInactive(driver, vm); - qemuProcessEndJob(driver, vm); - goto cleanup; - } - - if (inactiveConfig) { - virDomainObjAssignDef(vm, inactiveConfig, false, NULL); - inactiveConfig =3D NULL; - defined =3D true; - } - - if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | - VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { - /* Flush first event, now do transition 2 or 3 */ - bool paused =3D (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != =3D 0; - - start_flags |=3D paused ? VIR_QEMU_PROCESS_START_PAUSED : 0; - - virObjectEventStateQueue(driver->domainEventState, event); - rc =3D qemuProcessStart(snapshot->domain->conn, driver, vm, NU= LL, - QEMU_ASYNC_JOB_START, NULL, -1, NULL, NU= LL, - VIR_NETDEV_VPORT_PROFILE_OP_CREATE, - start_flags); - virDomainAuditStart(vm, "from-snapshot", rc >=3D 0); - if (rc < 0) { - qemuDomainRemoveInactive(driver, vm); - qemuProcessEndJob(driver, vm); - goto cleanup; - } - detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; - event =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - detail); - if (paused) { - detail =3D VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; - event2 =3D virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPEND= ED, - detail); - } - } - break; - - case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED: - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("qemu doesn't support reversion of snapshot taken= in " - "PMSUSPENDED state")); - goto endjob; - - case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT: - /* Rejected earlier as an external snapshot */ - case VIR_DOMAIN_SNAPSHOT_NOSTATE: - case VIR_DOMAIN_SNAPSHOT_BLOCKED: - case VIR_DOMAIN_SNAPSHOT_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Invalid target domain state '%s'. Refusing " - "snapshot reversion"), - virDomainSnapshotStateTypeToString(snapdef->state)); - goto endjob; - } - - ret =3D 0; - - endjob: - qemuProcessEndJob(driver, vm); + ret =3D qemuSnapshotRevert(vm, snapshot, flags); cleanup: - if (ret =3D=3D 0) { - virDomainSnapshotSetCurrent(vm->snapshots, snap); - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->xmlopt, - cfg->snapshotDir) < 0) { - virDomainSnapshotSetCurrent(vm->snapshots, NULL); - ret =3D -1; - } - } - if (ret =3D=3D 0 && defined && vm->persistent && - !(ret =3D virDomainDefSave(vm->newDef ? vm->newDef : vm->def, - driver->xmlopt, cfg->configDir))) { - detail =3D VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT; - virObjectEventStateQueue(driver->domainEventState, - virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_DEFINED, - detail)); - } - virObjectEventStateQueue(driver->domainEventState, event); - virObjectEventStateQueue(driver->domainEventState, event2); virDomainObjEndAPI(&vm); virNWFilterUnlockFilterUpdates(); - virCPUDefFree(origCPU); - virDomainDefFree(config); - virDomainDefFree(inactiveConfig); - return ret; } -typedef struct _virQEMUMomentReparent virQEMUMomentReparent; -typedef virQEMUMomentReparent *virQEMUMomentReparentPtr; -struct _virQEMUMomentReparent { - const char *dir; - virDomainMomentObjPtr parent; - virDomainObjPtr vm; - virDomainXMLOptionPtr xmlopt; - int err; - int (*writeMetadata)(virDomainObjPtr, virDomainMomentObjPtr, - virDomainXMLOptionPtr, const char *); -}; - - -static int -qemuDomainMomentReparentChildren(void *payload, - const void *name G_GNUC_UNUSED, - void *data) -{ - virDomainMomentObjPtr moment =3D payload; - virQEMUMomentReparentPtr rep =3D data; - - if (rep->err < 0) - return 0; - - VIR_FREE(moment->def->parent_name); - - if (rep->parent->def) - moment->def->parent_name =3D g_strdup(rep->parent->def->name); - - rep->err =3D rep->writeMetadata(rep->vm, moment, rep->xmlopt, - rep->dir); - return 0; -} - - static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) { - virQEMUDriverPtr driver =3D snapshot->domain->conn->privateData; virDomainObjPtr vm =3D NULL; int ret =3D -1; - virDomainMomentObjPtr snap =3D NULL; - virQEMUMomentRemove rem; - virQEMUMomentReparent rep; - bool metadata_only =3D !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_= ONLY); - int external =3D 0; - g_autoptr(virQEMUDriverConfig) cfg =3D NULL; - - virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); if (!(vm =3D qemuDomObjFromSnapshot(snapshot))) return -1; - cfg =3D virQEMUDriverGetConfig(driver); - if (virDomainSnapshotDeleteEnsureACL(snapshot->domain->conn, vm->def) = < 0) goto cleanup; - if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) - goto cleanup; - - if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) - goto endjob; - - if (!metadata_only) { - if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) && - virDomainSnapshotIsExternal(snap)) - external++; - if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) - virDomainMomentForEachDescendant(snap, - qemuDomainSnapshotCountExtern= al, - &external); - if (external) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("deletion of %d external disk snapshots not " - "supported yet"), external); - goto endjob; - } - } - - if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { - rem.driver =3D driver; - rem.vm =3D vm; - rem.metadata_only =3D metadata_only; - rem.err =3D 0; - rem.current =3D virDomainSnapshotGetCurrent(vm->snapshots); - rem.found =3D false; - rem.momentDiscard =3D qemuDomainSnapshotDiscard; - virDomainMomentForEachDescendant(snap, qemuDomainMomentDiscardAll, - &rem); - if (rem.err < 0) - goto endjob; - if (rem.found) { - virDomainSnapshotSetCurrent(vm->snapshots, snap); - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { - if (qemuDomainSnapshotWriteMetadata(vm, snap, - driver->xmlopt, - cfg->snapshotDir) < 0)= { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("failed to set snapshot '%s' as curre= nt"), - snap->def->name); - virDomainSnapshotSetCurrent(vm->snapshots, NULL); - goto endjob; - } - } - } - } else if (snap->nchildren) { - rep.dir =3D cfg->snapshotDir; - rep.parent =3D snap->parent; - rep.vm =3D vm; - rep.err =3D 0; - rep.xmlopt =3D driver->xmlopt; - rep.writeMetadata =3D qemuDomainSnapshotWriteMetadata; - virDomainMomentForEachChild(snap, - qemuDomainMomentReparentChildren, - &rep); - if (rep.err < 0) - goto endjob; - virDomainMomentMoveChildren(snap, snap->parent); - } - - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { - virDomainMomentDropChildren(snap); - ret =3D 0; - } else { - ret =3D qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata= _only); - } - - endjob: - qemuDomainObjEndJob(driver, vm); + ret =3D qemuSnapshotDelete(vm, snapshot, flags); cleanup: virDomainObjEndAPI(&vm); @@ -19593,7 +17410,7 @@ qemuDomainFSFreeze(virDomainPtr dom, if (virDomainObjCheckActive(vm) < 0) goto endjob; - ret =3D qemuDomainSnapshotFSFreeze(vm, mountpoints, nmountpoints); + ret =3D qemuSnapshotFSFreeze(vm, mountpoints, nmountpoints); endjob: qemuDomainObjEndAgentJob(vm); @@ -19634,7 +17451,7 @@ qemuDomainFSThaw(virDomainPtr dom, if (virDomainObjCheckActive(vm) < 0) goto endjob; - ret =3D qemuDomainSnapshotFSThaw(vm, true); + ret =3D qemuSnapshotFSThaw(vm, true); endjob: qemuDomainObjEndAgentJob(vm); diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c new file mode 100644 index 0000000000..1e8ea80b22 --- /dev/null +++ b/src/qemu/qemu_snapshot.c @@ -0,0 +1,2266 @@ +/* + * qemu_snapshot.c: snapshot related implementation + * + * 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 + * . + */ + +#include + +#include "qemu_snapshot.h" + +#include "qemu_monitor.h" +#include "qemu_domain.h" +#include "qemu_block.h" +#include "qemu_process.h" +#include "qemu_migration.h" +#include "qemu_command.h" +#include "qemu_security.h" +#include "qemu_saveimage.h" + +#include "virerror.h" +#include "virlog.h" +#include "datatypes.h" +#include "viralloc.h" +#include "domain_conf.h" +#include "domain_audit.h" +#include "locking/domain_lock.h" +#include "libvirt_internal.h" +#include "virxml.h" +#include "virstring.h" +#include "virdomainsnapshotobjlist.h" +#include "virqemu.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.qemu_snapshot"); + + +/* Looks up snapshot object from VM and name */ +virDomainMomentObjPtr +qemuSnapObjFromName(virDomainObjPtr vm, + const char *name) +{ + virDomainMomentObjPtr snap =3D NULL; + snap =3D 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 */ +virDomainMomentObjPtr +qemuSnapObjFromSnapshot(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot) +{ + return qemuSnapObjFromName(vm, snapshot->name); +} + + +/* Count how many snapshots in a set are external snapshots. */ +static int +qemuSnapshotCountExternal(void *payload, + const void *name G_GNUC_UNUSED, + void *data) +{ + virDomainMomentObjPtr snap =3D payload; + int *count =3D data; + + if (virDomainSnapshotIsExternal(snap)) + (*count)++; + return 0; +} + + +/* Return -1 if request is not sent to agent due to misconfig, -2 if reque= st + * is sent but failed, and number of frozen filesystems on success. If -2 = is + * returned, FSThaw should be called revert the quiesced status. */ +int +qemuSnapshotFSFreeze(virDomainObjPtr vm, + const char **mountpoints, + unsigned int nmountpoints) +{ + qemuAgentPtr agent; + int frozen; + + if (!qemuDomainAgentAvailable(vm, true)) + return -1; + + agent =3D qemuDomainObjEnterAgent(vm); + frozen =3D qemuAgentFSFreeze(agent, mountpoints, nmountpoints); + qemuDomainObjExitAgent(vm, agent); + return frozen < 0 ? -2 : frozen; +} + + +/* Return -1 on error, otherwise number of thawed filesystems. */ +int +qemuSnapshotFSThaw(virDomainObjPtr vm, + bool report) +{ + qemuAgentPtr agent; + int thawed; + virErrorPtr err =3D NULL; + + if (!qemuDomainAgentAvailable(vm, report)) + return -1; + + agent =3D qemuDomainObjEnterAgent(vm); + if (!report) + virErrorPreserveLast(&err); + thawed =3D qemuAgentFSThaw(agent); + qemuDomainObjExitAgent(vm, agent); + + virErrorRestore(&err); + + return thawed; +} + + +/* The domain is expected to be locked and inactive. */ +static int +qemuSnapshotCreateInactiveInternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap) +{ + return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false); +} + + +/* The domain is expected to be locked and inactive. */ +static int +qemuSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap, + bool reuse) +{ + size_t i; + virDomainSnapshotDiskDefPtr snapdisk; + virDomainDiskDefPtr defdisk; + virCommandPtr cmd =3D NULL; + const char *qemuImgPath; + virBitmapPtr created =3D NULL; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + int ret =3D -1; + g_auto(virBuffer) buf =3D VIR_BUFFER_INITIALIZER; + virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); + + if (!(qemuImgPath =3D qemuFindQemuImgBinary(driver))) + goto cleanup; + + if (!(created =3D virBitmapNew(snapdef->ndisks))) + goto cleanup; + + /* If reuse is true, then qemuSnapshotPrepare already + * ensured that the new files exist, and it was up to the user to + * create them correctly. */ + for (i =3D 0; i < snapdef->ndisks && !reuse; i++) { + snapdisk =3D &(snapdef->disks[i]); + defdisk =3D snapdef->parent.dom->disks[snapdisk->idx]; + if (snapdisk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + + if (!snapdisk->src->format) + snapdisk->src->format =3D VIR_STORAGE_FILE_QCOW2; + + if (qemuDomainStorageSourceValidateDepth(defdisk->src, 1, defdisk-= >dst) < 0) + goto cleanup; + + /* creates cmd line args: qemu-img create -f qcow2 -o */ + if (!(cmd =3D virCommandNewArgList(qemuImgPath, + "create", + "-f", + virStorageFileFormatTypeToString(= snapdisk->src->format), + "-o", + NULL))) + goto cleanup; + + /* adds cmd line arg: backing_fmt=3Dformat,backing_file=3D/path/to= /backing/file */ + virBufferAsprintf(&buf, "backing_fmt=3D%s,backing_file=3D", + virStorageFileFormatTypeToString(defdisk->src->f= ormat)); + virQEMUBuildBufferEscapeComma(&buf, defdisk->src->path); + virCommandAddArgBuffer(cmd, &buf); + + /* adds cmd line args: /path/to/target/file */ + virQEMUBuildBufferEscapeComma(&buf, snapdisk->src->path); + virCommandAddArgBuffer(cmd, &buf); + + /* If the target does not exist, we're going to create it possibly= */ + if (!virFileExists(snapdisk->src->path)) + ignore_value(virBitmapSetBit(created, i)); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + virCommandFree(cmd); + cmd =3D NULL; + } + + /* update disk definitions */ + for (i =3D 0; i < snapdef->ndisks; i++) { + g_autoptr(virStorageSource) newsrc =3D NULL; + + snapdisk =3D &(snapdef->disks[i]); + defdisk =3D vm->def->disks[snapdisk->idx]; + + if (snapdisk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) + continue; + + if (!(newsrc =3D virStorageSourceCopy(snapdisk->src, false))) + goto cleanup; + + if (virStorageSourceInitChainElement(newsrc, defdisk->src, false) = < 0) + goto cleanup; + + if (!reuse && + virStorageSourceHasBacking(defdisk->src)) { + defdisk->src->readonly =3D true; + newsrc->backingStore =3D g_steal_pointer(&defdisk->src); + } else { + virObjectUnref(defdisk->src); + } + + defdisk->src =3D g_steal_pointer(&newsrc); + } + + if (virDomainDefSave(vm->def, driver->xmlopt, cfg->configDir) < 0) + goto cleanup; + + ret =3D 0; + + cleanup: + virCommandFree(cmd); + + /* unlink images if creation has failed */ + if (ret < 0 && created) { + ssize_t bit =3D -1; + while ((bit =3D virBitmapNextSetBit(created, bit)) >=3D 0) { + snapdisk =3D &(snapdef->disks[bit]); + if (unlink(snapdisk->src->path) < 0) + VIR_WARN("Failed to remove snapshot image '%s'", + snapdisk->src->path); + } + } + virBitmapFree(created); + + return ret; +} + + +/* The domain is expected to be locked and active. */ +static int +qemuSnapshotCreateActiveInternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap, + unsigned int flags) +{ + qemuDomainObjPrivatePtr priv =3D vm->privateData; + virObjectEventPtr event =3D NULL; + bool resume =3D false; + virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); + int ret =3D -1; + + if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0)) + goto cleanup; + + if (virDomainObjGetState(vm, NULL) =3D=3D 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_SNAPSHOT) < 0) + goto cleanup; + + resume =3D true; + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto cleanup; + } + } + + if (qemuDomainObjEnterMonitorAsync(driver, vm, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) { + resume =3D false; + goto cleanup; + } + + ret =3D qemuMonitorCreateSnapshot(priv->mon, snap->def->name); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + ret =3D -1; + if (ret < 0) + goto cleanup; + + if (!(snapdef->cookie =3D (virObjectPtr) qemuDomainSaveCookieNew(vm))) + goto cleanup; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= TOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNA= PSHOT); + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + resume =3D false; + } + + cleanup: + if (resume && virDomainObjIsActive(vm) && + qemuProcessStartCPUs(driver, vm, + VIR_DOMAIN_RUNNING_UNPAUSED, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) { + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_API_ER= ROR); + if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("resuming after snapshot failed")); + } + } + + virObjectEventStateQueue(driver->domainEventState, event); + + return ret; +} + + +static int +qemuSnapshotPrepareDiskShared(virDomainSnapshotDiskDefPtr snapdisk, + virDomainDiskDefPtr domdisk) +{ + if (!domdisk->src->shared || domdisk->src->readonly) + return 0; + + if (!qemuBlockStorageSourceSupportsConcurrentAccess(snapdisk->src)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("shared access for disk '%s' requires use of " + "supported storage format"), domdisk->dst); + return -1; + } + + return 0; +} + + +static int +qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDefPtr snapdi= sk, + virDomainDiskDefPtr domdisk) +{ + int domDiskType =3D virStorageSourceGetActualType(domdisk->src); + int snapDiskType =3D virStorageSourceGetActualType(snapdisk->src); + + switch ((virStorageType)domDiskType) { + case VIR_STORAGE_TYPE_BLOCK: + case VIR_STORAGE_TYPE_FILE: + break; + + case VIR_STORAGE_TYPE_NETWORK: + switch ((virStorageNetProtocol) domdisk->src->protocol) { + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("external inactive snapshots are not supporte= d on " + "'network' disks using '%s' protocol"), + virStorageNetProtocolTypeToString(domdisk->src-= >protocol)); + return -1; + } + break; + + case VIR_STORAGE_TYPE_DIR: + case VIR_STORAGE_TYPE_VOLUME: + case VIR_STORAGE_TYPE_NVME: + case VIR_STORAGE_TYPE_NONE: + case VIR_STORAGE_TYPE_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("external inactive snapshots are not supported on= " + "'%s' disks"), virStorageTypeToString(domDiskType= )); + return -1; + } + + switch ((virStorageType)snapDiskType) { + case VIR_STORAGE_TYPE_BLOCK: + case VIR_STORAGE_TYPE_FILE: + break; + + case VIR_STORAGE_TYPE_NETWORK: + case VIR_STORAGE_TYPE_DIR: + case VIR_STORAGE_TYPE_VOLUME: + case VIR_STORAGE_TYPE_NVME: + case VIR_STORAGE_TYPE_NONE: + case VIR_STORAGE_TYPE_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("external inactive snapshots are not supported on= " + "'%s' disks"), virStorageTypeToString(snapDiskTyp= e)); + return -1; + } + + if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0) + return -1; + + return 0; +} + + +static int +qemuSnapshotPrepareDiskExternalActive(virDomainObjPtr vm, + virDomainSnapshotDiskDefPtr snapdisk, + virDomainDiskDefPtr domdisk, + bool blockdev) +{ + int actualType =3D virStorageSourceGetActualType(snapdisk->src); + + if (domdisk->device =3D=3D VIR_DOMAIN_DISK_DEVICE_LUN) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("external active snapshots are not supported on s= csi " + "passthrough devices")); + return -1; + } + + if (!qemuDomainDiskBlockJobIsSupported(vm, domdisk)) + return -1; + + switch ((virStorageType)actualType) { + case VIR_STORAGE_TYPE_BLOCK: + case VIR_STORAGE_TYPE_FILE: + break; + + case VIR_STORAGE_TYPE_NETWORK: + /* defer all of the checking to either qemu or libvirt's blockdev = code */ + if (blockdev) + break; + + switch ((virStorageNetProtocol) snapdisk->src->protocol) { + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + break; + + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("external active snapshots are not supported = on " + "'network' disks using '%s' protocol"), + virStorageNetProtocolTypeToString(snapdisk->src= ->protocol)); + return -1; + + } + break; + + case VIR_STORAGE_TYPE_DIR: + case VIR_STORAGE_TYPE_VOLUME: + case VIR_STORAGE_TYPE_NVME: + case VIR_STORAGE_TYPE_NONE: + case VIR_STORAGE_TYPE_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("external active snapshots are not supported on " + "'%s' disks"), virStorageTypeToString(actualType)= ); + return -1; + } + + if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0) + return -1; + + return 0; +} + + +static int +qemuSnapshotPrepareDiskExternal(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virDomainSnapshotDiskDefPtr snapdisk, + bool active, + bool reuse, + bool blockdev) +{ + struct stat st; + int err; + int rc; + + if (disk->src->readonly && !(reuse || blockdev)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot for readonly disk %s " + "is not supported"), disk->dst); + return -1; + } + + if (qemuTranslateSnapshotDiskSourcePool(snapdisk) < 0) + return -1; + + if (!active) { + if (virDomainDiskTranslateSourcePool(disk) < 0) + return -1; + + if (qemuSnapshotPrepareDiskExternalInactive(snapdisk, disk) < 0) + return -1; + } else { + if (qemuSnapshotPrepareDiskExternalActive(vm, snapdisk, disk, bloc= kdev) < 0) + return -1; + } + + if (virStorageSourceIsLocalStorage(snapdisk->src)) { + if (virStorageFileInit(snapdisk->src) < 0) + return -1; + + rc =3D virStorageFileStat(snapdisk->src, &st); + err =3D errno; + + virStorageFileDeinit(snapdisk->src); + + if (rc < 0) { + if (err !=3D ENOENT) { + virReportSystemError(err, + _("unable to stat for disk %s: %s"), + snapdisk->name, snapdisk->src->path); + return -1; + } else if (reuse) { + virReportSystemError(err, + _("missing existing file for disk %s:= %s"), + snapdisk->name, snapdisk->src->path); + return -1; + } + } 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"), + snapdisk->name, snapdisk->src->path); + return -1; + } + } + + return 0; +} + + +static int +qemuSnapshotPrepareDiskInternal(virDomainDiskDefPtr disk, + bool active) +{ + int actualType; + + /* active disks are handled by qemu itself so no need to worry about t= hose */ + if (active) + return 0; + + if (virDomainDiskTranslateSourcePool(disk) < 0) + return -1; + + actualType =3D virStorageSourceGetActualType(disk->src); + + switch ((virStorageType)actualType) { + case VIR_STORAGE_TYPE_BLOCK: + case VIR_STORAGE_TYPE_FILE: + return 0; + + case VIR_STORAGE_TYPE_NETWORK: + switch ((virStorageNetProtocol) disk->src->protocol) { + case VIR_STORAGE_NET_PROTOCOL_NONE: + case VIR_STORAGE_NET_PROTOCOL_NBD: + case VIR_STORAGE_NET_PROTOCOL_RBD: + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + case VIR_STORAGE_NET_PROTOCOL_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("internal inactive snapshots are not supporte= d on " + "'network' disks using '%s' protocol"), + virStorageNetProtocolTypeToString(disk->src->pr= otocol)); + return -1; + } + break; + + case VIR_STORAGE_TYPE_DIR: + case VIR_STORAGE_TYPE_VOLUME: + case VIR_STORAGE_TYPE_NVME: + case VIR_STORAGE_TYPE_NONE: + case VIR_STORAGE_TYPE_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("internal inactive snapshots are not supported on= " + "'%s' disks"), virStorageTypeToString(actualType)= ); + return -1; + } + + return 0; +} + + +static int +qemuSnapshotPrepare(virDomainObjPtr vm, + virDomainSnapshotDefPtr def, + unsigned int *flags) +{ + qemuDomainObjPrivatePtr priv =3D vm->privateData; + bool blockdev =3D virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); + size_t i; + bool active =3D virDomainObjIsActive(vm); + bool reuse =3D (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) !=3D 0; + bool found_internal =3D false; + bool forbid_internal =3D false; + int external =3D 0; + + for (i =3D 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk =3D &def->disks[i]; + virDomainDiskDefPtr dom_disk =3D vm->def->disks[i]; + + if (disk->snapshot !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE && + qemuDomainDiskBlockJobIsActive(dom_disk)) + return -1; + + switch ((virDomainSnapshotLocation) disk->snapshot) { + case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: + found_internal =3D true; + + if (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && act= ive) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("active qemu domains require external dis= k " + "snapshots; disk %s requested internal"), + disk->name); + return -1; + } + + if (qemuSnapshotPrepareDiskInternal(dom_disk, + active) < 0) + return -1; + + if (dom_disk->src->format > 0 && + dom_disk->src->format !=3D VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("internal snapshot for disk %s unsupporte= d " + "for storage type %s"), + disk->name, + virStorageFileFormatTypeToString(dom_disk->= src->format)); + return -1; + } + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: + if (!disk->src->format) { + disk->src->format =3D VIR_STORAGE_FILE_QCOW2; + } else if (disk->src->format !=3D VIR_STORAGE_FILE_QCOW2 && + disk->src->format !=3D VIR_STORAGE_FILE_QED) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot format for disk %s " + "is unsupported: %s"), + disk->name, + virStorageFileFormatTypeToString(disk->src-= >format)); + return -1; + } + + if (qemuSnapshotPrepareDiskExternal(vm, dom_disk, disk, + active, reuse, blockdev) <= 0) + return -1; + + external++; + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE: + /* Remember seeing a disk that has snapshot disabled */ + if (!virStorageSourceIsEmpty(dom_disk->src) && + !dom_disk->src->readonly) + forbid_internal =3D true; + break; + + case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: + case VIR_DOMAIN_SNAPSHOT_LOCATION_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected code path")); + return -1; + } + } + + if (!found_internal && !external && + def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("nothing selected for snapshot")); + return -1; + } + + /* internal snapshot requires a disk image to store the memory image t= o, and + * also disks can't be excluded from an internal snapshot */ + if ((def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && !foun= d_internal) || + (found_internal && forbid_internal)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("internal and full system snapshots require all " + "disks to be selected for snapshot")); + return -1; + } + + /* disk snapshot requires at least one disk */ + if (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk-only snapshots require at least " + "one disk to be selected for snapshot")); + return -1; + } + + /* 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) || + (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && exte= rnal) || + (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL && foun= d_internal)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("mixing internal and external targets for a snaps= hot " + "is not yet supported")); + return -1; + } + + /* internal snapshots + pflash based loader have the following problem= s: + * - if the variable store is raw, the snapshot fails + * - allowing a qcow2 image as the varstore would make it eligible to = receive + * the vmstate dump, which would make it huge + * - offline snapshot would not snapshot the varstore at all + * + * Avoid the issues by forbidding internal snapshot with pflash comple= tely. + */ + if (found_internal && + virDomainDefHasOldStyleUEFI(vm->def)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("internal snapshots of a VM with pflash based " + "firmware are not supported")); + return -1; + } + + /* Alter flags to let later users know what we learned. */ + if (external && !active) + *flags |=3D VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; + + return 0; +} + + +struct _qemuSnapshotDiskData { + virStorageSourcePtr src; + bool initialized; /* @src was initialized in the storage driver */ + bool created; /* @src was created by the snapshot code */ + bool prepared; /* @src was prepared using qemuDomainStorageSourceAcces= sAllow */ + virDomainDiskDefPtr disk; + char *relPath; /* relative path component to fill into original disk */ + qemuBlockStorageSourceChainDataPtr crdata; + bool blockdevadded; + + virStorageSourcePtr persistsrc; + virDomainDiskDefPtr persistdisk; +}; + +typedef struct _qemuSnapshotDiskData qemuSnapshotDiskData; +typedef qemuSnapshotDiskData *qemuSnapshotDiskDataPtr; + + +static void +qemuSnapshotDiskCleanup(qemuSnapshotDiskDataPtr data, + size_t ndata, + virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuDomainAsyncJob asyncJob) +{ + virErrorPtr orig_err; + size_t i; + + if (!data) + return; + + virErrorPreserveLast(&orig_err); + + for (i =3D 0; i < ndata; i++) { + /* on success of the snapshot the 'src' and 'persistsrc' propertie= s will + * be set to NULL by qemuSnapshotDiskUpdateSource */ + if (data[i].src) { + if (data[i].blockdevadded) { + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) = =3D=3D 0) { + + qemuBlockStorageSourceAttachRollback(qemuDomainGetMoni= tor(vm), + data[i].crdata->s= rcdata[0]); + ignore_value(qemuDomainObjExitMonitor(driver, vm)); + } + } + + if (data[i].created && + virStorageFileUnlink(data[i].src) < 0) { + VIR_WARN("Unable to remove just-created %s", + NULLSTR(data[i].src->path)); + } + + if (data[i].initialized) + virStorageFileDeinit(data[i].src); + + if (data[i].prepared) + qemuDomainStorageSourceAccessRevoke(driver, vm, data[i].sr= c); + + virObjectUnref(data[i].src); + } + virObjectUnref(data[i].persistsrc); + VIR_FREE(data[i].relPath); + qemuBlockStorageSourceChainDataFree(data[i].crdata); + } + + VIR_FREE(data); + virErrorRestore(&orig_err); +} + + +/** + * qemuSnapshotDiskBitmapsPropagate: + * + * This function propagates any active persistent bitmap present in the or= iginal + * image into the new snapshot. This is necessary to keep tracking the cha= nged + * blocks in the active bitmaps as the backing file will become read-only. + * We leave the original bitmap active as in cases when the overlay is + * discarded (snapshot revert with abandoning the history) everything work= s as + * expected. + */ +static int +qemuSnapshotDiskBitmapsPropagate(qemuSnapshotDiskDataPtr dd, + virJSONValuePtr actions, + virHashTablePtr blockNamedNodeData) +{ + qemuBlockNamedNodeDataPtr entry; + size_t i; + + if (!(entry =3D virHashLookup(blockNamedNodeData, dd->disk->src->nodef= ormat))) + return 0; + + for (i =3D 0; i < entry->nbitmaps; i++) { + qemuBlockNamedNodeDataBitmapPtr bitmap =3D entry->bitmaps[i]; + + /* we don't care about temporary, inconsistent, or disabled bitmap= s */ + if (!bitmap->persistent || !bitmap->recording || bitmap->inconsist= ent) + continue; + + if (qemuMonitorTransactionBitmapAdd(actions, dd->src->nodeformat, + bitmap->name, true, false, + bitmap->granularity) < 0) + return -1; + } + + return 0; +} + + +static int +qemuSnapshotDiskPrepareOneBlockdev(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuSnapshotDiskDataPtr dd, + virQEMUDriverConfigPtr cfg, + bool reuse, + virHashTablePtr blockNamedNodeData, + qemuDomainAsyncJob asyncJob) +{ + qemuDomainObjPrivatePtr priv =3D vm->privateData; + g_autoptr(virStorageSource) terminator =3D NULL; + int rc; + + /* create a terminator for the snapshot disks so that qemu does not try + * to open them at first */ + if (!(terminator =3D virStorageSourceNew())) + return -1; + + if (qemuDomainPrepareStorageSourceBlockdev(dd->disk, dd->src, + priv, cfg) < 0) + return -1; + + if (!(dd->crdata =3D qemuBuildStorageSourceChainAttachPrepareBlockdevT= op(dd->src, + = terminator, + = priv->qemuCaps))) + return -1; + + if (reuse) { + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) + return -1; + + rc =3D qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm), + dd->crdata->srcdata[0]); + + if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) + return -1; + } else { + if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, + dd->src, dd->disk->src)= < 0) + return -1; + + if (qemuBlockStorageSourceCreate(vm, dd->src, dd->disk->src, + NULL, dd->crdata->srcdata[0], + asyncJob) < 0) + return -1; + } + + dd->blockdevadded =3D true; + return 0; +} + + +static int +qemuSnapshotDiskPrepareOne(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virQEMUDriverConfigPtr cfg, + virDomainDiskDefPtr disk, + virDomainSnapshotDiskDefPtr snapdisk, + qemuSnapshotDiskDataPtr dd, + virHashTablePtr blockNamedNodeData, + bool reuse, + bool blockdev, + qemuDomainAsyncJob asyncJob, + virJSONValuePtr actions) +{ + virDomainDiskDefPtr persistdisk; + bool supportsCreate; + bool updateRelativeBacking =3D false; + + dd->disk =3D disk; + + if (qemuDomainStorageSourceValidateDepth(disk->src, 1, disk->dst) < 0) + return -1; + + if (!(dd->src =3D virStorageSourceCopy(snapdisk->src, false))) + return -1; + + if (virStorageSourceInitChainElement(dd->src, dd->disk->src, false) < = 0) + return -1; + + /* modify disk in persistent definition only when the source is the sa= me */ + if (vm->newDef && + (persistdisk =3D virDomainDiskByTarget(vm->newDef, dd->disk->dst))= && + virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) { + + dd->persistdisk =3D persistdisk; + + if (!(dd->persistsrc =3D virStorageSourceCopy(dd->src, false))) + return -1; + + if (virStorageSourceInitChainElement(dd->persistsrc, + dd->persistdisk->src, false) = < 0) + return -1; + } + + supportsCreate =3D virStorageFileSupportsCreate(dd->src); + + /* relative backing store paths need to be updated so that relative + * block commit still works. With blockdev we must update it when doing + * commit anyways so it's skipped here */ + if (!blockdev && + virStorageFileSupportsBackingChainTraversal(dd->src)) + updateRelativeBacking =3D true; + + if (supportsCreate || updateRelativeBacking) { + if (qemuDomainStorageFileInit(driver, vm, dd->src, NULL) < 0) + return -1; + + dd->initialized =3D true; + + if (reuse) { + if (updateRelativeBacking) { + g_autofree char *backingStoreStr =3D NULL; + + if (virStorageFileGetBackingStoreStr(dd->src, &backingStor= eStr) < 0) + return -1; + if (backingStoreStr !=3D NULL) { + if (virStorageIsRelative(backingStoreStr)) + dd->relPath =3D g_steal_pointer(&backingStoreStr); + } + } + } else { + /* pre-create the image file so that we can label it before ha= nding it to qemu */ + if (supportsCreate && dd->src->type !=3D VIR_STORAGE_TYPE_BLOC= K) { + if (virStorageFileCreate(dd->src) < 0) { + virReportSystemError(errno, _("failed to create image = file '%s'"), + NULLSTR(dd->src->path)); + return -1; + } + dd->created =3D true; + } + } + } + + /* set correct security, cgroup and locking options on the new image */ + if (qemuDomainStorageSourceAccessAllow(driver, vm, dd->src, + false, true, true) < 0) + return -1; + + dd->prepared =3D true; + + if (blockdev) { + if (qemuSnapshotDiskPrepareOneBlockdev(driver, vm, dd, cfg, reuse, + blockNamedNodeData, asyncJo= b) < 0) + return -1; + + if (qemuSnapshotDiskBitmapsPropagate(dd, actions, blockNamedNodeDa= ta) < 0) + return -1; + + if (qemuBlockSnapshotAddBlockdev(actions, dd->disk, dd->src) < 0) + return -1; + } else { + if (qemuBlockSnapshotAddLegacy(actions, dd->disk, dd->src, reuse) = < 0) + return -1; + } + + return 0; +} + + +/** + * qemuSnapshotDiskPrepare: + * + * Collects and prepares a list of structures that hold information about = disks + * that are selected for the snapshot. + */ +static int +qemuSnapshotDiskPrepare(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap, + virQEMUDriverConfigPtr cfg, + bool reuse, + bool blockdev, + virHashTablePtr blockNamedNodeData, + qemuDomainAsyncJob asyncJob, + qemuSnapshotDiskDataPtr *rdata, + size_t *rndata, + virJSONValuePtr actions) +{ + size_t i; + qemuSnapshotDiskDataPtr data; + size_t ndata =3D 0; + virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); + int ret =3D -1; + + if (VIR_ALLOC_N(data, snapdef->ndisks) < 0) + return -1; + + for (i =3D 0; i < snapdef->ndisks; i++) { + if (snapdef->disks[i].snapshot =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION= _NONE) + continue; + + if (qemuSnapshotDiskPrepareOne(driver, vm, cfg, vm->def->disks[i], + snapdef->disks + i, + data + ndata++, + blockNamedNodeData, + reuse, blockdev, + asyncJob, + actions) < 0) + goto cleanup; + } + + *rdata =3D g_steal_pointer(&data); + *rndata =3D ndata; + ret =3D 0; + + cleanup: + qemuSnapshotDiskCleanup(data, ndata, driver, vm, asyncJob); + return ret; +} + + +static void +qemuSnapshotDiskUpdateSourceRenumber(virStorageSourcePtr src) +{ + virStorageSourcePtr next; + unsigned int idx =3D 1; + + for (next =3D src->backingStore; virStorageSourceIsBacking(next); next= =3D next->backingStore) + next->id =3D idx++; +} + + +/** + * qemuSnapshotDiskUpdateSource: + * @driver: QEMU driver + * @vm: domain object + * @dd: snapshot disk data object + * @blockdev: -blockdev is in use for the VM + * + * Updates disk definition after a successful snapshot. + */ +static void +qemuSnapshotDiskUpdateSource(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuSnapshotDiskDataPtr dd, + bool blockdev) +{ + /* storage driver access won'd be needed */ + if (dd->initialized) + virStorageFileDeinit(dd->src); + + if (qemuSecurityMoveImageMetadata(driver, vm, dd->disk->src, dd->src) = < 0) + VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name); + + /* unlock the write lock on the original image as qemu will no longer = write to it */ + virDomainLockImageDetach(driver->lockManager, vm, dd->disk->src); + + /* unlock also the new image if the VM is paused to follow the locking= semantics */ + if (virDomainObjGetState(vm, NULL) !=3D VIR_DOMAIN_RUNNING) + virDomainLockImageDetach(driver->lockManager, vm, dd->src); + + /* the old disk image is now readonly */ + dd->disk->src->readonly =3D true; + + dd->disk->src->relPath =3D g_steal_pointer(&dd->relPath); + dd->src->backingStore =3D g_steal_pointer(&dd->disk->src); + dd->disk->src =3D g_steal_pointer(&dd->src); + + /* fix numbering of disks */ + if (!blockdev) + qemuSnapshotDiskUpdateSourceRenumber(dd->disk->src); + + if (dd->persistdisk) { + dd->persistdisk->src->readonly =3D true; + dd->persistsrc->backingStore =3D g_steal_pointer(&dd->persistdisk-= >src); + dd->persistdisk->src =3D g_steal_pointer(&dd->persistsrc); + } +} + + +/* The domain is expected to be locked and active. */ +static int +qemuSnapshotCreateDiskActive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap, + virHashTablePtr blockNamedNodeData, + unsigned int flags, + virQEMUDriverConfigPtr cfg, + qemuDomainAsyncJob asyncJob) +{ + qemuDomainObjPrivatePtr priv =3D vm->privateData; + g_autoptr(virJSONValue) actions =3D NULL; + int rc; + int ret =3D -1; + size_t i; + bool reuse =3D (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) !=3D 0; + qemuSnapshotDiskDataPtr diskdata =3D NULL; + size_t ndiskdata =3D 0; + bool blockdev =3D virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); + + if (virDomainObjCheckActive(vm) < 0) + return -1; + + actions =3D virJSONValueNewArray(); + + /* prepare a list of objects to use in the vm definition so that we do= n't + * have to roll back later */ + if (qemuSnapshotDiskPrepare(driver, vm, snap, cfg, reuse, blockdev, + blockNamedNodeData, asyncJob, + &diskdata, &ndiskdata, actions) < 0) + goto cleanup; + + /* check whether there's anything to do */ + if (ndiskdata =3D=3D 0) { + ret =3D 0; + goto cleanup; + } + + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) + goto cleanup; + + rc =3D qemuMonitorTransaction(priv->mon, &actions); + + if (qemuDomainObjExitMonitor(driver, vm) < 0) + rc =3D -1; + + for (i =3D 0; i < ndiskdata; i++) { + qemuSnapshotDiskDataPtr dd =3D &diskdata[i]; + + virDomainAuditDisk(vm, dd->disk->src, dd->src, "snapshot", rc >=3D= 0); + + if (rc =3D=3D 0) + qemuSnapshotDiskUpdateSource(driver, vm, dd, blockdev); + } + + if (rc < 0) + goto cleanup; + + if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0 || + (vm->newDef && virDomainDefSave(vm->newDef, driver->xmlopt, + cfg->configDir) < 0)) + goto cleanup; + + ret =3D 0; + + cleanup: + qemuSnapshotDiskCleanup(diskdata, ndiskdata, driver, vm, asyncJob); + return ret; +} + + +static int +qemuSnapshotCreateActiveExternal(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap, + virQEMUDriverConfigPtr cfg, + unsigned int flags) +{ + virObjectEventPtr event; + bool resume =3D false; + int ret =3D -1; + qemuDomainObjPrivatePtr priv =3D vm->privateData; + g_autofree char *xml =3D NULL; + virDomainSnapshotDefPtr snapdef =3D virDomainSnapshotObjGetDef(snap); + bool memory =3D snapdef->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EX= TERNAL; + bool memory_unlink =3D false; + int thaw =3D 0; /* 1 if freeze succeeded, -1 if freeze failed */ + bool pmsuspended =3D false; + int compressed; + g_autoptr(virCommand) compressor =3D NULL; + virQEMUSaveDataPtr data =3D NULL; + g_autoptr(virHashTable) blockNamedNodeData =3D NULL; + + /* If quiesce was requested, then issue a freeze command, and a + * counterpart thaw command when it is actually sent to agent. + * The command will fail if the guest is paused or the guest agent + * is not running, or is already quiesced. */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) { + int freeze; + + if (qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) = < 0) + goto cleanup; + + if (virDomainObjCheckActive(vm) < 0) { + qemuDomainObjEndAgentJob(vm); + goto cleanup; + } + + freeze =3D qemuSnapshotFSFreeze(vm, NULL, 0); + qemuDomainObjEndAgentJob(vm); + + if (freeze < 0) { + /* the helper reported the error */ + if (freeze =3D=3D -2) + thaw =3D -1; /* the command is sent but agent failed */ + goto cleanup; + } + thaw =3D 1; + } + + /* We need to track what state the guest is in, since taking the + * snapshot may alter that state and we must restore it later. */ + if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_PMSUSPENDED) { + pmsuspended =3D true; + } else if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_RUNNING) { + /* For full system external snapshots (those with memory), the gue= st + * must pause (either by libvirt up front, or by qemu after + * _LIVE converges). */ + if (memory) + resume =3D true; + + if (memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) { + if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto cleanup; + } + + resume =3D true; + } + } + + /* We need to collect reply from 'query-named-block-nodes' prior to the + * migration step as qemu deactivates bitmaps after migration so the r= esult + * would be wrong */ + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && + !(blockNamedNodeData =3D qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_= JOB_SNAPSHOT))) + goto cleanup; + + /* do the memory snapshot if necessary */ + if (memory) { + /* check if migration is possible */ + if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0)) + goto cleanup; + + priv->job.current->statsType =3D QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDU= MP; + + /* allow the migration job to be cancelled or the domain to be pau= sed */ + qemuDomainObjSetAsyncJobMask(vm, (QEMU_JOB_DEFAULT_MASK | + JOB_MASK(QEMU_JOB_SUSPEND) | + JOB_MASK(QEMU_JOB_MIGRATION_OP))= ); + + if ((compressed =3D qemuSaveImageGetCompressionProgram(cfg->snapsh= otImageFormat, + &compressor, + "snapshot", f= alse)) < 0) + goto cleanup; + + if (!(xml =3D qemuDomainDefFormatLive(driver, priv->qemuCaps, + vm->def, priv->origCPU, + true, true)) || + !(snapdef->cookie =3D (virObjectPtr) qemuDomainSaveCookieNew(v= m))) + goto cleanup; + + if (!(data =3D virQEMUSaveDataNew(xml, + (qemuDomainSaveCookiePtr) snapdef-= >cookie, + resume, compressed, driver->xmlopt= ))) + goto cleanup; + xml =3D NULL; + + if ((ret =3D qemuSaveImageCreate(driver, vm, snapdef->file, data, + compressor, 0, + QEMU_ASYNC_JOB_SNAPSHOT)) < 0) + goto cleanup; + + /* the memory image was created, remove it on errors */ + memory_unlink =3D true; + + /* forbid any further manipulation */ + qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_DEFAULT_MASK); + } + + /* the domain is now paused if a memory snapshot was requested */ + + if ((ret =3D qemuSnapshotCreateDiskActive(driver, vm, snap, + blockNamedNodeData, flags, cfg, + QEMU_ASYNC_JOB_SNAPSHOT)) < 0) + goto cleanup; + + /* the snapshot is complete now */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= TOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNA= PSHOT); + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_SNAPSHOT, 0); + virDomainAuditStop(vm, "from-snapshot"); + resume =3D false; + thaw =3D 0; + virObjectEventStateQueue(driver->domainEventState, event); + } else if (memory && pmsuspended) { + /* qemu 1.3 is unable to save a domain in pm-suspended (S3) + * state; so we must emit an event stating that it was + * converted to paused. */ + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + event =3D virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_S= USPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_S= NAPSHOT); + virObjectEventStateQueue(driver->domainEventState, event); + } + + ret =3D 0; + + cleanup: + if (resume && virDomainObjIsActive(vm) && + qemuProcessStartCPUs(driver, vm, + VIR_DOMAIN_RUNNING_UNPAUSED, + QEMU_ASYNC_JOB_SNAPSHOT) < 0) { + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_API_ER= ROR); + virObjectEventStateQueue(driver->domainEventState, event); + if (virGetLastErrorCode() =3D=3D VIR_ERR_OK) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("resuming after snapshot failed")); + } + + ret =3D -1; + } + + if (thaw !=3D 0 && + qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) >=3D= 0 && + virDomainObjIsActive(vm)) { + if (qemuSnapshotFSThaw(vm, ret =3D=3D 0 && thaw > 0) < 0) { + /* helper reported the error, if it was needed */ + if (thaw > 0) + ret =3D -1; + } + + qemuDomainObjEndAgentJob(vm); + } + + virQEMUSaveDataFree(data); + if (memory_unlink && ret < 0) + unlink(snapdef->file); + + return ret; +} + + +virDomainSnapshotPtr +qemuSnapshotCreateXML(virDomainPtr domain, + virDomainObjPtr vm, + const char *xmlDesc, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D domain->conn->privateData; + g_autofree char *xml =3D NULL; + virDomainMomentObjPtr snap =3D NULL; + virDomainSnapshotPtr snapshot =3D NULL; + virDomainMomentObjPtr current =3D NULL; + bool update_current =3D true; + bool redefine =3D flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE; + unsigned int parse_flags =3D VIR_DOMAIN_SNAPSHOT_PARSE_DISKS; + int align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; + bool align_match =3D true; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + qemuDomainObjPrivatePtr priv =3D vm->privateData; + virDomainSnapshotState state; + g_autoptr(virDomainSnapshotDef) def =3D NULL; + + 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 | + VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE, NULL); + + VIR_REQUIRE_FLAG_RET(VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE, + VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY, + NULL); + VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_SNAPSHOT_CREATE_LIVE, + VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE, + NULL); + + if ((redefine && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) || + (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) + update_current =3D false; + if (redefine) + parse_flags |=3D VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE; + + if (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0) + 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 |=3D VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE) + parse_flags |=3D VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE; + + if (!(def =3D virDomainSnapshotDefParseString(xmlDesc, driver->xmlopt, + priv->qemuCaps, NULL, pars= e_flags))) + goto cleanup; + + /* reject snapshot names containing slashes or starting with dot as + * snapshot definitions are saved in files named by the snapshot name = */ + if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { + if (strchr(def->parent.name, '/')) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid snapshot name '%s': " + "name can't contain '/'"), + def->parent.name); + goto cleanup; + } + + if (def->parent.name[0] =3D=3D '.') { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid snapshot name '%s': " + "name can't start with '.'"), + def->parent.name); + goto cleanup; + } + } + + /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported= */ + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE && + (!virDomainObjIsActive(vm) || + def->memory !=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("live snapshot creation is supported only " + "during full system snapshots")); + goto cleanup; + } + + /* allow snapshots only in certain states */ + state =3D redefine ? def->state : vm->state.state; + switch (state) { + /* valid states */ + case VIR_DOMAIN_SNAPSHOT_RUNNING: + case VIR_DOMAIN_SNAPSHOT_PAUSED: + case VIR_DOMAIN_SNAPSHOT_SHUTDOWN: + case VIR_DOMAIN_SNAPSHOT_SHUTOFF: + case VIR_DOMAIN_SNAPSHOT_CRASHED: + break; + + case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT: + if (!redefine) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state= %s"), + virDomainSnapshotStateTypeToString(state)); + goto cleanup; + } + break; + + case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED: + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("qemu doesn't support taking snapshots of " + "PMSUSPENDED guests")); + goto cleanup; + + /* invalid states */ + case VIR_DOMAIN_SNAPSHOT_NOSTATE: + case VIR_DOMAIN_SNAPSHOT_BLOCKED: /* invalid state, unused in qemu */ + case VIR_DOMAIN_SNAPSHOT_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"= ), + virDomainSnapshotStateTypeToString(state)); + goto cleanup; + } + + /* We are going to modify the domain below. Internal snapshots would u= se + * a regular job, so we need to set the job mask to disallow query as + * 'savevm' blocks the monitor. External snapshot will then modify the + * job mask appropriately. */ + if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_SNAPSHOT, + VIR_DOMAIN_JOB_OPERATION_SNAPSHOT, flag= s) < 0) + goto cleanup; + + qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); + + if (redefine) { + if (virDomainSnapshotRedefinePrep(vm, &def, &snap, + driver->xmlopt, + flags) < 0) + goto endjob; + } else { + /* Easiest way to clone inactive portion of vm->def is via + * conversion in and back out of xml. */ + if (!(xml =3D qemuDomainDefFormatLive(driver, priv->qemuCaps, + vm->def, priv->origCPU, + true, true)) || + !(def->parent.dom =3D virDomainDefParseString(xml, driver->xml= opt, + priv->qemuCaps, + VIR_DOMAIN_DEF_PAR= SE_INACTIVE | + VIR_DOMAIN_DEF_PAR= SE_SKIP_VALIDATE))) + goto endjob; + + if (vm->newDef) { + def->parent.inactiveDom =3D virDomainDefCopy(vm->newDef, + driver->xmlopt, pri= v->qemuCaps, true); + if (!def->parent.inactiveDom) + goto endjob; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match =3D false; + if (virDomainObjIsActive(vm)) + def->state =3D VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT; + else + def->state =3D VIR_DOMAIN_SNAPSHOT_SHUTOFF; + def->memory =3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; + } else if (def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNA= L) { + def->state =3D virDomainObjGetState(vm, NULL); + align_location =3D VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match =3D false; + } else { + def->state =3D virDomainObjGetState(vm, NULL); + + if (virDomainObjIsActive(vm) && + def->memory =3D=3D VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("internal snapshot of a running VM " + "must include the memory state")); + goto endjob; + } + + def->memory =3D (def->state =3D=3D VIR_DOMAIN_SNAPSHOT_SHUTOFF= ? + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE : + VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL); + } + if (virDomainSnapshotAlignDisks(def, align_location, + align_match) < 0 || + qemuSnapshotPrepare(vm, def, &flags) < 0) + goto endjob; + } + + if (!snap) { + if (!(snap =3D virDomainSnapshotAssignDef(vm->snapshots, def))) + goto endjob; + + def =3D NULL; + } + + current =3D virDomainSnapshotGetCurrent(vm->snapshots); + if (current) { + if (!redefine) + snap->def->parent_name =3D g_strdup(current->def->name); + } + + /* actually do the snapshot */ + if (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 || + virDomainSnapshotObjGetDef(snap)->memory =3D=3D VIR_DOMAIN_SNA= PSHOT_LOCATION_EXTERNAL) { + /* external full system or disk snapshot */ + if (qemuSnapshotCreateActiveExternal(driver, vm, snap, cfg, fl= ags) < 0) + goto endjob; + } else { + /* internal full system */ + if (qemuSnapshotCreateActiveInternal(driver, vm, snap, flags) = < 0) + goto endjob; + } + } else { + /* inactive; qemuSnapshotPrepare 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 =3D !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT= ); + + if (qemuSnapshotCreateInactiveExternal(driver, vm, snap, reuse= ) < 0) + goto endjob; + } else { + if (qemuSnapshotCreateInactiveInternal(driver, vm, snap) < 0) + goto endjob; + } + } + + /* 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 =3D virGetDomainSnapshot(domain, snap->def->name); + + endjob: + if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) { + if (update_current) + virDomainSnapshotSetCurrent(vm->snapshots, snap); + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->xmlopt, + cfg->snapshotDir) < 0) { + /* if writing of metadata fails, error out rather than trying + * to silently carry on without completing the snapshot */ + virObjectUnref(snapshot); + snapshot =3D NULL; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for snapshot %s"), + snap->def->name); + virDomainSnapshotObjListRemove(vm->snapshots, snap); + } else { + virDomainSnapshotLinkParent(vm->snapshots, snap); + } + } else if (snap) { + virDomainSnapshotObjListRemove(vm->snapshots, snap); + } + + qemuDomainObjEndAsyncJob(driver, vm); + + cleanup: + return snapshot; +} + + +/* The domain is expected to be locked and inactive. */ +static int +qemuSnapshotRevertInactive(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr snap) +{ + /* Try all disks, but report failure if we skipped any. */ + int ret =3D qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", tru= e); + return ret > 0 ? -1 : ret; +} + + +int +qemuSnapshotRevert(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D snapshot->domain->conn->privateData; + int ret =3D -1; + virDomainMomentObjPtr snap =3D NULL; + virDomainSnapshotDefPtr snapdef; + virObjectEventPtr event =3D NULL; + virObjectEventPtr event2 =3D NULL; + int detail; + qemuDomainObjPrivatePtr priv =3D vm->privateData; + int rc; + virDomainDefPtr config =3D NULL; + virDomainDefPtr inactiveConfig =3D NULL; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + bool was_stopped =3D false; + qemuDomainSaveCookiePtr cookie; + virCPUDefPtr origCPU =3D NULL; + unsigned int start_flags =3D VIR_QEMU_PROCESS_START_GEN_VMID; + qemuDomainAsyncJob jobType =3D QEMU_ASYNC_JOB_START; + bool defined =3D false; + + 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 event= s: + * 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. + */ + + if (qemuDomainHasBlockjob(vm, false)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain has active block job")); + goto cleanup; + } + + if (qemuProcessBeginJob(driver, vm, + VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT, + flags) < 0) + goto cleanup; + + if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) + goto endjob; + snapdef =3D virDomainSnapshotObjGetDef(snap); + + if (!vm->persistent && + snapdef->state !=3D VIR_DOMAIN_SNAPSHOT_RUNNING && + snapdef->state !=3D VIR_DOMAIN_SNAPSHOT_PAUSED && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) =3D=3D 0) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient domain needs to request run or pause " + "to revert to inactive snapshot")); + goto endjob; + } + + if (virDomainSnapshotIsExternal(snap)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("revert to external snapshot not supported yet")); + goto endjob; + } + + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + if (!snap->def->dom) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, + _("snapshot '%s' lacks domain '%s' rollback inf= o"), + snap->def->name, vm->def->name); + goto endjob; + } + if (virDomainObjIsActive(vm) && + !(snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || + snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_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 endjob; + } + if (vm->hasManagedSave && + !(snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || + snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_PAUSED)) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", + _("snapshot without memory state, removal of " + "existing managed saved state strongly " + "recommended to avoid corruption")); + goto endjob; + } + } + + if (snap->def->dom) { + config =3D virDomainDefCopy(snap->def->dom, + driver->xmlopt, priv->qemuCaps, true); + if (!config) + goto endjob; + } + + if (snap->def->inactiveDom) { + inactiveConfig =3D virDomainDefCopy(snap->def->inactiveDom, + driver->xmlopt, priv->qemuCaps, = true); + if (!inactiveConfig) + goto endjob; + } else { + /* Inactive domain definition is missing: + * - either this is an old active snapshot and we need to copy the + * active definition as an inactive one + * - or this is an inactive snapshot which means config contains t= he + * inactive definition. + */ + if (snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_RUNNING || + snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_PAUSED) { + inactiveConfig =3D virDomainDefCopy(snap->def->dom, + driver->xmlopt, priv->qemuCa= ps, true); + if (!inactiveConfig) + goto endjob; + } else { + inactiveConfig =3D g_steal_pointer(&config); + } + } + + cookie =3D (qemuDomainSaveCookiePtr) snapdef->cookie; + + switch ((virDomainSnapshotState) snapdef->state) { + case VIR_DOMAIN_SNAPSHOT_RUNNING: + case VIR_DOMAIN_SNAPSHOT_PAUSED: + start_flags |=3D VIR_QEMU_PROCESS_START_PAUSED; + + /* Transitions 2, 3, 5, 6, 8, 9 */ + /* 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. We need to do this check again= st + * the migratable XML or it will always fail otherwise */ + if (config) { + bool compatible; + + /* Replace the CPU in config and put the original one in p= riv + * once we're done. When we have the updated CPU def in the + * cookie, we don't want to replace the CPU in migratable = def + * when doing ABI checks to make sure the current CPU exac= tly + * matches the one used at the time the snapshot was taken. + */ + if (cookie && cookie->cpu && config->cpu) { + origCPU =3D config->cpu; + if (!(config->cpu =3D virCPUDefCopy(cookie->cpu))) + goto endjob; + + compatible =3D qemuDomainDefCheckABIStability(driver, + priv->qemu= Caps, + vm->def, + config); + } else { + compatible =3D qemuDomainCheckABIStability(driver, vm,= config); + } + + /* If using VM GenID, there is no way currently to change + * the genid for the running guest, so set an error, + * mark as incompatible, and don't allow change of genid + * if the revert force flag would start the guest again. */ + if (compatible && config->genidRequested) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("domain genid update requires restart= ")); + compatible =3D false; + start_flags &=3D ~VIR_QEMU_PROCESS_START_GEN_VMID; + } + + if (!compatible) { + virErrorPtr err =3D virGetLastError(); + + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + /* Re-spawn error using correct category. */ + if (err->code =3D=3D 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, + QEMU_ASYNC_JOB_START, 0); + virDomainAuditStop(vm, "from-snapshot"); + detail =3D VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STOP= PED, + detail); + virObjectEventStateQueue(driver->domainEventState, eve= nt); + /* Start after stop won't be an async start job, so + * reset to none */ + jobType =3D QEMU_ASYNC_JOB_NONE; + goto load; + } + } + + if (virDomainObjGetState(vm, NULL) =3D=3D VIR_DOMAIN_RUNNING) { + /* Transitions 5, 6 */ + if (qemuProcessStopCPUs(driver, vm, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_START) < 0) + goto endjob; + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + } + + if (qemuDomainObjEnterMonitorAsync(driver, vm, + QEMU_ASYNC_JOB_START) < 0) + goto endjob; + rc =3D qemuMonitorLoadSnapshot(priv->mon, snap->def->name); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + goto endjob; + if (rc < 0) { + /* XXX resume domain if it was running before the + * failed loadvm attempt? */ + goto endjob; + } + if (config) { + virCPUDefFree(priv->origCPU); + priv->origCPU =3D g_steal_pointer(&origCPU); + } + + if (cookie && !cookie->slirpHelper) + priv->disableSlirp =3D true; + + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig =3D NULL; + defined =3D true; + } + } else { + /* Transitions 2, 3 */ + load: + was_stopped =3D true; + + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig =3D NULL; + defined =3D true; + } + + if (config) { + virDomainObjAssignDef(vm, config, true, NULL); + config =3D NULL; + } + + /* No cookie means libvirt which saved the domain was too old = to + * mess up the CPU definitions. + */ + if (cookie && + qemuDomainFixupCPUs(vm, &cookie->cpu) < 0) + goto cleanup; + + rc =3D qemuProcessStart(snapshot->domain->conn, driver, vm, + cookie ? cookie->cpu : NULL, + jobType, NULL, -1, NULL, snap, + VIR_NETDEV_VPORT_PROFILE_OP_CREATE, + start_flags); + virDomainAuditStart(vm, "from-snapshot", rc >=3D 0); + detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + if (rc < 0) + goto endjob; + } + + /* Touch up domain state. */ + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && + (snapdef->state =3D=3D VIR_DOMAIN_SNAPSHOT_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 =3D VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPEND= ED, + 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 =3D qemuProcessStartCPUs(driver, vm, + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, + jobType); + if (rc < 0) + goto endjob; + virObjectUnref(event); + event =3D NULL; + if (was_stopped) { + /* Transition 2 */ + detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + } + } + break; + + case VIR_DOMAIN_SNAPSHOT_SHUTDOWN: + case VIR_DOMAIN_SNAPSHOT_SHUTOFF: + case VIR_DOMAIN_SNAPSHOT_CRASHED: + /* 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, + QEMU_ASYNC_JOB_START, 0); + virDomainAuditStop(vm, "from-snapshot"); + detail =3D VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + detail); + } + + if (qemuSnapshotRevertInactive(driver, vm, snap) < 0) { + qemuDomainRemoveInactive(driver, vm); + qemuProcessEndJob(driver, vm); + goto cleanup; + } + + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig =3D NULL; + defined =3D true; + } + + if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { + /* Flush first event, now do transition 2 or 3 */ + bool paused =3D (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != =3D 0; + + start_flags |=3D paused ? VIR_QEMU_PROCESS_START_PAUSED : 0; + + virObjectEventStateQueue(driver->domainEventState, event); + rc =3D qemuProcessStart(snapshot->domain->conn, driver, vm, NU= LL, + QEMU_ASYNC_JOB_START, NULL, -1, NULL, NU= LL, + VIR_NETDEV_VPORT_PROFILE_OP_CREATE, + start_flags); + virDomainAuditStart(vm, "from-snapshot", rc >=3D 0); + if (rc < 0) { + qemuDomainRemoveInactive(driver, vm); + qemuProcessEndJob(driver, vm); + goto cleanup; + } + detail =3D VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + if (paused) { + detail =3D VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 =3D virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPEND= ED, + detail); + } + } + break; + + case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED: + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("qemu doesn't support reversion of snapshot taken= in " + "PMSUSPENDED state")); + goto endjob; + + case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT: + /* Rejected earlier as an external snapshot */ + case VIR_DOMAIN_SNAPSHOT_NOSTATE: + case VIR_DOMAIN_SNAPSHOT_BLOCKED: + case VIR_DOMAIN_SNAPSHOT_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid target domain state '%s'. Refusing " + "snapshot reversion"), + virDomainSnapshotStateTypeToString(snapdef->state)); + goto endjob; + } + + ret =3D 0; + + endjob: + qemuProcessEndJob(driver, vm); + + cleanup: + if (ret =3D=3D 0) { + virDomainSnapshotSetCurrent(vm->snapshots, snap); + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->xmlopt, + cfg->snapshotDir) < 0) { + virDomainSnapshotSetCurrent(vm->snapshots, NULL); + ret =3D -1; + } + } + if (ret =3D=3D 0 && defined && vm->persistent && + !(ret =3D virDomainDefSave(vm->newDef ? vm->newDef : vm->def, + driver->xmlopt, cfg->configDir))) { + detail =3D VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT; + virObjectEventStateQueue(driver->domainEventState, + virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_DEFINED, + detail)); + } + virObjectEventStateQueue(driver->domainEventState, event); + virObjectEventStateQueue(driver->domainEventState, event2); + virCPUDefFree(origCPU); + virDomainDefFree(config); + virDomainDefFree(inactiveConfig); + + return ret; +} + + +typedef struct _virQEMUMomentReparent virQEMUMomentReparent; +typedef virQEMUMomentReparent *virQEMUMomentReparentPtr; +struct _virQEMUMomentReparent { + const char *dir; + virDomainMomentObjPtr parent; + virDomainObjPtr vm; + virDomainXMLOptionPtr xmlopt; + int err; + int (*writeMetadata)(virDomainObjPtr, virDomainMomentObjPtr, + virDomainXMLOptionPtr, const char *); +}; + + +static int +qemuSnapshotChildrenReparent(void *payload, + const void *name G_GNUC_UNUSED, + void *data) +{ + virDomainMomentObjPtr moment =3D payload; + virQEMUMomentReparentPtr rep =3D data; + + if (rep->err < 0) + return 0; + + VIR_FREE(moment->def->parent_name); + + if (rep->parent->def) + moment->def->parent_name =3D g_strdup(rep->parent->def->name); + + rep->err =3D rep->writeMetadata(rep->vm, moment, rep->xmlopt, + rep->dir); + return 0; +} + + +int +qemuSnapshotDelete(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D snapshot->domain->conn->privateData; + int ret =3D -1; + virDomainMomentObjPtr snap =3D NULL; + virQEMUMomentRemove rem; + virQEMUMomentReparent rep; + bool metadata_only =3D !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_= ONLY); + int external =3D 0; + g_autoptr(virQEMUDriverConfig) cfg =3D virQEMUDriverGetConfig(driver); + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (!(snap =3D qemuSnapObjFromSnapshot(vm, snapshot))) + goto endjob; + + if (!metadata_only) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) && + virDomainSnapshotIsExternal(snap)) + external++; + if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) + virDomainMomentForEachDescendant(snap, + qemuSnapshotCountExternal, + &external); + if (external) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("deletion of %d external disk snapshots not " + "supported yet"), external); + goto endjob; + } + } + + if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { + rem.driver =3D driver; + rem.vm =3D vm; + rem.metadata_only =3D metadata_only; + rem.err =3D 0; + rem.current =3D virDomainSnapshotGetCurrent(vm->snapshots); + rem.found =3D false; + rem.momentDiscard =3D qemuDomainSnapshotDiscard; + virDomainMomentForEachDescendant(snap, qemuDomainMomentDiscardAll, + &rem); + if (rem.err < 0) + goto endjob; + if (rem.found) { + virDomainSnapshotSetCurrent(vm->snapshots, snap); + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->xmlopt, + cfg->snapshotDir) < 0)= { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to set snapshot '%s' as curre= nt"), + snap->def->name); + virDomainSnapshotSetCurrent(vm->snapshots, NULL); + goto endjob; + } + } + } + } else if (snap->nchildren) { + rep.dir =3D cfg->snapshotDir; + rep.parent =3D snap->parent; + rep.vm =3D vm; + rep.err =3D 0; + rep.xmlopt =3D driver->xmlopt; + rep.writeMetadata =3D qemuDomainSnapshotWriteMetadata; + virDomainMomentForEachChild(snap, + qemuSnapshotChildrenReparent, + &rep); + if (rep.err < 0) + goto endjob; + virDomainMomentMoveChildren(snap, snap->parent); + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + virDomainMomentDropChildren(snap); + ret =3D 0; + } else { + ret =3D qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata= _only); + } + + endjob: + qemuDomainObjEndJob(driver, vm); + + cleanup: + return ret; +} diff --git a/src/qemu/qemu_snapshot.h b/src/qemu/qemu_snapshot.h new file mode 100644 index 0000000000..8b3ebe87b1 --- /dev/null +++ b/src/qemu/qemu_snapshot.h @@ -0,0 +1,55 @@ +/* + * qemu_snapshot.h: Implementation and handling of snapshots + * + * 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 + * . + */ + +#pragma once + +#include "virconftypes.h" +#include "datatypes.h" +#include "qemu_conf.h" + +virDomainMomentObjPtr +qemuSnapObjFromName(virDomainObjPtr vm, + const char *name); + +virDomainMomentObjPtr +qemuSnapObjFromSnapshot(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot); + +int +qemuSnapshotFSFreeze(virDomainObjPtr vm, + const char **mountpoints, + unsigned int nmountpoints); +int +qemuSnapshotFSThaw(virDomainObjPtr vm, + bool report); + +virDomainSnapshotPtr +qemuSnapshotCreateXML(virDomainPtr domain, + virDomainObjPtr vm, + const char *xmlDesc, + unsigned int flags); + +int +qemuSnapshotRevert(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot, + unsigned int flags); + +int +qemuSnapshotDelete(virDomainObjPtr vm, + virDomainSnapshotPtr snapshot, + unsigned int flags); --=20 2.26.2