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


Reply via email to