From nobody Sun Nov 24 03:18:43 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=reject dis=none) header.from=linux.ibm.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1731519915584284.6952369804958; Wed, 13 Nov 2024 09:45:15 -0800 (PST) Received: by lists.libvirt.org (Postfix, from userid 996) id 75DEB11C; Wed, 13 Nov 2024 12:45:14 -0500 (EST) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id 1251D173C; Wed, 13 Nov 2024 12:40:30 -0500 (EST) Received: by lists.libvirt.org (Postfix, from userid 996) id 4797A1899; Wed, 13 Nov 2024 12:40:19 -0500 (EST) Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 659101899 for ; Wed, 13 Nov 2024 12:40:08 -0500 (EST) Received: from pps.filterd (m0356517.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 4ADGe2TY022570; Wed, 13 Nov 2024 17:40:07 GMT Received: from ppma23.wdc07v.mail.ibm.com (5d.69.3da9.ip4.static.sl-reverse.com [169.61.105.93]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 42vyu3897d-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 13 Nov 2024 17:40:06 +0000 (GMT) Received: from pps.filterd (ppma23.wdc07v.mail.ibm.com [127.0.0.1]) by ppma23.wdc07v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 4ADGShTH029753; Wed, 13 Nov 2024 17:40:05 GMT Received: from smtprelay05.wdc07v.mail.ibm.com ([172.16.1.72]) by ppma23.wdc07v.mail.ibm.com (PPS) with ESMTPS id 42tkjm6jd8-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 13 Nov 2024 17:40:05 +0000 Received: from smtpav06.dal12v.mail.ibm.com (smtpav06.dal12v.mail.ibm.com [10.241.53.105]) by smtprelay05.wdc07v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 4ADHe5ac26542726 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 13 Nov 2024 17:40:05 GMT Received: from smtpav06.dal12v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 2421558059; Wed, 13 Nov 2024 17:40:05 +0000 (GMT) Received: from smtpav06.dal12v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id C22135805D; Wed, 13 Nov 2024 17:40:04 +0000 (GMT) Received: from sbct-3.pok.ibm.com (unknown [9.47.158.153]) by smtpav06.dal12v.mail.ibm.com (Postfix) with ESMTP; Wed, 13 Nov 2024 17:40:04 +0000 (GMT) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=DKIM_INVALID,DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=2e+BlCdP4cg6PzAVa JbtKOxhlq3UTxqZ6x25tsVBQ6A=; b=XdHm0midMtY0PVAoEKumWd0we+gP1Nb7G 3IhbYsWaaGi4Irzlvm6YluAy5ARwtdpIO4OOLnQ8FS0eVW56FD9vzUztJ04VxKCu onwbe2nTxT+NXbiVlq5KYq4TjlvXFPUa11mJLr8mGy+u2D8ggkdExcAormncMTpW 6ZN8/7E665NvU5tye7NEKGR+QyHPgrIu0Ino5PRGSU4eBvTvAPmPsLGUoHQqcd2d Tu4p1xyQscP+1ojrnr6VuUEjxYewPXhKBl/LhI2c8MAi5K0YzRvXncjMXrlPfWNu LqdwjWcxi8zvHwsdianX6+WP0B6qTpmVAp3sqz1nz/1g7xRoXgnqQ== From: Stefan Berger To: devel@lists.libvirt.org Subject: [PATCH v4 11/11] qemu: Read back the profile name after creation of a TPM instance Date: Wed, 13 Nov 2024 12:39:51 -0500 Message-ID: <20241113173951.813781-12-stefanb@linux.ibm.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20241113173951.813781-1-stefanb@linux.ibm.com> References: <20241113173951.813781-1-stefanb@linux.ibm.com> MIME-Version: 1.0 X-TM-AS-GCONF: 00 X-Proofpoint-ORIG-GUID: N8s9e-b1HZhFt0zmX4IuNV01hUcamAfV X-Proofpoint-GUID: N8s9e-b1HZhFt0zmX4IuNV01hUcamAfV X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1051,Hydra:6.0.680,FMLib:17.12.62.30 definitions=2024-10-15_01,2024-10-11_01,2024-09-30_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 lowpriorityscore=0 bulkscore=0 adultscore=0 mlxlogscore=999 priorityscore=1501 impostorscore=0 suspectscore=0 phishscore=0 malwarescore=0 mlxscore=0 spamscore=0 clxscore=1015 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2409260000 definitions=main-2411130146 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: IDBCMFBWZMLYEZWVATFBNBMCEORD7WTB X-Message-ID-Hash: IDBCMFBWZMLYEZWVATFBNBMCEORD7WTB X-MailFrom: stefanb@linux.ibm.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0 CC: marcandre.lureau@redhat.com, Stefan Berger X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1731519917968116600 Content-Type: text/plain; charset="utf-8" Get the JSON profile that the swtpm instance was created with from the output of 'swtpm socket --tpm2 --print-info 0x20 --tpmstate ...'. Get the name of the profile from the JSON and set it in the current and persistent emulator descriptions as 'name' attribute and have the persistent description stored with this update. The user should avoid setting this 'name' attribute since it is meant to be read-only. The following is an example of how the XML could look like: If the user provided no profile node, and therefore swtpm_setup picked its default profile, the XML may now shows the 'name' attribute with the name of the profile. This makes the 'source' attribute now optional. Signed-off-by: Stefan Berger --- docs/formatdomain.rst | 16 ++--- src/conf/domain_conf.c | 18 +++--- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 13 +++- src/qemu/qemu_extdevice.c | 5 +- src/qemu/qemu_tpm.c | 100 ++++++++++++++++++++++++++++-- src/qemu/qemu_tpm.h | 3 +- src/util/virtpm.c | 1 + src/util/virtpm.h | 1 + 9 files changed, 135 insertions(+), 23 deletions(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 6539f620fa..20c86087ef 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -8131,7 +8131,7 @@ Example: usage of the TPM Emulator - + @@ -8229,12 +8229,14 @@ Example: usage of the TPM Emulator ``profile`` The ``profile`` node is used to set a profile for a TPM 2.0 given in the source attribute. This profile will be set when the TPM is initially - created and after that cannot be changed anymore. If no profile is prov= ided, - then swtpm will use the latest built-in 'default' profile or the default - profile set in swtpm_setup.conf. Otherwise swtpm_setup will search for a - profile with the given name with appended .json suffix in a configurable - local and then in a distro directory. If none could be found in either,= it - will fall back trying to use a built-in one. + created and after that cannot be changed anymore. Once a profile has be= en + set the name attribute will be updated with the name of the profile that + is running. If no profile is provided, then swtpm will use the latest + built-in 'default' profile or the default profile set in swtpm_setup.co= nf. + Otherwise swtpm_setup will search for a profile with the given name with + appended .json suffix in a configurable local and then in a distro + directory. If none could be found in either, it will fall back trying to + use a built-in one. =20 The built-in 'null' profile provides backwards compatibility with libtpms v0.9 but also restricts the user to use only TPM features that = were diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 7d91e3e958..6f6898014b 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3479,6 +3479,7 @@ void virDomainTPMDefFree(virDomainTPMDef *def) g_free(def->data.emulator.logfile); virBitmapFree(def->data.emulator.activePcrBanks); g_free(def->data.emulator.profile_source); + g_free(def->data.emulator.profile_name); break; case VIR_DOMAIN_TPM_TYPE_EXTERNAL: virObjectUnref(def->data.external.source); @@ -10926,10 +10927,6 @@ virDomainTPMDefParseXML(virDomainXMLOption *xmlopt, =20 if ((profile =3D virXPathNode("./backend/profile[1]", ctxt))) { def->data.emulator.profile_source =3D virXMLPropString(profile= , "source"); - if (!def->data.emulator.profile_source) { - virReportError(VIR_ERR_XML_ERROR, "%s", _("missing profile= source")); - goto error; - } if (virXMLPropEnum(profile, "remove_disabled", virDomainTPMProfileRemoveDisabledTypeFromSt= ring, VIR_XML_PROP_NONZERO, @@ -10938,6 +10935,7 @@ virDomainTPMDefParseXML(virDomainXMLOption *xmlopt, if (profile_remove_disabled !=3D VIR_DOMAIN_TPM_PROFILE_REMOVE= _DISABLED_NONE) def->data.emulator.profile_remove_disabled =3D virDomainTPMProfileRemoveDisabledTypeToString(profile_= remove_disabled); + def->data.emulator.profile_name =3D virXMLPropString(profile, = "name"); } break; case VIR_DOMAIN_TPM_TYPE_EXTERNAL: @@ -25134,12 +25132,18 @@ virDomainTPMDefFormat(virBuffer *buf, virDomainTPMSourceTypeTypeToString(def->data= .emulator.source_type)); virBufferEscapeString(&backendChildBuf, " path=3D'%s'/>\n", de= f->data.emulator.source_path); } - if (def->data.emulator.profile_source) { - virBufferAsprintf(&backendChildBuf, "data.emulator.profile_source); + if (def->data.emulator.profile_source || + def->data.emulator.profile_name) { + virBufferAddLit(&backendChildBuf, "data.emulator.profile_source) + virBufferAsprintf(&backendChildBuf, " source=3D'%s'", + def->data.emulator.profile_source); if (def->data.emulator.profile_remove_disabled) virBufferAsprintf(&backendChildBuf, " remove_disabled=3D'%s= '", def->data.emulator.profile_remove_disable= d); + if (def->data.emulator.profile_name) + virBufferAsprintf(&backendChildBuf, " name=3D'%s'", + def->data.emulator.profile_name); virBufferAddLit(&backendChildBuf, "/>\n"); } break; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index bd2740af26..45421d4772 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1493,6 +1493,7 @@ struct _virDomainTPMEmulatorDef { bool persistent_state; virBitmap *activePcrBanks; char *profile_source; /* 'source' profile was created from */ + char *profile_name; /* name read from active profile */ const char *profile_remove_disabled; }; =20 diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincom= mon.rng index d94ff9b4c3..e26e65fd7c 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -6056,9 +6056,11 @@ - - - + + + + + @@ -6067,6 +6069,11 @@ + + + + + diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c index dc1bb56237..a6f31f9773 100644 --- a/src/qemu/qemu_extdevice.c +++ b/src/qemu/qemu_extdevice.c @@ -175,6 +175,7 @@ qemuExtDevicesStart(virQEMUDriver *driver, virDomainObj *vm, bool incomingMigration) { + virDomainDef *persistentDef =3D vm->newDef; virDomainDef *def =3D vm->def; size_t i; =20 @@ -189,9 +190,11 @@ qemuExtDevicesStart(virQEMUDriver *driver, =20 for (i =3D 0; i < def->ntpms; i++) { virDomainTPMDef *tpm =3D def->tpms[i]; + virDomainTPMDef *persistentTPMDef =3D persistentDef->tpms[i]; =20 if (tpm->type =3D=3D VIR_DOMAIN_TPM_TYPE_EMULATOR && - qemuExtTPMStart(driver, vm, tpm, incomingMigration) < 0) + qemuExtTPMStart(driver, vm, tpm, persistentTPMDef, + incomingMigration) < 0) return -1; } =20 diff --git a/src/qemu/qemu_tpm.c b/src/qemu/qemu_tpm.c index a7eee501bf..2fb3796910 100644 --- a/src/qemu/qemu_tpm.c +++ b/src/qemu/qemu_tpm.c @@ -627,15 +627,89 @@ qemuTPMVirCommandSwtpmAddTPMState(virCommand *cmd, } } =20 +/* qemuTPMEmulatorUpdateProfileName: + * + * @emulator: TPM emulator definition + * @persistentTPMDef: TPM definition from the persistent domain definition + * @cfg: virQEMUDriverConfig + * @saveDef: whether caller should save the persistent domain def + */ +static int +qemuTPMEmulatorUpdateProfileName(virDomainTPMEmulatorDef *emulator, + virDomainTPMDef *persistentTPMDef, + const virQEMUDriverConfig *cfg, + bool *saveDef) +{ + g_autoptr(virJSONValue) object =3D NULL; + g_autofree char *stderr_buf =3D NULL; + g_autofree char *stdout_buf =3D NULL; + g_autoptr(virCommand) cmd =3D NULL; + g_autofree char *swtpm =3D NULL; + virJSONValue *active_profile; + const char *profile_name; + int exitstatus; + + if (emulator->version !=3D VIR_DOMAIN_TPM_VERSION_2_0 || + !virTPMSwtpmCapsGet(VIR_TPM_SWTPM_FEATURE_CMDARG_PRINT_INFO)) + return 0; + + swtpm =3D virTPMGetSwtpm(); + if (!swtpm) + return -1; + + cmd =3D virCommandNew(swtpm); + + virCommandSetUID(cmd, cfg->swtpm_user); /* should be uid of 'tss' or '= root' */ + virCommandSetGID(cmd, cfg->swtpm_group); + + virCommandAddArgList(cmd, "socket", "--print-info", "0x20", "--tpm2", = NULL); + + qemuTPMVirCommandSwtpmAddTPMState(cmd, emulator); + + if (qemuTPMVirCommandSwtpmAddEncryption(cmd, emulator, swtpm) < 0) + return -1; + + virCommandClearCaps(cmd); + + virCommandSetOutputBuffer(cmd, &stdout_buf); + virCommandSetErrorBuffer(cmd, &stderr_buf); + + if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus !=3D 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not run '%1$s --print-info'. exitstatus: %= 2$d; stderr: %3$s\n"), + swtpm, exitstatus, stderr_buf); + return -1; + } + + if (!(object =3D virJSONValueFromString(stdout_buf))) + return -1; + + if (!(active_profile =3D virJSONValueObjectGetObject(object, "ActivePr= ofile"))) + return -1; + + profile_name =3D g_strdup(virJSONValueObjectGetString(active_profile, = "Name")); + + g_free(emulator->profile_name); + emulator->profile_name =3D g_strdup(profile_name); + + *saveDef =3D true; + g_free(persistentTPMDef->data.emulator.profile_name); + persistentTPMDef->data.emulator.profile_name =3D g_strdup(profile_name= ); + + return 0; +} + /* * qemuTPMEmulatorBuildCommand: * * @tpm: TPM definition + * @persistentTPMDef: TPM definition from the persistent domain definition * @vmname: The name of the VM * @vmuuid: The UUID of the VM * @privileged: whether we are running in privileged mode * @cfg: virQEMUDriverConfig * @incomingMigration: whether we have an incoming migration + * @saveDef: whether caller should save the persistent domain def * * Create the virCommand use for starting the emulator * Do some initializations on the way, such as creation of storage @@ -643,11 +717,13 @@ qemuTPMVirCommandSwtpmAddTPMState(virCommand *cmd, */ static virCommand * qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm, + virDomainTPMDef *persistentTPMDef, const char *vmname, const unsigned char *vmuuid, bool privileged, const virQEMUDriverConfig *cfg, - bool incomingMigration) + bool incomingMigration, + bool *saveDef) { g_autoptr(virCommand) cmd =3D NULL; bool created =3D false; @@ -696,6 +772,11 @@ qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm, incomingMigration) < 0) goto error; =20 + if (run_setup && !incomingMigration && + qemuTPMEmulatorUpdateProfileName(&tpm->data.emulator, persistentTP= MDef, + cfg, saveDef) < 0) + goto error; + if (!incomingMigration && qemuTPMEmulatorReconfigure(&tpm->data.emulator, cfg, secretuuid) <= 0) goto error; @@ -995,6 +1076,7 @@ qemuExtTPMEmulatorSetupCgroup(const char *swtpmStateDi= r, * @driver: QEMU driver * @vm: the domain object * @tpm: TPM definition + * @persistentTPMDef: TPM definition from persistent domain definition * @shortName: short and unique name of the domain * @incomingMigration: whether we have an incoming migration * @@ -1007,6 +1089,7 @@ qemuTPMEmulatorStart(virQEMUDriver *driver, virDomainObj *vm, const char *shortName, virDomainTPMDef *tpm, + virDomainTPMDef *persistentTPMDef, bool incomingMigration) { g_autoptr(virCommand) cmd =3D NULL; @@ -1015,6 +1098,7 @@ qemuTPMEmulatorStart(virQEMUDriver *driver, g_autofree char *pidfile =3D NULL; virTimeBackOffVar timebackoff; const unsigned long long timeout =3D 1000; /* ms */ + bool saveDef =3D false; pid_t pid =3D -1; bool lockMetadataException =3D false; =20 @@ -1023,12 +1107,18 @@ qemuTPMEmulatorStart(virQEMUDriver *driver, /* stop any left-over TPM emulator for this VM */ qemuTPMEmulatorStop(cfg->swtpmStateDir, shortName); =20 - if (!(cmd =3D qemuTPMEmulatorBuildCommand(tpm, vm->def->name, vm->def-= >uuid, + if (!(cmd =3D qemuTPMEmulatorBuildCommand(tpm, persistentTPMDef, + vm->def->name, vm->def->uuid, driver->privileged, cfg, - incomingMigration))) + incomingMigration, + &saveDef))) return -1; =20 + if (saveDef && + virDomainDefSave(vm->newDef, driver->xmlopt, cfg->configDir) < 0) + goto error; + if (qemuExtDeviceLogCommand(driver, vm, cmd, "TPM Emulator") < 0) return -1; =20 @@ -1212,6 +1302,7 @@ int qemuExtTPMStart(virQEMUDriver *driver, virDomainObj *vm, virDomainTPMDef *tpm, + virDomainTPMDef *persistentTPMDef, bool incomingMigration) { g_autofree char *shortName =3D virDomainDefGetShortName(vm->def); @@ -1219,7 +1310,8 @@ qemuExtTPMStart(virQEMUDriver *driver, if (!shortName) return -1; =20 - return qemuTPMEmulatorStart(driver, vm, shortName, tpm, incomingMigrat= ion); + return qemuTPMEmulatorStart(driver, vm, shortName, tpm, persistentTPMD= ef, + incomingMigration); } =20 =20 diff --git a/src/qemu/qemu_tpm.h b/src/qemu/qemu_tpm.h index 3071dc3f71..7096060a2a 100644 --- a/src/qemu/qemu_tpm.h +++ b/src/qemu/qemu_tpm.h @@ -44,9 +44,10 @@ void qemuExtTPMCleanupHost(virQEMUDriver *driver, int qemuExtTPMStart(virQEMUDriver *driver, virDomainObj *vm, virDomainTPMDef *def, + virDomainTPMDef *persistentDefTPM, bool incomingMigration) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) - ATTRIBUTE_NONNULL(3) + ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4) G_GNUC_WARN_UNUSED_RESULT; =20 void qemuExtTPMStop(virQEMUDriver *driver, diff --git a/src/util/virtpm.c b/src/util/virtpm.c index 1c736b0229..4016ad8fc4 100644 --- a/src/util/virtpm.c +++ b/src/util/virtpm.c @@ -42,6 +42,7 @@ VIR_ENUM_IMPL(virTPMSwtpmFeature, "cmdarg-migration", "nvram-backend-dir", "nvram-backend-file", + "cmdarg-print-info", ); =20 VIR_ENUM_IMPL(virTPMSwtpmSetupFeature, diff --git a/src/util/virtpm.h b/src/util/virtpm.h index 9ca09c2d80..03fb92629a 100644 --- a/src/util/virtpm.h +++ b/src/util/virtpm.h @@ -33,6 +33,7 @@ typedef enum { VIR_TPM_SWTPM_FEATURE_CMDARG_MIGRATION, VIR_TPM_SWTPM_FEATURE_NVRAM_BACKEND_DIR, VIR_TPM_SWTPM_FEATURE_NVRAM_BACKEND_FILE, + VIR_TPM_SWTPM_FEATURE_CMDARG_PRINT_INFO, =20 VIR_TPM_SWTPM_FEATURE_LAST } virTPMSwtpmFeature; --=20 2.47.0