The combined binary takes arch:name on -M/-machine and in a [machine] type from the default config file or -readconfig, and selects that target before QOM init. The default file is read first. -nouserconfig skips only that file. select_machine() matches the machine in the selected target. qemu_read_config_file_cb() supplies the callback for that early pass. qemu-options.hx documents arch:name and arch:help.
- A later bare name keeps the last arch prefix - Last -M wins for help - Plain -M help lists every target; arch:help lists one target - qemu-system-<arch> still prints machine help after early options Signed-off-by: Yonggang Luo <[email protected]> --- include/qemu/config-file.h | 2 + qemu-options.hx | 11 +- system/vl.c | 294 ++++++++++++++++++++++++++++++++++--- util/qemu-config.c | 10 +- 4 files changed, 291 insertions(+), 26 deletions(-) diff --git a/include/qemu/config-file.h b/include/qemu/config-file.h index 51b310fa3b4..f733b0e9769 100644 --- a/include/qemu/config-file.h +++ b/include/qemu/config-file.h @@ -22,6 +22,8 @@ int qemu_config_parse(FILE *fp, QemuOptsList **lists, const char *fname, void qemu_config_do_parse(const char *group, QDict *qdict, void *opaque, Error **errp); int qemu_read_config_file(const char *filename, QEMUConfigCB *f, Error **errp); +int qemu_read_config_file_cb(const char *filename, QEMUConfigCB *f, + void *opaque, Error **errp); /* Parse QDict options as a replacement for a config file (allowing multiple enumerated (0..(n-1)) configuration "sections") */ diff --git a/qemu-options.hx b/qemu-options.hx index 2f6863180b0..32be1ceeda2 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -27,6 +27,8 @@ ERST DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ "-machine [type=]name[,prop=value[,...]]\n" " selects emulated machine ('-machine help' for list)\n" + " use arch:name (e.g. aarch64:virt) on the combined binary\n" + " '-machine help' lists all targets; use arch:help for one\n" " property accel=accel1[:accel2[:...]] selects accelerator\n" " supported accelerators are kvm, xen, hvf, nitro, nvmm, whpx, mshv or tcg (default: tcg)\n" " vmport=on|off|auto controls emulation of vmport (default: auto)\n" @@ -53,7 +55,14 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ SRST ``-machine [type=]name[,prop=value[,...]]`` Select the emulated machine by name. Use ``-machine help`` to list - available machines. + available machines. On a single-target binary this lists that + target. On the combined qemu-system binary, ``-machine help`` + lists every target; pass ``arch:name`` (for example + ``aarch64:virt``) to select one, or ``arch:help`` (for example + ``aarch64:help``) to list that target only. A ``[machine]`` + ``type`` in the default config file or ``-readconfig`` uses the + same form. The default file is read first. A later bare name + keeps the last arch prefix. For architectures which aim to support live migration compatibility across releases, each release will introduce a new versioned machine diff --git a/system/vl.c b/system/vl.c index 7f1381d84ff..1bdd5b2da05 100644 --- a/system/vl.c +++ b/system/vl.c @@ -28,6 +28,8 @@ #include "qemu/units.h" #include "qemu/module.h" #include "qemu/target-info.h" +#include "qemu/target-info-select.h" +#include "qemu/target-info-def.h" #include "exec/cpu-common.h" #include "exec/page-vary.h" #include "hw/core/qdev-properties.h" @@ -842,6 +844,12 @@ static bool usb_parse(const char *cmdline, Error **errp) /***********************************************************/ /* machine registration */ +/* Same grouping as -M help: each machine must have is_available. */ +static bool machine_class_in_target(MachineClass *mc, const TargetInfo *ti) +{ + return object_class_get_is_available(OBJECT_CLASS(mc))(ti); +} + static MachineClass *find_machine(const char *name, GSList *machines) { GSList *el; @@ -850,6 +858,9 @@ static MachineClass *find_machine(const char *name, GSList *machines) MachineClass *mc = el->data; if (!strcmp(mc->name, name) || !g_strcmp0(mc->alias, name)) { + if (!machine_class_in_target(mc, target_info())) { + continue; + } return mc; } } @@ -866,6 +877,9 @@ static MachineClass *find_default_machine(GSList *machines) MachineClass *mc = el->data; if (mc->is_default) { + if (!machine_class_in_target(mc, target_info())) { + continue; + } assert(default_machineclass == NULL && "Multiple default machines"); default_machineclass = mc; } @@ -874,6 +888,106 @@ static MachineClass *find_default_machine(GSList *machines) return default_machineclass; } +/* + * Split "aarch64:virt". "aarch64:" binds arch and leaves an empty name. + * A leading colon is not a prefix. *machine_name always takes the latest + * parsed name; NULL type leaves it unchanged. keep_target leaves + * *machine_target unchanged when the new type has no prefix. + */ +static void machine_type_parse_target(const char *type, + char **machine_name, + char **machine_target, + bool keep_target) +{ + const char *colon = NULL; + const char *parsed_name = NULL; + const char *parsed_target = NULL; + + if (type) { + colon = strchr(type, ':'); + if (!colon || colon == type) { + parsed_name = type; + } else { + parsed_target = type; + parsed_name = colon + 1; + } + } + + if (parsed_name) { + g_free(*machine_name); + *machine_name = g_strdup(parsed_name); + } + if (parsed_target || !keep_target) { + g_free(*machine_target); + *machine_target = parsed_target ? + g_strndup(parsed_target, colon - parsed_target) : NULL; + } +} + +typedef struct MachineTypeOpt { + char *type; + char *name; + char *target; + bool help; +} MachineTypeOpt; + +static void machine_type_opt_clear(MachineTypeOpt *opt) +{ + g_free(opt->type); + g_free(opt->name); + g_free(opt->target); + opt->type = NULL; + opt->name = NULL; + opt->target = NULL; +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(MachineTypeOpt, machine_type_opt_clear) + +/* A bare name keeps the previous arch prefix. */ +static void machine_type_opt_set(MachineTypeOpt *opt, const char *type, + bool help) +{ + if (!type && !help) { + return; + } + if (type) { + g_free(opt->type); + opt->type = g_strdup(type); + machine_type_parse_target(opt->type, &opt->name, &opt->target, true); + } + opt->help = help || (type && opt->name && is_help_option(opt->name)); +} + +static void qemu_config_set_machine_type(const char *group, QDict *qdict, + void *opaque, Error **errp) +{ + MachineTypeOpt *opt = opaque; + + (void)errp; + if (strcmp(group, "machine") != 0) { + return; + } + machine_type_opt_set(opt, qdict_get_try_str(qdict, "type"), false); +} + +static void machine_type_config_file(const char *filename, bool optional, + MachineTypeOpt *opt) +{ + Error *err = NULL; + int ret; + + ret = qemu_read_config_file_cb(filename, qemu_config_set_machine_type, + opt, &err); + if (ret < 0) { + if (optional && ret == -ENOENT) { + error_free(err); + return; + } + error_report_err(err); + exit(1); + } +} + static void version(void) { printf("QEMU emulator version " QEMU_FULL_VERSION "\n" @@ -1563,25 +1677,31 @@ static gint machine_class_cmp(gconstpointer a, gconstpointer b, gpointer d) object_class_get_name(OBJECT_CLASS(mc1))); } -static void machine_help_func(const QDict *qdict) +static void machine_help_print_target(GSList *machines, const TargetInfo *ti, + const char *machine_target) { - g_autoptr(GSList) machines = NULL; + bool printed = false; GSList *el; - const char *type = qdict_get_try_str(qdict, "type"); + bool combined = target_combined_binary(); + bool print_header = combined && !machine_target; - machines = object_class_get_list(TYPE_MACHINE, false); - if (type) { - ObjectClass *machine_class = OBJECT_CLASS(find_machine(type, machines)); - if (machine_class) { - type_print_class_properties(object_class_get_name(machine_class)); - return; - } + if (!combined) { + g_assert(ti == target_info()); + } else if (machine_target && ti != target_info()) { + /* -M arch:help: skip every target except the selected one. */ + return; } - printf("Supported machines are:\n"); - machines = g_slist_sort_with_data(machines, machine_class_cmp, NULL); for (el = machines; el; el = el->next) { MachineClass *mc = el->data; + + if (!machine_class_in_target(mc, ti)) { + continue; + } + if (print_header && !printed) { + printf("%s:\n", ti->target_name); + printed = true; + } if (mc->alias) { printf("%-20s %s (alias of %s)\n", mc->alias, mc->desc, mc->name); } @@ -1590,6 +1710,54 @@ static void machine_help_func(const QDict *qdict) mc->deprecation_reason ? " (deprecated)" : "", object_class_is_secure(OBJECT_CLASS(mc)) ? " (secure)" : ""); } + + if (print_header && printed) { + printf("\n"); + } +} + +static void machine_help_func(const char *type, + const char *machine_name, + const char *machine_target) +{ + g_autoptr(GSList) machines = NULL; + MachineClass *mc = NULL; + TargetInfoNode *node; + + machines = object_class_get_list(TYPE_MACHINE, false); + + if (machine_name && *machine_name && !is_help_option(machine_name)) { + mc = find_machine(machine_name, machines); + if (mc) { + type_print_class_properties(object_class_get_name(OBJECT_CLASS(mc))); + return; + } + if (target_none()) { + error_report("machine type \"%s\" needs an arch: prefix", + machine_name); + exit(1); + } + error_report("unsupported machine type: \"%s\"", type); + exit(1); + } + + if (type && (!machine_name || !*machine_name)) { + if (!target_none()) { + mc = find_default_machine(machines); + if (!mc) { + error_report("No machine specified, and there is no default"); + exit(1); + } + type_print_class_properties(object_class_get_name(OBJECT_CLASS(mc))); + return; + } + } + + printf("Supported machines are:\n"); + machines = g_slist_sort_with_data(machines, machine_class_cmp, NULL); + QLIST_FOREACH(node, target_info_list(), next) { + machine_help_print_target(machines, node->info, machine_target); + } } static void @@ -1684,18 +1852,58 @@ static MachineClass *select_machine(QDict *qdict, Error **errp) const char *machine_type = qdict_get_try_str(qdict, "type"); g_autoptr(GSList) machines = object_class_get_list(TYPE_MACHINE, false); MachineClass *machine_class = NULL; + g_autofree char *machine_target = NULL; + g_autofree char *machine_name = NULL; if (machine_type) { - machine_class = find_machine(machine_type, machines); + machine_type_parse_target(machine_type, &machine_name, &machine_target, false); + if (machine_target && strcmp(target_name(), machine_target) != 0) { + error_setg(errp, "machine type \"%s\" is not available " + "for target %s", machine_type, target_name()); + error_append_hint(errp, + "Use -machine help to list supported " + "machines\n"); + return NULL; + } + } + + if (machine_name && *machine_name) { + machine_class = find_machine(machine_name, machines); if (!machine_class) { - error_setg(errp, "unsupported machine type: \"%s\"", machine_type); + if (machine_target && target_none()) { + error_setg(errp, "machine type \"%s\" is not available " + "for target %s", machine_type, target_name()); + } else if (target_none()) { + error_setg(errp, + "machine type \"%s\" needs an arch: prefix", + machine_name); + error_append_hint(errp, + "Use -M arch:name (e.g. aarch64:virt) " + "or -machine help\n"); + return NULL; + } else { + error_setg(errp, "unsupported machine type: \"%s\"", + machine_type); + } } - qdict_del(qdict, "type"); - } else { + } else if (!target_none()) { machine_class = find_default_machine(machines); if (!machine_class) { error_setg(errp, "No machine specified, and there is no default"); } + } else { + machine_class = find_machine("none", machines); + if (!machine_class) { + error_setg(errp, "No machine specified"); + error_append_hint(errp, + "Use -M arch:name (e.g. aarch64:virt) or " + "-machine help\n"); + return NULL; + } + } + + if (machine_type) { + qdict_del(qdict, "type"); } if (!machine_class) { @@ -2872,6 +3080,9 @@ void qemu_init(int argc, char **argv) const char *optarg; MachineClass *machine_class; bool userconfig = true; + int parse_opt_pass = 0; + /* Combined: NULL target selects NONE. Single-target stub ignores this. */ + g_auto(MachineTypeOpt) machine_opt = { }; FILE *vmstate_dump_file = NULL; qemu_add_opts(&qemu_drive_opts); @@ -2911,9 +3122,23 @@ void qemu_init(int argc, char **argv) error_init(argv[0]); qemu_init_exec_dir(argv[0]); - /* first pass of option parsing */ + /* + * Pass 0 records -nouserconfig. Pass 1 parses -M and -readconfig + * into machine_opt. The loop ends only after pass 1. + */ optind = 1; - while (optind < argc) { + for (parse_opt_pass = 0; parse_opt_pass < 2;) { + if (optind >= argc) { + if (parse_opt_pass == 0 && userconfig) { + g_autofree char *file = + get_relocated_path(CONFIG_QEMU_CONFDIR "/qemu.conf"); + + machine_type_config_file(file, true, &machine_opt); + } + parse_opt_pass += 1; + optind = 1; + continue; + } if (argv[optind][0] != '-') { /* disk image */ optind++; @@ -2923,7 +3148,27 @@ void qemu_init(int argc, char **argv) popt = lookup_opt(argc, argv, &optarg, &optind); switch (popt->index) { case QEMU_OPTION_nouserconfig: - userconfig = false; + if (parse_opt_pass == 0) { + userconfig = false; + } + break; + case QEMU_OPTION_M: + case QEMU_OPTION_machine: + if (parse_opt_pass == 1) { + bool help = false; + g_autoptr(QDict) dict = qdict_new(); + const char *type; + + keyval_parse_into(dict, optarg, "type", &help, + &error_fatal); + type = qdict_get_try_str(dict, "type"); + machine_type_opt_set(&machine_opt, type, help); + } + break; + case QEMU_OPTION_readconfig: + if (parse_opt_pass == 1) { + machine_type_config_file(optarg, false, &machine_opt); + } break; } } @@ -2931,6 +3176,8 @@ void qemu_init(int argc, char **argv) os_setup_limits(); + target_info_select_by_name(machine_opt.target, &error_fatal); + module_init_info(qemu_modinfo); module_allow_arch(target_name()); @@ -3435,10 +3682,6 @@ void qemu_init(int argc, char **argv) bool help; keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal); - if (help) { - machine_help_func(machine_opts_dict); - exit(EXIT_SUCCESS); - } break; } case QEMU_OPTION_accel: @@ -3770,6 +4013,11 @@ void qemu_init(int argc, char **argv) qemu_process_early_options(); qemu_process_help_options(); + if (machine_opt.help) { + machine_help_func(machine_opt.type, machine_opt.name, + machine_opt.target); + exit(EXIT_SUCCESS); + } qemu_maybe_daemonize(pid_file); /* diff --git a/util/qemu-config.c b/util/qemu-config.c index d1fc49c507d..f0ef711450e 100644 --- a/util/qemu-config.c +++ b/util/qemu-config.c @@ -183,7 +183,8 @@ int qemu_config_parse(FILE *fp, QemuOptsList **lists, const char *fname, Error * return qemu_config_foreach(fp, qemu_config_do_parse, lists, fname, errp); } -int qemu_read_config_file(const char *filename, QEMUConfigCB *cb, Error **errp) +int qemu_read_config_file_cb(const char *filename, QEMUConfigCB *cb, + void *opaque, Error **errp) { FILE *f = fopen(filename, "r"); int ret; @@ -193,11 +194,16 @@ int qemu_read_config_file(const char *filename, QEMUConfigCB *cb, Error **errp) return -errno; } - ret = qemu_config_foreach(f, cb, vm_config_groups, filename, errp); + ret = qemu_config_foreach(f, cb, opaque, filename, errp); fclose(f); return ret; } +int qemu_read_config_file(const char *filename, QEMUConfigCB *cb, Error **errp) +{ + return qemu_read_config_file_cb(filename, cb, vm_config_groups, errp); +} + static bool config_parse_qdict_section(QDict *options, QemuOptsList *opts, Error **errp) { -- 2.52.0.windows.1
