762 lines
15 KiB
C
762 lines
15 KiB
C
/*
|
|
* Copyright (C) 2014 Martin Willi
|
|
* Copyright (C) 2014 revosec AG
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program 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 General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include "libvici.h"
|
|
#include "vici_builder.h"
|
|
#include "vici_dispatcher.h"
|
|
#include "vici_socket.h"
|
|
|
|
#include <library.h>
|
|
#include <threading/mutex.h>
|
|
#include <threading/condvar.h>
|
|
#include <collections/hashtable.h>
|
|
|
|
#include <errno.h>
|
|
|
|
/**
|
|
* Event registration
|
|
*/
|
|
typedef struct {
|
|
/** name of event */
|
|
char *name;
|
|
/** callback function */
|
|
vici_event_cb_t cb;
|
|
/** user data for callback */
|
|
void *user;
|
|
} event_t;
|
|
|
|
/**
|
|
* Wait state signaled by asynchronous on_read callback
|
|
*/
|
|
typedef enum {
|
|
WAIT_IDLE = 0,
|
|
WAIT_SUCCESS,
|
|
WAIT_FAILURE,
|
|
WAIT_READ_ERROR,
|
|
} wait_state_t;
|
|
|
|
/**
|
|
* Private vici connection contex.
|
|
*/
|
|
struct vici_conn_t {
|
|
/** connection stream */
|
|
stream_t *stream;
|
|
/** event registrations, as char* => event_t */
|
|
hashtable_t *events;
|
|
/** connection lock */
|
|
mutex_t *mutex;
|
|
/** condvar to signal incoming response */
|
|
condvar_t *cond;
|
|
/** queued response message */
|
|
chunk_t queue;
|
|
/** asynchronous read error */
|
|
int error;
|
|
/** wait state */
|
|
wait_state_t wait;
|
|
};
|
|
|
|
/**
|
|
* Private vici request message.
|
|
*/
|
|
struct vici_req_t {
|
|
/** connection context */
|
|
vici_conn_t *conn;
|
|
/** name of request message */
|
|
char *name;
|
|
/** message builder */
|
|
vici_builder_t *b;
|
|
};
|
|
|
|
/**
|
|
* Private vici response/event message.
|
|
*/
|
|
struct vici_res_t {
|
|
/** response message */
|
|
vici_message_t *message;
|
|
/** allocated strings */
|
|
linked_list_t *strings;
|
|
/** item enumerator */
|
|
enumerator_t *enumerator;
|
|
/** currently enumerating type */
|
|
vici_type_t type;
|
|
/** currently enumerating name */
|
|
char *name;
|
|
/** currently enumerating value */
|
|
chunk_t value;
|
|
/** section nesting level of callback parser */
|
|
int level;
|
|
};
|
|
|
|
/**
|
|
* Signal wait result for waiting user thread
|
|
*/
|
|
static bool wait_result(vici_conn_t *conn, wait_state_t wait)
|
|
{
|
|
conn->mutex->lock(conn->mutex);
|
|
conn->wait = wait;
|
|
conn->mutex->unlock(conn->mutex);
|
|
conn->cond->signal(conn->cond);
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Signal wait error result for waiting user thread
|
|
*/
|
|
static bool read_error(vici_conn_t *conn, int err)
|
|
{
|
|
conn->error = err;
|
|
return wait_result(conn, WAIT_READ_ERROR);
|
|
}
|
|
|
|
/**
|
|
* Handle a command response message
|
|
*/
|
|
static bool handle_response(vici_conn_t *conn, uint32_t len)
|
|
{
|
|
chunk_t buf;
|
|
|
|
buf = chunk_alloc(len);
|
|
if (!conn->stream->read_all(conn->stream, buf.ptr, buf.len))
|
|
{
|
|
free(buf.ptr);
|
|
return read_error(conn, errno);
|
|
}
|
|
conn->queue = buf;
|
|
return wait_result(conn, WAIT_SUCCESS);
|
|
}
|
|
|
|
/**
|
|
* Dispatch received event message
|
|
*/
|
|
static bool handle_event(vici_conn_t *conn, uint32_t len)
|
|
{
|
|
vici_message_t *message;
|
|
event_t *event;
|
|
uint8_t namelen;
|
|
char name[257], *buf;
|
|
|
|
if (len < sizeof(namelen))
|
|
{
|
|
return read_error(conn, EBADMSG);
|
|
}
|
|
if (!conn->stream->read_all(conn->stream, &namelen, sizeof(namelen)))
|
|
{
|
|
return read_error(conn, errno);
|
|
}
|
|
if (namelen > len - sizeof(namelen))
|
|
{
|
|
return read_error(conn, EBADMSG);
|
|
}
|
|
if (!conn->stream->read_all(conn->stream, name, namelen))
|
|
{
|
|
return read_error(conn, errno);
|
|
}
|
|
name[namelen] = '\0';
|
|
len -= sizeof(namelen) + namelen;
|
|
buf = malloc(len);
|
|
if (!conn->stream->read_all(conn->stream, buf, len))
|
|
{
|
|
free(buf);
|
|
return read_error(conn, errno);
|
|
}
|
|
message = vici_message_create_from_data(chunk_create(buf, len), TRUE);
|
|
|
|
conn->mutex->lock(conn->mutex);
|
|
event = conn->events->get(conn->events, name);
|
|
if (event)
|
|
{
|
|
vici_res_t res = {
|
|
.message = message,
|
|
.enumerator = message->create_enumerator(message),
|
|
.strings = linked_list_create(),
|
|
};
|
|
|
|
event->cb(event->user, name, &res);
|
|
|
|
res.enumerator->destroy(res.enumerator);
|
|
res.strings->destroy_function(res.strings, free);
|
|
}
|
|
conn->mutex->unlock(conn->mutex);
|
|
|
|
message->destroy(message);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CALLBACK(on_read, bool,
|
|
vici_conn_t *conn, stream_t *stream)
|
|
{
|
|
uint32_t len;
|
|
uint8_t op;
|
|
ssize_t hlen;
|
|
|
|
hlen = stream->read(stream, &len, sizeof(len), FALSE);
|
|
if (hlen <= 0)
|
|
{
|
|
if (errno == EWOULDBLOCK)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return read_error(conn, errno);
|
|
}
|
|
if (hlen < sizeof(len))
|
|
{
|
|
if (!stream->read_all(stream, ((void*)&len) + hlen, sizeof(len) - hlen))
|
|
{
|
|
return read_error(conn, errno);
|
|
}
|
|
}
|
|
|
|
len = ntohl(len);
|
|
if (len > VICI_MESSAGE_SIZE_MAX)
|
|
{
|
|
return read_error(conn, EBADMSG);
|
|
}
|
|
if (len-- < sizeof(op))
|
|
{
|
|
return read_error(conn, EBADMSG);
|
|
}
|
|
if (!stream->read_all(stream, &op, sizeof(op)))
|
|
{
|
|
return read_error(conn, errno);
|
|
}
|
|
switch (op)
|
|
{
|
|
case VICI_EVENT:
|
|
return handle_event(conn, len);
|
|
case VICI_CMD_RESPONSE:
|
|
return handle_response(conn, len);
|
|
case VICI_EVENT_CONFIRM:
|
|
return wait_result(conn, WAIT_SUCCESS);
|
|
case VICI_CMD_UNKNOWN:
|
|
case VICI_EVENT_UNKNOWN:
|
|
return wait_result(conn, WAIT_FAILURE);
|
|
case VICI_CMD_REQUEST:
|
|
case VICI_EVENT_REGISTER:
|
|
case VICI_EVENT_UNREGISTER:
|
|
default:
|
|
return read_error(conn, EBADMSG);
|
|
}
|
|
}
|
|
|
|
vici_conn_t* vici_connect(char *uri)
|
|
{
|
|
vici_conn_t *conn;
|
|
stream_t *stream;
|
|
|
|
stream = lib->streams->connect(lib->streams, uri ?: VICI_DEFAULT_URI);
|
|
if (!stream)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
INIT(conn,
|
|
.stream = stream,
|
|
.events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1),
|
|
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
|
.cond = condvar_create(CONDVAR_TYPE_DEFAULT),
|
|
);
|
|
|
|
stream->on_read(stream, on_read, conn);
|
|
|
|
return conn;
|
|
}
|
|
|
|
void vici_disconnect(vici_conn_t *conn)
|
|
{
|
|
enumerator_t *enumerator;
|
|
event_t *event;
|
|
|
|
conn->stream->destroy(conn->stream);
|
|
enumerator = conn->events->create_enumerator(conn->events);
|
|
while (enumerator->enumerate(enumerator, NULL, &event))
|
|
{
|
|
free(event->name);
|
|
free(event);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
conn->events->destroy(conn->events);
|
|
conn->mutex->destroy(conn->mutex);
|
|
conn->cond->destroy(conn->cond);
|
|
free(conn);
|
|
}
|
|
|
|
vici_req_t* vici_begin(char *name)
|
|
{
|
|
vici_req_t *req;
|
|
|
|
INIT(req,
|
|
.name = strdup(name),
|
|
.b = vici_builder_create(),
|
|
);
|
|
|
|
return req;
|
|
}
|
|
|
|
void vici_begin_section(vici_req_t *req, char *name)
|
|
{
|
|
req->b->add(req->b, VICI_SECTION_START, name);
|
|
}
|
|
|
|
void vici_end_section(vici_req_t *req)
|
|
{
|
|
req->b->add(req->b, VICI_SECTION_END);
|
|
}
|
|
|
|
void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len)
|
|
{
|
|
req->b->add(req->b, VICI_KEY_VALUE, key, chunk_create(buf, len));
|
|
}
|
|
|
|
void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
req->b->vadd_kv(req->b, key, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void vici_begin_list(vici_req_t *req, char *name)
|
|
{
|
|
req->b->add(req->b, VICI_LIST_START, name);
|
|
}
|
|
|
|
void vici_add_list_item(vici_req_t *req, void *buf, int len)
|
|
{
|
|
req->b->add(req->b, VICI_LIST_ITEM, chunk_create(buf, len));
|
|
}
|
|
|
|
void vici_add_list_itemf(vici_req_t *req, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
req->b->vadd_li(req->b, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void vici_end_list(vici_req_t *req)
|
|
{
|
|
req->b->add(req->b, VICI_LIST_END);
|
|
}
|
|
|
|
vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn)
|
|
{
|
|
vici_message_t *message;
|
|
vici_res_t *res;
|
|
chunk_t data;
|
|
uint32_t len;
|
|
uint8_t namelen, op;
|
|
|
|
message = req->b->finalize(req->b);
|
|
if (!message)
|
|
{
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
op = VICI_CMD_REQUEST;
|
|
namelen = strlen(req->name);
|
|
data = message->get_encoding(message);
|
|
len = htonl(sizeof(op) + sizeof(namelen) + namelen + data.len);
|
|
|
|
if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) ||
|
|
!conn->stream->write_all(conn->stream, &op, sizeof(op)) ||
|
|
!conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) ||
|
|
!conn->stream->write_all(conn->stream, req->name, namelen) ||
|
|
!conn->stream->write_all(conn->stream, data.ptr, data.len))
|
|
{
|
|
free(req->name);
|
|
free(req);
|
|
message->destroy(message);
|
|
return NULL;
|
|
}
|
|
free(req->name);
|
|
free(req);
|
|
message->destroy(message);
|
|
|
|
message = NULL;
|
|
conn->mutex->lock(conn->mutex);
|
|
while (conn->wait == WAIT_IDLE)
|
|
{
|
|
conn->cond->wait(conn->cond, conn->mutex);
|
|
}
|
|
switch (conn->wait)
|
|
{
|
|
case WAIT_SUCCESS:
|
|
message = vici_message_create_from_data(conn->queue, TRUE);
|
|
conn->queue = chunk_empty;
|
|
break;
|
|
case WAIT_READ_ERROR:
|
|
errno = conn->error;
|
|
break;
|
|
case WAIT_FAILURE:
|
|
default:
|
|
errno = ENOENT;
|
|
break;
|
|
}
|
|
conn->wait = WAIT_IDLE;
|
|
conn->mutex->unlock(conn->mutex);
|
|
|
|
conn->stream->on_read(conn->stream, on_read, conn);
|
|
|
|
if (message)
|
|
{
|
|
INIT(res,
|
|
.message = message,
|
|
.enumerator = message->create_enumerator(message),
|
|
.strings = linked_list_create(),
|
|
);
|
|
return res;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void vici_free_req(vici_req_t *req)
|
|
{
|
|
free(req->name);
|
|
req->b->destroy(req->b);
|
|
free(req);
|
|
}
|
|
|
|
int vici_dump(vici_res_t *res, char *label, int pretty, FILE *out)
|
|
{
|
|
if (res->message->dump(res->message, label, pretty, out))
|
|
{
|
|
return 0;
|
|
}
|
|
errno = EBADMSG;
|
|
return 1;
|
|
}
|
|
|
|
vici_parse_t vici_parse(vici_res_t *res)
|
|
{
|
|
if (!res->enumerator->enumerate(res->enumerator,
|
|
&res->type, &res->name, &res->value))
|
|
{
|
|
return VICI_PARSE_ERROR;
|
|
}
|
|
switch (res->type)
|
|
{
|
|
case VICI_END:
|
|
return VICI_PARSE_END;
|
|
case VICI_SECTION_START:
|
|
return VICI_PARSE_BEGIN_SECTION;
|
|
case VICI_SECTION_END:
|
|
return VICI_PARSE_END_SECTION;
|
|
case VICI_LIST_START:
|
|
return VICI_PARSE_BEGIN_LIST;
|
|
case VICI_LIST_ITEM:
|
|
return VICI_PARSE_LIST_ITEM;
|
|
case VICI_LIST_END:
|
|
return VICI_PARSE_END_LIST;
|
|
case VICI_KEY_VALUE:
|
|
return VICI_PARSE_KEY_VALUE;
|
|
default:
|
|
return VICI_PARSE_ERROR;
|
|
}
|
|
}
|
|
|
|
char* vici_parse_name(vici_res_t *res)
|
|
{
|
|
char *name;
|
|
|
|
switch (res->type)
|
|
{
|
|
case VICI_SECTION_START:
|
|
case VICI_LIST_START:
|
|
case VICI_KEY_VALUE:
|
|
name = strdup(res->name);
|
|
res->strings->insert_last(res->strings, name);
|
|
return name;
|
|
default:
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int vici_parse_name_eq(vici_res_t *res, char *name)
|
|
{
|
|
switch (res->type)
|
|
{
|
|
case VICI_SECTION_START:
|
|
case VICI_LIST_START:
|
|
case VICI_KEY_VALUE:
|
|
return streq(name, res->name) ? 1 : 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void* vici_parse_value(vici_res_t *res, int *len)
|
|
{
|
|
switch (res->type)
|
|
{
|
|
case VICI_LIST_ITEM:
|
|
case VICI_KEY_VALUE:
|
|
*len = res->value.len;
|
|
return res->value.ptr;
|
|
default:
|
|
*len = 0;
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
char* vici_parse_value_str(vici_res_t *res)
|
|
{
|
|
char *val;
|
|
|
|
switch (res->type)
|
|
{
|
|
case VICI_LIST_ITEM:
|
|
case VICI_KEY_VALUE:
|
|
if (!chunk_printable(res->value, NULL, 0))
|
|
{
|
|
errno = EBADMSG;
|
|
return NULL;
|
|
}
|
|
val = strndup(res->value.ptr, res->value.len);
|
|
res->strings->insert_last(res->strings, val);
|
|
return val;
|
|
default:
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int vici_parse_cb(vici_res_t *res, vici_parse_section_cb_t section,
|
|
vici_parse_value_cb_t kv, vici_parse_value_cb_t li,
|
|
void *user)
|
|
{
|
|
char *name, *list = NULL;
|
|
void *value;
|
|
int base, len, ret;
|
|
|
|
base = res->level;
|
|
|
|
while (TRUE)
|
|
{
|
|
switch (vici_parse(res))
|
|
{
|
|
case VICI_PARSE_KEY_VALUE:
|
|
if (res->level == base)
|
|
{
|
|
if (kv)
|
|
{
|
|
name = vici_parse_name(res);
|
|
value = vici_parse_value(res, &len);
|
|
if (name && value)
|
|
{
|
|
ret = kv(user, res, name, value, len);
|
|
if (ret)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VICI_PARSE_BEGIN_SECTION:
|
|
if (res->level++ == base)
|
|
{
|
|
if (section)
|
|
{
|
|
name = vici_parse_name(res);
|
|
if (name)
|
|
{
|
|
ret = section(user, res, name);
|
|
if (ret)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VICI_PARSE_END_SECTION:
|
|
if (res->level-- == base)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case VICI_PARSE_END:
|
|
res->level = 0;
|
|
return 0;
|
|
case VICI_PARSE_BEGIN_LIST:
|
|
if (res->level == base)
|
|
{
|
|
list = vici_parse_name(res);
|
|
}
|
|
break;
|
|
case VICI_PARSE_LIST_ITEM:
|
|
if (list && li)
|
|
{
|
|
value = vici_parse_value(res, &len);
|
|
if (value)
|
|
{
|
|
ret = li(user, res, list, value, len);
|
|
if (ret)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VICI_PARSE_END_LIST:
|
|
if (res->level == base)
|
|
{
|
|
list = NULL;
|
|
}
|
|
break;
|
|
case VICI_PARSE_ERROR:
|
|
res->level = 0;
|
|
errno = EBADMSG;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void* vici_find(vici_res_t *res, int *len, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
chunk_t value;
|
|
|
|
va_start(args, fmt);
|
|
value = res->message->vget_value(res->message, chunk_empty, fmt, args);
|
|
va_end(args);
|
|
|
|
*len = value.len;
|
|
return value.ptr;
|
|
}
|
|
|
|
char* vici_find_str(vici_res_t *res, char *def, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char *str;
|
|
|
|
va_start(args, fmt);
|
|
str = res->message->vget_str(res->message, def, fmt, args);
|
|
va_end(args);
|
|
|
|
return str;
|
|
}
|
|
|
|
int vici_find_int(vici_res_t *res, int def, char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int val;
|
|
|
|
va_start(args, fmt);
|
|
val = res->message->vget_int(res->message, def, fmt, args);
|
|
va_end(args);
|
|
|
|
return val;
|
|
}
|
|
|
|
void vici_free_res(vici_res_t *res)
|
|
{
|
|
res->strings->destroy_function(res->strings, free);
|
|
res->message->destroy(res->message);
|
|
res->enumerator->destroy(res->enumerator);
|
|
free(res);
|
|
}
|
|
|
|
int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user)
|
|
{
|
|
event_t *event;
|
|
uint32_t len;
|
|
uint8_t namelen, op;
|
|
int ret = 1;
|
|
|
|
op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER;
|
|
namelen = strlen(name);
|
|
len = htonl(sizeof(op) + sizeof(namelen) + namelen);
|
|
if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) ||
|
|
!conn->stream->write_all(conn->stream, &op, sizeof(op)) ||
|
|
!conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) ||
|
|
!conn->stream->write_all(conn->stream, name, namelen))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
conn->mutex->lock(conn->mutex);
|
|
while (conn->wait == WAIT_IDLE)
|
|
{
|
|
conn->cond->wait(conn->cond, conn->mutex);
|
|
}
|
|
switch (conn->wait)
|
|
{
|
|
case WAIT_SUCCESS:
|
|
ret = 0;
|
|
break;
|
|
case WAIT_READ_ERROR:
|
|
errno = conn->error;
|
|
break;
|
|
case WAIT_FAILURE:
|
|
default:
|
|
errno = ENOENT;
|
|
break;
|
|
}
|
|
conn->wait = WAIT_IDLE;
|
|
conn->mutex->unlock(conn->mutex);
|
|
|
|
conn->stream->on_read(conn->stream, on_read, conn);
|
|
|
|
if (ret == 0)
|
|
{
|
|
conn->mutex->lock(conn->mutex);
|
|
if (cb)
|
|
{
|
|
INIT(event,
|
|
.name = strdup(name),
|
|
.cb = cb,
|
|
.user = user,
|
|
);
|
|
event = conn->events->put(conn->events, event->name, event);
|
|
}
|
|
else
|
|
{
|
|
event = conn->events->remove(conn->events, name);
|
|
}
|
|
conn->mutex->unlock(conn->mutex);
|
|
|
|
if (event)
|
|
{
|
|
free(event->name);
|
|
free(event);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void vici_init()
|
|
{
|
|
library_init(NULL, "vici");
|
|
if (lib->processor->get_total_threads(lib->processor) < 4)
|
|
{
|
|
dbg_default_set_level(0);
|
|
lib->processor->set_threads(lib->processor, 4);
|
|
dbg_default_set_level(1);
|
|
}
|
|
}
|
|
|
|
void vici_deinit()
|
|
{
|
|
lib->processor->cancel(lib->processor);
|
|
library_deinit();
|
|
}
|