]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: rework firmware selection logic
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Jan 2024 17:50:43 +0000 (18:50 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 22 Jan 2024 15:24:00 +0000 (16:24 +0100)
Let's make the firmware file to choose configurable, and enumeratable.

This adds --firmware= to select the formare, and in particular
--firmware=list to show available options.

man/systemd-vmspawn.xml
src/vmspawn/vmspawn-util.c
src/vmspawn/vmspawn-util.h
src/vmspawn/vmspawn.c

index cd46f823ad3e43b8848e28e66b0cc2a6d1c7e0a4..351b7c077292a9a4140fbcd583e24f6dbe37b561 100644 (file)
 
         <xi:include href="version-info.xml" xpointer="v255"/></listitem>
       </varlistentry>
-    </variablelist>
+
+      <varlistentry>
+        <term><option>--firmware=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Takes an absolute path, or a relative path beginning with
+        <filename>./</filename>. Specifies a JSON firmware definition file, which allows selecting the
+        firmware to boot in the VM. If not specified a suitable firmware is automatically discovered. If the
+        special string <literal>list</literal> is specified lists all discovered firmwares.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+      </variablelist>
 
     </refsect2><refsect2>
       <title>System Identity Options</title>
index c8e67597909fcf80b09fba2cdd450b82f2f2d77b..a66352c51073dd93e7218776ef8f13f681493861 100644 (file)
@@ -81,6 +81,12 @@ typedef struct FirmwareData {
         char *vars;
 } FirmwareData;
 
+static bool firmware_data_supports_sb(const FirmwareData *fwd) {
+        assert(fwd);
+
+        return strv_contains(fwd->features, "secure-boot");
+}
+
 static FirmwareData* firmware_data_free(FirmwareData *fwd) {
         if (!fwd)
                 return NULL;
@@ -124,12 +130,134 @@ static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags
         return json_dispatch(v, table, flags, userdata);
 }
 
+static int get_firmware_search_dirs(char ***ret) {
+        int r;
+
+        assert(ret);
+
+        /* Search in:
+         * - $XDG_CONFIG_HOME/qemu/firmware
+         * - /etc/qemu/firmware
+         * - /usr/share/qemu/firmware
+         *
+         * Prioritising entries in "more specific" directories */
+
+        _cleanup_free_ char *user_firmware_dir = NULL;
+        r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+        if (r < 0)
+                return r;
+
+        _cleanup_strv_free_ char **l = NULL;
+        l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware");
+        if (!l)
+                return log_oom_debug();
+
+        *ret = TAKE_PTR(l);
+        return 0;
+}
+
+int list_ovmf_config(char ***ret) {
+        _cleanup_strv_free_ char **search_dirs = NULL;
+        int r;
+
+        assert(ret);
+
+        r = get_firmware_search_dirs(&search_dirs);
+        if (r < 0)
+                return r;
+
+        r = conf_files_list_strv(
+                        ret,
+                        ".json",
+                        /* root= */ NULL,
+                        CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
+                        (const char *const*) search_dirs);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to list firmware files: %m");
+
+        return 0;
+}
+
+static int load_firmware_data(const char *path, FirmwareData **ret) {
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        _cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
+        r = json_parse_file(
+                        /* f= */ NULL,
+                        path,
+                        /* flags= */ 0,
+                        &json,
+                        /* ret_line= */ NULL,
+                        /* ret_column= */ NULL);
+        if (r < 0)
+                return r;
+
+        static const JsonDispatch table[] = {
+                { "description",     JSON_VARIANT_STRING, NULL,               0,                                JSON_MANDATORY },
+                { "interface-types", JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                { "mapping",         JSON_VARIANT_OBJECT, firmware_mapping,   0,                                JSON_MANDATORY },
+                { "targets",         JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                { "features",        JSON_VARIANT_ARRAY,  json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
+                { "tags",            JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
+                {}
+        };
+
+        _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+        fwd = new0(FirmwareData, 1);
+        if (!fwd)
+                return -ENOMEM;
+
+        r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(fwd);
+        return 0;
+}
+
+static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) {
+        assert(fwd);
+        assert(ret);
+
+        _cleanup_free_ OvmfConfig *config = NULL;
+        config = new(OvmfConfig, 1);
+        if (!config)
+                return -ENOMEM;
+
+        *config = (OvmfConfig) {
+                .path = TAKE_PTR(fwd->firmware),
+                .vars = TAKE_PTR(fwd->vars),
+                .supports_sb = firmware_data_supports_sb(fwd),
+        };
+
+        *ret = TAKE_PTR(config);
+        return 0;
+}
+
+int load_ovmf_config(const char *path, OvmfConfig **ret) {
+        _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        r = load_firmware_data(path, &fwd);
+        if (r < 0)
+                return r;
+
+        return ovmf_config_make(fwd, ret);
+}
+
 int find_ovmf_config(int search_sb, OvmfConfig **ret) {
         _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
-        _cleanup_free_ char *user_firmware_dir = NULL;
         _cleanup_strv_free_ char **conf_files = NULL;
         int r;
 
+        assert(ret);
+
         /* Search in:
          * - $XDG_CONFIG_HOME/qemu/firmware
          * - /etc/qemu/firmware
@@ -138,79 +266,35 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) {
          * Prioritising entries in "more specific" directories
          */
 
-        r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
+        r = list_ovmf_config(&conf_files);
         if (r < 0)
                 return r;
 
-        r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
-                        STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"));
-        if (r < 0)
-                return log_debug_errno(r, "Failed to list config files: %m");
-
         STRV_FOREACH(file, conf_files) {
                 _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
-                _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL;
-                _cleanup_free_ char *contents = NULL;
-                size_t contents_sz = 0;
 
-                r = read_full_file(*file, &contents, &contents_sz);
-                if (r == -ENOMEM)
-                        return r;
+                r = load_firmware_data(*file, &fwd);
                 if (r < 0) {
-                        log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file);
-                        continue;
-                }
-
-                r = json_parse(contents, 0, &config_json, NULL, NULL);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file);
-                        continue;
-                }
-
-                static const JsonDispatch table[] = {
-                        { "description",     JSON_VARIANT_STRING, NULL,               0,                                JSON_MANDATORY },
-                        { "interface-types", JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        { "mapping",         JSON_VARIANT_OBJECT, firmware_mapping,   0,                                JSON_MANDATORY },
-                        { "targets",         JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        { "features",        JSON_VARIANT_ARRAY,  json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY },
-                        { "tags",            JSON_VARIANT_ARRAY,  NULL,               0,                                JSON_MANDATORY },
-                        {}
-                };
-
-                fwd = new0(FirmwareData, 1);
-                if (!fwd)
-                        return -ENOMEM;
-
-                r = json_dispatch(config_json, table, JSON_ALLOW_EXTENSIONS, fwd);
-                if (r == -ENOMEM)
-                        return r;
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file);
+                        log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
                         continue;
                 }
 
                 if (strv_contains(fwd->features, "enrolled-keys")) {
-                        log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues", *file);
+                        log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file);
                         continue;
                 }
 
-                bool sb_present = strv_contains(fwd->features, "secure-boot");
-
                 /* exclude firmware which doesn't match our Secure Boot requirements */
-                if (search_sb >= 0 && search_sb != sb_present) {
-                        log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file);
+                if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) {
+                        log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file);
                         continue;
                 }
 
-                config = new0(OvmfConfig, 1);
-                if (!config)
-                        return -ENOMEM;
+                r = ovmf_config_make(fwd, &config);
+                if (r < 0)
+                        return r;
 
-                config->path = TAKE_PTR(fwd->firmware);
-                config->vars = TAKE_PTR(fwd->vars);
-                config->supports_sb = sb_present;
+                log_debug("Selected firmware definition %s.", *file);
                 break;
         }
 
index c931e66aacddf996fe1eadb15e7a7da6bcceaea5..3617bb07d6e6a08a6a134eccb72d27ddd0b26324 100644 (file)
@@ -31,6 +31,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
 
 int qemu_check_kvm_support(void);
 int qemu_check_vsock_support(void);
-int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config);
+int list_ovmf_config(char ***ret);
+int load_ovmf_config(const char *path, OvmfConfig **ret);
+int find_ovmf_config(int search_sb, OvmfConfig **ret);
 int find_qemu_binary(char **ret_qemu_binary);
 int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock);
index 5341699b79cb34425b6d0b7c1c1d48296f0237af..bcc3e1662a91090bbfde1c13462e7b039010ea73 100644 (file)
@@ -49,12 +49,14 @@ static int arg_secure_boot = -1;
 static MachineCredentialContext arg_credentials = {};
 static SettingsMask arg_settings_mask = 0;
 static char **arg_parameters = NULL;
+static char *arg_firmware = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
+STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
@@ -83,6 +85,7 @@ static int help(void) {
                "     --qemu-gui             Start QEMU in graphical mode\n"
                "     --secure-boot=BOOL     Configure whether to search for firmware which\n"
                "                            supports Secure Boot\n"
+               "     --firmware=PATH|list   Select firmware definition file (or list available)\n"
                "\n%3$sSystem Identity:%4$s\n"
                "  -M --machine=NAME         Set the machine name for the container\n"
                "\n%3$sCredentials:%4$s\n"
@@ -115,6 +118,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SECURE_BOOT,
                 ARG_SET_CREDENTIAL,
                 ARG_LOAD_CREDENTIAL,
+                ARG_FIRMWARE,
         };
 
         static const struct option options[] = {
@@ -132,6 +136,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "secure-boot",     required_argument, NULL, ARG_SECURE_BOOT     },
                 { "set-credential",  required_argument, NULL, ARG_SET_CREDENTIAL  },
                 { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
+                { "firmware",        required_argument, NULL, ARG_FIRMWARE        },
                 {}
         };
 
@@ -242,6 +247,31 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_FIRMWARE:
+                        if (streq(optarg, "list")) {
+                                _cleanup_strv_free_ char **l = NULL;
+
+                                r = list_ovmf_config(&l);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to list firmwares: %m");
+
+                                bool nl = false;
+                                fputstrv(stdout, l, "\n", &nl);
+                                if (nl)
+                                        putchar('\n');
+
+                                return 0;
+                        }
+
+                        if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
+                                return log_error_errno(SYNTHETIC_ERRNO(errno), "Absolute path or path starting with './' required.");
+
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -460,7 +490,10 @@ static int run_virtual_machine(void) {
                 use_kvm = r;
         }
 
-        r = find_ovmf_config(arg_secure_boot, &ovmf_config);
+        if (arg_firmware)
+                r = load_ovmf_config(arg_firmware, &ovmf_config);
+        else
+                r = find_ovmf_config(arg_secure_boot, &ovmf_config);
         if (r < 0)
                 return log_error_errno(r, "Failed to find OVMF config: %m");