diff mbox series

[v3,2/3] module: add Error arguments to module_load_one and module_load_qom_one

Message ID 20220908145308.30282-3-cfontana@suse.de
State New
Headers show
Series improve error handling for module load | expand

Commit Message

Claudio Fontana Sept. 8, 2022, 2:53 p.m. UTC
improve error handling during module load, by changing:

bool module_load_one(const char *prefix, const char *lib_name);
void module_load_qom_one(const char *type);

to:

bool module_load_one(const char *prefix, const char *name, Error **errp);
bool module_load_qom_one(const char *type, Error **errp);

module_load_qom_one has been introduced in:

commit 28457744c345 ("module: qom module support"), which built on top of
module_load_one, but discarded the bool return value. Restore it.

Adapt all callers to emit errors, or ignore them, or fail hard,
as appropriate in each context.

Signed-off-by: Claudio Fontana <cfontana@suse.de>
---
 audio/audio.c         |   6 +-
 block.c               |  12 +++-
 block/dmg.c           |  10 ++-
 hw/core/qdev.c        |  10 ++-
 include/qemu/module.h |  27 ++++++--
 qom/object.c          |  15 ++++-
 softmmu/qtest.c       |   6 +-
 ui/console.c          |  18 ++++-
 util/module.c         | 150 +++++++++++++++++++++++++-----------------
 9 files changed, 176 insertions(+), 78 deletions(-)

Comments

Richard Henderson Sept. 8, 2022, 4:03 p.m. UTC | #1
On 9/8/22 15:53, Claudio Fontana wrote:
> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>           return -EINVAL;
>       }
>   
> -    block_module_load_one("dmg-bz2");
> -    block_module_load_one("dmg-lzfse");
> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> +        error_report_err(local_err);
> +    }
> +    local_err = NULL;
> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> +        error_report_err(local_err);
> +    }
>   
>       s->n_chunks = 0;
>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;

I wonder if these shouldn't fail hard if the modules don't exist?
Or at least pass back the error.

Kevin?

> @@ -1033,7 +1039,10 @@ ObjectClass *module_object_class_by_name(const char *typename)
>       oc = object_class_by_name(typename);
>   #ifdef CONFIG_MODULES
>       if (!oc) {
> -        module_load_qom_one(typename);
> +        Error *local_err = NULL;
> +        if (!module_load_qom_one(typename, &local_err) && local_err) {
> +            error_report_err(local_err);
> +        }

You can return NULL from this path, we know it failed.

> @@ -172,46 +170,38 @@ static int module_load_file(const char *fname, bool export_symbols)
>       }
>       g_module = g_module_open(fname, flags);
>       if (!g_module) {
> -        fprintf(stderr, "Failed to open module: %s\n",
> -                g_module_error());
> -        ret = -EINVAL;
> -        goto out;
> +        error_setg(errp, "failed to open module: %s", g_module_error());
> +        return false;
>       }
>       if (!g_module_symbol(g_module, DSO_STAMP_FUN_STR, (gpointer *)&sym)) {
> -        fprintf(stderr, "Failed to initialize module: %s\n",
> -                fname);
> -        /* Print some info if this is a QEMU module (but from different build),
> -         * this will make debugging user problems easier. */
> +        error_setg(errp, "failed to initialize module: %s", fname);
> +        /*
> +         * Print some info if this is a QEMU module (but from different build),
> +         * this will make debugging user problems easier.
> +         */
>           if (g_module_symbol(g_module, "qemu_module_dummy", (gpointer *)&sym)) {
> -            fprintf(stderr,
> -                    "Note: only modules from the same build can be loaded.\n");
> +            error_append_hint(errp,
> +                              "Only modules from the same build can be loaded");

With error_append_hint, you add the newline.

The rest of the util/module.c reorg looks good.


r~
Claudio Fontana Sept. 8, 2022, 5:10 p.m. UTC | #2
On 9/8/22 18:03, Richard Henderson wrote:
> On 9/8/22 15:53, Claudio Fontana wrote:
>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>           return -EINVAL;
>>       }
>>   
>> -    block_module_load_one("dmg-bz2");
>> -    block_module_load_one("dmg-lzfse");
>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>> +        error_report_err(local_err);
>> +    }
>> +    local_err = NULL;
>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>> +        error_report_err(local_err);
>> +    }
>>   
>>       s->n_chunks = 0;
>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> 
> I wonder if these shouldn't fail hard if the modules don't exist?
> Or at least pass back the error.
> 
> Kevin?
> 
>> @@ -1033,7 +1039,10 @@ ObjectClass *module_object_class_by_name(const char *typename)
>>       oc = object_class_by_name(typename);
>>   #ifdef CONFIG_MODULES
>>       if (!oc) {
>> -        module_load_qom_one(typename);
>> +        Error *local_err = NULL;
>> +        if (!module_load_qom_one(typename, &local_err) && local_err) {
>> +            error_report_err(local_err);
>> +        }
> 
> You can return NULL from this path, we know it failed.


I am tempted then to change also other similar instances in the code.

> 
>> @@ -172,46 +170,38 @@ static int module_load_file(const char *fname, bool export_symbols)
>>       }
>>       g_module = g_module_open(fname, flags);
>>       if (!g_module) {
>> -        fprintf(stderr, "Failed to open module: %s\n",
>> -                g_module_error());
>> -        ret = -EINVAL;
>> -        goto out;
>> +        error_setg(errp, "failed to open module: %s", g_module_error());
>> +        return false;
>>       }
>>       if (!g_module_symbol(g_module, DSO_STAMP_FUN_STR, (gpointer *)&sym)) {
>> -        fprintf(stderr, "Failed to initialize module: %s\n",
>> -                fname);
>> -        /* Print some info if this is a QEMU module (but from different build),
>> -         * this will make debugging user problems easier. */
>> +        error_setg(errp, "failed to initialize module: %s", fname);
>> +        /*
>> +         * Print some info if this is a QEMU module (but from different build),
>> +         * this will make debugging user problems easier.
>> +         */
>>           if (g_module_symbol(g_module, "qemu_module_dummy", (gpointer *)&sym)) {
>> -            fprintf(stderr,
>> -                    "Note: only modules from the same build can be loaded.\n");
>> +            error_append_hint(errp,
>> +                              "Only modules from the same build can be loaded");
> 
> With error_append_hint, you add the newline.
> 
> The rest of the util/module.c reorg looks good.
> 
> 
> r~
Claudio Fontana Sept. 8, 2022, 5:36 p.m. UTC | #3
On 9/8/22 19:10, Claudio Fontana wrote:
> On 9/8/22 18:03, Richard Henderson wrote:
>> On 9/8/22 15:53, Claudio Fontana wrote:
>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>>           return -EINVAL;
>>>       }
>>>   
>>> -    block_module_load_one("dmg-bz2");
>>> -    block_module_load_one("dmg-lzfse");
>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>>> +        error_report_err(local_err);
>>> +    }
>>> +    local_err = NULL;
>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>>> +        error_report_err(local_err);
>>> +    }
>>>   
>>>       s->n_chunks = 0;
>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>>
>> I wonder if these shouldn't fail hard if the modules don't exist?
>> Or at least pass back the error.
>>
>> Kevin?

is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg image is not compressed,
"dmg" can function even if the extra dmg-bz module is not loaded right?

I'd suspect we should then do:

if (!block_module_load_one("dmg-bz2", &local_err)) {
  if (local_err) {
     error_report_err(local_err);
     return -EINVAL;
  }
  warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
}

and same for dmg-lzfse...?

Ciao,

C
     
>>> +        error_report_err(local_err);
>>> +    }
>>
>>> @@ -1033,7 +1039,10 @@ ObjectClass *module_object_class_by_name(const char *typename)
>>>       oc = object_class_by_name(typename);
>>>   #ifdef CONFIG_MODULES
>>>       if (!oc) {
>>> -        module_load_qom_one(typename);
>>> +        Error *local_err = NULL;
>>> +        if (!module_load_qom_one(typename, &local_err) && local_err) {
>>> +            error_report_err(local_err);
>>> +        }
>>
>> You can return NULL from this path, we know it failed.
> 
> 
> I am tempted then to change also other similar instances in the code.
> 
>>
>>> @@ -172,46 +170,38 @@ static int module_load_file(const char *fname, bool export_symbols)
>>>       }
>>>       g_module = g_module_open(fname, flags);
>>>       if (!g_module) {
>>> -        fprintf(stderr, "Failed to open module: %s\n",
>>> -                g_module_error());
>>> -        ret = -EINVAL;
>>> -        goto out;
>>> +        error_setg(errp, "failed to open module: %s", g_module_error());
>>> +        return false;
>>>       }
>>>       if (!g_module_symbol(g_module, DSO_STAMP_FUN_STR, (gpointer *)&sym)) {
>>> -        fprintf(stderr, "Failed to initialize module: %s\n",
>>> -                fname);
>>> -        /* Print some info if this is a QEMU module (but from different build),
>>> -         * this will make debugging user problems easier. */
>>> +        error_setg(errp, "failed to initialize module: %s", fname);
>>> +        /*
>>> +         * Print some info if this is a QEMU module (but from different build),
>>> +         * this will make debugging user problems easier.
>>> +         */
>>>           if (g_module_symbol(g_module, "qemu_module_dummy", (gpointer *)&sym)) {
>>> -            fprintf(stderr,
>>> -                    "Note: only modules from the same build can be loaded.\n");
>>> +            error_append_hint(errp,
>>> +                              "Only modules from the same build can be loaded");
>>
>> With error_append_hint, you add the newline.
>>
>> The rest of the util/module.c reorg looks good.
>>
>>
>> r~
>
Kevin Wolf Sept. 20, 2022, 4:50 p.m. UTC | #4
Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
> On 9/8/22 19:10, Claudio Fontana wrote:
> > On 9/8/22 18:03, Richard Henderson wrote:
> >> On 9/8/22 15:53, Claudio Fontana wrote:
> >>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
> >>>           return -EINVAL;
> >>>       }
> >>>   
> >>> -    block_module_load_one("dmg-bz2");
> >>> -    block_module_load_one("dmg-lzfse");
> >>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> >>> +        error_report_err(local_err);
> >>> +    }
> >>> +    local_err = NULL;
> >>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> >>> +        error_report_err(local_err);
> >>> +    }
> >>>   
> >>>       s->n_chunks = 0;
> >>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> >>
> >> I wonder if these shouldn't fail hard if the modules don't exist?
> >> Or at least pass back the error.
> >>
> >> Kevin?
> 
> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
> image is not compressed, "dmg" can function even if the extra dmg-bz
> module is not loaded right?

Indeed. The code seems to consider that the modules may not be present.
The behaviour in these cases is questionable (it seems to silently leave
the buffers as they are and return success), but the modules are clearly
optional.

> I'd suspect we should then do:
> 
> if (!block_module_load_one("dmg-bz2", &local_err)) {
>   if (local_err) {
>      error_report_err(local_err);
>      return -EINVAL;
>   }
>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
> }
> 
> and same for dmg-lzfse...?

Actually, I think during initialisation, we should just pass NULL as
errp and ignore any errors.

When a request would access a block that can't be uncompressed because
of the missing module, that's where we can have a warn_report_once() and
arguably should fail the I/O request.

Kevin
Markus Armbruster Sept. 21, 2022, 4:45 a.m. UTC | #5
Kevin Wolf <kwolf@redhat.com> writes:

> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>> On 9/8/22 19:10, Claudio Fontana wrote:
>> > On 9/8/22 18:03, Richard Henderson wrote:
>> >> On 9/8/22 15:53, Claudio Fontana wrote:
>> >>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>> >>>           return -EINVAL;
>> >>>       }
>> >>>   
>> >>> -    block_module_load_one("dmg-bz2");
>> >>> -    block_module_load_one("dmg-lzfse");
>> >>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>> >>> +        error_report_err(local_err);
>> >>> +    }
>> >>> +    local_err = NULL;
>> >>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>> >>> +        error_report_err(local_err);
>> >>> +    }
>> >>>   
>> >>>       s->n_chunks = 0;
>> >>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>> >>
>> >> I wonder if these shouldn't fail hard if the modules don't exist?
>> >> Or at least pass back the error.
>> >>
>> >> Kevin?
>> 
>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>> image is not compressed, "dmg" can function even if the extra dmg-bz
>> module is not loaded right?
>
> Indeed. The code seems to consider that the modules may not be present.
> The behaviour in these cases is questionable (it seems to silently leave
> the buffers as they are and return success), but the modules are clearly
> optional.
>
>> I'd suspect we should then do:
>> 
>> if (!block_module_load_one("dmg-bz2", &local_err)) {
>>   if (local_err) {
>>      error_report_err(local_err);
>>      return -EINVAL;
>>   }
>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>> }
>> 
>> and same for dmg-lzfse...?
>
> Actually, I think during initialisation, we should just pass NULL as
> errp and ignore any errors.
>
> When a request would access a block that can't be uncompressed because
> of the missing module, that's where we can have a warn_report_once() and
> arguably should fail the I/O request.

Seems like asking for data corruption.  To avoid it, the complete stack
needs to handle I/O errors correctly.

Can we detect presence of compressed blocks on open?
Claudio Fontana Sept. 21, 2022, 7:50 a.m. UTC | #6
On 9/20/22 18:50, Kevin Wolf wrote:
> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>> On 9/8/22 19:10, Claudio Fontana wrote:
>>> On 9/8/22 18:03, Richard Henderson wrote:
>>>> On 9/8/22 15:53, Claudio Fontana wrote:
>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>           return -EINVAL;
>>>>>       }
>>>>>   
>>>>> -    block_module_load_one("dmg-bz2");
>>>>> -    block_module_load_one("dmg-lzfse");
>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>>>>> +        error_report_err(local_err);
>>>>> +    }
>>>>> +    local_err = NULL;
>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>>>>> +        error_report_err(local_err);
>>>>> +    }
>>>>>   
>>>>>       s->n_chunks = 0;
>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>>>>
>>>> I wonder if these shouldn't fail hard if the modules don't exist?
>>>> Or at least pass back the error.
>>>>
>>>> Kevin?
>>
>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>> image is not compressed, "dmg" can function even if the extra dmg-bz
>> module is not loaded right?
> 
> Indeed. The code seems to consider that the modules may not be present.
> The behaviour in these cases is questionable (it seems to silently leave
> the buffers as they are and return success), but the modules are clearly
> optional.
> 
>> I'd suspect we should then do:
>>
>> if (!block_module_load_one("dmg-bz2", &local_err)) {
>>   if (local_err) {
>>      error_report_err(local_err);
>>      return -EINVAL;
>>   }
>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>> }
>>
>> and same for dmg-lzfse...?
> 
> Actually, I think during initialisation, we should just pass NULL as
> errp and ignore any errors.

Hmm really? I'd think that if there is an actual error loading the module (module is installed, but the loading itself fails due to broken module, wrong permissions, I/O errors etc)
we would want to report that fact as it happens?

> 
> When a request would access a block that can't be uncompressed because
> of the missing module, that's where we can have a warn_report_once() and
> arguably should fail the I/O request.
> 
> Kevin
> 

That would mean, moving the

warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")

to the uncompression code and change it to a warn_report_once() right?

Thanks,

Claudio
Kevin Wolf Sept. 21, 2022, 11:43 a.m. UTC | #7
Am 21.09.2022 um 06:45 hat Markus Armbruster geschrieben:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
> >> On 9/8/22 19:10, Claudio Fontana wrote:
> >> > On 9/8/22 18:03, Richard Henderson wrote:
> >> >> On 9/8/22 15:53, Claudio Fontana wrote:
> >> >>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
> >> >>>           return -EINVAL;
> >> >>>       }
> >> >>>   
> >> >>> -    block_module_load_one("dmg-bz2");
> >> >>> -    block_module_load_one("dmg-lzfse");
> >> >>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> >> >>> +        error_report_err(local_err);
> >> >>> +    }
> >> >>> +    local_err = NULL;
> >> >>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> >> >>> +        error_report_err(local_err);
> >> >>> +    }
> >> >>>   
> >> >>>       s->n_chunks = 0;
> >> >>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> >> >>
> >> >> I wonder if these shouldn't fail hard if the modules don't exist?
> >> >> Or at least pass back the error.
> >> >>
> >> >> Kevin?
> >> 
> >> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
> >> image is not compressed, "dmg" can function even if the extra dmg-bz
> >> module is not loaded right?
> >
> > Indeed. The code seems to consider that the modules may not be present.
> > The behaviour in these cases is questionable (it seems to silently leave
> > the buffers as they are and return success), but the modules are clearly
> > optional.
> >
> >> I'd suspect we should then do:
> >> 
> >> if (!block_module_load_one("dmg-bz2", &local_err)) {
> >>   if (local_err) {
> >>      error_report_err(local_err);
> >>      return -EINVAL;
> >>   }
> >>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
> >> }
> >> 
> >> and same for dmg-lzfse...?
> >
> > Actually, I think during initialisation, we should just pass NULL as
> > errp and ignore any errors.
> >
> > When a request would access a block that can't be uncompressed because
> > of the missing module, that's where we can have a warn_report_once() and
> > arguably should fail the I/O request.
> 
> Seems like asking for data corruption.  To avoid it, the complete stack
> needs to handle I/O errors correctly.

If you have any component that doesn't handle I/O errors correctly, keep
it far away from your data because it _will_ cause corruption eventually.
The earlier it fails, the better for you.

I don't think we should put great effort into making fundamentally
broken software a little bit less broken in the corner case that you're
least likely to hit.

> Can we detect presence of compressed blocks on open?

We seem to read in the full metadata of the image in dmg_open(). So I
think it would be possible to detect it there.

dmg_read_mish_block() is what fills in s->types. However, it never fills
in types that it doesn't know (and it pretends it doesn't know the types
of compressed blocks whose module is not loaded). So instead of checking
it in dmg_open() after dmg_read_mish_block() has completed, you would
have to catch the situation already in dmg_read_mish_block() while
parsing the image file, which should be entirely doable if you want.

This is a change in dmg's behaviour, though, which is not the goal of
the proposed patch. So if we want to do that, it should be a separate
patch.

Kevin
Kevin Wolf Sept. 21, 2022, 11:56 a.m. UTC | #8
Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
> On 9/20/22 18:50, Kevin Wolf wrote:
> > Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
> >> On 9/8/22 19:10, Claudio Fontana wrote:
> >>> On 9/8/22 18:03, Richard Henderson wrote:
> >>>> On 9/8/22 15:53, Claudio Fontana wrote:
> >>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
> >>>>>           return -EINVAL;
> >>>>>       }
> >>>>>   
> >>>>> -    block_module_load_one("dmg-bz2");
> >>>>> -    block_module_load_one("dmg-lzfse");
> >>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> >>>>> +        error_report_err(local_err);
> >>>>> +    }
> >>>>> +    local_err = NULL;
> >>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> >>>>> +        error_report_err(local_err);
> >>>>> +    }
> >>>>>   
> >>>>>       s->n_chunks = 0;
> >>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> >>>>
> >>>> I wonder if these shouldn't fail hard if the modules don't exist?
> >>>> Or at least pass back the error.
> >>>>
> >>>> Kevin?
> >>
> >> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
> >> image is not compressed, "dmg" can function even if the extra dmg-bz
> >> module is not loaded right?
> > 
> > Indeed. The code seems to consider that the modules may not be present.
> > The behaviour in these cases is questionable (it seems to silently leave
> > the buffers as they are and return success)

I think I misunderstood the code here actually. dmg_read_mish_block()
skips chunks of unknown type, so later trying to find them fails and
dmg_co_preadv() returns -EIO. Which is a reasonable return value for
this.

> > , but the modules are clearly
> > optional.
> > 
> >> I'd suspect we should then do:
> >>
> >> if (!block_module_load_one("dmg-bz2", &local_err)) {
> >>   if (local_err) {
> >>      error_report_err(local_err);
> >>      return -EINVAL;
> >>   }
> >>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
> >> }
> >>
> >> and same for dmg-lzfse...?
> > 
> > Actually, I think during initialisation, we should just pass NULL as
> > errp and ignore any errors.
> 
> Hmm really? I'd think that if there is an actual error loading the
> module (module is installed, but the loading itself fails due to
> broken module, wrong permissions, I/O errors etc) we would want to
> report that fact as it happens?

Can we distinguish the two error cases?

Oooh... Reading the code again carefully, are you returning false
without setting errp if the module just couldn't be found? This is a
surprising interface.

Yes, I guess then your proposed code is fine (modulo moving
warn_report() somewhere else so that it doesn't complain when the image
doesn't even contain compressed chunks).

> > When a request would access a block that can't be uncompressed because
> > of the missing module, that's where we can have a warn_report_once() and
> > arguably should fail the I/O request.
> > 
> > Kevin
> > 
> 
> That would mean, moving the
> 
> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
> 
> to the uncompression code and change it to a warn_report_once() right?

Yeah, though I think this doesn't actually work because we never even
stored the metadata for chunks of unknown type (see above), so we never
reach the uncompression code.

What misled me initially is this code in dmg_read_chunk():

        case UDBZ: /* bzip2 compressed */
            if (!dmg_uncompress_bz2) {
                break;
            }

I believe this is dead code, it could actually be an assertion. So
if I'm not missing anything, adding the warning there would be useless.

The other option is moving it into dmg_is_known_block_type() or its
caller dmg_read_mish_block(), then we would detect it during open, which
is probably nicer anyway.

Kevin
Markus Armbruster Sept. 21, 2022, 12:08 p.m. UTC | #9
Kevin Wolf <kwolf@redhat.com> writes:

> Am 21.09.2022 um 06:45 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>> >> On 9/8/22 19:10, Claudio Fontana wrote:
>> >> > On 9/8/22 18:03, Richard Henderson wrote:
>> >> >> On 9/8/22 15:53, Claudio Fontana wrote:
>> >> >>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>> >> >>>           return -EINVAL;
>> >> >>>       }
>> >> >>>   
>> >> >>> -    block_module_load_one("dmg-bz2");
>> >> >>> -    block_module_load_one("dmg-lzfse");
>> >> >>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>> >> >>> +        error_report_err(local_err);
>> >> >>> +    }
>> >> >>> +    local_err = NULL;
>> >> >>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>> >> >>> +        error_report_err(local_err);
>> >> >>> +    }
>> >> >>>   
>> >> >>>       s->n_chunks = 0;
>> >> >>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>> >> >>
>> >> >> I wonder if these shouldn't fail hard if the modules don't exist?
>> >> >> Or at least pass back the error.
>> >> >>
>> >> >> Kevin?
>> >> 
>> >> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>> >> image is not compressed, "dmg" can function even if the extra dmg-bz
>> >> module is not loaded right?
>> >
>> > Indeed. The code seems to consider that the modules may not be present.
>> > The behaviour in these cases is questionable (it seems to silently leave
>> > the buffers as they are and return success), but the modules are clearly
>> > optional.
>> >
>> >> I'd suspect we should then do:
>> >> 
>> >> if (!block_module_load_one("dmg-bz2", &local_err)) {
>> >>   if (local_err) {
>> >>      error_report_err(local_err);
>> >>      return -EINVAL;
>> >>   }
>> >>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>> >> }
>> >> 
>> >> and same for dmg-lzfse...?
>> >
>> > Actually, I think during initialisation, we should just pass NULL as
>> > errp and ignore any errors.
>> >
>> > When a request would access a block that can't be uncompressed because
>> > of the missing module, that's where we can have a warn_report_once() and
>> > arguably should fail the I/O request.
>> 
>> Seems like asking for data corruption.  To avoid it, the complete stack
>> needs to handle I/O errors correctly.
>
> If you have any component that doesn't handle I/O errors correctly, keep
> it far away from your data because it _will_ cause corruption eventually.
> The earlier it fails, the better for you.

True for immortals.  Since we're not, it actually depends on
probabilities.

> I don't think we should put great effort into making fundamentally
> broken software a little bit less broken in the corner case that you're
> least likely to hit.

Fair point.

>> Can we detect presence of compressed blocks on open?
>
> We seem to read in the full metadata of the image in dmg_open(). So I
> think it would be possible to detect it there.
>
> dmg_read_mish_block() is what fills in s->types. However, it never fills
> in types that it doesn't know (and it pretends it doesn't know the types
> of compressed blocks whose module is not loaded). So instead of checking
> it in dmg_open() after dmg_read_mish_block() has completed, you would
> have to catch the situation already in dmg_read_mish_block() while
> parsing the image file, which should be entirely doable if you want.

In most cases, "open fails because some blocks are known to be
unreadable" is much better UX than "everything goes swimmingly until you
try to read one of the known-unreadable blocks".

Even when your software manages not to eat your data, surprise bad
blocks are still likely to result in a bad day.

> This is a change in dmg's behaviour, though, which is not the goal of
> the proposed patch. So if we want to do that, it should be a separate
> patch.

Makes sense.
Kevin Wolf Sept. 22, 2022, 2:33 p.m. UTC | #10
Am 21.09.2022 um 14:08 hat Markus Armbruster geschrieben:
> Kevin Wolf <kwolf@redhat.com> writes:
> 
> > Am 21.09.2022 um 06:45 hat Markus Armbruster geschrieben:
> >> Can we detect presence of compressed blocks on open?
> >
> > We seem to read in the full metadata of the image in dmg_open(). So I
> > think it would be possible to detect it there.
> >
> > dmg_read_mish_block() is what fills in s->types. However, it never fills
> > in types that it doesn't know (and it pretends it doesn't know the types
> > of compressed blocks whose module is not loaded). So instead of checking
> > it in dmg_open() after dmg_read_mish_block() has completed, you would
> > have to catch the situation already in dmg_read_mish_block() while
> > parsing the image file, which should be entirely doable if you want.
> 
> In most cases, "open fails because some blocks are known to be
> unreadable" is much better UX than "everything goes swimmingly until you
> try to read one of the known-unreadable blocks".
> 
> Even when your software manages not to eat your data, surprise bad
> blocks are still likely to result in a bad day.

That's fair. On the other hand, not allowing the user to read the part
of data that is perfectly readable would be bad, too.

Maybe the right solution would be to have a driver option like
"unknown-block-types=io-error|fail-open" (probably with better names),
and then having "fail-open" as the new default would be reasonable
enough.

Kevin
Markus Armbruster Sept. 22, 2022, 3:09 p.m. UTC | #11
Kevin Wolf <kwolf@redhat.com> writes:

> Am 21.09.2022 um 14:08 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Am 21.09.2022 um 06:45 hat Markus Armbruster geschrieben:
>> >> Can we detect presence of compressed blocks on open?
>> >
>> > We seem to read in the full metadata of the image in dmg_open(). So I
>> > think it would be possible to detect it there.
>> >
>> > dmg_read_mish_block() is what fills in s->types. However, it never fills
>> > in types that it doesn't know (and it pretends it doesn't know the types
>> > of compressed blocks whose module is not loaded). So instead of checking
>> > it in dmg_open() after dmg_read_mish_block() has completed, you would
>> > have to catch the situation already in dmg_read_mish_block() while
>> > parsing the image file, which should be entirely doable if you want.
>> 
>> In most cases, "open fails because some blocks are known to be
>> unreadable" is much better UX than "everything goes swimmingly until you
>> try to read one of the known-unreadable blocks".
>> 
>> Even when your software manages not to eat your data, surprise bad
>> blocks are still likely to result in a bad day.
>
> That's fair. On the other hand, not allowing the user to read the part
> of data that is perfectly readable would be bad, too.
>
> Maybe the right solution would be to have a driver option like
> "unknown-block-types=io-error|fail-open" (probably with better names),
> and then having "fail-open" as the new default would be reasonable
> enough.

Makes sense.
Claudio Fontana Sept. 23, 2022, 2:10 p.m. UTC | #12
On 9/21/22 13:56, Kevin Wolf wrote:
> Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
>> On 9/20/22 18:50, Kevin Wolf wrote:
>>> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>>>> On 9/8/22 19:10, Claudio Fontana wrote:
>>>>> On 9/8/22 18:03, Richard Henderson wrote:
>>>>>> On 9/8/22 15:53, Claudio Fontana wrote:
>>>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>>>           return -EINVAL;
>>>>>>>       }
>>>>>>>   
>>>>>>> -    block_module_load_one("dmg-bz2");
>>>>>>> -    block_module_load_one("dmg-lzfse");
>>>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>>>>>>> +        error_report_err(local_err);
>>>>>>> +    }
>>>>>>> +    local_err = NULL;
>>>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>>>>>>> +        error_report_err(local_err);
>>>>>>> +    }
>>>>>>>   
>>>>>>>       s->n_chunks = 0;
>>>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>>>>>>
>>>>>> I wonder if these shouldn't fail hard if the modules don't exist?
>>>>>> Or at least pass back the error.
>>>>>>
>>>>>> Kevin?
>>>>
>>>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>>>> image is not compressed, "dmg" can function even if the extra dmg-bz
>>>> module is not loaded right?
>>>
>>> Indeed. The code seems to consider that the modules may not be present.
>>> The behaviour in these cases is questionable (it seems to silently leave
>>> the buffers as they are and return success)
> 
> I think I misunderstood the code here actually. dmg_read_mish_block()
> skips chunks of unknown type, so later trying to find them fails and
> dmg_co_preadv() returns -EIO. Which is a reasonable return value for
> this.
> 
>>> , but the modules are clearly
>>> optional.
>>>
>>>> I'd suspect we should then do:
>>>>
>>>> if (!block_module_load_one("dmg-bz2", &local_err)) {
>>>>   if (local_err) {
>>>>      error_report_err(local_err);
>>>>      return -EINVAL;
>>>>   }
>>>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>>>> }
>>>>
>>>> and same for dmg-lzfse...?
>>>
>>> Actually, I think during initialisation, we should just pass NULL as
>>> errp and ignore any errors.
>>
>> Hmm really? I'd think that if there is an actual error loading the
>> module (module is installed, but the loading itself fails due to
>> broken module, wrong permissions, I/O errors etc) we would want to
>> report that fact as it happens?
> 
> Can we distinguish the two error cases?
> 
> Oooh... Reading the code again carefully, are you returning false
> without setting errp if the module just couldn't be found? This is a
> surprising interface.
> 
> Yes, I guess then your proposed code is fine (modulo moving
> warn_report() somewhere else so that it doesn't complain when the image
> doesn't even contain compressed chunks).
> 
>>> When a request would access a block that can't be uncompressed because
>>> of the missing module, that's where we can have a warn_report_once() and
>>> arguably should fail the I/O request.
>>>
>>> Kevin
>>>
>>
>> That would mean, moving the
>>
>> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
>>
>> to the uncompression code and change it to a warn_report_once() right?
> 
> Yeah, though I think this doesn't actually work because we never even
> stored the metadata for chunks of unknown type (see above), so we never
> reach the uncompression code.
> 
> What misled me initially is this code in dmg_read_chunk():
> 
>         case UDBZ: /* bzip2 compressed */
>             if (!dmg_uncompress_bz2) {
>                 break;
>             }
> 
> I believe this is dead code, it could actually be an assertion. So
> if I'm not missing anything, adding the warning there would be useless.
> 
> The other option is moving it into dmg_is_known_block_type() or its
> caller dmg_read_mish_block(), then we would detect it during open, which
> is probably nicer anyway.
> 
> Kevin
> 
> 

Hi Kevin, I got a bit lost on whether we have some agreement on where if anywhere to move the check/warning about missing decompression submodules.

If that's ok I'd post a V5, and we can rediscuss from the new starting point?

Thanks,

Claudio
Kevin Wolf Sept. 23, 2022, 2:42 p.m. UTC | #13
Am 23.09.2022 um 16:10 hat Claudio Fontana geschrieben:
> On 9/21/22 13:56, Kevin Wolf wrote:
> > Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
> >> On 9/20/22 18:50, Kevin Wolf wrote:
> >>> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
> >>>> On 9/8/22 19:10, Claudio Fontana wrote:
> >>>>> On 9/8/22 18:03, Richard Henderson wrote:
> >>>>>> On 9/8/22 15:53, Claudio Fontana wrote:
> >>>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
> >>>>>>>           return -EINVAL;
> >>>>>>>       }
> >>>>>>>   
> >>>>>>> -    block_module_load_one("dmg-bz2");
> >>>>>>> -    block_module_load_one("dmg-lzfse");
> >>>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> >>>>>>> +        error_report_err(local_err);
> >>>>>>> +    }
> >>>>>>> +    local_err = NULL;
> >>>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> >>>>>>> +        error_report_err(local_err);
> >>>>>>> +    }
> >>>>>>>   
> >>>>>>>       s->n_chunks = 0;
> >>>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> >>>>>>
> >>>>>> I wonder if these shouldn't fail hard if the modules don't exist?
> >>>>>> Or at least pass back the error.
> >>>>>>
> >>>>>> Kevin?
> >>>>
> >>>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
> >>>> image is not compressed, "dmg" can function even if the extra dmg-bz
> >>>> module is not loaded right?
> >>>
> >>> Indeed. The code seems to consider that the modules may not be present.
> >>> The behaviour in these cases is questionable (it seems to silently leave
> >>> the buffers as they are and return success)
> > 
> > I think I misunderstood the code here actually. dmg_read_mish_block()
> > skips chunks of unknown type, so later trying to find them fails and
> > dmg_co_preadv() returns -EIO. Which is a reasonable return value for
> > this.
> > 
> >>> , but the modules are clearly
> >>> optional.
> >>>
> >>>> I'd suspect we should then do:
> >>>>
> >>>> if (!block_module_load_one("dmg-bz2", &local_err)) {
> >>>>   if (local_err) {
> >>>>      error_report_err(local_err);
> >>>>      return -EINVAL;
> >>>>   }
> >>>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
> >>>> }
> >>>>
> >>>> and same for dmg-lzfse...?
> >>>
> >>> Actually, I think during initialisation, we should just pass NULL as
> >>> errp and ignore any errors.
> >>
> >> Hmm really? I'd think that if there is an actual error loading the
> >> module (module is installed, but the loading itself fails due to
> >> broken module, wrong permissions, I/O errors etc) we would want to
> >> report that fact as it happens?
> > 
> > Can we distinguish the two error cases?
> > 
> > Oooh... Reading the code again carefully, are you returning false
> > without setting errp if the module just couldn't be found? This is a
> > surprising interface.
> > 
> > Yes, I guess then your proposed code is fine (modulo moving
> > warn_report() somewhere else so that it doesn't complain when the image
> > doesn't even contain compressed chunks).
> > 
> >>> When a request would access a block that can't be uncompressed because
> >>> of the missing module, that's where we can have a warn_report_once() and
> >>> arguably should fail the I/O request.
> >>>
> >>> Kevin
> >>>
> >>
> >> That would mean, moving the
> >>
> >> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
> >>
> >> to the uncompression code and change it to a warn_report_once() right?
> > 
> > Yeah, though I think this doesn't actually work because we never even
> > stored the metadata for chunks of unknown type (see above), so we never
> > reach the uncompression code.
> > 
> > What misled me initially is this code in dmg_read_chunk():
> > 
> >         case UDBZ: /* bzip2 compressed */
> >             if (!dmg_uncompress_bz2) {
> >                 break;
> >             }
> > 
> > I believe this is dead code, it could actually be an assertion. So
> > if I'm not missing anything, adding the warning there would be useless.
> > 
> > The other option is moving it into dmg_is_known_block_type() or its
> > caller dmg_read_mish_block(), then we would detect it during open, which
> > is probably nicer anyway.
> > 
> > Kevin
> > 
> > 
> 
> Hi Kevin, I got a bit lost on whether we have some agreement on where
> if anywhere to move the check/warning about missing decompression
> submodules.
> 
> If that's ok I'd post a V5, and we can rediscuss from the new starting
> point?

Sure, feel free, though I don't think the code will otherwise change for
dmg, so we could as well continue here.

My conclusion was that only dmg_read_mish_block() or something called by
it can know whether compressed blocks exist in the image when the
modules aren't present. So if we want to make the warning conditional on
that (and my understanding is correct), this is where a
warn_report_once() would have to live.

Kevin
Claudio Fontana Sept. 23, 2022, 2:46 p.m. UTC | #14
On 9/23/22 16:42, Kevin Wolf wrote:
> Am 23.09.2022 um 16:10 hat Claudio Fontana geschrieben:
>> On 9/21/22 13:56, Kevin Wolf wrote:
>>> Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
>>>> On 9/20/22 18:50, Kevin Wolf wrote:
>>>>> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>>>>>> On 9/8/22 19:10, Claudio Fontana wrote:
>>>>>>> On 9/8/22 18:03, Richard Henderson wrote:
>>>>>>>> On 9/8/22 15:53, Claudio Fontana wrote:
>>>>>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>>>>>           return -EINVAL;
>>>>>>>>>       }
>>>>>>>>>   
>>>>>>>>> -    block_module_load_one("dmg-bz2");
>>>>>>>>> -    block_module_load_one("dmg-lzfse");
>>>>>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>>>>>>>>> +        error_report_err(local_err);
>>>>>>>>> +    }
>>>>>>>>> +    local_err = NULL;
>>>>>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>>>>>>>>> +        error_report_err(local_err);
>>>>>>>>> +    }
>>>>>>>>>   
>>>>>>>>>       s->n_chunks = 0;
>>>>>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>>>>>>>>
>>>>>>>> I wonder if these shouldn't fail hard if the modules don't exist?
>>>>>>>> Or at least pass back the error.
>>>>>>>>
>>>>>>>> Kevin?
>>>>>>
>>>>>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>>>>>> image is not compressed, "dmg" can function even if the extra dmg-bz
>>>>>> module is not loaded right?
>>>>>
>>>>> Indeed. The code seems to consider that the modules may not be present.
>>>>> The behaviour in these cases is questionable (it seems to silently leave
>>>>> the buffers as they are and return success)
>>>
>>> I think I misunderstood the code here actually. dmg_read_mish_block()
>>> skips chunks of unknown type, so later trying to find them fails and
>>> dmg_co_preadv() returns -EIO. Which is a reasonable return value for
>>> this.
>>>
>>>>> , but the modules are clearly
>>>>> optional.
>>>>>
>>>>>> I'd suspect we should then do:
>>>>>>
>>>>>> if (!block_module_load_one("dmg-bz2", &local_err)) {
>>>>>>   if (local_err) {
>>>>>>      error_report_err(local_err);
>>>>>>      return -EINVAL;
>>>>>>   }
>>>>>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>>>>>> }
>>>>>>
>>>>>> and same for dmg-lzfse...?
>>>>>
>>>>> Actually, I think during initialisation, we should just pass NULL as
>>>>> errp and ignore any errors.
>>>>
>>>> Hmm really? I'd think that if there is an actual error loading the
>>>> module (module is installed, but the loading itself fails due to
>>>> broken module, wrong permissions, I/O errors etc) we would want to
>>>> report that fact as it happens?
>>>
>>> Can we distinguish the two error cases?
>>>
>>> Oooh... Reading the code again carefully, are you returning false
>>> without setting errp if the module just couldn't be found? This is a
>>> surprising interface.
>>>
>>> Yes, I guess then your proposed code is fine (modulo moving
>>> warn_report() somewhere else so that it doesn't complain when the image
>>> doesn't even contain compressed chunks).
>>>
>>>>> When a request would access a block that can't be uncompressed because
>>>>> of the missing module, that's where we can have a warn_report_once() and
>>>>> arguably should fail the I/O request.
>>>>>
>>>>> Kevin
>>>>>
>>>>
>>>> That would mean, moving the
>>>>
>>>> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
>>>>
>>>> to the uncompression code and change it to a warn_report_once() right?
>>>
>>> Yeah, though I think this doesn't actually work because we never even
>>> stored the metadata for chunks of unknown type (see above), so we never
>>> reach the uncompression code.
>>>
>>> What misled me initially is this code in dmg_read_chunk():
>>>
>>>         case UDBZ: /* bzip2 compressed */
>>>             if (!dmg_uncompress_bz2) {
>>>                 break;
>>>             }
>>>
>>> I believe this is dead code, it could actually be an assertion. So
>>> if I'm not missing anything, adding the warning there would be useless.
>>>
>>> The other option is moving it into dmg_is_known_block_type() or its
>>> caller dmg_read_mish_block(), then we would detect it during open, which
>>> is probably nicer anyway.
>>>
>>> Kevin
>>>
>>>
>>
>> Hi Kevin, I got a bit lost on whether we have some agreement on where
>> if anywhere to move the check/warning about missing decompression
>> submodules.
>>
>> If that's ok I'd post a V5, and we can rediscuss from the new starting
>> point?
> 
> Sure, feel free, though I don't think the code will otherwise change for
> dmg, so we could as well continue here.
> 
> My conclusion was that only dmg_read_mish_block() or something called by
> it can know whether compressed blocks exist in the image when the
> modules aren't present. So if we want to make the warning conditional on
> that (and my understanding is correct), this is where a
> warn_report_once() would have to live.
> 
> Kevin
> 

I took a look, but I feel a bit too ignorant of the code there, maybe you could move the warning as a patch to the right place after the series?

Or give me the extra commit needed to move into the right place?

Ciao,

Claudio
Kevin Wolf Sept. 23, 2022, 4:29 p.m. UTC | #15
Am 23.09.2022 um 16:46 hat Claudio Fontana geschrieben:
> On 9/23/22 16:42, Kevin Wolf wrote:
> > Am 23.09.2022 um 16:10 hat Claudio Fontana geschrieben:
> >> On 9/21/22 13:56, Kevin Wolf wrote:
> >>> Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
> >>>> On 9/20/22 18:50, Kevin Wolf wrote:
> >>>>> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
> >>>>>> On 9/8/22 19:10, Claudio Fontana wrote:
> >>>>>>> On 9/8/22 18:03, Richard Henderson wrote:
> >>>>>>>> On 9/8/22 15:53, Claudio Fontana wrote:
> >>>>>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
> >>>>>>>>>           return -EINVAL;
> >>>>>>>>>       }
> >>>>>>>>>   
> >>>>>>>>> -    block_module_load_one("dmg-bz2");
> >>>>>>>>> -    block_module_load_one("dmg-lzfse");
> >>>>>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
> >>>>>>>>> +        error_report_err(local_err);
> >>>>>>>>> +    }
> >>>>>>>>> +    local_err = NULL;
> >>>>>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
> >>>>>>>>> +        error_report_err(local_err);
> >>>>>>>>> +    }
> >>>>>>>>>   
> >>>>>>>>>       s->n_chunks = 0;
> >>>>>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
> >>>>>>>>
> >>>>>>>> I wonder if these shouldn't fail hard if the modules don't exist?
> >>>>>>>> Or at least pass back the error.
> >>>>>>>>
> >>>>>>>> Kevin?
> >>>>>>
> >>>>>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
> >>>>>> image is not compressed, "dmg" can function even if the extra dmg-bz
> >>>>>> module is not loaded right?
> >>>>>
> >>>>> Indeed. The code seems to consider that the modules may not be present.
> >>>>> The behaviour in these cases is questionable (it seems to silently leave
> >>>>> the buffers as they are and return success)
> >>>
> >>> I think I misunderstood the code here actually. dmg_read_mish_block()
> >>> skips chunks of unknown type, so later trying to find them fails and
> >>> dmg_co_preadv() returns -EIO. Which is a reasonable return value for
> >>> this.
> >>>
> >>>>> , but the modules are clearly
> >>>>> optional.
> >>>>>
> >>>>>> I'd suspect we should then do:
> >>>>>>
> >>>>>> if (!block_module_load_one("dmg-bz2", &local_err)) {
> >>>>>>   if (local_err) {
> >>>>>>      error_report_err(local_err);
> >>>>>>      return -EINVAL;
> >>>>>>   }
> >>>>>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
> >>>>>> }
> >>>>>>
> >>>>>> and same for dmg-lzfse...?
> >>>>>
> >>>>> Actually, I think during initialisation, we should just pass NULL as
> >>>>> errp and ignore any errors.
> >>>>
> >>>> Hmm really? I'd think that if there is an actual error loading the
> >>>> module (module is installed, but the loading itself fails due to
> >>>> broken module, wrong permissions, I/O errors etc) we would want to
> >>>> report that fact as it happens?
> >>>
> >>> Can we distinguish the two error cases?
> >>>
> >>> Oooh... Reading the code again carefully, are you returning false
> >>> without setting errp if the module just couldn't be found? This is a
> >>> surprising interface.
> >>>
> >>> Yes, I guess then your proposed code is fine (modulo moving
> >>> warn_report() somewhere else so that it doesn't complain when the image
> >>> doesn't even contain compressed chunks).
> >>>
> >>>>> When a request would access a block that can't be uncompressed because
> >>>>> of the missing module, that's where we can have a warn_report_once() and
> >>>>> arguably should fail the I/O request.
> >>>>>
> >>>>> Kevin
> >>>>>
> >>>>
> >>>> That would mean, moving the
> >>>>
> >>>> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
> >>>>
> >>>> to the uncompression code and change it to a warn_report_once() right?
> >>>
> >>> Yeah, though I think this doesn't actually work because we never even
> >>> stored the metadata for chunks of unknown type (see above), so we never
> >>> reach the uncompression code.
> >>>
> >>> What misled me initially is this code in dmg_read_chunk():
> >>>
> >>>         case UDBZ: /* bzip2 compressed */
> >>>             if (!dmg_uncompress_bz2) {
> >>>                 break;
> >>>             }
> >>>
> >>> I believe this is dead code, it could actually be an assertion. So
> >>> if I'm not missing anything, adding the warning there would be useless.
> >>>
> >>> The other option is moving it into dmg_is_known_block_type() or its
> >>> caller dmg_read_mish_block(), then we would detect it during open, which
> >>> is probably nicer anyway.
> >>>
> >>> Kevin
> >>>
> >>>
> >>
> >> Hi Kevin, I got a bit lost on whether we have some agreement on where
> >> if anywhere to move the check/warning about missing decompression
> >> submodules.
> >>
> >> If that's ok I'd post a V5, and we can rediscuss from the new starting
> >> point?
> > 
> > Sure, feel free, though I don't think the code will otherwise change for
> > dmg, so we could as well continue here.
> > 
> > My conclusion was that only dmg_read_mish_block() or something called by
> > it can know whether compressed blocks exist in the image when the
> > modules aren't present. So if we want to make the warning conditional on
> > that (and my understanding is correct), this is where a
> > warn_report_once() would have to live.
> > 
> > Kevin
> > 
> 
> I took a look, but I feel a bit too ignorant of the code there, maybe you could move the warning as a patch to the right place after the series?
> 
> Or give me the extra commit needed to move into the right place?

Only built and tested with an uncompressed image, so this could use
testing with an actual compressed dmg image and the module present or
missing, but something like the following should do the trick.

Kevin


diff --git a/block/dmg.c b/block/dmg.c
index 98db18d82a..630cde3416 100644
--- a/block/dmg.c
+++ b/block/dmg.c
@@ -254,6 +254,25 @@ static int dmg_read_mish_block(BDRVDMGState *s, DmgHeaderState *ds,
     for (i = s->n_chunks; i < s->n_chunks + chunk_count; i++) {
         s->types[i] = buff_read_uint32(buffer, offset);
         if (!dmg_is_known_block_type(s->types[i])) {
+            switch (s->types[i]) {
+            case UDBZ:
+                warn_report_once("dmg-bzip2 module is missing, accessing bzip2 "
+                                 "compressed blocks will result in I/O errors");
+                break;
+            case ULFO:
+                warn_report_once("dmg-lzfse module is missing, accessing lzfse "
+                                 "compressed blocks will result in I/O errors");
+                break;
+            case UDCM:
+            case UDLE:
+                /* Comments and last entry can be ignored without problems */
+                break;
+            default:
+                warn_report_once("Image contains chunks of unknown type %x, "
+                                 "accessing them will result in I/O errors",
+                                 s->types[i]);
+                break;
+            }
             chunk_count--;
             i--;
             offset += 40;
Claudio Fontana Sept. 23, 2022, 10:23 p.m. UTC | #16
On 9/23/22 18:29, Kevin Wolf wrote:
> Am 23.09.2022 um 16:46 hat Claudio Fontana geschrieben:
>> On 9/23/22 16:42, Kevin Wolf wrote:
>>> Am 23.09.2022 um 16:10 hat Claudio Fontana geschrieben:
>>>> On 9/21/22 13:56, Kevin Wolf wrote:
>>>>> Am 21.09.2022 um 09:50 hat Claudio Fontana geschrieben:
>>>>>> On 9/20/22 18:50, Kevin Wolf wrote:
>>>>>>> Am 08.09.2022 um 19:36 hat Claudio Fontana geschrieben:
>>>>>>>> On 9/8/22 19:10, Claudio Fontana wrote:
>>>>>>>>> On 9/8/22 18:03, Richard Henderson wrote:
>>>>>>>>>> On 9/8/22 15:53, Claudio Fontana wrote:
>>>>>>>>>>> @@ -446,8 +447,13 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
>>>>>>>>>>>           return -EINVAL;
>>>>>>>>>>>       }
>>>>>>>>>>>   
>>>>>>>>>>> -    block_module_load_one("dmg-bz2");
>>>>>>>>>>> -    block_module_load_one("dmg-lzfse");
>>>>>>>>>>> +    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
>>>>>>>>>>> +        error_report_err(local_err);
>>>>>>>>>>> +    }
>>>>>>>>>>> +    local_err = NULL;
>>>>>>>>>>> +    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
>>>>>>>>>>> +        error_report_err(local_err);
>>>>>>>>>>> +    }
>>>>>>>>>>>   
>>>>>>>>>>>       s->n_chunks = 0;
>>>>>>>>>>>       s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
>>>>>>>>>>
>>>>>>>>>> I wonder if these shouldn't fail hard if the modules don't exist?
>>>>>>>>>> Or at least pass back the error.
>>>>>>>>>>
>>>>>>>>>> Kevin?
>>>>>>>>
>>>>>>>> is "dmg-bz" _required_ for dmg open to work? I suspect if the dmg
>>>>>>>> image is not compressed, "dmg" can function even if the extra dmg-bz
>>>>>>>> module is not loaded right?
>>>>>>>
>>>>>>> Indeed. The code seems to consider that the modules may not be present.
>>>>>>> The behaviour in these cases is questionable (it seems to silently leave
>>>>>>> the buffers as they are and return success)
>>>>>
>>>>> I think I misunderstood the code here actually. dmg_read_mish_block()
>>>>> skips chunks of unknown type, so later trying to find them fails and
>>>>> dmg_co_preadv() returns -EIO. Which is a reasonable return value for
>>>>> this.
>>>>>
>>>>>>> , but the modules are clearly
>>>>>>> optional.
>>>>>>>
>>>>>>>> I'd suspect we should then do:
>>>>>>>>
>>>>>>>> if (!block_module_load_one("dmg-bz2", &local_err)) {
>>>>>>>>   if (local_err) {
>>>>>>>>      error_report_err(local_err);
>>>>>>>>      return -EINVAL;
>>>>>>>>   }
>>>>>>>>   warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks */
>>>>>>>> }
>>>>>>>>
>>>>>>>> and same for dmg-lzfse...?
>>>>>>>
>>>>>>> Actually, I think during initialisation, we should just pass NULL as
>>>>>>> errp and ignore any errors.
>>>>>>
>>>>>> Hmm really? I'd think that if there is an actual error loading the
>>>>>> module (module is installed, but the loading itself fails due to
>>>>>> broken module, wrong permissions, I/O errors etc) we would want to
>>>>>> report that fact as it happens?
>>>>>
>>>>> Can we distinguish the two error cases?
>>>>>
>>>>> Oooh... Reading the code again carefully, are you returning false
>>>>> without setting errp if the module just couldn't be found? This is a
>>>>> surprising interface.
>>>>>
>>>>> Yes, I guess then your proposed code is fine (modulo moving
>>>>> warn_report() somewhere else so that it doesn't complain when the image
>>>>> doesn't even contain compressed chunks).
>>>>>
>>>>>>> When a request would access a block that can't be uncompressed because
>>>>>>> of the missing module, that's where we can have a warn_report_once() and
>>>>>>> arguably should fail the I/O request.
>>>>>>>
>>>>>>> Kevin
>>>>>>>
>>>>>>
>>>>>> That would mean, moving the
>>>>>>
>>>>>> warn_report("dmg-bz2 is not present, dmg will skip bz2-compressed chunks")
>>>>>>
>>>>>> to the uncompression code and change it to a warn_report_once() right?
>>>>>
>>>>> Yeah, though I think this doesn't actually work because we never even
>>>>> stored the metadata for chunks of unknown type (see above), so we never
>>>>> reach the uncompression code.
>>>>>
>>>>> What misled me initially is this code in dmg_read_chunk():
>>>>>
>>>>>         case UDBZ: /* bzip2 compressed */
>>>>>             if (!dmg_uncompress_bz2) {
>>>>>                 break;
>>>>>             }
>>>>>
>>>>> I believe this is dead code, it could actually be an assertion. So
>>>>> if I'm not missing anything, adding the warning there would be useless.
>>>>>
>>>>> The other option is moving it into dmg_is_known_block_type() or its
>>>>> caller dmg_read_mish_block(), then we would detect it during open, which
>>>>> is probably nicer anyway.
>>>>>
>>>>> Kevin
>>>>>
>>>>>
>>>>
>>>> Hi Kevin, I got a bit lost on whether we have some agreement on where
>>>> if anywhere to move the check/warning about missing decompression
>>>> submodules.
>>>>
>>>> If that's ok I'd post a V5, and we can rediscuss from the new starting
>>>> point?
>>>
>>> Sure, feel free, though I don't think the code will otherwise change for
>>> dmg, so we could as well continue here.
>>>
>>> My conclusion was that only dmg_read_mish_block() or something called by
>>> it can know whether compressed blocks exist in the image when the
>>> modules aren't present. So if we want to make the warning conditional on
>>> that (and my understanding is correct), this is where a
>>> warn_report_once() would have to live.
>>>
>>> Kevin
>>>
>>
>> I took a look, but I feel a bit too ignorant of the code there, maybe you could move the warning as a patch to the right place after the series?
>>
>> Or give me the extra commit needed to move into the right place?
> 
> Only built and tested with an uncompressed image, so this could use
> testing with an actual compressed dmg image and the module present or
> missing, but something like the following should do the trick.
> 
> Kevin
> 
> 
> diff --git a/block/dmg.c b/block/dmg.c
> index 98db18d82a..630cde3416 100644
> --- a/block/dmg.c
> +++ b/block/dmg.c
> @@ -254,6 +254,25 @@ static int dmg_read_mish_block(BDRVDMGState *s, DmgHeaderState *ds,
>      for (i = s->n_chunks; i < s->n_chunks + chunk_count; i++) {
>          s->types[i] = buff_read_uint32(buffer, offset);
>          if (!dmg_is_known_block_type(s->types[i])) {
> +            switch (s->types[i]) {
> +            case UDBZ:
> +                warn_report_once("dmg-bzip2 module is missing, accessing bzip2 "
> +                                 "compressed blocks will result in I/O errors");
> +                break;
> +            case ULFO:
> +                warn_report_once("dmg-lzfse module is missing, accessing lzfse "
> +                                 "compressed blocks will result in I/O errors");
> +                break;
> +            case UDCM:
> +            case UDLE:
> +                /* Comments and last entry can be ignored without problems */
> +                break;
> +            default:
> +                warn_report_once("Image contains chunks of unknown type %x, "
> +                                 "accessing them will result in I/O errors",
> +                                 s->types[i]);
> +                break;
> +            }
>              chunk_count--;
>              i--;
>              offset += 40;
> 

Awesome thanks!

Claudio
diff mbox series

Patch

diff --git a/audio/audio.c b/audio/audio.c
index 76b8735b44..4f4bb10cce 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -72,6 +72,7 @@  void audio_driver_register(audio_driver *drv)
 audio_driver *audio_driver_lookup(const char *name)
 {
     struct audio_driver *d;
+    Error *local_err = NULL;
 
     QLIST_FOREACH(d, &audio_drivers, next) {
         if (strcmp(name, d->name) == 0) {
@@ -79,7 +80,10 @@  audio_driver *audio_driver_lookup(const char *name)
         }
     }
 
-    audio_module_load_one(name);
+    if (!audio_module_load_one(name, &local_err) && local_err) {
+        error_report_err(local_err);
+    }
+
     QLIST_FOREACH(d, &audio_drivers, next) {
         if (strcmp(name, d->name) == 0) {
             return d;
diff --git a/block.c b/block.c
index bc85f46eed..85c3742d7a 100644
--- a/block.c
+++ b/block.c
@@ -464,7 +464,11 @@  BlockDriver *bdrv_find_format(const char *format_name)
     /* The driver isn't registered, maybe we need to load a module */
     for (i = 0; i < (int)ARRAY_SIZE(block_driver_modules); ++i) {
         if (!strcmp(block_driver_modules[i].format_name, format_name)) {
-            block_module_load_one(block_driver_modules[i].library_name);
+            Error *local_err = NULL;
+            if (!block_module_load_one(block_driver_modules[i].library_name,
+                                       &local_err) && local_err) {
+                error_report_err(local_err);
+            }
             break;
         }
     }
@@ -976,7 +980,11 @@  BlockDriver *bdrv_find_protocol(const char *filename,
     for (i = 0; i < (int)ARRAY_SIZE(block_driver_modules); ++i) {
         if (block_driver_modules[i].protocol_name &&
             !strcmp(block_driver_modules[i].protocol_name, protocol)) {
-            block_module_load_one(block_driver_modules[i].library_name);
+            Error *local_err = NULL;
+            if (!block_module_load_one(block_driver_modules[i].library_name,
+                                       &local_err) && local_err) {
+                error_report_err(local_err);
+            }
             break;
         }
     }
diff --git a/block/dmg.c b/block/dmg.c
index 98db18d82a..349b05d20b 100644
--- a/block/dmg.c
+++ b/block/dmg.c
@@ -434,6 +434,7 @@  static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
     uint64_t plist_xml_offset, plist_xml_length;
     int64_t offset;
     int ret;
+    Error *local_err = NULL;
 
     ret = bdrv_apply_auto_read_only(bs, NULL, errp);
     if (ret < 0) {
@@ -446,8 +447,13 @@  static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
         return -EINVAL;
     }
 
-    block_module_load_one("dmg-bz2");
-    block_module_load_one("dmg-lzfse");
+    if (!block_module_load_one("dmg-bz2", &local_err) && local_err) {
+        error_report_err(local_err);
+    }
+    local_err = NULL;
+    if (!block_module_load_one("dmg-lzfse", &local_err) && local_err) {
+        error_report_err(local_err);
+    }
 
     s->n_chunks = 0;
     s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index 0806d8fcaa..5902c59c94 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -148,7 +148,15 @@  bool qdev_set_parent_bus(DeviceState *dev, BusState *bus, Error **errp)
 DeviceState *qdev_new(const char *name)
 {
     if (!object_class_by_name(name)) {
-        module_load_qom_one(name);
+        Error *local_err = NULL;
+        if (!module_load_qom_one(name, &local_err)) {
+            if (local_err) {
+                error_report_err(local_err);
+            } else {
+                error_report("could not find a module for type '%s'", name);
+            }
+            abort();
+        }
     }
     return DEVICE(object_new(name));
 }
diff --git a/include/qemu/module.h b/include/qemu/module.h
index 8c012bbe03..e1fd4a6d7b 100644
--- a/include/qemu/module.h
+++ b/include/qemu/module.h
@@ -61,16 +61,33 @@  typedef enum {
 #define fuzz_target_init(function) module_init(function, \
                                                MODULE_INIT_FUZZ_TARGET)
 #define migration_init(function) module_init(function, MODULE_INIT_MIGRATION)
-#define block_module_load_one(lib) module_load_one("block-", lib)
-#define ui_module_load_one(lib) module_load_one("ui-", lib)
-#define audio_module_load_one(lib) module_load_one("audio-", lib)
+#define block_module_load_one(lib, errp) module_load_one("block-", lib, errp)
+#define ui_module_load_one(lib, errp) module_load_one("ui-", lib, errp)
+#define audio_module_load_one(lib, errp) module_load_one("audio-", lib, errp)
 
 void register_module_init(void (*fn)(void), module_init_type type);
 void register_dso_module_init(void (*fn)(void), module_init_type type);
 
 void module_call_init(module_init_type type);
-bool module_load_one(const char *prefix, const char *lib_name);
-void module_load_qom_one(const char *type);
+
+/*
+ * module_load_one: attempt to load a module from a set of directories
+ *
+ * directories searched are:
+ * - getenv("QEMU_MODULE_DIR")
+ * - get_relocated_path(CONFIG_QEMU_MODDIR);
+ * - /var/run/qemu/${version_dir}
+ *
+ * prefix:         a subsystem prefix, or the empty string ("audio-", ..., "")
+ * name:           name of the module
+ * errp:           error to set in case the module is found, but load failed.
+ *
+ * Return value:   true on success (found and loaded);
+ *                 if module if found, but load failed, errp will be set.
+ *                 if module is not found, errp will not be set.
+ */
+bool module_load_one(const char *prefix, const char *name, Error **errp);
+bool module_load_qom_one(const char *type, Error **errp);
 void module_load_qom_all(void);
 void module_allow_arch(const char *arch);
 
diff --git a/qom/object.c b/qom/object.c
index d34608558e..6a74e6a478 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -526,8 +526,14 @@  void object_initialize(void *data, size_t size, const char *typename)
 
 #ifdef CONFIG_MODULES
     if (!type) {
-        module_load_qom_one(typename);
-        type = type_get_by_name(typename);
+        Error *local_err = NULL;
+        if (!module_load_qom_one(typename, &local_err)) {
+            if (local_err) {
+                error_report_err(local_err);
+            }
+        } else {
+            type = type_get_by_name(typename);
+        }
     }
 #endif
     if (!type) {
@@ -1033,7 +1039,10 @@  ObjectClass *module_object_class_by_name(const char *typename)
     oc = object_class_by_name(typename);
 #ifdef CONFIG_MODULES
     if (!oc) {
-        module_load_qom_one(typename);
+        Error *local_err = NULL;
+        if (!module_load_qom_one(typename, &local_err) && local_err) {
+            error_report_err(local_err);
+        }
         oc = object_class_by_name(typename);
     }
 #endif
diff --git a/softmmu/qtest.c b/softmmu/qtest.c
index 76eb7bac56..bb83c7aae9 100644
--- a/softmmu/qtest.c
+++ b/softmmu/qtest.c
@@ -753,12 +753,16 @@  static void qtest_process_command(CharBackend *chr, gchar **words)
         qtest_sendf(chr, "OK %"PRIi64"\n",
                     (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
     } else if (strcmp(words[0], "module_load") == 0) {
+        Error *local_err = NULL;
         g_assert(words[1] && words[2]);
 
         qtest_send_prefix(chr);
-        if (module_load_one(words[1], words[2])) {
+        if (module_load_one(words[1], words[2], &local_err)) {
             qtest_sendf(chr, "OK\n");
         } else {
+            if (local_err) {
+                error_report_err(local_err);
+            }
             qtest_sendf(chr, "FAIL\n");
         }
     } else if (qtest_enabled() && strcmp(words[0], "clock_set") == 0) {
diff --git a/ui/console.c b/ui/console.c
index 765892f84f..34dd206167 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -2632,7 +2632,11 @@  bool qemu_display_find_default(DisplayOptions *opts)
 
     for (i = 0; i < (int)ARRAY_SIZE(prio); i++) {
         if (dpys[prio[i]] == NULL) {
-            ui_module_load_one(DisplayType_str(prio[i]));
+            Error *local_err = NULL;
+            if (!ui_module_load_one(DisplayType_str(prio[i]), &local_err)
+                && local_err) {
+                error_report_err(local_err);
+            }
         }
         if (dpys[prio[i]] == NULL) {
             continue;
@@ -2650,7 +2654,11 @@  void qemu_display_early_init(DisplayOptions *opts)
         return;
     }
     if (dpys[opts->type] == NULL) {
-        ui_module_load_one(DisplayType_str(opts->type));
+        Error *local_err = NULL;
+        if (!ui_module_load_one(DisplayType_str(opts->type), &local_err)
+            && local_err) {
+            error_report_err(local_err);
+        }
     }
     if (dpys[opts->type] == NULL) {
         error_report("Display '%s' is not available.",
@@ -2680,7 +2688,11 @@  void qemu_display_help(void)
     printf("none\n");
     for (idx = DISPLAY_TYPE_NONE; idx < DISPLAY_TYPE__MAX; idx++) {
         if (!dpys[idx]) {
-            ui_module_load_one(DisplayType_str(idx));
+            Error *local_err = NULL;
+            if (!ui_module_load_one(DisplayType_str(idx), &local_err)
+                && local_err) {
+                error_report_err(local_err);
+            }
         }
         if (dpys[idx]) {
             printf("%s\n",  DisplayType_str(dpys[idx]->type));
diff --git a/util/module.c b/util/module.c
index 8563edd626..14e18e59c6 100644
--- a/util/module.c
+++ b/util/module.c
@@ -21,6 +21,7 @@ 
 #include "qemu/module.h"
 #include "qemu/cutils.h"
 #include "qemu/config-file.h"
+#include "qapi/error.h"
 #ifdef CONFIG_MODULE_UPGRADES
 #include "qemu-version.h"
 #endif
@@ -144,25 +145,22 @@  static bool module_check_arch(const QemuModinfo *modinfo)
     return true;
 }
 
-static int module_load_file(const char *fname, bool export_symbols)
+/*
+ * module_load_dso: attempt to load an existing dso file
+ *
+ * fname:          full pathname of the file to load
+ * export_symbols: if true, add the symbols to the global name space
+ * errp:           error to set.
+ *
+ * Return value:   true on success, false on error, and errp will be set.
+ */
+static bool module_load_dso(const char *fname, bool export_symbols,
+                            Error **errp)
 {
     GModule *g_module;
     void (*sym)(void);
-    const char *dsosuf = CONFIG_HOST_DSOSUF;
-    int len = strlen(fname);
-    int suf_len = strlen(dsosuf);
     ModuleEntry *e, *next;
-    int ret, flags;
-
-    if (len <= suf_len || strcmp(&fname[len - suf_len], dsosuf)) {
-        /* wrong suffix */
-        ret = -EINVAL;
-        goto out;
-    }
-    if (access(fname, F_OK)) {
-        ret = -ENOENT;
-        goto out;
-    }
+    int flags;
 
     assert(QTAILQ_EMPTY(&dso_init_list));
 
@@ -172,46 +170,38 @@  static int module_load_file(const char *fname, bool export_symbols)
     }
     g_module = g_module_open(fname, flags);
     if (!g_module) {
-        fprintf(stderr, "Failed to open module: %s\n",
-                g_module_error());
-        ret = -EINVAL;
-        goto out;
+        error_setg(errp, "failed to open module: %s", g_module_error());
+        return false;
     }
     if (!g_module_symbol(g_module, DSO_STAMP_FUN_STR, (gpointer *)&sym)) {
-        fprintf(stderr, "Failed to initialize module: %s\n",
-                fname);
-        /* Print some info if this is a QEMU module (but from different build),
-         * this will make debugging user problems easier. */
+        error_setg(errp, "failed to initialize module: %s", fname);
+        /*
+         * Print some info if this is a QEMU module (but from different build),
+         * this will make debugging user problems easier.
+         */
         if (g_module_symbol(g_module, "qemu_module_dummy", (gpointer *)&sym)) {
-            fprintf(stderr,
-                    "Note: only modules from the same build can be loaded.\n");
+            error_append_hint(errp,
+                              "Only modules from the same build can be loaded");
         }
         g_module_close(g_module);
-        ret = -EINVAL;
-    } else {
-        QTAILQ_FOREACH(e, &dso_init_list, node) {
-            e->init();
-            register_module_init(e->init, e->type);
-        }
-        ret = 0;
+        return false;
     }
 
+    QTAILQ_FOREACH(e, &dso_init_list, node) {
+        e->init();
+        register_module_init(e->init, e->type);
+    }
     trace_module_load_module(fname);
     QTAILQ_FOREACH_SAFE(e, &dso_init_list, node, next) {
         QTAILQ_REMOVE(&dso_init_list, e, node);
         g_free(e);
     }
-out:
-    return ret;
+    return true;
 }
-#endif
 
-bool module_load_one(const char *prefix, const char *lib_name)
+bool module_load_one(const char *prefix, const char *name, Error **errp)
 {
     bool success = false;
-
-#ifdef CONFIG_MODULES
-    char *fname = NULL;
 #ifdef CONFIG_MODULE_UPGRADES
     char *version_dir;
 #endif
@@ -219,14 +209,13 @@  bool module_load_one(const char *prefix, const char *lib_name)
     char *dirs[5];
     char *module_name;
     int i = 0, n_dirs = 0;
-    int ret;
     bool export_symbols = false;
     static GHashTable *loaded_modules;
     const QemuModinfo *modinfo;
     const char **sl;
 
     if (!g_module_supported()) {
-        fprintf(stderr, "Module is not supported by system.\n");
+        error_setg(errp, "%s", "this platform does not support GLib modules");
         return false;
     }
 
@@ -234,7 +223,7 @@  bool module_load_one(const char *prefix, const char *lib_name)
         loaded_modules = g_hash_table_new(g_str_hash, g_str_equal);
     }
 
-    module_name = g_strdup_printf("%s%s", prefix, lib_name);
+    module_name = g_strdup_printf("%s%s", prefix, name);
 
     if (g_hash_table_contains(loaded_modules, module_name)) {
         g_free(module_name);
@@ -246,6 +235,8 @@  bool module_load_one(const char *prefix, const char *lib_name)
         if (modinfo->arch) {
             if (strcmp(modinfo->name, module_name) == 0) {
                 if (!module_check_arch(modinfo)) {
+                    error_setg(errp, "module arch does not match: "
+                        "expected '%s', got '%s'", module_arch, modinfo->arch);
                     return false;
                 }
             }
@@ -254,7 +245,9 @@  bool module_load_one(const char *prefix, const char *lib_name)
             if (strcmp(modinfo->name, module_name) == 0) {
                 /* we depend on other module(s) */
                 for (sl = modinfo->deps; *sl != NULL; sl++) {
-                    module_load_one("", *sl);
+                    if (!(module_load_one("", *sl, errp))) {
+                        return false;
+                    }
                 }
             } else {
                 for (sl = modinfo->deps; *sl != NULL; sl++) {
@@ -283,16 +276,26 @@  bool module_load_one(const char *prefix, const char *lib_name)
     assert(n_dirs <= ARRAY_SIZE(dirs));
 
     for (i = 0; i < n_dirs; i++) {
-        fname = g_strdup_printf("%s/%s%s",
-                dirs[i], module_name, CONFIG_HOST_DSOSUF);
-        ret = module_load_file(fname, export_symbols);
-        g_free(fname);
-        fname = NULL;
-        /* Try loading until loaded a module file */
-        if (!ret) {
-            success = true;
+        char *fname = g_strdup_printf("%s/%s%s",
+                                      dirs[i], module_name, CONFIG_HOST_DSOSUF);
+        int ret = access(fname, F_OK);
+        if (ret != 0) {
+            if (errno == ENOENT || errno == ENOTDIR) {
+                /*
+                 * if we just don't find the module in this dir, try the next one.
+                 * If we don't find it in any dir, that can be fine too, user or
+                 * distro did not install the module. We will return false in this
+                 * case, with no error set.
+                 */
+                continue;
+            }
+            /* most common is EACCES here */
+            error_setg_errno(errp, errno, "error trying to access %s", fname);
             break;
         }
+        success = module_load_dso(fname, export_symbols, errp);
+        g_free(fname);
+        break;
     }
 
     if (!success) {
@@ -304,21 +307,30 @@  bool module_load_one(const char *prefix, const char *lib_name)
         g_free(dirs[i]);
     }
 
-#endif
     return success;
 }
 
-#ifdef CONFIG_MODULES
-
 static bool module_loaded_qom_all;
 
-void module_load_qom_one(const char *type)
+/*
+ * module_load_qom_one: attempt to load a module to provide a QOM type
+ *
+ * type:           the type to be provided
+ * errp:           error to set.
+ *
+ * Return value:   true on success (found and loaded), false on failure.
+ *                 If a module is simply not found for the type,
+ *                 errp will not be set.
+ */
+bool module_load_qom_one(const char *type, Error **errp)
 {
+    bool found = false;
     const QemuModinfo *modinfo;
     const char **sl;
 
     if (!type) {
-        return;
+        error_setg(errp, "%s", "type is NULL");
+        return false;
     }
 
     trace_module_lookup_object_type(type);
@@ -331,15 +343,26 @@  void module_load_qom_one(const char *type)
         }
         for (sl = modinfo->objs; *sl != NULL; sl++) {
             if (strcmp(type, *sl) == 0) {
-                module_load_one("", modinfo->name);
+                if (found) {
+                    error_setg(errp, "multiple modules providing '%s'", type);
+                    found = false;
+                    break;
+                }
+                found = module_load_one("", modinfo->name, errp);
+                if (!found) {
+                    /* errp optionally set in module_load_one */
+                    break;
+                }
             }
         }
     }
+    return found;
 }
 
 void module_load_qom_all(void)
 {
     const QemuModinfo *modinfo;
+    Error *local_err = NULL;
 
     if (module_loaded_qom_all) {
         return;
@@ -352,7 +375,9 @@  void module_load_qom_all(void)
         if (!module_check_arch(modinfo)) {
             continue;
         }
-        module_load_one("", modinfo->name);
+        if (!module_load_one("", modinfo->name, &local_err) && local_err) {
+            error_report_err(local_err);
+        }
     }
     module_loaded_qom_all = true;
 }
@@ -368,7 +393,11 @@  void qemu_load_module_for_opts(const char *group)
         }
         for (sl = modinfo->opts; *sl != NULL; sl++) {
             if (strcmp(group, *sl) == 0) {
-                module_load_one("", modinfo->name);
+                Error *local_err = NULL;
+                if (!module_load_one("", modinfo->name, &local_err)
+                    && local_err) {
+                    error_report_err(local_err);
+                }
             }
         }
     }
@@ -378,7 +407,8 @@  void qemu_load_module_for_opts(const char *group)
 
 void module_allow_arch(const char *arch) {}
 void qemu_load_module_for_opts(const char *group) {}
-void module_load_qom_one(const char *type) {}
+bool module_load_one(const char *prefix, const char *name, Error **errp) { return true; }
+bool module_load_qom_one(const char *type, Error **errp) { return true; }
 void module_load_qom_all(void) {}
 
 #endif