From patchwork Mon Mar 7 20:10:33 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Roth X-Patchwork-Id: 85811 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 05963B6F0D for ; Tue, 8 Mar 2011 07:47:50 +1100 (EST) Received: from localhost ([127.0.0.1]:37898 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Pwgsk-0004sw-PG for incoming@patchwork.ozlabs.org; Mon, 07 Mar 2011 15:18:18 -0500 Received: from [140.186.70.92] (port=41634 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Pwgm8-0001lT-AQ for qemu-devel@nongnu.org; Mon, 07 Mar 2011 15:11:31 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Pwgls-0006ST-Bj for qemu-devel@nongnu.org; Mon, 07 Mar 2011 15:11:27 -0500 Received: from e6.ny.us.ibm.com ([32.97.182.146]:52982) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Pwgls-0006SB-7V for qemu-devel@nongnu.org; Mon, 07 Mar 2011 15:11:12 -0500 Received: from d01dlp02.pok.ibm.com (d01dlp02.pok.ibm.com [9.56.224.85]) by e6.ny.us.ibm.com (8.14.4/8.13.1) with ESMTP id p27JkqLU030559 for ; Mon, 7 Mar 2011 14:46:52 -0500 Received: from d01relay05.pok.ibm.com (d01relay05.pok.ibm.com [9.56.227.237]) by d01dlp02.pok.ibm.com (Postfix) with ESMTP id A52CD6E8039 for ; Mon, 7 Mar 2011 15:11:09 -0500 (EST) Received: from d01av01.pok.ibm.com (d01av01.pok.ibm.com [9.56.224.215]) by d01relay05.pok.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id p27KB9CE226004 for ; Mon, 7 Mar 2011 15:11:09 -0500 Received: from d01av01.pok.ibm.com (loopback [127.0.0.1]) by d01av01.pok.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id p27KB86x012445 for ; Mon, 7 Mar 2011 15:11:09 -0500 Received: from localhost.localdomain (sig-9-76-30-5.mts.ibm.com [9.76.30.5]) by d01av01.pok.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id p27KAi8H010005; Mon, 7 Mar 2011 15:11:07 -0500 From: Michael Roth To: qemu-devel@nongnu.org Date: Mon, 7 Mar 2011 14:10:33 -0600 Message-Id: <1299528642-23631-8-git-send-email-mdroth@linux.vnet.ibm.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: <1299528642-23631-1-git-send-email-mdroth@linux.vnet.ibm.com> References: <1299528642-23631-1-git-send-email-mdroth@linux.vnet.ibm.com> X-Content-Scanned: Fidelis XPS MAILER X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6, seldom 2.4 (older, 4) X-Received-From: 32.97.182.146 Cc: agl@linux.vnet.ibm.com, stefanha@linux.vnet.ibm.com, Jes.Sorensen@redhat.com, mdroth@linux.vnet.ibm.com, markus_mueller@de.ibm.com, aliguori@linux.vnet.ibm.com, abeekhof@redhat.com Subject: [Qemu-devel] [RFC][PATCH v7 07/16] virtagent: base RPC client definitions X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Signed-off-by: Michael Roth --- monitor.c | 1 + qerror.c | 8 + qerror.h | 6 + virtagent.c | 455 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ virtagent.h | 46 ++++++ 5 files changed, 516 insertions(+), 0 deletions(-) create mode 100644 virtagent.c create mode 100644 virtagent.h diff --git a/monitor.c b/monitor.c index 22ae3bb..44f5033 100644 --- a/monitor.c +++ b/monitor.c @@ -57,6 +57,7 @@ #include "json-parser.h" #include "osdep.h" #include "exec-all.h" +#include "virtagent.h" #ifdef CONFIG_SIMPLE_TRACE #include "trace.h" #endif diff --git a/qerror.c b/qerror.c index 4855604..741e0bc 100644 --- a/qerror.c +++ b/qerror.c @@ -209,6 +209,14 @@ static const QErrorStringTable qerror_table[] = { .error_fmt = QERR_VNC_SERVER_FAILED, .desc = "Could not start VNC server on %(target)", }, + { + .error_fmt = QERR_RPC_FAILED, + .desc = "An RPC error has occurred: %(message)", + }, + { + .error_fmt = QERR_VA_FAILED, + .desc = "An error was reported by virtagent: %(message)", + }, {} }; diff --git a/qerror.h b/qerror.h index f732d45..f3322e7 100644 --- a/qerror.h +++ b/qerror.h @@ -171,4 +171,10 @@ QError *qobject_to_qerror(const QObject *obj); #define QERR_VNC_SERVER_FAILED \ "{ 'class': 'VNCServerFailed', 'data': { 'target': %s } }" +#define QERR_RPC_FAILED \ + "{ 'class': 'RPCFailed', 'data': { 'code': %i, 'message': %s } }" + +#define QERR_VA_FAILED \ + "{ 'class': 'VirtagentFailed', 'data': { 'code': %i, 'message': %s } }" + #endif /* QERROR_H */ diff --git a/virtagent.c b/virtagent.c new file mode 100644 index 0000000..670309b --- /dev/null +++ b/virtagent.c @@ -0,0 +1,455 @@ +/* + * virtagent - host/guest RPC client functions + * + * Copyright IBM Corp. 2010 + * + * Authors: + * Adam Litke + * Michael Roth + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu_socket.h" +#include "qjson.h" +#include "qint.h" +#include "monitor.h" +#include "virtagent-common.h" + +static VAClientData *va_client_data; + +static void va_set_capabilities(QList *qlist) +{ + TRACE("called"); + + if (va_client_data == NULL) { + LOG("client is uninitialized, unable to set capabilities"); + return; + } + + if (va_client_data->supported_methods != NULL) { + qobject_decref(QOBJECT(va_client_data->supported_methods)); + va_client_data->supported_methods = NULL; + TRACE("capabilities reset"); + } + + if (qlist != NULL) { + va_client_data->supported_methods = qlist_copy(qlist); + TRACE("capabilities set"); + } +} + +static void va_set_version_level(const char *version) { + if (version) { + pstrcpy(va_client_data->guest_version, 32, version); + } +} + +typedef struct VACmpState { + const char *method; + bool found; +} VACmpState; + +static void va_cmp_capability_iter(QObject *obj, void *opaque) +{ + QString *method = qobject_to_qstring(obj); + const char *method_str = NULL; + VACmpState *cmp_state = opaque; + + if (method) { + method_str = qstring_get_str(method); + } + + if (method_str && opaque) { + if (strcmp(method_str, cmp_state->method) == 0) { + cmp_state->found = 1; + } + } +} + +static bool va_has_capability(const char *method) +{ + VACmpState cmp_state; + + if (method == NULL) { + return false; + } + + /* we can assume capabilities is available */ + if (strcmp(method, "capabilities") == 0) { + return true; + } + /* assume hello is available to we can probe for/notify the host + * rpc server + */ + if (strcmp(method, "hello") == 0) { + return true; + } + + /* compare method against the last retrieved supported method list */ + cmp_state.method = method; + cmp_state.found = false; + if (va_client_data->supported_methods) { + qlist_iter(va_client_data->supported_methods, + va_cmp_capability_iter, + (void *)&cmp_state); + } + + return cmp_state.found; +} + +int va_client_init(VAManager *m, VAClientData *client_data) +{ + client_data->supported_methods = NULL; + client_data->enabled = true; + client_data->manager = m; + va_client_data = client_data; + + return 0; +} + +static bool va_is_enabled(void) +{ + return va_client_data && va_client_data->enabled; +} + +typedef struct VAClientRequest { + QString *payload; + char tag[64]; + VAClientCallback *cb; + /* for use by QMP functions */ + MonitorCompletion *mon_cb; + void *mon_data; + int timeout; + QEMUTimer *timer; +} VAClientRequest; + +typedef struct VAClientResponse { + char *content; + size_t content_len; +} VAClientResponse; + +static void va_client_timeout(void *opaque) +{ + VAClientRequest *req = opaque; + qemu_del_timer(req->timer); + req->timer = NULL; + va_client_job_cancel(va_client_data->manager, req->tag); +} + +/* called by xport layer to indicate send completion to VAManager */ +static void va_send_request_cb(const void *opaque) +{ + const char *tag = opaque; + va_client_job_send_done(va_client_data->manager, tag); +} + +/* called by VAManager to start send, in turn calls out to xport layer */ +static int va_send_request(void *opaque, const char *tag) +{ + VAClientRequest *req = opaque; + const char *payload_json; + int ret; + + TRACE("called"); + if (!req || !req->payload) { + TRACE("marker"); + return -EINVAL; + } + payload_json = qstring_get_str(req->payload); + if (!payload_json) { + TRACE("marker"); + return -EINVAL; + } + TRACE("marker"); + ret = va_xport_send_request(payload_json, strlen(payload_json), + tag, tag, va_send_request_cb); + TRACE("marker"); + /* register timeout */ + if (req->timeout) { + TRACE("marker"); + req->timer = qemu_new_timer(rt_clock, va_client_timeout, req); + qemu_mod_timer(req->timer, qemu_get_clock(rt_clock) + req->timeout); + } + TRACE("marker"); + return ret; +} + +/* called by xport layer to pass response to VAManager */ +void va_client_read_response_done(const char *content, size_t content_len, const char *tag) +{ + QDict *resp = NULL; + QObject *resp_qobject; + + resp_qobject = qobject_from_json(content); + if (resp_qobject) { + resp = qobject_to_qdict(resp_qobject); + } + va_client_job_read_done(va_client_data->manager, tag, resp); +} + +/* called by VAManager once RPC response is recieved */ +static int va_callback(void *opaque, void *resp_opaque, const char *tag) +{ + VAClientRequest *req = opaque; + QDict *resp = resp_opaque; + + TRACE("called"); + + if (req->timer) { + qemu_del_timer(req->timer); + } + + if (req->cb) { + if (resp) { + req->cb(resp, req->mon_cb, req->mon_data); + } else { + /* RPC did not complete */ + req->cb(NULL, req->mon_cb, req->mon_data); + } + } + + if (req) { + if (req->payload) { + QDECREF(req->payload); + } + qemu_free(req); + } + + if (resp) { + QDECREF(resp); + } + + return 0; +} + +static VAClientJobOps client_job_ops = { + .send = va_send_request, + .callback = va_callback, +}; + +static int va_do_rpc(const char *method, const QDict *params, + VAClientCallback *cb, MonitorCompletion *mon_cb, + void *mon_data) +{ + VAClientRequest *req; + QDict *payload, *params_copy = NULL; + QString *payload_json; + struct timeval ts; + int ret; + + if (!va_is_enabled()) { + LOG("virtagent not initialized"); + ret = -ENOTCONN; + } + + if (!va_has_capability(method)) { + LOG("guest agent does not have required capability: %s", method); + ret = -ENOTSUP; + goto out; + } + + req = qemu_mallocz(sizeof(VAClientRequest)); + req->cb = cb; + req->mon_cb = mon_cb; + req->mon_data = mon_data; + req->timeout = VA_CLIENT_TIMEOUT_MS; + + /* add params and remote RPC method to call to payload */ + payload = qdict_new(); + qdict_put_obj(payload, "method", + QOBJECT(qstring_from_str(method))); + if (params) { + params_copy = va_qdict_copy(params); + if (!params_copy) { + LOG("error processing parameters"); + QDECREF(payload); + ret = -EINVAL; + goto out_free; + } + qdict_put_obj(payload, "params", QOBJECT(params_copy)); + } + + /* convert payload to json */ + payload_json = qobject_to_json(QOBJECT(payload)); + QDECREF(payload); + if (!payload_json) { + LOG("error converting request to json"); + ret = -EINVAL; + goto out_free; + } + req->payload = payload_json; + + /* TODO: should switch to UUIDs eventually */ + memset(req->tag, 0, 64); + gettimeofday(&ts, NULL); + sprintf(req->tag, "%u.%u", (uint32_t)ts.tv_sec, (uint32_t)ts.tv_usec); + TRACE("req->payload: %p, req->cb: %p, req->mon_cb: %p, req->mon_data: %p", + req->payload, req->cb, req->mon_cb, req->mon_data); + + ret = va_client_job_add(va_client_data->manager, req->tag, req, + client_job_ops); + if (ret) { + TRACE("marker"); + va_client_job_cancel(va_client_data->manager, req->tag); + goto out_free; + } + +out: + return ret; +out_free: + qemu_free(req); + return ret; +} + +/* validate the RPC response. if response indicates an error, log it + * to stderr/monitor. if return_data != NULL, return_data will be set + * to the response payload of the RPC if present, otherwise an error + * will be logged. if return_data == NULL, response payload is ignored, + * and only the RPC's error indicator is checked for success. + * + * XXX: The JSON that generates the response may originate from untrusted + * sources such as an unsupported/malicious guest agent, so we must take + * particular care to not make any assumptions about what the response + * contains. In particular, always check for key existence, and no blind + * qdict_get_() calls since the value may be an unexpected type. This + * also applies to the return_data we pass back to callers. + */ +static bool va_check_response_ok(QDict *resp, QDict **return_data) +{ + int errnum; + const char *errstr = NULL; + + TRACE("called"); + /* TODO: not sure if errnum is of much use here */ + if (!resp) { + errnum = ENOMSG; + errstr = "response is null"; + goto out_bad; + } + + if (va_qdict_haskey_with_type(resp, "errnum", QTYPE_QINT)) { + errnum = qdict_get_int(resp, "errnum"); + if (errnum) { + if (va_qdict_haskey_with_type(resp, "errstr", QTYPE_QSTRING)) { + errstr = qdict_get_str(resp, "errstr"); + } + goto out_bad; + } + } else { + errnum = EINVAL; + errstr = "response is missing error code"; + goto out_bad; + } + + if (return_data) { + if (va_qdict_haskey_with_type(resp, "return_data", QTYPE_QDICT)) { + TRACE("marker"); + *return_data = qdict_get_qdict(resp, "return_data"); + } else { + errnum = EINVAL; + errstr = "response indicates success, but missing expected retval"; + goto out_bad; + } + } + + return true; +out_bad: + qerror_report(QERR_RPC_FAILED, errnum, errstr); + return false; +} + +/* QMP/HMP RPC client functions and their helpers */ + +static void va_print_capability_iter(QObject *obj, void *opaque) +{ + Monitor *mon = opaque; + QString *function = qobject_to_qstring(obj); + const char *function_str; + + if (function) { + function_str = qstring_get_str(function); + monitor_printf(mon, "%s\n", function_str); + } +} + +void do_va_capabilities_print(Monitor *mon, const QObject *data) +{ + QDict *ret = qobject_to_qdict(data); + + TRACE("called"); + if (!data) { + return; + } + + monitor_printf(mon, + "guest agent version: %s\n" + "supported methods:\n", qdict_get_str(ret, "version")); + qlist_iter(qdict_get_qlist(ret, "methods"), va_print_capability_iter, mon); +} + +static void do_va_capabilities_cb(QDict *resp, + MonitorCompletion *mon_cb, + void *mon_data) +{ + QDict *ret = NULL; + QObject *ret_qobject = NULL; + + TRACE("called"); + if (!va_check_response_ok(resp, &ret)) { + goto out; + } + + if (!va_qdict_haskey_with_type(ret, "methods", QTYPE_QLIST) || + !va_qdict_haskey_with_type(ret, "version", QTYPE_QSTRING)) { + qerror_report(QERR_VA_FAILED, -EINVAL, + "response does not contain required fields"); + goto out; + } + va_set_capabilities(qdict_get_qlist(ret, "methods")); + va_set_version_level(qdict_get_str(ret, "version")); + ret_qobject = QOBJECT(ret); +out: + if (mon_cb) { + mon_cb(mon_data, ret_qobject); + } +} + +/* + * do_va_capabilities(): Fetch/re-negotiate guest agent capabilities + */ +int do_va_capabilities(Monitor *mon, const QDict *params, + MonitorCompletion cb, void *opaque) +{ + int ret = va_do_rpc("capabilities", params, do_va_capabilities_cb, cb, + opaque); + if (ret) { + qerror_report(QERR_VA_FAILED, ret, strerror(-ret)); + } + return ret; +} + +/* RPC client functions called outside of HMP/QMP */ + +int va_client_init_capabilities(void) +{ + int ret = va_do_rpc("capabilities", NULL, do_va_capabilities_cb, NULL, + NULL); + if (ret) { + LOG("erroring negotiating capabilities: %s", strerror(-ret)); + } + + return 0; +} + +int va_send_hello(void) +{ + int ret = va_do_rpc("hello", NULL, NULL, NULL, NULL); + if (ret) { + LOG("error sending start up notification to host: %s", + strerror(-ret)); + } + return ret; +} diff --git a/virtagent.h b/virtagent.h new file mode 100644 index 0000000..1652fdc --- /dev/null +++ b/virtagent.h @@ -0,0 +1,46 @@ +/* + * virt-agent - host/guest RPC client functions + * + * Copyright IBM Corp. 2010 + * + * Authors: + * Adam Litke + * Michael Roth + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef VIRTAGENT_H +#define VIRTAGENT_H + +#include "monitor.h" +#include "virtagent-manager.h" + +#define VA_GUEST_PATH_VIRTIO_DEFAULT "/dev/virtio-ports/org.qemu.virtagent" +#define VA_HOST_PATH_DEFAULT "/tmp/virtagent.sock" +#define VA_MAX_CHUNK_SIZE 4096 /* max bytes at a time for get/send file */ + +typedef void (VAClientCallback)(QDict *resp, + MonitorCompletion *mon_cb, void *mon_data); +typedef struct VAClientData { + QList *supported_methods; + char guest_version[32]; + bool enabled; + VAManager *manager; +} VAClientData; + +int va_client_init(VAManager *m, VAClientData *client_data); +int va_client_close(void); +void va_client_read_response_done(const char *content, size_t content_len, + const char *tag); +int va_client_init_capabilities(void); +int va_send_hello(void); +int do_agent_shutdown(Monitor *mon, const QDict *mon_params, + MonitorCompletion cb, void *opaque); +void do_va_capabilities_print(Monitor *mon, const QObject *qobject); +int do_va_capabilities(Monitor *mon, const QDict *mon_params, + MonitorCompletion cb, void *opaque); + +#endif /* VIRTAGENT_H */