libvirt/tests/qemumonitortestutils.c
Daniel P. Berrange 9129f9b3f1 Avoid double free in qemuMonitorCommonTestInit on OOM
The qemuMonitorCommonTestInit method did not allocate the
test object, so it should not free it upon failure. Doing
so causes a double free with the caller.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-09-25 18:12:09 +01:00

954 lines
24 KiB
C

/*
* Copyright (C) 2011-2013 Red Hat, Inc.
*
* 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, see
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "qemumonitortestutils.h"
#include "virthread.h"
#include "qemu/qemu_processpriv.h"
#include "qemu/qemu_monitor.h"
#include "qemu/qemu_agent.h"
#include "rpc/virnetsocket.h"
#include "viralloc.h"
#include "virlog.h"
#include "virerror.h"
#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_NONE
struct _qemuMonitorTestItem {
qemuMonitorTestResponseCallback cb;
void *opaque;
virFreeCallback freecb;
};
struct _qemuMonitorTest {
virMutex lock;
virThread thread;
bool json;
bool quit;
bool running;
char *incoming;
size_t incomingLength;
size_t incomingCapacity;
char *outgoing;
size_t outgoingLength;
size_t outgoingCapacity;
virNetSocketPtr server;
virNetSocketPtr client;
qemuMonitorPtr mon;
qemuAgentPtr agent;
char *tmpdir;
size_t nitems;
qemuMonitorTestItemPtr *items;
virDomainObjPtr vm;
};
static void
qemuMonitorTestItemFree(qemuMonitorTestItemPtr item)
{
if (!item)
return;
if (item->freecb)
(item->freecb)(item->opaque);
VIR_FREE(item);
}
/*
* Appends data for a reply to the outgoing buffer
*/
int
qemuMonitorTestAddReponse(qemuMonitorTestPtr test,
const char *response)
{
size_t want = strlen(response) + 2;
size_t have = test->outgoingCapacity - test->outgoingLength;
VIR_DEBUG("Adding response to monitor command: '%s", response);
if (have < want) {
size_t need = want - have;
if (VIR_EXPAND_N(test->outgoing, test->outgoingCapacity, need) < 0)
return -1;
}
want -= 2;
memcpy(test->outgoing + test->outgoingLength, response, want);
memcpy(test->outgoing + test->outgoingLength + want, "\r\n", 2);
test->outgoingLength += want + 2;
return 0;
}
int
qemuMonitorTestAddUnexpectedErrorResponse(qemuMonitorTestPtr test)
{
if (test->agent || test->json) {
return qemuMonitorTestAddReponse(test,
"{ \"error\": "
" { \"desc\": \"Unexpected command\", "
" \"class\": \"UnexpectedCommand\" } }");
} else {
return qemuMonitorTestAddReponse(test, "unexpected command");
}
}
int ATTRIBUTE_FMT_PRINTF(2, 3)
qemuMonitorReportError(qemuMonitorTestPtr test, const char *errmsg, ...)
{
va_list msgargs;
char *msg = NULL;
char *jsonmsg = NULL;
int ret = -1;
va_start(msgargs, errmsg);
if (virVasprintf(&msg, errmsg, msgargs) < 0)
goto cleanup;
if (test->agent || test->json) {
if (virAsprintf(&jsonmsg, "{ \"error\": "
" { \"desc\": \"%s\", "
" \"class\": \"UnexpectedCommand\" } }",
msg) < 0)
goto cleanup;
} else {
if (virAsprintf(&jsonmsg, "error: '%s'", msg) < 0)
goto cleanup;
}
ret = qemuMonitorTestAddReponse(test, jsonmsg);
cleanup:
va_end(msgargs);
VIR_FREE(msg);
VIR_FREE(jsonmsg);
return ret;
}
static int
qemuMonitorTestProcessCommand(qemuMonitorTestPtr test,
const char *cmdstr)
{
int ret;
VIR_DEBUG("Processing string from monitor handler: '%s", cmdstr);
if (test->nitems == 0) {
return qemuMonitorTestAddUnexpectedErrorResponse(test);
} else {
qemuMonitorTestItemPtr item = test->items[0];
ret = (item->cb)(test, item, cmdstr);
qemuMonitorTestItemFree(item);
if (VIR_DELETE_ELEMENT(test->items, 0, test->nitems) < 0)
return -1;
}
return ret;
}
/*
* Handles read/write of monitor data on the monitor server side
*/
static void
qemuMonitorTestIO(virNetSocketPtr sock,
int events,
void *opaque)
{
qemuMonitorTestPtr test = opaque;
bool err = false;
virMutexLock(&test->lock);
if (test->quit) {
virMutexUnlock(&test->lock);
return;
}
if (events & VIR_EVENT_HANDLE_WRITABLE) {
ssize_t ret;
if ((ret = virNetSocketWrite(sock,
test->outgoing,
test->outgoingLength)) < 0) {
err = true;
goto cleanup;
}
memmove(test->outgoing,
test->outgoing + ret,
test->outgoingLength - ret);
test->outgoingLength -= ret;
if ((test->outgoingCapacity - test->outgoingLength) > 1024)
VIR_SHRINK_N(test->outgoing, test->outgoingCapacity, 1024);
}
if (events & VIR_EVENT_HANDLE_READABLE) {
ssize_t ret, used;
char *t1, *t2;
if ((test->incomingCapacity - test->incomingLength) < 1024) {
if (VIR_EXPAND_N(test->incoming, test->incomingCapacity, 1024) < 0) {
err = true;
goto cleanup;
}
}
if ((ret = virNetSocketRead(sock,
test->incoming + test->incomingLength,
(test->incomingCapacity - test->incomingLength) - 1)) < 0) {
err = true;
goto cleanup;
}
test->incomingLength += ret;
test->incoming[test->incomingLength] = '\0';
/* Look to see if we've got a complete line, and
* if so, handle that command
*/
t1 = test->incoming;
while ((t2 = strstr(t1, "\n"))) {
*t2 = '\0';
if (qemuMonitorTestProcessCommand(test, t1) < 0) {
err = true;
goto cleanup;
}
t1 = t2 + 1;
}
used = t1 - test->incoming;
memmove(test->incoming, t1, test->incomingLength - used);
test->incomingLength -= used;
if ((test->incomingCapacity - test->incomingLength) > 1024) {
VIR_SHRINK_N(test->incoming,
test->incomingCapacity,
1024);
}
}
if (events & (VIR_EVENT_HANDLE_HANGUP |
VIR_EVENT_HANDLE_ERROR))
err = true;
cleanup:
if (err) {
virNetSocketRemoveIOCallback(sock);
virNetSocketClose(sock);
virObjectUnref(test->client);
test->client = NULL;
} else {
events = VIR_EVENT_HANDLE_READABLE;
if (test->outgoingLength)
events |= VIR_EVENT_HANDLE_WRITABLE;
virNetSocketUpdateIOCallback(sock, events);
}
virMutexUnlock(&test->lock);
}
static void
qemuMonitorTestWorker(void *opaque)
{
qemuMonitorTestPtr test = opaque;
virMutexLock(&test->lock);
while (!test->quit) {
virMutexUnlock(&test->lock);
if (virEventRunDefaultImpl() < 0) {
virMutexLock(&test->lock);
test->quit = true;
break;
}
virMutexLock(&test->lock);
}
test->running = false;
virMutexUnlock(&test->lock);
return;
}
static void
qemuMonitorTestFreeTimer(int timer ATTRIBUTE_UNUSED,
void *opaque ATTRIBUTE_UNUSED)
{
/* nothing to be done here */
}
void
qemuMonitorTestFree(qemuMonitorTestPtr test)
{
size_t i;
int timer = -1;
if (!test)
return;
virMutexLock(&test->lock);
if (test->running) {
test->quit = true;
/* HACK: Add a dummy timeout to break event loop */
timer = virEventAddTimeout(0, qemuMonitorTestFreeTimer, NULL, NULL);
}
virMutexUnlock(&test->lock);
if (test->client) {
virNetSocketRemoveIOCallback(test->client);
virNetSocketClose(test->client);
virObjectUnref(test->client);
}
virObjectUnref(test->server);
if (test->mon) {
virObjectUnlock(test->mon);
qemuMonitorClose(test->mon);
}
if (test->agent) {
virObjectUnlock(test->agent);
qemuAgentClose(test->agent);
}
virObjectUnref(test->vm);
if (test->running)
virThreadJoin(&test->thread);
if (timer != -1)
virEventRemoveTimeout(timer);
VIR_FREE(test->incoming);
VIR_FREE(test->outgoing);
for (i = 0; i < test->nitems; i++)
qemuMonitorTestItemFree(test->items[i]);
VIR_FREE(test->items);
if (test->tmpdir && rmdir(test->tmpdir) < 0)
VIR_WARN("Failed to remove tempdir: %s", strerror(errno));
VIR_FREE(test->tmpdir);
virMutexDestroy(&test->lock);
VIR_FREE(test);
}
int
qemuMonitorTestAddHandler(qemuMonitorTestPtr test,
qemuMonitorTestResponseCallback cb,
void *opaque,
virFreeCallback freecb)
{
qemuMonitorTestItemPtr item;
if (VIR_ALLOC(item) < 0)
goto error;
item->cb = cb;
item->freecb = freecb;
item->opaque = opaque;
virMutexLock(&test->lock);
if (VIR_APPEND_ELEMENT(test->items, test->nitems, item) < 0) {
virMutexUnlock(&test->lock);
goto error;
}
virMutexUnlock(&test->lock);
return 0;
error:
if (freecb)
(freecb)(opaque);
VIR_FREE(item);
return -1;
}
void *
qemuMonitorTestItemGetPrivateData(qemuMonitorTestItemPtr item)
{
return item ? item->opaque : NULL;
}
typedef struct _qemuMonitorTestCommandArgs qemuMonitorTestCommandArgs;
typedef qemuMonitorTestCommandArgs *qemuMonitorTestCommandArgsPtr;
struct _qemuMonitorTestCommandArgs {
char *argname;
char *argval;
};
struct qemuMonitorTestHandlerData {
char *command_name;
char *response;
size_t nargs;
qemuMonitorTestCommandArgsPtr args;
};
static void
qemuMonitorTestHandlerDataFree(void *opaque)
{
struct qemuMonitorTestHandlerData *data = opaque;
size_t i;
if (!data)
return;
for (i = 0; i < data->nargs; i++) {
VIR_FREE(data->args[i].argname);
VIR_FREE(data->args[i].argval);
}
VIR_FREE(data->command_name);
VIR_FREE(data->response);
VIR_FREE(data->args);
VIR_FREE(data);
}
static int
qemuMonitorTestProcessCommandDefault(qemuMonitorTestPtr test,
qemuMonitorTestItemPtr item,
const char *cmdstr)
{
struct qemuMonitorTestHandlerData *data = item->opaque;
virJSONValuePtr val = NULL;
char *cmdcopy = NULL;
const char *cmdname;
char *tmp;
int ret = -1;
if (test->agent || test->json) {
if (!(val = virJSONValueFromString(cmdstr)))
return -1;
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
ret = qemuMonitorReportError(test, "Missing command name in %s", cmdstr);
goto cleanup;
}
} else {
if (VIR_STRDUP(cmdcopy, cmdstr) < 0)
return -1;
cmdname = cmdcopy;
if (!(tmp = strchr(cmdcopy, ' '))) {
ret = qemuMonitorReportError(test,
"Cannot find command name in '%s'",
cmdstr);
goto cleanup;
}
*tmp = '\0';
}
if (STRNEQ(data->command_name, cmdname))
ret = qemuMonitorTestAddUnexpectedErrorResponse(test);
else
ret = qemuMonitorTestAddReponse(test, data->response);
cleanup:
VIR_FREE(cmdcopy);
virJSONValueFree(val);
return ret;
}
int
qemuMonitorTestAddItem(qemuMonitorTestPtr test,
const char *command_name,
const char *response)
{
struct qemuMonitorTestHandlerData *data;
if (VIR_ALLOC(data) < 0)
return -1;
if (VIR_STRDUP(data->command_name, command_name) < 0 ||
VIR_STRDUP(data->response, response) < 0) {
qemuMonitorTestHandlerDataFree(data);
return -1;
}
return qemuMonitorTestAddHandler(test,
qemuMonitorTestProcessCommandDefault,
data, qemuMonitorTestHandlerDataFree);
}
static int
qemuMonitorTestProcessGuestAgentSync(qemuMonitorTestPtr test,
qemuMonitorTestItemPtr item ATTRIBUTE_UNUSED,
const char *cmdstr)
{
virJSONValuePtr val = NULL;
virJSONValuePtr args;
unsigned long long id;
const char *cmdname;
char *retmsg = NULL;
int ret = -1;
if (!(val = virJSONValueFromString(cmdstr)))
return -1;
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
ret = qemuMonitorReportError(test, "Missing guest-sync command name");
goto cleanup;
}
if (STRNEQ(cmdname, "guest-sync")) {
ret = qemuMonitorTestAddUnexpectedErrorResponse(test);
goto cleanup;
}
if (!(args = virJSONValueObjectGet(val, "arguments"))) {
ret = qemuMonitorReportError(test, "Missing arguments for guest-sync");
goto cleanup;
}
if (virJSONValueObjectGetNumberUlong(args, "id", &id)) {
ret = qemuMonitorReportError(test, "Missing id for guest sync");
goto cleanup;
}
if (virAsprintf(&retmsg, "{\"return\":%llu}", id) < 0)
goto cleanup;
ret = qemuMonitorTestAddReponse(test, retmsg);
cleanup:
virJSONValueFree(val);
VIR_FREE(retmsg);
return ret;
}
int
qemuMonitorTestAddAgentSyncResponse(qemuMonitorTestPtr test)
{
if (!test->agent) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"This test is not an agent test");
return -1;
}
return qemuMonitorTestAddHandler(test,
qemuMonitorTestProcessGuestAgentSync,
NULL, NULL);
}
static int
qemuMonitorTestProcessCommandWithArgs(qemuMonitorTestPtr test,
qemuMonitorTestItemPtr item,
const char *cmdstr)
{
struct qemuMonitorTestHandlerData *data = item->opaque;
virJSONValuePtr val = NULL;
virJSONValuePtr args;
virJSONValuePtr argobj;
char *argstr = NULL;
const char *cmdname;
size_t i;
int ret = -1;
if (!(val = virJSONValueFromString(cmdstr)))
return -1;
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
ret = qemuMonitorReportError(test, "Missing command name in %s", cmdstr);
goto cleanup;
}
if (STRNEQ(data->command_name, cmdname)) {
ret = qemuMonitorTestAddUnexpectedErrorResponse(test);
goto cleanup;
}
if (!(args = virJSONValueObjectGet(val, "arguments"))) {
ret = qemuMonitorReportError(test,
"Missing arguments section for command '%s'",
data->command_name);
goto cleanup;
}
/* validate the args */
for (i = 0; i < data->nargs; i++) {
qemuMonitorTestCommandArgsPtr arg = &data->args[i];
if (!(argobj = virJSONValueObjectGet(args, arg->argname))) {
ret = qemuMonitorReportError(test,
"Missing argument '%s' for command '%s'",
arg->argname, data->command_name);
goto cleanup;
}
/* convert the argument to string */
if (!(argstr = virJSONValueToString(argobj, false)))
goto cleanup;
/* verify that the argument value is expected */
if (STRNEQ(argstr, arg->argval)) {
ret = qemuMonitorReportError(test,
"Invalid value of argument '%s' "
"of command '%s': "
"expected '%s' got '%s'",
arg->argname, data->command_name,
arg->argval, argstr);
goto cleanup;
}
VIR_FREE(argstr);
}
/* arguments checked out, return the response */
ret = qemuMonitorTestAddReponse(test, data->response);
cleanup:
VIR_FREE(argstr);
virJSONValueFree(val);
return ret;
}
/* this allows to add a responder that is able to check
* a (shallow) structure of arguments for a command */
int
qemuMonitorTestAddItemParams(qemuMonitorTestPtr test,
const char *cmdname,
const char *response,
...)
{
struct qemuMonitorTestHandlerData *data;
const char *argname;
const char *argval;
va_list args;
va_start(args, response);
if (VIR_ALLOC(data) < 0)
goto error;
if (VIR_STRDUP(data->command_name, cmdname) < 0 ||
VIR_STRDUP(data->response, response) < 0)
goto error;
while ((argname = va_arg(args, char *))) {
size_t i;
if (!(argval = va_arg(args, char *))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"Missing argument value for argument '%s'",
argname);
goto error;
}
i = data->nargs;
if (VIR_EXPAND_N(data->args, data->nargs, 1))
goto error;
if (VIR_STRDUP(data->args[i].argname, argname) < 0 ||
VIR_STRDUP(data->args[i].argval, argval) < 0)
goto error;
}
va_end(args);
return qemuMonitorTestAddHandler(test,
qemuMonitorTestProcessCommandWithArgs,
data, qemuMonitorTestHandlerDataFree);
error:
va_end(args);
qemuMonitorTestHandlerDataFree(data);
return -1;
}
static void
qemuMonitorTestEOFNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED,
void *opaque ATTRIBUTE_UNUSED)
{
}
static void
qemuMonitorTestErrorNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED,
void *opaque ATTRIBUTE_UNUSED)
{
}
static qemuMonitorCallbacks qemuMonitorTestCallbacks = {
.eofNotify = qemuMonitorTestEOFNotify,
.errorNotify = qemuMonitorTestErrorNotify,
.domainDeviceDeleted = qemuProcessHandleDeviceDeleted,
};
static void
qemuMonitorTestAgentNotify(qemuAgentPtr agent ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED)
{
}
static qemuAgentCallbacks qemuMonitorTestAgentCallbacks = {
.eofNotify = qemuMonitorTestAgentNotify,
.errorNotify = qemuMonitorTestAgentNotify,
};
static qemuMonitorTestPtr
qemuMonitorCommonTestNew(virDomainXMLOptionPtr xmlopt,
virDomainObjPtr vm,
virDomainChrSourceDefPtr src)
{
qemuMonitorTestPtr test = NULL;
char *path = NULL;
char *tmpdir_template = NULL;
if (VIR_ALLOC(test) < 0)
goto error;
if (virMutexInit(&test->lock) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
"Cannot initialize mutex");
VIR_FREE(test);
return NULL;
}
if (VIR_STRDUP(tmpdir_template, "/tmp/libvirt_XXXXXX") < 0)
goto error;
if (!(test->tmpdir = mkdtemp(tmpdir_template))) {
virReportSystemError(errno, "%s",
"Failed to create temporary directory");
goto error;
}
tmpdir_template = NULL;
if (virAsprintf(&path, "%s/qemumonitorjsontest.sock", test->tmpdir) < 0)
goto error;
if (vm) {
virObjectRef(vm);
test->vm = vm;
} else {
test->vm = virDomainObjNew(xmlopt);
if (!test->vm)
goto error;
}
if (virNetSocketNewListenUNIX(path, 0700, getuid(), getgid(),
&test->server) < 0)
goto error;
memset(src, 0, sizeof(*src));
src->type = VIR_DOMAIN_CHR_TYPE_UNIX;
src->data.nix.path = (char *)path;
src->data.nix.listen = false;
path = NULL;
if (virNetSocketListen(test->server, 1) < 0)
goto error;
cleanup:
return test;
error:
VIR_FREE(path);
VIR_FREE(tmpdir_template);
qemuMonitorTestFree(test);
test = NULL;
goto cleanup;
}
static int
qemuMonitorCommonTestInit(qemuMonitorTestPtr test)
{
int events = VIR_EVENT_HANDLE_READABLE;
if (!test)
return -1;
if (virNetSocketAccept(test->server, &test->client) < 0)
goto error;
if (!test->client)
goto error;
if (test->outgoingLength > 0)
events = VIR_EVENT_HANDLE_WRITABLE;
if (virNetSocketAddIOCallback(test->client,
events,
qemuMonitorTestIO,
test,
NULL) < 0)
goto error;
virMutexLock(&test->lock);
if (virThreadCreate(&test->thread,
true,
qemuMonitorTestWorker,
test) < 0) {
virMutexUnlock(&test->lock);
goto error;
}
test->running = true;
virMutexUnlock(&test->lock);
return 0;
error:
return -1;
}
#define QEMU_JSON_GREETING "{\"QMP\":"\
" {\"version\":"\
" {\"qemu\":"\
" {\"micro\": 1,"\
" \"minor\": 0,"\
" \"major\": 1"\
" },"\
" \"package\": \"(qemu-kvm-1.0.1)"\
" \"},"\
" \"capabilities\": []"\
" }"\
"}"
/* We skip the normal handshake reply of "{\"execute\":\"qmp_capabilities\"}" */
#define QEMU_TEXT_GREETING "QEMU 1.0,1 monitor - type 'help' for more information"
qemuMonitorTestPtr
qemuMonitorTestNew(bool json,
virDomainXMLOptionPtr xmlopt,
virDomainObjPtr vm,
virQEMUDriverPtr driver)
{
qemuMonitorTestPtr test = NULL;
virDomainChrSourceDef src;
if (!(test = qemuMonitorCommonTestNew(xmlopt, vm, &src)))
goto error;
test->json = json;
if (!(test->mon = qemuMonitorOpen(test->vm,
&src,
json,
&qemuMonitorTestCallbacks,
driver)))
goto error;
virObjectLock(test->mon);
if (qemuMonitorTestAddReponse(test, json ?
QEMU_JSON_GREETING :
QEMU_TEXT_GREETING) < 0)
goto error;
if (qemuMonitorCommonTestInit(test) < 0)
goto error;
virDomainChrSourceDefClear(&src);
return test;
error:
virDomainChrSourceDefClear(&src);
qemuMonitorTestFree(test);
return NULL;
}
qemuMonitorTestPtr
qemuMonitorTestNewAgent(virDomainXMLOptionPtr xmlopt)
{
qemuMonitorTestPtr test = NULL;
virDomainChrSourceDef src;
if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, &src)))
goto error;
if (!(test->agent = qemuAgentOpen(test->vm,
&src,
&qemuMonitorTestAgentCallbacks)))
goto error;
virObjectLock(test->agent);
if (qemuMonitorCommonTestInit(test) < 0)
goto error;
virDomainChrSourceDefClear(&src);
return test;
error:
virDomainChrSourceDefClear(&src);
qemuMonitorTestFree(test);
return NULL;
}
qemuMonitorPtr
qemuMonitorTestGetMonitor(qemuMonitorTestPtr test)
{
return test->mon;
}
qemuAgentPtr
qemuMonitorTestGetAgent(qemuMonitorTestPtr test)
{
return test->agent;
}