[libvirt] [PATCH v4 3/3] qemu: Automatically create NVRAM store

Michal Privoznik mprivozn at redhat.com
Thu Aug 21 08:50:24 UTC 2014


When using split UEFI image, it may come handy if libvirt manages per
domain _VARS file automatically. While the _CODE file is RO and can be
shared among multiple domains, you certainly don't want to do that on
the _VARS file. This latter one needs to be per domain. So at the
domain startup process, if it's determined that domain needs _VARS
file it's copied from this master _VARS file. The location of the
master file is configurable in qemu.conf.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 libvirt.spec.in                    |   2 +
 src/Makefile.am                    |   1 +
 src/qemu/libvirtd_qemu.aug         |   3 +
 src/qemu/qemu.conf                 |  14 ++++
 src/qemu/qemu_conf.c               |  93 ++++++++++++++++++++++++++
 src/qemu/qemu_conf.h               |   5 ++
 src/qemu/qemu_process.c            | 132 +++++++++++++++++++++++++++++++++++++
 src/qemu/test_libvirtd_qemu.aug.in |   3 +
 8 files changed, 253 insertions(+)

diff --git a/libvirt.spec.in b/libvirt.spec.in
index f491de7..762b404 100644
--- a/libvirt.spec.in
+++ b/libvirt.spec.in
@@ -1948,6 +1948,7 @@ exit 0
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/channel/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/channel/target/
+%dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/nvram/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/cache/libvirt/qemu/
 %{_datadir}/augeas/lenses/libvirtd_qemu.aug
 %{_datadir}/augeas/lenses/tests/test_libvirtd_qemu.aug
@@ -2050,6 +2051,7 @@ exit 0
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/channel/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/channel/target/
+%dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/nvram/
 %dir %attr(0750, %{qemu_user}, %{qemu_group}) %{_localstatedir}/cache/libvirt/qemu/
 %{_datadir}/augeas/lenses/libvirtd_qemu.aug
 %{_datadir}/augeas/lenses/tests/test_libvirtd_qemu.aug
diff --git a/src/Makefile.am b/src/Makefile.am
index 538530e..dc34dd9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2644,6 +2644,7 @@ endif WITH_SANLOCK
 if WITH_QEMU
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/qemu"
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/qemu/channel/target"
+	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/qemu/nvram"
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/run/libvirt/qemu"
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/libvirt/qemu"
 	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/log/libvirt/qemu"
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug
index e7db7fe..62951da 100644
--- a/src/qemu/libvirtd_qemu.aug
+++ b/src/qemu/libvirtd_qemu.aug
@@ -88,6 +88,8 @@ module Libvirtd_qemu =
 
    let log_entry = bool_entry "log_timestamp"
 
+   let nvram_entry = str_array_entry "nvram"
+
    (* Each entry in the config is one of the following ... *)
    let entry = vnc_entry
              | spice_entry
@@ -100,6 +102,7 @@ module Libvirtd_qemu =
              | rpc_entry
              | network_entry
              | log_entry
+             | nvram_entry
 
    let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
    let empty = [ label "#empty" . eol ]
diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf
index 7bbbe09..79bba36 100644
--- a/src/qemu/qemu.conf
+++ b/src/qemu/qemu.conf
@@ -487,3 +487,17 @@
 # Defaults to 1.
 #
 #log_timestamp = 0
+
+
+# Location of master nvram file
+#
+# When a domain is configured to use UEFI instead of standard
+# BIOS it may use a separate storage for UEFI variables. If
+# that's the case libvirt creates the variable store per domain
+# using this master file as image. Each UEFI firmware can,
+# however, have different variables store. Therefore the nvram is
+# a list of strings when a single item is in form of:
+#   ${PATH_TO_UEFI_FW}:${PATH_TO_UEFI_VARS}.
+# Later, when libvirt creates per domain variable store, this
+# list is searched for the master image.
+#nvram = [ "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd" ]
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 25e6d5e..6409d3a 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -107,6 +107,9 @@ void qemuDomainCmdlineDefFree(qemuDomainCmdlineDefPtr def)
     VIR_FREE(def);
 }
 
+#define VIR_QEMU_LOADER_FILE_PATH "/usr/share/OVMF/OVMF_CODE.fd"
+#define VIR_QEMU_NVRAM_FILE_PATH "/usr/share/OVMF/OVMF_VARS.fd"
+
 virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
 {
     virQEMUDriverConfigPtr cfg;
@@ -255,6 +258,15 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
 
     cfg->logTimestamp = true;
 
+    if (VIR_ALLOC_N(cfg->loader, 1) < 0 ||
+        VIR_ALLOC_N(cfg->nvram, 1) < 0)
+        goto error;
+    cfg->nloader = 1;
+
+    if (VIR_STRDUP(cfg->loader[0], VIR_QEMU_LOADER_FILE_PATH) < 0 ||
+        VIR_STRDUP(cfg->nvram[0], VIR_QEMU_NVRAM_FILE_PATH) < 0)
+        goto error;
+
     return cfg;
 
  error:
@@ -305,6 +317,14 @@ static void virQEMUDriverConfigDispose(void *obj)
     virStringFreeList(cfg->securityDriverNames);
 
     VIR_FREE(cfg->lockManagerName);
+
+    while (cfg->nloader) {
+        VIR_FREE(cfg->loader[cfg->nloader - 1]);
+        VIR_FREE(cfg->nvram[cfg->nloader - 1]);
+        cfg->nloader--;
+    }
+    VIR_FREE(cfg->loader);
+    VIR_FREE(cfg->nvram);
 }
 
 
@@ -328,6 +348,43 @@ virQEMUDriverConfigHugeTLBFSInit(virHugeTLBFSPtr hugetlbfs,
 }
 
 
+static int
+virQEMUDriverConfigNVRAMParse(const char *str,
+                              char **loader,
+                              char **nvram)
+{
+    int ret = -1;
+    char **token;
+
+    if (!(token = virStringSplit(str, ":", 0)))
+        goto cleanup;
+
+    if (token[0]) {
+        virSkipSpaces((const char **) &token[0]);
+        if (token[1])
+            virSkipSpaces((const char **) &token[1]);
+    }
+
+    /* Exactly two tokens are expected */
+    if (!token[0] || !token[1] || token[2] ||
+        STREQ(token[0], "") || STREQ(token[1], "")) {
+        virReportError(VIR_ERR_CONF_SYNTAX,
+                       _("Invalid nvram format: '%s'"),
+                       str);
+        goto cleanup;
+    }
+
+    if (VIR_STRDUP(*loader, token[0]) < 0 ||
+        VIR_STRDUP(*nvram, token[1]) < 0)
+        goto cleanup;
+
+    ret = 0;
+ cleanup:
+    virStringFreeList(token);
+    return ret;
+}
+
+
 int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
                                 const char *filename)
 {
@@ -654,6 +711,42 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
 
     GET_VALUE_BOOL("log_timestamp", cfg->logTimestamp);
 
+    CHECK_TYPE("nvram", VIR_CONF_LIST);
+    if ((p = virConfGetValue(conf, "nvram"))) {
+        size_t len;
+        virConfValuePtr pp;
+
+        while (cfg->nloader) {
+            VIR_FREE(cfg->loader[cfg->nloader - 1]);
+            VIR_FREE(cfg->nvram[cfg->nloader - 1]);
+            cfg->nloader--;
+        }
+        VIR_FREE(cfg->loader);
+        VIR_FREE(cfg->nvram);
+
+        /* Calc length and check items */
+        for (len = 0, pp = p->list; pp; len++, pp = pp->next) {
+            if (pp->type != VIR_CONF_STRING) {
+                virReportError(VIR_ERR_CONF_SYNTAX, "%s",
+                               _("nvram must be a list of strings"));
+                goto cleanup;
+            }
+        }
+
+        if (len &&
+            (VIR_ALLOC_N(cfg->loader, len) < 0 ||
+             VIR_ALLOC_N(cfg->nvram, len) < 0))
+            goto cleanup;
+        cfg->nloader = len;
+
+        for (i = 0, pp = p->list; pp; i++, pp = pp->next) {
+            if (virQEMUDriverConfigNVRAMParse(pp->str,
+                                              &cfg->loader[i],
+                                              &cfg->nvram[i]) < 0)
+                goto cleanup;
+        }
+    }
+
     ret = 0;
 
  cleanup:
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index ae7ac56..1f521e5 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -172,6 +172,11 @@ struct _virQEMUDriverConfig {
     int migrationPortMax;
 
     bool logTimestamp;
+
+    /* Pairs of loader:nvram paths. The list is @nloader items long */
+    char **loader;
+    char **nvram;
+    size_t nloader;
 };
 
 /* Main driver state */
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index baa866a..6f79a17 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -67,6 +67,7 @@
 #include "virstring.h"
 #include "virhostdev.h"
 #include "storage/storage_driver.h"
+#include "configmake.h"
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
@@ -3734,6 +3735,130 @@ qemuProcessVerifyGuestCPU(virQEMUDriverPtr driver,
 }
 
 
+static int
+qemuPrepareNVRAM(virQEMUDriverConfigPtr cfg,
+                 virDomainDefPtr def,
+                 bool migrated)
+{
+    int ret = -1;
+    int srcFD = -1;
+    int dstFD = -1;
+    virDomainLoaderDefPtr loader = def->os.loader;
+    bool generated = false;
+    bool created = false;
+
+    /* Unless domain has RO loader of pflash type, we have
+     * nothing to do here.  If the loader is RW then it's not
+     * using split code and vars feature, so no nvram file needs
+     * to be created. */
+    if (!loader || loader->type != VIR_DOMAIN_LOADER_TYPE_PFLASH ||
+        loader->readonly != VIR_TRISTATE_SWITCH_ON)
+        return 0;
+
+    /* If the nvram path is configured already, there's nothing
+     * we need to do. Unless we are starting the destination side
+     * of migration in which case nvram is configured in the
+     * domain XML but the file doesn't exist yet. Moreover, after
+     * the migration is completed, qemu will invoke a
+     * synchronization write into the nvram file so we don't have
+     * to take care about transmitting the real data on the other
+     * side. */
+    if (loader->nvram && !migrated)
+        return 0;
+
+    /* Autogenerate nvram path if needed.*/
+    if (!loader->nvram) {
+        if (virAsprintf(&loader->nvram,
+                        "%s/lib/libvirt/qemu/nvram/%s_VARS.fd",
+                        LOCALSTATEDIR, def->name) < 0)
+            goto cleanup;
+
+        generated = true;
+    }
+
+    if (!virFileExists(loader->nvram)) {
+        size_t i;
+        ssize_t r;
+
+        for (i = 0; i < cfg->nloader; i++) {
+            if (STREQ(cfg->loader[i], loader->path))
+                break;
+        }
+
+        if (i == cfg->nloader) {
+            virReportError(VIR_ERR_OPERATION_FAILED,
+                           _("unable to find any master var store for "
+                             "loader: %s"), loader->path);
+            goto cleanup;
+        }
+
+        if ((srcFD = virFileOpenAs(cfg->nvram[i], O_RDONLY,
+                                   0, -1, -1, 0)) < 0) {
+            virReportSystemError(-srcFD,
+                                 _("Failed to open file '%s'"),
+                                 cfg->nvram[i]);
+            goto cleanup;
+        }
+        if ((dstFD = virFileOpenAs(loader->nvram,
+                                   O_WRONLY | O_CREAT | O_EXCL,
+                                   S_IRUSR | S_IWUSR,
+                                   cfg->user, cfg->group, 0)) < 0) {
+            virReportSystemError(-dstFD,
+                                 _("Failed to create file '%s'"),
+                                 loader->nvram);
+            goto cleanup;
+        }
+        created = true;
+
+        do {
+            char buf[1024];
+
+            if ((r = saferead(srcFD, buf, sizeof(buf))) < 0) {
+                virReportSystemError(errno,
+                                     _("Unable to read from file '%s'"),
+                                     cfg->nvram[i]);
+                goto cleanup;
+            }
+
+            if (safewrite(dstFD, buf, r) < 0) {
+                virReportSystemError(errno,
+                                     _("Unable to write to file '%s'"),
+                                     loader->nvram);
+                goto cleanup;
+            }
+        } while (r);
+
+        if (VIR_CLOSE(srcFD) < 0) {
+            virReportSystemError(errno,
+                                 _("Unable to close file '%s'"),
+                                 cfg->nvram[i]);
+            goto cleanup;
+        }
+        if (VIR_CLOSE(dstFD) < 0) {
+            virReportSystemError(errno,
+                                 _("Unable to close file '%s'"),
+                                 loader->nvram);
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    /* We successfully generated the nvram path, but failed to
+     * copy the file content. Roll back. */
+    if (ret < 0) {
+        if (created)
+            unlink(loader->nvram);
+        if (generated)
+            VIR_FREE(loader->nvram);
+    }
+
+    VIR_FORCE_CLOSE(srcFD);
+    VIR_FORCE_CLOSE(dstFD);
+    return ret;
+}
+
+
 int qemuProcessStart(virConnectPtr conn,
                      virQEMUDriverPtr driver,
                      virDomainObjPtr vm,
@@ -3802,6 +3927,13 @@ int qemuProcessStart(virConnectPtr conn,
     if (!(caps = virQEMUDriverGetCapabilities(driver, false)))
         goto cleanup;
 
+    /* Some things, paths, ... are generated here and we want them to persist.
+     * Fill them in prior to setting the domain def as transient. */
+    VIR_DEBUG("Generating paths");
+
+    if (qemuPrepareNVRAM(cfg, vm->def, migrateFrom) < 0)
+        goto cleanup;
+
     /* Do this upfront, so any part of the startup process can add
      * runtime state to vm->def that won't be persisted. This let's us
      * report implicit runtime defaults in the XML, like vnc listen/socket
diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in
index 7796acc..d2bc2c0 100644
--- a/src/qemu/test_libvirtd_qemu.aug.in
+++ b/src/qemu/test_libvirtd_qemu.aug.in
@@ -74,3 +74,6 @@ module Test_libvirtd_qemu =
 { "migration_port_min" = "49152" }
 { "migration_port_max" = "49215" }
 { "log_timestamp" = "0" }
+{ "nvram"
+    { "1" = "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd" }
+}
-- 
1.8.5.5




More information about the libvir-list mailing list