From 8ec8e8a051c57230443e97279b274fcb620e9540 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 29 Dec 2019 15:50:32 +0100 Subject: [WIP] prometheus module (with prometheus_vty) --- include/osmocom/vty/prometheus.h | 135 ++++++++ src/vty/Makefile.am | 3 +- src/vty/prometheus_vty.c | 679 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 816 insertions(+), 1 deletion(-) create mode 100644 include/osmocom/vty/prometheus.h create mode 100644 src/vty/prometheus_vty.c diff --git a/include/osmocom/vty/prometheus.h b/include/osmocom/vty/prometheus.h new file mode 100644 index 00000000..2a09a127 --- /dev/null +++ b/include/osmocom/vty/prometheus.h @@ -0,0 +1,135 @@ +#include + + +/*! A telnet connection */ +struct prom_connection { + /*! linked list header for internal management */ + struct llist_head entry; + /*! private data pointer passed through */ + void *priv; + /*! filedsecriptor (socket ) */ + struct osmo_fd fd; + /*! VTY instance associated with telnet connection */ + struct prom_vty *vty; + /*! logging target associated with this telnet connection */ + struct log_target *dbg; +}; + + +int osmo_prom_init(void *ctx, int port); + +int osmo_prom_init_dynif(void *ctx, const char *host, int port); + +#define PROM_HTTP_BUFSIZ 512 + +//#define PROM_VTY_BUFSIZ 512 + +// TODO kill PROM_VTY_MAXHIST +#define PROM_VTY_MAXHIST 0 + +// +//enum event { +// PROM_VTY_SERV, +// PROM_VTY_READ, +// PROM_VTY_WRITE, +// PROM_VTY_CLOSED, +// PROM_VTY_TIMEOUT_RESET, +//}; + +/*! Internal representation of a single VTY */ +struct prom_vty { + /*! underlying file (if any) */ + FILE *file; + + /*! private data, specified by creator */ + void *priv; + + /*! File descripter of this vty. */ + int fd; + + /*! Is this vty connect to file or not */ + //enum prom_vty_type type; + + /*! Node status of this vty */ + int node; + + /*! Failure count */ + int fail; + + /*! Output buffer. */ + struct buffer *obuf; + + /*! Command input buffer */ + char *buf; + + /*! Command cursor point */ + int cp; + + /*! Command length */ + int length; + + /*! Command max length. */ + int max; + + /*! Histry of command */ + char *hist[PROM_VTY_MAXHIST]; + + /*! History lookup current point */ + int hp; + + /*! History insert end point */ + int hindex; + + /*! For current referencing point of interface, route-map, + access-list etc... */ + void *index; + + /*! For multiple level index treatment such as key chain and key. */ + void *index_sub; + + /*! For escape character. */ + unsigned char escape; + + /*! Current vty status. */ + enum { PROM_VTY_NORMAL, PROM_VTY_CLOSE, PROM_VTY_MORE, PROM_VTY_MORELINE } status; + + /*! IAC handling + * + * IAC handling: was the last character received the IAC + * (interpret-as-command) escape character (and therefore the next + * character will be the command code)? Refer to Telnet RFC 854. */ + unsigned char iac; + + /*! IAC SB (option subnegotiation) handling */ + unsigned char iac_sb_in_progress; + /* At the moment, we care only about the NAWS (window size) negotiation, + * and that requires just a 5-character buffer (RFC 1073): + * <16-bit width> <16-bit height> */ +#define TELNET_NAWS_SB_LEN 5 + /*! sub-negotiation buffer */ + unsigned char sb_buf[TELNET_NAWS_SB_LEN]; + /*! How many subnegotiation characters have we received? + * + * We just drop those that do not fit in the buffer. */ + size_t sb_len; + + /*! Window width */ + int width; + /*! Widnow height */ + int height; + + /*! Configure lines. */ + int lines; + + int monitor; + + /*! In configure mode. */ + int config; + + /*! List of parent nodes, last item is the outermost parent. */ + struct llist_head parent_nodes; + + /*! When reading from a config file, these are the indenting characters expected for children of + * the current VTY node. */ + char *indent; +}; diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am index abed92ac..450549d0 100644 --- a/src/vty/Makefile.am +++ b/src/vty/Makefile.am @@ -12,7 +12,8 @@ lib_LTLIBRARIES = libosmovty.la libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \ telnet_interface.c logging_vty.c stats_vty.c \ fsm_vty.c talloc_ctx_vty.c \ - tdef_vty.c + tdef_vty.c \ + prometheus_vty.c libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS) endif diff --git a/src/vty/prometheus_vty.c b/src/vty/prometheus_vty.c new file mode 100644 index 00000000..89652444 --- /dev/null +++ b/src/vty/prometheus_vty.c @@ -0,0 +1,679 @@ +//#include +//#include +#include +//#include +//#include +#include // TODO killme? +#include +#include +#include // TODO killme +// +//#include +//XXX[osmo_counter]#include +//XXX[osmo_counter]#include +#include +#include +#include +#include +#include +#include +// +#include +#include +#include + +#include + + +/* per connection data */ +LLIST_HEAD(prom_active_connections); + +static void *prom_ctx; + +/* Vector which store each vty structure. */ +static vector prom_vtyvec; + +/* per network data */ +static int prom_new_connection(struct osmo_fd *fd, unsigned int what); + +static struct osmo_fd server_socket = { + .when = OSMO_FD_READ, + .cb = prom_new_connection, + .priv_nr = 0, +}; + +/*! Create new vty structure. */ +//struct prom_vty *vty_create (int vty_sock, void *priv) +// +/*! Create new vty structure. */ +// TODO extern struct host prom_host; + +/*! Return if this VTY is a shell or not */ +//int prom_vty_shell(struct prom_vty *vty) +//{ +// return vty->type == VTY_SHELL ? 1 : 0; +//} + + +/*! close a telnet connection */ +int prom_telnet_close_client(struct osmo_fd *fd) +{ + struct prom_connection *conn = (struct prom_connection*)fd->data; + char sock_name_buf[OSMO_SOCK_NAME_MAXLEN]; + int rc; + + /* FIXME: getsockname() always fails: "Bad file descriptor" */ + rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, fd->fd); + LOGP(DLGLOBAL, LOGL_INFO, "Closing telnet connection %s\n", + (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf); + + close(fd->fd); + osmo_fd_unregister(fd); + + if (conn->dbg) { + log_del_target(conn->dbg); + talloc_free(conn->dbg); + } + + llist_del(&conn->entry); + talloc_free(conn); + return 0; +} + + + +/*! callback from core VTY code about VTY related events */ +void prom_vty_event(enum event event, int sock, struct prom_vty *vty) +{ + struct vty_signal_data sig_data; + struct prom_connection *connection = vty->priv; + struct osmo_fd *bfd; + + //if (vty->type != VTY_TERM) + // return; + + sig_data.event = event; + sig_data.sock = sock; + sig_data.vty = vty; + osmo_signal_dispatch(SS_L_VTY, S_VTY_EVENT, &sig_data); + + if (!connection) + return; + + bfd = &connection->fd; + + switch (event) { + case VTY_READ: + bfd->when |= OSMO_FD_READ; + break; + case VTY_WRITE: + bfd->when |= OSMO_FD_WRITE; + break; + case VTY_CLOSED: + /* vty layer is about to free() vty */ + prom_telnet_close_client(bfd); + break; + default: + break; + } +} + +int prom_vty_out_va(struct prom_vty *vty, const char *format, va_list ap) +{ + int len = 0; + int size = 1024; + char buf[1024]; + char *p = NULL; + + // TODO don't use prom_vty_shell + //if (prom_vty_shell(vty)) { + // vprintf(format, ap); + //} else { + va_list args; + /* Try to write to initial buffer. */ + va_copy(args, ap); + len = vsnprintf(buf, sizeof buf, format, args); + va_end(args); + + /* Initial buffer is not enough. */ + if (len < 0 || len >= size) { + while (1) { + if (len > -1) + size = len + 1; + else + size = size * 2; + + p = talloc_realloc_size(vty, p, size); + if (!p) + return -1; + + va_copy(args, ap); + len = vsnprintf(p, size, format, args); + va_end(args); + + if (len > -1 && len < size) + break; + } + } + + /* When initial buffer is enough to store all output. */ + if (!p) + p = buf; + + /* Pointer p must point out buffer. */ + buffer_put(vty->obuf, (unsigned char *) p, len); + + /* If p is not different with buf, it is allocated buffer. */ + if (p != buf) + talloc_free(p); + //} + + prom_vty_event(VTY_WRITE, vty->fd, vty); + + return len; +} + +/*! VTY standard output function + * \param[in] vty VTY to which we should print + * \param[in] format variable-length format string + */ +int prom_vty_out(struct prom_vty *vty, const char *format, ...) +{ + va_list args; + int rc; + va_start(args, format); + rc = prom_vty_out_va(vty, format, args); + va_end(args); + return rc; +} + + +/*! Initialize telnet based VTY interface listening to 127.0.0.1 + * \param[in] tall_ctx \ref talloc context + * \param[in] priv private data to be passed to callback + * \param[in] port TCP port number to bind to + */ +//int telnet_init(void *tall_ctx, void *priv, int port) +//{ +// return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port); +//} + +int osmo_prom_init(void *ctx, int port) +{ + return osmo_prom_init_dynif(ctx, "127.0.0.1", port); +} + +/*! Initialize telnet based VTY interface + * \param[in] tall_ctx \ref talloc context + * \param[in] priv private data to be passed to callback + * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...) + * \param[in] port TCP port number to bind to + */ +//int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port) + +int osmo_prom_init_dynif(void *ctx, const char *host, int port) +{ + int rc; + + prom_ctx = talloc_named_const(ctx, 1, + "prometheus_connection"); + + rc = osmo_sock_init_ofd( + &server_socket, + AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, + host, port, OSMO_SOCK_F_BIND + ); + + server_socket.data = NULL; // könnte priv + + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n", + host, port); + return -1; + } + + prom_vtyvec = vector_init(VECTOR_MIN_SIZE); + + LOGP(DLGLOBAL, LOGL_NOTICE, "Available via HTTP %s %d\n", host, port); + return 0; +} + +/*! Unlock the configuration from a given VTY + * \param[in] vty VTY from which the configuration shall be unlocked + * \returns 0 in case of success + */ +int prom_vty_config_unlock(struct prom_vty *vty) +{ + //if (vty_config == 1 && vty->config == 1) { + // vty->config = 0; + // vty_config = 0; + //} + return vty->config; +} + + +void prom_vty_close(struct prom_vty *vty) +{ + int i; + + /* VTY_CLOSED is handled by the telnet_interface */ + prom_vty_event(VTY_CLOSED, vty->fd, vty); + + if (vty->obuf) { + /* Flush buffer. */ + buffer_flush_all(vty->obuf, vty->fd); + + /* Free input buffer. */ + buffer_free(vty->obuf); + vty->obuf = NULL; + } + + /* Free command history. */ + for (i = 0; i < PROM_VTY_MAXHIST; i++) + if (vty->hist[i]) + talloc_free(vty->hist[i]); + + /* Unset vector. */ + vector_unset(prom_vtyvec, vty->fd); + + /* Close socket (ignore standard I/O streams). */ + if (vty->fd > 2) { + close(vty->fd); + vty->fd = -1; + } + + if (vty->buf) { + talloc_free(vty->buf); + vty->buf = NULL; + } + + /* Check configure. */ + prom_vty_config_unlock(vty); + + /* OK free vty. */ + talloc_free(vty); +} + +//XXX[osmo_counter]static int osmo_counter_handler(struct osmo_counter *counter, void *vty_) +//XXX[osmo_counter]{ +//XXX[osmo_counter] struct vty *vty = vty_; +//XXX[osmo_counter] const char *description = counter->description; +//XXX[osmo_counter] +//XXX[osmo_counter] if (!counter->description) +//XXX[osmo_counter] description = counter->name; +//XXX[osmo_counter] +//XXX[osmo_counter] //vty_out(vty, " %s%s: %8lu%s", +//XXX[osmo_counter] // vctx->prefix, description, +//XXX[osmo_counter] // osmo_counter_get(counter), VTY_NEWLINE); +//XXX[osmo_counter] +//XXX[osmo_counter] char buf[1024]; +//XXX[osmo_counter] +//XXX[osmo_counter] // TODO only export current value and let graphing tools sum it up? +//XXX[osmo_counter] +//XXX[osmo_counter] /* TODO ensure name contains only [a-zA-Z0-] */ +//XXX[osmo_counter] const char *name = description; +//XXX[osmo_counter] +//XXX[osmo_counter] snprintf(buf, sizeof buf, +//XXX[osmo_counter] "%s %d\n", +//XXX[osmo_counter] name, +//XXX[osmo_counter] osmo_counter_get(counter) +//XXX[osmo_counter] ); +//XXX[osmo_counter] +//XXX[osmo_counter] prom_vty_out(vty, "%X\r\n", strlen(buf)); +//XXX[osmo_counter] prom_vty_out(vty, "%s\r\n", buf); +//XXX[osmo_counter] +//XXX[osmo_counter] return 0; +//XXX[osmo_counter]} + + +void sanitize_name(char *name) { + for (char c; (c = *name) != '\0'; name++) { + if ('a' <= c && c <= 'z') continue; + if ('A' <= c && c <= 'Z') continue; + if ('0' <= c && c <= '9') continue; + if (c == ':' || c == '_') continue; + *name = '_'; + } +} + + +static int rate_ctr_handler( + struct rate_ctr_group *ctrg, struct rate_ctr *ctr, + const struct rate_ctr_desc *desc, void *vty_) +{ + struct vty *vty = vty_; + + char buf[1024]; + char name[1024]; + + // TODO only export current value and let graphing tools sum it up? + + strcpy(name, desc->name); + sanitize_name(name); + + char *group = ctrg->desc->group_name_prefix; + //char intv_sec_name[1024]; + //char intv_min_name[1024]; + //char intv_hour_name[1024]; + //char intv_day_name[1024]; + + //strcpy(intv_sec_name, name); + //strcpy(intv_min_name, name); + //strcpy(intv_hour_name, name); + //strcpy(intv_day_name, name); + + //strcat(intv_sec_name, "_intv_sec"); + //strcat(intv_min_name, "_intv_min"); + //strcat(intv_hour_name, "_intv_hour"); + //strcat(intv_day_name, "_intv_day"); + + snprintf(buf, sizeof buf, + //"%s %d\n" + //"%s %d\n" + //"%s %d\n" + //"%s %d\n" + "%s{group=\"%s\"} %d\n", + //intv_sec_name, ctr->intv[RATE_CTR_INTV_SEC].rate, + //intv_min_name, ctr->intv[RATE_CTR_INTV_MIN].rate, + //intv_hour_name, ctr->intv[RATE_CTR_INTV_HOUR].rate, + //intv_day_name, ctr->intv[RATE_CTR_INTV_DAY].rate + name, group, ctr->current + ); + + prom_vty_out(vty, "%X\r\n", strlen(buf)); + prom_vty_out(vty, "%s\r\n", buf); + + return 0; +} + +static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vty_) +{ + struct vty *vty = vty_; + + //TODO +// if (ctrg->idx) +// prom_vty_out(vty, "%s (%d):\r\n", +// ctrg->desc->group_description, ctrg->idx); +// else +// prom_vty_out(vty, "%s:\r\n", +// ctrg->desc->group_description); + + rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vty); + + return 0; +} + +static int osmo_stat_item_handler( + struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *vty_) +{ + struct vty *vty = vty_; + const char *unit = + item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ? + item->desc->unit : ""; + + char buf[1024]; + char name[1024]; + + //vty_out(vty, " %s%s: %8" PRIi32 " %s%s", + // vctx->prefix, item->desc->description, + // osmo_stat_item_get_last(item), + // unit, VTY_NEWLINE); + + strcpy(name, item->desc->name); + sanitize_name(name); + + char *group = statg->desc->group_name_prefix; + int value = osmo_stat_item_get_last(item); + // TODO unit + snprintf(buf, sizeof buf, "%s{group=\"%s\",unit=\"%s\"} %d\n", name, group, unit, value); + + prom_vty_out(vty, "%X\r\n", strlen(buf)); + prom_vty_out(vty, "%s\r\n", buf); + + return 0; +} + +static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vty_) +{ + struct vty *vty = vty_; + + //if (statg->idx) + // vty_out(vty, "%s%s (%d):%s", vctx->prefix, + // statg->desc->group_description, statg->idx, + // VTY_NEWLINE); + //else + // vty_out(vty, "%s%s:%s", vctx->prefix, + // statg->desc->group_description, VTY_NEWLINE); + + osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vty); + + return 0; +} + +/*! Read data via vty socket. */ +int prom_vty_read(struct prom_vty *vty) +{ + //int i; + int nbytes; + unsigned char buf[VTY_READ_BUFSIZ]; + + int vty_sock = vty->fd; + + /* Read raw data from socket */ + if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) { + if (nbytes < 0) { + if (ERRNO_IO_RETRY(errno)) { + prom_vty_event(VTY_READ, vty_sock, vty); + return 0; + } + } + buffer_reset(vty->obuf); + vty->status = PROM_VTY_CLOSE; + } + + //LOGP(DLGLOBAL, LOGL_NOTICE, "read %d byte(s)\n", nbytes); + prom_vty_out(vty, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + // "Content-Length: 6\r\n" + "\r\n" + ); + //vty->status = PROM_VTY_CLOSE; + + //XXX[osmo_counter]osmo_counters_for_each(osmo_counter_handler, vty); + rate_ctr_for_each_group(rate_ctr_group_handler, vty); + osmo_stat_item_for_each_group(osmo_stat_item_group_handler, vty); + + /* finish chunked transfer encoding */ + prom_vty_out(vty, "0\r\n\r\n"); + + // TODO parse + + /* Check status. */ + if (vty->status == PROM_VTY_CLOSE) { + prom_vty_close(vty); + return -EBADF; + } else { + prom_vty_event(VTY_WRITE, vty_sock, vty); + prom_vty_event(VTY_READ, vty_sock, vty); + } + + return 0; +} + + +static int client_data(struct osmo_fd *fd, unsigned int what) +{ + struct prom_connection *conn = fd->data; + int rc = 0; + + if (what & OSMO_FD_READ) { + conn->fd.when &= ~OSMO_FD_READ; + // TODO feed server + rc = prom_vty_read(conn->vty); + } + + /* vty might have been closed from vithin prom_vty_read() */ + if (rc == -EBADF) + return rc; + + if (what & OSMO_FD_WRITE) { + rc = buffer_flush_all(conn->vty->obuf, fd->fd); + if (rc == BUFFER_EMPTY) + conn->fd.when &= ~OSMO_FD_WRITE; + } + + return rc; +} + + +/*! Allocate a new vty interface structure */ +struct prom_vty *prom_vty_new(void) +{ + struct prom_vty *new = talloc_zero(tall_vty_ctx, struct vty); + + if (!new) + goto out; + + INIT_LLIST_HEAD(&new->parent_nodes); + + new->obuf = buffer_new(new, 0); /* Use default buffer size. */ + if (!new->obuf) + goto out_new; + new->buf = _talloc_zero(new, PROM_HTTP_BUFSIZ, "prom_vty_new->buf"); + if (!new->buf) + goto out_obuf; + + new->max = PROM_HTTP_BUFSIZ; + new->fd = -1; + + return new; + +out_obuf: + buffer_free(new->obuf); +out_new: + talloc_free(new); + new = NULL; +out: + return new; +} + +struct vty * +prom_vty_create (int vty_sock, void *priv) +{ + struct prom_vty *vty; + + struct termios t = {}; + + tcgetattr(vty_sock, &t); + cfmakeraw(&t); + tcsetattr(vty_sock, TCSANOW, &t); + + /* Allocate new vty structure and set up default values. */ + vty = prom_vty_new (); + vty->fd = vty_sock; + vty->priv = priv; + //vty->type = VTY_TERM; // TODO kill vty->type + //if (!password_check) + // { + // if (prom_host.advanced) + // vty->node = ENABLE_NODE; + // else + // vty->node = VIEW_NODE; + // } + //else + // vty->node = AUTH_NODE; + vty->fail = 0; + vty->cp = 0; + // TODO vty_clear_buf (vty); + vty->length = 0; + memset (vty->hist, 0, sizeof (vty->hist)); + vty->hp = 0; + vty->hindex = 0; + vector_set_index (prom_vtyvec, vty_sock, vty); + vty->status = PROM_VTY_NORMAL; + //if (prom_host.lines >= 0) + // vty->lines = prom_host.lines; + //else + // vty->lines = -1; + + //if (password_check) + // { + // /* Vty is not available if password isn't set. */ + // if (prom_host.password == NULL && prom_host.password_encrypt == NULL) + // { + // prom_vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); + // vty->status = PROM_VTY_CLOSE; + // prom_vty_close (vty); + // return NULL; + // } + // } + + /* Say hello to the world. */ + //prom_vty_hello (vty); + //if (password_check) + // prom_vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + + /* Setting up terminal. */ + // TODO vty_will_echo (vty); + //TODO vty_will_suppress_go_ahead (vty); + + // TODO vty_dont_linemode (vty); + // TODO vty_do_window_size (vty); + /* vty_dont_lflow_ahead (vty); */ + + // TODO vty_prompt (vty); + + /* Add read/write thread. */ + prom_vty_event (VTY_WRITE, vty_sock, vty); + prom_vty_event (VTY_READ, vty_sock, vty); + + return vty; +} + + +static int prom_new_connection(struct osmo_fd *fd, unsigned int what) +{ + //LOGP(DLGLOBAL, LOGL_ERROR, "derp\n"); + struct prom_connection *connection; + struct sockaddr_in sockaddr; + socklen_t len = sizeof(sockaddr); + int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len); + char sock_name_buf[OSMO_SOCK_NAME_MAXLEN]; + int rc; + + if (new_connection < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "HTTP accept failed\n"); + return new_connection; + } + + rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, new_connection); + LOGP(DLGLOBAL, LOGL_INFO, "Accept()ed new HTTP connection %s\n", + (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf); + + connection = talloc_zero(prom_ctx, struct prom_connection); + connection->priv = fd->data; + connection->fd.data = connection; + connection->fd.fd = new_connection; + connection->fd.when = OSMO_FD_READ; + connection->fd.cb = client_data; + rc = osmo_fd_register(&connection->fd); + if (rc < 0) { + talloc_free(connection); + return rc; + } + llist_add_tail(&connection->entry, &prom_active_connections); + + // TODO create server + connection->vty = prom_vty_create(new_connection, connection); + if (!connection->vty) { + LOGP(DLGLOBAL, LOGL_ERROR, "couldn't create VTY\n"); + /* prom_vty_create() is already closing the fd if it returns NULL */ + talloc_free(connection); + return -1; + } + + return 0; +} + -- cgit v1.2.3