[PATCH 10/12] target-info: add -target and program-name selection

Yonggang Luo <[email protected]>
Newsgroups org.nongnu.qemu-devel,org.nongnu.qemu-arm,org.nongnu.qemu-riscv
Message-ID <[email protected]>
- Parse -target before TargetInfo is fixed; CLI overrides the program
  basename.
- Tokens are target_name or target_name-be / -le; -target help lists
  registered names in sorted order.
- When -target is omitted, infer the token from g_get_prgname(): strip
  .exe, take the longest dash-separated registered suffix, and retry
  without a trailing w for Windows GUI binaries.
- A basename that matches no registered token falls through to the
  unique default. Combined binaries with no suffix require -target
  when more than one default TargetInfo is registered.

Signed-off-by: Yonggang Luo <[email protected]>
---
 include/qemu/target-info-qom.h |  10 +-
 qemu-options.hx                |  13 +++
 system/vl.c                    |  28 ++++--
 target-info-qom.c              | 167 ++++++++++++++++++++++++++++++---
 tests/qtest/fuzz/fuzz.c        |   2 +-
 5 files changed, 197 insertions(+), 23 deletions(-)

diff --git a/include/qemu/target-info-qom.h b/include/qemu/target-info-qom.h
index 842cf241e0a..3b59cf82d65 100644
--- a/include/qemu/target-info-qom.h
+++ b/include/qemu/target-info-qom.h
@@ -40,7 +40,15 @@ typedef struct TargetInfoQomClass {
 
 OBJECT_DECLARE_TYPE(TargetInfoQom, TargetInfoQomClass, TARGET_INFO)
 
-void target_info_qom_set_target(void);
+/**
+ * target_info_qom_set_target:
+ * @name: -target token, or NULL/%empty to infer from the program
+ * basename or the unique default
+ *
+ * Tokens are target_name (arch default endian) or target_name-be /
+ * target_name-le. -target overrides the program basename.
+ */
+void target_info_qom_set_target(const char *name);
 
 /**
  * get_machine_types_available:
diff --git a/qemu-options.hx b/qemu-options.hx
index 34970fffc94..638b531aa49 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -223,6 +223,19 @@ SRST
     as -machine.
 ERST
 
+DEF("target", HAS_ARG, QEMU_OPTION_target,
+    "-target name    select target ('-target help' for list)\n", QEMU_ARCH_ALL)
+SRST
+``-target name``
+    Select the emulation target (``-target help`` for the list). The
+    default endian is the architecture default (``riscv64`` is little
+    endian). The other endian uses a ``-be`` or ``-le`` suffix
+    (``riscv64-be``, ``ppc64-le``). If omitted, a matching suffix of
+    the program basename is used (``qemu-system-riscv64-be``, and
+    ``qemu-system-aarch64w.exe`` on Windows). ``-target`` overrides
+    the program name.
+ERST
+
 DEF("cpu", HAS_ARG, QEMU_OPTION_cpu,
     "-cpu cpu        select CPU ('-cpu help' for list)\n", QEMU_ARCH_ALL)
 SRST
diff --git a/system/vl.c b/system/vl.c
index 83b9425de9b..405f434e036 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -166,6 +166,7 @@ typedef struct DeviceOption {
     QTAILQ_ENTRY(DeviceOption) next;
 } DeviceOption;
 
+static const char *target_option;
 static const char *cpu_option;
 static const char *mem_path;
 static const char *incoming;
@@ -2899,19 +2900,12 @@ void qemu_init(int argc, char **argv)
 
     os_setup_limits();
 
-    module_call_init(MODULE_INIT_TARGET_INFO);
-    target_info_qom_set_target();
-
-    module_init_info(qemu_modinfo);
-    module_allow_arch(target_name());
-
-    qemu_init_subsystems();
-
-    /* first pass of option parsing */
+    /*
+     * First pass: options needed before TargetInfo is fixed.
+     */
     optind = 1;
     while (optind < argc) {
         if (argv[optind][0] != '-') {
-            /* disk image */
             optind++;
         } else {
             const QEMUOption *popt;
@@ -2921,10 +2915,21 @@ void qemu_init(int argc, char **argv)
             case QEMU_OPTION_nouserconfig:
                 userconfig = false;
                 break;
+            case QEMU_OPTION_target:
+                target_option = optarg;
+                break;
             }
         }
     }
 
+    module_call_init(MODULE_INIT_TARGET_INFO);
+    target_info_qom_set_target(target_option);
+
+    module_init_info(qemu_modinfo);
+    module_allow_arch(target_name());
+
+    qemu_init_subsystems();
+
     machine_opts_dict = qdict_new();
     if (userconfig) {
         qemu_read_default_config_file(&error_fatal);
@@ -3687,6 +3692,9 @@ void qemu_init(int argc, char **argv)
             case QEMU_OPTION_nouserconfig:
                 /* Nothing to be parsed here. Especially, do not error out below. */
                 break;
+            case QEMU_OPTION_target:
+                /* Parsed before TargetInfo is selected. */
+                break;
 #if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN)
             case QEMU_OPTION_daemonize:
                 os_set_daemonize(true);
diff --git a/target-info-qom.c b/target-info-qom.c
index 2c1c57b05fd..be03573a699 100644
--- a/target-info-qom.c
+++ b/target-info-qom.c
@@ -7,6 +7,7 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu/help_option.h"
 #include "qapi/error.h"
 #include "qom/object.h"
 #include "qemu/target-info-impl.h"
@@ -87,7 +88,139 @@ const TargetInfo *target_info(void)
     return target_info_ptr;
 }
 
-void target_info_qom_set_target(void)
+static const TargetInfo *target_info_from_class(ObjectClass *oc)
+{
+    return TARGET_INFO_CLASS(oc)->target_info;
+}
+
+/* Default token is target_name; the other endian is target_name-le or -be. */
+static char *target_info_option_name(const TargetInfo *ti)
+{
+    if (ti->is_default) {
+        return g_strdup(ti->target_name);
+    }
+    if (ti->endianness == ENDIAN_MODE_BIG) {
+        return g_strdup_printf("%s-be", ti->target_name);
+    }
+    return g_strdup_printf("%s-le", ti->target_name);
+}
+
+static gint target_info_token_name_cmp(gconstpointer a, gconstpointer b,
+                                       gpointer data)
+{
+    const TargetInfo *ta = target_info_from_class((ObjectClass *)a);
+    const TargetInfo *tb = target_info_from_class((ObjectClass *)b);
+    g_autofree char *na = target_info_option_name(ta);
+    g_autofree char *nb = target_info_option_name(tb);
+
+    return strcmp(na, nb);
+}
+
+static void G_NORETURN target_info_list_and_exit(GSList *targets)
+{
+    printf("Supported targets:\n");
+    for (GSList *l = targets; l; l = l->next) {
+        const TargetInfo *ti = target_info_from_class(l->data);
+        g_autofree char *opt_name = target_info_option_name(ti);
+
+        printf("  %s%s\n", opt_name, ti->is_default ? " (default)" : "");
+    }
+    exit(0);
+}
+
+static const TargetInfo *target_info_find_default(GSList *targets,
+                                                  size_t *num_default)
+{
+    const TargetInfo *chosen = NULL;
+
+    *num_default = 0;
+    for (GSList *l = targets; l; l = l->next) {
+        const TargetInfo *ti = target_info_from_class(l->data);
+
+        if (ti->is_default) {
+            (*num_default)++;
+            chosen = ti;
+        }
+    }
+    return chosen;
+}
+
+static const TargetInfo *target_info_find_token(GSList *targets,
+                                                const char *token)
+{
+    for (GSList *l = targets; l; l = l->next) {
+        const TargetInfo *ti = target_info_from_class(l->data);
+        g_autofree char *opt_name = target_info_option_name(ti);
+
+        if (!strcmp(opt_name, token)) {
+            return ti;
+        }
+    }
+    return NULL;
+}
+
+static bool target_info_base_match_token(const char *base, const char *token)
+{
+    size_t nlen = strlen(token);
+    size_t blen;
+
+    if (!base || !token[0] || !g_str_has_suffix(base, token)) {
+        return false;
+    }
+    blen = strlen(base);
+    return blen == nlen || base[blen - nlen - 1] == '-';
+}
+
+static const TargetInfo *target_info_find_prgname(GSList *targets,
+                                                  const char *base)
+{
+    const TargetInfo *chosen = NULL;
+    size_t best_len = 0;
+
+    if (!base || !base[0]) {
+        return NULL;
+    }
+
+    for (GSList *l = targets; l; l = l->next) {
+        const TargetInfo *ti = target_info_from_class(l->data);
+        g_autofree char *opt_name = target_info_option_name(ti);
+        size_t nlen = strlen(opt_name);
+
+        if (nlen > best_len &&
+            target_info_base_match_token(base, opt_name)) {
+            chosen = ti;
+            best_len = nlen;
+        }
+    }
+    return chosen;
+}
+
+static const TargetInfo *target_info_from_prgname(GSList *targets)
+{
+    const char *prg = g_get_prgname();
+    g_autofree char *base = NULL;
+    char *dot;
+    const TargetInfo *chosen;
+
+    if (!prg || !prg[0]) {
+        return NULL;
+    }
+
+    base = g_path_get_basename(prg);
+    dot = strrchr(base, '.');
+    if (dot && g_ascii_strcasecmp(dot, ".exe") == 0) {
+        *dot = '\0';
+    }
+
+    chosen = target_info_find_prgname(targets, base);
+    if (!chosen && strlen(base) > 1 && g_str_has_suffix(base, "w")) {
+        base[strlen(base) - 1] = '\0';
+        chosen = target_info_find_prgname(targets, base);
+    }
+    return chosen;
+}
+
+void target_info_qom_set_target(const char *name)
 {
     g_autoptr(GSList) targets = object_class_get_list(TYPE_TARGET_INFO, false);
     const TargetInfo *chosen = NULL;
@@ -98,25 +231,37 @@ void target_info_qom_set_target(void)
         error_setg(&error_fatal, "no target-info is available");
     }
 
-    if (num_found == 1) {
-        target_info_ptr = TARGET_INFO_CLASS(targets->data)->target_info;
+    if (name && is_help_option(name)) {
+        targets = g_slist_sort_with_data(targets, target_info_token_name_cmp,
+                                         NULL);
+        target_info_list_and_exit(targets);
+    }
+
+    if (name && name[0]) {
+        chosen = target_info_find_token(targets, name);
+        if (!chosen) {
+            error_setg(&error_fatal, "unknown target '%s'", name);
+        }
+        target_info_ptr = chosen;
         return;
     }
 
-    for (GSList *l = targets; l; l = l->next) {
-        const TargetInfo *ti = TARGET_INFO_CLASS(l->data)->target_info;
+    chosen = target_info_from_prgname(targets);
+    if (chosen) {
+        target_info_ptr = chosen;
+        return;
+    }
 
-        if (ti->is_default) {
-            num_default++;
-            chosen = ti;
-        }
+    if (num_found == 1) {
+        target_info_ptr = target_info_from_class(targets->data);
+        return;
     }
 
+    chosen = target_info_find_default(targets, &num_default);
     if (num_default != 1) {
         error_setg(&error_fatal, num_default == 0 ?
                                  "no default target-info is available" :
-                                 "more than one default target-info "
-                                 "is available");
+                                 "multiple default targets; use -target");
     }
 
     target_info_ptr = chosen;
diff --git a/tests/qtest/fuzz/fuzz.c b/tests/qtest/fuzz/fuzz.c
index a3a131c80f8..489c7138bb9 100644
--- a/tests/qtest/fuzz/fuzz.c
+++ b/tests/qtest/fuzz/fuzz.c
@@ -174,7 +174,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
     /* Initialize qgraph and modules */
     qos_graph_init();
     module_call_init(MODULE_INIT_TARGET_INFO);
-    target_info_qom_set_target();
+    target_info_qom_set_target(NULL);
     module_call_init(MODULE_INIT_FUZZ_TARGET);
     module_call_init(MODULE_INIT_QOM);
     module_call_init(MODULE_INIT_LIBQOS);
-- 
2.52.0.windows.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.