Features/QCFG

From QEMU

Summary

Replace QemuOpts infrastructure with code generation leveraging QAPI.

Owner

Limitations of QemuOpts

QemuOpts provides a mechanism to parse comma separated key/value pairs into an array of variants. It then provides an accessor interface to access each variant by key using a typed interface. It optionally supports the ability to associated a schema with the key/values such that the values can be validated in one step.

Even though QemuOpts provides schema validation, this only applies to input being parsed from the user. There is no automatic checking of whether we're making appropriate use of the types within QEMU or even whether a key is valid. Right now, this results in a run-time error.

Additionally, QemuOpts does not have a notion of required arguments. This means that we need to manually determine whether a required argument is specified.

QemuOpts also mixes key/value parsing with validation which means that there is no way to determine the schema based on a key/value combination. The result is a no-schema mode of QemuOpts where the user is forced to perform all validation manually. This mode is frequently used in QEMU today.

The biggest limitation of QemuOpts is that it is fundamentally incompatible with QMP which makes exposing QemuOpts interfaces over QMP very awkward. QemuOpts expects a set of key/values in string form. In QMP, we are forced to convert QMP data structures into key/value string form, which is then remarshalled by QemuOpts.

The result is odd behavior like the ability to create a device with a name as a floating point number. Because a floating point number as a string can be a valid name, this ends up working even though it makes no sense from a wire protocol perspective.

Using QAPI to Replace QemuOpts

Use code generator to generate demarshalling routines for command line arguments into structures. Pass structures to function calls to process QEMU command line.

For instance, consider the -vnc option. We would first add a type corresponding to the options -vnc takes:

{ 'type': 'VncConfig',
  'data': { 'addr': 'str', '*x509': 'str', '*x509verify': 'str',
            '*password': 'bool', '*tls': 'bool', '*sasl': 'bool' } }

We can generate an unmarshalling function that will convert a string of comma encoded options to a proper structure. It will generate the following code:

// qemu/qcfg-marshal.h
static VncConfig *qcfg_unmarshal_VncConfig(KeyValues *kv, Error **errp)
{
    VncConfig *config;
    Error *local_err = NULL;
    bool has_addr = false;

    config = qmp_alloc_VncConfig();

    for (; kv; kv = kv->next) {
        if (strequals(kv->key, "addr")) {
            has_addr = true;
            config->addr = qcfg_unmarshal_str(kv->value, &local_err);
        } else if (strequals(kv->key, "x509")) {
            config->has_x509 = true;
            config->x509 = qcfg_unmarshal_str(kv->value, &local_err);
        } else if (strequals(kv->key, "x509verify")) {
            config->has_x509verify = true;
            config->x509verify = qcfg_unmarshal_str(kv->value, &local_err);
        } else if (strequals(kv->key, "password")) {
            config->has_password = true;
            config->password = qcfg_unmarshal_bool(kv->value, &local_err);
        } else if (strequals(kv->key, "tls")) {
            config->has_tls = true;
            config->tls = qcfg_unmarshal_bool(kv->value, &local_err);
        } else if (strequals(kv->key, "sasl")) {
            config->has_sasl = true;
            config->sasl = qcfg_unmarshal_bool(kv->value, &local_err);
        } else {
            error_set(&local_err, QERR_INVALID_PARAMETER, kv->key);
        }

        if (local_err) {
            goto out;
        }
    }

    if (!has_addr) {
        error_set(&local_err, QERR_MISSING_PARAMETER, "addr");
        goto out;
    }

    return config;

out:
    qmp_free_vnc_config(config);
    error_propagate(errp, local_err);
    return NULL;
}

There will still be the normal code generated in qmp-types and qmp-marshal-types which means that this type can also be encoded and decode via QMP.

In order to bridge this type to a function that is called to process a command line argument, we just need to add the following stanza:

// qemu/qcfg-schema.json
{ 'config': 'vnc', 'type': 'VncConfig' }

Which generates:

// qemu/qcfg.h
void qcfg_vnc_config(VncConfig * config, Error **errp);

static void qcfg_handle_vnc(KeyValues *kv, Error **errp)
{
    VncConfig *config;
    Error *local_err = NULL;

    config = qcfg_unmarshal_VncConfig(kv, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    qcfg_vnc(config, errp);

    qmp_free_VncConfig(config);
}

void qcfg_init(void)
{
    qcfg_register_handler("vnc", &qcfg_marshal_vnc);
}

The effect is that we can call a function that takes a string argument and will decode that to a strongly typed call to a function.

This function can also be reused by QMP trivially without going through multiple levels of marshaling and unmarshaling.

This provides a consistent way to document all options including the parameters that they take. It also provides a consistent way to do introspection of command line arguments.

This mechanism can also be applied to qdev which will allow for strongly typed qdev initialization functions that can also be trivially called from within QEMU.

For instance, for the serial device, we would do:

{ 'type': 'ISASerialConfig',
  'data': { '*index': 'int', '*iobase': 'int', '*irq': 'int',
            'chardev': 'CharDriverState' } }

This would generate a standard qmp-types entry and corresponding marshalling functions. This means the type is usuable via both QMP and through the command line. To generate a device factory interface, we just use the following schema entry:

{ 'device': 'isa-serial', 'config': 'ISASerialConfig' }

This would generate the following code:

DeviceState *qdev_ctor_isa_serial(ISASerialConfig *config, Error **errp);

static DeviceState *qdev_marshal_isa_serial(KeyValues *kv, Error **errp)
{
    ISASerialConfig *config;
    Error *local_err = NULL;
    DeviceState *dev;

    config = qcfg_unmarshal_ISASerialConfig(kv, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    dev = qdev_ctor_isa_serial(config, errp);

    qmp_free_isa_serial_config(config);

    return dev;
}

This lets QMP invoke qdev constructors directly without remarshaling to strings. It also provides a convenient interface for QEMU to consume internally to create devices which is fully type safe.

It also provides a mechanism to do full introspection of qdev and a consistent way to provide documentation.

Open Questions

  • Should we use a structure or marshal parameters as function calls?