Some UEFI firmwares may want to use a non-volatile memory to store
- some variables. In the host, this is represented as a file and the
- path to the file is stored in this element. Since
- 1.2.8
+ some variables. In the host, this is represented as a file and the path
+ to the file is stored in this element. Moreover, when the domain is
+ started up libvirt copies so called master NVRAM store file defined
+ in qemu.conf. If needed, the template
+ attribute can be used to per domain override map of master NVRAM stores
+ from the config file. Since 1.2.8
boot
The dev attribute takes one of the values "fd", "hd",
"cdrom" or "network" and is used to specify the next boot device
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index 5d9c21c245..6ae940ad26 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -263,7 +263,14 @@
-
+
+
+
+
+
+
+
+
diff --git a/libvirt.spec.in b/libvirt.spec.in
index 4756d7d09f..a6a58cf905 100644
--- a/libvirt.spec.in
+++ b/libvirt.spec.in
@@ -1938,6 +1938,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
@@ -2040,6 +2041,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 46e411e1fd..fa741a809d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2679,6 +2679,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/conf/domain_conf.c b/src/conf/domain_conf.c
index 989f2dc63c..a2a7d92d6a 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -2026,6 +2026,7 @@ virDomainLoaderDefFree(virDomainLoaderDefPtr loader)
VIR_FREE(loader->path);
VIR_FREE(loader->nvram);
+ VIR_FREE(loader->templt);
VIR_FREE(loader);
}
@@ -12822,6 +12823,7 @@ virDomainDefParseXML(xmlDocPtr xml,
goto error;
def->os.loader->nvram = virXPathString("string(./os/nvram[1])", ctxt);
+ def->os.loader->templt = virXPathString("string(./os/nvram[1]/@template)", ctxt);
}
}
@@ -17918,7 +17920,14 @@ virDomainLoaderDefFormat(virBufferPtr buf,
virBufferAsprintf(buf, " type='%s'>", type);
virBufferEscapeString(buf, "%s\n", loader->path);
- virBufferEscapeString(buf, "%s\n", loader->nvram);
+ if (loader->nvram || loader->templt) {
+ virBufferAddLit(buf, "templt);
+ if (loader->nvram)
+ virBufferEscapeString(buf, ">%s\n", loader->nvram);
+ else
+ virBufferAddLit(buf, "/>\n");
+ }
}
static bool
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index b5b4c675c5..efae2f5ee9 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1644,6 +1644,7 @@ struct _virDomainLoaderDef {
int readonly; /* enum virTristateBool */
virDomainLoader type;
char *nvram; /* path to non-volatile RAM */
+ char *templt; /* user override of path to master nvram */
};
void virDomainLoaderDefFree(virDomainLoaderDefPtr loader);
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug
index e7db7fe2be..62951daa2b 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 7bbbe09d58..79bba36b01 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 e2ec54f5ba..ac10b64c01 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,43 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
GET_VALUE_BOOL("log_timestamp", cfg->logTimestamp);
+ if ((p = virConfGetValue(conf, "nvram"))) {
+ size_t len;
+ virConfValuePtr pp;
+
+ CHECK_TYPE("nvram", VIR_CONF_LIST);
+
+ 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 ae7ac56b3d..1f521e534a 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 c70290af31..ac40ea8e44 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
@@ -3742,6 +3743,135 @@ 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)) {
+ const char *master_nvram_path = loader->templt;
+ ssize_t r;
+
+ if (!loader->templt) {
+ size_t i;
+ for (i = 0; i < cfg->nloader; i++) {
+ if (STREQ(cfg->loader[i], loader->path)) {
+ master_nvram_path = cfg->nvram[i];
+ break;
+ }
+ }
+ }
+
+ if (!master_nvram_path) {
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("unable to find any master var store for "
+ "loader: %s"), loader->path);
+ goto cleanup;
+ }
+
+ if ((srcFD = virFileOpenAs(master_nvram_path, O_RDONLY,
+ 0, -1, -1, 0)) < 0) {
+ virReportSystemError(-srcFD,
+ _("Failed to open file '%s'"),
+ master_nvram_path);
+ 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'"),
+ master_nvram_path);
+ 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'"),
+ master_nvram_path);
+ 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,
@@ -3810,6 +3940,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 7796acc1d8..d2bc2c0450 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" }
+}
diff --git a/tests/domainschemadata/domain-bios-nvram-empty.xml b/tests/domainschemadata/domain-bios-nvram-empty.xml
new file mode 100644
index 0000000000..e7643f3d91
--- /dev/null
+++ b/tests/domainschemadata/domain-bios-nvram-empty.xml
@@ -0,0 +1,40 @@
+
+ test-bios
+ 362d1fc1-df7d-193e-5c18-49a71bd1da66
+ 1048576
+ 1048576
+ 1
+
+ hvm
+ /usr/share/OVMF/OVMF_CODE.fd
+
+
+
+
+
+
+
+
+ destroy
+ restart
+ restart
+
+ /usr/bin/qemu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+