]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: Add --firmware=describe
authorDaan De Meyer <daan@amutable.com>
Fri, 27 Mar 2026 11:55:32 +0000 (12:55 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 27 Mar 2026 13:22:29 +0000 (14:22 +0100)
It's useful to be able to check what firmware description vmspawn
will select. In particular, this will allow me to figure out the
nvram template file that will be picked up so I can pick it up in
mkosi and operate on it to pass a modified version of it to vmspawn
with --efi-nvram-template=.

man/systemd-vmspawn.xml
shell-completion/bash/systemd-vmspawn
src/vmspawn/vmspawn-util.c
src/vmspawn/vmspawn-util.h
src/vmspawn/vmspawn.c

index 72dbcb15d9d0f7616bb43e4b32715c8614521d7f..28070cfe8f66cb0210c3d9d61a949210f5fc3238 100644 (file)
           <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>
+          special string <literal>list</literal> is specified lists all discovered firmwares. If the special
+          string <literal>describe</literal> is specified, the firmware that would be selected (taking
+          <option>--firmware-features=</option> into account) is printed and the program exits.</para>
 
           <xi:include href="version-info.xml" xpointer="v256"/></listitem>
         </varlistentry>
index a6ce9708abe874859ccc2a3d16e198042aeda399..b035a42a6550ecf1868b557784a223540d8a44d8 100644 (file)
@@ -54,7 +54,7 @@ _systemd_vmspawn() {
         comps=$(compgen -f -- "$cur" )
     elif __contains_word "$prev" ${OPTS[FIRMWARE]}; then
         compopt -o nospace -o filenames
-        comps="list $(compgen -f -- "$cur" )"
+        comps="list describe $(compgen -f -- "$cur" )"
     elif __contains_word "$prev" ${OPTS[FIRMWARE_FEATURES]}; then
         comps='list'
     elif __contains_word "$prev" ${OPTS[BIND]}; then
index b8e2c09c830c27d6311ef21dbafdccad978b9f5f..149fb8f7c917711c5f6930ec3e0d3554ceb10409 100644 (file)
@@ -244,7 +244,7 @@ int list_ovmf_config(char ***ret) {
         return 0;
 }
 
-static int load_firmware_data(const char *path, FirmwareData **ret) {
+static int load_firmware_data(const char *path, FirmwareData **ret, sd_json_variant **ret_json) {
         int r;
 
         assert(path);
@@ -281,6 +281,10 @@ static int load_firmware_data(const char *path, FirmwareData **ret) {
                 return r;
 
         *ret = TAKE_PTR(fwd);
+
+        if (ret_json)
+                *ret_json = TAKE_PTR(json);
+
         return 0;
 }
 
@@ -298,7 +302,7 @@ int list_ovmf_firmware_features(char ***ret) {
         STRV_FOREACH(file, conf_files) {
                 _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
 
-                r = load_firmware_data(*file, &fwd);
+                r = load_firmware_data(*file, &fwd, /* ret_json= */ NULL);
                 if (r < 0) {
                         log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
                         continue;
@@ -347,14 +351,18 @@ int load_ovmf_config(const char *path, OvmfConfig **ret) {
         assert(path);
         assert(ret);
 
-        r = load_firmware_data(path, &fwd);
+        r = load_firmware_data(path, &fwd, /* ret_json= */ NULL);
         if (r < 0)
                 return r;
 
         return ovmf_config_make(fwd, ret);
 }
 
-int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret) {
+int find_ovmf_config(
+                Set *features_include,
+                Set *features_exclude,
+                OvmfConfig **ret,
+                sd_json_variant **ret_firmware_json) {
         _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
         _cleanup_strv_free_ char **conf_files = NULL;
         const char* native_arch_qemu;
@@ -380,8 +388,9 @@ int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **
 
         STRV_FOREACH(file, conf_files) {
                 _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
 
-                r = load_firmware_data(*file, &fwd);
+                r = load_firmware_data(*file, &fwd, ret_firmware_json ? &json : NULL);
                 if (r < 0) {
                         log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
                         continue;
@@ -428,6 +437,10 @@ int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **
                         return r;
 
                 log_debug("Selected firmware definition %s.", *file);
+
+                if (ret_firmware_json)
+                        *ret_firmware_json = TAKE_PTR(json);
+
                 break;
         }
 
index 28c2df78a70141ae19093d52edf896c178a98611..90efd936612240796f8de6af05ea29b075227c74 100644 (file)
@@ -89,7 +89,7 @@ int qemu_check_vsock_support(void);
 int list_ovmf_config(char ***ret);
 int list_ovmf_firmware_features(char ***ret);
 int load_ovmf_config(const char *path, OvmfConfig **ret);
-int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret);
+int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret, sd_json_variant **ret_firmware_json);
 int find_qemu_binary(char **ret_qemu_binary);
 int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine);
 
index fbe4d2145bfe637c077da262f1af9bc3ef6d153b..02f2b0df2e08a25540ed469b56c9080ab54599ff 100644 (file)
@@ -138,6 +138,7 @@ static MachineCredentialContext arg_credentials = {};
 static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U;
 static RuntimeMountContext arg_runtime_mounts = {};
 static char *arg_firmware = NULL;
+static bool arg_firmware_describe = false;
 static Set *arg_firmware_features_include = NULL;
 static Set *arg_firmware_features_exclude = NULL;
 static char *arg_forward_journal = NULL;
@@ -239,7 +240,9 @@ static int help(void) {
                "     --network-user-mode   Use user mode networking\n"
                "     --secure-boot=BOOL|auto\n"
                "                           Enable searching for firmware supporting SecureBoot\n"
-               "     --firmware=PATH|list  Select firmware definition file (or list available)\n"
+               "     --firmware=PATH|list|describe\n"
+               "                           Select firmware definition file (or list/describe\n"
+               "                           available)\n"
                "     --firmware-features=FEATURE[,FEATURE...]|list\n"
                "                           Require/exclude specific firmware features\n"
                "     --discard-disk=BOOL   Control processing of discard requests\n"
@@ -737,6 +740,16 @@ static int parse_argv(int argc, char *argv[]) {
                                 return 0;
                         }
 
+                        if (streq(optarg, "describe")) {
+                                /* Handled after argument parsing so that --firmware-features= is
+                                 * taken into account. */
+                                arg_firmware = mfree(arg_firmware);
+                                arg_firmware_describe = true;
+                                break;
+                        }
+
+                        arg_firmware_describe = false;
+
                         if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required.");
 
@@ -2171,7 +2184,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
         if (arg_firmware)
                 r = load_ovmf_config(arg_firmware, &ovmf_config);
         else
-                r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config);
+                r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to find OVMF config: %m");
 
@@ -3637,6 +3650,21 @@ static int run(int argc, char *argv[]) {
         if (r <= 0)
                 return r;
 
+        if (arg_firmware_describe) {
+                _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
+
+                r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, &json);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to find OVMF config: %m");
+
+                r = sd_json_variant_dump(json, SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to output JSON: %m");
+
+                return 0;
+        }
+
         r = determine_names();
         if (r < 0)
                 return r;