diff mbox

[05/16] pci-assign: propagate errors from get_real_id()

Message ID 1397118285-11715-6-git-send-email-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek April 10, 2014, 8:24 a.m. UTC
get_real_id() has two thin wrappers (and no other callers),
get_real_vendor_id() and get_real_device_id(); it's easiest to convert
them in one fell swoop.

Signed-off-by: Laszlo Ersek <lersek@redhat.com>
---
 hw/i386/kvm/pci-assign.c | 45 +++++++++++++++++++++++++++------------------
 1 file changed, 27 insertions(+), 18 deletions(-)

Comments

Eric Blake April 25, 2014, 4:05 p.m. UTC | #1
On 04/10/2014 02:24 AM, Laszlo Ersek wrote:
> get_real_id() has two thin wrappers (and no other callers),
> get_real_vendor_id() and get_real_device_id(); it's easiest to convert
> them in one fell swoop.
> 
> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
> ---
>  hw/i386/kvm/pci-assign.c | 45 +++++++++++++++++++++++++++------------------
>  1 file changed, 27 insertions(+), 18 deletions(-)
> 
> diff --git a/hw/i386/kvm/pci-assign.c b/hw/i386/kvm/pci-assign.c

> +static void get_real_id(const char *devpath, const char *idname, uint16_t *val,
> +                        Error **errp)
>  {
>      FILE *f;
>      char name[128];
>      long id;
>  
>      snprintf(name, sizeof(name), "%s%s", devpath, idname);
>      f = fopen(name, "r");
>      if (f == NULL) {
> -        error_report("%s: %s: %m", __func__, name);
> -        return -1;
> +        error_setg_file_open(errp, errno, name);
> +        return;
>      }
>      if (fscanf(f, "%li\n", &id) == 1) {
>          *val = id;

Latent bug (does not affect review of this patch): fscanf(%i) triggers
undefined behavior on overflow.  Although qemu already has a number of
violators, I've been advocating that people considering cleanups to use
strtol() and friends when parsing integers (preferably sane wrappers, as
strtol() is also a bear to use correctly), to avoid undefined behavior
of *scanf.

> @@ -759,12 +764,16 @@ static char *assign_failed_examine(const AssignedDevice *dev)
>          goto fail;
>      }
>  
>      ns++;
>  
> -    if (get_real_vendor_id(dir, &vendor_id) ||
> -        get_real_device_id(dir, &device_id)) {
> +    if ((get_real_vendor_id(dir, &vendor_id, &local_err), local_err) ||
> +        (get_real_device_id(dir, &device_id, &local_err), local_err)) {
> +        /* We're already analyzing an assignment error, so we suppress this
> +         * one just like the others above.
> +         */
> +        error_free(local_err);

This feels idiomatically wrong to me to rely on a comma operator only
ignore a failure.  Why not just:

get_real_vendor_id(dir, &vendor_id, NULL);
get_real_device_id(dir, &device_id, NULL);

and skip the use of local_err?

>          goto fail;
>      }
>  
>      return g_strdup_printf(
>          "*** The driver '%s' is occupying your device %04x:%02x:%02x.%x.\n"

Or is the problem that you are trying to prevent this g_strdup_printf
from using uninitialized vendor_id and device_id if either lookup failed?

But as for THIS commit, you have done an accurate transformation with no
change in semantic behavior.  Therefore, I'm fine leaving it as-is and
adding:

Reviewed-by: Eric Blake <eblake@redhat.com>
Laszlo Ersek April 25, 2014, 9:17 p.m. UTC | #2
On 04/25/14 18:05, Eric Blake wrote:
> On 04/10/2014 02:24 AM, Laszlo Ersek wrote:
>> get_real_id() has two thin wrappers (and no other callers),
>> get_real_vendor_id() and get_real_device_id(); it's easiest to convert
>> them in one fell swoop.
>>
>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>> ---
>>  hw/i386/kvm/pci-assign.c | 45 +++++++++++++++++++++++++++------------------
>>  1 file changed, 27 insertions(+), 18 deletions(-)
>>
>> diff --git a/hw/i386/kvm/pci-assign.c b/hw/i386/kvm/pci-assign.c
> 
>> +static void get_real_id(const char *devpath, const char *idname, uint16_t *val,
>> +                        Error **errp)
>>  {
>>      FILE *f;
>>      char name[128];
>>      long id;
>>  
>>      snprintf(name, sizeof(name), "%s%s", devpath, idname);
>>      f = fopen(name, "r");
>>      if (f == NULL) {
>> -        error_report("%s: %s: %m", __func__, name);
>> -        return -1;
>> +        error_setg_file_open(errp, errno, name);
>> +        return;
>>      }
>>      if (fscanf(f, "%li\n", &id) == 1) {
>>          *val = id;
> 
> Latent bug (does not affect review of this patch): fscanf(%i) triggers
> undefined behavior on overflow.  Although qemu already has a number of
> violators, I've been advocating that people considering cleanups to use
> strtol() and friends when parsing integers (preferably sane wrappers, as
> strtol() is also a bear to use correctly), to avoid undefined behavior
> of *scanf.

You are right of course. Perhaps this case is a milder offender because
we're parsing sysfs files here, which we could consider a trusted /
stable interface. I think the fopen() check is justified (maybe sysfs is
not mounted), but once the file is available, then maybe we can trust
its contents.

(I'm saying this without having written the original code of course. I'm
not trying to prove the code right -- again, you are right -- just
trying to reinvent the original author's thought process. You could
correctly argue that if we expect sysfs not to be mounted, then
something else mounted in its usual place, with random contents, should
be plausible too.)

> 
>> @@ -759,12 +764,16 @@ static char *assign_failed_examine(const AssignedDevice *dev)
>>          goto fail;
>>      }
>>  
>>      ns++;
>>  
>> -    if (get_real_vendor_id(dir, &vendor_id) ||
>> -        get_real_device_id(dir, &device_id)) {
>> +    if ((get_real_vendor_id(dir, &vendor_id, &local_err), local_err) ||
>> +        (get_real_device_id(dir, &device_id, &local_err), local_err)) {
>> +        /* We're already analyzing an assignment error, so we suppress this
>> +         * one just like the others above.
>> +         */
>> +        error_free(local_err);
> 
> This feels idiomatically wrong to me to rely on a comma operator only
> ignore a failure.  Why not just:
> 
> get_real_vendor_id(dir, &vendor_id, NULL);
> get_real_device_id(dir, &device_id, NULL);
> 
> and skip the use of local_err?

We're suppressing the error only in the sense that we don't print it or
format it / report it otherwise. (Just like we don't specifically report
any readlink() or strrchr() failures above, which are not visible in the
context here.)

We do handle the error by jumping to the "fail" label; this goto below
is unchanged context:

> 
>>          goto fail;
>>      }

Without using local_err it's not possible to tell if get_real_*_id()
fails or not (they both return void).

>>  
>>      return g_strdup_printf(
>>          "*** The driver '%s' is occupying your device %04x:%02x:%02x.%x.\n"
> 
> Or is the problem that you are trying to prevent this g_strdup_printf
> from using uninitialized vendor_id and device_id if either lookup failed?

Yes. Not so much for avoiding undefined behavior (accessing auto objects
with indeterminate values), although that's a valid direct reason too,
but more because (according to the preexistent function logic) if one
these functions fails, we want to report "couldn't find out why", under
the "fail" label (not visible in the context), rather than saying
"driver foobar is occupying your device -- which we can't identify BTW".

> 
> But as for THIS commit, you have done an accurate transformation with no
> change in semantic behavior.  Therefore, I'm fine leaving it as-is and
> adding:
> 
> Reviewed-by: Eric Blake <eblake@redhat.com>

Thank you. That was indeed my purpose, to keep the semantics intact.

Laszlo
diff mbox

Patch

diff --git a/hw/i386/kvm/pci-assign.c b/hw/i386/kvm/pci-assign.c
index 6b8db25..997ef09 100644
--- a/hw/i386/kvm/pci-assign.c
+++ b/hw/i386/kvm/pci-assign.c
@@ -497,47 +497,47 @@  static int assigned_dev_register_regions(PCIRegion *io_regions,
 
     /* success */
     return 0;
 }
 
-static int get_real_id(const char *devpath, const char *idname, uint16_t *val)
+static void get_real_id(const char *devpath, const char *idname, uint16_t *val,
+                        Error **errp)
 {
     FILE *f;
     char name[128];
     long id;
 
     snprintf(name, sizeof(name), "%s%s", devpath, idname);
     f = fopen(name, "r");
     if (f == NULL) {
-        error_report("%s: %s: %m", __func__, name);
-        return -1;
+        error_setg_file_open(errp, errno, name);
+        return;
     }
     if (fscanf(f, "%li\n", &id) == 1) {
         *val = id;
     } else {
-        fclose(f);
-        return -1;
+        error_setg(errp, "Failed to parse contents of '%s'", name);
     }
     fclose(f);
-
-    return 0;
 }
 
-static int get_real_vendor_id(const char *devpath, uint16_t *val)
+static void get_real_vendor_id(const char *devpath, uint16_t *val,
+                               Error **errp)
 {
-    return get_real_id(devpath, "vendor", val);
+    get_real_id(devpath, "vendor", val, errp);
 }
 
-static int get_real_device_id(const char *devpath, uint16_t *val)
+static void get_real_device_id(const char *devpath, uint16_t *val,
+                               Error **errp)
 {
-    return get_real_id(devpath, "device", val);
+    get_real_id(devpath, "device", val, errp);
 }
 
 static int get_real_device(AssignedDevice *pci_dev)
 {
     char dir[128], name[128];
-    int fd, r = 0, v;
+    int fd, r = 0;
     FILE *f;
     uint64_t start, end, size, flags;
     uint16_t id;
     PCIRegion *rp;
     PCIDevRegions *dev = &pci_dev->real_device;
@@ -637,20 +637,24 @@  again:
     }
 
     fclose(f);
 
     /* read and fill vendor ID */
-    v = get_real_vendor_id(dir, &id);
-    if (v) {
+    get_real_vendor_id(dir, &id, &local_err);
+    if (local_err) {
+        error_report("%s", error_get_pretty(local_err));
+        error_free(local_err);
         return 1;
     }
     pci_dev->dev.config[0] = id & 0xff;
     pci_dev->dev.config[1] = (id & 0xff00) >> 8;
 
     /* read and fill device ID */
-    v = get_real_device_id(dir, &id);
-    if (v) {
+    get_real_device_id(dir, &id, &local_err);
+    if (local_err) {
+        error_report("%s", error_get_pretty(local_err));
+        error_free(local_err);
         return 1;
     }
     pci_dev->dev.config[2] = id & 0xff;
     pci_dev->dev.config[3] = (id & 0xff00) >> 8;
 
@@ -739,10 +743,11 @@  static void free_assigned_device(AssignedDevice *dev)
 static char *assign_failed_examine(const AssignedDevice *dev)
 {
     char name[PATH_MAX], dir[PATH_MAX], driver[PATH_MAX] = {}, *ns;
     uint16_t vendor_id, device_id;
     int r;
+    Error *local_err = NULL;
 
     snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/",
             dev->host.domain, dev->host.bus, dev->host.slot,
             dev->host.function);
 
@@ -759,12 +764,16 @@  static char *assign_failed_examine(const AssignedDevice *dev)
         goto fail;
     }
 
     ns++;
 
-    if (get_real_vendor_id(dir, &vendor_id) ||
-        get_real_device_id(dir, &device_id)) {
+    if ((get_real_vendor_id(dir, &vendor_id, &local_err), local_err) ||
+        (get_real_device_id(dir, &device_id, &local_err), local_err)) {
+        /* We're already analyzing an assignment error, so we suppress this
+         * one just like the others above.
+         */
+        error_free(local_err);
         goto fail;
     }
 
     return g_strdup_printf(
         "*** The driver '%s' is occupying your device %04x:%02x:%02x.%x.\n"