mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-08-27 19:11:16 +00:00
Pull QEMU monitor interaction out to separate file
Pull out all the QEMU monitor interaction code to a separate file. This will make life easier when we need to drop in a new implementation for the forthcoming QMP machine friendly monitor support. Next step is to add formal APIs for each monitor command, and remove direct commands for sending/receiving generic data. * src/Makefile.am: Add qemu_monitor.c to build * src/qemu/qemu_driver.c: Remove code for monitor interaction * src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h: New file for monitor interaction * po/POTFILES.in: Add src/qemu/qemu_monitor_text.c
This commit is contained in:
parent
c93370c4ad
commit
a541c76238
@ -25,6 +25,7 @@ src/openvz/openvz_driver.c
|
||||
src/phyp/phyp_driver.c
|
||||
src/qemu/qemu_conf.c
|
||||
src/qemu/qemu_driver.c
|
||||
src/qemu/qemu_monitor_text.c
|
||||
src/remote/remote_driver.c
|
||||
src/secret/secret_driver.c
|
||||
src/security/security_driver.c
|
||||
|
@ -169,6 +169,8 @@ VBOX_DRIVER_EXTRA_DIST = vbox/vbox_tmpl.c vbox/README
|
||||
|
||||
QEMU_DRIVER_SOURCES = \
|
||||
qemu/qemu_conf.c qemu/qemu_conf.h \
|
||||
qemu/qemu_monitor_text.c \
|
||||
qemu/qemu_monitor_text.h \
|
||||
qemu/qemu_driver.c qemu/qemu_driver.h
|
||||
|
||||
UML_DRIVER_SOURCES = \
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include "datatypes.h"
|
||||
#include "qemu_driver.h"
|
||||
#include "qemu_conf.h"
|
||||
#include "qemu_monitor_text.h"
|
||||
#include "c-ctype.h"
|
||||
#include "event.h"
|
||||
#include "buf.h"
|
||||
@ -74,9 +75,6 @@
|
||||
|
||||
#define VIR_FROM_THIS VIR_FROM_QEMU
|
||||
|
||||
#define QEMU_CMD_PROMPT "\n(qemu) "
|
||||
#define QEMU_PASSWD_PROMPT "Password: "
|
||||
|
||||
static int qemudShutdown(void);
|
||||
|
||||
static void qemuDriverLock(struct qemud_driver *driver)
|
||||
@ -88,12 +86,6 @@ static void qemuDriverUnlock(struct qemud_driver *driver)
|
||||
virMutexUnlock(&driver->lock);
|
||||
}
|
||||
|
||||
/* Return -1 for error, 0 for success */
|
||||
typedef int qemudMonitorExtraPromptHandler(const virDomainObjPtr vm,
|
||||
const char *buf,
|
||||
const char *prompt,
|
||||
void *data);
|
||||
|
||||
static void qemuDomainEventFlush(int timer, void *opaque);
|
||||
static void qemuDomainEventQueue(struct qemud_driver *driver,
|
||||
virDomainEventPtr event);
|
||||
@ -115,28 +107,6 @@ static void qemudShutdownVMDaemon(virConnectPtr conn,
|
||||
|
||||
static int qemudDomainGetMaxVcpus(virDomainPtr dom);
|
||||
|
||||
static int qemudMonitorCommand(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
char **reply);
|
||||
static int qemudMonitorCommandWithFd(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
static int qemudMonitorCommandWithHandler(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extraPrompt,
|
||||
qemudMonitorExtraPromptHandler extraHandler,
|
||||
void *handlerData,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
static int qemudMonitorCommandExtra(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extra,
|
||||
const char *extraPrompt,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
static int qemudMonitorSendCont(virConnectPtr conn,
|
||||
const virDomainObjPtr vm);
|
||||
static int qemudDomainSetMemoryBalloon(virConnectPtr conn,
|
||||
virDomainObjPtr vm,
|
||||
unsigned long newmem);
|
||||
@ -2411,400 +2381,6 @@ cleanup:
|
||||
}
|
||||
|
||||
|
||||
/* Throw away any data available on the monitor
|
||||
* This is done before executing a command, in order
|
||||
* to allow re-synchronization if something went badly
|
||||
* wrong in the past. it also deals with problem of
|
||||
* QEMU *sometimes* re-printing its initial greeting
|
||||
* when we reconnect to the monitor after restarts.
|
||||
*/
|
||||
static void
|
||||
qemuMonitorDiscardPendingData(virDomainObjPtr vm) {
|
||||
char buf[1024];
|
||||
int ret = 0;
|
||||
|
||||
/* Monitor is non-blocking, so just loop till we
|
||||
* get -1 or 0. Don't bother with detecting
|
||||
* errors, since we'll deal with that better later */
|
||||
do {
|
||||
ret = read(vm->monitor, buf, sizeof (buf)-1);
|
||||
} while (ret > 0);
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSendUnix(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
size_t cmdlen,
|
||||
int scm_fd)
|
||||
{
|
||||
struct msghdr msg;
|
||||
struct iovec iov[1];
|
||||
ssize_t ret;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
|
||||
iov[0].iov_base = (void *)cmd;
|
||||
iov[0].iov_len = cmdlen;
|
||||
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
if (scm_fd != -1) {
|
||||
char control[CMSG_SPACE(sizeof(int))];
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
memcpy(CMSG_DATA(cmsg), &scm_fd, sizeof(int));
|
||||
}
|
||||
|
||||
do {
|
||||
ret = sendmsg(vm->monitor, &msg, 0);
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
|
||||
return ret == cmdlen ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSend(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd)
|
||||
{
|
||||
char *full;
|
||||
size_t len;
|
||||
int ret = -1;
|
||||
|
||||
if (virAsprintf(&full, "%s\r", cmd) < 0)
|
||||
return -1;
|
||||
|
||||
len = strlen(full);
|
||||
|
||||
switch (vm->monitor_chr->type) {
|
||||
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
||||
if (qemudMonitorSendUnix(vm, full, len, scm_fd) < 0)
|
||||
goto out;
|
||||
break;
|
||||
default:
|
||||
case VIR_DOMAIN_CHR_TYPE_PTY:
|
||||
if (safewrite(vm->monitor, full, len) != len)
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
VIR_FREE(full);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorCommandWithHandler(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extraPrompt,
|
||||
qemudMonitorExtraPromptHandler extraHandler,
|
||||
void *handlerData,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
int size = 0;
|
||||
char *buf = NULL;
|
||||
|
||||
/* Should never happen, but just in case, protect
|
||||
* against null monitor (ocurrs when VM is inactive) */
|
||||
if (!vm->monitor_chr)
|
||||
return -1;
|
||||
|
||||
qemuMonitorDiscardPendingData(vm);
|
||||
|
||||
VIR_DEBUG("Send '%s'", cmd);
|
||||
if (qemudMonitorSend(vm, cmd, scm_fd) < 0)
|
||||
return -1;
|
||||
|
||||
*reply = NULL;
|
||||
|
||||
for (;;) {
|
||||
struct pollfd fd = { vm->monitor, POLLIN | POLLERR | POLLHUP, 0 };
|
||||
char *tmp;
|
||||
|
||||
/* Read all the data QEMU has sent thus far */
|
||||
for (;;) {
|
||||
char data[1024];
|
||||
int got = read(vm->monitor, data, sizeof(data));
|
||||
|
||||
if (got == 0)
|
||||
goto error;
|
||||
if (got < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
goto error;
|
||||
}
|
||||
if (VIR_REALLOC_N(buf, size+got+1) < 0)
|
||||
goto error;
|
||||
|
||||
memmove(buf+size, data, got);
|
||||
buf[size+got] = '\0';
|
||||
size += got;
|
||||
}
|
||||
|
||||
/* Look for QEMU prompt to indicate completion */
|
||||
if (buf) {
|
||||
char *foundPrompt;
|
||||
|
||||
if (extraPrompt &&
|
||||
(foundPrompt = strstr(buf, extraPrompt)) != NULL) {
|
||||
char *promptEnd;
|
||||
|
||||
if (extraHandler(vm, buf, foundPrompt, handlerData) < 0)
|
||||
return -1;
|
||||
/* Discard output so far, necessary to detect whether
|
||||
extraPrompt appears again. We don't need the output between
|
||||
original command and this prompt anyway. */
|
||||
promptEnd = foundPrompt + strlen(extraPrompt);
|
||||
memmove(buf, promptEnd, strlen(promptEnd)+1);
|
||||
size -= promptEnd - buf;
|
||||
} else if ((tmp = strstr(buf, QEMU_CMD_PROMPT)) != NULL) {
|
||||
char *commptr = NULL, *nlptr = NULL;
|
||||
/* Preserve the newline */
|
||||
tmp[1] = '\0';
|
||||
|
||||
/* The monitor doesn't dump clean output after we have written to
|
||||
* it. Every character we write dumps a bunch of useless stuff,
|
||||
* so the result looks like "cXcoXcomXcommXcommaXcommanXcommand"
|
||||
* Try to throw away everything before the first full command
|
||||
* occurence, and inbetween the command and the newline starting
|
||||
* the response
|
||||
*/
|
||||
if ((commptr = strstr(buf, cmd))) {
|
||||
memmove(buf, commptr, strlen(commptr)+1);
|
||||
if ((nlptr = strchr(buf, '\n')))
|
||||
memmove(buf+strlen(cmd), nlptr, strlen(nlptr)+1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
pollagain:
|
||||
/* Need to wait for more data */
|
||||
if (poll(&fd, 1, -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
goto pollagain;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
*reply = buf;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
VIR_FREE(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct extraHandlerData
|
||||
{
|
||||
const char *reply;
|
||||
bool first;
|
||||
};
|
||||
|
||||
static int
|
||||
qemudMonitorCommandSimpleExtraHandler(const virDomainObjPtr vm,
|
||||
const char *buf ATTRIBUTE_UNUSED,
|
||||
const char *prompt ATTRIBUTE_UNUSED,
|
||||
void *data_)
|
||||
{
|
||||
struct extraHandlerData *data = data_;
|
||||
|
||||
if (!data->first)
|
||||
return 0;
|
||||
if (qemudMonitorSend(vm, data->reply, -1) < 0)
|
||||
return -1;
|
||||
data->first = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorCommandExtra(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extra,
|
||||
const char *extraPrompt,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
struct extraHandlerData data;
|
||||
|
||||
data.reply = extra;
|
||||
data.first = true;
|
||||
return qemudMonitorCommandWithHandler(vm, cmd, extraPrompt,
|
||||
qemudMonitorCommandSimpleExtraHandler,
|
||||
&data, scm_fd, reply);
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorCommandWithFd(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
return qemudMonitorCommandExtra(vm, cmd, NULL, NULL, scm_fd, reply);
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorCommand(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
char **reply) {
|
||||
return qemudMonitorCommandWithFd(vm, cmd, -1, reply);
|
||||
}
|
||||
|
||||
static virStorageEncryptionPtr
|
||||
findDomainDiskEncryption(virConnectPtr conn, virDomainObjPtr vm,
|
||||
const char *path)
|
||||
{
|
||||
bool seen_volume;
|
||||
int i;
|
||||
|
||||
seen_volume = false;
|
||||
for (i = 0; i < vm->def->ndisks; i++) {
|
||||
virDomainDiskDefPtr disk;
|
||||
|
||||
disk = vm->def->disks[i];
|
||||
if (disk->src != NULL && STREQ(disk->src, path)) {
|
||||
seen_volume = true;
|
||||
if (disk->encryption != NULL)
|
||||
return disk->encryption;
|
||||
}
|
||||
}
|
||||
if (seen_volume)
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||
_("missing <encryption> for volume %s"), path);
|
||||
else
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
|
||||
_("unexpected passphrase request for volume %s"),
|
||||
path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
findVolumeQcowPassphrase(virConnectPtr conn, virDomainObjPtr vm,
|
||||
const char *path, size_t *passphrase_len)
|
||||
{
|
||||
virStorageEncryptionPtr enc;
|
||||
virSecretPtr secret;
|
||||
char *passphrase;
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
|
||||
if (conn->secretDriver == NULL ||
|
||||
conn->secretDriver->lookupByUUID == NULL ||
|
||||
conn->secretDriver->getValue == NULL) {
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s",
|
||||
_("secret storage not supported"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enc = findDomainDiskEncryption(conn, vm, path);
|
||||
if (enc == NULL)
|
||||
return NULL;
|
||||
|
||||
if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW ||
|
||||
enc->nsecrets != 1 ||
|
||||
enc->secrets[0]->type !=
|
||||
VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) {
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||
_("invalid <encryption> for volume %s"), path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
secret = conn->secretDriver->lookupByUUID(conn,
|
||||
enc->secrets[0]->uuid);
|
||||
if (secret == NULL)
|
||||
return NULL;
|
||||
data = conn->secretDriver->getValue(secret, &size,
|
||||
VIR_SECRET_GET_VALUE_INTERNAL_CALL);
|
||||
virUnrefSecret(secret);
|
||||
if (data == NULL)
|
||||
return NULL;
|
||||
|
||||
if (memchr(data, '\0', size) != NULL) {
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_SECRET,
|
||||
_("format='qcow' passphrase for %s must not contain a "
|
||||
"'\\0'"), path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (VIR_ALLOC_N(passphrase, size + 1) < 0) {
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
virReportOOMError(conn);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(passphrase, data, size);
|
||||
passphrase[size] = '\0';
|
||||
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
|
||||
*passphrase_len = size;
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSendVolumePassphrase(const virDomainObjPtr vm,
|
||||
const char *buf,
|
||||
const char *prompt,
|
||||
void *data)
|
||||
{
|
||||
virConnectPtr conn = data;
|
||||
char *passphrase, *path;
|
||||
const char *prompt_path;
|
||||
size_t path_len, passphrase_len = 0;
|
||||
int res;
|
||||
|
||||
/* The complete prompt looks like this:
|
||||
ide0-hd0 (/path/to/volume) is encrypted.
|
||||
Password:
|
||||
"prompt" starts with ") is encrypted". Extract /path/to/volume. */
|
||||
for (prompt_path = prompt; prompt_path > buf && prompt_path[-1] != '(';
|
||||
prompt_path--)
|
||||
;
|
||||
if (prompt_path == buf)
|
||||
return -1;
|
||||
path_len = prompt - prompt_path;
|
||||
if (VIR_ALLOC_N(path, path_len + 1) < 0)
|
||||
return -1;
|
||||
memcpy(path, prompt_path, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
passphrase = findVolumeQcowPassphrase(conn, vm, path, &passphrase_len);
|
||||
VIR_FREE(path);
|
||||
if (passphrase == NULL)
|
||||
return -1;
|
||||
|
||||
res = qemudMonitorSend(vm, passphrase, -1);
|
||||
|
||||
memset(passphrase, 0, passphrase_len);
|
||||
VIR_FREE(passphrase);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSendCont(virConnectPtr conn,
|
||||
const virDomainObjPtr vm) {
|
||||
char *reply;
|
||||
|
||||
if (qemudMonitorCommandWithHandler(vm, "cont", ") is encrypted.",
|
||||
qemudMonitorSendVolumePassphrase, conn,
|
||||
-1, &reply) < 0)
|
||||
return -1;
|
||||
qemudDebug ("%s: cont reply: %s", vm->def->name, info);
|
||||
VIR_FREE(reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static virDrvOpenStatus qemudOpen(virConnectPtr conn,
|
||||
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
|
||||
|
437
src/qemu/qemu_monitor_text.c
Normal file
437
src/qemu/qemu_monitor_text.c
Normal file
@ -0,0 +1,437 @@
|
||||
/*
|
||||
* qemu_monitor_text.c: interaction with QEMU monitor console
|
||||
*
|
||||
* Copyright (C) 2006-2009 Red Hat, Inc.
|
||||
* Copyright (C) 2006 Daniel P. Berrange
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Author: Daniel P. Berrange <berrange@redhat.com>
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "qemu_monitor_text.h"
|
||||
#include "qemu_conf.h"
|
||||
#include "memory.h"
|
||||
#include "logging.h"
|
||||
#include "driver.h"
|
||||
#include "datatypes.h"
|
||||
#include "virterror_internal.h"
|
||||
|
||||
#define VIR_FROM_THIS VIR_FROM_QEMU
|
||||
|
||||
/* Throw away any data available on the monitor
|
||||
* This is done before executing a command, in order
|
||||
* to allow re-synchronization if something went badly
|
||||
* wrong in the past. it also deals with problem of
|
||||
* QEMU *sometimes* re-printing its initial greeting
|
||||
* when we reconnect to the monitor after restarts.
|
||||
*/
|
||||
static void
|
||||
qemuMonitorDiscardPendingData(virDomainObjPtr vm) {
|
||||
char buf[1024];
|
||||
int ret = 0;
|
||||
|
||||
/* Monitor is non-blocking, so just loop till we
|
||||
* get -1 or 0. Don't bother with detecting
|
||||
* errors, since we'll deal with that better later */
|
||||
do {
|
||||
ret = read(vm->monitor, buf, sizeof (buf)-1);
|
||||
} while (ret > 0);
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSendUnix(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
size_t cmdlen,
|
||||
int scm_fd)
|
||||
{
|
||||
struct msghdr msg;
|
||||
struct iovec iov[1];
|
||||
ssize_t ret;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
|
||||
iov[0].iov_base = (void *)cmd;
|
||||
iov[0].iov_len = cmdlen;
|
||||
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
if (scm_fd != -1) {
|
||||
char control[CMSG_SPACE(sizeof(int))];
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
memcpy(CMSG_DATA(cmsg), &scm_fd, sizeof(int));
|
||||
}
|
||||
|
||||
do {
|
||||
ret = sendmsg(vm->monitor, &msg, 0);
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
|
||||
return ret == cmdlen ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSend(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd)
|
||||
{
|
||||
char *full;
|
||||
size_t len;
|
||||
int ret = -1;
|
||||
|
||||
if (virAsprintf(&full, "%s\r", cmd) < 0)
|
||||
return -1;
|
||||
|
||||
len = strlen(full);
|
||||
|
||||
switch (vm->monitor_chr->type) {
|
||||
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
||||
if (qemudMonitorSendUnix(vm, full, len, scm_fd) < 0)
|
||||
goto out;
|
||||
break;
|
||||
default:
|
||||
case VIR_DOMAIN_CHR_TYPE_PTY:
|
||||
if (safewrite(vm->monitor, full, len) != len)
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
VIR_FREE(full);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
qemudMonitorCommandWithHandler(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extraPrompt,
|
||||
qemudMonitorExtraPromptHandler extraHandler,
|
||||
void *handlerData,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
int size = 0;
|
||||
char *buf = NULL;
|
||||
|
||||
/* Should never happen, but just in case, protect
|
||||
* against null monitor (ocurrs when VM is inactive) */
|
||||
if (!vm->monitor_chr)
|
||||
return -1;
|
||||
|
||||
qemuMonitorDiscardPendingData(vm);
|
||||
|
||||
VIR_DEBUG("Send '%s'", cmd);
|
||||
if (qemudMonitorSend(vm, cmd, scm_fd) < 0)
|
||||
return -1;
|
||||
|
||||
*reply = NULL;
|
||||
|
||||
for (;;) {
|
||||
struct pollfd fd = { vm->monitor, POLLIN | POLLERR | POLLHUP, 0 };
|
||||
char *tmp;
|
||||
|
||||
/* Read all the data QEMU has sent thus far */
|
||||
for (;;) {
|
||||
char data[1024];
|
||||
int got = read(vm->monitor, data, sizeof(data));
|
||||
|
||||
if (got == 0)
|
||||
goto error;
|
||||
if (got < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
goto error;
|
||||
}
|
||||
if (VIR_REALLOC_N(buf, size+got+1) < 0)
|
||||
goto error;
|
||||
|
||||
memmove(buf+size, data, got);
|
||||
buf[size+got] = '\0';
|
||||
size += got;
|
||||
}
|
||||
|
||||
/* Look for QEMU prompt to indicate completion */
|
||||
if (buf) {
|
||||
char *foundPrompt;
|
||||
|
||||
if (extraPrompt &&
|
||||
(foundPrompt = strstr(buf, extraPrompt)) != NULL) {
|
||||
char *promptEnd;
|
||||
|
||||
if (extraHandler(vm, buf, foundPrompt, handlerData) < 0)
|
||||
return -1;
|
||||
/* Discard output so far, necessary to detect whether
|
||||
extraPrompt appears again. We don't need the output between
|
||||
original command and this prompt anyway. */
|
||||
promptEnd = foundPrompt + strlen(extraPrompt);
|
||||
memmove(buf, promptEnd, strlen(promptEnd)+1);
|
||||
size -= promptEnd - buf;
|
||||
} else if ((tmp = strstr(buf, QEMU_CMD_PROMPT)) != NULL) {
|
||||
char *commptr = NULL, *nlptr = NULL;
|
||||
/* Preserve the newline */
|
||||
tmp[1] = '\0';
|
||||
|
||||
/* The monitor doesn't dump clean output after we have written to
|
||||
* it. Every character we write dumps a bunch of useless stuff,
|
||||
* so the result looks like "cXcoXcomXcommXcommaXcommanXcommand"
|
||||
* Try to throw away everything before the first full command
|
||||
* occurence, and inbetween the command and the newline starting
|
||||
* the response
|
||||
*/
|
||||
if ((commptr = strstr(buf, cmd))) {
|
||||
memmove(buf, commptr, strlen(commptr)+1);
|
||||
if ((nlptr = strchr(buf, '\n')))
|
||||
memmove(buf+strlen(cmd), nlptr, strlen(nlptr)+1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
pollagain:
|
||||
/* Need to wait for more data */
|
||||
if (poll(&fd, 1, -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
goto pollagain;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
*reply = buf;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
VIR_FREE(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct extraHandlerData
|
||||
{
|
||||
const char *reply;
|
||||
bool first;
|
||||
};
|
||||
|
||||
static int
|
||||
qemudMonitorCommandSimpleExtraHandler(const virDomainObjPtr vm,
|
||||
const char *buf ATTRIBUTE_UNUSED,
|
||||
const char *prompt ATTRIBUTE_UNUSED,
|
||||
void *data_)
|
||||
{
|
||||
struct extraHandlerData *data = data_;
|
||||
|
||||
if (!data->first)
|
||||
return 0;
|
||||
if (qemudMonitorSend(vm, data->reply, -1) < 0)
|
||||
return -1;
|
||||
data->first = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
qemudMonitorCommandExtra(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extra,
|
||||
const char *extraPrompt,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
struct extraHandlerData data;
|
||||
|
||||
data.reply = extra;
|
||||
data.first = true;
|
||||
return qemudMonitorCommandWithHandler(vm, cmd, extraPrompt,
|
||||
qemudMonitorCommandSimpleExtraHandler,
|
||||
&data, scm_fd, reply);
|
||||
}
|
||||
|
||||
int
|
||||
qemudMonitorCommandWithFd(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd,
|
||||
char **reply) {
|
||||
return qemudMonitorCommandExtra(vm, cmd, NULL, NULL, scm_fd, reply);
|
||||
}
|
||||
|
||||
int
|
||||
qemudMonitorCommand(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
char **reply) {
|
||||
return qemudMonitorCommandWithFd(vm, cmd, -1, reply);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static virStorageEncryptionPtr
|
||||
findDomainDiskEncryption(virConnectPtr conn, virDomainObjPtr vm,
|
||||
const char *path)
|
||||
{
|
||||
bool seen_volume;
|
||||
int i;
|
||||
|
||||
seen_volume = false;
|
||||
for (i = 0; i < vm->def->ndisks; i++) {
|
||||
virDomainDiskDefPtr disk;
|
||||
|
||||
disk = vm->def->disks[i];
|
||||
if (disk->src != NULL && STREQ(disk->src, path)) {
|
||||
seen_volume = true;
|
||||
if (disk->encryption != NULL)
|
||||
return disk->encryption;
|
||||
}
|
||||
}
|
||||
if (seen_volume)
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||
_("missing <encryption> for volume %s"), path);
|
||||
else
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
|
||||
_("unexpected passphrase request for volume %s"),
|
||||
path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
findVolumeQcowPassphrase(virConnectPtr conn, virDomainObjPtr vm,
|
||||
const char *path, size_t *passphrase_len)
|
||||
{
|
||||
virStorageEncryptionPtr enc;
|
||||
virSecretPtr secret;
|
||||
char *passphrase;
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
|
||||
if (conn->secretDriver == NULL ||
|
||||
conn->secretDriver->lookupByUUID == NULL ||
|
||||
conn->secretDriver->getValue == NULL) {
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s",
|
||||
_("secret storage not supported"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enc = findDomainDiskEncryption(conn, vm, path);
|
||||
if (enc == NULL)
|
||||
return NULL;
|
||||
|
||||
if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW ||
|
||||
enc->nsecrets != 1 ||
|
||||
enc->secrets[0]->type !=
|
||||
VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) {
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||
_("invalid <encryption> for volume %s"), path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
secret = conn->secretDriver->lookupByUUID(conn,
|
||||
enc->secrets[0]->uuid);
|
||||
if (secret == NULL)
|
||||
return NULL;
|
||||
data = conn->secretDriver->getValue(secret, &size,
|
||||
VIR_SECRET_GET_VALUE_INTERNAL_CALL);
|
||||
virUnrefSecret(secret);
|
||||
if (data == NULL)
|
||||
return NULL;
|
||||
|
||||
if (memchr(data, '\0', size) != NULL) {
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_SECRET,
|
||||
_("format='qcow' passphrase for %s must not contain a "
|
||||
"'\\0'"), path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (VIR_ALLOC_N(passphrase, size + 1) < 0) {
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
virReportOOMError(conn);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(passphrase, data, size);
|
||||
passphrase[size] = '\0';
|
||||
|
||||
memset(data, 0, size);
|
||||
VIR_FREE(data);
|
||||
|
||||
*passphrase_len = size;
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
static int
|
||||
qemudMonitorSendVolumePassphrase(const virDomainObjPtr vm,
|
||||
const char *buf,
|
||||
const char *prompt,
|
||||
void *data)
|
||||
{
|
||||
virConnectPtr conn = data;
|
||||
char *passphrase, *path;
|
||||
const char *prompt_path;
|
||||
size_t path_len, passphrase_len = 0;
|
||||
int res;
|
||||
|
||||
/* The complete prompt looks like this:
|
||||
ide0-hd0 (/path/to/volume) is encrypted.
|
||||
Password:
|
||||
"prompt" starts with ") is encrypted". Extract /path/to/volume. */
|
||||
for (prompt_path = prompt; prompt_path > buf && prompt_path[-1] != '(';
|
||||
prompt_path--)
|
||||
;
|
||||
if (prompt_path == buf)
|
||||
return -1;
|
||||
path_len = prompt - prompt_path;
|
||||
if (VIR_ALLOC_N(path, path_len + 1) < 0)
|
||||
return -1;
|
||||
memcpy(path, prompt_path, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
passphrase = findVolumeQcowPassphrase(conn, vm, path, &passphrase_len);
|
||||
VIR_FREE(path);
|
||||
if (passphrase == NULL)
|
||||
return -1;
|
||||
|
||||
res = qemudMonitorSend(vm, passphrase, -1);
|
||||
|
||||
memset(passphrase, 0, passphrase_len);
|
||||
VIR_FREE(passphrase);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
qemudMonitorSendCont(virConnectPtr conn,
|
||||
const virDomainObjPtr vm) {
|
||||
char *reply;
|
||||
|
||||
if (qemudMonitorCommandWithHandler(vm, "cont", ") is encrypted.",
|
||||
qemudMonitorSendVolumePassphrase, conn,
|
||||
-1, &reply) < 0)
|
||||
return -1;
|
||||
qemudDebug ("%s: cont reply: %s", vm->def->name, info);
|
||||
VIR_FREE(reply);
|
||||
return 0;
|
||||
}
|
72
src/qemu/qemu_monitor_text.h
Normal file
72
src/qemu/qemu_monitor_text.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* qemu_monitor_text.h: interaction with QEMU monitor console
|
||||
*
|
||||
* Copyright (C) 2006-2009 Red Hat, Inc.
|
||||
* Copyright (C) 2006 Daniel P. Berrange
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Author: Daniel P. Berrange <berrange@redhat.com>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef QEMU_MONITOR_TEXT_H
|
||||
#define QEMU_MONITOR_TEXT_H
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include "domain_conf.h"
|
||||
|
||||
/* XXX remove these two from public header */
|
||||
#define QEMU_CMD_PROMPT "\n(qemu) "
|
||||
#define QEMU_PASSWD_PROMPT "Password: "
|
||||
|
||||
/* Return -1 for error, 0 for success */
|
||||
typedef int qemudMonitorExtraPromptHandler(const virDomainObjPtr vm,
|
||||
const char *buf,
|
||||
const char *prompt,
|
||||
void *data);
|
||||
|
||||
/* These first 4 APIs are generic monitor interaction. They will
|
||||
* go away eventually
|
||||
*/
|
||||
int qemudMonitorCommand(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
char **reply);
|
||||
int qemudMonitorCommandWithFd(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
int qemudMonitorCommandWithHandler(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extraPrompt,
|
||||
qemudMonitorExtraPromptHandler extraHandler,
|
||||
void *handlerData,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
int qemudMonitorCommandExtra(const virDomainObjPtr vm,
|
||||
const char *cmd,
|
||||
const char *extra,
|
||||
const char *extraPrompt,
|
||||
int scm_fd,
|
||||
char **reply);
|
||||
|
||||
/* Formal APIs for each required monitor command */
|
||||
|
||||
int qemudMonitorSendCont(virConnectPtr conn,
|
||||
const virDomainObjPtr vm);
|
||||
|
||||
#endif /* QEMU_MONITOR_TEXT_H */
|
||||
|
Loading…
Reference in New Issue
Block a user