Signed-off-by: Michael Roth <mdr...@linux.vnet.ibm.com> --- virtagent-common.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++ virtagent-common.h | 95 ++++++++++++++++++++++++ 2 files changed, 301 insertions(+), 0 deletions(-) create mode 100644 virtagent-common.c create mode 100644 virtagent-common.h
diff --git a/virtagent-common.c b/virtagent-common.c new file mode 100644 index 0000000..4b13ee8 --- /dev/null +++ b/virtagent-common.c @@ -0,0 +1,206 @@ +/* + * virtagent - common host/guest functions + * + * Copyright IBM Corp. 2010 + * + * Authors: + * Adam Litke <agli...@linux.vnet.ibm.com> + * Michael Roth <mdr...@linux.vnet.ibm.com> + * + * 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 "virtagent-common.h" + +VAState *va_state; + +/* helper to avoid tedious key/type checking on QDict entries */ +bool va_qdict_haskey_with_type(const QDict *qdict, const char *key, + qtype_code type) +{ + QObject *qobj; + if (!qdict) { + return false; + } + if (!qdict_haskey(qdict, key)) { + return false; + } + qobj = qdict_get(qdict, key); + if (qobject_type(qobj) != type) { + return false; + } + + return true; +} + +static void va_qdict_insert(const char *key, QObject *entry, void *opaque) +{ + QDict *dict = opaque; + + if (key && entry) { + qdict_put_obj(dict, key, entry); + } +} + +QDict *va_qdict_copy(const QDict *old) +{ + QDict *new; + + if (!old) { + return NULL; + } + + new = qdict_new(); + qdict_iter(old, va_qdict_insert, new); + + return new; +} + +static int va_connect(void) +{ + QemuOpts *opts; + int fd, ret = 0; + + TRACE("called"); + if (va_state->channel_method == NULL) { + LOG("no channel method specified"); + return -EINVAL; + } + if (va_state->channel_path == NULL) { + LOG("no channel path specified"); + return -EINVAL; + } + + if (strcmp(va_state->channel_method, "unix-connect") == 0) { + TRACE("connecting to %s", va_state->channel_path); + opts = qemu_opts_create(qemu_find_opts("chardev"), NULL, 0); + qemu_opt_set(opts, "path", va_state->channel_path); + fd = unix_connect_opts(opts); + if (fd == -1) { + qemu_opts_del(opts); + LOG("error opening channel: %s", strerror(errno)); + return -errno; + } + qemu_opts_del(opts); + socket_set_nonblock(fd); + } else if (strcmp(va_state->channel_method, "virtio-serial") == 0) { + if (va_state->is_host) { + LOG("specified channel method not available for host"); + return -EINVAL; + } + if (va_state->channel_path == NULL) { + va_state->channel_path = VA_GUEST_PATH_VIRTIO_DEFAULT; + } + TRACE("opening %s", va_state->channel_path); + fd = qemu_open(va_state->channel_path, O_RDWR); + if (fd == -1) { + LOG("error opening channel: %s", strerror(errno)); + return -errno; + } + ret = fcntl(fd, F_GETFL); + if (ret < 0) { + LOG("error getting channel flags: %s", strerror(errno)); + return -errno; + } + ret = fcntl(fd, F_SETFL, ret | O_ASYNC | O_NONBLOCK); + if (ret < 0) { + LOG("error setting channel flags: %s", strerror(errno)); + return -errno; + } + } else if (strcmp(va_state->channel_method, "isa-serial") == 0) { + struct termios tio; + if (va_state->is_host) { + LOG("specified channel method not available for host"); + return -EINVAL; + } + if (va_state->channel_path == NULL) { + LOG("you must specify the path of the serial device to use"); + return -EINVAL; + } + TRACE("opening %s", va_state->channel_path); + fd = qemu_open(va_state->channel_path, O_RDWR | O_NOCTTY); + if (fd == -1) { + LOG("error opening channel: %s", strerror(errno)); + return -errno; + } + tcgetattr(fd, &tio); + /* set up serial port for non-canonical, dumb byte streaming */ + tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | + INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | IMAXBEL); + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cflag |= VA_BAUDRATE; + /* 1 available byte min, else reads will block (we'll set non-blocking + * elsewhere, else we'd have to deal with read()=0 instead) + */ + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + /* flush everything waiting for read/xmit, it's garbage at this point */ + tcflush(fd, TCIFLUSH); + tcsetattr(fd, TCSANOW, &tio); + } else { + LOG("invalid channel method"); + return -EINVAL; + } + + va_state->fd = fd; + return 0; +} + +int va_init(VAContext ctx) +{ + VAState *s; + VAManager *m; + int ret; + + TRACE("called"); + if (va_state) { + LOG("virtagent already initialized"); + return -EPERM; + } + + s = qemu_mallocz(sizeof(VAState)); + m = va_manager_new(); + + ret = va_server_init(m, &s->server_data, ctx.is_host); + if (ret) { + LOG("error initializing virtagent server"); + goto out_bad; + } + ret = va_client_init(m, &s->client_data); + if (ret) { + LOG("error initializing virtagent client"); + goto out_bad; + } + + s->client_job_count = 0; + s->client_jobs_in_flight = 0; + s->server_job_count = 0; + s->channel_method = ctx.channel_method; + s->channel_path = ctx.channel_path; + s->is_host = ctx.is_host; + s->manager = m; + va_state = s; + + /* connect to our end of the channel */ + ret = va_connect(); + if (ret) { + LOG("error connecting to channel"); + goto out_bad; + } + + /* start listening for requests/responses */ + qemu_set_fd_handler(va_state->fd, va_http_read_handler, NULL, NULL); + + if (!va_state->is_host) { + /* tell the host the agent is running */ + va_send_hello(); + } + + return 0; +out_bad: + qemu_free(s); + return ret; +} diff --git a/virtagent-common.h b/virtagent-common.h new file mode 100644 index 0000000..5ae50d1 --- /dev/null +++ b/virtagent-common.h @@ -0,0 +1,95 @@ +/* + * virt-agent - host/guest RPC client functions + * + * Copyright IBM Corp. 2010 + * + * Authors: + * Adam Litke <agli...@linux.vnet.ibm.com> + * Michael Roth <mdr...@linux.vnet.ibm.com> + * + * 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_COMMON_H +#define VIRTAGENT_COMMON_H + +#include <termios.h> +#include "qemu-common.h" +#include "qemu_socket.h" +#include "qemu-timer.h" +#include "monitor.h" +#include "virtagent-manager.h" +#include "virtagent-server.h" +#include "virtagent.h" + +#define DEBUG_VA + +#ifdef DEBUG_VA +#define TRACE(msg, ...) do { \ + fprintf(stderr, "%s:%s():L%d: " msg "\n", \ + __FILE__, __FUNCTION__, __LINE__, ## __VA_ARGS__); \ +} while(0) +#else +#define TRACE(msg, ...) \ + do { } while (0) +#endif + +#define LOG(msg, ...) do { \ + fprintf(stderr, "%s:%s(): " msg "\n", \ + __FILE__, __FUNCTION__, ## __VA_ARGS__); \ +} while(0) + +#define VA_VERSION "1.0" +#define EOL "\r\n" + +#define VA_PIDFILE "/var/run/qemu-va.pid" +#define VA_HDR_LEN_MAX 4096 /* http header limit */ +#define VA_CONTENT_LEN_MAX 2*1024*1024 /* rpc/http send limit */ +#define VA_CLIENT_JOBS_MAX 5 /* max client rpcs we can queue */ +#define VA_SERVER_JOBS_MAX 5 /* max server rpcs we can queue */ +#define VA_SERVER_TIMEOUT_MS 5 * 1000 +#define VA_CLIENT_TIMEOUT_MS 5 * 1000 +#define VA_SENTINEL 0xFF +#define VA_BAUDRATE B38400 /* for isa-serial channels */ + +typedef struct VAContext { + bool is_host; + const char *channel_method; + const char *channel_path; +} VAContext; + +typedef struct VAState { + bool is_host; + const char *channel_method; + const char *channel_path; + int fd; + QEMUTimer *client_timer; + QEMUTimer *server_timer; + VAClientData client_data; + VAServerData server_data; + int client_job_count; + int client_jobs_in_flight; + int server_job_count; + VAManager *manager; +} VAState; + +enum va_job_status { + VA_JOB_STATUS_PENDING = 0, + VA_JOB_STATUS_OK, + VA_JOB_STATUS_ERROR, + VA_JOB_STATUS_CANCELLED, +}; + +typedef void (VAHTSendCallback)(const void *opaque); + +int va_init(VAContext ctx); +bool va_qdict_haskey_with_type(const QDict *qdict, const char *key, + qtype_code type); +QDict *va_qdict_copy(const QDict *old); +int va_xport_send_response(const char *content, size_t content_len, const char *tag, + const void *opaque, VAHTSendCallback cb); +int va_xport_send_request(const char *content, size_t content_len, const char *tag, + const void *opaque, VAHTSendCallback cb); +void va_http_read_handler(void *opaque); +#endif /* VIRTAGENT_COMMON_H */ -- 1.7.0.4