In order to save some CPU cycles we will collect all the necessary data
to delete external snapshot before we even start. They will be later
used by code that deletes the snapshots and updates metadata when
needed.
With external snapshots we need data that libvirt gets from running QEMU
process so if the VM is not running we need to start paused QEMU process
for the snapshot deletion and kill at afterwards.
Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
---
src/qemu/qemu_snapshot.c | 144 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 142 insertions(+), 2 deletions(-)
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
index da9b4c30f0..dade5dcea4 100644
--- a/src/qemu/qemu_snapshot.c
+++ b/src/qemu/qemu_snapshot.c
@@ -2232,6 +2232,120 @@ qemuSnapshotRevert(virDomainObj *vm,
}
+typedef struct {
+ virDomainMomentObj *parentSnap;
+ virDomainSnapshotDiskDef *snapDisk;
+ virDomainDiskDef *domDisk;
+ virDomainDiskDef *parentDomDisk;
+ virStorageSource *diskSrc;
+ virStorageSource *parentDiskSrc;
+ virStorageSource *prevDiskSrc;
+ qemuBlockJobData *job;
+} qemuSnapshotDeleteExternalData;
+
+
+static virDomainMomentObj*
+qemuSnapshotFindParentSnapForDisk(virDomainMomentObj *snap,
+ virDomainSnapshotDiskDef *snapDisk)
+{
+ virDomainMomentObj *parentSnap = snap->parent;
+
+ while (parentSnap) {
+ ssize_t i;
+ virDomainSnapshotDef *parentSnapdef = virDomainSnapshotObjGetDef(parentSnap);
+
+ if (!parentSnapdef)
+ break;
+
+ for (i = 0; i < parentSnapdef->ndisks; i++) {
+ virDomainSnapshotDiskDef *parentSnapDisk = &(parentSnapdef->disks[i]);
+
+ if (parentSnapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
+ STREQ(snapDisk->name, parentSnapDisk->name)) {
+ return parentSnap;
+ }
+ }
+
+ parentSnap = parentSnap->parent;
+ }
+
+ return NULL;
+}
+
+
+static GPtrArray*
+qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
+ virDomainMomentObj *snap)
+{
+ ssize_t i;
+ virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
+ g_autoptr(GPtrArray) ret = g_ptr_array_new_full(0, g_free);
+
+ for (i = 0; i < snapdef->ndisks; i++) {
+ g_autofree qemuSnapshotDeleteExternalData *data = NULL;
+ virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
+
+ if (snapDisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)
+ continue;
+
+ data = g_new0(qemuSnapshotDeleteExternalData, 1);
+ data->snapDisk = snapDisk;
+
+ data->domDisk = qemuDomainDiskByName(vm->def, snapDisk->name);
+ if (!data->domDisk)
+ return NULL;
+
+ data->diskSrc = virStorageSourceChainLookup(data->domDisk->src, NULL,
+ data->snapDisk->src->path,
+ NULL, &data->prevDiskSrc);
+ if (!data->diskSrc)
+ return NULL;
+
+ if (!virStorageSourceIsSameLocation(data->diskSrc, data->snapDisk->src)) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("VM disk source and snapshot disk source are not the same"));
+ return NULL;
+ }
+
+ data->parentDomDisk = virDomainDiskByTarget(snapdef->parent.dom,
+ data->snapDisk->name);
+ if (!data->parentDomDisk) {
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("failed to find disk '%s' in snapshot VM XML"),
+ snapDisk->name);
+ return NULL;
+ }
+
+ data->parentDiskSrc = data->diskSrc->backingStore;
+ if (!data->parentDiskSrc) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("failed to find parent disk source in backing chain"));
+ return NULL;
+ }
+
+ if (!virStorageSourceIsSameLocation(data->parentDiskSrc, data->parentDomDisk->src)) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("snapshot VM disk source and parent disk source are not the same"));
+ return NULL;
+ }
+
+ data->parentSnap = qemuSnapshotFindParentSnapForDisk(snap, data->snapDisk);
+
+ if (data->parentSnap && !virDomainSnapshotIsExternal(data->parentSnap)) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
+ _("parent snapshot '%s' for disk '%s' is internal snapshot"),
+ snapDisk->name,
+ data->parentSnap->def->name);
+ return NULL;
+ }
+
+ g_ptr_array_add(ret, g_steal_pointer(&data));
+ }
+
+ return g_steal_pointer(&ret);
+}
+
+
typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
struct _virQEMUMomentReparent {
const char *dir;
@@ -2504,9 +2618,9 @@ qemuSnapshotDeleteValidate(virDomainMomentObj *snap,
}
if (virDomainSnapshotIsExternal(snap) &&
- !(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
+ (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
- _("deletion of external disk snapshots not supported"));
+ _("deletion of external disk snapshot with internal children disk snapshots not supported"));
return -1;
}
@@ -2523,6 +2637,8 @@ qemuSnapshotDelete(virDomainObj *vm,
int ret = -1;
virDomainMomentObj *snap = NULL;
bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
+ bool stop_qemu = false;
+ g_autoptr(GPtrArray) externalData = NULL;
virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
@@ -2537,6 +2653,25 @@ qemuSnapshotDelete(virDomainObj *vm,
if (!metadata_only) {
if (qemuSnapshotDeleteValidate(snap, flags) < 0)
goto endjob;
+
+ if (virDomainSnapshotIsExternal(snap) &&
+ !(flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+ VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))) {
+ if (!virDomainObjIsActive(vm)) {
+ if (qemuProcessStart(NULL, driver, vm, NULL, VIR_ASYNC_JOB_NONE,
+ NULL, -1, NULL, NULL,
+ VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
+ VIR_QEMU_PROCESS_START_PAUSED) < 0) {
+ goto endjob;
+ }
+
+ stop_qemu = true;
+ }
+
+ externalData = qemuSnapshotDeleteExternalPrepare(vm, snap);
+ if (!externalData)
+ goto endjob;
+ }
}
if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
@@ -2547,6 +2682,11 @@ qemuSnapshotDelete(virDomainObj *vm,
}
endjob:
+ if (stop_qemu) {
+ qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN,
+ VIR_ASYNC_JOB_NONE, 0);
+ }
+
qemuDomainObjEndJob(vm);
return ret;
--
2.37.2
On Tue, Aug 23, 2022 at 18:32:23 +0200, Pavel Hrdina wrote:
> In order to save some CPU cycles we will collect all the necessary data
> to delete external snapshot before we even start. They will be later
> used by code that deletes the snapshots and updates metadata when
> needed.
>
> With external snapshots we need data that libvirt gets from running QEMU
> process so if the VM is not running we need to start paused QEMU process
> for the snapshot deletion and kill at afterwards.
>
> Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
> ---
> src/qemu/qemu_snapshot.c | 144 ++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 142 insertions(+), 2 deletions(-)
First a few general notes for all upcoming patches.
1) All of the non-trivial functions dealing with snapshots should have
documentation explaing what they do. The snapshot code, as you certainly
know, is very complex and in many cases un-obvious. Having them will
save us (or anybody else wanting to maintain the code) some headaches in
the future.
2) I'd also suggest that you add a knowledge base internals article
outlining how snapshot deletion is supposed to work. In case there are
some user-visible caveats, we also have an user-focused article on
snapshots ( kbase/snapshots.rst ).
3) I'll try to assess the code also from the point of view of changes
needed to implement reversion of snapshots and the intricacies that
might bring for deletion.
> diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
> index da9b4c30f0..dade5dcea4 100644
> --- a/src/qemu/qemu_snapshot.c
> +++ b/src/qemu/qemu_snapshot.c
> @@ -2232,6 +2232,120 @@ qemuSnapshotRevert(virDomainObj *vm,
> }
>
>
> +typedef struct {
> + virDomainMomentObj *parentSnap;
> + virDomainSnapshotDiskDef *snapDisk;
> + virDomainDiskDef *domDisk;
> + virDomainDiskDef *parentDomDisk;
> + virStorageSource *diskSrc;
> + virStorageSource *parentDiskSrc;
> + virStorageSource *prevDiskSrc;
> + qemuBlockJobData *job;
> +} qemuSnapshotDeleteExternalData;
> +
> +
> +static virDomainMomentObj*
> +qemuSnapshotFindParentSnapForDisk(virDomainMomentObj *snap,
> + virDomainSnapshotDiskDef *snapDisk)
> +{
> + virDomainMomentObj *parentSnap = snap->parent;
> +
> + while (parentSnap) {
> + ssize_t i;
> + virDomainSnapshotDef *parentSnapdef = virDomainSnapshotObjGetDef(parentSnap);
> +
> + if (!parentSnapdef)
> + break;
> +
> + for (i = 0; i < parentSnapdef->ndisks; i++) {
> + virDomainSnapshotDiskDef *parentSnapDisk = &(parentSnapdef->disks[i]);
> +
> + if (parentSnapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
> + STREQ(snapDisk->name, parentSnapDisk->name)) {
> + return parentSnap;
> + }
> + }
> +
> + parentSnap = parentSnap->parent;
> + }
> +
> + return NULL;
> +}
> +
> +
> +static GPtrArray*
> +qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
> + virDomainMomentObj *snap)
> +{
> + ssize_t i;
> + virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
> + g_autoptr(GPtrArray) ret = g_ptr_array_new_full(0, g_free);
Is there any specific requirement to access these randomly?
(specifically why GPtrArray is used instead of e.g. a G(S)List?
> + for (i = 0; i < snapdef->ndisks; i++) {
> + g_autofree qemuSnapshotDeleteExternalData *data = NULL;
> + virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
> +
> + if (snapDisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)
> + continue;
[...]
> typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
> struct _virQEMUMomentReparent {
> const char *dir;
> @@ -2504,9 +2618,9 @@ qemuSnapshotDeleteValidate(virDomainMomentObj *snap,
> }
>
> if (virDomainSnapshotIsExternal(snap) &&
> - !(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
> + (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN)) {
> virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> - _("deletion of external disk snapshots not supported"));
> + _("deletion of external disk snapshot with internal children disk snapshots not supported"));
This seems a bit unrelated to this patch.
> return -1;
> }
>
> @@ -2523,6 +2637,8 @@ qemuSnapshotDelete(virDomainObj *vm,
> int ret = -1;
> virDomainMomentObj *snap = NULL;
> bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
> + bool stop_qemu = false;
> + g_autoptr(GPtrArray) externalData = NULL;
>
> virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
> @@ -2537,6 +2653,25 @@ qemuSnapshotDelete(virDomainObj *vm,
> if (!metadata_only) {
> if (qemuSnapshotDeleteValidate(snap, flags) < 0)
> goto endjob;
> +
> + if (virDomainSnapshotIsExternal(snap) &&
> + !(flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))) {
> + if (!virDomainObjIsActive(vm)) {
> + if (qemuProcessStart(NULL, driver, vm, NULL, VIR_ASYNC_JOB_NONE,
> + NULL, -1, NULL, NULL,
> + VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
> + VIR_QEMU_PROCESS_START_PAUSED) < 0) {
Note that a user might attempt to call a 'virsh resume' here. This might
be a problem especially if you went the route of asynchronously handling
the deletion.
> + goto endjob;
> + }
> +
> + stop_qemu = true;
> + }
> +
> + externalData = qemuSnapshotDeleteExternalPrepare(vm, snap);
> + if (!externalData)
> + goto endjob;
Preferrably this operation will be done before starting qemu to do the
actual deletion. I understand that it has to be done after, to have
correct pointers after the copy of the definition needed for the
startup.
In this instance it probably is still better to do the checks first,
throw them away if qemu startup is needed and re-do them after, as the
startup of qemu is a waaay more expensive operation.
> + }
> }
>
> if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> @@ -2547,6 +2682,11 @@ qemuSnapshotDelete(virDomainObj *vm,
> }
>
> endjob:
> + if (stop_qemu) {
> + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN,
> + VIR_ASYNC_JOB_NONE, 0);
So definitely user interaction of the VM needs to be forbidden during
this API because it would be bad if the VM gets terminated here.
> + }
> +
> qemuDomainObjEndJob(vm);
>
> return ret;
> --
> 2.37.2
>
On Tue, Sep 06, 2022 at 12:19:38PM +0200, Peter Krempa wrote:
> On Tue, Aug 23, 2022 at 18:32:23 +0200, Pavel Hrdina wrote:
> > In order to save some CPU cycles we will collect all the necessary data
> > to delete external snapshot before we even start. They will be later
> > used by code that deletes the snapshots and updates metadata when
> > needed.
> >
> > With external snapshots we need data that libvirt gets from running QEMU
> > process so if the VM is not running we need to start paused QEMU process
> > for the snapshot deletion and kill at afterwards.
> >
> > Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
> > ---
> > src/qemu/qemu_snapshot.c | 144 ++++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 142 insertions(+), 2 deletions(-)
>
> First a few general notes for all upcoming patches.
>
> 1) All of the non-trivial functions dealing with snapshots should have
> documentation explaing what they do. The snapshot code, as you certainly
> know, is very complex and in many cases un-obvious. Having them will
> save us (or anybody else wanting to maintain the code) some headaches in
> the future.
>
> 2) I'd also suggest that you add a knowledge base internals article
> outlining how snapshot deletion is supposed to work. In case there are
> some user-visible caveats, we also have an user-focused article on
> snapshots ( kbase/snapshots.rst ).
>
> 3) I'll try to assess the code also from the point of view of changes
> needed to implement reversion of snapshots and the intricacies that
> might bring for deletion.
>
> > diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
> > index da9b4c30f0..dade5dcea4 100644
> > --- a/src/qemu/qemu_snapshot.c
> > +++ b/src/qemu/qemu_snapshot.c
> > @@ -2232,6 +2232,120 @@ qemuSnapshotRevert(virDomainObj *vm,
> > }
> >
> >
> > +typedef struct {
> > + virDomainMomentObj *parentSnap;
> > + virDomainSnapshotDiskDef *snapDisk;
> > + virDomainDiskDef *domDisk;
> > + virDomainDiskDef *parentDomDisk;
> > + virStorageSource *diskSrc;
> > + virStorageSource *parentDiskSrc;
> > + virStorageSource *prevDiskSrc;
> > + qemuBlockJobData *job;
> > +} qemuSnapshotDeleteExternalData;
> > +
> > +
> > +static virDomainMomentObj*
> > +qemuSnapshotFindParentSnapForDisk(virDomainMomentObj *snap,
> > + virDomainSnapshotDiskDef *snapDisk)
> > +{
> > + virDomainMomentObj *parentSnap = snap->parent;
> > +
> > + while (parentSnap) {
> > + ssize_t i;
> > + virDomainSnapshotDef *parentSnapdef = virDomainSnapshotObjGetDef(parentSnap);
> > +
> > + if (!parentSnapdef)
> > + break;
> > +
> > + for (i = 0; i < parentSnapdef->ndisks; i++) {
> > + virDomainSnapshotDiskDef *parentSnapDisk = &(parentSnapdef->disks[i]);
> > +
> > + if (parentSnapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
> > + STREQ(snapDisk->name, parentSnapDisk->name)) {
> > + return parentSnap;
> > + }
> > + }
> > +
> > + parentSnap = parentSnap->parent;
> > + }
> > +
> > + return NULL;
> > +}
> > +
> > +
> > +static GPtrArray*
> > +qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
> > + virDomainMomentObj *snap)
> > +{
> > + ssize_t i;
> > + virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
> > + g_autoptr(GPtrArray) ret = g_ptr_array_new_full(0, g_free);
>
> Is there any specific requirement to access these randomly?
> (specifically why GPtrArray is used instead of e.g. a G(S)List?
There is no need to access these randomly but G(S)List doesn't work that
nicely with g_autoptr and dynamically allocated elements. For this case
we would have to use g_list_free_full().
> > + for (i = 0; i < snapdef->ndisks; i++) {
> > + g_autofree qemuSnapshotDeleteExternalData *data = NULL;
> > + virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
> > +
> > + if (snapDisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)
> > + continue;
>
> [...]
>
> > typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
> > struct _virQEMUMomentReparent {
> > const char *dir;
> > @@ -2504,9 +2618,9 @@ qemuSnapshotDeleteValidate(virDomainMomentObj *snap,
> > }
> >
> > if (virDomainSnapshotIsExternal(snap) &&
> > - !(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
> > + (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN)) {
> > virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> > - _("deletion of external disk snapshots not supported"));
> > + _("deletion of external disk snapshot with internal children disk snapshots not supported"));
>
> This seems a bit unrelated to this patch.
>
> > return -1;
> > }
> >
> > @@ -2523,6 +2637,8 @@ qemuSnapshotDelete(virDomainObj *vm,
> > int ret = -1;
> > virDomainMomentObj *snap = NULL;
> > bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
> > + bool stop_qemu = false;
> > + g_autoptr(GPtrArray) externalData = NULL;
> >
> > virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> > VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
> > @@ -2537,6 +2653,25 @@ qemuSnapshotDelete(virDomainObj *vm,
> > if (!metadata_only) {
> > if (qemuSnapshotDeleteValidate(snap, flags) < 0)
> > goto endjob;
> > +
> > + if (virDomainSnapshotIsExternal(snap) &&
> > + !(flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> > + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))) {
> > + if (!virDomainObjIsActive(vm)) {
> > + if (qemuProcessStart(NULL, driver, vm, NULL, VIR_ASYNC_JOB_NONE,
> > + NULL, -1, NULL, NULL,
> > + VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
> > + VIR_QEMU_PROCESS_START_PAUSED) < 0) {
>
> Note that a user might attempt to call a 'virsh resume' here. This might
> be a problem especially if you went the route of asynchronously handling
> the deletion.
>
> > + goto endjob;
> > + }
> > +
> > + stop_qemu = true;
> > + }
> > +
> > + externalData = qemuSnapshotDeleteExternalPrepare(vm, snap);
> > + if (!externalData)
> > + goto endjob;
>
> Preferrably this operation will be done before starting qemu to do the
> actual deletion. I understand that it has to be done after, to have
> correct pointers after the copy of the definition needed for the
> startup.
>
> In this instance it probably is still better to do the checks first,
> throw them away if qemu startup is needed and re-do them after, as the
> startup of qemu is a waaay more expensive operation.
Yeah I don't like this ordering as well, I'll check what parts of the
qemuSnapshotDeleteExternalPrepare() can be done without qemu running.
> > + }
> > }
> >
> > if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> > @@ -2547,6 +2682,11 @@ qemuSnapshotDelete(virDomainObj *vm,
> > }
> >
> > endjob:
> > + if (stop_qemu) {
> > + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN,
> > + VIR_ASYNC_JOB_NONE, 0);
>
> So definitely user interaction of the VM needs to be forbidden during
> this API because it would be bad if the VM gets terminated here.
Good point, will look into it.
Pavel
On Wed, Oct 12, 2022 at 17:19:23 +0200, Pavel Hrdina wrote:
> On Tue, Sep 06, 2022 at 12:19:38PM +0200, Peter Krempa wrote:
> > On Tue, Aug 23, 2022 at 18:32:23 +0200, Pavel Hrdina wrote:
[...]
> > > +
> > > +
> > > +static GPtrArray*
> > > +qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
> > > + virDomainMomentObj *snap)
> > > +{
> > > + ssize_t i;
> > > + virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
> > > + g_autoptr(GPtrArray) ret = g_ptr_array_new_full(0, g_free);
> >
> > Is there any specific requirement to access these randomly?
> > (specifically why GPtrArray is used instead of e.g. a G(S)List?
>
> There is no need to access these randomly but G(S)List doesn't work that
> nicely with g_autoptr and dynamically allocated elements. For this case
> we would have to use g_list_free_full().
Sure, but you also have g_autoslist/g_autolist, so you can also use
automatic cleanup.
© 2016 - 2026 Red Hat, Inc.