From nobody Wed May 14 20:54:53 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1518432978938349.74047805680175; Mon, 12 Feb 2018 02:56:18 -0800 (PST) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 525C55B303; Mon, 12 Feb 2018 10:56:17 +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 244C460C8D; Mon, 12 Feb 2018 10:56:17 +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 D82684A472; Mon, 12 Feb 2018 10:56:16 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id w1CAr6Yb025694 for ; Mon, 12 Feb 2018 05:53:06 -0500 Received: by smtp.corp.redhat.com (Postfix) id 405AF1243EC2; Mon, 12 Feb 2018 10:53:06 +0000 (UTC) Received: from localhost.localdomain (ovpn-204-25.brq.redhat.com [10.40.204.25]) by smtp.corp.redhat.com (Postfix) with ESMTP id 80952124BB37; Mon, 12 Feb 2018 10:53:05 +0000 (UTC) From: Michal Privoznik To: libvir-list@redhat.com Date: Mon, 12 Feb 2018 11:52:52 +0100 Message-Id: <7ae2434d2c8d0157037721f5d90138777d707f03.1518432508.git.mprivozn@redhat.com> In-Reply-To: References: In-Reply-To: References: X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 X-loop: libvir-list@redhat.com Subject: [libvirt] [PATCH 5/6] nwfilter: Convert _virNWFilterObjList to use virObjectRWLockable 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: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.26]); Mon, 12 Feb 2018 10:56:17 +0000 (UTC) X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" Based-on-work-of: John Ferlan Signed-off-by: Michal Privoznik --- cfg.mk | 1 - src/conf/virdomainobjlist.c | 3 +- src/conf/virnwfilterobj.c | 409 +++++++++++++++++++++++++++----------= ---- src/conf/virnwfilterobj.h | 3 - src/libvirt_private.syms | 1 - src/nwfilter/nwfilter_driver.c | 4 +- 6 files changed, 279 insertions(+), 142 deletions(-) diff --git a/cfg.mk b/cfg.mk index 78f805b27..89779fb05 100644 --- a/cfg.mk +++ b/cfg.mk @@ -242,7 +242,6 @@ useless_free_options =3D \ # y virNWFilterIncludeDefFree # n virNWFilterFreeName (returns int) # y virNWFilterObjFree -# n virNWFilterObjListFree FIXME # y virNWFilterRuleDefFree # n virNWFilterRuleFreeInstanceData (typedef) # y virNWFilterRuleInstFree diff --git a/src/conf/virdomainobjlist.c b/src/conf/virdomainobjlist.c index 87a742b1e..4d3cc94b3 100644 --- a/src/conf/virdomainobjlist.c +++ b/src/conf/virdomainobjlist.c @@ -206,7 +206,8 @@ virDomainObjPtr virDomainObjListFindByName(virDomainObj= ListPtr doms, =20 virObjectRWLockRead(doms); obj =3D virHashLookup(doms->objsName, name); - virObjectRef(obj); + if (obj) + virObjectRef(obj); virObjectRWUnlock(doms); if (obj) { virObjectLock(obj); diff --git a/src/conf/virnwfilterobj.c b/src/conf/virnwfilterobj.c index 6a54628b6..bb4d0a036 100644 --- a/src/conf/virnwfilterobj.c +++ b/src/conf/virnwfilterobj.c @@ -43,11 +43,20 @@ struct _virNWFilterObj { }; =20 static virClassPtr virNWFilterObjClass; +static virClassPtr virNWFilterObjListClass; static void virNWFilterObjDispose(void *obj); +static void virNWFilterObjListDispose(void *obj); =20 struct _virNWFilterObjList { - size_t count; - virNWFilterObjPtr *objs; + virObjectRWLockable parent; + + /* uuid string -> virNWFilterObj mapping + * for O(1), lockless lookup-by-uuid */ + virHashTable *objs; + + /* name -> virNWFilterObj mapping for O(1), + * lockless lookup-by-name */ + virHashTable *objsName; }; =20 static int virNWFilterObjOnceInit(void) @@ -58,6 +67,12 @@ static int virNWFilterObjOnceInit(void) virNWFilterObjDispose))) return -1; =20 + if (!(virNWFilterObjListClass =3D virClassNew(virClassForObjectRWLocka= ble(), + "virNWFilterObjList", + sizeof(virNWFilterObjList), + virNWFilterObjListDispose)= )) + return -1; + return 0; } =20 @@ -123,14 +138,14 @@ virNWFilterObjWantRemoved(virNWFilterObjPtr obj) } =20 =20 -void -virNWFilterObjListFree(virNWFilterObjListPtr nwfilters) +static void +virNWFilterObjListDispose(void *obj) { - size_t i; - for (i =3D 0; i < nwfilters->count; i++) - virObjectUnref(nwfilters->objs[i]); - VIR_FREE(nwfilters->objs); - VIR_FREE(nwfilters); + + virNWFilterObjListPtr nwfilters =3D obj; + + virHashFree(nwfilters->objs); + virHashFree(nwfilters->objsName); } =20 =20 @@ -139,8 +154,18 @@ virNWFilterObjListNew(void) { virNWFilterObjListPtr nwfilters; =20 - if (VIR_ALLOC(nwfilters) < 0) + if (virNWFilterObjInitialize() < 0) return NULL; + + if (!(nwfilters =3D virObjectRWLockableNew(virNWFilterObjListClass))) + return NULL; + + if (!(nwfilters->objs =3D virHashCreate(10, virObjectFreeHashData)) || + !(nwfilters->objsName =3D virHashCreate(10, virObjectFreeHashData)= )) { + virObjectUnref(nwfilters); + return NULL; + } + return nwfilters; } =20 @@ -149,20 +174,35 @@ void virNWFilterObjListRemove(virNWFilterObjListPtr nwfilters, virNWFilterObjPtr obj) { - size_t i; + char uuidstr[VIR_UUID_STRING_BUFLEN]; =20 + virUUIDFormat(obj->def->uuid, uuidstr); + virObjectRef(obj); virObjectUnlock(obj); =20 - for (i =3D 0; i < nwfilters->count; i++) { - virObjectLock(nwfilters->objs[i]); - if (nwfilters->objs[i] =3D=3D obj) { - virNWFilterObjEndAPI(&nwfilters->objs[i]); + virObjectRWLockWrite(nwfilters); + virObjectLock(obj); + virHashRemoveEntry(nwfilters->objs, uuidstr); + virHashRemoveEntry(nwfilters->objsName, obj->def->name); + virObjectUnlock(obj); + virObjectUnref(obj); + virObjectRWUnlock(nwfilters); +} + + +static virNWFilterObjPtr +virNWFilterObjListFindByUUIDLocked(virNWFilterObjListPtr nwfilters, + const unsigned char *uuid) +{ + virNWFilterObjPtr obj =3D NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(uuid, uuidstr); =20 - VIR_DELETE_ELEMENT(nwfilters->objs, i, nwfilters->count); - break; - } - virObjectUnlock(nwfilters->objs[i]); - } + obj =3D virHashLookup(nwfilters->objs, uuidstr); + if (obj) + virObjectRef(obj); + return obj; } =20 =20 @@ -170,20 +210,27 @@ virNWFilterObjPtr virNWFilterObjListFindByUUID(virNWFilterObjListPtr nwfilters, const unsigned char *uuid) { - size_t i; virNWFilterObjPtr obj; - virNWFilterDefPtr def; =20 - for (i =3D 0; i < nwfilters->count; i++) { - obj =3D nwfilters->objs[i]; + virObjectRWLockRead(nwfilters); + obj =3D virNWFilterObjListFindByUUIDLocked(nwfilters, uuid); + virObjectRWUnlock(nwfilters); + if (obj) virObjectLock(obj); - def =3D obj->def; - if (!memcmp(def->uuid, uuid, VIR_UUID_BUFLEN)) - return virObjectRef(obj); - virObjectUnlock(obj); - } + return obj; +} =20 - return NULL; + +static virNWFilterObjPtr +virNWFilterObjListFindByNameLocked(virNWFilterObjListPtr nwfilters, + const char *name) +{ + virNWFilterObjPtr obj; + + obj =3D virHashLookup(nwfilters->objsName, name); + if (obj) + virObjectRef(obj); + return obj; } =20 =20 @@ -191,20 +238,14 @@ virNWFilterObjPtr virNWFilterObjListFindByName(virNWFilterObjListPtr nwfilters, const char *name) { - size_t i; virNWFilterObjPtr obj; - virNWFilterDefPtr def; =20 - for (i =3D 0; i < nwfilters->count; i++) { - obj =3D nwfilters->objs[i]; + virObjectRWLockRead(nwfilters); + obj =3D virNWFilterObjListFindByNameLocked(nwfilters, name); + virObjectRWUnlock(nwfilters); + if (obj) virObjectLock(obj); - def =3D obj->def; - if (STREQ_NULLABLE(def->name, name)) - return virObjectRef(obj); - virObjectUnlock(obj); - } - - return NULL; + return obj; } =20 =20 @@ -253,9 +294,10 @@ _virNWFilterObjListDefLoopDetect(virNWFilterObjListPtr= nwfilters, break; } =20 - obj =3D virNWFilterObjListFindByName(nwfilters, - entry->include->filterref); + obj =3D virNWFilterObjListFindByNameLocked(nwfilters, + entry->include->filte= rref); if (obj) { + virObjectLock(obj); rc =3D _virNWFilterObjListDefLoopDetect(nwfilters, obj->de= f, filtername); virNWFilterObjEndAPI(&obj); @@ -325,51 +367,52 @@ virNWFilterDefEqual(const virNWFilterDef *def1, } =20 =20 -virNWFilterObjPtr -virNWFilterObjListAssignDef(virNWFilterObjListPtr nwfilters, - virNWFilterDefPtr def) +static virNWFilterObjPtr +virNWFilterObjListAssignDefLocked(virNWFilterObjListPtr nwfilters, + virNWFilterDefPtr def) { - virNWFilterObjPtr obj; - virNWFilterDefPtr objdef; + virNWFilterObjPtr obj =3D NULL; + virNWFilterObjPtr ret =3D NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; =20 - if ((obj =3D virNWFilterObjListFindByUUID(nwfilters, def->uuid))) { - objdef =3D obj->def; + virUUIDFormat(def->uuid, uuidstr); =20 - if (STRNEQ(def->name, objdef->name)) { + if ((obj =3D virNWFilterObjListFindByUUIDLocked(nwfilters, def->uuid))= ) { + virObjectLock(obj); + + if (STRNEQ(def->name, obj->def->name)) { virReportError(VIR_ERR_OPERATION_FAILED, _("filter with same UUID but different name " "('%s') already exists"), - objdef->name); - virNWFilterObjEndAPI(&obj); - return NULL; + obj->def->name); + goto cleanup; } - virNWFilterObjEndAPI(&obj); } else { - if ((obj =3D virNWFilterObjListFindByName(nwfilters, def->name))) { - char uuidstr[VIR_UUID_STRING_BUFLEN]; + if ((obj =3D virNWFilterObjListFindByNameLocked(nwfilters, def->na= me))) { + virObjectLock(obj); =20 - objdef =3D obj->def; - virUUIDFormat(objdef->uuid, uuidstr); + virUUIDFormat(obj->def->uuid, uuidstr); virReportError(VIR_ERR_OPERATION_FAILED, - _("filter '%s' already exists with uuid %s"), + _("filter '%s' already exists with UUID %s"), def->name, uuidstr); - virNWFilterObjEndAPI(&obj); - return NULL; + goto cleanup; } } =20 + virNWFilterObjEndAPI(&obj); + if (virNWFilterObjListDefLoopDetect(nwfilters, def) < 0) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("filter would introduce a loop")); - return NULL; + goto cleanup; } =20 =20 - if ((obj =3D virNWFilterObjListFindByName(nwfilters, def->name))) { + if ((obj =3D virNWFilterObjListFindByNameLocked(nwfilters, def->name))= ) { + virObjectLock(obj); =20 - objdef =3D obj->def; - if (virNWFilterDefEqual(def, objdef)) { - virNWFilterDefFree(objdef); + if (virNWFilterDefEqual(def, obj->def)) { + virNWFilterDefFree(obj->def); obj->def =3D def; return obj; } @@ -382,7 +425,7 @@ virNWFilterObjListAssignDef(virNWFilterObjListPtr nwfil= ters, return NULL; } =20 - virNWFilterDefFree(objdef); + virNWFilterDefFree(obj->def); obj->def =3D def; obj->newDef =3D NULL; return obj; @@ -391,35 +434,114 @@ virNWFilterObjListAssignDef(virNWFilterObjListPtr nw= filters, if (!(obj =3D virNWFilterObjNew())) return NULL; =20 - if (VIR_APPEND_ELEMENT_COPY(nwfilters->objs, - nwfilters->count, obj) < 0) { - virNWFilterObjEndAPI(&obj); - return NULL; + if (virHashAddEntry(nwfilters->objs, uuidstr, obj) < 0) + goto cleanup; + + if (virHashAddEntry(nwfilters->objsName, def->name, obj) < 0) { + virHashRemoveEntry(nwfilters->objs, uuidstr); + goto cleanup; } virObjectRef(obj); + + /* Increase refcounter again. We need two references for the + * hash tables above and one to return to the caller. */ + virObjectRef(obj); obj->def =3D def; =20 + ret =3D obj; + obj =3D NULL; + + cleanup: + virNWFilterObjEndAPI(&obj); + return ret; +} + + +virNWFilterObjPtr +virNWFilterObjListAssignDef(virNWFilterObjListPtr nwfilters, + virNWFilterDefPtr def) +{ + virNWFilterObjPtr obj; + + virObjectRWLockWrite(nwfilters); + obj =3D virNWFilterObjListAssignDefLocked(nwfilters, def); + virObjectRWUnlock(nwfilters); return obj; } =20 =20 +struct virNWFilterObjListData { + virNWFilterObjListFilter filter; + virConnectPtr conn; + int count; +}; + + +static int +virNWFilterObjListCount(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virNWFilterObjPtr obj =3D payload; + struct virNWFilterObjListData *data =3D opaque; + + virObjectLock(obj); + if (!data->filter || + data->filter(data->conn, obj->def)) + data->count++; + virObjectUnlock(obj); + return 0; +} + + int virNWFilterObjListNumOfNWFilters(virNWFilterObjListPtr nwfilters, virConnectPtr conn, virNWFilterObjListFilter filter) { - size_t i; - int nfilters =3D 0; - - for (i =3D 0; i < nwfilters->count; i++) { - virNWFilterObjPtr obj =3D nwfilters->objs[i]; - virObjectLock(obj); - if (!filter || filter(conn, obj->def)) - nfilters++; - virObjectUnlock(obj); + struct virNWFilterObjListData data =3D { filter, conn, 0 }; + + virObjectRWLockRead(nwfilters); + virHashForEach(nwfilters->objs, virNWFilterObjListCount, &data); + virObjectRWUnlock(nwfilters); + return data.count; +} + + +struct virNWFilterNameData { + virNWFilterObjListFilter filter; + virConnectPtr conn; + int oom; + int numnames; + int maxnames; + char **const names; +}; + + +static int +virNWFilterObjListCopyNames(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virNWFilterObjPtr obj =3D payload; + struct virNWFilterNameData *data =3D opaque; + + if (data->oom) + return 0; + + virObjectLock(obj); + if (data->filter && + !data->filter(data->conn, obj->def)) + goto cleanup; + if (data->numnames < data->maxnames) { + if (VIR_STRDUP(data->names[data->numnames], obj->def->name) < 0) + data->oom =3D 1; + else + data->numnames++; } - - return nfilters; + cleanup: + virObjectUnlock(obj); + return 0; } =20 =20 @@ -430,31 +552,64 @@ virNWFilterObjListGetNames(virNWFilterObjListPtr nwfi= lters, char **const names, int maxnames) { - int nnames =3D 0; + struct virNWFilterNameData data =3D {filter, conn, 0, 0, maxnames, nam= es}; size_t i; - virNWFilterDefPtr def; - - for (i =3D 0; i < nwfilters->count && nnames < maxnames; i++) { - virNWFilterObjPtr obj =3D nwfilters->objs[i]; - virObjectLock(obj); - def =3D obj->def; - if (!filter || filter(conn, def)) { - if (VIR_STRDUP(names[nnames], def->name) < 0) { - virObjectUnlock(obj); - goto failure; - } - nnames++; - } - virObjectUnlock(obj); + + virObjectRWLockRead(nwfilters); + virHashForEach(nwfilters->objs, virNWFilterObjListCopyNames, &data); + virObjectRWUnlock(nwfilters); + if (data.oom) { + for (i =3D 0; i < data.numnames; i++) + VIR_FREE(data.names[i]); + return -1; + } + + return data.numnames; +} + + +struct virNWFilterListData { + virConnectPtr conn; + virNWFilterPtr *nwfilters; + virNWFilterObjListFilter filter; + int nnwfilters; + bool error; +}; + + +static int +virNWFilterObjListPopulate(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + struct virNWFilterListData *data =3D opaque; + virNWFilterObjPtr obj =3D payload; + virNWFilterPtr nwfilter =3D NULL; + + if (data->error) + return 0; + + virObjectLock(obj); + + if (data->filter && + !data->filter(data->conn, obj->def)) + goto cleanup; + + if (!data->nwfilters) { + data->nnwfilters++; + goto cleanup; } =20 - return nnames; + if (!(nwfilter =3D virGetNWFilter(data->conn, obj->def->name, obj->def= ->uuid))) { + data->error =3D true; + goto cleanup; + } =20 - failure: - while (--nnames >=3D 0) - VIR_FREE(names[nnames]); + data->nwfilters[data->nnwfilters++] =3D nwfilter; =20 - return -1; + cleanup: + virObjectUnlock(obj); + return 0; } =20 =20 @@ -464,47 +619,33 @@ virNWFilterObjListExport(virConnectPtr conn, virNWFilterPtr **filters, virNWFilterObjListFilter filter) { - virNWFilterPtr *tmp_filters =3D NULL; - int nfilters =3D 0; - virNWFilterPtr nwfilter =3D NULL; - virNWFilterObjPtr obj =3D NULL; - virNWFilterDefPtr def; - size_t i; int ret =3D -1; + struct virNWFilterListData data =3D {.conn =3D conn, .nwfilters =3D NU= LL, + .filter =3D filter, .nnwfilters =3D 0, .error =3D false}; =20 - if (!filters) { - ret =3D nwfilters->count; + virObjectRWLockRead(nwfilters); + if (filters && VIR_ALLOC_N(data.nwfilters, virHashSize(nwfilters->objs= ) + 1) < 0) goto cleanup; - } =20 - if (VIR_ALLOC_N(tmp_filters, nwfilters->count + 1) < 0) + virHashForEach(nwfilters->objs, virNWFilterObjListPopulate, &data); + + if (data.error) goto cleanup; =20 - for (i =3D 0; i < nwfilters->count; i++) { - obj =3D nwfilters->objs[i]; - virObjectLock(obj); - def =3D obj->def; - if (!filter || filter(conn, def)) { - if (!(nwfilter =3D virGetNWFilter(conn, def->name, def->uuid))= ) { - virObjectUnlock(obj); - goto cleanup; - } - tmp_filters[nfilters++] =3D nwfilter; - } - virObjectUnlock(obj); + if (data.nnwfilters) { + /* trim the array to the final size */ + ignore_value(VIR_REALLOC_N(data.nwfilters, data.nnwfilters + 1)); + *filters =3D data.nwfilters; + data.nwfilters =3D NULL; } =20 - *filters =3D tmp_filters; - tmp_filters =3D NULL; - ret =3D nfilters; - + ret =3D data.nnwfilters; cleanup: - if (tmp_filters) { - for (i =3D 0; i < nfilters; i ++) - virObjectUnref(tmp_filters[i]); - } - VIR_FREE(tmp_filters); + virObjectRWUnlock(nwfilters); + while (data.nwfilters && data.nnwfilters) + virObjectUnref(data.nwfilters[--data.nnwfilters]); =20 + VIR_FREE(data.nwfilters); return ret; } =20 diff --git a/src/conf/virnwfilterobj.h b/src/conf/virnwfilterobj.h index 0281bc5f5..caff76e9a 100644 --- a/src/conf/virnwfilterobj.h +++ b/src/conf/virnwfilterobj.h @@ -56,9 +56,6 @@ virNWFilterObjWantRemoved(virNWFilterObjPtr obj); virNWFilterObjListPtr virNWFilterObjListNew(void); =20 -void -virNWFilterObjListFree(virNWFilterObjListPtr nwfilters); - void virNWFilterObjListRemove(virNWFilterObjListPtr nwfilters, virNWFilterObjPtr obj); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index edda56f80..fe63defb3 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1047,7 +1047,6 @@ virNWFilterObjListExport; virNWFilterObjListFindByName; virNWFilterObjListFindByUUID; virNWFilterObjListFindInstantiateFilter; -virNWFilterObjListFree; virNWFilterObjListGetNames; virNWFilterObjListLoadAllConfigs; virNWFilterObjListNew; diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c index c9bbae422..093844c9e 100644 --- a/src/nwfilter/nwfilter_driver.c +++ b/src/nwfilter/nwfilter_driver.c @@ -270,7 +270,7 @@ nwfilterStateInitialize(bool privileged, virNWFilterIPAddrMapShutdown(); =20 err_free_driverstate: - virNWFilterObjListFree(driver->nwfilters); + virObjectUnref(driver->nwfilters); VIR_FREE(driver); =20 return -1; @@ -354,7 +354,7 @@ nwfilterStateCleanup(void) } =20 /* free inactive nwfilters */ - virNWFilterObjListFree(driver->nwfilters); + virObjectUnref(driver->nwfilters); =20 virMutexDestroy(&driver->lock); VIR_FREE(driver); --=20 2.13.6 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list