This patch adds support for TPM for the Qemu driver to
- detect whether Qemu supports TPM and then sets the TPM capability flag
appropriately
- create the Qemu command line to add a TPM to the VM
- create a QCoW2 file that holds the TPM persistent state
- parse a Qemu command line
This patch also moves the function qemuFindQemuImgBinary(void) from
qemu_driver to qemu_command and makes it a non-static function. qemu_command
seemed a better place for 'public' functions that qemu_driver.
Signed-off-by: Stefan Berger <stefanb(a)linux.vnet.ibm.com>
---
src/qemu/qemu_capabilities.c | 54 ++++++++++
src/qemu/qemu_capabilities.h | 3
src/qemu/qemu_command.c | 232 +++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_command.h | 1
src/qemu/qemu_driver.c | 37 ++++--
src/qemu/qemu_migration.c | 1
6 files changed, 314 insertions(+), 14 deletions(-)
Index: libvirt-acl/src/qemu/qemu_command.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_command.c
+++ libvirt-acl/src/qemu/qemu_command.c
@@ -910,6 +910,109 @@ int qemuDomainPCIAddressSetNextAddr(qemu
return -1;
}
+
+static int qemudTPMCreateBlockStore(const char *tpmstorefile,
+ const char *qemu_cmd)
+{
+ int rc = 0;
+ struct stat statbuf;
+ char *qemuimg = NULL;
+ int status;
+ char filesize[10];
+ unsigned int bssize;
+ const char *argv[] = { qemuimg, "create", "-f",
"qcow2", tpmstorefile,
+ filesize, NULL};
+
+ if (stat(tpmstorefile, &statbuf) == -1 || statbuf.st_size == 0) {
+ if (errno == ENOENT || statbuf.st_size == 0) {
+ /* determine size of qcow2 */
+ if (qemuCapsGetTPMBuiltinBSSize(qemu_cmd,
+ &bssize) < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s",
+ _("Could not determine TPM store file size"));
+ rc = 1;
+ goto err_exit;
+ }
+
+ /* create the file */
+ qemuimg = qemuFindQemuImgBinary();
+ if (!qemuimg) {
+ rc = 1;
+ goto err_exit;
+ }
+ argv[0] = qemuimg;
+ snprintf(filesize, sizeof(filesize),"%dk", bssize);
+
+ VIR_DEBUG("Creating BS file %s of size %dkb\n",
+ tpmstorefile,
+ bssize);
+
+ if (virRun(argv, &status) != 0 || status != 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("Could not create TPM store
file"));
+ rc = 1;
+ goto err_exit;
+ }
+ } else {
+ // FIXME: Not sure what to do in all the other error cases
+ virReportSystemError(errno, "%s",
+ _("Error while stating TPM blockstore file"));
+ rc = 1;
+ }
+ } else {
+ VIR_DEBUG("TPM Blockstorage file %s exists\n", tpmstorefile);
+ }
+
+ err_exit:
+ VIR_FREE(qemuimg);
+ return rc;
+}
+
+
+static char * qemudBuildCommandLineTPMDevStr(const virDomainDefPtr def,
+ const char *qemu_cmd)
+{
+ int rc = 0;
+ char *tmp = NULL;
+ const virDomainTPMDefPtr tpm = def->tpm;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+ switch (tpm->type) {
+ case VIR_DOMAIN_TPM_TYPE_BUILTIN:
+ tmp = virDomainTPMGetStorageFilename(tpm, def->uuid);
+ if (!tmp) {
+ rc = -1;
+ goto err_exit;
+ }
+ if (qemudTPMCreateBlockStore(tmp, qemu_cmd)) {
+ rc = -1;
+ goto err_exit;
+ }
+ virBufferVSprintf(&buf, "type=builtin,path=%s", tmp);
+ if (virBufferError(&buf)) {
+ virReportOOMError();
+ rc = -1;
+ goto err_exit;
+ }
+ break;
+
+ default:
+ rc = -1;
+ goto err_exit;
+ }
+
+ VIR_FREE(tmp);
+
+ return virBufferContentAndReset(&buf);
+
+ err_exit:
+ virBufferFreeAndReset(&buf);
+ VIR_FREE(tmp);
+ return NULL;
+}
+
+
/*
* This assigns static PCI slots to all configured devices.
* The ordering here is chosen to match the ordering used
@@ -3831,6 +3934,21 @@ qemuBuildCommandLine(virConnectPtr conn,
}
}
+ if (qemuCapsGet(qemuCaps, QEMU_CAPS_TPM)) {
+ if (!def->tpm) {
+ virCommandAddArg(cmd, "-tpm");
+ virCommandAddArg(cmd, "none");
+ } else {
+ char *optstr;
+ if (!(optstr = qemudBuildCommandLineTPMDevStr(def, emulator)))
+ goto error;
+
+ virCommandAddArg(cmd, "-tpm");
+ virCommandAddArg(cmd, optstr);
+ VIR_FREE(optstr);
+ }
+ }
+
virCommandAddArg(cmd, "-usb");
for (i = 0 ; i < def->ninputs ; i++) {
virDomainInputDefPtr input = def->inputs[i];
@@ -5508,6 +5626,99 @@ error:
static int
+qemuParseCommandLineTPM(virDomainDefPtr dom,
+ const char *val)
+{
+ int rc = 0;
+ virDomainTPMDefPtr tpm;
+ char **keywords;
+ char **values;
+ int nkeywords;
+ int i;
+
+ if (dom->tpm)
+ goto error;
+
+ nkeywords = qemuParseKeywords(val, &keywords, &values, 1);
+ if (nkeywords < 0)
+ goto error;
+
+ if (VIR_ALLOC(tpm) < 0)
+ goto no_memory;
+
+ tpm->type = VIR_DOMAIN_TPM_TYPE_BUILTIN;
+
+ for (i = 0; i < nkeywords; i++) {
+ if (STREQ(keywords[i], "type")) {
+ if (values[i] && STREQ(values[i], "builtin"))
+ tpm->type = VIR_DOMAIN_TPM_TYPE_BUILTIN;
+ } else if (STREQ(keywords[i], "path")) {
+ if (values[i]) {
+ tpm->data.builtin.storage = values[i];
+ values[i] = NULL;
+ } else
+ goto syntax;
+ }
+ }
+
+ /* sanity checks */
+ switch (tpm->type) {
+ case VIR_DOMAIN_TPM_TYPE_BUILTIN:
+ if (!tpm->data.builtin.storage) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s",
+ _("missing required file=<filename>
option"));
+ goto bad_definition;
+ }
+ break;
+ default:
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s",
+ _("unknown TPM type"));
+ goto bad_definition;
+ }
+
+ /* all ok */
+ dom->tpm = tpm;
+
+cleanup:
+ for (i = 0 ; i < nkeywords ; i++) {
+ VIR_FREE(keywords[i]);
+ VIR_FREE(values[i]);
+ }
+ VIR_FREE(keywords);
+ VIR_FREE(values);
+
+
+ return rc;
+
+syntax:
+ virDomainTPMDefFree(tpm);
+
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown TPM syntax '%s'"), val);
+ rc = -1;
+ goto cleanup;
+
+bad_definition:
+ virDomainTPMDefFree(tpm);
+
+ rc = -1;
+ goto cleanup;
+
+no_memory:
+ virReportOOMError();
+
+ rc = -1;
+ goto cleanup;
+
+
+error:
+ return -1;
+}
+
+
+static int
qemuParseCommandLineSmp(virDomainDefPtr dom,
const char *val)
{
@@ -6141,6 +6352,12 @@ virDomainDefPtr qemuParseCommandLine(vir
/* ignore, used internally by libvirt */
} else if (STREQ(arg, "-S")) {
/* ignore, always added by libvirt */
+ } else if (STREQ(arg, "-tpm")) {
+ WANT_VALUE();
+ if (STRNEQ(val, "none")) {
+ if (qemuParseCommandLineTPM(def, val) < 0)
+ goto error;
+ }
} else {
/* something we can't yet parse. Add it to the qemu namespace
* cmdline/environment advanced options and hope for the best
@@ -6330,3 +6547,18 @@ cleanup:
return def;
}
+
+
+char *qemuFindQemuImgBinary(void)
+{
+ char *ret;
+
+ ret = virFindFileInPath("kvm-img");
+ if (ret == NULL)
+ ret = virFindFileInPath("qemu-img");
+ if (ret == NULL)
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("unable to find kvm-img or
qemu-img"));
+
+ return ret;
+}
Index: libvirt-acl/src/qemu/qemu_command.h
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_command.h
+++ libvirt-acl/src/qemu/qemu_command.h
@@ -172,5 +172,6 @@ qemuParseKeywords(const char *str,
char ***retvalues,
int allowEmptyValue);
+char *qemuFindQemuImgBinary(void);
#endif /* __QEMU_COMMAND_H__*/
Index: libvirt-acl/src/qemu/qemu_driver.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_driver.c
+++ libvirt-acl/src/qemu/qemu_driver.c
@@ -1268,6 +1268,7 @@ static virDomainPtr qemudDomainCreate(vi
(flags & VIR_DOMAIN_START_PAUSED) != 0,
-1, NULL, VIR_VM_OP_CREATE) < 0) {
qemuAuditDomainStart(vm, "booted", false);
+ virDomainTPMDelete(vm, false);
if (qemuDomainObjEndJob(vm) > 0)
virDomainRemoveInactive(&driver->domains,
vm);
@@ -1496,6 +1497,7 @@ static int qemudDomainDestroy(virDomainP
qemuAuditDomainStop(vm, "destroyed");
if (!vm->persistent) {
+ virDomainTPMDelete(vm, false);
if (qemuDomainObjEndJob(vm) > 0)
virDomainRemoveInactive(&driver->domains,
vm);
@@ -3693,6 +3695,8 @@ static int qemudDomainUndefine(virDomain
goto cleanup;
}
+ virDomainTPMDelete(vm, false);
+
if (virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm) < 0)
goto cleanup;
@@ -5856,20 +5860,6 @@ cleanup:
return ret;
}
-static char *qemuFindQemuImgBinary(void)
-{
- char *ret;
-
- ret = virFindFileInPath("kvm-img");
- if (ret == NULL)
- ret = virFindFileInPath("qemu-img");
- if (ret == NULL)
- qemuReportError(VIR_ERR_INTERNAL_ERROR,
- "%s", _("unable to find kvm-img or
qemu-img"));
-
- return ret;
-}
-
static int qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm,
virDomainSnapshotObjPtr snapshot,
char *snapshotDir)
@@ -6474,6 +6464,7 @@ static int qemuDomainSnapshotDiscard(str
int i;
qemuDomainObjPrivatePtr priv;
virDomainSnapshotObjPtr parentsnap;
+ virDomainTPMDefPtr tpm;
if (!virDomainObjIsActive(vm)) {
qemuimgarg[0] = qemuFindQemuImgBinary();
@@ -6504,6 +6495,24 @@ static int qemuDomainSnapshotDiscard(str
}
}
}
+
+ tpm = vm->def->tpm;
+ if (tpm) {
+ switch (tpm->type) {
+ case VIR_DOMAIN_TPM_TYPE_BUILTIN:
+ qemuimgarg[4] = virDomainTPMGetStorageFilename(tpm,
+ vm->def->uuid);
+ if (virRun(qemuimgarg, NULL) < 0) {
+ /* we continue on even in the face of error
+ */
+ }
+ VIR_FREE(qemuimgarg[4]);
+ break;
+
+ case VIR_DOMAIN_TPM_TYPE_LAST:
+ break;
+ }
+ }
}
else {
priv = vm->privateData;
Index: libvirt-acl/src/qemu/qemu_migration.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_migration.c
+++ libvirt-acl/src/qemu/qemu_migration.c
@@ -1090,6 +1090,7 @@ int qemuMigrationPerform(struct qemud_dr
VIR_DOMAIN_EVENT_STOPPED_MIGRATED);
if (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE)) {
virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm);
+ virDomainTPMDelete(vm, true);
if (qemuDomainObjEndJob(vm) > 0)
virDomainRemoveInactive(&driver->domains, vm);
vm = NULL;
Index: libvirt-acl/src/qemu/qemu_capabilities.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_capabilities.c
+++ libvirt-acl/src/qemu/qemu_capabilities.c
@@ -40,6 +40,7 @@
#include <sys/wait.h>
#include <sys/utsname.h>
#include <stdarg.h>
+#include <c-ctype.h>
#define VIR_FROM_THIS VIR_FROM_QEMU
@@ -808,6 +809,8 @@ qemuCapsComputeCmdFlags(const char *help
qemuCapsSet(flags, QEMU_CAPS_XEN_DOMID);
else if (strstr(help, "-domid"))
qemuCapsSet(flags, QEMU_CAPS_DOMID);
+ if (strstr(help, "-tpm"))
+ qemuCapsSet(flags, QEMU_CAPS_TPM);
if (strstr(help, "-drive")) {
qemuCapsSet(flags, QEMU_CAPS_DRIVE);
if (strstr(help, "cache=") &&
@@ -1238,6 +1241,57 @@ int qemuCapsExtractVersion(virCapsPtr ca
}
+int qemuCapsGetTPMBuiltinBSSize(const char *qemu,
+ unsigned int *bssize)
+{
+ int ret = -1;
+ char *output = NULL, *pos;
+ virCommandPtr cmd;
+
+ /* Make sure the binary we are about to try exec'ing exists.
+ * Technically we could catch the exec() failure, but that's
+ * in a sub-process so it's hard to feed back a useful error.
+ */
+ if (virFileIsExecutable(qemu) < 0) {
+ virReportSystemError(errno, _("Cannot find QEMU binary %s"), qemu);
+ return -1;
+ }
+
+ cmd = virCommandNewArgList(qemu, "-tpm", "?", NULL);
+ virCommandAddEnvPassCommon(cmd);
+ virCommandSetOutputBuffer(cmd, &output);
+ virCommandClearCaps(cmd);
+
+ if (virCommandRun(cmd, NULL) < 0)
+ goto cleanup;
+
+ pos = strstr(output, "builtin");
+ if (!pos)
+ goto cleanup;
+
+ while (1) {
+ if (*pos == '\n' || *pos == 0)
+ goto cleanup;
+
+ if (c_isdigit(*pos)) {
+ if (sscanf(pos, "%dkb", bssize) != 1)
+ goto cleanup;
+ else
+ break;
+ }
+ pos++;
+ }
+
+ ret = 0;
+
+cleanup:
+ VIR_FREE(output);
+ virCommandFree(cmd);
+
+ return ret;
+}
+
+
virBitmapPtr
qemuCapsNew(void)
{
Index: libvirt-acl/src/qemu/qemu_capabilities.h
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_capabilities.h
+++ libvirt-acl/src/qemu/qemu_capabilities.h
@@ -95,6 +95,7 @@ enum qemuCapsFlags {
QEMU_CAPS_DEVICE_SPICEVMC = 57, /* older -device spicevmc*/
QEMU_CAPS_VIRTIO_TX_ALG = 58, /* -device virtio-net-pci,tx=string */
QEMU_CAPS_DEVICE_QXL_VGA = 59, /* Is the primary and vga campatible qxl device
named qxl-vga? */
+ QEMU_CAPS_TPM = 60, /* if TPM (-tpm) support is available*/
QEMU_CAPS_LAST, /* this must always be the last item */
};
@@ -141,5 +142,7 @@ int qemuCapsParseHelpStr(const char *qem
int qemuCapsParseDeviceStr(const char *str,
virBitmapPtr qemuCaps);
+int qemuCapsGetTPMBuiltinBSSize(const char *qemu,
+ unsigned int *size);
#endif /* __QEMU_CAPABILITIES_H__*/