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 --- 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= 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 #include #include +#include #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__*/