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

Michal Privoznik mprivozn at redhat.com
Fri Aug 8 10:17:43 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 and the default path can be
compiled in via --with-qemu-nvram-file configure option.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 configure.ac                       |  27 ++++++++
 libvirt.spec.in                    |   2 +
 src/Makefile.am                    |   1 +
 src/qemu/libvirtd_qemu.aug         |   3 +
 src/qemu/qemu.conf                 |   9 +++
 src/qemu/qemu_conf.c               |   8 +++
 src/qemu/qemu_conf.h               |   3 +
 src/qemu/qemu_process.c            | 125 +++++++++++++++++++++++++++++++++++++
 src/qemu/test_libvirtd_qemu.aug.in |   1 +
 9 files changed, 179 insertions(+)

diff --git a/configure.ac b/configure.ac
index 9dd07d2..16ca266 100644
--- a/configure.ac
+++ b/configure.ac
@@ -569,6 +569,11 @@ AC_ARG_WITH([chrdev-lock-files],
     [location for UUCP style lock files for character devices
      (use auto for default paths on some platforms) @<:@default=auto@:>@])])
 m4_divert_text([DEFAULTS], [with_chrdev_lock_files=auto])
+AC_ARG_WITH([qemu-nvram-file],
+  [AS_HELP_STRING([--with-qemu-nvram-file],
+    [location of OVMF_VARS.fd file
+     (use auto for default path on some platforms) @<:@default=auto@:>@])])
+m4_divert_text([DEFAULTS], [with_qemu_nvram_file=auto])
 AC_ARG_WITH([pm-utils],
   [AS_HELP_STRING([--with-pm-utils],
     [use pm-utils for power management @<:@default=yes@:>@])])
@@ -1418,6 +1423,27 @@ platform])
 fi
 AM_CONDITIONAL([VIR_CHRDEV_LOCK_FILE_PATH], [test "$with_chrdev_lock_files" != "no"])
 
+dnl OVMF_VARS.fd file
+if test "$with_qemu_nvram_file" != "no"; then
+  case $with_qemu_nvram_file in
+  yes | auto)
+    dnl Default locations for platforms, or disable if unknown
+    if test "$with_linux" = "yes"; then
+      with_qemu_nvram_file=/usr/share/OVMF/OVMF_VARS.fd
+    elif test "$with_chrdev_lock_files" = "auto"; then
+      with_qemu_nvram_file=no
+    fi ;;
+  esac
+  if test "$with_qemu_nvram_file" = "yes"; then
+    AC_MSG_ERROR([You must specify path for the lock files on this
+platform])
+  fi
+  if test "$with_qemu_nvram_file" != "no"; then
+    AC_DEFINE_UNQUOTED([VIR_QEMU_NVRAM_FILE_PATH], "$with_qemu_nvram_file",
+                       [default path to OVMF_VARS.fd file])
+  fi
+fi
+AM_CONDITIONAL([VIR_QEMU_NVRAM_FILE_PATH], [test "$with_qemu_nvram_file" != "no"])
 
 AC_ARG_WITH([secdriver-selinux],
   [AS_HELP_STRING([--with-secdriver-selinux],
@@ -2925,6 +2951,7 @@ AC_MSG_NOTICE([            numad: $with_numad])
 AC_MSG_NOTICE([      XML Catalog: $XML_CATALOG_FILE])
 AC_MSG_NOTICE([      Init script: $with_init_script])
 AC_MSG_NOTICE([Char device locks: $with_chrdev_lock_files])
+AC_MSG_NOTICE([ OVMF_VARS.fd loc: $with_qemu_nvram_file])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Developer Tools])
 AC_MSG_NOTICE([])
diff --git a/libvirt.spec.in b/libvirt.spec.in
index 29da071..486fc66 100644
--- a/libvirt.spec.in
+++ b/libvirt.spec.in
@@ -1947,6 +1947,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
@@ -2049,6 +2050,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 982f63d..9bd38f5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2632,6 +2632,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..3d9db99 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_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..a0c9485 100644
--- a/src/qemu/qemu.conf
+++ b/src/qemu/qemu.conf
@@ -487,3 +487,12 @@
 # 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.
+#nvram = "/usr/share/OVMF/OVMF_VARS.fd"
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 6bfa48e..3f6d365 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -255,6 +255,11 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged)
 
     cfg->logTimestamp = true;
 
+#ifdef VIR_QEMU_NVRAM_FILE_PATH
+    if (VIR_STRDUP(cfg->nvram, VIR_QEMU_NVRAM_FILE_PATH) < 0)
+        goto error;
+#endif
+
     return cfg;
 
  error:
@@ -305,6 +310,7 @@ static void virQEMUDriverConfigDispose(void *obj)
     virStringFreeList(cfg->securityDriverNames);
 
     VIR_FREE(cfg->lockManagerName);
+    VIR_FREE(cfg->nvram);
 }
 
 
@@ -654,6 +660,8 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
 
     GET_VALUE_BOOL("log_timestamp", cfg->logTimestamp);
 
+    GET_VALUE_STR("nvram", cfg->nvram);
+
     ret = 0;
 
  cleanup:
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 90aebef..0bb5333 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -172,6 +172,9 @@ struct _virQEMUDriverConfig {
     int migrationPortMax;
 
     bool logTimestamp;
+
+    char *nvram;    /* path to master NVRAM file from which
+                       copies per domain are derived */
 };
 
 /* Main driver state */
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 407da5e..2cb693a 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -66,6 +66,7 @@
 #include "virnuma.h"
 #include "virstring.h"
 #include "virhostdev.h"
+#include "configmake.h"
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
@@ -3714,6 +3715,123 @@ qemuProcessVerifyGuestCPU(virQEMUDriverPtr driver, virDomainObjPtr vm)
 }
 
 
+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;
+
+    /* 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)) {
+        ssize_t r = -1;
+
+        if (!cfg->nvram) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("master NVRAM file is not configured "
+                             "or turned off by administrator"));
+            goto cleanup;
+        }
+
+        if ((srcFD = virFileOpenAs(cfg->nvram, O_RDONLY,
+                                   0, -1, -1, 0)) < 0) {
+            virReportSystemError(-srcFD,
+                                 _("Failed to open file '%s'"),
+                                 cfg->nvram);
+            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;
+        }
+
+        while (r) {
+            char buf[1024];
+            ssize_t w = 0;
+
+            if ((r = saferead(srcFD, buf, sizeof(buf))) < 0) {
+                virReportSystemError(errno,
+                                     _("Unable to read from file '%s'"),
+                                     cfg->nvram);
+                goto cleanup;
+            }
+
+            do {
+                ssize_t actW;
+                if ((actW = safewrite(dstFD, buf + w, r - w)) < 0) {
+                    virReportSystemError(errno,
+                                         _("Unable to write to file '%s'"),
+                                         loader->nvram);
+                    goto cleanup;
+                }
+                w += actW;
+            } while (w != r);
+        }
+
+        if (VIR_CLOSE(srcFD) < 0) {
+            virReportSystemError(errno,
+                                 _("Unable to close file '%s'"),
+                                 cfg->nvram);
+            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 && generated)
+        VIR_FREE(loader->nvram);
+
+    VIR_FORCE_CLOSE(srcFD);
+    VIR_FORCE_CLOSE(dstFD);
+    return ret;
+}
+
+
 int qemuProcessStart(virConnectPtr conn,
                      virQEMUDriverPtr driver,
                      virDomainObjPtr vm,
@@ -3781,6 +3899,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..1ecb690 100644
--- a/src/qemu/test_libvirtd_qemu.aug.in
+++ b/src/qemu/test_libvirtd_qemu.aug.in
@@ -74,3 +74,4 @@ module Test_libvirtd_qemu =
 { "migration_port_min" = "49152" }
 { "migration_port_max" = "49215" }
 { "log_timestamp" = "0" }
+{ "nvram" = "/usr/share/OVMF/OVMF_VARS.fd" }
-- 
1.8.5.5




More information about the libvir-list mailing list