[PATCH] qemuDomainDiskChangeSupported: Add missing iothreads check

Adam Julis posted 1 patch 6 months, 2 weeks ago
There is a newer version of this series
src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
[PATCH] qemuDomainDiskChangeSupported: Add missing iothreads check
Posted by Adam Julis 6 months, 2 weeks ago
GSList of iothreads is not allowed to be changed while the
virtual machine is running.

Resolves: https://issues.redhat.com/browse/RHEL-23607
Signed-off-by: Adam Julis <ajulis@redhat.com>
---
While the qemuDomainDiskChangeSupported() design primarily uses
its macros (CHECK_EQ and CHECK_STREQ_NULLABLE), the logic for comparing 2
GSList of iothreads could perhaps be extracted into a separate function
(e.g. IothreadsGslistCompare(GSList *first, GSList *second)). I am
absolutely not sure about this idea so feel free to comment.

 src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 298f4bfb9e..2b5222c685 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -8505,6 +8505,59 @@ qemuDomainDiskChangeSupported(virDomainDiskDef *disk,
     CHECK_EQ(discard, "discard", true);
     CHECK_EQ(iothread, "iothread", true);
 
+    /* compare list of iothreads, no change allowed */
+    if (orig_disk->iothreads != disk->iothreads) {
+        GSList *old;
+        GSList *new = disk->iothreads;
+        bool print_err = true;
+
+        for (old = orig_disk->iothreads; old; old = old->next) {
+            virDomainDiskIothreadDef *orig = old->data;
+            virDomainDiskIothreadDef *update;
+            print_err = false;
+
+            if (new == NULL) {
+                print_err = true;
+                break;
+            }
+
+            update = new->data;
+
+            if (orig->id != update->id) {
+                print_err = true;
+                break;
+            }
+
+            if (orig->nqueues != update->nqueues) {
+                print_err = true;
+                break;
+            }
+
+            if (orig->nqueues != 0) {
+                ssize_t i = 0;
+
+                while (i < orig->nqueues) {
+                    if (orig->queues[i] != update->queues[i]) {
+                        print_err = true;
+                        break;
+                    }
+                }
+            }
+
+            new = new->next;
+            if (new)
+                print_err = true;
+        }
+
+        if (print_err) {
+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
+                           _("cannot modify field '%1$s' (or it's parts) of the disk"),
+                           "iothreads");
+            return false;
+        }
+    }
+
+
     CHECK_STREQ_NULLABLE(domain_name,
                          "backenddomain");
 
-- 
2.45.2
Re: [PATCH] qemuDomainDiskChangeSupported: Add missing iothreads check
Posted by Peter Krempa 2 months, 3 weeks ago
On Fri, Jul 26, 2024 at 10:52:18 +0200, Adam Julis wrote:
> GSList of iothreads is not allowed to be changed while the
> virtual machine is running.
> 
> Resolves: https://issues.redhat.com/browse/RHEL-23607
> Signed-off-by: Adam Julis <ajulis@redhat.com>
> ---
> While the qemuDomainDiskChangeSupported() design primarily uses
> its macros (CHECK_EQ and CHECK_STREQ_NULLABLE), the logic for comparing 2
> GSList of iothreads could perhaps be extracted into a separate function
> (e.g. IothreadsGslistCompare(GSList *first, GSList *second)). I am
> absolutely not sure about this idea so feel free to comment.
> 
>  src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 53 insertions(+)
> 
> diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
> index 298f4bfb9e..2b5222c685 100644
> --- a/src/qemu/qemu_domain.c
> +++ b/src/qemu/qemu_domain.c
> @@ -8505,6 +8505,59 @@ qemuDomainDiskChangeSupported(virDomainDiskDef *disk,
>      CHECK_EQ(discard, "discard", true);
>      CHECK_EQ(iothread, "iothread", true);
>  
> +    /* compare list of iothreads, no change allowed */

This is a bit too long for the existing function and thus should be put
into it's own:

static bool
qemuDomainDiskChangeSupportedIothreads(virDomainDiskDef *disk,
                                       virDomainDiskDef *orig_disk)


> +    if (orig_disk->iothreads != disk->iothreads) {

These are pointers which will be equal only if both are NULL. While this
is technically correct it's confusing at first glance.

Since the loop below needs to handle mismatched-lenght GSlists anyways
there's no need to do this check before.

> +        GSList *old;
> +        GSList *new = disk->iothreads;
> +        bool print_err = true;
> +
> +        for (old = orig_disk->iothreads; old; old = old->next) {
> +            virDomainDiskIothreadDef *orig = old->data;
> +            virDomainDiskIothreadDef *update;
> +            print_err = false;
> +
> +            if (new == NULL) {
> +                print_err = true;
> +                break;
> +            }

The two styles of iterating through the two GSlists are also confusing
at the first glance. In order to make the code more readable I'll change
it to a uniform style.

> +
> +            update = new->data;
> +
> +            if (orig->id != update->id) {
> +                print_err = true;
> +                break;
> +            }
> +
> +            if (orig->nqueues != update->nqueues) {
> +                print_err = true;
> +                break;
> +            }
> +
> +            if (orig->nqueues != 0) {
> +                ssize_t i = 0;

'nqueues' is a 'size_t' so no need for ssize_t.

> +
> +                while (i < orig->nqueues) {
> +                    if (orig->queues[i] != update->queues[i]) {
> +                        print_err = true;
> +                        break;
> +                    }

This loop doesn't have an increment, so it'll loop forever if the first
entry matches.

> +                }
> +            }
> +
> +            new = new->next;
> +            if (new)
> +                print_err = true;
> +        }
> +
> +        if (print_err) {
> +            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> +                           _("cannot modify field '%1$s' (or it's parts) of the disk"),
> +                           "iothreads");

This is translation unfriendly:

https://www.libvirt.org/coding-style.html#error-message-format

I'll post a v2.
Re: [PATCH] qemuDomainDiskChangeSupported: Add missing iothreads check
Posted by Martin Kletzander 6 months ago
On Fri, Jul 26, 2024 at 10:52:18AM +0200, Adam Julis wrote:
>GSList of iothreads is not allowed to be changed while the
>virtual machine is running.
>
>Resolves: https://issues.redhat.com/browse/RHEL-23607
>Signed-off-by: Adam Julis <ajulis@redhat.com>
>---
>While the qemuDomainDiskChangeSupported() design primarily uses
>its macros (CHECK_EQ and CHECK_STREQ_NULLABLE), the logic for comparing 2
>GSList of iothreads could perhaps be extracted into a separate function
>(e.g. IothreadsGslistCompare(GSList *first, GSList *second)). I am
>absolutely not sure about this idea so feel free to comment.
>

Since we are doing this in one place and glib does not have such
function I'd say leave it.  What I'd probably extract into its own
function now that I'm looking into the patch is something like
virDomainDiskIothreadDefEquals() which you could run in a loop like
this:

for (old = orig_disk->iothreads, new = orig_disk->iothreads;
      old && new;
      old = old->next, new = new->next) {
      if (!virDomainDiskIothreadDefEquals(old->data, new->data))
          break;
}

if (old || new) {
     virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                    _("cannot modify field '%1$s' (or it's parts) of the disk"),
                    "iothreads");
     return false;
}

I understand it might be less readable for some, but the suggested
extracted function above will be way more concise since you can just
return false instead of `{ print_err = true; break; }`.

> src/qemu/qemu_domain.c | 53 ++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 53 insertions(+)
>
>diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
>index 298f4bfb9e..2b5222c685 100644
>--- a/src/qemu/qemu_domain.c
>+++ b/src/qemu/qemu_domain.c
>@@ -8505,6 +8505,59 @@ qemuDomainDiskChangeSupported(virDomainDiskDef *disk,
>     CHECK_EQ(discard, "discard", true);
>     CHECK_EQ(iothread, "iothread", true);
>
>+    /* compare list of iothreads, no change allowed */
>+    if (orig_disk->iothreads != disk->iothreads) {

Since the new disk will be parsed separately the pointers will never be
the same, unless I missed some part of the calling flow in which case it
is updated with the old information, but I don't think so.

>+        GSList *old;
>+        GSList *new = disk->iothreads;
>+        bool print_err = true;
>+
>+        for (old = orig_disk->iothreads; old; old = old->next) {
>+            virDomainDiskIothreadDef *orig = old->data;
>+            virDomainDiskIothreadDef *update;
>+            print_err = false;
>+
>+            if (new == NULL) {
>+                print_err = true;
>+                break;
>+            }
>+
>+            update = new->data;
>+
>+            if (orig->id != update->id) {
>+                print_err = true;
>+                break;
>+            }
>+
>+            if (orig->nqueues != update->nqueues) {
>+                print_err = true;
>+                break;
>+            }
>+
>+            if (orig->nqueues != 0) {
>+                ssize_t i = 0;
>+
>+                while (i < orig->nqueues) {
>+                    if (orig->queues[i] != update->queues[i]) {
>+                        print_err = true;
>+                        break;
>+                    }
>+                }
>+            }
>+
>+            new = new->next;
>+            if (new)
>+                print_err = true;
>+        }
>+
>+        if (print_err) {
>+            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
>+                           _("cannot modify field '%1$s' (or it's parts) of the disk"),
>+                           "iothreads");
>+            return false;
>+        }
>+    }
>+
>+
>     CHECK_STREQ_NULLABLE(domain_name,
>                          "backenddomain");
>
>-- 
>2.45.2
>