diff options
Diffstat (limited to 'src/lib/atom.c')
-rw-r--r-- | src/lib/atom.c | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/src/lib/atom.c b/src/lib/atom.c new file mode 100644 index 0000000..04011d4 --- /dev/null +++ b/src/lib/atom.c @@ -0,0 +1,655 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include "lldpctl.h" +#include "atom.h" +#include "../log.h" +#include "../marshal.h" +#include "../ctl.h" + +lldpctl_conn_t * +lldpctl_atom_get_connection(lldpctl_atom_t *atom) +{ + if (atom) return atom->conn; + return NULL; +} + +void +lldpctl_atom_inc_ref(lldpctl_atom_t *atom) +{ + if (atom) atom->count++; +} + +void +lldpctl_atom_dec_ref(lldpctl_atom_t *atom) +{ + struct atom_buffer *buffer, *buffer_next; + if (atom && (--atom->count == 0)) { + if (atom->free) atom->free(atom); + + /* Remove special allocated buffers */ + for (buffer = TAILQ_FIRST(&atom->buffers); buffer; + buffer = buffer_next) { + buffer_next = TAILQ_NEXT(buffer, next); + free(buffer); + } + + free(atom); + } +} + +lldpctl_atom_t * +lldpctl_atom_get(lldpctl_atom_t *atom, lldpctl_key_t key) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->get == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + return atom->get(atom, key); +} + +lldpctl_atom_t * +lldpctl_atom_set(lldpctl_atom_t *atom, lldpctl_key_t key, lldpctl_atom_t *value) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->set == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + return atom->set(atom, key, value); +} + +const char * +lldpctl_atom_get_str(lldpctl_atom_t *atom, lldpctl_key_t key) +{ + char *strresult = NULL; + const uint8_t *bufresult = NULL; + long int intresult = -1; + int n1; + size_t n2; + + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->get_str != NULL) { + strresult = (char *)atom->get_str(atom, key); + if (strresult) return strresult; + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST) + return NULL; + } + + RESET_ERROR(atom->conn); + if (atom->get_int != NULL) { + intresult = atom->get_int(atom, key); + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST) { + strresult = _lldpctl_alloc_in_atom(atom, 21); + if (!strresult) return NULL; + n1 = snprintf(strresult, 21, "%ld", intresult); + if (n1 > -1 && n1 < 21) return strresult; + SET_ERROR(atom->conn, + LLDPCTL_ERR_NOMEM); /* Not really true... */ + return NULL; + } + } + + RESET_ERROR(atom->conn); + if (atom->get_buffer != NULL) { + bufresult = atom->get_buffer(atom, key, &n2); + if (bufresult) + return _lldpctl_dump_in_atom(atom, bufresult, n2, ' ', 0); + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST) + return NULL; + } + + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; +} + +lldpctl_atom_t * +lldpctl_atom_set_str(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value) +{ + lldpctl_atom_t *result = NULL; + const char *errstr; + long long converted = 0; + int isint = 0; + int bad = 0; + + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->set_str != NULL) { + result = atom->set_str(atom, key, value); + if (result) return result; + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST && + lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE) + return NULL; + bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE); + } + + if (value) { + converted = strtonum(value, LLONG_MIN, LLONG_MAX, &errstr); + isint = (errstr == NULL); + } + + RESET_ERROR(atom->conn); + if (atom->set_int != NULL && isint) { + result = atom->set_int(atom, key, converted); + if (result) return result; + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST && + lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE) + return NULL; + bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE); + } + + RESET_ERROR(atom->conn); + if (atom->set_buffer != NULL) { + result = atom->set_buffer(atom, key, (u_int8_t *)value, + value ? strlen(value) : 0); + if (result) return result; + if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST && + lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE) + return NULL; + bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE); + } + + SET_ERROR(atom->conn, bad ? LLDPCTL_ERR_BAD_VALUE : LLDPCTL_ERR_NOT_EXIST); + return NULL; +} + +const u_int8_t * +lldpctl_atom_get_buffer(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *length) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->get_buffer == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + return atom->get_buffer(atom, key, length); +} + +lldpctl_atom_t * +lldpctl_atom_set_buffer(lldpctl_atom_t *atom, lldpctl_key_t key, const u_int8_t *value, + size_t length) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->set_buffer == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + return atom->set_buffer(atom, key, value, length); +} + +long int +lldpctl_atom_get_int(lldpctl_atom_t *atom, lldpctl_key_t key) +{ + if (atom == NULL) return LLDPCTL_ERR_NOT_EXIST; + RESET_ERROR(atom->conn); + + if (atom->get_int == NULL) return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return atom->get_int(atom, key); +} + +lldpctl_atom_t * +lldpctl_atom_set_int(lldpctl_atom_t *atom, lldpctl_key_t key, long int value) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (atom->set_int == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + return atom->set_int(atom, key, value); +} + +lldpctl_atom_iter_t * +lldpctl_atom_iter(lldpctl_atom_t *atom) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (!atom->iter) { + SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE); + return NULL; + } + return atom->iter(atom); +} + +lldpctl_atom_iter_t * +lldpctl_atom_iter_next(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (!atom->next) { + SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE); + return NULL; + } + return atom->next(atom, iter); +} + +lldpctl_atom_t * +lldpctl_atom_iter_value(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (!atom->value) { + SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE); + return NULL; + } + return atom->value(atom, iter); +} + +lldpctl_atom_t * +lldpctl_atom_create(lldpctl_atom_t *atom) +{ + if (atom == NULL) return NULL; + RESET_ERROR(atom->conn); + + if (!atom->create) { + SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_CREATE); + return NULL; + } + return atom->create(atom); +} + +/** + * Get somethin with IO. + * + * @param conn The connection to lldpd. + * @param state_send State to be when "sending" + * @param state_recv State to be when "receiving" + * @param state_data Ancillary data for state handling + * @param type Type of message to send (and receive) + * @param to_send Data to send. + * @param mi_send Marshalling info for data to send. + * @param to_recv Data to receive. + * @param mi_recv Marshalling info for data to recive. + * @return 0 in case of success, a negative integer in case of failure. + * + * The current state must match one of @c CONN_STATE_IDLE, @c state_send or @c + * state_recv and in the two later cases, the provided @c state_data must match. + */ +int +_lldpctl_do_something(lldpctl_conn_t *conn, int state_send, int state_recv, + const char *state_data, enum hmsg_type type, void *to_send, + struct marshal_info *mi_send, void **to_recv, struct marshal_info *mi_recv) +{ + ssize_t rc; + + if (conn->state == CONN_STATE_WATCHING) + /* The connection cannot be used anymore. */ + return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE); + + if (conn->state == CONN_STATE_IDLE) { + /* We need to build the message to send, then send + * it. */ + if (ctl_msg_send_unserialized(&conn->output_buffer, + &conn->output_buffer_len, type, to_send, mi_send) != 0) + return SET_ERROR(conn, LLDPCTL_ERR_SERIALIZATION); + conn->state = state_send; + if (state_data) + strlcpy(conn->state_data, state_data, sizeof(conn->state_data)); + else + conn->state_data[0] = 0; + } + if (conn->state == state_send && + (state_data == NULL || + !strncmp(conn->state_data, state_data, sizeof(conn->state_data) - 1))) { + /* We need to send the currently built message */ + rc = lldpctl_send(conn); + if (rc < 0) return SET_ERROR(conn, rc); + conn->state = state_recv; + } + if (conn->state == state_recv && + (state_data == NULL || + !strncmp(conn->state_data, state_data, sizeof(conn->state_data) - 1))) { + /* We need to receive the answer */ + while ((rc = ctl_msg_recv_unserialized(&conn->input_buffer, + &conn->input_buffer_len, type, to_recv, mi_recv)) > 0) { + /* We need more bytes */ + rc = _lldpctl_needs(conn, rc); + if (rc < 0) return SET_ERROR(conn, rc); + } + if (rc < 0) return SET_ERROR(conn, LLDPCTL_ERR_SERIALIZATION); + /* rc == 0 */ + conn->state = CONN_STATE_IDLE; + conn->state_data[0] = 0; + return 0; + } else + return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE); +} + +int +lldpctl_watch_callback(lldpctl_conn_t *conn, lldpctl_change_callback cb, void *data) +{ + int rc; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_SET_WATCH_SEND, + CONN_STATE_SET_WATCH_RECV, NULL, SUBSCRIBE, NULL, NULL, NULL, NULL); + if (rc == 0) { + conn->watch_cb = cb; + conn->watch_data = data; + conn->state = CONN_STATE_WATCHING; + } + return rc; +} + +int +lldpctl_watch_callback2(lldpctl_conn_t *conn, lldpctl_change_callback2 cb, void *data) +{ + int rc; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_SET_WATCH_SEND, + CONN_STATE_SET_WATCH_RECV, NULL, SUBSCRIBE, NULL, NULL, NULL, NULL); + if (rc == 0) { + conn->watch_cb2 = cb; + conn->watch_data = data; + conn->state = CONN_STATE_WATCHING; + } + return rc; +} + +int +lldpctl_watch(lldpctl_conn_t *conn) +{ + int rc = 0; + + RESET_ERROR(conn); + + if (conn->state != CONN_STATE_WATCHING) + return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE); + + conn->watch_triggered = 0; + while (!conn->watch_triggered) { + rc = _lldpctl_needs(conn, 1); + if (rc < 0) return SET_ERROR(conn, rc); + } + + RESET_ERROR(conn); + return 0; +} + +lldpctl_atom_t * +lldpctl_get_configuration(lldpctl_conn_t *conn) +{ + int rc; + struct lldpd_config *config; + void *p; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_GET_CONFIG_SEND, + CONN_STATE_GET_CONFIG_RECV, NULL, GET_CONFIG, NULL, NULL, &p, + &MARSHAL_INFO(lldpd_config)); + if (rc == 0) { + config = p; + return _lldpctl_new_atom(conn, atom_config, config); + } + return NULL; +} + +lldpctl_atom_t * +lldpctl_get_interfaces(lldpctl_conn_t *conn) +{ + struct lldpd_interface_list *ifs; + void *p; + int rc; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_GET_INTERFACES_SEND, + CONN_STATE_GET_INTERFACES_RECV, NULL, GET_INTERFACES, NULL, NULL, &p, + &MARSHAL_INFO(lldpd_interface_list)); + if (rc == 0) { + ifs = p; + return _lldpctl_new_atom(conn, atom_interfaces_list, ifs); + } + return NULL; +} + +lldpctl_atom_t * +lldpctl_get_local_chassis(lldpctl_conn_t *conn) +{ + struct lldpd_chassis *chassis; + void *p; + int rc; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_GET_CHASSIS_SEND, + CONN_STATE_GET_CHASSIS_RECV, NULL, GET_CHASSIS, NULL, NULL, &p, + &MARSHAL_INFO(lldpd_chassis)); + if (rc == 0) { + chassis = p; + return _lldpctl_new_atom(conn, atom_chassis, chassis, NULL, 0); + } + return NULL; +} + +lldpctl_atom_t * +lldpctl_get_port(lldpctl_atom_t *atom) +{ + int rc; + lldpctl_conn_t *conn = atom->conn; + struct lldpd_hardware *hardware; + void *p; + struct _lldpctl_atom_interface_t *iface = + (struct _lldpctl_atom_interface_t *)atom; + + RESET_ERROR(conn); + + if (atom->type != atom_interface) { + SET_ERROR(conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE); + return NULL; + } + rc = _lldpctl_do_something(conn, CONN_STATE_GET_PORT_SEND, + CONN_STATE_GET_PORT_RECV, iface->name, GET_INTERFACE, (void *)iface->name, + &MARSHAL_INFO(string), &p, &MARSHAL_INFO(lldpd_hardware)); + if (rc == 0) { + hardware = p; + return _lldpctl_new_atom(conn, atom_port, 1, hardware, + &hardware->h_lport, NULL); + } + return NULL; +} + +lldpctl_atom_t * +lldpctl_get_default_port(lldpctl_conn_t *conn) +{ + struct lldpd_port *port; + void *p; + int rc; + + RESET_ERROR(conn); + + rc = _lldpctl_do_something(conn, CONN_STATE_GET_DEFAULT_PORT_SEND, + CONN_STATE_GET_DEFAULT_PORT_RECV, "", GET_DEFAULT_PORT, NULL, NULL, &p, + &MARSHAL_INFO(lldpd_port)); + if (rc == 0) { + port = p; + return _lldpctl_new_atom(conn, atom_port, 1, NULL, port, NULL); + } + return NULL; +} + +static lldpctl_map_t empty_map[] = { { 0, NULL } }; + +static struct atom_map atom_map_list = { .next = NULL }; + +lldpctl_map_t * +lldpctl_key_get_map(lldpctl_key_t key) +{ + init_atom_map(); + struct atom_map *map; + for (map = atom_map_list.next; map; map = map->next) { + if (map->key == key) return map->map; + } + return empty_map; +} + +void +atom_map_register(struct atom_map *map, int prio) +{ + (void)prio; + struct atom_map *iter = &atom_map_list; + + while (iter->next) + iter = iter->next; + + iter->next = map; +} + +static struct atom_builder atom_builder_list = { .nextb = NULL }; + +void +atom_builder_register(struct atom_builder *builder, int prio) +{ + (void)prio; + struct atom_builder *iter = &atom_builder_list; + + while (iter->nextb) + iter = iter->nextb; + + iter->nextb = builder; +} + +lldpctl_atom_t * +_lldpctl_new_atom(lldpctl_conn_t *conn, atom_t type, ...) +{ + init_atom_builder(); + struct atom_builder *builder; + struct lldpctl_atom_t *atom; + va_list(ap); + for (builder = atom_builder_list.nextb; builder; builder = builder->nextb) { + if (builder->type != type) continue; + atom = calloc(1, builder->size); + if (atom == NULL) { + SET_ERROR(conn, LLDPCTL_ERR_NOMEM); + return NULL; + } + atom->count = 1; + atom->type = type; + atom->conn = conn; + TAILQ_INIT(&atom->buffers); + atom->free = builder->free; + + atom->iter = builder->iter; + atom->next = builder->next; + atom->value = builder->value; + + atom->get = builder->get; + atom->get_str = builder->get_str; + atom->get_buffer = builder->get_buffer; + atom->get_int = builder->get_int; + + atom->set = builder->set; + atom->set_str = builder->set_str; + atom->set_buffer = builder->set_buffer; + atom->set_int = builder->set_int; + atom->create = builder->create; + + va_start(ap, type); + if (builder->init && builder->init(atom, ap) == 0) { + free(atom); + va_end(ap); + /* Error to be set in init() */ + return NULL; + } + va_end(ap); + return atom; + } + log_warnx("rpc", "unknown atom type: %d", type); + SET_ERROR(conn, LLDPCTL_ERR_FATAL); + return NULL; +} + +/** + * Allocate a buffer inside an atom. + * + * It will be freed automatically when the atom is released. This buffer cannot + * be reallocated and should not be freed! + * + * @param atom Atom which will be used as a container. + * @param size Size of the allocated area. + * @return Pointer to the buffer or @c NULL if allocation fails. + */ +void * +_lldpctl_alloc_in_atom(lldpctl_atom_t *atom, size_t size) +{ + struct atom_buffer *buffer; + + if ((buffer = calloc(1, size + sizeof(struct atom_buffer))) == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM); + return NULL; + } + TAILQ_INSERT_TAIL(&atom->buffers, buffer, next); + return &buffer->data[0]; +} + +/** + * Allocate a buffer inside an atom and dump another buffer in it. + * + * The dump is done in hexadecimal with the provided separator. + * + * @param atom Atom which will be used as a container. + * @param input Buffer we want to dump. + * @param size Size of the buffer + * @param sep Separator to use. + * @param max Maximum number of bytes to dump. Can be 0 if no maximum. + * @return A string representing the dump of the buffer or @c NULL if error. + */ +const char * +_lldpctl_dump_in_atom(lldpctl_atom_t *atom, const uint8_t *input, size_t size, char sep, + size_t max) +{ + static const char truncation[] = "[...]"; + size_t i, len; + char *buffer = NULL; + + if (max > 0 && size > max) + len = max * 3 + sizeof(truncation) + 1; + else + len = size * 3 + 1; + + if ((buffer = _lldpctl_alloc_in_atom(atom, len)) == NULL) return NULL; + + for (i = 0; (i < size) && (max == 0 || i < max); i++) + snprintf(buffer + i * 3, 4, "%02x%c", *(u_int8_t *)(input + i), sep); + if (max > 0 && size > max) + snprintf(buffer + i * 3, sizeof(truncation) + 1, "%s", truncation); + else + *(buffer + i * 3 - 1) = 0; + return buffer; +} |