From nobody Mon Feb 9 08:28:21 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=suse.de Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1654593619827555.8402750004028; Tue, 7 Jun 2022 02:20:19 -0700 (PDT) Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-369-JwpkT_kWPhun4xsV-dxvvQ-1; Tue, 07 Jun 2022 05:20:05 -0400 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id F368B804199; Tue, 7 Jun 2022 09:19:50 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (unknown [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id D81581121314; Tue, 7 Jun 2022 09:19:50 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 3B64B1947B9E; Tue, 7 Jun 2022 09:19:50 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id E530419451F0 for ; Tue, 7 Jun 2022 09:19:46 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id B8046C23DBF; Tue, 7 Jun 2022 09:19:46 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast04.extmail.prod.ext.rdu2.redhat.com [10.11.55.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id B3FF2C15E72 for ; Tue, 7 Jun 2022 09:19:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 9769E10726A2 for ; Tue, 7 Jun 2022 09:19:46 +0000 (UTC) Received: from smtp-out1.suse.de (smtp-out1.suse.de [195.135.220.28]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-53-6jbiIn8TPDS4BzgMZZDzkw-1; Tue, 07 Jun 2022 05:19:43 -0400 Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id 4289821B38; Tue, 7 Jun 2022 09:19:42 +0000 (UTC) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 07B9E13638; Tue, 7 Jun 2022 09:19:42 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id oOhEAC4Yn2IoLwAAMHmgww (envelope-from ); Tue, 07 Jun 2022 09:19:42 +0000 X-MC-Unique: JwpkT_kWPhun4xsV-dxvvQ-1 X-Original-To: libvir-list@listman.corp.redhat.com X-MC-Unique: 6jbiIn8TPDS4BzgMZZDzkw-1 From: Claudio Fontana To: =?UTF-8?q?Daniel=20P=20=2E=20Berrang=C3=A9?= Subject: [libvirt RFCv11 03/33] qemu: saveimage: rework image read/write to be O_DIRECT friendly Date: Tue, 7 Jun 2022 11:19:06 +0200 Message-Id: <20220607091936.7948-4-cfontana@suse.de> In-Reply-To: <20220607091936.7948-1-cfontana@suse.de> References: <20220607091936.7948-1-cfontana@suse.de> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.85 on 10.11.54.8 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: libvir-list@redhat.com, Claudio Fontana , "Dr . David Alan Gilbert" , Anthony Iliopoulos Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 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-ZM-MESSAGEID: 1654593620421100007 Content-Type: text/plain; charset="utf-8"; x-default="true" maintain a compatible image format (still QEMU_SAVE_VERSION 2), but change the on-disk representation to be more O_DIRECT friendly: 1) ensure that the header struct fields are packed, so we can be sure no padding will ever ruin the day in the future 2) finish the libvirt header (header + xml + cookie) with zero padding, in order to ensure that the QEMU VM (QEVM Magic) is aligned. Adapt the read and write of the libvirt header accordingly. The old images should be still loadable after this change, and the new images should be still loadable in old libvirt as well. Signed-off-by: Claudio Fontana --- src/qemu/qemu_saveimage.c | 267 ++++++++++++++++++++++++-------------- src/qemu/qemu_saveimage.h | 20 +-- 2 files changed, 182 insertions(+), 105 deletions(-) diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c index 4fd4c5cfcd..276033ff6a 100644 --- a/src/qemu/qemu_saveimage.c +++ b/src/qemu/qemu_saveimage.c @@ -139,12 +139,12 @@ virQEMUSaveDataWrite(virQEMUSaveData *data, int fd, const char *path) { + g_autofree void *base =3D NULL; + char *buf, *cur; virQEMUSaveHeader *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; =20 xml_len =3D strlen(data->xml) + 1; if (data->cookie) @@ -165,42 +165,190 @@ virQEMUSaveDataWrite(virQEMUSaveData *data, return -1; } } + /* align data_len */ + len =3D sizeof(*header) + header->data_len; + header->data_len +=3D virFileDirectAlign(len) - len; =20 - zerosLen =3D header->data_len - len; - zeros =3D g_new0(char, zerosLen); + buf =3D virFileDirectBufferNew(&base, sizeof(*header) + header->data_l= en); + cur =3D buf; =20 if (data->cookie) header->cookieOffset =3D xml_len; =20 - if (safewrite(fd, header, sizeof(*header)) !=3D sizeof(*header)) { - virReportSystemError(errno, - _("failed to write header to domain save file= '%s'"), - path); - return -1; + memcpy(cur, header, sizeof(*header)); + cur +=3D sizeof(*header); + memcpy(cur, data->xml, xml_len); + cur +=3D xml_len; + if (data->cookie) { + memcpy(cur, data->cookie, cookie_len); + cur +=3D cookie_len; } =20 - if (safewrite(fd, data->xml, xml_len) !=3D xml_len) { + if (virFileDirectWrite(fd, buf, sizeof(*header) + header->data_len) < = 0) { virReportSystemError(errno, - _("failed to write domain xml to '%s'"), + _("failed to write libvirt header of domain s= ave file '%s'"), path); return -1; } =20 - if (data->cookie && - safewrite(fd, data->cookie, cookie_len) !=3D cookie_len) { + return 0; +} + +/* virQEMUSaveDataRead: + * + * Reads libvirt's header (including domain XML) from a saved image. + * + * Returns -1 on generic failure, -3 on a corrupted image, or 0 on success. + */ +int +virQEMUSaveDataRead(virQEMUSaveData *data, + int fd, + const char *path) +{ + g_autofree void *base =3D NULL; + virQEMUSaveHeader *header =3D &data->header; + size_t xml_len; + size_t cookie_len; + ssize_t rv; + void *dst; + int oflags =3D fcntl(fd, F_GETFL); + bool isDirect =3D O_DIRECT && (oflags & O_DIRECT); + size_t buflen =3D 1024 * 1024; + char *buf =3D NULL; + void *src =3D NULL; + + header =3D &data->header; + if (isDirect) { + buf =3D virFileDirectBufferNew(&base, buflen); + src =3D buf; + rv =3D virFileDirectReadCopy(fd, &src, buflen, header, sizeof(*hea= der)); + } else { + rv =3D saferead(fd, header, sizeof(*header)); + } + if (rv < 0) { virReportSystemError(errno, - _("failed to write cookie to '%s'"), + _("failed to read libvirt header of domain sa= ve file '%s'"), path); return -1; } + if (rv < sizeof(*header)) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("domain save file '%s' libvirt header appears tru= ncated"), + path); + return -3; + } + rv -=3D sizeof(*header); =20 - if (safewrite(fd, zeros, zerosLen) !=3D zerosLen) { - virReportSystemError(errno, - _("failed to write padding to '%s'"), - path); + 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) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("domain save file '%s' seems incomplete"), + path); + return -3; + } + 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->cookieOffset) + xml_len =3D header->cookieOffset; + else + xml_len =3D header->data_len; =20 + if (xml_len <=3D 0) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("invalid xml length: %lu"), (unsigned long)xml_le= n); + return -1; + } + if (header->data_len < xml_len) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("invalid cookieOffset: %u"), header->cookieOffset= ); + return -1; + } + cookie_len =3D header->data_len - xml_len; + data->xml =3D g_new0(char, xml_len); + dst =3D data->xml; + if (rv > 0) { + if (isDirect) { + rv -=3D virFileDirectCopyBuf(&src, rv, &dst, &xml_len); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected lef= tover data in non-direct mode")); + return -1; + } + } + if (xml_len > 0) { + if (isDirect) { + rv =3D virFileDirectReadCopy(fd, &src, buflen, dst, xml_len); + } else { + rv =3D saferead(fd, dst, xml_len); + } + if (rv < 0) { + virReportSystemError(errno, + _("failed to read libvirt xml in domain s= ave file '%s'"), + path); + return -1; + } + if (rv < xml_len) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("domain save file '%s' xml seems incomplete"), + path); + return -3; + } + rv -=3D xml_len; + } + if (cookie_len > 0) { + data->cookie =3D g_new0(char, cookie_len); + dst =3D data->cookie; + if (rv > 0) { + if (isDirect) { + rv -=3D virFileDirectCopyBuf(&src, rv, &dst, &cookie_len); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected= leftover data in non-direct mode")); + return -1; + } + } + if (cookie_len > 0) { + if (isDirect) { + rv =3D virFileDirectReadCopy(fd, &src, buflen, dst, cookie= _len); + } else { + rv =3D saferead(fd, dst, cookie_len); + } + if (rv < 0) { + virReportSystemError(errno, + _("failed to read libvirt cookie in d= omain save file '%s'"), + path); + return -1; + } + if (rv < cookie_len) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("domain save file '%s' cookie seems incom= plete"), + path); + return -3; + } + rv -=3D cookie_len; + } + } + /* + * we should now be aligned and ready to read the QEVM. + */ + if (rv > 0) { + if (isDirect) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected una= ligned data in direct mode")); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected lef= tover data in non-direct mode")); + return -1; + } + } return 0; } =20 @@ -444,11 +592,8 @@ qemuSaveImageOpen(virQEMUDriver *driver, VIR_AUTOCLOSE fd =3D -1; int ret =3D -1; g_autoptr(virQEMUSaveData) data =3D NULL; - virQEMUSaveHeader *header; g_autoptr(virDomainDef) def =3D NULL; int oflags =3D open_write ? O_RDWR : O_RDONLY; - size_t xml_len; - size_t cookie_len; =20 if (bypass_cache) { int directFlag =3D virFileDirectFdFlag(); @@ -469,89 +614,17 @@ qemuSaveImageOpen(virQEMUDriver *driver, return -1; =20 data =3D g_new0(virQEMUSaveData, 1); - - header =3D &data->header; - if (saferead(fd, header, sizeof(*header)) !=3D sizeof(*header)) { - if (unlink_corrupt) { + ret =3D virQEMUSaveDataRead(data, fd, path); + if (ret < 0) { + if (unlink_corrupt && ret =3D=3D -3) { 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; - } + return ret; } =20 /* Create a domain from this XML */ diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h index 391cd55ed0..f12374b3d7 100644 --- a/src/qemu/qemu_saveimage.h +++ b/src/qemu/qemu_saveimage.h @@ -36,14 +36,14 @@ G_STATIC_ASSERT(sizeof(QEMU_SAVE_MAGIC) =3D=3D sizeof(Q= EMU_SAVE_PARTIAL)); =20 typedef struct _virQEMUSaveHeader virQEMUSaveHeader; 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]; -}; + char magic[sizeof(QEMU_SAVE_MAGIC)-1]; /* 16 bytes */ + uint32_t version; /* 4 bytes */ + uint32_t data_len; /* 4 bytes */ + uint32_t was_running; /* 4 bytes */ + uint32_t compressed; /* 4 bytes */ + uint32_t cookieOffset; /* 4 bytes */ + uint32_t unused[14]; /* 56 bytes */ +} ATTRIBUTE_PACKED; /* =3D 92 bytes */ =20 =20 typedef struct _virQEMUSaveData virQEMUSaveData; @@ -103,6 +103,10 @@ int virQEMUSaveDataWrite(virQEMUSaveData *data, int fd, const char *path); +int +virQEMUSaveDataRead(virQEMUSaveData *data, + int fd, + const char *path); =20 virQEMUSaveData * virQEMUSaveDataNew(char *domXML, --=20 2.26.2