diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 14 | ||||
-rw-r--r-- | lib/buffer.c | 51 | ||||
-rw-r--r-- | lib/buffer.h | 17 | ||||
-rw-r--r-- | lib/command.c | 2940 | ||||
-rw-r--r-- | lib/command.h | 258 | ||||
-rw-r--r-- | lib/command_execute.h | 79 | ||||
-rw-r--r-- | lib/command_queue.c | 154 | ||||
-rw-r--r-- | lib/command_queue.h | 31 | ||||
-rw-r--r-- | lib/confirm.h | 28 | ||||
-rw-r--r-- | lib/daemon.c | 6 | ||||
-rw-r--r-- | lib/distribute.c | 53 | ||||
-rw-r--r-- | lib/errno_names.c | 383 | ||||
-rw-r--r-- | lib/errno_names.h | 31 | ||||
-rw-r--r-- | lib/filter.c | 26 | ||||
-rw-r--r-- | lib/getopt.c | 1016 | ||||
-rw-r--r-- | lib/getopt.h | 66 | ||||
-rw-r--r-- | lib/getopt1.c | 158 | ||||
-rw-r--r-- | lib/heap.c | 517 | ||||
-rw-r--r-- | lib/heap.h | 160 | ||||
-rw-r--r-- | lib/if.c | 56 | ||||
-rw-r--r-- | lib/if_rmap.c | 41 | ||||
-rw-r--r-- | lib/keychain.c | 50 | ||||
-rw-r--r-- | lib/keystroke.c | 1204 | ||||
-rw-r--r-- | lib/keystroke.h | 194 | ||||
-rw-r--r-- | lib/list_util.c | 80 | ||||
-rw-r--r-- | lib/list_util.h | 729 | ||||
-rw-r--r-- | lib/log.c | 844 | ||||
-rw-r--r-- | lib/log.h | 107 | ||||
-rw-r--r-- | lib/mem_tracker.c | 590 | ||||
-rw-r--r-- | lib/memory.c | 583 | ||||
-rw-r--r-- | lib/memory.h | 67 | ||||
-rw-r--r-- | lib/memtypes.awk | 2 | ||||
-rw-r--r-- | lib/memtypes.c | 52 | ||||
-rw-r--r-- | lib/miyagi.h | 40 | ||||
-rw-r--r-- | lib/mqueue.c | 1319 | ||||
-rw-r--r-- | lib/mqueue.h | 393 | ||||
-rw-r--r-- | lib/network.c | 167 | ||||
-rw-r--r-- | lib/network.h | 6 | ||||
-rw-r--r-- | lib/node_type.h | 81 | ||||
-rw-r--r-- | lib/pid_output.c | 98 | ||||
-rw-r--r-- | lib/plist.c | 2447 | ||||
-rw-r--r-- | lib/plist.h | 48 | ||||
-rw-r--r-- | lib/prefix.c | 241 | ||||
-rw-r--r-- | lib/prefix.h | 86 | ||||
-rw-r--r-- | lib/privs.c | 400 | ||||
-rw-r--r-- | lib/privs.h | 4 | ||||
-rw-r--r-- | lib/pthread_safe.c | 516 | ||||
-rw-r--r-- | lib/pthread_safe.h | 46 | ||||
-rw-r--r-- | lib/qafi_safi.h | 172 | ||||
-rw-r--r-- | lib/qfstring.c | 1204 | ||||
-rw-r--r-- | lib/qfstring.h | 163 | ||||
-rw-r--r-- | lib/qiovec.c | 261 | ||||
-rw-r--r-- | lib/qiovec.h | 99 | ||||
-rw-r--r-- | lib/qlib_init.c | 98 | ||||
-rw-r--r-- | lib/qlib_init.h | 40 | ||||
-rw-r--r-- | lib/qpnexus.c | 327 | ||||
-rw-r--r-- | lib/qpnexus.h | 158 | ||||
-rw-r--r-- | lib/qpselect.c | 1414 | ||||
-rw-r--r-- | lib/qpselect.h | 241 | ||||
-rw-r--r-- | lib/qpthreads.c | 775 | ||||
-rw-r--r-- | lib/qpthreads.h | 443 | ||||
-rw-r--r-- | lib/qstring.c | 511 | ||||
-rw-r--r-- | lib/qstring.h | 468 | ||||
-rw-r--r-- | lib/qtime.c | 205 | ||||
-rw-r--r-- | lib/qtime.h | 333 | ||||
-rw-r--r-- | lib/qtimers.c | 450 | ||||
-rw-r--r-- | lib/qtimers.h | 174 | ||||
-rw-r--r-- | lib/routemap.c | 206 | ||||
-rw-r--r-- | lib/routemap.h | 27 | ||||
-rw-r--r-- | lib/sigevent.c | 109 | ||||
-rw-r--r-- | lib/sigevent.h | 9 | ||||
-rw-r--r-- | lib/smux.c | 115 | ||||
-rw-r--r-- | lib/sockopt.c | 1222 | ||||
-rw-r--r-- | lib/sockopt.h | 50 | ||||
-rw-r--r-- | lib/sockunion.c | 1289 | ||||
-rw-r--r-- | lib/sockunion.h | 103 | ||||
-rw-r--r-- | lib/stream.c | 365 | ||||
-rw-r--r-- | lib/stream.h | 24 | ||||
-rw-r--r-- | lib/symtab.c | 1186 | ||||
-rw-r--r-- | lib/symtab.h | 320 | ||||
-rw-r--r-- | lib/thread.c | 779 | ||||
-rw-r--r-- | lib/thread.h | 24 | ||||
-rw-r--r-- | lib/uty.h | 239 | ||||
-rw-r--r-- | lib/vector.c | 1289 | ||||
-rw-r--r-- | lib/vector.h | 348 | ||||
-rw-r--r-- | lib/vio_fifo.c | 1165 | ||||
-rw-r--r-- | lib/vio_fifo.h | 205 | ||||
-rw-r--r-- | lib/vio_lines.c | 380 | ||||
-rw-r--r-- | lib/vio_lines.h | 91 | ||||
-rw-r--r-- | lib/vty.c | 3732 | ||||
-rw-r--r-- | lib/vty.h | 287 | ||||
-rw-r--r-- | lib/vty_cli.c | 2705 | ||||
-rw-r--r-- | lib/vty_cli.h | 49 | ||||
-rw-r--r-- | lib/vty_io.c | 2742 | ||||
-rw-r--r-- | lib/vty_io.h | 310 | ||||
-rw-r--r-- | lib/workqueue.c | 295 | ||||
-rw-r--r-- | lib/workqueue.h | 114 | ||||
-rw-r--r-- | lib/zassert.h | 36 | ||||
-rw-r--r-- | lib/zclient.c | 332 | ||||
-rw-r--r-- | lib/zclient.h | 16 | ||||
-rw-r--r-- | lib/zebra.h | 65 |
101 files changed, 36137 insertions, 8106 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 315e919b..e88c5998 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,7 +12,11 @@ libzebra_la_SOURCES = \ sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \ filter.c routemap.c distribute.c stream.c str.c log.c plist.c \ zclient.c sockopt.c smux.c md5.c if_rmap.c keychain.c privs.c \ - sigevent.c pqueue.c jhash.c memtypes.c workqueue.c + sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \ + qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \ + command_queue.c qlib_init.c pthread_safe.c list_util.c \ + vty_io.c vty_cli.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ + qiovec.c qfstring.c errno_names.c BUILT_SOURCES = memtypes.h route_types.h @@ -27,7 +31,13 @@ pkginclude_HEADERS = \ str.h stream.h table.h thread.h vector.h version.h vty.h zebra.h \ plist.h zclient.h sockopt.h smux.h md5.h if_rmap.h keychain.h \ privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \ - workqueue.h route_types.h + workqueue.h route_types.h symtab.h heap.h \ + qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \ + command_queue.h qlib_init.h qafi_safi.h \ + confirm.h miyagi.h pthread_safe.h list_util.h node_type.h uty.h \ + vty_io.h vty_cli.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ + qiovec.h qfstring.h errno_names.h \ + route_types.h command_execute.h EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt diff --git a/lib/buffer.c b/lib/buffer.c index f19a9e0c..816b0d1d 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -1,5 +1,5 @@ /* - * Buffering of output and input. + * Buffering of output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -8,7 +8,7 @@ * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. - * + * * GNU Zebra 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 @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Boston, MA 02111-1307, USA. */ #include <zebra.h> @@ -29,18 +29,6 @@ #include <stddef.h> - -/* Buffer master. */ -struct buffer -{ - /* Data list. */ - struct buffer_data *head; - struct buffer_data *tail; - - /* Size of each buffer_data chunk. */ - size_t size; -}; - /* Data container. */ struct buffer_data { @@ -67,11 +55,12 @@ struct buffer_data /* Make new buffer. */ struct buffer * -buffer_new (size_t size) +buffer_init_new (struct buffer* b, size_t size) { - struct buffer *b; - - b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer)); + if (b == NULL) + b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer)); + else + memset(b, 0, sizeof (struct buffer)) ; if (size) b->size = size; @@ -89,6 +78,13 @@ buffer_new (size_t size) return b; } +/* Make new buffer. */ +struct buffer * +buffer_new (size_t size) +{ + return buffer_init_new(NULL, size); +} + /* Free buffer. */ void buffer_free (struct buffer *b) @@ -133,7 +129,7 @@ buffer_reset (struct buffer *b) { struct buffer_data *data; struct buffer_data *next; - + for (data = b->head; data; data = next) { next = data->next; @@ -148,7 +144,8 @@ buffer_add (struct buffer *b) { struct buffer_data *d; - d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(struct buffer_data, data[b->size])); + typedef struct buffer_data buffer_data_t ; /* stop Eclipse whinging */ + d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(buffer_data_t, data[b->size])); d->cp = d->sp = 0; d->next = NULL; @@ -169,7 +166,7 @@ buffer_put(struct buffer *b, const void *p, size_t size) const char *ptr = p; /* We use even last one byte of data buffer. */ - while (size) + while (size) { size_t chunk; @@ -226,7 +223,7 @@ buffer_flush_all (struct buffer *b, int fd) /* Flush enough data to fill a terminal window of the given scene (used only by vty telnet interface). */ buffer_status_t -buffer_flush_window (struct buffer *b, int fd, int width, int height, +buffer_flush_window (struct buffer *b, int fd, int width, int height, int erase_flag, int no_more_flag) { int nbytes; @@ -353,7 +350,7 @@ buffer_flush_window (struct buffer *b, int fd, int width, int height, if ((nbytes = writev(fd, c_iov, iov_size)) < 0) { zlog_warn("%s: writev to fd %d failed: %s", - __func__, fd, safe_strerror(errno)); + __func__, fd, errtoa(errno, 0).str) ; break; } @@ -365,7 +362,7 @@ buffer_flush_window (struct buffer *b, int fd, int width, int height, #else /* IOV_MAX */ if ((nbytes = writev (fd, iov, iov_index)) < 0) zlog_warn("%s: writev to fd %d failed: %s", - __func__, fd, safe_strerror(errno)); + __func__, fd, errtoa(errno, 0).str); #endif /* IOV_MAX */ /* Free printed buffer data. */ @@ -427,7 +424,7 @@ in one shot. */ /* Calling code should try again later. */ return BUFFER_PENDING; zlog_warn("%s: write error on fd %d: %s", - __func__, fd, safe_strerror(errno)); + __func__, fd, errtoa(errno, 0).str); return BUFFER_ERROR; } @@ -482,7 +479,7 @@ buffer_write(struct buffer *b, int fd, const void *p, size_t size) else { zlog_warn("%s: write error on fd %d: %s", - __func__, fd, safe_strerror(errno)); + __func__, fd, errtoa(errno, 0).str); return BUFFER_ERROR; } } diff --git a/lib/buffer.h b/lib/buffer.h index 6c3dc76a..132fe155 100644 --- a/lib/buffer.h +++ b/lib/buffer.h @@ -1,5 +1,5 @@ /* - * Buffering to output and input. + * Buffering to output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -23,11 +23,22 @@ #ifndef _ZEBRA_BUFFER_H #define _ZEBRA_BUFFER_H +/* Buffer master. */ +struct buffer +{ + /* Data list. */ + struct buffer_data *head; + struct buffer_data *tail; + + /* Size of each buffer_data chunk. */ + size_t size; +}; /* Create a new buffer. Memory will be allocated in chunks of the given size. If the argument is 0, the library will supply a reasonable default size suitable for buffering socket I/O. */ extern struct buffer *buffer_new (size_t); +struct buffer *buffer_init_new (struct buffer* b, size_t size) ; /* Free all data in the buffer. */ extern void buffer_reset (struct buffer *); @@ -73,7 +84,7 @@ typedef enum extern buffer_status_t buffer_write(struct buffer *, int fd, const void *, size_t); -/* This function attempts to flush some (but perhaps not all) of +/* This function attempts to flush some (but perhaps not all) of the queued data to the given file descriptor. */ extern buffer_status_t buffer_flush_available(struct buffer *, int fd); @@ -88,7 +99,7 @@ extern buffer_status_t buffer_flush_all (struct buffer *, int fd); /* Attempt to write enough data to the given fd to fill a window of the given width and height (and remove the data written from the buffer). - If !no_more, then a message saying " --More-- " is appended. + If !no_more, then a message saying " --More-- " is appended. If erase is true, then first overwrite the previous " --More-- " message with spaces. diff --git a/lib/command.c b/lib/command.c index 264e0f7b..67a9ab04 100644 --- a/lib/command.c +++ b/lib/command.c @@ -1,11 +1,11 @@ /* $Id$ - + Command interpreter routine for virtual terminal [aka TeletYpe] Copyright (C) 1997, 98, 99 Kunihiro Ishiguro This file is part of GNU Zebra. - + GNU Zebra 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, or (at your @@ -30,8 +30,12 @@ Boston, MA 02111-1307, USA. */ #include "thread.h" #include "vector.h" #include "vty.h" +#include "uty.h" +#include "qstring.h" #include "command.h" +#include "command_execute.h" #include "workqueue.h" +#include "command_queue.h" /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ @@ -89,11 +93,17 @@ Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ").\r\n\ \r\n"; +#ifdef QDEBUG +const char* debug_banner = + QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG_NAME " " + __DATE__ " " __TIME__ ; +#endif + static const struct facility_map { int facility; const char *name; size_t match; -} syslog_facilities[] = +} syslog_facilities[] = { { LOG_KERN, "kern", 1 }, { LOG_USER, "user", 2 }, @@ -145,7 +155,7 @@ static int level_match(const char *s) { int level ; - + for ( level = 0 ; zlog_priority [level] != NULL ; level ++ ) if (!strncmp (s, zlog_priority[level], 2)) return level; @@ -160,11 +170,11 @@ print_version (const char *progname) printf ("%s\n", QUAGGA_COPYRIGHT); } - + /* Utility function to concatenate argv argument into a single string with inserting ' ' character between each argument. */ char * -argv_concat (const char **argv, int argc, int shift) +argv_concat (const char* const* argv, int argc, int shift) { int i; size_t len; @@ -188,16 +198,7 @@ argv_concat (const char **argv, int argc, int shift) return str; } -/* Install top node of command vector. */ -void -install_node (struct cmd_node *node, - int (*func) (struct vty *)) -{ - vector_set_index (cmdvec, node->node, node); - node->func = func; - node->cmd_vector = vector_init (VECTOR_MIN_SIZE); -} - +#if 0 //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /* Compare two command's string. Used in sort_node (). */ static int cmp_node (const void *p, const void *q) @@ -217,102 +218,245 @@ cmp_desc (const void *p, const void *q) return strcmp (a->cmd, b->cmd); } +#endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +/* Install top node of command vector. */ +void +install_node (struct cmd_node *node, + int (*func) (struct vty *)) +{ + vector_set_index (cmdvec, node->node, node); + node->func = func; + node->cmd_vector = vector_init (0); +} + +/* Compare two command's string. Used in sort_node (). */ +static int +cmp_node (const struct cmd_element **a, const struct cmd_element **b) +{ + return strcmp ((*a)->string, (*b)->string); +} + +static int +cmp_desc (const struct desc **a, const struct desc **b) +{ + return strcmp ((*a)->cmd, (*b)->cmd); +} + /* Sort each node's command element according to command string. */ void sort_node () { - unsigned int i, j; - struct cmd_node *cnode; - vector descvec; - struct cmd_element *cmd_element; + unsigned int i ; - for (i = 0; i < vector_active (cmdvec); i++) - if ((cnode = vector_slot (cmdvec, i)) != NULL) - { - vector cmd_vector = cnode->cmd_vector; - qsort (cmd_vector->index, vector_active (cmd_vector), - sizeof (void *), cmp_node); + for (i = 0; i < vector_length(cmdvec); i++) + { + struct cmd_node *cnode; + vector cmd_vector ; + unsigned int j; - for (j = 0; j < vector_active (cmd_vector); j++) - if ((cmd_element = vector_slot (cmd_vector, j)) != NULL - && vector_active (cmd_element->strvec)) - { - descvec = vector_slot (cmd_element->strvec, - vector_active (cmd_element->strvec) - 1); - qsort (descvec->index, vector_active (descvec), - sizeof (void *), cmp_desc); - } - } -} + cnode = vector_get_item(cmdvec, i) ; -/* Breaking up string into each command piece. I assume given - character is separated by a space character. Return value is a - vector which includes char ** data element. */ -vector -cmd_make_strvec (const char *string) + if (cnode == NULL) + continue ; + + cmd_vector = cnode->cmd_vector; + if (cmd_vector == NULL) + continue ; + + vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ; + + for (j = 0; j < vector_length(cmd_vector); j++) + { + struct cmd_element *cmd_element ; + vector descvec ; + + cmd_element = vector_get_item (cmd_vector, j); + if (cmd_element == NULL) + continue ; + + descvec = vector_get_last_item(cmd_element->strvec) ; + if (descvec == NULL) + continue ; + + vector_sort(descvec, (vector_sort_cmp*)cmp_desc) ; + } ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Take string and break it into tokens. + * + * Discards leading and trailing white-space. + * + * Treats lines that start with '!' or '#' (after any leading white-space) + * as empty -- these are comment lines. + * + * Tokens are non-whitespace separated by one or more white-space. + * + * White-space is anything that isspace() thinks is a space. (Which in the + * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) + * + * Returns: NULL => empty line (after white-space trimming) or comment line. + * otherwise: is vector containing one or more tokens. + * + * .... + * + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the caller's responsibility to release the vector and its contents, + * see cmd_free_strvec(). + */ +static vector +cmd_make_vline(vector vline, qstring qs, const char *string) { - const char *cp, *start; - char *token; - int strlen; - vector strvec; - + char *token, *tp ; + const char *cp, *sp, *ep, *op ; + + /* Reset any existing vline, and empty the qstring if given. */ + if (vline != NULL) + vector_set_length(vline, 0) ; + + qs_clear(qs) ; + + /* Strip leading and trailing white-space and deal with empty or effectively + * empty lines -- comment lines are treated as effectively empty. + */ + cp = string; + if (string == NULL) return NULL; - - cp = string; - /* Skip white spaces. */ - while (isspace ((int) *cp) && *cp != '\0') + while (isspace((int) *cp)) cp++; - /* Return if there is only white spaces */ - if (*cp == '\0') - return NULL; + ep = cp + strlen(cp) ; - if (*cp == '!' || *cp == '#') + while ((ep > cp) && (isspace((int)*(ep - 1)))) + --ep ; + + if ((cp == ep) || (*cp == '!') || (*cp == '#')) return NULL; - /* Prepare return vector. */ - strvec = vector_init (VECTOR_MIN_SIZE); + /* Prepare return vector -- expect some reasonable number of tokens. */ + if (vline == NULL) + vline = vector_init(10) ; - /* Copy each command piece and set into vector. */ - while (1) + /* If writing the words to a qstring, copy the body of the original (less + * any leading/trailing whitespace) to the qstring and '\0' terminate. + */ + if (qs != NULL) { - start = cp; - while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') && - *cp != '\0') - cp++; - strlen = cp - start; - token = XMALLOC (MTYPE_STRVEC, strlen + 1); - memcpy (token, start, strlen); - *(token + strlen) = '\0'; - vector_set (strvec, token); - - while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') && - *cp != '\0') - cp++; - - if (*cp == '\0') - return strvec; + qs_set_n(qs, cp, ep - cp) ; + tp = (char*)qs->body ; /* start at the beginning */ } + else + tp = NULL ; /* not used, but not undefined */ + + op = cp ; /* original start position */ + + /* Now have string cp..ep with no leading/trailing whitespace. + * + * If using a qstring, a copy of that exists at tp, complete with terminating + * '\0'. Writes '\0' terminators after each word found -- overwriting first + * separating white-space or the '\0' at the end. + * + * If not using a qstring, construct a new MTYPE_STRVEC for each word. + */ + while (cp < ep) + { + while (isspace((int) *cp)) + cp++ ; /* skip white-space */ + + sp = cp ; + while ((cp < ep) && !isspace((int) *cp)) + cp++ ; /* eat token characters */ + + if (qs == NULL) + { + /* creating array of MTYPE_STRVEC */ + size_t len ; + + len = cp - sp ; + token = XMALLOC (MTYPE_STRVEC, len + 1); + memcpy (token, sp, len); + *(token + len) = '\0'; + } + else + { + /* using qstring */ + token = tp + (sp - op) ; /* token in qstring */ + *(tp + (cp - op)) = '\0' ; /* terminate */ + } ; + + vector_push_item(vline, token); + } ; + + return vline ; } -/* Free allocated string vector. */ -void -cmd_free_strvec (vector v) +/*------------------------------------------------------------------------------ + * Take string and break it into tokens. + * + * Discards leading and trailing white-space. + * + * Treats lines that start with '!' or '#' (after any leading white-space) + * as empty -- these are comment lines. + * + * Tokens are non-whitespace separated by one or more white-space. + * + * White-space is anything that isspace() thinks is a space. (Which in the + * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) + * + * Returns: NULL => empty line (after white-space trimming) or comment line. + * otherwise: is vector containing one or more tokens. + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the caller's responsibility to release the vector and its contents, + * see cmd_free_strvec(). + */ +extern vector +cmd_make_strvec (const char *string) { - unsigned int i; - char *cp; + return cmd_make_vline(NULL, NULL, string) ; +} ; + +/*------------------------------------------------------------------------------ + * Add given string to vector of strings. + * + * Create vector if required. + */ +extern vector +cmd_add_to_strvec (vector strvec, const char* str) +{ + if (strvec == NULL) + strvec = vector_init(1) ; - if (!v) - return; + vector_push_item(strvec, XSTRDUP(MTYPE_STRVEC, str)); - for (i = 0; i < vector_active (v); i++) - if ((cp = vector_slot (v, i)) != NULL) - XFREE (MTYPE_STRVEC, cp); + return strvec ; +} ; - vector_free (v); -} +/*------------------------------------------------------------------------------ + * Free allocated string vector (if any) and all its contents. + * + * Note that this is perfectly happy with strvec == NULL. + */ +extern void +cmd_free_strvec (vector strvec) +{ + char *cp; + + /* Note that vector_ream_free() returns NULL if strvec == NULL */ + while((cp = vector_ream_free(strvec)) != NULL) + XFREE (MTYPE_STRVEC, cp); +} ; + +/*----------------------------------------------------------------------------*/ /* Fetch next description. Used in cmd_make_descvec(). */ static char * @@ -321,7 +465,7 @@ cmd_desc_str (const char **string) const char *cp, *start; char *token; int strlen; - + cp = *string; if (cp == NULL) @@ -370,7 +514,7 @@ cmd_make_descvec (const char *string, const char *descstr) if (cp == NULL) return NULL; - allvec = vector_init (VECTOR_MIN_SIZE); + allvec = vector_init (0); while (1) { @@ -396,7 +540,7 @@ cmd_make_descvec (const char *string, const char *descstr) } cp++; } - + while (isspace ((int) *cp) && *cp != '\0') cp++; @@ -406,7 +550,7 @@ cmd_make_descvec (const char *string, const char *descstr) cp++; } - if (*cp == '\0') + if (*cp == '\0') return allvec; sp = cp; @@ -428,14 +572,14 @@ cmd_make_descvec (const char *string, const char *descstr) { if (multiple == 1) { - strvec = vector_init (VECTOR_MIN_SIZE); + strvec = vector_init (0); vector_set (allvec, strvec); } multiple++; } else { - strvec = vector_init (VECTOR_MIN_SIZE); + strvec = vector_init (0); vector_set (allvec, strvec); } vector_set (strvec, desc); @@ -449,23 +593,27 @@ cmd_cmdsize (vector strvec) { unsigned int i; int size = 0; - vector descvec; - struct desc *desc; - for (i = 0; i < vector_active (strvec); i++) - if ((descvec = vector_slot (strvec, i)) != NULL) + for (i = 0; i < vector_length(strvec); i++) { - if ((vector_active (descvec)) == 1 - && (desc = vector_slot (descvec, 0)) != NULL) - { - if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) - return size; - else - size++; - } - else - size++; - } + vector descvec; + + descvec = vector_get_item (strvec, i) ; + if (descvec == NULL) + continue ; + + if (vector_length(descvec) == 1) + { + struct desc *desc; + + desc = vector_get_item(descvec, 0) ; + if (desc != NULL) + if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) + break ; + } + size++; + } ; + return size; } @@ -475,7 +623,19 @@ cmd_prompt (enum node_type node) { struct cmd_node *cnode; - cnode = vector_slot (cmdvec, node); + assert(cmdvec != NULL) ; + assert(cmdvec->p_items != NULL) ; + + cnode = NULL ; + if (node < cmdvec->limit) + cnode = vector_get_item (cmdvec, node); + + if (cnode == NULL) + { + zlog_err("Could not find prompt for node %d for", node) ; + return NULL ; + } ; + return cnode->prompt; } @@ -484,14 +644,14 @@ void install_element (enum node_type ntype, struct cmd_element *cmd) { struct cmd_node *cnode; - + /* cmd_init hasn't been called */ if (!cmdvec) return; - - cnode = vector_slot (cmdvec, ntype); - if (cnode == NULL) + cnode = vector_get_item (cmdvec, ntype); + + if (cnode == NULL) { fprintf (stderr, "Command node %d doesn't exist, please check it\n", ntype); @@ -512,7 +672,7 @@ static const unsigned char itoa64[] = static void to64(char *s, long v, int n) { - while (--n >= 0) + while (--n >= 0) { *s++ = itoa64[v&0x3f]; v >>= 6; @@ -527,7 +687,7 @@ zencrypt (const char *passwd) char *crypt (const char *, const char *); gettimeofday(&tv,0); - + to64(&salt[0], random(), 3); to64(&salt[3], tv.tv_usec, 3); salt[5] = '\0'; @@ -539,15 +699,18 @@ zencrypt (const char *passwd) static int config_write_host (struct vty *vty) { + if (qpthreads_enabled) + vty_out (vty, "threaded%s", VTY_NEWLINE); + if (host.name) vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE); if (host.encrypt) { if (host.password_encrypt) - vty_out (vty, "password 8 %s%s", host.password_encrypt, VTY_NEWLINE); + vty_out (vty, "password 8 %s%s", host.password_encrypt, VTY_NEWLINE); if (host.enable_encrypt) - vty_out (vty, "enable password 8 %s%s", host.enable_encrypt, VTY_NEWLINE); + vty_out (vty, "enable password 8 %s%s", host.enable_encrypt, VTY_NEWLINE); } else { @@ -557,57 +720,57 @@ config_write_host (struct vty *vty) vty_out (vty, "enable password %s%s", host.enable, VTY_NEWLINE); } - if (zlog_default->default_lvl != LOG_DEBUG) + if (zlog_get_default_lvl(NULL) != LOG_DEBUG) { vty_out (vty, "! N.B. The 'log trap' command is deprecated.%s", VTY_NEWLINE); vty_out (vty, "log trap %s%s", - zlog_priority[zlog_default->default_lvl], VTY_NEWLINE); + zlog_priority[zlog_get_default_lvl(NULL)], VTY_NEWLINE); } - if (host.logfile && (zlog_default->maxlvl[ZLOG_DEST_FILE] != ZLOG_DISABLED)) + if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED)) { vty_out (vty, "log file %s", host.logfile); - if (zlog_default->maxlvl[ZLOG_DEST_FILE] != zlog_default->default_lvl) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL)) vty_out (vty, " %s", - zlog_priority[zlog_default->maxlvl[ZLOG_DEST_FILE]]); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]); vty_out (vty, "%s", VTY_NEWLINE); } - if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED) { vty_out (vty, "log stdout"); - if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != zlog_default->default_lvl) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL)) vty_out (vty, " %s", - zlog_priority[zlog_default->maxlvl[ZLOG_DEST_STDOUT]]); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]); vty_out (vty, "%s", VTY_NEWLINE); } - if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED) vty_out(vty,"no log monitor%s",VTY_NEWLINE); - else if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] != zlog_default->default_lvl) + else if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) != zlog_get_default_lvl(NULL)) vty_out(vty,"log monitor %s%s", - zlog_priority[zlog_default->maxlvl[ZLOG_DEST_MONITOR]],VTY_NEWLINE); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)],VTY_NEWLINE); - if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED) { vty_out (vty, "log syslog"); - if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != zlog_default->default_lvl) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL)) vty_out (vty, " %s", - zlog_priority[zlog_default->maxlvl[ZLOG_DEST_SYSLOG]]); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]); vty_out (vty, "%s", VTY_NEWLINE); } - if (zlog_default->facility != LOG_DAEMON) + if (zlog_get_facility(NULL) != LOG_DAEMON) vty_out (vty, "log facility %s%s", - facility_name(zlog_default->facility), VTY_NEWLINE); + facility_name(zlog_get_facility(NULL)), VTY_NEWLINE); - if (zlog_default->record_priority == 1) + if (zlog_get_record_priority(NULL) == 1) vty_out (vty, "log record-priority%s", VTY_NEWLINE); - if (zlog_default->timestamp_precision > 0) + if (zlog_get_timestamp_precision(NULL) > 0) vty_out (vty, "log timestamp precision %d%s", - zlog_default->timestamp_precision, VTY_NEWLINE); + zlog_get_timestamp_precision(NULL), VTY_NEWLINE); if (host.advanced) vty_out (vty, "service advanced-vty%s", VTY_NEWLINE); @@ -631,7 +794,7 @@ config_write_host (struct vty *vty) static vector cmd_node_vector (vector v, enum node_type ntype) { - struct cmd_node *cnode = vector_slot (v, ntype); + struct cmd_node *cnode = vector_get_item (v, ntype); return cnode->cmd_vector; } @@ -683,21 +846,41 @@ cmd_filter_by_symbol (char *command, char *symbol) } #endif +/*============================================================================== + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? + */ + /* Completion match types. */ -enum match_type +enum match_type { - no_match, + no_match, /* nope */ extend_match, + ipv4_prefix_match, ipv4_match, ipv6_prefix_match, ipv6_match, range_match, vararg_match, - partly_match, - exact_match + + partly_match, /* OK as far as it went */ + exact_match /* Syntactically complete */ }; +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address: + * + * 999.999.999.999 -- where no part may be > 255 + * + * TODO: cmd_ipv4_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ static enum match_type cmd_ipv4_match (const char *str) { @@ -755,6 +938,22 @@ cmd_ipv4_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv4 Prefix: + * + * 999.999.999.999/99 -- where no part may be > 255, + * and prefix length may not be > 32 + * + * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ static enum match_type cmd_ipv4_prefix_match (const char *str) { @@ -834,6 +1033,16 @@ cmd_ipv4_prefix_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address: + * + * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ + #define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" #define STATE_START 1 @@ -866,7 +1075,7 @@ cmd_ipv6_match (const char *str) * ::1.2.3.4 */ ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); - + if (ret == 1) return exact_match; @@ -952,6 +1161,19 @@ cmd_ipv6_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv6 Prefix: + * + * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? + * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ static enum match_type cmd_ipv6_prefix_match (const char *str) { @@ -1071,7 +1293,7 @@ cmd_ipv6_prefix_match (const char *str) if (mask < 0 || mask > 128) return no_match; - + /* I don't know why mask < 13 makes command match partly. Forgive me to make this comments. I Want to set static default route because of lack of function to originate default in ospf6d; sorry @@ -1085,6 +1307,14 @@ cmd_ipv6_prefix_match (const char *str) #endif /* HAVE_IPV6 */ +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: 1 => OK -- *including* empty string + * 0 => not a valid number, or not in required range + * (or invalid range !!) + */ + #define DECIMAL_STRLEN_MAX 10 static int @@ -1132,340 +1362,496 @@ cmd_range_match (const char *range, const char *str) return 1; } -/* Make completion match and return match type flag. */ +/*============================================================================== + * Command "filtering". + * + * The command parsing process starts with a (shallow) copy of the cmd_vector + * entry for the current "node". + * + * So cmd_v contains pointers to struct cmd_element values. When match fails, + * the pointer is set NULL -- so parsing is a process of reducing the cmd_v + * down to just the entries that match. + * + * Each cmd_element has a vector "strvec", which contains an entry for each + * "token" position. That entry is a vector containing the possible values at + * that position. + * + * + */ + +/*------------------------------------------------------------------------------ + * Make completion match and return match type flag. + * + * Takes: command -- address of candidate token + * cmd_v -- vector of commands that is being reduced/filtered + * index -- index of token (position in line -- 0 == first) + * + * Returns: any of the enum match_type values: + * + * no_match => no match of any kind + * + * extend_match => saw an optional token + * ipv4_prefix_match ) + * ipv4_match ) + * ipv6_prefix_match ) saw full or partial match for this + * ipv6_match ) + * range_match ) + * vararg_match ) + * + * partly_match => saw partial match for a keyword + * exact_match => saw exact match for a keyword + * + * Note that these return values are in ascending order of preference. So, + * if there are multiple possibilities at this position, will return the one + * furthest down this list. + */ static enum match_type -cmd_filter_by_completion (char *command, vector v, unsigned int index) +cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) { unsigned int i; - const char *str; - struct cmd_element *cmd_element; + unsigned int k; enum match_type match_type; - vector descvec; - struct desc *desc; match_type = no_match; - /* If command and cmd_element string does not match set NULL to vector */ - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) - { - if (index >= vector_active (cmd_element->strvec)) - vector_slot (v, i) = NULL; - else - { - unsigned int j; - int matched = 0; - - descvec = vector_slot (cmd_element->strvec, index); - - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - str = desc->cmd; - - if (CMD_VARARG (str)) - { - if (match_type < vararg_match) - match_type = vararg_match; - matched++; - } - else if (CMD_RANGE (str)) - { - if (cmd_range_match (str, command)) - { - if (match_type < range_match) - match_type = range_match; + /* If command and cmd_element string does not match, remove from vector */ + k = 0 ; + for (i = 0; i < vector_length (cmd_v); i++) + { + const char *str; + struct cmd_element *cmd_element; + vector descvec; + struct desc *desc; + unsigned int j; + int matched ; + + cmd_element = vector_get_item(cmd_v, i) ; + + /* Skip past cmd_v entries that have already been set NULL */ + if (cmd_element == NULL) + continue ; + + /* Discard cmd_v entry that has no token at the current position */ + descvec = vector_get_item(cmd_element->strvec, index) ; + if (descvec == NULL) + continue ; + + /* See if get any sort of match at current position */ + matched = 0 ; + for (j = 0; j < vector_length (descvec); j++) + { + desc = vector_get_item(descvec, j) ; + if (desc == NULL) + continue ; + + str = desc->cmd; + + if (CMD_VARARG (str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE (str)) + { + if (cmd_range_match (str, command)) + { + if (match_type < range_match) + match_type = range_match; - matched++; - } - } + matched++; + } + } #ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command)) - { - if (match_type < ipv6_match) - match_type = ipv6_match; + else if (CMD_IPV6 (str)) + { + if (cmd_ipv6_match (command)) + { + if (match_type < ipv6_match) + match_type = ipv6_match; - matched++; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command)) - { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; + matched++; + } + } + else if (CMD_IPV6_PREFIX (str)) + { + if (cmd_ipv6_prefix_match (command)) + { + if (match_type < ipv6_prefix_match) + match_type = ipv6_prefix_match; - matched++; - } - } + matched++; + } + } #endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command)) - { - if (match_type < ipv4_match) - match_type = ipv4_match; + else if (CMD_IPV4 (str)) + { + if (cmd_ipv4_match (command)) + { + if (match_type < ipv4_match) + match_type = ipv4_match; - matched++; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command)) - { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; - } - } - else - /* Check is this point's argument optional ? */ - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (match_type < extend_match) - match_type = extend_match; - matched++; - } - else if (strncmp (command, str, strlen (command)) == 0) - { - if (strcmp (command, str) == 0) - match_type = exact_match; - else - { - if (match_type < partly_match) - match_type = partly_match; - } - matched++; - } - } - if (!matched) - vector_slot (v, i) = NULL; - } - } - return match_type; -} + matched++; + } + } + else if (CMD_IPV4_PREFIX (str)) + { + if (cmd_ipv4_prefix_match (command)) + { + if (match_type < ipv4_prefix_match) + match_type = ipv4_prefix_match; + matched++; + } + } + else if (CMD_OPTION (str) || CMD_VARIABLE (str)) + /* Check is this point's argument optional ? */ + { + if (match_type < extend_match) + match_type = extend_match; + matched++; + } + else if (strncmp (command, str, strlen (command)) == 0) + { + if (strcmp (command, str) == 0) + match_type = exact_match; + else + { + if (match_type < partly_match) + match_type = partly_match; + } + matched++; + } ; + } ; + + /* Keep cmd_v entry that has a match at this position */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; -/* Filter vector by command character with index. */ + vector_set_length(cmd_v, k) ; /* discard what did not keep */ + + return match_type; +} ; + +/*------------------------------------------------------------------------------ + * Filter vector by command character with index. + * + * This appears to be identical to cmd_filter_by_completion(), except that + * when matching keywords, requires an exact match. + * + * TODO: see if can merge cmd_filter_by_completion() & cmd_filter_by_string() + */ static enum match_type -cmd_filter_by_string (char *command, vector v, unsigned int index) +cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) { - unsigned int i; - const char *str; - struct cmd_element *cmd_element; + unsigned int i ; + unsigned int k ; enum match_type match_type; - vector descvec; - struct desc *desc; match_type = no_match; - /* If command and cmd_element string does not match set NULL to vector */ - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) - { - /* If given index is bigger than max string vector of command, - set NULL */ - if (index >= vector_active (cmd_element->strvec)) - vector_slot (v, i) = NULL; - else - { - unsigned int j; - int matched = 0; + /* If command and cmd_element string do match, keep in vector */ + k = 0 ; + for (i = 0; i < vector_length(cmd_v); i++) + { + unsigned int j; + int matched ; + const char *str; + struct cmd_element *cmd_element; + vector descvec; + struct desc *desc; + + cmd_element = vector_get_item(cmd_v, i) ; + + /* Skip past NULL cmd_v entries (just in case) */ + if (cmd_element == NULL) + continue ; + + /* Discard cmd_v entry that has no token at the current position */ + descvec = vector_get_item (cmd_element->strvec, index) ; + if (descvec == NULL) + continue ; + + /* See if have a match against any of the current possibilities */ + matched = 0 ; + for (j = 0; j < vector_length(descvec); j++) + { + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; + + str = desc->cmd; + + if (CMD_VARARG (str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE (str)) + { + if (cmd_range_match (str, command)) + { + if (match_type < range_match) + match_type = range_match; + matched++; + } + } +#ifdef HAVE_IPV6 + else if (CMD_IPV6 (str)) + { + if (cmd_ipv6_match (command) == exact_match) + { + if (match_type < ipv6_match) + match_type = ipv6_match; + matched++; + } + } + else if (CMD_IPV6_PREFIX (str)) + { + if (cmd_ipv6_prefix_match (command) == exact_match) + { + if (match_type < ipv6_prefix_match) + match_type = ipv6_prefix_match; + matched++; + } + } +#endif /* HAVE_IPV6 */ + else if (CMD_IPV4 (str)) + { + if (cmd_ipv4_match (command) == exact_match) + { + if (match_type < ipv4_match) + match_type = ipv4_match; + matched++; + } + } + else if (CMD_IPV4_PREFIX (str)) + { + if (cmd_ipv4_prefix_match (command) == exact_match) + { + if (match_type < ipv4_prefix_match) + match_type = ipv4_prefix_match; + matched++; + } + } + else if (CMD_OPTION (str) || CMD_VARIABLE (str)) + { + if (match_type < extend_match) + match_type = extend_match; + matched++; + } + else + { + if (strcmp (command, str) == 0) + { + match_type = exact_match; + matched++; + } ; + } ; + } ; - descvec = vector_slot (cmd_element->strvec, index); + /* Keep cmd_element if have a match */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - str = desc->cmd; + vector_set_length(cmd_v, k) ; /* discard what did not keep */ - if (CMD_VARARG (str)) - { - if (match_type < vararg_match) - match_type = vararg_match; - matched++; - } - else if (CMD_RANGE (str)) - { - if (cmd_range_match (str, command)) - { - if (match_type < range_match) - match_type = range_match; - matched++; - } - } -#ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command) == exact_match) - { - if (match_type < ipv6_match) - match_type = ipv6_match; - matched++; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command) == exact_match) - { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; - matched++; - } - } -#endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command) == exact_match) - { - if (match_type < ipv4_match) - match_type = ipv4_match; - matched++; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command) == exact_match) - { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; - } - } - else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (match_type < extend_match) - match_type = extend_match; - matched++; - } - else - { - if (strcmp (command, str) == 0) - { - match_type = exact_match; - matched++; - } - } - } - if (!matched) - vector_slot (v, i) = NULL; - } - } return match_type; } -/* Check ambiguous match */ +/*------------------------------------------------------------------------------ + * Check for ambiguous match + * + * Given the best that cmd_filter_by_completion() or cmd_filter_by_string() + * found, check as follows: + * + * 1. discard all commands for which do not have the type of match selected. + * + * See above for the ranking of matches. + * + * 2. for "partial match", look out for matching more than one keyword, and + * return 1 if finds that. + * + * 3. for "range match", look out for matching more than one range, and + * return 1 if finds that. + * + * 4. for ipv4_prefix_match and ipv6_prefix_match, if get a "partial match", + * return 2. + * + * This appears to catch things which are supposed to be prefixes, but + * do not have a '/' or do not have any digits after the '/'. + * + * Takes: command -- address of candidate token + * cmd_v -- vector of commands that is being reduced/filtered + * index -- index of token (position in line -- 0 == first) + * type -- as returned by cmd_filter_by_completion() + * or cmd_filter_by_string() + * + * Returns: 0 => not ambiguous + * 1 => ambiguous -- the candidate token matches more than one + * keyword, or the candidate number matches more + * than one number range. + * 2 => partial match for ipv4_prefix or ipv6_prefix + * (missing '/' or no digits after '/'). + * + * NB: it is assumed that cannot find both 1 and 2 states. But in any case, + * returns 1 in preference. + */ static int -is_cmd_ambiguous (char *command, vector v, int index, enum match_type type) +is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) { unsigned int i; - unsigned int j; - const char *str = NULL; - struct cmd_element *cmd_element; - const char *matched = NULL; - vector descvec; - struct desc *desc; + unsigned int k; + int ret ; - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) - { - int match = 0; + ret = 0 ; /* all's well so far */ + k = 0 ; /* nothing kept, yet */ + + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + struct cmd_element *cmd_element; + const char *str_matched ; + vector descvec; + struct desc *desc; + int matched ; + enum match_type mt ; + + cmd_element = vector_get_item (cmd_v, i) ; + + /* Skip past NULL cmd_v entries (just in case) */ + if (cmd_element == NULL) + continue ; + + /* The cmd_v entry MUST have a token at the current position */ + descvec = vector_get_item (cmd_element->strvec, index) ; + assert(descvec != NULL) ; + + /* See if have a match against any of the current possibilities + * + * str_matched is set the first time get a partial string match, + * or the first time get a number range match. + * + * If get a second partial string match or number range match, then + * unless + */ + str_matched = NULL ; + matched = 0; + for (j = 0; j < vector_length (descvec); j++) + { + enum match_type ret; + const char *str ; - descvec = vector_slot (cmd_element->strvec, index); + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - enum match_type ret; - - str = desc->cmd; + str = desc->cmd; + + switch (type) + { + case exact_match: + if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) + && strcmp (command, str) == 0) + matched++; + break; + + case partly_match: + if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) + && strncmp (command, str, strlen (command)) == 0) + { + if (str_matched && (strcmp (str_matched, str) != 0)) + ret = 1; /* There is ambiguous match. */ + else + str_matched = str; + matched++; + } + break; + + case range_match: + if (cmd_range_match (str, command)) + { + if (str_matched && strcmp (str_matched, str) != 0) + ret = 1; + else + str_matched = str; + matched++; + } + break; - switch (type) - { - case exact_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strcmp (command, str) == 0) - match++; - break; - case partly_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strncmp (command, str, strlen (command)) == 0) - { - if (matched && strcmp (matched, str) != 0) - return 1; /* There is ambiguous match. */ - else - matched = str; - match++; - } - break; - case range_match: - if (cmd_range_match (str, command)) - { - if (matched && strcmp (matched, str) != 0) - return 1; - else - matched = str; - match++; - } - break; #ifdef HAVE_IPV6 - case ipv6_match: - if (CMD_IPV6 (str)) - match++; - break; - case ipv6_prefix_match: - if ((ret = cmd_ipv6_prefix_match (command)) != no_match) - { - if (ret == partly_match) - return 2; /* There is incomplete match. */ + case ipv6_match: + if (CMD_IPV6 (str)) + matched++; + break; - match++; - } - break; + case ipv6_prefix_match: + if ((mt = cmd_ipv6_prefix_match (command)) != no_match) + { + if (mt == partly_match) + if (ret != 1) + ret = 2; /* There is incomplete match. */ + + matched++; + } + break; #endif /* HAVE_IPV6 */ - case ipv4_match: - if (CMD_IPV4 (str)) - match++; - break; - case ipv4_prefix_match: - if ((ret = cmd_ipv4_prefix_match (command)) != no_match) - { - if (ret == partly_match) - return 2; /* There is incomplete match. */ - match++; - } - break; - case extend_match: - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - match++; - break; - case no_match: - default: - break; - } - } - if (!match) - vector_slot (v, i) = NULL; - } - return 0; -} + case ipv4_match: + if (CMD_IPV4 (str)) + matched++; + break; -/* If src matches dst return dst string, otherwise return NULL */ + case ipv4_prefix_match: + if ((mt = cmd_ipv4_prefix_match (command)) != no_match) + { + if (mt == partly_match) + if (ret != 1) + ret = 2; /* There is incomplete match. */ + + matched++; + } + break; + + case extend_match: + if (CMD_OPTION (str) || CMD_VARIABLE (str)) + matched++; + break; + + case no_match: + default: + break; + } ; + } ; + + /* Keep cmd_element if have a match */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; + + vector_set_length(cmd_v, k) ; /* discard what did not keep */ + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * If src matches dst return dst string, otherwise return NULL + * + * Returns NULL if dst is an option, variable of vararg. + * + * NULL or empty src are deemed to match. + */ static const char * cmd_entry_function (const char *src, const char *dst) { - /* Skip variable arguments. */ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst) || - CMD_IPV4 (dst) || CMD_IPV4_PREFIX (dst) || CMD_RANGE (dst)) + if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) return NULL; - /* In case of 'command \t', given src is NULL string. */ - if (src == NULL) + if ((src == NULL) || (*src == '\0')) return dst; - /* Matched with input string. */ if (strncmp (src, dst, strlen (src)) == 0) return dst; @@ -1537,16 +1923,20 @@ cmd_entry_function_desc (const char *src, const char *dst) return NULL; } -/* Check same string element existence. If it isn't there return - 1. */ -static int +/*------------------------------------------------------------------------------ + * Check same string element existence. + * + * Returns: 0 => found same string in the vector + * 1 => NOT found same string in the vector + */ +static bool cmd_unique_string (vector v, const char *str) { unsigned int i; char *match; - for (i = 0; i < vector_active (v); i++) - if ((match = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_length (v); i++) + if ((match = vector_get_item (v, i)) != NULL) if (strcmp (match, str) == 0) return 0; return 1; @@ -1554,35 +1944,29 @@ cmd_unique_string (vector v, const char *str) /* Compare string to description vector. If there is same string return 1 else return 0. */ -static int +static bool desc_unique_string (vector v, const char *str) { unsigned int i; struct desc *desc; - for (i = 0; i < vector_active (v); i++) - if ((desc = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_length (v); i++) + if ((desc = vector_get_item (v, i)) != NULL) if (strcmp (desc->cmd, str) == 0) return 1; return 0; } -static int +static bool cmd_try_do_shortcut (enum node_type node, char* first_word) { - if ( first_word != NULL && - node != AUTH_NODE && - node != VIEW_NODE && - node != AUTH_ENABLE_NODE && - node != ENABLE_NODE && - node != RESTRICTED_NODE && - 0 == strcmp( "do", first_word ) ) - return 1; - return 0; + return (node >= MIN_DO_SHORTCUT_NODE) + && (first_word != NULL) + && (strcmp( "do", first_word) == 0) ? 1 : 0 ; } /* '?' describe command support. */ static vector -cmd_describe_command_real (vector vline, struct vty *vty, int *status) +cmd_describe_command_real (vector vline, int node, int *status) { unsigned int i; vector cmd_vector; @@ -1595,16 +1979,16 @@ cmd_describe_command_real (vector vline, struct vty *vty, int *status) char *command; /* Set index. */ - if (vector_active (vline) == 0) + if (vector_length (vline) == 0) { *status = CMD_ERR_NO_MATCH; return NULL; } else - index = vector_active (vline) - 1; - + index = vector_length (vline) - 1; + /* Make copy vector of current node's command vector. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node)); + cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); /* Prepare match vector */ matchvec = vector_init (INIT_MATCHVEC_SIZE); @@ -1612,29 +1996,29 @@ cmd_describe_command_real (vector vline, struct vty *vty, int *status) /* Filter commands. */ /* Only words precedes current word will be checked in this loop. */ for (i = 0; i < index; i++) - if ((command = vector_slot (vline, i))) + if ((command = vector_get_item (vline, i))) { match = cmd_filter_by_completion (command, cmd_vector, i); - + if (match == vararg_match) { struct cmd_element *cmd_element; vector descvec; unsigned int j, k; - for (j = 0; j < vector_active (cmd_vector); j++) - if ((cmd_element = vector_slot (cmd_vector, j)) != NULL - && (vector_active (cmd_element->strvec))) + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_element->strvec))) { - descvec = vector_slot (cmd_element->strvec, - vector_active (cmd_element->strvec) - 1); - for (k = 0; k < vector_active (descvec); k++) + descvec = vector_get_item (cmd_element->strvec, + vector_length (cmd_element->strvec) - 1); + for (k = 0; k < vector_length (descvec); k++) { - struct desc *desc = vector_slot (descvec, k); + struct desc *desc = vector_get_item (descvec, k); vector_set (matchvec, desc); } } - + vector_set (matchvec, &desc_cr); vector_free (cmd_vector); @@ -1660,53 +2044,58 @@ cmd_describe_command_real (vector vline, struct vty *vty, int *status) /* Prepare match vector */ /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - /* Make sure that cmd_vector is filtered based on current word */ - command = vector_slot (vline, index); + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (vline, index); if (command) match = cmd_filter_by_completion (command, cmd_vector, index); /* Make description vector. */ - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i)) != NULL) - { - vector strvec = cmd_element->strvec; + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector strvec ; + + cmd_element = vector_get_item (cmd_vector, i) ; + if (cmd_element == NULL) + continue ; + + /* Ignore cmd_element if no tokens at index position. + * + * Deal with special case of possible <cr> completion. + */ + strvec = cmd_element->strvec; + if (index >= vector_length (strvec)) + { + if (command == NULL && index == vector_length (strvec)) + { + if (!desc_unique_string (matchvec, command_cr)) + vector_push_item(matchvec, &desc_cr); + } + continue ; + } ; + + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (strvec, index); + struct desc *desc; + + for (j = 0; j < vector_length (descvec); j++) + if ((desc = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, desc->cmd); + if (string) + { + /* Uniqueness check */ + if (!desc_unique_string (matchvec, string)) + vector_push_item(matchvec, desc); + } + } ; + } ; - /* if command is NULL, index may be equal to vector_active */ - if (command && index >= vector_active (strvec)) - vector_slot (cmd_vector, i) = NULL; - else - { - /* Check if command is completed. */ - if (command == NULL && index == vector_active (strvec)) - { - if (!desc_unique_string (matchvec, command_cr)) - vector_set (matchvec, &desc_cr); - } - else - { - unsigned int j; - vector descvec = vector_slot (strvec, index); - struct desc *desc; - - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - const char *string; - - string = cmd_entry_function_desc (command, desc->cmd); - if (string) - { - /* Uniqueness check */ - if (!desc_unique_string (matchvec, string)) - vector_set (matchvec, desc); - } - } - } - } - } vector_free (cmd_vector); - if (vector_slot (matchvec, 0) == NULL) + if (vector_length(matchvec) == 0) { vector_free (matchvec); *status = CMD_ERR_NO_MATCH; @@ -1717,269 +2106,285 @@ cmd_describe_command_real (vector vline, struct vty *vty, int *status) return matchvec; } +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct desc" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ vector -cmd_describe_command (vector vline, struct vty *vty, int *status) +cmd_describe_command (vector vline, int node, int *status) { vector ret; - if ( cmd_try_do_shortcut(vty->node, vector_slot(vline, 0) ) ) + if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) { - enum node_type onode; - vector shifted_vline; + vector shifted_vline; unsigned int index; - onode = vty->node; - vty->node = ENABLE_NODE; /* We can try it on enable node, cos' the vty is authenticated */ shifted_vline = vector_init (vector_count(vline)); /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) + for (index = 1; index < vector_length (vline); index++) { vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); } - ret = cmd_describe_command_real (shifted_vline, vty, status); + ret = cmd_describe_command_real (shifted_vline, ENABLE_NODE, status); vector_free(shifted_vline); - vty->node = onode; return ret; } - - return cmd_describe_command_real (vline, vty, status); + return cmd_describe_command_real (vline, node, status); } - -/* Check LCD of matched command. */ +/*------------------------------------------------------------------------------ + * Check LCD of matched command. + * + * Scan list of matched keywords, and by comparing them pair-wise, find the + * longest common leading substring. + * + * Returns: 0 if zero or one matched keywords + * length of longest common leading substring, otherwise. + */ static int -cmd_lcd (char **matched) +cmd_lcd (vector matchvec) { - int i; - int j; - int lcd = -1; - char *s1, *s2; - char c1, c2; + int n ; + int i ; + int lcd ; + char *sp, *sq, *ss ; - if (matched[0] == NULL || matched[1] == NULL) - return 0; + n = vector_end(matchvec) ; + if (n < 2) + return 0 ; + + ss = vector_get_item(matchvec, 0) ; + lcd = strlen(ss) ; - for (i = 1; matched[i] != NULL; i++) + for (i = 1 ; i < n ; i++) { - s1 = matched[i - 1]; - s2 = matched[i]; + sq = ss ; + ss = vector_get_item(matchvec, i) ; + sp = ss ; - for (j = 0; (c1 = s1[j]) && (c2 = s2[j]); j++) - if (c1 != c2) - break; + while ((*sp == *sq) && (*sp != '\0')) + { + ++sp ; + ++sq ; + } ; - if (lcd < 0) - lcd = j; - else - { - if (lcd > j) - lcd = j; - } + if (lcd > (sp - ss)) + lcd = (sp - ss) ; } return lcd; } -/* Command line completion support. */ -static char ** -cmd_complete_command_real (vector vline, struct vty *vty, int *status) +/*------------------------------------------------------------------------------ + * Command line completion support. + */ +static vector +cmd_complete_command_real (vector vline, int node, int *status) { unsigned int i; - vector cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node)); + unsigned int ivl ; + unsigned int last_ivl ; + vector cmd_v ; #define INIT_MATCHVEC_SIZE 10 vector matchvec; struct cmd_element *cmd_element; unsigned int index; - char **match_str; struct desc *desc; vector descvec; - char *command; - int lcd; + char *token; + int n ; - if (vector_active (vline) == 0) + /* Stop immediately if the vline is empty. */ + if (vector_length (vline) == 0) { - vector_free (cmd_vector); *status = CMD_ERR_NO_MATCH; return NULL; } - else - index = vector_active (vline) - 1; - /* First, filter by preceeding command string */ - for (i = 0; i < index; i++) - if ((command = vector_slot (vline, i))) - { - enum match_type match; - int ret; + /* Take (shallow) copy of cmdvec for given node. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); - /* First try completion match, if there is exactly match return 1 */ - match = cmd_filter_by_completion (command, cmd_vector, i); + /* First, filter upto, but excluding last token */ + last_ivl = vector_length (vline) - 1; - /* If there is exact match then filter ambiguous match else check - ambiguousness. */ - if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1) - { - vector_free (cmd_vector); - *status = CMD_ERR_AMBIGUOUS; - return NULL; - } - /* - else if (ret == 2) - { - vector_free (cmd_vector); + for (ivl = 0; ivl < last_ivl; ivl++) + { + enum match_type match; + int ret; + + /* TODO: does this test make any sense ? */ + if ((token = vector_get_item (vline, ivl)) == NULL) + continue ; + + /* First try completion match, return best kind of match */ + index = ivl ; + match = cmd_filter_by_completion (token, cmd_v, index) ; + + /* Eliminate all but the selected kind of match */ + ret = is_cmd_ambiguous (token, cmd_v, index, match) ; + + if (ret == 1) + { + /* ret == 1 => either token matches more than one keyword + * or token matches more than one number range + */ + vector_free (cmd_v); + *status = CMD_ERR_AMBIGUOUS; + return NULL; + } +#if 0 + /* For command completion purposes do not appear to care about + * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). + */ + else if (ret == 2) + { + vector_free (cmd_v); *status = CMD_ERR_NO_MATCH; return NULL; - } - */ + } +#endif } - - /* Prepare match vector. */ + + /* Prepare match vector. */ matchvec = vector_init (INIT_MATCHVEC_SIZE); - /* Now we got into completion */ - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i))) - { - const char *string; - vector strvec = cmd_element->strvec; + /* Now we got into completion */ + index = last_ivl ; + token = vector_get_item(vline, last_ivl) ; /* is now the last token */ - /* Check field length */ - if (index >= vector_active (strvec)) - vector_slot (cmd_vector, i) = NULL; - else - { - unsigned int j; + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + const char *string; - descvec = vector_slot (strvec, index); - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - if ((string = - cmd_entry_function (vector_slot (vline, index), - desc->cmd))) - if (cmd_unique_string (matchvec, string)) - vector_set (matchvec, XSTRDUP (MTYPE_TMP, string)); - } - } - } + if ((cmd_element = vector_get_item (cmd_v, i)) == NULL) + continue ; - /* We don't need cmd_vector any more. */ - vector_free (cmd_vector); + descvec = vector_get_item (cmd_element->strvec, index); + if (descvec == NULL) + continue ; + + for (j = 0; j < vector_length (descvec); j++) + { + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; + + string = cmd_entry_function(token, desc->cmd) ; + if ((string != NULL) && cmd_unique_string(matchvec, string)) + cmd_add_to_strvec (matchvec, string) ; + } ; + } ; + + n = vector_length(matchvec) ; /* number of entries in the matchvec */ + + /* We don't need cmd_v any more. */ + vector_free (cmd_v); /* No matched command */ - if (vector_slot (matchvec, 0) == NULL) + if (n == 0) { vector_free (matchvec); /* In case of 'command \t' pattern. Do you need '?' command at the end of the line. */ - if (vector_slot (vline, index) == '\0') - *status = CMD_ERR_NOTHING_TODO; + if (*token == '\0') + *status = CMD_COMPLETE_ALREADY; else *status = CMD_ERR_NO_MATCH; return NULL; } - /* Only one matched */ - if (vector_slot (matchvec, 1) == NULL) + /* Only one matched */ + if (n == 1) { - match_str = (char **) matchvec->index; - vector_only_wrapper_free (matchvec); *status = CMD_COMPLETE_FULL_MATCH; - return match_str; + return matchvec ; } - /* Make it sure last element is NULL. */ - vector_set (matchvec, NULL); - /* Check LCD of matched strings. */ - if (vector_slot (vline, index) != NULL) + /* Check LCD of matched strings. */ + if (token != NULL) { - lcd = cmd_lcd ((char **) matchvec->index); + unsigned lcd = cmd_lcd (matchvec) ; - if (lcd) + if (lcd != 0) { - int len = strlen (vector_slot (vline, index)); - - if (len < lcd) + if (strlen(token) < lcd) { char *lcdstr; lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); - memcpy (lcdstr, matchvec->index[0], lcd); + memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; lcdstr[lcd] = '\0'; - /* match_str = (char **) &lcdstr; */ + cmd_free_strvec(matchvec) ; /* discard the match vector */ - /* Free matchvec. */ - for (i = 0; i < vector_active (matchvec); i++) - { - if (vector_slot (matchvec, i)) - XFREE (MTYPE_STRVEC, vector_slot (matchvec, i)); - } - vector_free (matchvec); - - /* Make new matchvec. */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - vector_set (matchvec, lcdstr); - match_str = (char **) matchvec->index; - vector_only_wrapper_free (matchvec); + matchvec = vector_init (1); + vector_push_item(matchvec, lcdstr) ; *status = CMD_COMPLETE_MATCH; - return match_str; + return matchvec ; } } } - match_str = (char **) matchvec->index; - vector_only_wrapper_free (matchvec); *status = CMD_COMPLETE_LIST_MATCH; - return match_str; + return matchvec ; } -char ** -cmd_complete_command (vector vline, struct vty *vty, int *status) +/*------------------------------------------------------------------------------ + * Can the current command be completed ? + */ +extern vector +cmd_complete_command (vector vline, int node, int *status) { - char **ret; + vector ret; - if ( cmd_try_do_shortcut(vty->node, vector_slot(vline, 0) ) ) + if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) { - enum node_type onode; vector shifted_vline; unsigned int index; - onode = vty->node; - vty->node = ENABLE_NODE; /* We can try it on enable node, cos' the vty is authenticated */ shifted_vline = vector_init (vector_count(vline)); /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) + for (index = 1; index < vector_length (vline); index++) { vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); } - ret = cmd_complete_command_real (shifted_vline, vty, status); + ret = cmd_complete_command_real (shifted_vline, ENABLE_NODE, status); vector_free(shifted_vline); - vty->node = onode; return ret; } - - return cmd_complete_command_real (vline, vty, status); + return cmd_complete_command_real (vline, node, status); } -/* return parent node */ -/* MUST eventually converge on CONFIG_NODE */ +/*------------------------------------------------------------------------------ + * Return parent node + * + * All nodes > CONFIG_NODE are descended from CONFIG_NODE + */ enum node_type node_parent ( enum node_type node ) { - enum node_type ret; - assert (node > CONFIG_NODE); switch (node) @@ -1989,493 +2394,585 @@ node_parent ( enum node_type node ) case BGP_IPV4M_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: - ret = BGP_NODE; - break; + return BGP_NODE; + case KEYCHAIN_KEY_NODE: - ret = KEYCHAIN_NODE; - break; + return KEYCHAIN_NODE; + default: - ret = CONFIG_NODE; + return CONFIG_NODE; } - - return ret; } -/* Execute command by argument vline vector. */ -static int -cmd_execute_command_real (vector vline, struct vty *vty, - struct cmd_element **cmd) +/*------------------------------------------------------------------------------ + * Initialise a new struct cmd_parsed, allocating if required + */ +extern cmd_parsed +cmd_parse_init_new(cmd_parsed parsed) { - unsigned int i; - unsigned int index; - vector cmd_vector; - struct cmd_element *cmd_element; - struct cmd_element *matched_element; - unsigned int matched_count, incomplete_count; - int argc; - const char *argv[CMD_ARGC_MAX]; - enum match_type match = 0; - int varflag; - char *command; + if (parsed == NULL) + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; + else + memset(parsed, 0, sizeof(*parsed)) ; + + /* Zeroising the structure has set: + * + * cmd = NULL -- no command parsed, yet + * cnode -- no node set, yet + * + * do_shortcut -- false + * onode -- not material (do_shortcut is false) + * + * line = zeroised qstring -- empty + * words = zeroised qstring -- empty + * + * vline = zeroised vector -- empty + * + * so nothing else to do + */ - /* Make copy of command elements. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node)); + return parsed ; +} ; - for (index = 0; index < vector_active (vline); index++) - if ((command = vector_slot (vline, index))) - { - int ret; +/*------------------------------------------------------------------------------ + * Initialise a new struct cmd_parsed, allocating if required + */ +extern cmd_parsed +cmd_parse_reset(cmd_parsed parsed, bool free_structure) +{ + if (parsed != NULL) + { + qs_reset_keep(&parsed->words) ; + vector_reset_keep(&parsed->vline) ; - match = cmd_filter_by_completion (command, cmd_vector, index); + if (free_structure) + XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + else + cmd_parse_init_new(parsed) ; + } ; + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", if possible, ready for execution. + * + * If 'strict': use cmd_filter_by_string() + * otherwise: use cmd_filter_by_completion() + * + * If 'do': see if there is a 'do' at the front and proceed accordingly. + * + * If 'tree': move up the node tree to find command if not found in the + * current node. + */ + +static enum cmd_return_code +cmd_parse_this(struct cmd_parsed* parsed, bool strict) ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", or (if required) any of its ancestors. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * NB: parsed->cnode may have changed. + * + * NB: parsed->cmd->daemon => daemon + * + * CMD_EMPTY => empty or comment line + * + * NB: parsed->cmd == NULL + * + * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? + * + * CMD_ERR_NO_MATCH ) + * CMD_ERR_AMBIGUOUS ) failed to parse + * CMD_ERR_INCOMPLETE ) + * + * NB: if has failed to parse in the current node + * and in any ancestor nodes, returns the error + * from the attempt to parse in the current node + * (parsed->cnode which is returned unchanged). + * + * NB: the command line MUST be preserved until the parsed command is no + * longer required -- no copy is made. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + * + * See elsewhere for description of parsed structure. + */ +extern enum cmd_return_code +cmd_parse_command(struct vty* vty, enum cmd_parse_type type) +{ + enum cmd_return_code ret ; + enum cmd_return_code first_ret ; + cmd_parsed parsed ; - if (match == vararg_match) - break; - - ret = is_cmd_ambiguous (command, cmd_vector, index, match); + /* Initialise the parsed structure -- assuming no 'do' */ + if (vty->parsed == NULL) + vty->parsed = cmd_parse_init_new(NULL) ; + parsed = vty->parsed ; - if (ret == 1) - { - vector_free (cmd_vector); - return CMD_ERR_AMBIGUOUS; - } - else if (ret == 2) - { - vector_free (cmd_vector); - return CMD_ERR_NO_MATCH; - } - } + parsed->onode = parsed->cnode = vty->node ; - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; - incomplete_count = 0; + parsed->cmd = NULL ; + parsed->do_shortcut = 0 ; - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i))) - { - if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; -#if 0 - printf ("DEBUG: %s\n", cmd_element->string); -#endif - matched_count++; - } - else - { - incomplete_count++; - } - } + /* Parse the line into words -- set up parsed->words and parsed->vline */ + cmd_make_vline(&parsed->vline, &parsed->words, vty->buf) ; - /* Finish of using cmd_vector. */ - vector_free (cmd_vector); + if (vector_length(&parsed->vline) == 0) + return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - /* To execute command, matched_count must be 1. */ - if (matched_count == 0) + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if ((type & cmd_parse_do) && + cmd_try_do_shortcut(parsed->cnode, vector_get_item(&parsed->vline, 0))) { - if (incomplete_count) - return CMD_ERR_INCOMPLETE; - else - return CMD_ERR_NO_MATCH; - } + parsed->cnode = ENABLE_NODE ; + parsed->do_shortcut = 1 ; + } ; - if (matched_count > 1) - return CMD_ERR_AMBIGUOUS; + /* Try in the current node */ + ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; - /* Argument treatment */ - varflag = 0; - argc = 0; - - for (i = 0; i < vector_active (vline); i++) + if (ret != CMD_SUCCESS) { - if (varflag) - argv[argc++] = vector_slot (vline, i); - else - { - vector descvec = vector_slot (matched_element->strvec, i); + if (((type & cmd_parse_tree) == 0) || parsed->do_shortcut) + return ret ; /* done if not allowed to walk tree + or just tried to parse a 'do' */ - if (vector_active (descvec) == 1) - { - struct desc *desc = vector_slot (descvec, 0); + /* Try in parent node(s) */ + first_ret = ret ; - if (CMD_VARARG (desc->cmd)) - varflag = 1; - - if (varflag || CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) - argv[argc++] = vector_slot (vline, i); - } - else - argv[argc++] = vector_slot (vline, i); - } + while (ret != CMD_SUCCESS) + { + if (parsed->cnode <= CONFIG_NODE) + { + parsed->cnode = parsed->onode ; /* restore node state */ + return first_ret ; /* return original result */ + } ; + + parsed->cnode = node_parent(parsed->cnode) ; + ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; + } ; + } ; + + return vty->parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Work function for cmd_parse_command + * + * Takes a parsed structure, with the: + * + * cnode -- node to parse in + * vline -- the line broken into words + * do_shortcut -- true if first word is 'do' (to be ignored) + * + * and parses either strictly or with command completion. + * + * If successful, reduces the vline structure down to the variable portions, + * ie to the argv[] for the command function. + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH ) + * CMD_ERR_AMBIGUOUS ) failed to parse + * CMD_ERR_INCOMPLETE ) + */ +static enum cmd_return_code +cmd_parse_this(cmd_parsed parsed, bool strict) +{ + unsigned int i ; + unsigned int ivl ; + unsigned index ; + unsigned first ; + unsigned argc ; + vector cmd_v; + struct cmd_element *cmd_element; + struct cmd_element *matched_element; + unsigned int matched_count, incomplete_count; + enum match_type match = 0; + int varflag; + char *command; - if (argc >= CMD_ARGC_MAX) - return CMD_ERR_EXEED_ARGC_MAX; - } + /* Need length of vline, discounting the first entry if required */ + first = parsed->do_shortcut ? 1 : 0 ; - /* For vtysh execution. */ - if (cmd) - *cmd = matched_element; + assert(vector_length(&parsed->vline) >= first) ; + ivl = vector_length(&parsed->vline) - first ; - if (matched_element->daemon) - return CMD_SUCCESS_DAEMON; + /* Make copy of command elements. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, parsed->cnode)); - /* Execute matched command. */ - return (*matched_element->func) (matched_element, vty, argc, argv); -} + /* Look for an unambiguous result */ + for (index = 0 ; index < ivl; index++) + { + int ret ; -int -cmd_execute_command (vector vline, struct vty *vty, struct cmd_element **cmd, - int vtysh) { - int ret, saved_ret, tried = 0; - enum node_type onode, try_node; + command = vector_get_item(&parsed->vline, index + first) ; + if (command == NULL) + continue ; - onode = try_node = vty->node; + match = strict ? cmd_filter_by_string(command, cmd_v, index) + : cmd_filter_by_completion(command, cmd_v, index) ; - if ( cmd_try_do_shortcut(vty->node, vector_slot(vline, 0) ) ) - { - vector shifted_vline; - unsigned int index; + if (match == vararg_match) + break; - vty->node = ENABLE_NODE; - /* We can try it on enable node, cos' the vty is authenticated */ + ret = is_cmd_ambiguous (command, cmd_v, index, match); - shifted_vline = vector_init (vector_count(vline)); - /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) - { - vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); + if (ret != 0) + { + assert((ret == 1) || (ret == 2)) ; + vector_free (cmd_v); + return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; } + } ; - ret = cmd_execute_command_real (shifted_vline, vty, cmd); + /* Check matched count. */ + matched_element = NULL; + matched_count = 0; + incomplete_count = 0; - vector_free(shifted_vline); - vty->node = onode; - return ret; - } + for (i = 0; i < vector_length(cmd_v); i++) + { + cmd_element = vector_get_item(cmd_v, i) ; + if (cmd_element == NULL) + continue ; + if (match == vararg_match || index >= cmd_element->cmdsize) + { + matched_element = cmd_element; +#if 0 + printf ("DEBUG: %s\n", cmd_element->string); +#endif + matched_count++; + } + else + { + incomplete_count++; + } + } ; - saved_ret = ret = cmd_execute_command_real (vline, vty, cmd); + /* Finished with cmd_v. */ + vector_free (cmd_v); - if (vtysh) - return saved_ret; + /* To execute command, matched_count must be 1. */ + if (matched_count != 1) + { + if (matched_count == 0) + return (incomplete_count) ? CMD_ERR_INCOMPLETE : CMD_ERR_NO_MATCH ; + else + return CMD_ERR_AMBIGUOUS ; + } ; + + /* Found command -- process the arguments ready for execution */ + varflag = 0 ; + argc = 0 ; - /* This assumes all nodes above CONFIG_NODE are childs of CONFIG_NODE */ - while ( ret != CMD_SUCCESS && ret != CMD_WARNING - && vty->node > CONFIG_NODE ) + for (index = 0; index < ivl ; index++) { - try_node = node_parent(try_node); - vty->node = try_node; - ret = cmd_execute_command_real (vline, vty, cmd); - tried = 1; - if (ret == CMD_SUCCESS || ret == CMD_WARNING) + int take = varflag ; + + if (!varflag) { - /* succesfull command, leave the node as is */ - return ret; + vector descvec = vector_get_item (matched_element->strvec, index); + + if (vector_length (descvec) == 1) + { + struct desc *desc = vector_get_item (descvec, 0); + + if (CMD_VARARG (desc->cmd)) + take = varflag = 1 ; + else + take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; + } + else + take = 1 ; } - } - /* no command succeeded, reset the vty to the original node and - return the error for this node */ - if ( tried ) - vty->node = onode; - return saved_ret; -} -/* Execute command by argument readline. */ -int -cmd_execute_command_strict (vector vline, struct vty *vty, - struct cmd_element **cmd) -{ - unsigned int i; - unsigned int index; - vector cmd_vector; - struct cmd_element *cmd_element; - struct cmd_element *matched_element; - unsigned int matched_count, incomplete_count; - int argc; - const char *argv[CMD_ARGC_MAX]; - int varflag; - enum match_type match = 0; - char *command; + if (take) + vector_assign_item(&parsed->vline, argc++, index + first) ; + } ; - /* Make copy of command element */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node)); + vector_set_length(&parsed->vline, argc) ; /* set to new length */ - for (index = 0; index < vector_active (vline); index++) - if ((command = vector_slot (vline, index))) - { - int ret; - - match = cmd_filter_by_string (vector_slot (vline, index), - cmd_vector, index); + /* Everything checks out... ready to execute command */ + parsed->cmd = matched_element ; - /* If command meets '.VARARG' then finish matching. */ - if (match == vararg_match) - break; - - ret = is_cmd_ambiguous (command, cmd_vector, index, match); - if (ret == 1) - { - vector_free (cmd_vector); - return CMD_ERR_AMBIGUOUS; - } - if (ret == 2) - { - vector_free (cmd_vector); - return CMD_ERR_NO_MATCH; - } - } + return CMD_SUCCESS ; +} ; - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; - incomplete_count = 0; - for (i = 0; i < vector_active (cmd_vector); i++) - if (vector_slot (cmd_vector, i) != NULL) - { - cmd_element = vector_slot (cmd_vector, i); +/*------------------------------------------------------------------------------ + * Dispatch a parsed command. + * + * Returns: command return code. NB: may be CMD_QUEUED (unless no_queue). + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + */ +extern enum cmd_return_code +cmd_dispatch(struct vty* vty, bool no_queue) +{ + cmd_parsed parsed = vty->parsed ; + enum cmd_return_code ret ; - if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; - matched_count++; - } - else - incomplete_count++; - } + if (parsed->cmd == NULL) + return CMD_SUCCESS ; /* NULL commands are easy */ - /* Finish of using cmd_vector. */ - vector_free (cmd_vector); + vty->node = parsed->cnode ; - /* To execute command, matched_count must be 1. */ - if (matched_count == 0) + if (no_queue || !vty_cli_nexus) { - if (incomplete_count) - return CMD_ERR_INCOMPLETE; - else - return CMD_ERR_NO_MATCH; + ret = cmd_dispatch_call(vty) ; + cmd_post_command(vty, ret) ; } - - if (matched_count > 1) - return CMD_ERR_AMBIGUOUS; - - /* Argument treatment */ - varflag = 0; - argc = 0; - - for (i = 0; i < vector_active (vline); i++) + else { - if (varflag) - argv[argc++] = vector_slot (vline, i); + /* Don't do it now, but send to bgp qpthread */ + if (parsed->cmd->attr & CMD_ATTR_CALL) + cq_enqueue(vty, vty_cli_nexus) ; else - { - vector descvec = vector_slot (matched_element->strvec, i); + cq_enqueue(vty, vty_cmd_nexus) ; + + ret = CMD_QUEUED ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after executing command. + * + * This is separated out so that can be called when queued command completes. + * + * If have just processed a "do" shortcut command, and it has not set the + * vty->node to something other than ENABLE_NODE, then restore to the original + * state. + * + * Arguments: ret = CMD_XXXX -- NB: CMD_QUEUED => command revoked + */ +extern void +cmd_post_command(struct vty* vty, int ret) +{ + if (vty->parsed->do_shortcut) + { + if (vty->node == ENABLE_NODE) + vty->node = vty->parsed->onode ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Parse and execute a command. + * + * The command is given by vty->buf and vty->node. + * + * Uses vty->parsed. + * + * -- use strict/completion parsing, as required. + * + * -- parse in current node and in ancestors, as required + * + * If does not find in any ancestor, return error from current node. + * + * -- implement the "do" shortcut, as required + * + * If qpthreads_enabled, then may queue the command rather than execute it + * here. + * + * The vty->node may be changed during the execution of the command, and may + * be returned changed once the command has completed. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + */ +extern enum cmd_return_code +cmd_execute_command(struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) +{ + enum cmd_return_code ret ; + + /* Try to parse in vty->node or, if required, ancestors thereof. */ + ret = cmd_parse_command(vty, type) ; + + if (cmd != NULL) + *cmd = vty->parsed->cmd ; /* for vtysh */ + + if (ret == CMD_SUCCESS) + ret = cmd_dispatch(vty, 0) ; + else if (ret == CMD_EMPTY) + ret = CMD_SUCCESS ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Read configuration from file. + * + * In the qpthreads world this assumes that it is running with the vty + * locked, and that all commands are to be executed directly. + * + * If the 'first_cmd' argument is not NULL it is the address of the first + * command that is expected to appear. If the first command is not this, then + * the 'first_cmd' is called with argv == NULL (and argc == 0) to signal the + * command is being invoked by default. + * + * Command processing continues while CMD_SUCCESS is returned by the command + * parser and command execution. + * + * If 'ignore_warning' is set, then any CMD_WARNING returned by command + * execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned + * by command parsing (or in execution of any default 'first_cmd'). + * + * Returns: cmd_return_code for last command + * vty->buf is last line processed + * vty->lineno is number of last line processed (1 is first) + * + * If the file is empty, will return CMD_SUCCESS. + * + * Never returns CMD_EMPTY -- that counts as CMD_SUCCESS. + * + * If + * + * If return code is not CMD_SUCCESS, the the output buffering contains the + * output from the last command attempted. + */ +extern enum cmd_return_code +config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd, + qstring buf, bool ignore_warning) +{ + enum cmd_return_code ret; - if (vector_active (descvec) == 1) - { - struct desc *desc = vector_slot (descvec, 0); + vty->buf = buf->body ; + vty->lineno = 0 ; - if (CMD_VARARG (desc->cmd)) - varflag = 1; + ret = CMD_SUCCESS ; /* in case file is empty */ + vty_out_clear(vty) ; - if (varflag || CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) - argv[argc++] = vector_slot (vline, i); - } - else - argv[argc++] = vector_slot (vline, i); - } + while (fgets (buf->body, buf->size, fp)) + { + ++vty->lineno ; - if (argc >= CMD_ARGC_MAX) - return CMD_ERR_EXEED_ARGC_MAX; - } + /* Execute configuration command : this is strict match */ + ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - /* For vtysh execution. */ - if (cmd) - *cmd = matched_element; + if (ret == CMD_EMPTY) + continue ; /* skip empty/comment */ - if (matched_element->daemon) - return CMD_SUCCESS_DAEMON; + if (ret != CMD_SUCCESS) + break ; /* stop on *any* parsing issue */ - /* Now execute matched command */ - return (*matched_element->func) (matched_element, vty, argc, argv); -} + /* special handling before of first command */ + if (first_cmd != NULL) + { + if (first_cmd != vty->parsed->cmd) + { + ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; + if (ret != CMD_SUCCESS) + break ; /* stop on *any* issue with "default" */ + } ; + first_cmd = NULL ; + } -/* Configration make from file. */ -int -config_from_file (struct vty *vty, FILE *fp) -{ - int ret; - vector vline; + /* Standard command handling */ + ret = cmd_dispatch(vty, cmd_no_queue) ; - while (fgets (vty->buf, VTY_BUFSIZ, fp)) - { - vline = cmd_make_strvec (vty->buf); + if (ret != CMD_SUCCESS) + { + /* Ignore CMD_WARNING if required + * + * Ignore CMD_CLOSE at all times. + */ + if ( ((ret == CMD_WARNING) && ignore_warning) + || (ret == CMD_CLOSE) ) + ret = CMD_SUCCESS ; /* in case at EOF */ + else + break ; /* stop */ + } ; - /* In case of comment line */ - if (vline == NULL) - continue; - /* Execute configuration command : this is strict match */ - ret = cmd_execute_command_strict (vline, vty, NULL); + vty_out_clear(vty) ; + } ; - /* Try again with setting node to CONFIG_NODE */ - while (ret != CMD_SUCCESS && ret != CMD_WARNING - && ret != CMD_ERR_NOTHING_TODO && vty->node != CONFIG_NODE) - { - vty->node = node_parent(vty->node); - ret = cmd_execute_command_strict (vline, vty, NULL); - } + if (ret == CMD_EMPTY) + ret = CMD_SUCCESS ; /* OK if end on empty line */ - cmd_free_strvec (vline); + return ret ; +} ; - if (ret != CMD_SUCCESS && ret != CMD_WARNING - && ret != CMD_ERR_NOTHING_TODO) - return ret; - } - return CMD_SUCCESS; -} +/*----------------------------------------------------------------------------*/ /* Configration from terminal */ -DEFUN (config_terminal, +DEFUN_CALL (config_terminal, config_terminal_cmd, "configure terminal", "Configuration from vty interface\n" "Configuration terminal\n") { - if (vty_config_lock (vty)) - vty->node = CONFIG_NODE; - else - { - vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); - return CMD_WARNING; - } - return CMD_SUCCESS; + if (vty_config_lock (vty, CONFIG_NODE)) + return CMD_SUCCESS; + + vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); + return CMD_WARNING; } /* Enable command */ -DEFUN (enable, +DEFUN_CALL (enable, config_enable_cmd, "enable", "Turn on privileged mode command\n") { /* If enable password is NULL, change to ENABLE_NODE */ if ((host.enable == NULL && host.enable_encrypt == NULL) || - vty->type == VTY_SHELL_SERV) - vty->node = ENABLE_NODE; + vty_shell_serv(vty)) + vty_set_node(vty, ENABLE_NODE); else - vty->node = AUTH_ENABLE_NODE; + vty_set_node(vty, AUTH_ENABLE_NODE); return CMD_SUCCESS; } /* Disable command */ -DEFUN (disable, +DEFUN_CALL (disable, config_disable_cmd, "disable", "Turn off privileged mode command\n") { - if (vty->node == ENABLE_NODE) - vty->node = VIEW_NODE; + if (vty_get_node(vty) == ENABLE_NODE) + vty_set_node(vty, VIEW_NODE); return CMD_SUCCESS; } /* Down vty node level. */ -DEFUN (config_exit, +DEFUN_CALL (config_exit, config_exit_cmd, "exit", "Exit current mode and down to previous mode\n") { - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - if (vty_shell (vty)) - exit (0); - else - vty->status = VTY_CLOSE; - break; - case CONFIG_NODE: - vty->node = ENABLE_NODE; - vty_config_unlock (vty); - break; - case INTERFACE_NODE: - case ZEBRA_NODE: - case BGP_NODE: - case RIP_NODE: - case RIPNG_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case MASC_NODE: - case RMAP_NODE: - case VTY_NODE: - vty->node = CONFIG_NODE; - break; - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - vty->node = BGP_NODE; - break; - case KEYCHAIN_KEY_NODE: - vty->node = KEYCHAIN_NODE; - break; - default: - break; - } - return CMD_SUCCESS; + return vty_cmd_exit(vty) ; } /* quit is alias of exit. */ -ALIAS (config_exit, +ALIAS_CALL (config_exit, config_quit_cmd, "quit", "Exit current mode and down to previous mode\n") - + /* End of configuration. */ -DEFUN (config_end, +DEFUN_CALL (config_end, config_end_cmd, "end", "End current mode and change to enable mode.") { - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - vty_config_unlock (vty); - vty->node = ENABLE_NODE; - break; - default: - break; - } - return CMD_SUCCESS; + return vty_cmd_end(vty) ; } /* Show version. */ -DEFUN (show_version, +DEFUN_CALL (show_version, show_version_cmd, "show version", SHOW_STR @@ -2489,12 +2986,12 @@ DEFUN (show_version, } /* Help display function for all node. */ -DEFUN (config_help, +DEFUN_CALL (config_help, config_help_cmd, "help", "Description of the interactive help system\n") { - vty_out (vty, + vty_out (vty, "Quagga VTY provides advanced help feature. When you need help,%s\ anytime at the command line please press '?'.%s\ %s\ @@ -2513,39 +3010,38 @@ argument.%s\ } /* Help display function for all node. */ -DEFUN (config_list, +DEFUN_CALL (config_list, config_list_cmd, "list", "Print command list\n") { unsigned int i; - struct cmd_node *cnode = vector_slot (cmdvec, vty->node); + struct cmd_node *cnode = vector_get_item (cmdvec, vty_get_node(vty)); struct cmd_element *cmd; - for (i = 0; i < vector_active (cnode->cmd_vector); i++) - if ((cmd = vector_slot (cnode->cmd_vector, i)) != NULL - && !(cmd->attr == CMD_ATTR_DEPRECATED - || cmd->attr == CMD_ATTR_HIDDEN)) + for (i = 0; i < vector_length (cnode->cmd_vector); i++) + if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL + && !(cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN))) vty_out (vty, " %s%s", cmd->string, VTY_NEWLINE); return CMD_SUCCESS; } /* Write current configuration into file. */ -DEFUN (config_write_file, +DEFUN (config_write_file, config_write_file_cmd, - "write file", + "write file", "Write running configuration to memory, network, or terminal\n" "Write to configuration file\n") { unsigned int i; int fd; + int err; struct cmd_node *node; char *config_file; char *config_file_tmp = NULL; char *config_file_sav = NULL; int ret = CMD_WARNING; - struct vty *file_vty; /* Check and see if we are operating under vtysh configuration */ if (host.config == NULL) @@ -2557,7 +3053,7 @@ DEFUN (config_write_file, /* Get filename. */ config_file = host.config; - + config_file_sav = XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1); strcpy (config_file_sav, config_file); @@ -2566,7 +3062,7 @@ DEFUN (config_write_file, config_file_tmp = XMALLOC (MTYPE_TMP, strlen (config_file) + 8); sprintf (config_file_tmp, "%s.XXXXXX", config_file); - + /* Open file to configuration write. */ fd = mkstemp (config_file_tmp); if (fd < 0) @@ -2575,62 +3071,74 @@ DEFUN (config_write_file, VTY_NEWLINE); goto finished; } - + /* Make vty for configuration file. */ - file_vty = vty_new (); - file_vty->fd = fd; - file_vty->type = VTY_FILE; + vty_open_config_write(vty, fd) ; /* Config file header print. */ - vty_out (file_vty, "!\n! Zebra configuration saved from vty\n! "); - vty_time_print (file_vty, 1); - vty_out (file_vty, "!\n"); + vty_out (vty, "!\n! Zebra configuration saved from vty\n! "); + vty_time_print (vty, 1); + vty_out (vty, "!\n"); - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func) { - if ((*node->func) (file_vty)) - vty_out (file_vty, "!\n"); + if ((*node->func) (vty)) + vty_out (vty, "!\n"); } - vty_close (file_vty); + + err = vty_close_config_write(vty) ; + close(fd) ; + + if (err != 0) + { + vty_out (vty, "Failed while writing configuration file %s.%s", + config_file_tmp, VTY_NEWLINE); + goto finished; + } if (unlink (config_file_sav) != 0) if (errno != ENOENT) { - vty_out (vty, "Can't unlink backup configuration file %s.%s", config_file_sav, - VTY_NEWLINE); + vty_out (vty, "Can't unlink backup configuration file %s.%s", + config_file_sav, VTY_NEWLINE); goto finished; - } + } ; + if (link (config_file, config_file_sav) != 0) { - vty_out (vty, "Can't backup old configuration file %s.%s", config_file_sav, - VTY_NEWLINE); + vty_out (vty, "Can't backup old configuration file %s.%s", + config_file_sav, VTY_NEWLINE); goto finished; - } - sync (); + } ; + + sync () ; + if (unlink (config_file) != 0) { - vty_out (vty, "Can't unlink configuration file %s.%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Can't unlink configuration file %s.%s", + config_file, VTY_NEWLINE); goto finished; - } + } ; + if (link (config_file_tmp, config_file) != 0) { - vty_out (vty, "Can't save configuration file %s.%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Can't save configuration file %s.%s", + config_file, VTY_NEWLINE); goto finished; - } + } ; + sync (); - + if (chmod (config_file, CONFIGFILE_MASK) != 0) { - vty_out (vty, "Can't chmod configuration file %s: %s (%d).%s", - config_file, safe_strerror(errno), errno, VTY_NEWLINE); + vty_out (vty, "Can't chmod configuration file %s: %s (%s).\n", + config_file, errtostr(errno, 0).str, errtoname(errno, 0).str); goto finished; } - vty_out (vty, "Configuration saved to %s%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Configuration saved to %s\n", config_file); + ret = CMD_SUCCESS; finished: @@ -2640,20 +3148,20 @@ finished: return ret; } -ALIAS (config_write_file, +ALIAS (config_write_file, config_write_cmd, - "write", + "write", "Write running configuration to memory, network, or terminal\n") -ALIAS (config_write_file, +ALIAS (config_write_file, config_write_memory_cmd, - "write memory", + "write memory", "Write running configuration to memory, network, or terminal\n" "Write configuration to the file (same as write file)\n") -ALIAS (config_write_file, +ALIAS (config_write_file, copy_runningconfig_startupconfig_cmd, - "copy running-config startup-config", + "copy running-config startup-config", "Copy configuration\n" "Copy running config to... \n" "Copy running config to startup config (same as write file)\n") @@ -2668,10 +3176,10 @@ DEFUN (config_write_terminal, unsigned int i; struct cmd_node *node; - if (vty->type == VTY_SHELL_SERV) + if (vty_shell_serv(vty)) { - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func && node->vtysh) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func && node->vtysh) { if ((*node->func) (vty)) vty_out (vty, "!%s", VTY_NEWLINE); @@ -2683,8 +3191,8 @@ DEFUN (config_write_terminal, VTY_NEWLINE); vty_out (vty, "!%s", VTY_NEWLINE); - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func) { if ((*node->func) (vty)) vty_out (vty, "!%s", VTY_NEWLINE); @@ -2736,7 +3244,7 @@ DEFUN (show_startup_config, } /* Hostname configuration */ -DEFUN (config_hostname, +DEFUN_CALL (config_hostname, hostname_cmd, "hostname WORD", "Set system's network name\n" @@ -2748,28 +3256,40 @@ DEFUN (config_hostname, return CMD_WARNING; } + VTY_LOCK() ; + if (host.name) XFREE (MTYPE_HOST, host.name); - + host.name = XSTRDUP (MTYPE_HOST, argv[0]); + uty_set_host_name(host.name) ; + + VTY_UNLOCK() ; + return CMD_SUCCESS; } -DEFUN (config_no_hostname, +DEFUN_CALL (config_no_hostname, no_hostname_cmd, "no hostname [HOSTNAME]", NO_STR "Reset system's network name\n" "Host name of this router\n") { + VTY_LOCK() ; + if (host.name) XFREE (MTYPE_HOST, host.name); host.name = NULL; + uty_set_host_name(host.name) ; + + VTY_UNLOCK() ; + return CMD_SUCCESS; } /* VTY interface password set. */ -DEFUN (config_password, password_cmd, +DEFUN_CALL (config_password, password_cmd, "password (8|) WORD", "Assign the terminal connection password\n" "Specifies a HIDDEN password will follow\n" @@ -2804,7 +3324,7 @@ DEFUN (config_password, password_cmd, if (!isalnum ((int) *argv[0])) { - vty_out (vty, + vty_out (vty, "Please specify string starting with alphanumeric%s", VTY_NEWLINE); return CMD_WARNING; } @@ -2825,13 +3345,13 @@ DEFUN (config_password, password_cmd, return CMD_SUCCESS; } -ALIAS (config_password, password_text_cmd, +ALIAS_CALL (config_password, password_text_cmd, "password LINE", "Assign the terminal connection password\n" "The UNENCRYPTED (cleartext) line password\n") /* VTY enable password set. */ -DEFUN (config_enable_password, enable_password_cmd, +DEFUN_CALL (config_enable_password, enable_password_cmd, "enable password (8|) WORD", "Modify enable password parameters\n" "Assign the privileged level password\n" @@ -2870,7 +3390,7 @@ DEFUN (config_enable_password, enable_password_cmd, if (!isalnum ((int) *argv[0])) { - vty_out (vty, + vty_out (vty, "Please specify string starting with alphanumeric%s", VTY_NEWLINE); return CMD_WARNING; } @@ -2892,7 +3412,7 @@ DEFUN (config_enable_password, enable_password_cmd, return CMD_SUCCESS; } -ALIAS (config_enable_password, +ALIAS_CALL (config_enable_password, enable_password_text_cmd, "enable password LINE", "Modify enable password parameters\n" @@ -2900,7 +3420,7 @@ ALIAS (config_enable_password, "The UNENCRYPTED (cleartext) 'enable' password\n") /* VTY enable password delete. */ -DEFUN (no_config_enable_password, no_enable_password_cmd, +DEFUN_CALL (no_config_enable_password, no_enable_password_cmd, "no enable password", NO_STR "Modify enable password parameters\n" @@ -2916,8 +3436,8 @@ DEFUN (no_config_enable_password, no_enable_password_cmd, return CMD_SUCCESS; } - -DEFUN (service_password_encrypt, + +DEFUN_CALL (service_password_encrypt, service_password_encrypt_cmd, "service password-encryption", "Set up miscellaneous service\n" @@ -2944,7 +3464,7 @@ DEFUN (service_password_encrypt, return CMD_SUCCESS; } -DEFUN (no_service_password_encrypt, +DEFUN_CALL (no_service_password_encrypt, no_service_password_encrypt_cmd, "no service password-encryption", NO_STR @@ -2967,7 +3487,7 @@ DEFUN (no_service_password_encrypt, return CMD_SUCCESS; } -DEFUN (config_terminal_length, config_terminal_length_cmd, +DEFUN_CALL (config_terminal_length, config_terminal_length_cmd, "terminal length <0-512>", "Set terminal line parameters\n" "Set number of lines on a screen\n" @@ -2982,22 +3502,22 @@ DEFUN (config_terminal_length, config_terminal_length_cmd, vty_out (vty, "length is malformed%s", VTY_NEWLINE); return CMD_WARNING; } - vty->lines = lines; + vty_set_lines(vty, lines); return CMD_SUCCESS; } -DEFUN (config_terminal_no_length, config_terminal_no_length_cmd, +DEFUN_CALL (config_terminal_no_length, config_terminal_no_length_cmd, "terminal no length", "Set terminal line parameters\n" NO_STR "Set number of lines on a screen\n") { - vty->lines = -1; + vty_set_lines(vty, -1); return CMD_SUCCESS; } -DEFUN (service_terminal_length, service_terminal_length_cmd, +DEFUN_CALL (service_terminal_length, service_terminal_length_cmd, "service terminal-length <0-512>", "Set up miscellaneous service\n" "System wide terminal length configuration\n" @@ -3017,7 +3537,7 @@ DEFUN (service_terminal_length, service_terminal_length_cmd, return CMD_SUCCESS; } -DEFUN (no_service_terminal_length, no_service_terminal_length_cmd, +DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd, "no service terminal-length [<0-512>]", NO_STR "Set up miscellaneous service\n" @@ -3028,7 +3548,7 @@ DEFUN (no_service_terminal_length, no_service_terminal_length_cmd, return CMD_SUCCESS; } -DEFUN_HIDDEN (do_echo, +DEFUN_HID_CALL (do_echo, echo_cmd, "echo .MESSAGE", "Echo a message back to the vty\n" @@ -3043,7 +3563,7 @@ DEFUN_HIDDEN (do_echo, return CMD_SUCCESS; } -DEFUN (config_logmsg, +DEFUN_CALL (config_logmsg, config_logmsg_cmd, "logmsg "LOG_LEVELS" .MESSAGE", "Send a message to enabled logging destinations\n" @@ -3056,76 +3576,79 @@ DEFUN (config_logmsg, if ((level = level_match(argv[0])) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; - zlog(NULL, level, "%s", ((message = argv_concat(argv, argc, 1)) ? message : "")); + message = argv_concat(argv, argc, 1); + zlog(NULL, level, "%s", (message ? message : "")); if (message) XFREE(MTYPE_TMP, message); return CMD_SUCCESS; } -DEFUN (show_logging, +DEFUN_CALL (show_logging, show_logging_cmd, "show logging", SHOW_STR "Show current logging configuration\n") { - struct zlog *zl = zlog_default; - vty_out (vty, "Syslog logging: "); - if (zl->maxlvl[ZLOG_DEST_SYSLOG] == ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) == ZLOG_DISABLED) vty_out (vty, "disabled"); else vty_out (vty, "level %s, facility %s, ident %s", - zlog_priority[zl->maxlvl[ZLOG_DEST_SYSLOG]], - facility_name(zl->facility), zl->ident); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)], + facility_name(zlog_get_facility(NULL)), zlog_get_ident(NULL)); vty_out (vty, "%s", VTY_NEWLINE); vty_out (vty, "Stdout logging: "); - if (zl->maxlvl[ZLOG_DEST_STDOUT] == ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) == ZLOG_DISABLED) vty_out (vty, "disabled"); else vty_out (vty, "level %s", - zlog_priority[zl->maxlvl[ZLOG_DEST_STDOUT]]); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]); vty_out (vty, "%s", VTY_NEWLINE); vty_out (vty, "Monitor logging: "); - if (zl->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) + if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED) vty_out (vty, "disabled"); else vty_out (vty, "level %s", - zlog_priority[zl->maxlvl[ZLOG_DEST_MONITOR]]); + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]); vty_out (vty, "%s", VTY_NEWLINE); vty_out (vty, "File logging: "); - if ((zl->maxlvl[ZLOG_DEST_FILE] == ZLOG_DISABLED) || - !zl->fp) + if ((zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) == ZLOG_DISABLED) || + !zlog_is_file(NULL)) vty_out (vty, "disabled"); else - vty_out (vty, "level %s, filename %s", - zlog_priority[zl->maxlvl[ZLOG_DEST_FILE]], - zl->filename); + { + char * filename = zlog_get_filename(NULL); + vty_out (vty, "level %s, filename %s", + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)], + filename); + free(filename); + } vty_out (vty, "%s", VTY_NEWLINE); vty_out (vty, "Protocol name: %s%s", - zlog_proto_names[zl->protocol], VTY_NEWLINE); + zlog_get_proto_name(NULL), VTY_NEWLINE); vty_out (vty, "Record priority: %s%s", - (zl->record_priority ? "enabled" : "disabled"), VTY_NEWLINE); + (zlog_get_record_priority(NULL) ? "enabled" : "disabled"), VTY_NEWLINE); vty_out (vty, "Timestamp precision: %d%s", - zl->timestamp_precision, VTY_NEWLINE); + zlog_get_timestamp_precision(NULL), VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN (config_log_stdout, +DEFUN_CALL (config_log_stdout, config_log_stdout_cmd, "log stdout", "Logging control\n" "Set stdout logging level\n") { - zlog_set_level (NULL, ZLOG_DEST_STDOUT, zlog_default->default_lvl); + zlog_set_level (NULL, ZLOG_DEST_STDOUT, zlog_get_default_lvl(NULL)); return CMD_SUCCESS; } -DEFUN (config_log_stdout_level, +DEFUN_CALL (config_log_stdout_level, config_log_stdout_level_cmd, "log stdout "LOG_LEVELS, "Logging control\n" @@ -3140,7 +3663,7 @@ DEFUN (config_log_stdout_level, return CMD_SUCCESS; } -DEFUN (no_config_log_stdout, +DEFUN_CALL (no_config_log_stdout, no_config_log_stdout_cmd, "no log stdout [LEVEL]", NO_STR @@ -3152,17 +3675,17 @@ DEFUN (no_config_log_stdout, return CMD_SUCCESS; } -DEFUN (config_log_monitor, +DEFUN_CALL (config_log_monitor, config_log_monitor_cmd, "log monitor", "Logging control\n" "Set terminal line (monitor) logging level\n") { - zlog_set_level (NULL, ZLOG_DEST_MONITOR, zlog_default->default_lvl); + zlog_set_level (NULL, ZLOG_DEST_MONITOR, zlog_get_default_lvl(NULL)); return CMD_SUCCESS; } -DEFUN (config_log_monitor_level, +DEFUN_CALL (config_log_monitor_level, config_log_monitor_level_cmd, "log monitor "LOG_LEVELS, "Logging control\n" @@ -3177,7 +3700,7 @@ DEFUN (config_log_monitor_level, return CMD_SUCCESS; } -DEFUN (no_config_log_monitor, +DEFUN_CALL (no_config_log_monitor, no_config_log_monitor_cmd, "no log monitor [LEVEL]", NO_STR @@ -3195,19 +3718,19 @@ set_log_file(struct vty *vty, const char *fname, int loglevel) int ret; char *p = NULL; const char *fullpath; - + /* Path detection. */ if (! IS_DIRECTORY_SEP (*fname)) { char cwd[MAXPATHLEN+1]; cwd[MAXPATHLEN] = '\0'; - + if (getcwd (cwd, MAXPATHLEN) == NULL) { zlog_err ("config_log_file: Unable to alloc mem!"); return CMD_WARNING; } - + if ( (p = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (fname) + 2)) == NULL) { @@ -3239,17 +3762,17 @@ set_log_file(struct vty *vty, const char *fname, int loglevel) return CMD_SUCCESS; } -DEFUN (config_log_file, +DEFUN_CALL (config_log_file, config_log_file_cmd, "log file FILENAME", "Logging control\n" "Logging to file\n" "Logging filename\n") { - return set_log_file(vty, argv[0], zlog_default->default_lvl); + return set_log_file(vty, argv[0], zlog_get_default_lvl(NULL)); } -DEFUN (config_log_file_level, +DEFUN_CALL (config_log_file_level, config_log_file_level_cmd, "log file FILENAME "LOG_LEVELS, "Logging control\n" @@ -3264,7 +3787,7 @@ DEFUN (config_log_file_level, return set_log_file(vty, argv[0], level); } -DEFUN (no_config_log_file, +DEFUN_CALL (no_config_log_file, no_config_log_file_cmd, "no log file [FILENAME]", NO_STR @@ -3282,7 +3805,7 @@ DEFUN (no_config_log_file, return CMD_SUCCESS; } -ALIAS (no_config_log_file, +ALIAS_CALL (no_config_log_file, no_config_log_file_level_cmd, "no log file FILENAME LEVEL", NO_STR @@ -3291,17 +3814,17 @@ ALIAS (no_config_log_file, "Logging file name\n" "Logging level\n") -DEFUN (config_log_syslog, +DEFUN_CALL (config_log_syslog, config_log_syslog_cmd, "log syslog", "Logging control\n" "Set syslog logging level\n") { - zlog_set_level (NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl); + zlog_set_level (NULL, ZLOG_DEST_SYSLOG, zlog_get_default_lvl(NULL)); return CMD_SUCCESS; } -DEFUN (config_log_syslog_level, +DEFUN_CALL (config_log_syslog_level, config_log_syslog_level_cmd, "log syslog "LOG_LEVELS, "Logging control\n" @@ -3316,7 +3839,7 @@ DEFUN (config_log_syslog_level, return CMD_SUCCESS; } -DEFUN_DEPRECATED (config_log_syslog_facility, +DEFUN_DEP_CALL (config_log_syslog_facility, config_log_syslog_facility_cmd, "log syslog facility "LOG_FACILITIES, "Logging control\n" @@ -3329,12 +3852,12 @@ DEFUN_DEPRECATED (config_log_syslog_facility, if ((facility = facility_match(argv[0])) < 0) return CMD_ERR_NO_MATCH; - zlog_set_level (NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl); - zlog_default->facility = facility; + zlog_set_level (NULL, ZLOG_DEST_SYSLOG, zlog_get_default_lvl(NULL)); + zlog_set_facility(NULL, facility); return CMD_SUCCESS; } -DEFUN (no_config_log_syslog, +DEFUN_CALL (no_config_log_syslog, no_config_log_syslog_cmd, "no log syslog [LEVEL]", NO_STR @@ -3346,7 +3869,7 @@ DEFUN (no_config_log_syslog, return CMD_SUCCESS; } -ALIAS (no_config_log_syslog, +ALIAS_CALL (no_config_log_syslog, no_config_log_syslog_facility_cmd, "no log syslog facility "LOG_FACILITIES, NO_STR @@ -3355,7 +3878,7 @@ ALIAS (no_config_log_syslog, "Facility parameter for syslog messages\n" LOG_FACILITY_DESC) -DEFUN (config_log_facility, +DEFUN_CALL (config_log_facility, config_log_facility_cmd, "log facility "LOG_FACILITIES, "Logging control\n" @@ -3366,11 +3889,11 @@ DEFUN (config_log_facility, if ((facility = facility_match(argv[0])) < 0) return CMD_ERR_NO_MATCH; - zlog_default->facility = facility; + zlog_set_facility(NULL, facility); return CMD_SUCCESS; } -DEFUN (no_config_log_facility, +DEFUN_CALL (no_config_log_facility, no_config_log_facility_cmd, "no log facility [FACILITY]", NO_STR @@ -3378,11 +3901,11 @@ DEFUN (no_config_log_facility, "Reset syslog facility to default (daemon)\n" "Syslog facility\n") { - zlog_default->facility = LOG_DAEMON; + zlog_set_facility(NULL, LOG_DAEMON); return CMD_SUCCESS; } -DEFUN_DEPRECATED (config_log_trap, +DEFUN_DEP_CALL (config_log_trap, config_log_trap_cmd, "log trap "LOG_LEVELS, "Logging control\n" @@ -3390,19 +3913,15 @@ DEFUN_DEPRECATED (config_log_trap, LOG_LEVEL_DESC) { int new_level ; - int i; - + if ((new_level = level_match(argv[0])) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; - zlog_default->default_lvl = new_level; - for (i = 0; i < ZLOG_NUM_DESTS; i++) - if (zlog_default->maxlvl[i] != ZLOG_DISABLED) - zlog_default->maxlvl[i] = new_level; + zlog_set_default_lvl_dest (NULL, new_level); return CMD_SUCCESS; } -DEFUN_DEPRECATED (no_config_log_trap, +DEFUN_DEP_CALL (no_config_log_trap, no_config_log_trap_cmd, "no log trap [LEVEL]", NO_STR @@ -3410,32 +3929,32 @@ DEFUN_DEPRECATED (no_config_log_trap, "Permit all logging information\n" "Logging level\n") { - zlog_default->default_lvl = LOG_DEBUG; + zlog_set_default_lvl(NULL, LOG_DEBUG); return CMD_SUCCESS; } -DEFUN (config_log_record_priority, +DEFUN_CALL (config_log_record_priority, config_log_record_priority_cmd, "log record-priority", "Logging control\n" "Log the priority of the message within the message\n") { - zlog_default->record_priority = 1 ; + zlog_set_record_priority(NULL, 1) ; return CMD_SUCCESS; } -DEFUN (no_config_log_record_priority, +DEFUN_CALL (no_config_log_record_priority, no_config_log_record_priority_cmd, "no log record-priority", NO_STR "Logging control\n" "Do not log the priority of the message within the message\n") { - zlog_default->record_priority = 0 ; + zlog_set_record_priority(NULL, 0) ; return CMD_SUCCESS; } -DEFUN (config_log_timestamp_precision, +DEFUN_CALL (config_log_timestamp_precision, config_log_timestamp_precision_cmd, "log timestamp precision <0-6>", "Logging control\n" @@ -3443,6 +3962,8 @@ DEFUN (config_log_timestamp_precision, "Set the timestamp precision\n" "Number of subsecond digits\n") { + int timestamp_precision; + if (argc != 1) { vty_out (vty, "Insufficient arguments%s", VTY_NEWLINE); @@ -3450,11 +3971,13 @@ DEFUN (config_log_timestamp_precision, } VTY_GET_INTEGER_RANGE("Timestamp Precision", - zlog_default->timestamp_precision, argv[0], 0, 6); + timestamp_precision, argv[0], 0, 6); + zlog_set_timestamp_precision(NULL, timestamp_precision); + return CMD_SUCCESS; } -DEFUN (no_config_log_timestamp_precision, +DEFUN_CALL (no_config_log_timestamp_precision, no_config_log_timestamp_precision_cmd, "no log timestamp precision", NO_STR @@ -3462,11 +3985,11 @@ DEFUN (no_config_log_timestamp_precision, "Timestamp configuration\n" "Reset the timestamp precision to the default value of 0\n") { - zlog_default->timestamp_precision = 0 ; + zlog_set_timestamp_precision(NULL, 0); return CMD_SUCCESS; } -DEFUN (banner_motd_file, +DEFUN_CALL (banner_motd_file, banner_motd_file_cmd, "banner motd file [FILE]", "Set banner\n" @@ -3481,7 +4004,7 @@ DEFUN (banner_motd_file, return CMD_SUCCESS; } -DEFUN (banner_motd_default, +DEFUN_CALL (banner_motd_default, banner_motd_default_cmd, "banner motd default", "Set banner string\n" @@ -3492,7 +4015,7 @@ DEFUN (banner_motd_default, return CMD_SUCCESS; } -DEFUN (no_banner_motd, +DEFUN_CALL (no_banner_motd, no_banner_motd_cmd, "no banner motd", NO_STR @@ -3500,7 +4023,7 @@ DEFUN (no_banner_motd, "Strings for motd\n") { host.motd = NULL; - if (host.motdfile) + if (host.motdfile) XFREE (MTYPE_HOST, host.motdfile); host.motdfile = NULL; return CMD_SUCCESS; @@ -3540,7 +4063,7 @@ cmd_init (int terminal) desc_cr.str = XSTRDUP(MTYPE_STRVEC, ""); /* Allocate initial top vector of commands. */ - cmdvec = vector_init (VECTOR_MIN_SIZE); + cmdvec = vector_init (0); /* Default host value settings. */ host.name = NULL; @@ -3604,7 +4127,7 @@ cmd_init (int terminal) install_default (CONFIG_NODE); } - + install_element (CONFIG_NODE, &hostname_cmd); install_element (CONFIG_NODE, &no_hostname_cmd); @@ -3669,39 +4192,52 @@ cmd_terminate () if (cmdvec) { - for (i = 0; i < vector_active (cmdvec); i++) - if ((cmd_node = vector_slot (cmdvec, i)) != NULL) - { - cmd_node_v = cmd_node->cmd_vector; + for (i = 0; i < vector_length (cmdvec); i++) + { + cmd_node = vector_get_item (cmdvec, i) ; + if (cmd_node == NULL) + continue ; - for (j = 0; j < vector_active (cmd_node_v); j++) - if ((cmd_element = vector_slot (cmd_node_v, j)) != NULL && - cmd_element->strvec != NULL) - { - cmd_element_v = cmd_element->strvec; - - for (k = 0; k < vector_active (cmd_element_v); k++) - if ((desc_v = vector_slot (cmd_element_v, k)) != NULL) - { - for (l = 0; l < vector_active (desc_v); l++) - if ((desc = vector_slot (desc_v, l)) != NULL) - { - if (desc->cmd) - XFREE (MTYPE_STRVEC, desc->cmd); - if (desc->str) - XFREE (MTYPE_STRVEC, desc->str); - - XFREE (MTYPE_DESC, desc); - } - vector_free (desc_v); - } - - cmd_element->strvec = NULL; - vector_free (cmd_element_v); - } + cmd_node_v = cmd_node->cmd_vector; + + for (j = 0; j < vector_length (cmd_node_v); j++) + { + cmd_element = vector_get_item (cmd_node_v, j) ; + if (cmd_element == NULL) + continue ; - vector_free (cmd_node_v); - } + cmd_element_v = cmd_element->strvec ; + if (cmd_element_v == NULL) + continue ; + + for (k = 0; k < vector_length (cmd_element_v); k++) + { + desc_v = vector_get_item (cmd_element_v, k) ; + if (desc_v == NULL) + continue ; + + for (l = 0; l < vector_length (desc_v); l++) + { + desc = vector_get_item (desc_v, l) ; + if (desc == NULL) + continue ; + + if (desc->cmd) + XFREE (MTYPE_STRVEC, desc->cmd); + if (desc->str) + XFREE (MTYPE_STRVEC, desc->str); + + XFREE (MTYPE_DESC, desc); + } ; + vector_free (desc_v); + } ; + + cmd_element->strvec = NULL; + vector_free (cmd_element_v); + } ; + + vector_free (cmd_node_v); + } ; vector_free (cmdvec); cmdvec = NULL; diff --git a/lib/command.h b/lib/command.h index 1275efee..ba7c4bb2 100644 --- a/lib/command.h +++ b/lib/command.h @@ -8,7 +8,7 @@ * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. - * + * * GNU Zebra 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 @@ -23,9 +23,17 @@ #ifndef _ZEBRA_COMMAND_H #define _ZEBRA_COMMAND_H +#include <stdbool.h> + +#include "node_type.h" #include "vector.h" -#include "vty.h" -#include "lib/route_types.h" +#include "qstring.h" + +#ifndef Inline +#define Inline static inline +#endif + +struct vty ; /* in case command.h expanded first */ /* Host configuration variable */ struct host @@ -59,83 +67,90 @@ struct host char *motdfile; }; -/* There are some command levels which called from command node. */ -enum node_type -{ - AUTH_NODE, /* Authentication mode of vty interface. */ - RESTRICTED_NODE, /* Restricted view mode */ - VIEW_NODE, /* View node. Default mode of vty interface. */ - AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ - ENABLE_NODE, /* Enable node. */ - CONFIG_NODE, /* Config node. Default mode of config file. */ - SERVICE_NODE, /* Service node. */ - DEBUG_NODE, /* Debug node. */ - AAA_NODE, /* AAA node. */ - KEYCHAIN_NODE, /* Key-chain node. */ - KEYCHAIN_KEY_NODE, /* Key-chain key node. */ - INTERFACE_NODE, /* Interface mode node. */ - ZEBRA_NODE, /* zebra connection node. */ - TABLE_NODE, /* rtm_table selection node. */ - RIP_NODE, /* RIP protocol mode node. */ - RIPNG_NODE, /* RIPng protocol mode node. */ - BGP_NODE, /* BGP protocol mode which includes BGP4+ */ - BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ - BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ - BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ - BGP_IPV6_NODE, /* BGP IPv6 address family */ - BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ - OSPF_NODE, /* OSPF protocol mode */ - OSPF6_NODE, /* OSPF protocol for IPv6 mode */ - ISIS_NODE, /* ISIS protocol mode */ - MASC_NODE, /* MASC for multicast. */ - IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ - IP_NODE, /* Static ip route node. */ - ACCESS_NODE, /* Access list node. */ - PREFIX_NODE, /* Prefix list node. */ - ACCESS_IPV6_NODE, /* Access list node. */ - PREFIX_IPV6_NODE, /* Prefix list node. */ - AS_LIST_NODE, /* AS list node. */ - COMMUNITY_LIST_NODE, /* Community list node. */ - RMAP_NODE, /* Route map node. */ - SMUX_NODE, /* SNMP configuration node. */ - DUMP_NODE, /* Packet dump node. */ - FORWARDING_NODE, /* IP forwarding node. */ - PROTOCOL_NODE, /* protocol filtering node */ - VTY_NODE, /* Vty node. */ -}; - /* Node which has some commands and prompt string and configuration function pointer . */ -struct cmd_node +struct cmd_node { /* Node index. */ - enum node_type node; + enum node_type node; /* Prompt character at vty interface. */ - const char *prompt; + const char *prompt; /* Is this node's configuration goes to vtysh ? */ int vtysh; - + /* Node's configuration write function */ int (*func) (struct vty *); /* Vector of this node's command list. */ - vector cmd_vector; + vector cmd_vector; }; enum { - CMD_ATTR_DEPRECATED = 1, - CMD_ATTR_HIDDEN, + /* bit significant */ + CMD_ATTR_DEPRECATED = 0x01, + CMD_ATTR_HIDDEN = 0x02, + CMD_ATTR_CALL = 0x04, }; -/* Structure of command element. */ -struct cmd_element +/* Return values for command handling. + * + * NB: when a command is executed it may return CMD_SUCCESS or CMD_WARNING. + * + * In both cases any output required (including any warning or error + * messages) must already have been output. + * + * All other return codes are for use within the command handler. + */ +enum cmd_return_code { - const char *string; /* Command specification by string. */ - int (*func) (struct cmd_element *, struct vty *, int, const char *[]); - const char *doc; /* Documentation of this command. */ + CMD_SUCCESS = 0, + CMD_WARNING = 1, + CMD_ERROR, + + CMD_EMPTY, + CMD_SUCCESS_DAEMON, + + CMD_CLOSE, + CMD_QUEUED, + + CMD_ERR_NO_MATCH, + CMD_ERR_AMBIGUOUS, + CMD_ERR_INCOMPLETE, + + CMD_COMPLETE_FULL_MATCH, + CMD_COMPLETE_MATCH, + CMD_COMPLETE_LIST_MATCH, + CMD_COMPLETE_ALREADY +} ; + +#define MSG_CMD_ERR_AMBIGUOUS "Ambiguous command" +#define MSG_CMD_ERR_NO_MATCH "Unrecognised command" +#define MSG_CMD_ERR_NO_MATCH_old "There is no matched command" + +/* Structure of command element. */ + +struct cmd_element ; +typedef struct cmd_element* cmd_element ; + +typedef const char* const argv_t[] ; + +#define DEFUN_CMD_ARG_UNUSED __attribute__ ((unused)) +#define DEFUN_CMD_FUNCTION(name) \ + enum cmd_return_code name (cmd_element self DEFUN_CMD_ARG_UNUSED, \ + struct vty* vty DEFUN_CMD_ARG_UNUSED, \ + int argc DEFUN_CMD_ARG_UNUSED, \ + argv_t argv DEFUN_CMD_ARG_UNUSED) + +typedef DEFUN_CMD_FUNCTION((cmd_function)) ; + +struct cmd_element +{ + const char *string; /* Command specification by string. */ + cmd_function* func ; + const char *doc; /* Documentation of this command. */ int daemon; /* Daemon to which this command belong. */ vector strvec; /* Pointing out each description vector. */ unsigned int cmdsize; /* Command index count. */ @@ -144,31 +159,57 @@ struct cmd_element u_char attr; /* Command attributes */ }; -/* Command description structure. */ +/* Command description structure. */ struct desc { char *cmd; /* Command string. */ char *str; /* Command's description. */ }; -/* Return value of the commands. */ -#define CMD_SUCCESS 0 -#define CMD_WARNING 1 -#define CMD_ERR_NO_MATCH 2 -#define CMD_ERR_AMBIGUOUS 3 -#define CMD_ERR_INCOMPLETE 4 -#define CMD_ERR_EXEED_ARGC_MAX 5 -#define CMD_ERR_NOTHING_TODO 6 -#define CMD_COMPLETE_FULL_MATCH 7 -#define CMD_COMPLETE_MATCH 8 -#define CMD_COMPLETE_LIST_MATCH 9 -#define CMD_SUCCESS_DAEMON 10 - -/* Argc max counts. */ -#define CMD_ARGC_MAX 25 +/* Command parsing options */ +enum cmd_parse_type /* bit significant */ +{ + cmd_parse_completion = 0x00, + cmd_parse_strict = 0x01, + + cmd_parse_do = 0x02, + cmd_parse_tree = 0x04, +} ; +/* Parsed command */ +typedef struct cmd_parsed* cmd_parsed ; +struct cmd_parsed +{ + struct cmd_element *cmd ; /* NULL if empty command + or fails to parse */ + + enum node_type cnode ; /* node command is in */ + enum node_type onode ; /* node the parser started in */ + + bool do_shortcut ; /* true => is "do" command */ + + qstring_t words ; /* the words, '\0' separated */ + + vector_t vline ; /* pointers to the words */ +} ; + + +/* Command dispatch options */ +enum { + cmd_no_queue = true, + cmd_may_queue = false, +} ; + +/*------------------------------------------------------------------------------ + * Can now include these + */ + +#include "vty.h" +#include "uty.h" + +/*----------------------------------------------------------------------------*/ /* Turn off these macros when uisng cpp with extract.pl */ -#ifndef VTYSH_EXTRACT_PL +#ifndef VTYSH_EXTRACT_PL /* helper defines for end-user DEFUN* macros */ #define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ @@ -182,14 +223,10 @@ struct desc }; #define DEFUN_CMD_FUNC_DECL(funcname) \ - static int funcname (struct cmd_element *, struct vty *, int, const char *[]); + static cmd_function funcname; #define DEFUN_CMD_FUNC_TEXT(funcname) \ - static int funcname \ - (struct cmd_element *self __attribute__ ((unused)), \ - struct vty *vty __attribute__ ((unused)), \ - int argc __attribute__ ((unused)), \ - const char *argv[] __attribute__ ((unused)) ) + static DEFUN_CMD_FUNCTION(funcname) /* DEFUN for vty command interafce. Little bit hacky ;-). */ #define DEFUN(funcname, cmdname, cmdstr, helpstr) \ @@ -205,8 +242,17 @@ struct desc #define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) +#define DEFUN_HID_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_HIDDEN)) + #define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) + +#define DEFUN_DEP_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_DEPRECATED)) + +#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL) /* DEFUN_NOSH for commands that vtysh should ignore */ #define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \ @@ -214,7 +260,7 @@ struct desc /* DEFSH for vtysh. */ #define DEFSH(daemon, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) \ + DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) /* DEFUN + DEFSH */ #define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \ @@ -247,6 +293,9 @@ struct desc #define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) +#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL, 0) + #define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) @@ -326,6 +375,11 @@ struct desc #endif /* HAVE_IPV6 */ /* Prototypes. */ +extern void cmd_init (int); +extern void cmd_terminate (void); + +extern void print_version (const char *); + extern void install_node (struct cmd_node *, int (*) (struct vty *)); extern void install_default (enum node_type); extern void install_element (enum node_type, struct cmd_element *); @@ -334,35 +388,13 @@ extern void sort_node (void); /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated string with a space between each element (allocated using XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ -extern char *argv_concat (const char **argv, int argc, int shift); - -extern vector cmd_make_strvec (const char *); -extern void cmd_free_strvec (vector); -extern vector cmd_describe_command (vector, struct vty *, int *status); -extern char **cmd_complete_command (vector, struct vty *, int *status); -extern const char *cmd_prompt (enum node_type); -extern int config_from_file (struct vty *, FILE *); -extern enum node_type node_parent (enum node_type); -extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int); -extern int cmd_execute_command_strict (vector, struct vty *, struct cmd_element **); -extern void config_replace_string (struct cmd_element *, char *, ...); -extern void cmd_init (int); -extern void cmd_terminate (void); - -/* Export typical functions. */ -extern struct cmd_element config_end_cmd; -extern struct cmd_element config_exit_cmd; -extern struct cmd_element config_quit_cmd; -extern struct cmd_element config_help_cmd; -extern struct cmd_element config_list_cmd; -extern char *host_config_file (void); -extern void host_config_set (char *); - -extern void print_version (const char *); +extern char *argv_concat (const char* const* argv, int argc, int shift); /* struct host global, ick */ -extern struct host host; +extern struct host host; + +#ifdef QDEBUG +extern const char *debug_banner ; +#endif -/* "<cr>" global */ -extern char *command_cr; #endif /* _ZEBRA_COMMAND_H */ diff --git a/lib/command_execute.h b/lib/command_execute.h new file mode 100644 index 00000000..a5a807c8 --- /dev/null +++ b/lib/command_execute.h @@ -0,0 +1,79 @@ +/* + * Zebra configuration command interface routine + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_COMMAND_EXECUTE_H +#define _ZEBRA_COMMAND_EXECUTE_H + +#include "command.h" + +extern vector cmd_make_strvec (const char *); +extern vector cmd_add_to_strvec (vector v, const char* str) ; +extern void cmd_free_strvec (vector); +extern vector cmd_describe_command (vector, int, int *status); +extern vector cmd_complete_command (vector, int, int *status); +extern const char *cmd_prompt (enum node_type); +extern enum cmd_return_code +config_from_file (struct vty* vty, FILE *fp, struct cmd_element* first_cmd, + qstring buf, bool stop_on_warning) ; +extern enum node_type node_parent (enum node_type); +extern enum cmd_return_code cmd_execute_command (struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) ; +extern enum cmd_return_code cmd_execute_command_strict (struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) ; + +extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; +extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; +extern enum cmd_return_code cmd_parse_command(struct vty* vty, + enum cmd_parse_type type) ; +extern enum cmd_return_code cmd_dispatch(struct vty* vty, bool no_queue) ; + +Inline enum cmd_return_code +cmd_dispatch_call(struct vty* vty) +{ + cmd_parsed parsed = vty->parsed ; + return (*(parsed->cmd->func))(parsed->cmd, vty, + vector_length(&parsed->vline), + (const char * const*)vector_body(&parsed->vline)) ; +} ; + +#define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0) +#define cmd_parse_reset_free(parsed) cmd_parse_reset(parsed, 1) + +extern void config_replace_string (struct cmd_element *, char *, ...); + +/* Export typical functions. */ +extern struct cmd_element config_end_cmd; +extern struct cmd_element config_exit_cmd; +extern struct cmd_element config_quit_cmd; +extern struct cmd_element config_help_cmd; +extern struct cmd_element config_list_cmd; +extern char *host_config_file (void); +extern void host_config_set (char *); + +/* "<cr>" global */ +extern char *command_cr; + +#ifdef QDEBUG +extern const char *debug_banner; +#endif + +#endif /* _ZEBRA_COMMAND_EXECUTE_H */ diff --git a/lib/command_queue.c b/lib/command_queue.c new file mode 100644 index 00000000..5f14abae --- /dev/null +++ b/lib/command_queue.c @@ -0,0 +1,154 @@ +/* Command Message Queue + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <zebra.h> + +#include "mqueue.h" +#include "qpnexus.h" +#include "memory.h" +#include "command_queue.h" +#include "command_execute.h" +#include "vty.h" +#include "uty.h" +#include "vector.h" +#include "qstring.h" + +/*------------------------------------------------------------------------------ + * Form of message passed with command to be executed + */ + +struct cq_command_args +{ + enum cmd_return_code ret ; /* return code from command */ +} ; +MQB_ARGS_SIZE_OK(cq_command_args) ; + +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void cq_action(mqueue_block mqb, mqb_flag_t flag); +static void cq_return(mqueue_block mqb, mqb_flag_t flag); + +/*------------------------------------------------------------------------------ + * Enqueue vty and argv[] for execution in given nexus. + */ +void +cq_enqueue(struct vty *vty, qpn_nexus dst) +{ + struct cq_command_args* args ; + mqueue_block mqb ; + + assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + + mqb = mqb_init_new(NULL, cq_action, vty) ; + args = mqb_get_args(mqb) ; + + args->ret = CMD_QUEUED ; + + mqueue_enqueue(dst->queue, mqb, 0) ; +} + +/*------------------------------------------------------------------------------ + * Dispatch a command from the message queue block + * + * When done (or revoked/deleted) return the message, so that the sender knows + * that the command has been dealt with (one way or another). + * + * Note that if the command is revoked the return is set to CMD_QUEUED. + */ +static void +cq_action(mqueue_block mqb, mqb_flag_t flag) +{ + struct vty *vty; + struct cq_command_args* args ; + + assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + + vty = mqb_get_arg0(mqb); + args = mqb_get_args(mqb) ; + + if (flag == mqb_action) + { + args->ret = cmd_dispatch_call(vty) ; + assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */ + } + else + args->ret = CMD_QUEUED ; + + mqb_set_action(mqb, cq_return) ; + mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept return from command which has completed. + * + * The command line processing for the vty may be stalled (with read mode + * disabled) waiting for the return from the command. + * + * Do not care whether the message is being revoked or not... the command + * has completed and that must be signalled to the CLI. Any pending output + * is released. + * + * The command itself may have been revoked before it was executed. That + * makes no difference either... the output buffers will simply be empty. + * However, the return code is CMD_QUEUED, to signal the fact that the command + * was never executed. + */ +static void +cq_return(mqueue_block mqb, mqb_flag_t flag) +{ + struct vty *vty ; + struct cq_command_args* args ; + + assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + + vty = mqb_get_arg0(mqb) ; + args = mqb_get_args(mqb) ; + + /* signal end of command */ + cmd_post_command(vty, args->ret) ; + vty_queued_result(vty, args->ret) ; + +//if (qpthreads_enabled) +// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); + + mqb_free(mqb); +} + +/*------------------------------------------------------------------------------ + * Revoke any messages related to the given VTY -- if running qnexus-wise. + * + * Revokes in vty_cmd_nexus -- so before command is started + * and in vty_cli_nexus -- so after command has completed + * + * Can do nothing about any command actually being executed in the + * vty_cmd_nexus. + */ +void +cq_revoke(struct vty *vty) +{ + if (vty_cli_nexus) + { + mqueue_revoke(vty_cmd_nexus->queue, vty) ; + mqueue_revoke(vty_cli_nexus->queue, vty) ; + } ; +} + diff --git a/lib/command_queue.h b/lib/command_queue.h new file mode 100644 index 00000000..804dfa1f --- /dev/null +++ b/lib/command_queue.h @@ -0,0 +1,31 @@ +/* Command Message Queue -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef COMMAND_QUEUE_H_ +#define COMMAND_QUEUE_H_ + +#include "command.h" +#include "qpnexus.h" + +extern void cq_enqueue(struct vty *vty, qpn_nexus dst) ; +extern void cq_revoke(struct vty* vty) ; + +#endif /* COMMAND_QUEUE_H_ */ diff --git a/lib/confirm.h b/lib/confirm.h new file mode 100644 index 00000000..caccf742 --- /dev/null +++ b/lib/confirm.h @@ -0,0 +1,28 @@ +/* Compile time CONFIRM gizmo + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + *. + * This file is part of GNU Zebra. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/*============================================================================== + * Compile time CONFIRM gizmo + * + * Two forms: CONFIRM(e) for use at top (file) level + * confirm(e) for use inside compound statements + */ +#ifndef CONFIRM + + #define CONFIRM(e) extern void CONFIRMATION(char CONFIRM[(e) ? 1 : -1]) ; + #define confirm(e) { CONFIRM(e) } + +#endif diff --git a/lib/daemon.c b/lib/daemon.c index c473555b..daf3c320 100644 --- a/lib/daemon.c +++ b/lib/daemon.c @@ -1,7 +1,7 @@ /* * Daemonize routine * Copyright (C) 1997, 1999 Kunihiro Ishiguro - * + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify @@ -36,7 +36,7 @@ daemon (int nochdir, int noclose) /* In case of fork is error. */ if (pid < 0) { - zlog_err ("fork failed: %s", safe_strerror(errno)); + zlog_err ("fork failed: %s", errtoa(errno, 0).str); return -1; } @@ -49,7 +49,7 @@ daemon (int nochdir, int noclose) if (pid == -1) { - zlog_err ("setsid failed: %s", safe_strerror(errno)); + zlog_err ("setsid failed: %s", errtoa(errno, 0).str); return -1; } diff --git a/lib/distribute.c b/lib/distribute.c index 420849da..8c0f057e 100644 --- a/lib/distribute.c +++ b/lib/distribute.c @@ -20,6 +20,7 @@ */ #include <zebra.h> +#include "miyagi.h" #include "hash.h" #include "if.h" @@ -34,7 +35,7 @@ struct hash *disthash; /* Hook functions. */ void (*distribute_add_hook) (struct distribute *); void (*distribute_delete_hook) (struct distribute *); - + static struct distribute * distribute_new (void) { @@ -69,10 +70,10 @@ distribute_lookup (const char *ifname) struct distribute *dist; /* temporary reference */ - key.ifname = (char *)ifname; + key.ifname = miyagi(ifname) ; dist = hash_lookup (disthash, &key); - + return dist; } @@ -108,8 +109,8 @@ distribute_get (const char *ifname) struct distribute key; /* temporary reference */ - key.ifname = (char *)ifname; - + key.ifname = miyagi(ifname) ; + return hash_get (disthash, &key, (void * (*) (void *))distribute_hash_alloc); } @@ -133,10 +134,10 @@ distribute_cmp (const struct distribute *dist1, const struct distribute *dist2) return 1; return 0; } - + /* Set access-list name to the distribute list. */ static struct distribute * -distribute_list_set (const char *ifname, enum distribute_type type, +distribute_list_set (const char *ifname, enum distribute_type type, const char *alist_name) { struct distribute *dist; @@ -158,14 +159,14 @@ distribute_list_set (const char *ifname, enum distribute_type type, /* Apply this distribute-list to the interface. */ (*distribute_add_hook) (dist); - + return dist; } /* Unset distribute-list. If matched distribute-list exist then return 1. */ static int -distribute_list_unset (const char *ifname, enum distribute_type type, +distribute_list_unset (const char *ifname, enum distribute_type type, const char *alist_name) { struct distribute *dist; @@ -182,7 +183,7 @@ distribute_list_unset (const char *ifname, enum distribute_type type, return 0; free (dist->list[DISTRIBUTE_IN]); - dist->list[DISTRIBUTE_IN] = NULL; + dist->list[DISTRIBUTE_IN] = NULL; } if (type == DISTRIBUTE_OUT) @@ -193,7 +194,7 @@ distribute_list_unset (const char *ifname, enum distribute_type type, return 0; free (dist->list[DISTRIBUTE_OUT]); - dist->list[DISTRIBUTE_OUT] = NULL; + dist->list[DISTRIBUTE_OUT] = NULL; } /* Apply this distribute-list to the interface. */ @@ -236,7 +237,7 @@ distribute_list_prefix_set (const char *ifname, enum distribute_type type, /* Apply this distribute-list to the interface. */ (*distribute_add_hook) (dist); - + return dist; } @@ -260,7 +261,7 @@ distribute_list_prefix_unset (const char *ifname, enum distribute_type type, return 0; free (dist->prefix[DISTRIBUTE_IN]); - dist->prefix[DISTRIBUTE_IN] = NULL; + dist->prefix[DISTRIBUTE_IN] = NULL; } if (type == DISTRIBUTE_OUT) @@ -271,7 +272,7 @@ distribute_list_prefix_unset (const char *ifname, enum distribute_type type, return 0; free (dist->prefix[DISTRIBUTE_OUT]); - dist->prefix[DISTRIBUTE_OUT] = NULL; + dist->prefix[DISTRIBUTE_OUT] = NULL; } /* Apply this distribute-list to the interface. */ @@ -396,7 +397,7 @@ DEFUN (distribute_list, dist = distribute_list_set (argv[2], type, argv[0]); return CMD_SUCCESS; -} +} ALIAS (distribute_list, ipv6_distribute_list_cmd, @@ -437,7 +438,7 @@ DEFUN (no_districute_list, no_distribute_list_cmd, return CMD_WARNING; } return CMD_SUCCESS; -} +} ALIAS (no_districute_list, no_ipv6_distribute_list_cmd, "no distribute-list WORD (in|out) WORD", @@ -467,7 +468,7 @@ DEFUN (districute_list_prefix_all, type = DISTRIBUTE_OUT; else { - vty_out (vty, "distribute list direction must be [in|out]%s", + vty_out (vty, "distribute list direction must be [in|out]%s", VTY_NEWLINE); return CMD_WARNING; } @@ -476,7 +477,7 @@ DEFUN (districute_list_prefix_all, dist = distribute_list_prefix_set (NULL, type, argv[0]); return CMD_SUCCESS; -} +} ALIAS (districute_list_prefix_all, ipv6_distribute_list_prefix_all_cmd, @@ -507,7 +508,7 @@ DEFUN (no_districute_list_prefix_all, type = DISTRIBUTE_OUT; else { - vty_out (vty, "distribute list direction must be [in|out]%s", + vty_out (vty, "distribute list direction must be [in|out]%s", VTY_NEWLINE); return CMD_WARNING; } @@ -519,7 +520,7 @@ DEFUN (no_districute_list_prefix_all, return CMD_WARNING; } return CMD_SUCCESS; -} +} ALIAS (no_districute_list_prefix_all, no_ipv6_distribute_list_prefix_all_cmd, @@ -550,7 +551,7 @@ DEFUN (districute_list_prefix, distribute_list_prefix_cmd, type = DISTRIBUTE_OUT; else { - vty_out (vty, "distribute list direction must be [in|out]%s", + vty_out (vty, "distribute list direction must be [in|out]%s", VTY_NEWLINE); return CMD_WARNING; } @@ -559,7 +560,7 @@ DEFUN (districute_list_prefix, distribute_list_prefix_cmd, dist = distribute_list_prefix_set (argv[2], type, argv[0]); return CMD_SUCCESS; -} +} ALIAS (districute_list_prefix, ipv6_distribute_list_prefix_cmd, "distribute-list prefix WORD (in|out) WORD", @@ -590,7 +591,7 @@ DEFUN (no_districute_list_prefix, no_distribute_list_prefix_cmd, type = DISTRIBUTE_OUT; else { - vty_out (vty, "distribute list direction must be [in|out]%s", + vty_out (vty, "distribute list direction must be [in|out]%s", VTY_NEWLINE); return CMD_WARNING; } @@ -602,7 +603,7 @@ DEFUN (no_districute_list_prefix, no_distribute_list_prefix_cmd, return CMD_WARNING; } return CMD_SUCCESS; -} +} ALIAS (no_districute_list_prefix, no_ipv6_distribute_list_prefix_cmd, "no distribute-list prefix WORD (in|out) WORD", @@ -709,7 +710,7 @@ config_write_distribute (struct vty *vty) if (dist->list[DISTRIBUTE_IN]) { - vty_out (vty, " distribute-list %s in %s%s", + vty_out (vty, " distribute-list %s in %s%s", dist->list[DISTRIBUTE_IN], dist->ifname ? dist->ifname : "", VTY_NEWLINE); @@ -718,7 +719,7 @@ config_write_distribute (struct vty *vty) if (dist->list[DISTRIBUTE_OUT]) { - vty_out (vty, " distribute-list %s out %s%s", + vty_out (vty, " distribute-list %s out %s%s", dist->list[DISTRIBUTE_OUT], dist->ifname ? dist->ifname : "", diff --git a/lib/errno_names.c b/lib/errno_names.c new file mode 100644 index 00000000..7238f1a7 --- /dev/null +++ b/lib/errno_names.c @@ -0,0 +1,383 @@ +/* Mapping Error Numbers to their names + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stddef.h> +#include <errno.h> +#include <netdb.h> + +#include "errno_names.h" + +/*============================================================================== + * Table to map error number to its name + */ +#define ERRNO(err) [err] = #err + +static const char* errno_name_table[] = +{ + /* Error number for no error + * + * (123456789012345), /-- no name is more than 15 characters + */ + ERRNO(EOK), /* No error */ + + /* POSIX Error Numbers -- taken Open Group Base Specifications Issue 7 + * IEEE Std 1003.1-2008 + * + * Stream related stuff does not seeem to be fully supported. + */ + ERRNO(E2BIG), /* Argument list too long. */ + ERRNO(EACCES), /* Permission denied. */ + ERRNO(EADDRINUSE), /* Address in use. */ + ERRNO(EADDRNOTAVAIL), /* Address not available. */ + ERRNO(EAFNOSUPPORT), /* Address family not supported. */ +#if EAGAIN != EWOULDBLOCK + ERRNO(EAGAIN), /* Resource unavailable, try again + (may be the same value as [EWOULDBLOCK]). */ +#endif + ERRNO(EALREADY), /* Connection already in progress. */ + ERRNO(EBADF), /* Bad file descriptor. */ + ERRNO(EBADMSG), /* Bad message. */ + ERRNO(EBUSY), /* Device or resource busy. */ + ERRNO(ECANCELED), /* Operation canceled. */ + ERRNO(ECHILD), /* No child processes. */ + ERRNO(ECONNABORTED), /* Connection aborted. */ + ERRNO(ECONNREFUSED), /* Connection refused. */ + ERRNO(ECONNRESET), /* Connection reset. */ + ERRNO(EDEADLK), /* Resource deadlock would occur. */ + ERRNO(EDESTADDRREQ), /* Destination address required. */ + ERRNO(EDOM), /* Mathematics argument out of domain of function. */ + ERRNO(EDQUOT), /* Reserved. */ + ERRNO(EEXIST), /* File exists. */ + ERRNO(EFAULT), /* Bad address. */ + ERRNO(EFBIG), /* File too large. */ + ERRNO(EHOSTUNREACH), /* Host is unreachable. */ + ERRNO(EIDRM), /* Identifier removed. */ + ERRNO(EILSEQ), /* Illegal byte sequence. */ + ERRNO(EINPROGRESS), /* Operation in progress. */ + ERRNO(EINTR), /* Interrupted function. */ + ERRNO(EINVAL), /* Invalid argument. */ + ERRNO(EIO), /* I/O error. */ + ERRNO(EISCONN), /* Socket is connected. */ + ERRNO(EISDIR), /* Is a directory. */ + ERRNO(ELOOP), /* Too many levels of symbolic links. */ + ERRNO(EMFILE), /* File descriptor value too large. */ + ERRNO(EMLINK), /* Too many links. */ + ERRNO(EMSGSIZE), /* Message too large. */ + ERRNO(EMULTIHOP), /* Reserved. */ + ERRNO(ENAMETOOLONG), /* Filename too long. */ + ERRNO(ENETDOWN), /* Network is down. */ + ERRNO(ENETRESET), /* Connection aborted by network. */ + ERRNO(ENETUNREACH), /* Network unreachable. */ + ERRNO(ENFILE), /* Too many files open in system. */ + ERRNO(ENOBUFS), /* No buffer space available. */ +#ifdef ENODATA + ERRNO(ENODATA), /* No message is available on the STREAM head read + queue. */ +#endif + ERRNO(ENODEV), /* No such device. */ + ERRNO(ENOENT), /* No such file or directory. */ + ERRNO(ENOEXEC), /* Executable file format error. */ + ERRNO(ENOLCK), /* No locks available. */ + ERRNO(ENOLINK), /* Reserved. */ + ERRNO(ENOMEM), /* Not enough space. */ + ERRNO(ENOMSG), /* No message of the desired type. */ + ERRNO(ENOPROTOOPT), /* Protocol not available. */ + ERRNO(ENOSPC), /* No space left on device. */ +#ifdef ENOSR + ERRNO(ENOSR), /* No STREAM resources. */ +#endif +#ifdef ENOSTR + ERRNO(ENOSTR), /* Not a STREAM. */ +#endif + ERRNO(ENOSYS), /* Function not supported. */ + ERRNO(ENOTCONN), /* The socket is not connected. */ + ERRNO(ENOTDIR), /* Not a directory. */ + ERRNO(ENOTEMPTY), /* Directory not empty. */ +#ifdef ENOTRECOVERABLE + ERRNO(ENOTRECOVERABLE), /* State not recoverable. */ +#endif + ERRNO(ENOTSOCK), /* Not a socket. */ + ERRNO(ENOTSUP), /* Not supported + (may be the same value as [EOPNOTSUPP]). */ + ERRNO(ENOTTY), /* Inappropriate I/O control operation. */ + ERRNO(ENXIO), /* No such device or address. */ +#if EOPNOTSUPP != ENOTSUP + ERRNO(EOPNOTSUPP), /* Operation not supported on socket + (may be the same value as [ENOTSUP]). */ +#endif + ERRNO(EOVERFLOW), /* Value too large to be stored in data type. */ +#ifdef EOWNERDEAD + ERRNO(EOWNERDEAD), /* Previous owner died. */ +#endif + ERRNO(EPERM), /* Operation not permitted. */ + ERRNO(EPIPE), /* Broken pipe. */ + ERRNO(EPROTO), /* Protocol error. */ + ERRNO(EPROTONOSUPPORT), /* Protocol not supported. */ + ERRNO(EPROTOTYPE), /* Protocol wrong type for socket. */ + ERRNO(ERANGE), /* Result too large. */ + ERRNO(EROFS), /* Read-only file system. */ + ERRNO(ESPIPE), /* Invalid seek. */ + ERRNO(ESRCH), /* No such process. */ + ERRNO(ESTALE), /* Reserved. */ +#ifdef ETIME + ERRNO(ETIME), /* Stream ioctl() timeout. */ +#endif + ERRNO(ETIMEDOUT), /* Connection timed out. */ + ERRNO(ETXTBSY), /* Text file busy. */ + ERRNO(EWOULDBLOCK), /* Operation would block + (may be the same value as [EAGAIN]). */ + ERRNO(EXDEV), /* Cross-device link. */ + +/* Linux Error Numbers -- for 2.6.30, taken 8-Apr-2010. + * + * (123456789012345), /-- no name is more than 15 characters + */ +#ifdef EADV + ERRNO(EADV), /* Advertise error */ +#endif +#ifdef EBADE + ERRNO(EBADE), /* Invalid exchange */ +#endif +#ifdef EBADFD + ERRNO(EBADFD), /* File descriptor in bad state */ +#endif +#ifdef EBADR + ERRNO(EBADR), /* Invalid request descriptor */ +#endif +#ifdef EBADRQC + ERRNO(EBADRQC), /* Invalid request code */ +#endif +#ifdef EBADSLT + ERRNO(EBADSLT), /* Invalid slot */ +#endif +#ifdef EBFONT + ERRNO(EBFONT), /* Bad font file format */ +#endif +#ifdef ECHRNG + ERRNO(ECHRNG), /* Channel number out of range */ +#endif +#ifdef ECOMM + ERRNO(ECOMM), /* Communication error on send */ +#endif +#ifdef EDEADLOCK + ERRNO(EDEADLOCK), /* same as EDEADLK */ +#endif +#ifdef EDOTDOT + ERRNO(EDOTDOT), /* RFS specific error */ +#endif +#ifdef EHOSTDOWN + ERRNO(EHOSTDOWN), /* Host is down */ +#endif +#ifdef EISNAM + ERRNO(EISNAM), /* Is a named type file */ +#endif +#ifdef EKEYEXPIRED + ERRNO(EKEYEXPIRED), /* Key has expired */ +#endif +#ifdef EKEYREJECTED + ERRNO(EKEYREJECTED), /* Key was rejected by service */ +#endif +#ifdef EKEYREVOKED + ERRNO(EKEYREVOKED), /* Key has been revoked */ +#endif +#ifdef EL2HLT + ERRNO(EL2HLT), /* Level 2 halted */ +#endif +#ifdef EL2NSYNC + ERRNO(EL2NSYNC), /* Level 2 not synchronized */ +#endif +#ifdef EL3HLT + ERRNO(EL3HLT), /* Level 3 halted */ +#endif +#ifdef EL3RST + ERRNO(EL3RST), /* Level 3 reset */ +#endif +#ifdef ELIBACC + ERRNO(ELIBACC), /* Can not access a needed shared library */ +#endif +#ifdef ELIBBAD + ERRNO(ELIBBAD), /* Accessing a corrupted shared library */ +#endif +#ifdef ELIBEXEC + ERRNO(ELIBEXEC), /* Cannot exec a shared library directly */ +#endif +#ifdef ELIBMAX + ERRNO(ELIBMAX), /* Attempting to link in too many shared libraries */ +#endif +#ifdef ELIBSCN + ERRNO(ELIBSCN), /* .lib section in a.out corrupted */ +#endif +#ifdef ELNRNG + ERRNO(ELNRNG), /* Link number out of range */ +#endif +#ifdef EMEDIUMTYPE + ERRNO(EMEDIUMTYPE), /* Wrong medium type */ +#endif +#ifdef ENAVAIL + ERRNO(ENAVAIL), /* No XENIX semaphores available */ +#endif +#ifdef ENOANO + ERRNO(ENOANO), /* No anode */ +#endif +#ifdef ENOCSI + ERRNO(ENOCSI), /* No CSI structure available */ +#endif +#ifdef ENOKEY + ERRNO(ENOKEY), /* Required key not available */ +#endif +#ifdef ENOMEDIUM + ERRNO(ENOMEDIUM), /* No medium found */ +#endif +#ifdef ENONET + ERRNO(ENONET), /* Machine is not on the network */ +#endif +#ifdef ENOPKG + ERRNO(ENOPKG), /* Package not installed */ +#endif +#ifdef ENOTBLK + ERRNO(ENOTBLK), /* Block device required */ +#endif +#ifdef ENOTNAM + ERRNO(ENOTNAM), /* Not a XENIX named type file */ +#endif +#ifdef ENOTUNIQ + ERRNO(ENOTUNIQ), /* Name not unique on network */ +#endif +#ifdef EPFNOSUPPORT + ERRNO(EPFNOSUPPORT), /* Protocol family not supported */ +#endif +#ifdef EREMCHG + ERRNO(EREMCHG), /* Remote address changed */ +#endif +#ifdef EREMOTE + ERRNO(EREMOTE), /* Object is remote */ +#endif +#ifdef EREMOTEIO + ERRNO(EREMOTEIO), /* Remote I/O error */ +#endif +#ifdef ERESTART + ERRNO(ERESTART), /* Interrupted system call should be restarted */ +#endif +#ifdef ESHUTDOWN + ERRNO(ESHUTDOWN), /* Cannot send after transport endpoint shutdown */ +#endif +#ifdef ESOCKTNOSUPPORT + ERRNO(ESOCKTNOSUPPORT), /* Socket type not supported */ +#endif +#ifdef ESRMNT + ERRNO(ESRMNT), /* Srmount error */ +#endif +#ifdef ESTRPIPE + ERRNO(ESTRPIPE), /* Streams pipe error */ +#endif +#ifdef ETOOMANYREFS + ERRNO(ETOOMANYREFS), /* Too many references: cannot splice */ +#endif +#ifdef EUCLEAN + ERRNO(EUCLEAN), /* Structure needs cleaning */ +#endif +#ifdef EUNATCH + ERRNO(EUNATCH), /* Protocol driver not attached */ +#endif +#ifdef EUSERS + ERRNO(EUSERS), /* Too many users */ +#endif +#ifdef EXFULL + ERRNO(EXFULL), /* Exchange full */ +#endif +} ; + +enum { errno_last = (sizeof(errno_name_table) / sizeof(char*)) - 1 } ; + +/*------------------------------------------------------------------------------ + * Lookup the name for given error number. + * + * Returns: address of string, or NULL if not known + * + * NB: for 0 returns "EOK". + * + * NB: async-signal-safe and thread-safe ! + */ +extern const char* +errno_name_lookup(int err) +{ + if ((err < 0) || (err > errno_last)) + return NULL ; + return errno_name_table[err] ; +} ; + +/*============================================================================== + * Table to map EAI error number to its name -- the errors generated by + * getaddrinfo() and getnameinfo(). + * + * At least one system uses -ve numbers for these... so the following will + * support either +ve or -ve values, provided that all have the same sign. + */ +#if EAI_AGAIN < 0 +enum { eai_sgn = -1 } ; +#else +enum { eai_sgn = +1 } ; +#endif + +#define EAINO(eai) [eai * eai_sgn] = #eai + +static const char* eaino_name_table[] = +{ + /* Error number for no error + * + * (123456789012345), /-- no name is more than 15 characters + */ + EAINO(EAI_OK), /* No error */ + + /* POSIX Error Numbers -- taken Open Group Base Specifications Issue 7 + * IEEE Std 1003.1-2008 + */ + EAINO(EAI_AGAIN), /* Temporary failure in name resolution. */ + EAINO(EAI_BADFLAGS), /* Invalid value for 'ai_flags' field. */ + EAINO(EAI_FAIL), /* Non-recoverable failure in name res. */ + EAINO(EAI_FAMILY), /* 'ai_family' not supported. */ + EAINO(EAI_MEMORY), /* Memory allocation failure. */ + EAINO(EAI_NONAME), /* NAME or SERVICE is unknown. */ + EAINO(EAI_OVERFLOW), /* Argument buffer overflow. */ + EAINO(EAI_SERVICE), /* SERVICE not supported for 'ai_socktype'. */ + EAINO(EAI_SOCKTYPE), /* 'ai_socktype' not supported. */ + EAINO(EAI_SYSTEM), /* System error returned in 'errno'. */ +} ; + +enum { eaino_last = (sizeof(eaino_name_table) / sizeof(char*)) - 1 } ; + +/*------------------------------------------------------------------------------ + * Lookup the name for given error number. + * + * Returns: address of string, or NULL if not known + * + * NB: for 0 returns "EOK". + * + * NB: async-signal-safe and thread-safe ! + */ +extern const char* +eaino_name_lookup(int eai) +{ + eai *= eai_sgn ; + if ((eai < 0) || (eai > eaino_last)) + return NULL ; + return eaino_name_table[eai] ; +} ; diff --git a/lib/errno_names.h b/lib/errno_names.h new file mode 100644 index 00000000..a4f1f2e3 --- /dev/null +++ b/lib/errno_names.h @@ -0,0 +1,31 @@ +/* Mapping Error Numbers to their names -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef EOK +#define EOK 0 +#endif + +#ifndef EAI_OK +#define EAI_OK 0 +#endif + +extern const char* errno_name_lookup(int err) ; +extern const char* eaino_name_lookup(int err) ; diff --git a/lib/filter.c b/lib/filter.c index af8d587f..a607cb4e 100644 --- a/lib/filter.c +++ b/lib/filter.c @@ -1584,7 +1584,7 @@ filter_show (struct vty *vty, const char *name, afi_t afi) /* Print the name of the protocol */ if (zlog_default) vty_out (vty, "%s:%s", - zlog_proto_names[zlog_default->protocol], VTY_NEWLINE); + zlog_get_proto_name(NULL), VTY_NEWLINE); for (access = master->num.head; access; access = access->next) { @@ -1620,9 +1620,9 @@ filter_show (struct vty *vty, const char *name, afi_t afi) vty_out (vty, " any%s", VTY_NEWLINE); else { - vty_out (vty, " %s", inet_ntoa (filter->addr)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr)); if (filter->addr_mask.s_addr != 0) - vty_out (vty, ", wildcard bits %s", inet_ntoa (filter->addr_mask)); + vty_out (vty, ", wildcard bits %s", safe_inet_ntoa (filter->addr_mask)); vty_out (vty, "%s", VTY_NEWLINE); } } @@ -1663,9 +1663,9 @@ filter_show (struct vty *vty, const char *name, afi_t afi) vty_out (vty, " any%s", VTY_NEWLINE); else { - vty_out (vty, " %s", inet_ntoa (filter->addr)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr)); if (filter->addr_mask.s_addr != 0) - vty_out (vty, ", wildcard bits %s", inet_ntoa (filter->addr_mask)); + vty_out (vty, ", wildcard bits %s", safe_inet_ntoa (filter->addr_mask)); vty_out (vty, "%s", VTY_NEWLINE); } } @@ -1735,21 +1735,21 @@ config_write_access_cisco (struct vty *vty, struct filter *mfilter) if (filter->addr_mask.s_addr == 0xffffffff) vty_out (vty, " any"); else if (filter->addr_mask.s_addr == 0) - vty_out (vty, " host %s", inet_ntoa (filter->addr)); + vty_out (vty, " host %s", safe_inet_ntoa (filter->addr)); else { - vty_out (vty, " %s", inet_ntoa (filter->addr)); - vty_out (vty, " %s", inet_ntoa (filter->addr_mask)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr_mask)); } if (filter->mask_mask.s_addr == 0xffffffff) vty_out (vty, " any"); else if (filter->mask_mask.s_addr == 0) - vty_out (vty, " host %s", inet_ntoa (filter->mask)); + vty_out (vty, " host %s", safe_inet_ntoa (filter->mask)); else { - vty_out (vty, " %s", inet_ntoa (filter->mask)); - vty_out (vty, " %s", inet_ntoa (filter->mask_mask)); + vty_out (vty, " %s", safe_inet_ntoa (filter->mask)); + vty_out (vty, " %s", safe_inet_ntoa (filter->mask_mask)); } vty_out (vty, "%s", VTY_NEWLINE); } @@ -1759,9 +1759,9 @@ config_write_access_cisco (struct vty *vty, struct filter *mfilter) vty_out (vty, " any%s", VTY_NEWLINE); else { - vty_out (vty, " %s", inet_ntoa (filter->addr)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr)); if (filter->addr_mask.s_addr != 0) - vty_out (vty, " %s", inet_ntoa (filter->addr_mask)); + vty_out (vty, " %s", safe_inet_ntoa (filter->addr_mask)); vty_out (vty, "%s", VTY_NEWLINE); } } diff --git a/lib/getopt.c b/lib/getopt.c index c784fb6c..ec98feac 100644 --- a/lib/getopt.c +++ b/lib/getopt.c @@ -3,11 +3,11 @@ "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! - Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 - Free Software Foundation, Inc. + Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, + 1996, 1997, 1998, 2005 Free Software Foundation, Inc. - NOTE: The canonical source of this file is maintained with the GNU C Library. - Bugs can be reported to bug-glibc@gnu.org. + NOTE: This source is derived from an old version taken from the GNU C + Library (glibc). 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 @@ -21,9 +21,9 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ - + /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO @@ -34,16 +34,7 @@ # include <config.h> #endif -#include <zebra.h> - -#if !defined __STDC__ || !__STDC__ -/* This is a separate conditional since some stdc systems - reject `defined (const)'. */ -# ifndef const -# define const -# endif -#endif - +#include "zebra.h" #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not @@ -67,12 +58,12 @@ /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ -#ifdef __GNU_LIBRARY__ +#ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include <stdlib.h> # include <unistd.h> -#endif /* GNU C library. */ +#endif /* GNU C library. */ #ifdef VMS # include <unixlib.h> @@ -84,11 +75,11 @@ #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ -# ifdef HAVE_LIBINTL_H +# if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC # include <libintl.h> -# define _(msgid) gettext (msgid) +# define _(msgid) gettext (msgid) # else -# define _(msgid) (msgid) +# define _(msgid) (msgid) # endif #endif @@ -146,6 +137,9 @@ int __getopt_initialized = 0; static char *nextchar; +/* Empty string -- not const ! */ +static char empty_string[1] = { '\0' } ; + /* Callers store zero here to inhibit the error message for unrecognized options. */ @@ -193,38 +187,47 @@ static enum /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; - -#ifdef __GNU_LIBRARY__ + +#ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include <string.h> -# define my_index strchr -#else +# define my_index (const char*)strchr + +#else /* not __GNU_LIBRARY__ */ # if HAVE_STRING_H # include <string.h> # else -# include <strings.h> +# if HAVE_STRINGS_H +# include <strings.h> +# endif # endif /* Avoid depending on library functions or files whose names are inconsistent. */ -#ifndef getenv -extern char *getenv (); -#endif +# if HAVE_STDLIB_H && HAVE_DECL_GETENV +# include <stdlib.h> +# elif !defined(getenv) +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ +extern char *getenv (const char *); +# ifdef __cplusplus +} +# endif /* __cplusplus */ +# endif /* HAVE_STDLIB_H && HAVE_DECL_GETENV */ -static char * -my_index (str, chr) - const char *str; - int chr; +static const char * +my_index (const char *str, int chr) { while (*str) { if (*str == chr) - return (char *) str; + return str; str++; } return 0; @@ -232,18 +235,18 @@ my_index (str, chr) /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ -#ifdef __GNUC__ +# ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ -# if (!defined __STDC__ || !__STDC__) && !defined strlen +# if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); -# endif /* not __STDC__ */ -#endif /* __GNUC__ */ +# endif /* not __STDC__ */ +# endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ -#endif /* not __GNU_LIBRARY__ */ - /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have @@ -283,15 +286,15 @@ text_set_element (__libc_subinit, store_args_and_env); # endif /* text_set_element */ # define SWAP_FLAGS(ch1, ch2) \ - if (nonoption_flags_len > 0) \ - { \ - char __tmp = __getopt_nonoption_flags[ch1]; \ - __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ - __getopt_nonoption_flags[ch2] = __tmp; \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ } -#else /* !_LIBC */ +#else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) -#endif /* _LIBC */ +#endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) @@ -302,13 +305,8 @@ text_set_element (__libc_subinit, store_args_and_env); `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ -#if defined __STDC__ && __STDC__ -static void exchange (char **); -#endif - static void -exchange (argv) - char **argv; +exchange (char **argv) { int bottom = first_nonopt; int middle = last_nonopt; @@ -327,57 +325,57 @@ exchange (argv) if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and - presents new arguments. */ - char *new_str = malloc (top + 1); + presents new arguments. */ + char *new_str = (char *) malloc (top + 1); if (new_str == NULL) - nonoption_flags_len = nonoption_flags_max_len = 0; + nonoption_flags_len = nonoption_flags_max_len = 0; else - { - memset (__mempcpy (new_str, __getopt_nonoption_flags, - nonoption_flags_max_len), - '\0', top + 1 - nonoption_flags_max_len); - nonoption_flags_max_len = top + 1; - __getopt_nonoption_flags = new_str; - } + { + memset (mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) - { - /* Bottom segment is the short one. */ - int len = middle - bottom; - register int i; - - /* Swap it with the top part of the top segment. */ - for (i = 0; i < len; i++) - { - tem = argv[bottom + i]; - argv[bottom + i] = argv[top - (middle - bottom) + i]; - argv[top - (middle - bottom) + i] = tem; - SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); - } - /* Exclude the moved bottom segment from further swapping. */ - top -= len; - } + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } else - { - /* Top segment is the short one. */ - int len = top - middle; - register int i; - - /* Swap it with the bottom part of the bottom segment. */ - for (i = 0; i < len; i++) - { - tem = argv[bottom + i]; - argv[bottom + i] = argv[middle + i]; - argv[middle + i] = tem; - SWAP_FLAGS (bottom + i, middle + i); - } - /* Exclude the moved top segment from further swapping. */ - bottom += len; - } + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } } /* Update records for the slots the non-options now occupy. */ @@ -388,14 +386,10 @@ exchange (argv) /* Initialize the internal data when the first call is made. */ -#if defined __STDC__ && __STDC__ -static const char *_getopt_initialize (int, char *const *, const char *); -#endif static const char * -_getopt_initialize (argc, argv, optstring) - int argc; - char *const *argv; - const char *optstring; +_getopt_initialize (int argc, + char **argv, + const char *optstring) { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped @@ -429,25 +423,25 @@ _getopt_initialize (argc, argv, optstring) && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) - { - if (__getopt_nonoption_flags == NULL - || __getopt_nonoption_flags[0] == '\0') - nonoption_flags_max_len = -1; - else - { - const char *orig_str = __getopt_nonoption_flags; - int len = nonoption_flags_max_len = strlen (orig_str); - if (nonoption_flags_max_len < argc) - nonoption_flags_max_len = argc; - __getopt_nonoption_flags = - (char *) malloc (nonoption_flags_max_len); - if (__getopt_nonoption_flags == NULL) - nonoption_flags_max_len = -1; - else - memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), - '\0', nonoption_flags_max_len - len); - } - } + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } nonoption_flags_len = nonoption_flags_max_len; } else @@ -456,7 +450,7 @@ _getopt_initialize (argc, argv, optstring) return optstring; } - + /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. @@ -514,20 +508,26 @@ _getopt_initialize (argc, argv, optstring) long-named options. */ int -_getopt_internal (argc, argv, optstring, longopts, longind, long_only) - int argc; - char *const *argv; - const char *optstring; - const struct option *longopts; - int *longind; - int long_only; +_getopt_internal (int argc, char *const *argv_nominal, const char *optstring, + const struct option *longopts, + int *longind, int long_only) { optarg = NULL; + char** argv ; + + union + { + char *const *nominal ; + char **actual ; + } miyagi ; + + miyagi.nominal = argv_nominal ; + argv = miyagi.actual ; if (optind == 0 || !__getopt_initialized) { if (optind == 0) - optind = 1; /* Don't scan ARGV[0], the program name. */ + optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } @@ -537,9 +537,9 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC -# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ - || (optind < nonoption_flags_len \ - && __getopt_nonoption_flags[optind] == '1')) +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif @@ -549,76 +549,76 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been - moved back by the user (who may also have changed the arguments). */ + moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) - last_nonopt = optind; + last_nonopt = optind; if (first_nonopt > optind) - first_nonopt = optind; + first_nonopt = optind; if (ordering == PERMUTE) - { - /* If we have just processed some options following some non-options, - exchange them so that the options come first. */ + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ - if (first_nonopt != last_nonopt && last_nonopt != optind) - exchange ((char **) argv); - else if (last_nonopt != optind) - first_nonopt = optind; + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange (argv); + else if (last_nonopt != optind) + first_nonopt = optind; - /* Skip any additional non-options - and extend the range of non-options previously skipped. */ + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ - while (optind < argc && NONOPTION_P) - optind++; - last_nonopt = optind; - } + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } /* The special ARGV-element `--' means premature end of options. - Skip it like a null option, - then exchange with previous non-options as if it were an option, - then skip everything else like a non-option. */ + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) - { - optind++; + { + optind++; - if (first_nonopt != last_nonopt && last_nonopt != optind) - exchange ((char **) argv); - else if (first_nonopt == last_nonopt) - first_nonopt = optind; - last_nonopt = argc; + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange (argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; - optind = argc; - } + optind = argc; + } /* If we have done all the ARGV-elements, stop the scan - and back over any non-options that we skipped and permuted. */ + and back over any non-options that we skipped and permuted. */ if (optind == argc) - { - /* Set the next-arg-index to point at the non-options - that we previously skipped, so the caller will digest them. */ - if (first_nonopt != last_nonopt) - optind = first_nonopt; - return -1; - } + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } /* If we have come to a non-option and did not permute it, - either stop the scan or describe it to the caller and pass it by. */ + either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) - { - if (ordering == REQUIRE_ORDER) - return -1; - optarg = argv[optind++]; - return 1; - } + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } /* We have found another option-ARGV-element. - Skip the initial punctuation. */ + Skip the initial punctuation. */ nextchar = (argv[optind] + 1 - + (longopts != NULL && argv[optind][1] == '-')); + + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ @@ -638,7 +638,7 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) if (longopts != NULL && (argv[optind][1] == '-' - || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; @@ -649,132 +649,132 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; + /* Do nothing. */ ; /* Test all long options for either exact match - or abbreviated matches. */ + or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp (p->name, nextchar, nameend - nextchar)) - { - if ((unsigned int) (nameend - nextchar) - == (unsigned int) strlen (p->name)) - { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } - else if (pfound == NULL) - { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } - else - /* Second or later nonexact match found. */ - ambig = 1; - } + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } if (ambig && !exact) - { - if (opterr) - fprintf (stderr, _("%s: option `%s' is ambiguous\n"), - argv[0], argv[optind]); - nextchar += strlen (nextchar); - optind++; - optopt = 0; - return '?'; - } + { + if (opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } if (pfound != NULL) - { - option_index = indfound; - optind++; - if (*nameend) - { - /* Don't test has_arg with >, because some C compilers don't - allow it to be used on enums. */ - if (pfound->has_arg) - optarg = nameend + 1; - else - { - if (opterr) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) { - if (argv[optind - 1][1] == '-') - /* --option */ - fprintf (stderr, - _("%s: option `--%s' doesn't allow an argument\n"), - argv[0], pfound->name); - else - /* +option or -option */ - fprintf (stderr, - _("%s: option `%c%s' doesn't allow an argument\n"), - argv[0], argv[optind - 1][0], pfound->name); + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; } - - nextchar += strlen (nextchar); - - optopt = pfound->val; - return '?'; - } - } - else if (pfound->has_arg == 1) - { - if (optind < argc) - optarg = argv[optind++]; - else - { - if (opterr) - fprintf (stderr, - _("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - nextchar += strlen (nextchar); - optopt = pfound->val; - return optstring[0] == ':' ? ':' : '?'; - } - } - nextchar += strlen (nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) - { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } /* Can't find it as a long option. If this is not getopt_long_only, - or the option starts with '--' or is not a valid short - option, then it's an error. - Otherwise interpret it as a short option. */ + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' - || my_index (optstring, *nextchar) == NULL) - { - if (opterr) - { - if (argv[optind][1] == '-') - /* --option */ - fprintf (stderr, _("%s: unrecognized option `--%s'\n"), - argv[0], nextchar); - else - /* +option or -option */ - fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), - argv[0], argv[optind][0], nextchar); - } - nextchar = (char *) ""; - optind++; - optopt = 0; - return '?'; - } + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = empty_string ; + optind++; + optopt = 0; + return '?'; + } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; - char *temp = my_index (optstring, c); + const char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') @@ -782,188 +782,188 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) if (temp == NULL || c == ':') { - if (opterr) - { - if (posixly_correct) - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, _("%s: illegal option -- %c\n"), - argv[0], c); - else - fprintf (stderr, _("%s: invalid option -- %c\n"), - argv[0], c); - } - optopt = c; - return '?'; + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { - char *nameend; - const struct option *p; - const struct option *pfound = NULL; - int exact = 0; - int ambig = 0; - int indfound = 0; - int option_index; - - /* This is an option that requires an argument. */ - if (*nextchar != '\0') - { - optarg = nextchar; - /* If we end this ARGV-element by taking the rest as an arg, - we must advance to the next element now. */ - optind++; - } - else if (optind == argc) - { - if (opterr) - { - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, _("%s: option requires an argument -- %c\n"), - argv[0], c); - } - optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - return c; - } - else - /* We already incremented `optind' once; - increment it again when taking next ARGV-elt as argument. */ - optarg = argv[optind++]; - - /* optarg is now the argument, see if it's in the - table of longopts. */ - - for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; - - /* Test all long options for either exact match - or abbreviated matches. */ - for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp (p->name, nextchar, nameend - nextchar)) - { - if ((unsigned int) (nameend - nextchar) == strlen (p->name)) - { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } - else if (pfound == NULL) - { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } - else - /* Second or later nonexact match found. */ - ambig = 1; - } - if (ambig && !exact) - { - if (opterr) - fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), - argv[0], argv[optind]); - nextchar += strlen (nextchar); - optind++; - return '?'; - } - if (pfound != NULL) - { - option_index = indfound; - if (*nameend) - { - /* Don't test has_arg with >, because some C compilers don't - allow it to be used on enums. */ - if (pfound->has_arg) - optarg = nameend + 1; - else - { - if (opterr) - fprintf (stderr, _("\ + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), - argv[0], pfound->name); - - nextchar += strlen (nextchar); - return '?'; - } - } - else if (pfound->has_arg == 1) - { - if (optind < argc) - optarg = argv[optind++]; - else - { - if (opterr) - fprintf (stderr, - _("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - nextchar += strlen (nextchar); - return optstring[0] == ':' ? ':' : '?'; - } - } - nextchar += strlen (nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) - { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } - nextchar = NULL; - return 'W'; /* Let the application handle it. */ + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { - if (temp[2] == ':') - { - /* This is an option that accepts an argument optionally. */ - if (*nextchar != '\0') - { - optarg = nextchar; - optind++; - } - else - optarg = NULL; - nextchar = NULL; - } - else - { - /* This is an option that requires an argument. */ - if (*nextchar != '\0') - { - optarg = nextchar; - /* If we end this ARGV-element by taking the rest as an arg, - we must advance to the next element now. */ - optind++; - } - else if (optind == argc) - { - if (opterr) - { - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, - _("%s: option requires an argument -- %c\n"), - argv[0], c); - } - optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - } - else - /* We already incremented `optind' once; - increment it again when taking next ARGV-elt as argument. */ - optarg = argv[optind++]; - nextchar = NULL; - } + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } } return c; } @@ -972,30 +972,25 @@ _getopt_internal (argc, argv, optstring, longopts, longind, long_only) #ifdef REALLY_NEED_PLAIN_GETOPT int -getopt (argc, argv, optstring) - int argc; - char *const *argv; - const char *optstring; +getopt (int argc, char *const *argv, const char *optstring) { return _getopt_internal (argc, argv, optstring, - (const struct option *) 0, - (int *) 0, - 0); + (const struct option *) 0, + (int *) 0, + 0); } #endif /* REALLY_NEED_PLAIN_GETOPT */ -#endif /* Not ELIDE_CODE. */ - +#endif /* Not ELIDE_CODE. */ + #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int -main (argc, argv) - int argc; - char **argv; +main (int argc, char **argv) { int c; int digit_optind = 0; @@ -1006,51 +1001,51 @@ main (argc, argv) c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) - break; + break; switch (c) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (digit_optind != 0 && digit_optind != this_option_optind) - printf ("digits occur in two different argv-elements.\n"); - digit_optind = this_option_optind; - printf ("option %c\n", c); - break; - - case 'a': - printf ("option a\n"); - break; - - case 'b': - printf ("option b\n"); - break; - - case 'c': - printf ("option c with value `%s'\n", optarg); - break; - - case '?': - break; - - default: - printf ("?? getopt returned character code 0%o ??\n", c); - } + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) - printf ("%s ", argv[optind++]); + printf ("%s ", argv[optind++]); printf ("\n"); } @@ -1058,3 +1053,4 @@ main (argc, argv) } #endif /* TEST */ + diff --git a/lib/getopt.h b/lib/getopt.h index b359a47b..c2eb9ed3 100644 --- a/lib/getopt.h +++ b/lib/getopt.h @@ -1,5 +1,6 @@ /* Declarations for getopt. - Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. + Copyright 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 2000, + 2002 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. @@ -16,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _GETOPT_H @@ -34,7 +35,7 @@ * to use the system version. */ -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -75,9 +76,9 @@ extern int optopt; zero. The field `has_arg' is: - no_argument (or 0) if the option does not take an argument, - required_argument (or 1) if the option requires an argument, - optional_argument (or 2) if the option takes an optional argument. + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but @@ -92,11 +93,7 @@ extern int optopt; struct option { -#if defined (__STDC__) && __STDC__ const char *name; -#else - char *name; -#endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; @@ -106,53 +103,38 @@ struct option /* Names for the values of the `has_arg' field of `struct option'. */ -#define no_argument 0 -#define required_argument 1 -#define optional_argument 2 - -#if defined (__STDC__) && __STDC__ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 -#if REALLY_NEED_PLAIN_GETOPT +#if REALLY_NEED_PLAIN_GETOPT /*------------------------------------*/ -/* - * getopt is defined in POSIX.2. Assume that if the system defines +/* getopt is defined in POSIX.2. Assume that if the system defines * getopt that it complies with POSIX.2. If not, an autoconf test * should be written to define NONPOSIX_GETOPT_DEFINITION. */ -#ifndef NONPOSIX_GETOPT_DEFINITION + +# ifndef NONPOSIX_GETOPT_DEFINITION extern int getopt (int argc, char *const *argv, const char *shortopts); -#else /* NONPOSIX_GETOPT_DEFINITION */ +# else /* NONPOSIX_GETOPT_DEFINITION */ extern int getopt (void); -#endif /* NONPOSIX_GETOPT_DEFINITION */ - -#endif +# endif /* NONPOSIX_GETOPT_DEFINITION */ +#endif /* REALLY_NEED_PLAIN_GETOPT ------------------------------------*/ extern int getopt_long (int argc, char *const *argv, const char *shortopts, - const struct option *longopts, int *longind); + const struct option *longopts, int *longind); extern int getopt_long_only (int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind); + const char *shortopts, + const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind, - int long_only); -#else /* not __STDC__ */ - -#ifdef REALLY_NEED_PLAIN_GETOPT -extern int getopt (); -#endif /* REALLY_NEED_PLAIN_GETOPT */ - -extern int getopt_long (); -extern int getopt_long_only (); - -extern int _getopt_internal (); - -#endif /* __STDC__ */ + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); -#ifdef __cplusplus +#ifdef __cplusplus } #endif diff --git a/lib/getopt1.c b/lib/getopt1.c index 985f12c5..dc36fd0e 100644 --- a/lib/getopt1.c +++ b/lib/getopt1.c @@ -1,9 +1,9 @@ /* getopt_long and getopt_long_only entry points for GNU getopt. - Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98,2005 Free Software Foundation, Inc. - NOTE: The canonical source of this file is maintained with the GNU C Library. - Bugs can be reported to bug-glibc@gnu.org. + NOTE: This source is derived from an old version taken from the GNU C + Library (glibc). 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 @@ -17,24 +17,16 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ - + #ifdef HAVE_CONFIG_H #include <config.h> #endif -#include <zebra.h> +#include "zebra.h" #include "getopt.h" -#if !defined __STDC__ || !__STDC__ -/* This is a separate conditional since some stdc systems - reject `defined (const)'. */ -#ifndef const -#define const -#endif -#endif - #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not @@ -62,17 +54,13 @@ #include <stdlib.h> #endif -#ifndef NULL +#ifndef NULL #define NULL 0 #endif int -getopt_long (argc, argv, options, long_options, opt_index) - int argc; - char *const *argv; - const char *options; - const struct option *long_options; - int *opt_index; +getopt_long (int argc, char *const *argv, const char *options, + const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } @@ -83,27 +71,21 @@ getopt_long (argc, argv, options, long_options, opt_index) instead. */ int -getopt_long_only (argc, argv, options, long_options, opt_index) - int argc; - char *const *argv; - const char *options; - const struct option *long_options; - int *opt_index; +getopt_long_only (int argc, char *const *argv, const char *options, + const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } -#endif /* Not ELIDE_CODE. */ - +#endif /* Not ELIDE_CODE. */ + #ifdef TEST #include <stdio.h> int -main (argc, argv) - int argc; - char **argv; +main (int argc, char **argv) { int c; int digit_optind = 0; @@ -114,74 +96,74 @@ main (argc, argv) int option_index = 0; static struct option long_options[] = { - {"add", 1, 0, 0}, - {"append", 0, 0, 0}, - {"delete", 1, 0, 0}, - {"verbose", 0, 0, 0}, - {"create", 0, 0, 0}, - {"file", 1, 0, 0}, - {0, 0, 0, 0} + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", - long_options, &option_index); + long_options, &option_index); if (c == -1) - break; + break; switch (c) - { - case 0: - printf ("option %s", long_options[option_index].name); - if (optarg) - printf (" with arg %s", optarg); - printf ("\n"); - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (digit_optind != 0 && digit_optind != this_option_optind) - printf ("digits occur in two different argv-elements.\n"); - digit_optind = this_option_optind; - printf ("option %c\n", c); - break; - - case 'a': - printf ("option a\n"); - break; - - case 'b': - printf ("option b\n"); - break; - - case 'c': - printf ("option c with value `%s'\n", optarg); - break; - - case 'd': - printf ("option d with value `%s'\n", optarg); - break; - - case '?': - break; - - default: - printf ("?? getopt returned character code 0%o ??\n", c); - } + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) - printf ("%s ", argv[optind++]); + printf ("%s ", argv[optind++]); printf ("\n"); } diff --git a/lib/heap.c b/lib/heap.c new file mode 100644 index 00000000..3a7ed360 --- /dev/null +++ b/lib/heap.c @@ -0,0 +1,517 @@ +/* Generic heap data structure -- functions. + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include "zassert.h" +#include "heap.h" +#include "memory.h" + +/* Heaps are implemented as a structure which includes a vector structure. + * So items in the heap are items in a vector, which are pointers to the item + * values. + * + * The heap structure may be statically allocated, embedded in another + * structure, or allocated dynamically. In any case the heap operations + * require the address of the heap structure -- see typedef for heap. + * + * An essential component of a heap is its comparison function. So, a heap + * CANNOT be used before it has been initialised and the comparison function + * set. (This is unlike a vector, which may be implicitly initialised empty by + * zeroising the vector structure.) + * + * Items may be pushed onto or popped off the heap, which is organised so that + * the top item has the smallest value -- according to the heap's comparison + * function. (For equal values, the order is undefined.) + * + * The top item in the heap may be examined, and its value may be updated. + * (Updating the top item is more efficient than popping and then pushing it.) + * + * Items may be deleted from the heap. Items may have their value updated + * while they are in the heap. Both of these operations cause the heap to be + * reorganised to maintain the heap's partial ordering. Note: these operations + * require knowledge of where in the heap the item is -- which, by default, is + * done by a linear scan of the heap. For large heaps, there is the option to + * keep a "backlink" from the item to it's heap position. + * + * Vectors may be pushed onto a heap -- copying or moving the contents. + * + * Heaps may popped to a vector -- copying or moving the contents. The + * resulting vector is fully sorted. + * + * ---------------------------- + * Comparison function for heap. + * + * int heap_cmp(...** a, ...** b) ... + * + * Must return -1, 0, +1 : where -1 => a < b, 0 => a == b & +1 => a > b + * + * Heap will sort "smallest" to the top. If you want the biggest at the top, + * return -1 * usual comparison. Note that the effective heap ordering for + * equal values is, essentially, random. + * + * NB: like other comparison functions (cf qsort) the parameters are pointers + * to pointers to the value. + * + * NB: there should never be NULL items in the heap. + */ + +/*============================================================================== + * Initialisation, allocation, reset etc. + */ + +static heap +heap_setup(heap h, int new_vector, vector_index size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) ; + +/* Initialize heap -- allocating heap structure if required. + * + * Does not allocate the underlying vector if the heap is initialised empty. + * + * eg: + * + * ... = heap_new_init_simple(NULL, 0, (heap_cmp*)my_cmp) + * + * See: #define heap_init_new_simple(h, size, cmp) + * #define heap_init_new_backlinked(h, size, cmp, offset) + * + * NB: when initialising an existing heap structure it is ESSENTIAL that + * any previous heap and its contents have been released, because this + * function simply discards whatever was there before. (This function may + * be called to initialise a heap structure which has never been + * initialised.) + * + * Backlink: + * + * The heap_delete_item and heap_update_item functions need the heap + * position of the item. The default way of finding that is to scan the + * underlying heap array, looking for the address of the item. + * + * If either of these functions is done often and on large heaps, it is + * possible to speed this up by implementing a 'backlink'. This requires + * a field of type heap_backlink_t in the item structure, and it is the + * offset of that which must be initialised here, eg: + * + * ... = heap_new_init_backlinked(NULL, 0, (heap_cmp*)my_cmp, + * offset_of(struct xxx_heap_item, backlink)) ; + * + * This adds a little extra work to every change in the heap -- keeping the + * backlink of any moved item up to date. But avoids a linear search for + * every heap_delete_item or heap_update_item. + * + * Returns the heap which has been initialised. + */ +heap +heap_init_new(heap h, unsigned int size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) +{ + if (h == NULL) + h = XCALLOC(MTYPE_HEAP, sizeof(struct heap)) ; + else + memset(h, 0, sizeof(struct heap)) ; + + return heap_setup(h, 1, size, cmp, with_backlink, backlink_offset) ; +} ; + +/* Reinitialise heap (or create a new one, if h == NULL). + * + * Allocates heap structure if none given -- allocating vector if size != 0. + * Otherwise, re-initialise the heap and any vector (reusing its memory). + * + * See: #define heap_re_init_simple(h, size, cmp) + * #define heap_re_init_backlinked(h, size, cmp, offset) + * + * NB: when reinitialising an existing heap it is the caller's + * responsibility to release any item values *before* doing this. + * + * Returns the heap that has been reinitialised. + */ +heap +heap_re_init(heap h, unsigned int size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) +{ + if (h == NULL) + return heap_init_new(h, size, cmp, with_backlink, backlink_offset) ; + else + return heap_setup(h, 0, size, cmp, with_backlink, backlink_offset) ; +} ; + +/* Release heap contents (underlying vector), and (if required) release the + * heap structure. + * + * Returns NULL if releases heap, otherwise the reset heap. + * + * If does not release the heap, it retains the comparison function and any + * backlink setting -- so heap can be reused without reinitialising it. + * + * NB: it is the callers responsibility to release any heap item values + * *before* doing this. + */ +heap +heap_reset(heap h, int free_structure) +{ + vector_reset_keep(&h->v) ; /* vector structure is embedded in the heap */ + + if (free_structure) + XFREE(MTYPE_VECTOR, h) ; /* sets h = NULL */ + + return h ; +} ; + +/* Common set-up for heap_init_new() & heap_reset(). + */ +static heap +heap_setup(heap h, int new_vector, unsigned int size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) +{ + assert(cmp != NULL) ; /* or there will be tears */ + + h->cmp = cmp ; + h->state = with_backlink ? Heap_Has_Backlink : 0 ; + h->backlink_offset = backlink_offset ; + + if (new_vector) + vector_init_new(&h->v, size) ; + else + vector_re_init(&h->v, size) ; + + return h ; +} ; + +/* Ream (another) item out of the given heap. + * + * If heap is empty, release the underlying vector, and (if required) release + * the heap structure. + * + * See: #define heap_ream_free(h) heap_ream(h, 1) + * #define heap_ream_keep(h) heap_ream(h, 0) + * + * Useful for emptying out and resetting/discarding a heap: + * + * while ((p_v = heap_ream_free(h))) + * ... do what's required to release the item p_v + * + * Returns NULL when heap is empty (and structure has been freed, if required). + * + * If does not release the heap, it retains the comparison function and any + * backlink setting -- so heap can be reused without reinitialising it. + * + * NB: once the process of reaming a heap has started: (a) MUST NOT attempt to + * use the heap until process completes, and (b) MUST complete the process. + * + * NB: items are reamed out in no defined order. + */ +p_vector_item +heap_ream(heap h, int free_structure) +{ + p_vector_item p_v ; + + if (h == NULL) + return NULL ; + + if ((p_v = vector_ream_keep(&h->v)) == NULL) + heap_reset(h, free_structure) ; + + return p_v ; +} ; + +/*============================================================================== + * Simple Heap Operations -- see also the Inline functions. + */ + +/* Pop item off the heap. + * + * Returns the popped value, which is NULL if the heap was (and still is) empty. + */ +p_vector_item +heap_pop_item(heap h) +{ + p_vector_item p_v ; + p_vector_item p_x ; + + p_v = vector_pop_item(&h->v) ; /* extract last item, if any */ + if ((p_v == NULL) || (h->v.end == 0)) + return p_v ; /* done if empty or last was also first */ + + p_x = h->v.p_items[0] ; /* this is what we are popping */ + + heap_bubble_down(h, 0, p_v) ; /* reposition what was the last item */ + /* updating any backlink */ + return p_x ; +} ; + +/* Pop one item off the heap and promptly push another. + * + * In this combination, the pop is essentially free. + * + * Returns the popped value, which is NULL if the heap was (and still is) empty. + */ +p_vector_item +heap_pop_push_item(heap h, p_vector_item p_v) +{ + p_vector_item p_x ; + + dassert(p_v != NULL) ; /* no NULLs, thank you. */ + + p_x = heap_top_item(h) ; /* what we are popping */ + + if (p_x == NULL) + heap_push_item(h, p_v) ; /* for empty heap, this deals with */ + /* extending heap etc. */ + else + heap_bubble_down(h, 0, p_v) ; /* position the replacement */ + /* setting any backlink */ + return p_x ; +} ; + +/*============================================================================== + * Heap Operations which use 'backlink', if implemented. + */ + +/* Delete given item from the heap. + * + * See notes on backlink, above. + * + * NB: do NOT try this on items which are not in the given heap ! + */ +void +heap_delete_item(heap h, p_vector_item p_v) +{ + p_vector_item p_x ; + vector_index i ; + + i = heap_find_item(h, p_v) ; /* index of item to be deleted */ + + p_x = vector_pop_item(&h->v) ; /* extract last item, if any */ + + if (i < h->v.end) /* if not deleting the last item... */ + heap_bubble(h, i, p_x) ; /* ...reinsert what was last, at the delete */ + /* position, updating any backlink */ +} ; + +/*============================================================================== + * Other Heap Operations. + */ + +/* Push entire vector onto heap copying or moving items as required. + * + * Copy or move vector to end of heap's vector, then move each + * (non-NULL) item into heap order (discarding any NULL items). + * + * See: #define heap_push_vector_copy(h, v) + * #define heap_push_vector_move(h, v) + */ +void +heap_push_vector(heap h, vector v, int move_vector) +{ + vector_index i = h->v.end ; + vector_index e ; + vector_index n = v->end ; + p_vector_item p_v ; + + if (move_vector) + vector_move_append(&h->v, v) ; + else + vector_copy_append(&h->v, v) ; + + e = i ; /* old end of the heap. */ + while (n--) { + p_v = h->v.p_items[i++] ; + if (p_v != NULL) + heap_bubble_up(h, e++, p_v) ; /* move new item into position in heap */ + /* setting any backlink */ + } ; + + h->v.end = e ; /* new end of heap */ +} ; + +/* Pop given heap to vector -- creating vector if required (v == NULL). + * + * Resulting vector is fully sorted. + * + * Moves or copies the contents of the heap. + * + * See: #define heap_pop_vector_copy(v, h) + * #define heap_pop_vector_move(v, h) + * + * NB: when creating new vector, will be exactly the required size. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to release any existing items if that is required. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to ensure the vector structure is currently valid. + */ +vector +heap_pop_vector(vector v, heap h, int move_heap) +{ + vector_index n = h->v.end ; + vector_index i ; + + v = vector_re_init(v, n) ; /* guarantees >= 'n' items in vector */ + v->end = n ; + + for (i = 0 ; i < n ; i++) + v->p_items[i] = heap_pop_item(h) ; + + if (!move_heap) + vector_copy_here(&h->v, v) ; /* fully sorted is also heap ordered ! */ + + return v ; +} ; + +/*============================================================================== + * The Heap internal mechanics. + */ + +/* Returns pointer to backlink value in heap item: lvalue or rvalue */ +#define HEAP_BACKLINK(h, p_v) \ + *(heap_backlink_t*)((char*)(p_v) + (h)->backlink_offset) +/* Sets backlink, if required. */ +#define heap_set_backlink(h, p_v, i) \ + if ((h)->state & Heap_Has_Backlink) HEAP_BACKLINK(h, p_v) = (i) + +/* Returns index of parent. */ +#define HEAP_UP(i) (((i) - 1) / 2) +/* Returns index of left child. */ +#define HEAP_DOWN(i) (((i) * 2) + 1) + +/* Insert given item in the required place in heap, given that there is now + * a hole at the given position -- may move up or down the heap, or stay put. + * + * Bubbles up or down as required. + * + * Note that this sets the backlink on the given item. + */ +private void +heap_bubble(heap h, vector_index i, p_vector_item p_v) +{ + /* If this is < parent, we bubble upwards. */ + if ((i != 0) && (h->cmp(&p_v, &h->v.p_items[HEAP_UP(i)]) < 0)) + heap_bubble_up(h, i, p_v) ; + /* Otherwise we try bubbling downwards. */ + else + heap_bubble_down(h, i, p_v) ; +} ; + +/* Insert given item in the required place in heap, given that there is now + * a hole at the given position -- where we know may *only* move up the heap. + * + * Note that this sets the backlink on the given item. + * + * NB: ignores anything in the heap beyond 'i' -- in particular does not use + * v.end at all. So this can be used to work along a vector and bring + * items into heap order. + */ +private void +heap_bubble_up(heap h, vector_index i, p_vector_item p_v) +{ + p_vector_item* ha = h->v.p_items ; /* underlying array */ + vector_index ip ; /* index of parent */ + p_vector_item p_p ; /* pointer to parent item */ + + dassert(ha != NULL) ; + + while (i != 0) + { + ip = HEAP_UP(i) ; + p_p = ha[ip] ; /* get parent */ + + if (h->cmp(&p_v, &p_p) >= 0) + break ; /* stop when value >= parent */ + + ha[i] = p_p ; /* move parent down... */ + heap_set_backlink(h, p_p, i) ; /* ...updating any backlink */ + + i = ip ; /* move up the heap */ + } ; + + ha[i] = p_v ; /* place in new position... */ + heap_set_backlink(h, p_v, i) ; /* ...updating any backlink */ +} ; + +/* Insert given item in the required place in heap, given that there is now + * a hole at the given position -- where we know may *only* move down the heap. + * + * Note that this sets the backlink on the given item. + */ +private void +heap_bubble_down(heap h, vector_index i, p_vector_item p_v) +{ + vector_index e = h->v.end ; /* end of heap */ + vector_index ic ; /* index of child */ + vector_index is ; /* index of sibling */ + p_vector_item p_c ; /* pointer to child */ + p_vector_item p_s ; /* pointer to sibling */ + + p_vector_item* ha = h->v.p_items ; /* underlying array */ + dassert(ha != NULL) ; + + while (1) + { + ic = HEAP_DOWN(i) ; + if (ic >= e) + break ; /* Quit if run out of heap ! */ + p_c = ha[ic] ; /* get left hand child */ + + is = ic + 1 ; + if (is < e) /* is there a right hand child ? */ + { + p_s = ha[is] ; /* get right hand child */ + if (h->cmp(&p_s, &p_c) < 0) + { + ic = is ; /* select smaller sibling */ + p_c = p_s ; + } ; + } ; + + if (h->cmp(&p_v, &p_c) <= 0) + break ; /* stop when <= both children */ + + ha[i] = p_c ; /* move smaller child up */ + heap_set_backlink(h, p_c, i) ; /* ...updating any backlink */ + + i = ic ; /* move down the heap */ + } ; + + ha[i] = p_v ; /* place in new position... */ + heap_set_backlink(h, p_v, i) ; /* ...updating any backlink */ +} ; + +/* Find index of given item in the given heap. */ +private vector_index +heap_find_item(heap h, p_vector_item p_v) +{ + vector_index i ; + + if (h->state & Heap_Has_Backlink) + i = HEAP_BACKLINK(h, p_v) ; + else + { + for (i = 0 ; i < h->v.end ; ++i) + if (h->v.p_items[i] == p_v) + return i ; + } ; + + assert((i < h->v.end) && (h->v.p_items[i] == p_v)) ; + + return i ; +} ; diff --git a/lib/heap.h b/lib/heap.h new file mode 100644 index 00000000..bd984398 --- /dev/null +++ b/lib/heap.h @@ -0,0 +1,160 @@ +/* Generic heap data structure -- header. + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + *. + * This file is part of GNU Zebra. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_HEAP_H +#define _ZEBRA_HEAP_H + +#include "vector.h" + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*============================================================================== + * Data structures etc. + */ + +typedef int heap_cmp(p_vector_item* a, p_vector_item*) ; + +enum heap_state { + Heap_Has_Backlink = 0x01, /* Set if backlink set */ +} ; + +typedef vector_index heap_backlink_t ; + +typedef struct heap* heap ; + +struct heap +{ + heap_cmp* cmp ; + + enum heap_state state ; + unsigned int backlink_offset ; + + struct vector v ; +} ; + +/*============================================================================== + * Prototypes. + */ + +extern heap heap_init_new(heap h, unsigned int size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) ; +#define heap_init_new_simple(h, size, cmp) \ + heap_init_new(h, size, cmp, 0, 0) +#define heap_init_new_backlinked(h, size, cmp, offset) \ + heap_init_new(h, size, cmp, 1, offset) + +extern heap heap_re_init(heap h, unsigned int size, heap_cmp* cmp, + int with_backlink, unsigned int backlink_offset) ; +#define heap_re_init_simple(h, size, cmp) \ + heap_re_init(h, size, cmp, 0, 0) +#define heap_re_init_backlinked(h, size, cmp, offset) \ + heap_re_init(h, size, cmp, 1, offset) + +extern heap heap_reset(heap h, int free_structure) ; +extern p_vector_item heap_ream(heap h, int free_structure) ; + +/* Reset heap and free the heap structure. */ +#define heap_reset_free(h) heap_reset(h, 1) +/* Reset heap but keep the heap structure. */ +#define heap_reset_keep(h) heap_reset(h, 0) +/* Ream out heap and free the heap structure. */ +#define heap_ream_free(h) heap_ream(h, 1) +/* Ream out heap but keep the heap structure. */ +#define heap_ream_keep(h) heap_ream(h, 0) + +Inline void heap_push_item(heap h, p_vector_item p_v) ; +extern p_vector_item heap_pop_item(heap h) ; +extern p_vector_item heap_pop_push_item(heap h, p_vector_item p_v) ; +Inline p_vector_item heap_top_item(heap h) ; +Inline void heap_update_top_item(heap h) ; + +extern void heap_delete_item(heap h, p_vector_item p_v) ; +Inline void heap_update_item(heap h, p_vector_item p_v) ; + +extern void heap_push_vector(heap h, vector v, int move_vector) ; +#define heap_push_vector_copy(h, v) \ + heap_push_vector(h, v, 0) +#define heap_push_vector_move(h, v) \ + heap_push_vector(h, v, 1) +extern vector heap_pop_vector(vector v, heap h, int move_heap) ; +#define heap_pop_vector_copy(v, h) \ + heap_pop_vector(v, h, 0) +#define heap_pop_vector_move(v, h) \ + heap_pop_vector(v, h, 1) + +/*============================================================================== + * This are extern only for use in Inline and other friends + */ + +#ifndef private + #define private extern +#endif + +private void +heap_bubble(heap h, vector_index i, p_vector_item p_v) ; + +private void +heap_bubble_up(heap h, vector_index i, p_vector_item p_v) ; + +private void +heap_bubble_down(heap h, vector_index i, p_vector_item p_v) ; + +private vector_index +heap_find_item(heap h, p_vector_item p_v) ; + +/*============================================================================== + * Inline Functions + */ + +/* Push given item onto the heap + */ +Inline void +heap_push_item(heap h, p_vector_item p_v) +{ + dassert(p_v != NULL) ; /* no NULLs, thank you. */ + heap_bubble_up(h, vector_extend_by_1(&h->v), p_v) ; +} ; + +/* Get copy of top heap item (does not pop). + * + * Returns NULL if heap is empty. + */ +Inline p_vector_item +heap_top_item(heap h) +{ + return vector_get_first_item(&h->v) ; /* if any */ +} ; + +/* Update heap to reflect new value of top item. + */ +Inline void +heap_update_top_item(heap h) +{ + heap_bubble_down(h, 0, heap_top_item(h)) ; +} ; + +/* Update heap to reflect new value of given item. + */ +Inline void +heap_update_item(heap h, p_vector_item p_v) +{ + heap_bubble(h, heap_find_item(h, p_v), p_v) ; +} ; + +#endif /* _ZEBRA_HEAP_H */ @@ -1,10 +1,10 @@ -/* +/* * Interface functions. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. - * + * * GNU Zebra 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, or (at your @@ -35,7 +35,7 @@ #include "buffer.h" #include "str.h" #include "log.h" - + /* Master list of interfaces. */ struct list *iflist; @@ -45,7 +45,7 @@ struct if_master int (*if_new_hook) (struct interface *); int (*if_delete_hook) (struct interface *); } if_master; - + /* Compare interface names, returning an integer greater than, equal to, or * less than 0, (following the strcmp convention), according to the * relationship between ifp1 and ifp2. Interface names consist of an @@ -53,7 +53,7 @@ struct if_master * lexicographic by name, and then numeric by number. No number sorts * before all numbers. Examples: de0 < de1, de100 < fxp0 < xl0, devpty < * devpty0, de0 < del0 - */ + */ int if_cmp_func (struct interface *ifp1, struct interface *ifp2) { @@ -87,9 +87,9 @@ if_cmp_func (struct interface *ifp1, struct interface *ifp2) p1 += l1; p2 += l1; - if (!*p1) + if (!*p1) return -1; - if (!*p2) + if (!*p2) return 1; x1 = strtol(p1, &p1, 10); @@ -119,7 +119,7 @@ if_create (const char *name, int namelen) ifp = XCALLOC (MTYPE_IF, sizeof (struct interface)); ifp->ifindex = IFINDEX_INTERNAL; - + assert (name); assert (namelen <= INTERFACE_NAMSIZ); /* Need space for '\0' at end. */ strncpy (ifp->name, name, namelen); @@ -215,7 +215,7 @@ if_lookup_by_name (const char *name) { struct listnode *node; struct interface *ifp; - + if (name) for (ALL_LIST_ELEMENTS_RO (iflist, node, ifp)) { @@ -262,7 +262,7 @@ if_lookup_exact_address (struct in_addr src) { if (IPV4_ADDR_SAME (&p->u.prefix4, &src)) return ifp; - } + } } } return NULL; @@ -434,12 +434,12 @@ if_dump (const struct interface *ifp) "mtu6 %d " #endif /* HAVE_IPV6 */ "%s", - ifp->name, ifp->ifindex, ifp->metric, ifp->mtu, + ifp->name, ifp->ifindex, ifp->metric, ifp->mtu, #ifdef HAVE_IPV6 ifp->mtu6, #endif /* HAVE_IPV6 */ if_flag_dump (ifp->flags)); - + for (ALL_LIST_ELEMENTS_RO (ifp->connected, node, c)) ; } @@ -455,7 +455,7 @@ if_dump_all (void) if_dump (p); } -DEFUN (interface_desc, +DEFUN (interface_desc, interface_desc_cmd, "description .LINE", "Interface specific description\n" @@ -474,7 +474,7 @@ DEFUN (interface_desc, return CMD_SUCCESS; } -DEFUN (no_interface_desc, +DEFUN (no_interface_desc, no_interface_desc_cmd, "no description", NO_STR @@ -489,7 +489,7 @@ DEFUN (no_interface_desc, return CMD_SUCCESS; } - + #ifdef SUNOS_5 /* Need to handle upgrade from SUNWzebra to Quagga. SUNWzebra created * a seperate struct interface for each logical interface, so config @@ -519,11 +519,11 @@ if_sunwzebra_get (const char *name, size_t nlen) if ( (ifp = if_lookup_by_name_len(name, nlen)) != NULL) return ifp; - + /* hunt the primary interface name... */ while (seppos < nlen && name[seppos] != ':') seppos++; - + /* Wont catch seperator as last char, e.g. 'foo0:' but thats invalid */ if (seppos < nlen) return if_get_by_name_len (name, seppos); @@ -531,7 +531,7 @@ if_sunwzebra_get (const char *name, size_t nlen) return if_get_by_name_len (name, nlen); } #endif /* SUNOS_5 */ - + DEFUN (interface, interface_cmd, "interface IFNAME", @@ -556,7 +556,7 @@ DEFUN (interface, #endif /* SUNOS_5 */ vty->index = ifp; - vty->node = INTERFACE_NODE; + vty_set_node(vty, INTERFACE_NODE) ; return CMD_SUCCESS; } @@ -579,7 +579,7 @@ DEFUN_NOSH (no_interface, return CMD_WARNING; } - if (CHECK_FLAG (ifp->status, ZEBRA_INTERFACE_ACTIVE)) + if (CHECK_FLAG (ifp->status, ZEBRA_INTERFACE_ACTIVE)) { vty_out (vty, "%% Only inactive interfaces can be deleted%s", VTY_NEWLINE); @@ -611,7 +611,7 @@ DEFUN (show_address, p = ifc->address; if (p->family == AF_INET) - vty_out (vty, "%s/%d%s", inet_ntoa (p->u.prefix4), p->prefixlen, + vty_out (vty, "%s/%d%s", safe_inet_ntoa (p->u.prefix4), p->prefixlen, VTY_NEWLINE); } } @@ -649,11 +649,11 @@ connected_log (struct connected *connected, char *str) struct interface *ifp; char logbuf[BUFSIZ]; char buf[BUFSIZ]; - + ifp = connected->ifp; p = connected->address; - snprintf (logbuf, BUFSIZ, "%s interface %s %s %s/%d ", + snprintf (logbuf, BUFSIZ, "%s interface %s %s %s/%d ", str, ifp->name, prefix_family_str (p), inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); @@ -734,7 +734,7 @@ connected_lookup_address (struct interface *ifp, struct in_addr dst) } struct connected * -connected_add_by_prefix (struct interface *ifp, struct prefix *p, +connected_add_by_prefix (struct interface *ifp, struct prefix *p, struct prefix *destination) { struct connected *ifc; @@ -782,7 +782,7 @@ if_indextoname (unsigned int ifindex, char *name) return ifp->name; } #endif - + #if 0 /* this route_table of struct connected's is unused * however, it would be good to use a route_table rather than * a list.. @@ -807,7 +807,7 @@ ifaddr_ipv4_add (struct in_addr *ifaddr, struct interface *ifp) { route_unlock_node (rn); zlog_info ("ifaddr_ipv4_add(): address %s is already added", - inet_ntoa (*ifaddr)); + safe_inet_ntoa (*ifaddr)); return; } rn->info = ifp; @@ -827,7 +827,7 @@ ifaddr_ipv4_delete (struct in_addr *ifaddr, struct interface *ifp) if (! rn) { zlog_info ("ifaddr_ipv4_delete(): can't find address %s", - inet_ntoa (*ifaddr)); + safe_inet_ntoa (*ifaddr)); return; } rn->info = NULL; @@ -852,7 +852,7 @@ ifaddr_ipv4_lookup (struct in_addr *addr, unsigned int ifindex) rn = route_node_lookup (ifaddr_ipv4_table, (struct prefix *) &p); if (! rn) return NULL; - + ifp = rn->info; route_unlock_node (rn); return ifp; diff --git a/lib/if_rmap.c b/lib/if_rmap.c index 9774be4b..e51f1fe5 100644 --- a/lib/if_rmap.c +++ b/lib/if_rmap.c @@ -16,10 +16,11 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> +#include "miyagi.h" #include "hash.h" #include "command.h" @@ -32,7 +33,7 @@ struct hash *ifrmaphash; /* Hook functions. */ static void (*if_rmap_add_hook) (struct if_rmap *) = NULL; static void (*if_rmap_delete_hook) (struct if_rmap *) = NULL; - + static struct if_rmap * if_rmap_new (void) { @@ -63,11 +64,11 @@ if_rmap_lookup (const char *ifname) struct if_rmap key; struct if_rmap *if_rmap; - /* temporary copy */ - key.ifname = (char *)ifname; + /* temporary reference */ + key.ifname = miyagi(ifname) ; if_rmap = hash_lookup (ifrmaphash, &key); - + return if_rmap; } @@ -100,8 +101,8 @@ if_rmap_get (const char *ifname) { struct if_rmap key; - /* temporary copy */ - key.ifname = (char *)ifname; + /* temporary reference */ + key.ifname = miyagi(ifname) ; return (struct if_rmap *) hash_get (ifrmaphash, &key, if_rmap_hash_alloc); } @@ -122,9 +123,9 @@ if_rmap_hash_cmp (const void *arg1, const void* arg2) return strcmp (if_rmap1->ifname, if_rmap2->ifname) == 0; } - + static struct if_rmap * -if_rmap_set (const char *ifname, enum if_rmap_type type, +if_rmap_set (const char *ifname, enum if_rmap_type type, const char *routemap_name) { struct if_rmap *if_rmap; @@ -135,25 +136,25 @@ if_rmap_set (const char *ifname, enum if_rmap_type type, { if (if_rmap->routemap[IF_RMAP_IN]) XFREE (MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); - if_rmap->routemap[IF_RMAP_IN] + if_rmap->routemap[IF_RMAP_IN] = XSTRDUP (MTYPE_IF_RMAP_NAME, routemap_name); } if (type == IF_RMAP_OUT) { if (if_rmap->routemap[IF_RMAP_OUT]) XFREE (MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); - if_rmap->routemap[IF_RMAP_OUT] + if_rmap->routemap[IF_RMAP_OUT] = XSTRDUP (MTYPE_IF_RMAP_NAME, routemap_name); } if (if_rmap_add_hook) (*if_rmap_add_hook) (if_rmap); - + return if_rmap; } static int -if_rmap_unset (const char *ifname, enum if_rmap_type type, +if_rmap_unset (const char *ifname, enum if_rmap_type type, const char *routemap_name) { struct if_rmap *if_rmap; @@ -170,7 +171,7 @@ if_rmap_unset (const char *ifname, enum if_rmap_type type, return 0; XFREE (MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); - if_rmap->routemap[IF_RMAP_IN] = NULL; + if_rmap->routemap[IF_RMAP_IN] = NULL; } if (type == IF_RMAP_OUT) @@ -181,7 +182,7 @@ if_rmap_unset (const char *ifname, enum if_rmap_type type, return 0; XFREE (MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); - if_rmap->routemap[IF_RMAP_OUT] = NULL; + if_rmap->routemap[IF_RMAP_OUT] = NULL; } if (if_rmap_delete_hook) @@ -222,7 +223,7 @@ DEFUN (if_rmap, if_rmap = if_rmap_set (argv[2], type, argv[0]); return CMD_SUCCESS; -} +} ALIAS (if_rmap, if_ipv6_rmap_cmd, @@ -263,7 +264,7 @@ DEFUN (no_if_rmap, return CMD_WARNING; } return CMD_SUCCESS; -} +} ALIAS (no_if_rmap, no_if_ipv6_rmap_cmd, @@ -274,7 +275,7 @@ ALIAS (no_if_rmap, "Route map for input filtering\n" "Route map for output filtering\n" "Route map interface name\n") - + /* Configuration write function. */ int config_write_if_rmap (struct vty *vty) @@ -292,7 +293,7 @@ config_write_if_rmap (struct vty *vty) if (if_rmap->routemap[IF_RMAP_IN]) { - vty_out (vty, " route-map %s in %s%s", + vty_out (vty, " route-map %s in %s%s", if_rmap->routemap[IF_RMAP_IN], if_rmap->ifname, VTY_NEWLINE); @@ -301,7 +302,7 @@ config_write_if_rmap (struct vty *vty) if (if_rmap->routemap[IF_RMAP_OUT]) { - vty_out (vty, " route-map %s out %s%s", + vty_out (vty, " route-map %s out %s%s", if_rmap->routemap[IF_RMAP_OUT], if_rmap->ifname, VTY_NEWLINE); diff --git a/lib/keychain.c b/lib/keychain.c index 6719cebf..2f8a0b77 100644 --- a/lib/keychain.c +++ b/lib/keychain.c @@ -74,7 +74,7 @@ key_cmp_func (void *arg1, void *arg2) { const struct key *k1 = arg1; const struct key *k2 = arg2; - + if (k1->index > k2->index) return 1; if (k1->index < k2->index) @@ -226,7 +226,7 @@ key_delete (struct keychain *keychain, struct key *key) free (key->string); key_free (key); } - + DEFUN (key_chain, key_chain_cmd, "key chain WORD", @@ -238,7 +238,7 @@ DEFUN (key_chain, keychain = keychain_get (argv[0]); vty->index = keychain; - vty->node = KEYCHAIN_NODE; + vty_set_node(vty, KEYCHAIN_NODE) ; return CMD_SUCCESS; } @@ -281,8 +281,8 @@ DEFUN (key, VTY_GET_INTEGER ("key identifier", index, argv[0]); key = key_get (keychain, index); vty->index_sub = key; - vty->node = KEYCHAIN_KEY_NODE; - + vty_set_node(vty, KEYCHAIN_KEY_NODE) ; + return CMD_SUCCESS; } @@ -296,7 +296,7 @@ DEFUN (no_key, struct keychain *keychain; struct key *key; u_int32_t index; - + keychain = vty->index; VTY_GET_INTEGER ("key identifier", index, argv[0]); @@ -309,7 +309,7 @@ DEFUN (no_key, key_delete (keychain, key); - vty->node = KEYCHAIN_NODE; + vty_set_node(vty, KEYCHAIN_NODE) ; return CMD_SUCCESS; } @@ -353,7 +353,7 @@ DEFUN (no_key_string, /* Convert HH:MM:SS MON DAY YEAR to time_t value. -1 is returned when given string is malformed. */ -static time_t +static time_t key_str2time (const char *time_str, const char *day_str, const char *month_str, const char *year_str) { @@ -364,7 +364,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, unsigned int sec, min, hour; unsigned int day, month, year; - const char *month_name[] = + const char *month_name[] = { "January", "February", @@ -392,7 +392,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, return -1; \ (V) = tmpl; \ } - + /* Check hour field of time_str. */ colon = strchr (time_str, ':'); if (colon == NULL) @@ -416,10 +416,10 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, time_str = colon + 1; if (*time_str == '\0') return -1; - + /* Sec must be between 0 and 59. */ GET_LONG_RANGE (sec, time_str, 0, 59); - + /* Check day_str. Day must be <1-31>. */ GET_LONG_RANGE (day, day_str, 1, 31); @@ -437,7 +437,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, /* Check year_str. Year must be <1993-2035>. */ GET_LONG_RANGE (year, year_str, 1993, 2035); - + memset (&tm, 0, sizeof (struct tm)); tm.tm_sec = sec; tm.tm_min = min; @@ -445,9 +445,9 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, tm.tm_mon = month; tm.tm_mday = day; tm.tm_year = year - 1900; - + time = mktime (&tm); - + return time; #undef GET_LONG_RANGE } @@ -461,7 +461,7 @@ key_lifetime_set (struct vty *vty, struct key_range *krange, { time_t time_start; time_t time_end; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -496,7 +496,7 @@ key_lifetime_duration_set (struct vty *vty, struct key_range *krange, { time_t time_start; u_int32_t duration; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -518,7 +518,7 @@ key_lifetime_infinite_set (struct vty *vty, struct key_range *krange, const char *smonth_str, const char *syear_str) { time_t time_start; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -531,7 +531,7 @@ key_lifetime_infinite_set (struct vty *vty, struct key_range *krange, return CMD_SUCCESS; } - + DEFUN (accept_lifetime_day_month_day_month, accept_lifetime_day_month_day_month_cmd, "accept-lifetime HH:MM:SS <1-31> MONTH <1993-2035> HH:MM:SS <1-31> MONTH <1993-2035>", @@ -689,7 +689,7 @@ DEFUN (accept_lifetime_duration_month_day, return key_lifetime_duration_set (vty, &key->accept, argv[0], argv[2], argv[1], argv[3], argv[4]); } - + DEFUN (send_lifetime_day_month_day_month, send_lifetime_day_month_day_month_cmd, "send-lifetime HH:MM:SS <1-31> MONTH <1993-2035> HH:MM:SS <1-31> MONTH <1993-2035>", @@ -847,7 +847,7 @@ DEFUN (send_lifetime_duration_month_day, return key_lifetime_duration_set (vty, &key->send, argv[0], argv[2], argv[1], argv[3], argv[4]); } - + static struct cmd_node keychain_node = { KEYCHAIN_NODE, @@ -865,12 +865,12 @@ static struct cmd_node keychain_key_node = static int keychain_strftime (char *buf, int bufsiz, time_t *time) { - struct tm *tm; + struct tm tm; size_t len; - tm = localtime (time); + localtime_r(time, &tm); - len = strftime (buf, bufsiz, "%T %b %d %Y", tm); + len = strftime (buf, bufsiz, "%T %b %d %Y", &tm); return len; } @@ -887,7 +887,7 @@ keychain_config_write (struct vty *vty) for (ALL_LIST_ELEMENTS_RO (keychain_list, node, keychain)) { vty_out (vty, "key chain %s%s", keychain->name, VTY_NEWLINE); - + for (ALL_LIST_ELEMENTS_RO (keychain->key, knode, key)) { vty_out (vty, " key %d%s", key->index, VTY_NEWLINE); diff --git a/lib/keystroke.c b/lib/keystroke.c new file mode 100644 index 00000000..c6eb811e --- /dev/null +++ b/lib/keystroke.c @@ -0,0 +1,1204 @@ +/* Keystroke Buffering + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stdbool.h> +#include <string.h> + +#include "keystroke.h" + +#include "list_util.h" + +#include "memory.h" +#include "mqueue.h" +#include "zassert.h" + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Parsing of incoming keystrokes. + * + * At present this code only handles 8-bit characters, which one assumes are + * ISO8859-1 (or similar). The encoding of keystrokes allows for characters + * of up to 32-bits -- so could parse UTF-8 (or similar). + * + * Handles: + * + * 0. Nothing, returned as: + * + * type = ks_null + * value = knull_eof + * knull_not_eof + * + * len = 0 + * truncated = false + * broken = false + * buf -- not used + * + * This is returned when there is nothing there. + * + * Note that this is NOT returned for NUL ('\0') characters. Those are + * real characters (whose value just happens to be null). + * + * 1. Characters, returned as: + * + * type = ks_char + * value = 0x0.. -- the character value + * '\0' if truncated, malformed or EOF met + * + * len = 1..n => length of character representation, or + * number of bytes in raw form if no good. + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- if OK, the representation for the character (UTF-8 ?) + * if truncated or broken, the raw bytes + * + * See notes below on the handling of '\r' and '\n'. + * + * 2. ESC X -- where X is single character, other than '['. + * + * Returned as: + * + * type = ks_esc + * value = 0x0.. -- the character value X + * ('\0' if hit EOF) + * + * len = 1 (or 0 if EOF met) + * truncated = false + * broken => EOF met + * buf = copy of X (unless EOF met) + * + * 3. ESC [ ... X or CSI ... X -- ANSI escape + * + * Returned as: + * + * type = ks_csi + * value = 0x0.. -- the character value X + * ('\0' if hit EOF or malformed) + * + * len = number of bytes in buf (excluding '\0' terminator) + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- bytes between ESC [ or CSI and terminating X + * NB: '\0' terminated. + * + * Note: an ANSI escape is malformed if a byte outside the range + * 0x20..0x7F is found before the terminating byte. The illegal + * byte is deemed not to be part of the sequence. + * + * 4. Telnet command -- IAC X ... + * + * IAC IAC is treated as character 0xFF, so appears as a ks_char, or + * possibly as a component of an ESC or other sequence. + * + * Returned as: + * + * type = ks_iac + * value = 0x0.. -- the value of X + * ('\0' if hit EOF) + * + * len = number of bytes in buf (0 if hit EOF after IAC) + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- the X and any further bytes, + * but *excluding* any terminating IAC SE + * + * Note: a Telnet command may appear at any time, including in the + * middle of any of the above "real" keystrokes. + * + * This is made invisible to the "real" keystrokes, and the Telnet + * command(s) will appear before the incomplete real keystroke in + * the keystroke stream. + * + * Note: there are three forms of Telnet command, and one escape. + * + * 1) IAC IAC is an escape, and maps simply to the value 0xFF, + * wherever it appears (except when reading an <option>). + * + * 2) IAC X -- two bytes, where X < 250 (SB) + * + * 3) IAC X O -- three bytes, where X is 251..254 (WILL/WONT/DO/DONT) + * + * the O may be 0x00..0xFF (where 0xFF is *NOT* escaped) + * + * 4) IAC SB O ... IAC SE -- many bytes. + * + * the O may be 0x00..0xFF (where 0xFF is *NOT* escaped) + * + * the data ... is subject to IAC IAC escaping (so that + * the terminating IAC SE is unambiguous). + * + * This implementation treats IAC X (where X is not IAC + * and not SE) as an error -- terminating the command + * before the IAC, and marking it malformed. The IAC X + * will then be taken as a new command. + * + * Extended Option objects (O = 0xFF) are exotic, but the above will + * parse them. + * + *------------------------------------------------------------------------------ + * CR, LF and NUL + * + * Telnet requires CR LF newlines. Where a CR is to appear alone it must be + * followed by NUL. + * + * This code accepts: + * + * * CR LF pair, returning LF ('\n') -- discards CR + * + * * CR NUL pair, returning CR ('\r') -- discards NUL + * + * * CR CR pair, returning CR ('\r') == discards one CR (seems pointless) + * + * * CR XX pair, returning CR and XX -- where XX is anything other than + * CR, LF or NUL + * + * It is tempting to throw away all NUL characters... but that doesn't seem + * like a job for this level. + * + * As a small compromise, will not steal a NUL character. + * + * Note that NUL appears as a real character. ks_null means literally nothing. + */ + +/*------------------------------------------------------------------------------ + * Encoding of keystrokes in the keystroke FIFO. + * + * The first byte of a keystroke is as follows: + * + * 1. 0x00..0x7F -- simple character -- complete keystroke + * + * 2. 0x80..0xFF -- compound keystroke -- further bytes (may) follow + * + * A compound keystroke comprises: + * + * 1. type of keystroke -- see enum keystroke_type + * + * 2. number of bytes that make up the keystroke + * + * This is limited to keystroke_max_len. Any keystroke longer than that + * is marked as truncated. + * + * 3. the bytes (0..keystroke_max_len of them) + * + * This is encoded as <first> <length> [ <bytes> ] + * + * Where: + * + * <first> is 0x80..0xFF (as above): + * + * b7 = 1 + * b6 = 0 : "reserved" + * b5 : 0 => OK + * 1 => broken object + * b4 : 0 => OK + * 1 => truncated + * + * b3..0 : type of keystroke -- so 16 types of keystroke + * + * <length> is 0..keystroke_max_len + * + * <bytes> (if present) are the bytes: + * + * ks_null -- should not appear in the FIFO + * + * ks_char -- character value, in network order, length 1..4 + * + * BUT, if broken or truncated, the raw bytes received (must + * be at least one). + * + * ks_esc -- byte that followed the ESC, length = 0..1 + * + * Length 0 => EOF met => broken + * + * ks_csi -- bytes that followed the ESC [ or CSI, length 1..n + * + * The last byte is *always* the byte that terminated the + * sequence, or '\0' if is badly formed, or hit EOF. + * + * If the sequence is truncated, then the last byte of the + * sequence is written over the last buffered byte. + * + * ks_iac -- bytes of the telnet command, excluding the leading + * IAC and the trailing IAC SE, length 1..n. + * + * IAC IAC pairs are not telnet commands. + * + * IAC IAC pairs between SB X O and IAC SE are reduced to + * 0xFF. + * + * Telnet commands are broken if EOF is met before the end + * of the command, or get IAC X between SB X O and IAC SE + * (where X is not IAC or SE). + */ + +enum stream_state +{ + kst_null, /* nothing special (but see iac) */ + + kst_char, /* collecting a multi-byte character */ + kst_cr, /* collecting '\r''\0' or '\r''\n' */ + kst_esc, /* collecting an ESC sequence */ + kst_csi, /* collecting an ESC '[' or CSI sequence */ + + kst_iac_option, /* waiting for option (just seen IAC X) */ + kst_iac_sub, /* waiting for IAC SE */ +} ; + +struct keystroke_state +{ + enum stream_state state ; + unsigned len ; + uint8_t raw[keystroke_max_len] ; +} ; + +struct keystroke_stream +{ + vio_fifo_t fifo ; /* the keystrokes */ + + keystroke_callback* iac_callback ; + void* iac_callback_context ; + + uint8_t CSI ; /* CSI character value (if any) */ + + bool eof_met ; /* nothing more to come */ + + bool steal_this ; /* steal current keystroke when complete */ + + bool iac ; /* last character was an IAC */ + + struct keystroke_state in ; /* current keystroke being collected */ + + struct keystroke_state pushed_in ; + /* keystroke interrupted by IAC */ +} ; + +/* Buffering of keystrokes */ + +enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */ + +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void keystroke_in_push(keystroke_stream stream, uint8_t u) ; +static void keystroke_in_pop(keystroke_stream stream) ; + +inline static int keystroke_set_null(keystroke_stream stream, keystroke stroke); +inline static uint8_t keystroke_get_byte(keystroke_stream stream) ; +inline static void keystroke_add_raw(keystroke_stream stream, uint8_t u) ; +static void keystroke_put_char(keystroke_stream stream, uint32_t u) ; +inline static void keystroke_put_esc(keystroke_stream stream, uint8_t u, + int len) ; +inline static void keystroke_put_csi(keystroke_stream stream, uint8_t u) ; +static void keystroke_put_iac_one(keystroke_stream stream, uint8_t u); +static void keystroke_put_iac_long(keystroke_stream stream, bool broken) ; +static void keystroke_clear_iac(keystroke_stream stream) ; +static void keystroke_steal_char(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_steal_esc(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_steal_csi(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_put(keystroke_stream stream, enum keystroke_type type, + bool broken, uint8_t* bytes, int len) ; + +/*============================================================================== + * Creating and freeing keystroke streams and keystroke stream buffers. + */ + +/*------------------------------------------------------------------------------ + * Create and initialise a keystroke stream. + * + * Can set CSI character value. '\0' => none. (As does '\x1B' !) + * + * The callback function is called when an IAC sequence is seen, the callback + * is: + * + * bool callback(void* context, keystroke stroke) + * + * see: #define keystroke_iac_callback_args + * and: typedef for keystroke_callback + * + * The callback must return true iff the IAC sequence has been dealt with, and + * should NOT be stored for later processing. + */ +extern keystroke_stream +keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, + void* iac_callback_context) +{ + keystroke_stream stream ; + + stream = XCALLOC(MTYPE_KEY_STREAM, sizeof(struct keystroke_stream)) ; + + /* Zeroising the structure sets: + * + * iac_callback = NULL -- none + * iac_callback_context = NULL -- none + * + * eof_met = false -- no EOF yet + * steal = false -- no stealing set + * iac = false -- last character was not an IAC + * + * in.state = kst_null + * in.len = 0 -- nothing in the buffer + * + * pushed_in.state ) ditto + * pushed_in.len ) + */ + confirm(kst_null == 0) ; + + stream->iac_callback = iac_callback ; + stream->iac_callback_context = iac_callback_context ; + + vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ; + + stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ; + + return stream ; +} ; + +/*------------------------------------------------------------------------------ + * Free keystroke stream and all associated buffers. + * + * Returns NULL + */ +extern keystroke_stream +keystroke_stream_free(keystroke_stream stream) +{ + if (stream != NULL) + { + vio_fifo_reset_keep(&stream->fifo) ; + + XFREE(MTYPE_KEY_STREAM, stream) ; + } ; + + return NULL ; +} ; + +/*============================================================================== + * Keystroke stream state + */ + +/*------------------------------------------------------------------------------ + * See if given keystroke stream is empty + * + * May or may not be at "EOF", see below. + * + * Returns: true <=> is empty + */ +extern bool +keystroke_stream_empty(keystroke_stream stream) +{ + return (stream == NULL) || vio_fifo_empty(&stream->fifo) ; +} ; + +/*------------------------------------------------------------------------------ + * See if given keystroke stream is at "EOF", that is: + * + * * keystroke stream is empty + * + * * there is no partial keystroke in construction + * + * * EOF has been signalled by a suitable call of keystroke_input(). + * + * Returns: true <=> is at EOF + */ +extern bool +keystroke_stream_eof(keystroke_stream stream) +{ + /* Note that when EOF is signalled, any partial keystroke in construction + * is converted to a broken keystroke and placed in the stream. + * (So eof_met => no partial keystroke.) + */ + return (stream == NULL) || (vio_fifo_empty(&stream->fifo) && stream->eof_met); +} ; + +/*------------------------------------------------------------------------------ + * Set keystroke stream to "EOF", that is: + * + * * discard contents of the stream, including any partial keystroke. + * + * * set the stream "eof_met". + */ +extern void +keystroke_stream_set_eof(keystroke_stream stream) +{ + vio_fifo_reset_keep(&stream->fifo) ; + + stream->eof_met = true ; /* essential information */ + + stream->steal_this = false ; /* keep tidy */ + stream->iac = false ; + stream->in.state = kst_null ; + stream->pushed_in.state = kst_null ; +} ; + +/*============================================================================== + * Input raw bytes to given keyboard stream. + * + * To steal the next keystroke, pass 'steal' = address of a keystroke structure. + * Otherwise, pass NULL. + * + * Note: when trying to steal, will complete any partial keystroke before + * stealing the next one. May exit from here: + * + * a. without having completed the partial keystroke. + * + * b. without having completed the keystroke to be stolen. + * + * State (b) is remembered by the keystroke_stream. + * + * Caller may have to call several times with steal != NULL to get a + * keystroke. + * + * If steal != NULL the keystroke will be set to the stolen keystroke. That + * will be type ks_null if nothing was available, and may be knull_eof. + * + * Note that never steals broken or truncated keystrokes. + * + * Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream. + * + * Updates the stream and returns updated raw + */ +extern void +keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, + keystroke steal) +{ + uint8_t* end ; + + /* Deal with EOF if required + * + * Any partial keystroke is converted into a broken keystroke and placed + * at the end of the stream. + * + * Note that this occurs before any attempt to steal a keystroke -- so can + * never steal a broken keystroke. + */ + if ((len == 0) && (ptr == NULL)) + { + stream->eof_met = true ; + stream->steal_this = false ; + + /* Loop to deal with any pending IAC and partial keystroke. + * + * An IAC in the middle of a real keystroke sequence appears before + * it. Do the same here, even with broken sequences. + * + * A partial IAC sequence may have pushed a partial real keystroke + * sequence -- so loop until have dealt with that. + */ + do + { + switch (stream->in.state) + { + case kst_null: /* not expecting anything, unless iac */ + keystroke_clear_iac(stream) ; + break ; + + case kst_cr: /* expecting something after CR */ + keystroke_clear_iac(stream) ; + + stream->in.len = 0 ; + keystroke_add_raw(stream, '\r') ; + keystroke_put(stream, ks_char, true, + stream->in.raw, stream->in.len) ; + break ; + + case kst_esc: /* expecting rest of escape */ + keystroke_clear_iac(stream) ; + + keystroke_put_esc(stream, '\0', 0) ; + break ; + + case kst_csi: + keystroke_clear_iac(stream) ; + + keystroke_put_csi(stream, '\0') ; + break ; + + case kst_iac_option: /* expecting rest of IAC */ + assert(!stream->iac) ; + /* fall through */ + case kst_iac_sub: + keystroke_put_iac_long(stream, true) ; + + /* For kst_iac_sub, an incomplete IAC could be anything, so + * don't include in the broken IAC, but don't lose it + * either. + */ + keystroke_clear_iac(stream) ; + break ; + + case kst_char: /* TBD */ + zabort("impossible keystroke stream state") ; + + default: + zabort("unknown keystroke stream state") ; + } ; + + assert(!stream->iac) ; /* must have dealt with this */ + + keystroke_in_pop(stream) ; /* pops kst_null, when all done */ + + } while (stream->in.state != kst_null) ; + } ; + + /* Update the stealing state + * + * steal != NULL => want to steal a keystroke + * + * If do not wish to steal now, must clear any + * remembered steal_this state. + * + * stream->steal_this => steal the next keystroke to complete. + * + * If want to steal a keystroke, this is set if + * currently "between" keystrokes, or later when + * reach that condition. + * + * Once set, this is remembered across calls to + * keystroke_input(), while still wish to steal. + */ + if (steal == NULL) + stream->steal_this = false; /* not now required */ + else + stream->steal_this = (stream->in.state == kst_null) ; + /* want to and can can steal the next + keystroke that completes */ + + /* Once EOF has been signalled, do not expect to receive any further input. + * + * However, keystroke_stream_set_eof() can set the stream artificially at + * EOF, and that is honoured here. + */ + if (stream->eof_met) + len = 0 ; + + /* Normal processing + * + * Note that when manages to steal a keystroke sets steal == NULL, and + * proceeds to collect any following keystrokes. + */ + end = ptr + len ; + while (ptr < end) + { + uint8_t u = *ptr++ ; + + /* IAC handling takes precedence over everything, except the <option> + * byte, which may be EXOPL, which happens to be 255 as well ! + * + * stream->iac means that the last thing seen was an IAC. + * + * First IAC sets the flag. On the next byte: + * + * * if is IAC, clears stream->iac, and lets the escaped IAC value + * through for further processing -- NOT in IAC state. + * + * * if is not IAC, will be let through for further processing, in + * IAC state. + */ + if ((u == tn_IAC) && (stream->in.state != kst_iac_option)) + { + if (stream->iac) + stream->iac = false ; /* IAC IAC => single IAC byte value */ + else + { + stream->iac = true ; /* seen an IAC */ + continue ; /* wait for next character */ + } ; + } ; + + /* If stream->iac, then need to worry about IAC XX + * + * Note that IAC sequences are entirely invisible to the general + * stream of keystrokes. So... IAC sequences may appear in the middle + * of multi-byte general keystroke objects. + * + * If this is not a simple 2 byte IAC, then must put whatever was + * collecting to one side, and deal with the IAC. + * + * Note: not interested in stealing an IAC object. + */ + if (stream->iac) + { + stream->iac = false ; /* expect will eat the IAC XX */ + + switch (stream->in.state) + { + case kst_null: + case kst_cr: + case kst_esc: + case kst_csi: + if (u < tn_SB) + /* This is a simple IAC XX, one byte IAC */ + keystroke_put_iac_one(stream, u) ; + else + /* This is a multi-byte IAC, so push whatever real + * keystroke sequence is currently on preparation, and + * set into kst_iac_option state. + */ + keystroke_in_push(stream, u) ; + break ; + + case kst_iac_sub: + assert(stream->in.raw[0] == tn_SB) ; + + if (u != tn_SE) + { + --ptr ; /* put back the XX */ + stream->iac = true ; /* put back the IAC */ + } ; + + keystroke_put_iac_long(stream, (u != tn_SE)) ; + keystroke_in_pop(stream) ; + break ; + + case kst_char: /* TBD */ + case kst_iac_option: + zabort("impossible keystroke stream state") ; + + default: + zabort("unknown keystroke stream state") ; + } ; + + continue ; + } ; + + /* No IAC complications... proceed per current state */ + switch (stream->in.state) + { + case kst_null: /* Expecting anything */ + stream->steal_this = (steal != NULL) ; + + if (u == '\r') + stream->in.state = kst_cr ; + else if (u == 0x1B) + stream->in.state = kst_esc ; + else if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */ + { + stream->in.len = 0 ; + stream->in.state = kst_csi ; + } + else + { + /* Won't steal NUL */ + if (!stream->steal_this || (u == '\0')) + keystroke_put_char(stream, u) ; + else + { + keystroke_steal_char(steal, stream, u) ; + stream->steal_this = false ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + } ; + break ; + + case kst_char: /* TBD */ + zabort("impossible keystroke stream state") ; + + case kst_cr: /* expecting something after CR */ + if ((u != '\n') && (u != '\r')) + { + if (u != '\0') + { + --ptr ; /* put back the duff XX */ + stream->iac = (u == tn_IAC) ; + /* re=escape if is IAC */ + } ; + u = '\r' ; + } ; + + if (!stream->steal_this) + keystroke_put_char(stream, u) ; + else + { + keystroke_steal_char(steal, stream, u) ; + stream->steal_this = false ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + break ; + + case kst_esc: /* Expecting XX after ESC */ + if (u == '[') + { + stream->in.len = 0 ; + stream->in.state = kst_csi ; + } + else + { + if (!stream->steal_this) + keystroke_put_esc(stream, u, 1) ; + else + { + keystroke_steal_esc(steal, stream, u) ; + stream->steal_this = false ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + } ; + break ; + + case kst_csi: /* Expecting ... after ESC [ or CSI */ + if ((u >= 0x20) && (u <= 0x3F)) + keystroke_add_raw(stream, u) ; + else + { + bool ok ; + + ok = stream->in.len < keystroke_max_len ; + /* have room for terminator */ + + if ((u < 0x40) || (u > 0x7E)) + { + --ptr ; /* put back the duff XX */ + stream->iac = (u == tn_IAC) ; + /* re=escape if is IAC */ + u = '\0' ; + ok = false ; /* broken */ + } ; + + if (!stream->steal_this || !ok) + keystroke_put_csi(stream, u) ; + else + { + keystroke_steal_csi(steal, stream, u) ; + stream->steal_this = false ; + steal = NULL ; + } ; + stream->in.state = kst_null ; + } ; + break ; + + case kst_iac_option: /* Expecting <option> after IAC XX */ + assert(stream->in.len == 1) ; + keystroke_add_raw(stream, u) ; + + if (stream->in.raw[0] == tn_SB) + stream->in.state = kst_iac_sub ; + else + { + keystroke_put_iac_long(stream, false) ; + keystroke_in_pop(stream) ; + } ; + break ; + + case kst_iac_sub: /* Expecting sub stuff */ + assert(stream->in.raw[0]== tn_SB) ; + + keystroke_add_raw(stream, u) ; + break ; + + default: + zabort("unknown keystroke stream state") ; + } + } ; + assert(ptr == end) ; + + /* If did not steal a keystroke, return a ks_null -- which may be + * a knull_eof. + */ + if (steal != NULL) + keystroke_set_null(stream, steal) ; +} ; + +/*------------------------------------------------------------------------------ + * Single level stack for keystroke input state, so that can handle IAC + * sequences transparently. + */ + +/* Push current state and set new current state for start of IAC option + * sequence. + */ +static void +keystroke_in_push(keystroke_stream stream, uint8_t u) +{ + assert(stream->pushed_in.state == kst_null) ; + + stream->pushed_in = stream->in ; + + stream->in.len = 1 ; + stream->in.raw[0] = u ; + + stream->in.state = kst_iac_option ; +} ; + +/* Pop the pushed state and clear the pushed state to kst_null */ +static void +keystroke_in_pop(keystroke_stream stream) +{ + stream->in = stream->pushed_in ; + + stream->pushed_in.state = kst_null ; + stream->pushed_in.len = 0 ; +} ; + +/*============================================================================== + * Fetch next keystroke from keystroke stream + * + * Returns: 1 => have a stroke type != ks_null + * 0 => stroke type is ks_null (may be EOF). + */ +extern int +keystroke_get(keystroke_stream stream, keystroke stroke) +{ + int b ; + uint8_t* p ; + uint8_t* e ; + + /* Get first byte and deal with FIFO empty response */ + b = vio_fifo_get_byte(&stream->fifo) ; + + if (b < 0) + return keystroke_set_null(stream, stroke) ; + + /* Fetch first byte and deal with the simple character case */ + if ((b & kf_compound) == 0) /* Simple character ? */ + { + stroke->type = ks_char ; + stroke->value = b ; + + stroke->flags = 0 ; + stroke->len = 1 ; + stroke->buf[0] = b ; + + return 1 ; + } ; + + /* Sex the compound keystroke */ + + stroke->type = b & kf_type_mask ; + stroke->value = 0 ; + stroke->flags = b & kf_flag_mask ; + stroke->len = keystroke_get_byte(stream) ; + + assert(stroke->len <= keystroke_max_len) ; + + /* Fetch what we need to the stroke buffer */ + p = stroke->buf ; + e = p + stroke->len ; + + while (p < e) + *p++ = keystroke_get_byte(stream) ; + + p = stroke->buf ; + + /* Complete the process, depending on the type */ + switch (stroke->type) + { + case ks_null: + zabort("ks_null found in FIFO") ; + + case ks_char: + /* If character is well formed, set its value */ + if (stroke->flags == 0) + { + assert((stroke->len > 0) && (stroke->len <= 4)) ; + while (p < e) + stroke->value = (stroke->value << 8) + *p++ ; + + /* NB: to do UTF-8 would need to create UTF form here */ + + } ; + break ; + + case ks_esc: + /* If have ESC X, set value = X */ + if (stroke->len == 1) + stroke->value = *p ; + else + assert(stroke->len == 0) ; + break ; + + case ks_csi: + /* If have the final X, set value = X */ + /* Null terminate the parameters */ + if (stroke->len != 0) + { + --e ; + stroke->value = *e ; + --stroke->len ; + } ; + *e = '\0' ; + break ; + + case ks_iac: + /* If have the command byte after IAC, set value */ + if (stroke->len > 0) + stroke->value = *p ; + break ; + + default: + zabort("unknown keystroke type") ; + } ; + + return 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Set given keystroke to ks_null -- and set to knull_eof if stream->eof_met + * + * Returns: 0 + */ +inline static int +keystroke_set_null(keystroke_stream stream, keystroke stroke) +{ + stroke->type = ks_null ; + stroke->value = stream->eof_met ? knull_eof : knull_not_eof ; + + stroke->flags = 0 ; + stroke->len = 0 ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Fetch 2nd or subsequent byte of keystroke. + * + * NB: it is impossible for partial keystrokes to be written, so this treats + * buffer empty as a FATAL error. + */ +inline static uint8_t +keystroke_get_byte(keystroke_stream stream) +{ + int b = vio_fifo_get_byte(&stream->fifo) ; + + passert(b >= 0) ; + + return b ; +} ; + +/*============================================================================== + * Functions to support keystroke_input. + */ + +/*------------------------------------------------------------------------------ + * If possible, add character to the stream->in.raw[] buffer + */ +static inline void +keystroke_add_raw(keystroke_stream stream, uint8_t u) +{ + if (stream->in.len < keystroke_max_len) + stream->in.raw[stream->in.len] = u ; + + ++stream->in.len ; +} ; + +/*------------------------------------------------------------------------------ + * Store simple character value + */ +static void +keystroke_put_char(keystroke_stream stream, uint32_t u) +{ + if (u < 0x80) + vio_fifo_put_byte(&stream->fifo, (uint8_t)u) ; + else + { + uint8_t buf[4] ; + uint8_t* p ; + + p = buf + 4 ; + + do + { + *(--p) = u & 0xFF ; + u >>= 8 ; + } + while (u != 0) ; + + keystroke_put(stream, ks_char, 0, p, (buf + 4) - p) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Store simple ESC. Is broken if length (after ESC) == 0 ! + */ +static void +keystroke_put_esc(keystroke_stream stream, uint8_t u, int len) +{ + keystroke_put(stream, ks_esc, (len == 0), &u, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store CSI. + * + * Plants the last character of the CSI in the buffer, even if has to overwrite + * the existing last character -- the sequence is truncated, but this way at + * least the end of the sequence is preserved. + * + * Is broken if u == '\0'. May also be truncated ! + */ +static void +keystroke_put_csi(keystroke_stream stream, uint8_t u) +{ + int l ; + + l = stream->in.len++ ; + + if (l >= keystroke_max_len) + l = keystroke_max_len - 1 ; + + stream->in.raw[l] = u ; /* plant terminator */ + + keystroke_put(stream, ks_csi, (u == '\0'), stream->in.raw, stream->in.len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store IAC -- if not broken, send it via any call-back. + */ +static void +keystroke_put_iac(keystroke_stream stream, bool broken, uint8_t* bytes, int len) +{ + bool dealt_with = false ; + + if (!broken && (stream->iac_callback != NULL)) + { + struct keystroke stroke ; + + assert((len >= 1) && (bytes != NULL) && (len <= keystroke_max_len)) ; + + stroke.type = ks_iac ; + stroke.value = bytes[0] ; + stroke.flags = 0 ; + stroke.len = len ; + + memcpy(&stroke.buf, bytes, len) ; + + dealt_with = (*stream->iac_callback)(stream->iac_callback_context, + &stroke) ; + } ; + + if (!dealt_with) + keystroke_put(stream, ks_iac, broken, bytes, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store one byte IAC. + */ +static void +keystroke_put_iac_one(keystroke_stream stream, uint8_t u) +{ + keystroke_put_iac(stream, false, &u, 1) ; +} ; + +/*------------------------------------------------------------------------------ + * Store long IAC. Is broken if says it is. + */ +static void +keystroke_put_iac_long(keystroke_stream stream, bool broken) +{ + keystroke_put_iac(stream, broken, stream->in.raw, stream->in.len) ; +} ; + +/*------------------------------------------------------------------------------ + * If in IAC state, issue broken IAC and clear state. + */ +static void +keystroke_clear_iac(keystroke_stream stream) +{ + if (stream->iac) + { + keystroke_put_iac(stream, true, NULL, 0) ; + stream->iac = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Store <first> <len> [<bytes>] + * + * If len == 0, bytes may be NULL + */ +static void +keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken, + uint8_t* bytes, int len) +{ + if (len > keystroke_max_len) + { + len = keystroke_max_len ; + type |= kf_truncated ; + } ; + + vio_fifo_put_byte(&stream->fifo, + kf_compound | (broken ? kf_broken : 0) | type) ; + vio_fifo_put_byte(&stream->fifo, len) ; + + if (len > 0) + vio_fifo_put(&stream->fifo, (void*)bytes, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Steal character value -- cannot be broken + */ +static void +keystroke_steal_char(keystroke steal, keystroke_stream stream, uint8_t u) +{ + steal->type = ks_char ; + steal->value = u ; + steal->flags = 0 ; + steal->len = 1 ; + steal->buf[0] = u ; +} ; + +/*------------------------------------------------------------------------------ + * Steal simple escape -- cannot be broken + */ +static void +keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u) +{ + steal->type = ks_esc ; + steal->value = u ; + steal->flags = 0 ; + steal->len = 1 ; + steal->buf[0] = u ; +} ; + +/*------------------------------------------------------------------------------ + * Steal CSI escape -- cannot be broken or truncated. + * + * In the stream-in.raw buffer the last character is the escape terminator, + * after the escape parameters. + * + * In keystroke buffer the escape parameters are '\0' terminated, and the + * escape terminator is the keystroke value. + */ +static void +keystroke_steal_csi(keystroke steal, keystroke_stream stream, uint8_t u) +{ + int len ; + + len = stream->in.len ; /* excludes the escape terminator */ + assert((len < keystroke_max_len) && (u >= 0x40) && (u <= 0x7E)) ; + + steal->type = ks_esc ; + steal->value = u ; + steal->flags = 0 ; + steal->len = len ; + + memcpy(steal->buf, stream->in.raw, len) ; + steal->buf[len] = '\0' ; +} ; + +/*============================================================================== + */ diff --git a/lib/keystroke.h b/lib/keystroke.h new file mode 100644 index 00000000..76fba15e --- /dev/null +++ b/lib/keystroke.h @@ -0,0 +1,194 @@ +/* Keystroke Buffering -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_KEYSTROKE_H +#define _ZEBRA_KEYSTROKE_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include <arpa/telnet.h> + +#include "zassert.h" +#include "vio_fifo.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Keystroke buffering + */ + +enum { keystroke_max_len = 100 } ; + +enum keystroke_type +{ + ks_null = 0, /* nothing, nada, bupkis... */ + ks_char, /* character -- uint32_t */ + ks_esc, /* ESC xx */ + ks_csi, /* ESC [ ... or CSI ... */ + ks_iac, /* Telnet command */ + + ks_type_count, + + ks_type_reserved = 0x0F, +} ; +CONFIRM(ks_type_count <= ks_type_reserved) ; + +enum keystroke_null +{ + knull_not_eof, + knull_eof +}; + +enum keystroke_flags +{ + kf_compound = 0x80, /* marker on all compound characters */ + + kf_reserved = 0x40, + kf_broken = 0x20, /* badly formed in some way */ + kf_truncated = 0x10, /* too big for buffer ! */ + /* for ks_null => EOF */ + + kf_flag_mask = 0x70, /* flags for the keystroke */ + + kf_type_mask = 0x0F, /* extraction of type */ +} ; + +CONFIRM(ks_type_reserved == (enum keystroke_type)kf_type_mask) ; + +typedef struct keystroke* keystroke ; +typedef struct keystroke_stream* keystroke_stream ; + +struct keystroke +{ + enum keystroke_type type ; + uint8_t flags ; /* the kf_flag_mask flags */ + + uint32_t value ; + + unsigned len ; + uint8_t buf[keystroke_max_len] ; +} ; + +#define keystroke_iac_callback_args void* context, keystroke stroke +typedef bool (keystroke_callback)(keystroke_iac_callback_args) ; + +/* Telnet commands/options */ +enum tn_Command +{ + tn_IAC = IAC, /* IAC IAC interpret as command: */ + tn_DONT = DONT, /* IAC DONT <opt> you are not to use option */ + tn_DO = DO, /* IAC DO <opt> please, you use option */ + tn_WONT = WONT, /* IAC WONT <opt> I won't use option */ + tn_WILL = WILL, /* IAC WILL <opt> I will use option */ + tn_SB = SB, /* IAC SB <opt> interpret as subnegotiation */ + tn_GA = GA, /* IAC GA you may reverse the line */ + tn_EL = EL, /* IAC EL erase the current line */ + tn_EC = EC, /* IAC EC erase the current character */ + tn_AYT = AYT, /* IAC AYT are you there */ + tn_AO = AO, /* IAC AO abort output--but let prog finish */ + tn_IP = IP, /* IAC IP interrupt process--permanently */ + tn_BREAK = BREAK, /* IAC BREAK break */ + tn_DM = DM, /* IAC DM data mark--for connect. cleaning */ + tn_NOP = NOP, /* IAC NOP nop */ + tn_SE = SE, /* IAC SE end sub negotiation */ + tn_EOR = EOR, /* IAC EOR end of record (transparent mode) */ + tn_ABORT = ABORT, /* IAC ABORT Abort process */ + tn_SUSP = SUSP, /* IAC SUSP Suspend process */ + tn_EOF = xEOF, /* IAC xEOF End of file: EOF is already used... */ + + tn_SYNCH = SYNCH, /* IAC SYNCH for telfunc calls */ +} ; + +enum tn_Option +{ + to_BINARY = TELOPT_BINARY, /* 8-bit data path */ + to_ECHO = TELOPT_ECHO, /* echo */ + to_RCP = TELOPT_RCP, /* prepare to reconnect */ + to_SGA = TELOPT_SGA, /* suppress go ahead */ + to_NAMS = TELOPT_NAMS, /* approximate message size */ + to_STATUS = TELOPT_STATUS, /* give status */ + to_TM = TELOPT_TM, /* timing mark */ + to_RCTE = TELOPT_RCTE, /* remote controlled tx and echo */ + to_NAOL = TELOPT_NAOL, /* neg. about output line width */ + to_NAOP = TELOPT_NAOP, /* neg. about output page size */ + to_NAOCRD = TELOPT_NAOCRD, /* neg. about CR disposition */ + to_NAOHTS = TELOPT_NAOHTS, /* neg. about horizontal tabstops */ + to_NAOHTD = TELOPT_NAOHTD, /* neg. about horizontal tab disp. */ + to_NAOFFD = TELOPT_NAOFFD, /* neg. about formfeed disposition */ + to_NAOVTS = TELOPT_NAOVTS, /* neg. about vertical tab stops */ + to_NAOVTD = TELOPT_NAOVTD, /* neg. about vertical tab disp. */ + to_NAOLFD = TELOPT_NAOLFD, /* neg. about output LF disposition */ + to_XASCII = TELOPT_XASCII, /* extended ascii character set */ + to_LOGOUT = TELOPT_LOGOUT, /* force logout */ + to_BM = TELOPT_BM, /* byte macro */ + to_DET = TELOPT_DET, /* data entry terminal */ + to_SUPDUP = TELOPT_SUPDUP, /* supdup protocol */ + to_SUPDUPOUTPUT = TELOPT_SUPDUPOUTPUT,/* supdup output */ + to_SNDLOC = TELOPT_SNDLOC, /* send location */ + to_TTYPE = TELOPT_TTYPE, /* terminal type */ + to_EOR = TELOPT_EOR, /* end or record */ + to_TUID = TELOPT_TUID, /* TACACS user identification */ + to_OUTMRK = TELOPT_OUTMRK, /* output marking */ + to_TTYLOC = TELOPT_TTYLOC, /* terminal location number */ + to_3270REGIME = TELOPT_3270REGIME, /* 3270 regime */ + to_X3PAD = TELOPT_X3PAD, /* X.3 PAD */ + to_NAWS = TELOPT_NAWS, /* window size */ + to_TSPEED = TELOPT_TSPEED, /* terminal speed */ + to_LFLOW = TELOPT_LFLOW, /* remote flow control */ + to_LINEMODE = TELOPT_LINEMODE, /* Linemode option */ + to_XDISPLOC = TELOPT_XDISPLOC, /* X Display Location */ + to_OLD_ENVIRON = TELOPT_OLD_ENVIRON, /* Old - Environment variables */ + to_AUTHENTICATION = TELOPT_AUTHENTICATION, /* Authenticate */ + to_ENCRYPT = TELOPT_ENCRYPT, /* Encryption option */ + to_NEW_ENVIRON = TELOPT_NEW_ENVIRON, /* New - Environment variables */ + to_EXOPL = TELOPT_EXOPL, /* extended-options-list */ +} ; + + +/*============================================================================== + * Functions + */ +extern keystroke_stream +keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, + void* iac_callback_context) ; + +extern void +keystroke_stream_set_eof(keystroke_stream stream) ; + +extern keystroke_stream +keystroke_stream_free(keystroke_stream stream) ; + +extern bool +keystroke_stream_empty(keystroke_stream stream) ; +extern bool +keystroke_stream_eof(keystroke_stream stream) ; + +extern void +keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, + keystroke steal) ; +extern int +keystroke_get(keystroke_stream stream, keystroke stroke) ; + +#endif /* _ZEBRA_KEYSTROKE_H */ diff --git a/lib/list_util.c b/lib/list_util.c new file mode 100644 index 00000000..720b8ca7 --- /dev/null +++ b/lib/list_util.c @@ -0,0 +1,80 @@ +/* List Utilities + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <list_util.h> + + +/*============================================================================== + * Single Base, Single Link + */ + +/*------------------------------------------------------------------------------ + * Deleting item + * + * Have to chase down list to find item. + * + * Note that p_this: + * + * * starts as pointer to the base pointer, so should really be void**, + * but that causes all sorts of problems with strict-aliasing. + * + * So: have to cast to (void**) before dereferencing to get the address + * of the first item on the list. + * + * * as steps along the list p_this points to the "next pointer" in the + * previous item. + * + * The _sl_p_next() macro adds the offset of the "next pointer" to the + * address of the this item. + * + * * at the end, assigns the item's "next pointer" to the "next pointer" + * field pointed at by p_this. + * + * Note again the cast to (void**). + * + * Returns: 0 => OK -- removed item from list (OR item == NULL) + * -1 => item not found on list + */ +extern int +ssl_del_func(void* p_this, void* item, size_t link_offset) +{ + void* this ; + + if (item == NULL) + return 0 ; + + while ((this = *(void**)p_this) != item) + { + if (this == NULL) + return -1 ; + + p_this = _sl_p_next(this, link_offset) ; + } ; + + *(void**)p_this = _sl_next(item, link_offset) ; + + return 0 ; +} ; + +/*============================================================================== + * Single Base, Double Link + */ + diff --git a/lib/list_util.h b/lib/list_util.h new file mode 100644 index 00000000..876b7b11 --- /dev/null +++ b/lib/list_util.h @@ -0,0 +1,729 @@ +/* List Utilities -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_LIST_UTIL_H +#define _ZEBRA_LIST_UTIL_H + +#include <stddef.h> + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*------------------------------------------------------------------------------ + * Note that the following fell foul of "strict-aliasing": + * + * #define ssl_del_head(base, next) \ + * ssl_del_head_func_i((void**)&(base), _lu_off(base, next)) + * + * Inline void* + * ssl_del_head_func_i(void** p_base, size_t link_offset) + * { + * void* item = *p_base ; + * + * if (item != NULL) + * *p_base = _sl_next(item, link_offset) ; + * + * return item ; + * } ; + * + * the assignment to *p_base is, apparently, unacceptable. This works + * perfectly well as am ordinary function. Using a GNUC extension it is + * possible to avoid the function call... hence the ugly skips. + */ +#ifdef __GNUC__ +#define __GNUC__LIST_UTIL +#endif + +/*============================================================================== + * These utilities provide for linked lists of items, where the list pointers + * are fields in the items. + * + * This is a little less general that the linklist stuff, but carries less + * overhead. + * + * The items will be structures of some sort, and are described here as being + * of type "struct item". Pointers to those items will be of type + * "struct item*". + * + * Most of these utilities are implemented as macros. + * + *------------------------------------------------------------------------------ + * Links and Bases. + * + * For a singly linked list, the item declaration is straightforward: + * + * struct item + * { + * .... + * struct item* foo_next ; + * .... + * } + * + * The item can live on more than one list, all that is required is that each + * list has its next pointer. + * + * For double linked lists, the item may be declared: + * + * struct item + * { + * .... + * struct dl_list_pair(struct item*) foo_list ; + * .... + * } ; + * + * A single base is straighforward: + * + * struct item* foo_base ; + * + * and that may be a variable or a structure field. + * + * A double base may be declared: + * + * struct dl_base_pair(struct item*) foo_base ; + * + * Various ways to construct structures or structure types: + * + * typedef struct dl_list_pair(struct foo*) foo_list ; + * + * struct foo_list dl_list_pair(struct foo*) ; + * + * struct foo_base dl_base_pair(struct foo*) ; + */ + +#define dl_list_pair(ptr_t) { ptr_t next ; ptr_t prev ; } + +#define dl_base_pair(ptr_t) { ptr_t head ; ptr_t tail ; } + +struct dl_void_list_pair list_pair(void*) ; +struct dl_void_base_pair base_pair(void*) ; + +#define _lu_off(obj, field) ((char*)&((obj)->field) - (char*)(obj)) + +/*============================================================================== + * Single Base, Single Link + * + * To delete entry must chase down list to find it. + * + * Supports: + * + * ssl_init(base) -- initialise base + * + * An empty list has a NULL base. + * + * ssl_push(base, item, next) -- add at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ssl_del(base, item, next) -- delete from list + * + * Treat as function returning int. Does nothing if the item is NULL. + * + * Returns: 0 => OK -- removed item from list (OR item == NULL) + * -1 => item not found on list + * + * ssl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ssl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ssl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * ssl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if item is NULL. + * + * Note that ssl_del() and ssl_pop() do NOT affect the item->next pointer. + * + * Where: + * + * "base" to be an r-value of type struct item* + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "next" to be the name of a field in struct item, with type struct item* + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct item* bar_next ; + * ... + * } ; + * + * static struct item* bar_base ; // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * ssl_push(bar_base, q, bar_next) ; + * + * // remove item from list + * ssl_del(bar_base, q, bar_next) ; + * + * // walk a list + * struct item* t = ssl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = ssl_next(t, bar_next) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (ssl_pop(&t, bar_base, bar_next) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = ssl_head(bar_base) != NULL) + * { + * .... + * ssl_del_head(bar_base, bar_next) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct item* bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * ssl_push(parent->bar_base, item, bar_next) ; + * .... + * } + */ + +#define ssl_init(base) \ + ((base) = NULL) + +#define ssl_push(base, item, next) \ + do { (item)->next = (base) ; \ + (base) = item ; \ + } while (0) + +extern int ssl_del_func(void* p_this, void* obj, size_t link_offset) + __attribute__((noinline)) ; + +#define ssl_del(base, item, next) \ + ssl_del_func((void*)(&base), item, _lu_off(item, next)) + +#define ssl_del_head(base, next) \ + do { if ((base) != NULL) \ + (base) = (base)->next ; \ + } while (0) + +#define ssl_pop(dst, base, next) \ + ((*(dst) = (base)) != NULL ? ((base) = (base)->next, *(dst)) : NULL) + +#define ssl_head(base) (base) + +#define ssl_next(item, next) \ + ((item) != NULL ? (item)->next : NULL) + +/* _sl_p_next(item, off) -- pointer to next pointer at given offset + * _sl_next(item, off) -- contents of next pointer at given offset + */ + +#define _sl_p_next(item, off) \ + ( (char*)(item) + (off) ) + +#define _sl_next(item, off) \ + *(void**)_sl_p_next(item, off) + +/*============================================================================== + * Single Base, Double Link + * + * Can delete entry directly. + * + * Supports: + * + * sdl_init(base) -- initialise base + * + * An empty list has a NULL base. + * + * sdl_push(base, item, list) -- add at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * sdl_del(base, item, list) -- delete from list + * + * Treat as void function. Does nothing if the item is NULL. + * + * Undefined if item is not on the list. + * + * sdl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * sdl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * sdl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * sdl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * sdl_prev(item, next) -- step to prev item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * Note that sdl_del() and sdl_pop() do NOT affect the item->list.next + * or item->list.prev pointers. + * + * Where: + * + * "base" to be an r-value of type: struct base_pair(struct item*)* + * + * That is... a variable or field which is a pointer to + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "list" to be the name of a field in struct item + * of type: struct list_pair(struct item*) + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct list_pair(struct item*) bar_list ; + * ... + * } ; + * + * static struct base_pair(struct item*) bar_base ; + * // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * sdl_push(bar_base, q, bar_list) ; + * + * // remove item from list + * sdl_del(bar_base, q, bar_list) ; + * + * // walk a list + * struct item* t = sdl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = sdl_next(t, bar_list) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (sdl_pop(&t, bar_base, bar_list) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = sdl_head(bar_base) != NULL) + * { + * .... + * sdl_del_head(bar_base, bar_list) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct base_pair(struct item*) bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * sdl_push(parent->bar_base, item, bar_list) ; + * .... + * } + */ + +#define sdl_init(base) \ + ((base) = NULL) + +#define sdl_push(base, item, list) \ + do { confirm(_lu_off(base, list.next) == _lu_off(item, list.next)) ; \ + confirm(_lu_off(base, list.prev) == _lu_off(item, list.prev)) ; \ + (item)->list.next = (base) ; \ + (item)->list.prev = NULL ; \ + if ((base) != NULL) \ + (base)->list.prev = (item) ; \ + (base) = (item) ; \ + } while (0) + +#define sdl_del(base, item, list) \ + do { confirm(_lu_off(base, list.next) == _lu_off(item, list.next)) ; \ + confirm(_lu_off(base, list.prev) == _lu_off(item, list.prev)) ; \ + if ((item) != NULL) \ + { \ + if ((item)->list.next != NULL) \ + (item)->list.next->list.prev = (item)->list.prev ; \ + if ((item)->list.prev != NULL) \ + (item)->list.prev->list.next = (item)->list.next ; \ + else \ + (base) = (item)->list.next ; \ + } ; \ + } while (0) + +#define sdl_del_head(base, list) \ + do { if ((base) != NULL) \ + { \ + (base) = (base)->list.next ; \ + if ((base) != NULL) \ + (base)->list.prev = NULL ; \ + } \ + } while (0) + +#define sdl_pop(dst, base, list) \ + ((*(dst) = (base)) != NULL \ + ? ( ((base) = (base)->list.next) != NULL \ + ? ( (base)->list.prev = NULL, *(dst) ) : *(dst) ) : NULL) + +#define sdl_head(base) (base) + +#define sdl_next(item, list) \ + ((item) != NULL ? (item)->list.next : NULL) + +#define sdl_prev(item, list) \ + ((item) != NULL ? (item)->list.prev : NULL) + +/* _dl_p_next(obj, off) -- pointer to next pointer at given offset + * _dl_next(obj, off) -- contents of next pointer at given offset + * _dl_p_prev(obj, off) -- pointer to prev pointer at given offset + * _dl_prev(obj, off) -- contents of prev pointer at given offset + */ +#define _dl_p_next(obj, off) \ + ( (void**)( (char*)(obj) + (off) + 0 ) ) + +#define _dl_next(obj, off) \ + *_dl_p_next(obj, off) + +#define _dl_p_prev(obj, off) \ + ( (void**)( (char*)(obj) + (off) _ sizeof(void*) ) ) + +#define _dl_prev(obj, off) \ + *_dl_p_next(obj, off) + +/*============================================================================== + * Double Base, Double Link + * + * Can delete entry directly. Can insert and remove at tail. + * + * Supports: + * + * ddl_init(base) -- initialise base + * + * An empty list has *both* head and tail pointers NULL. + * + * NB: confusion will arise if only one of these pointers is NULL. + * + * ddl_push(base, item, list) -- insert at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ddl_append(base, item, list) -- insert at tail of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ddl_in_after(after, base, item, list) -- insert after + * + * Treat as void function. The after & item may *not* be NULL. + * + * Undefined if item is already on any list (including this one), or if + * after is not on the list. + * + * ddl_in_before(before, base, item, list) -- insert before + * + * Treat as void function. The before & item may *not* be NULL. + * + * Undefined if item is already on any list (including this one), or if + * before is not on the list. + * + * ddl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ddl_crop(&dst, base, next) -- crop tail of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ddl_del(base, item, list) -- delete from list + * + * Treat as void function. Does nothing if the item is NULL. + * + * Undefined if item is not on the list. + * + * ddl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ddl_del_tail(base, next) -- delete tail of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ddl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * ddl_tail(base) -- return tail of list + * + * Treat as function returning void*. + * + * ddl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * ddl_prev(item, next) -- step to prev item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * Note that ddl_del() and ddl_pop() do NOT affect the item->list.next + * or item->list.prev pointers. + * + * Where: + * + * "base" to be an r-value of type: struct base_pair(struct item*)* + * + * That is... a variable or field which is a pointer to + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "list" to be the name of a field in struct item + * of type: struct list_pair(struct item*) + * + * + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct list_pair(struct item*) bar_list ; + * ... + * } ; + * + * static struct base_pair(struct item*) bar_base ; + * // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * ddl_push(bar_base, q, bar_list) ; + * + * // remove item from list + * ddl_del(bar_base, q, bar_list) ; + * + * // walk a list + * struct item* t = ddl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = ddl_next(t, bar_list) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (ddl_pop(&t, bar_base, bar_list) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = ddl_head(bar_base) != NULL) + * { + * .... + * ddl_del_head(bar_base, bar_list) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct base_pair(struct item*) bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * ddl_push(parent->bar_base, item, bar_list) ; + * .... + * } + */ + +#define ddl_init(base) \ + ((base).head = (base).tail = NULL) + +#define ddl_push(base, item, list) \ + do { (item)->list.next = (base).head ; \ + (item)->list.prev = NULL ; \ + if ((base).head != NULL) \ + (base).head->list.prev = (item) ; \ + else \ + (base).tail = (item) ; \ + (base).head = (item) ; \ + } while (0) + +#define ddl_append(base, item, list) \ + do { (item)->list.next = NULL ; \ + (item)->list.prev = (base).tail ; \ + if ((base).tail != NULL) \ + (base).tail->list.next = (item) ; \ + else \ + (base).head = (item) ; \ + (base).tail = (item) ; \ + } while (0) + +#define ddl_in_after(after, base, item, list) \ + do { (item)->list.next = (after)->list.next ; \ + (item)->list.prev = (after) ; \ + if ((after)->list.next != NULL) \ + (after)->list.next->list.prev = (item) ; \ + else \ + (base).tail = (item) ; \ + (after)->list.next = (item) ; \ + } while (0) + +#define ddl_in_before(before, base, item, list) \ + do { (item)->list.next = (before) ; \ + (item)->list.prev = (before)->list.prev ; \ + if ((before)->list.prev != NULL) \ + (before)->list.prev->list.next = (item) ; \ + else \ + (base).head = (item) ; \ + (before)->list.prev = (item) ; \ + } while (0) + +#define ddl_del(base, item, list) \ + do { if ((item) != NULL) \ + { \ + if ((item)->list.next != NULL) \ + (item)->list.next->list.prev = (item)->list.prev ; \ + else \ + (base).tail = (item)->list.prev ; \ + if ((item)->list.prev != NULL) \ + (item)->list.prev->list.next = (item)->list.next ; \ + else \ + (base).head = (item)->list.next ; \ + } ; \ + } while (0) + +#define ddl_del_head(base, list) \ + do { if ((base).head != NULL) \ + { \ + (base).head = (base).head->list.next ; \ + if ((base).head != NULL) \ + (base).head->list.prev = NULL ; \ + else \ + (base).tail = NULL ; \ + } \ + } while (0) + +#define ddl_del_tail(base, list) \ + do { if ((base).tail != NULL) \ + { \ + (base).tail = (base).tail->list.prev ; \ + if ((base).tail != NULL) \ + (base).tail->list.next = NULL ; \ + else \ + (base).head = NULL ; \ + } \ + } while (0) + +#define ddl_pop(dst, base, list) \ + ((*(dst) = (base).head) != NULL \ + ? ( ((base).head = (base).head->list.next) != NULL \ + ? ( (base).head->list.prev = NULL, *(dst) ) \ + : ( (base).tail = NULL, *(dst) ) ) \ + : NULL) + +#define ddl_crop(dst, base, list) \ + ((*(dst) = (base).tail) != NULL \ + ? ( ((base).tail = (base).tail->list.prev) != NULL \ + ? ( (base).tail->list.next = NULL, *(dst) ) \ + : ( (base).head = NULL, *(dst) ) ) \ + : NULL) + +#define ddl_head(base) ((base).head) + +#define ddl_tail(base) ((base).tail) + +#define ddl_next(item, list) \ + ((item) != NULL ? (item)->list.next : NULL) + +#define ddl_prev(item, list) \ + ((item) != NULL ? (item)->list.prev : NULL) + +#endif /* _ZEBRA_LIST_UTIL_H */ @@ -19,12 +19,14 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> #include "log.h" +#include "vty.h" +#include "uty.h" #include "memory.h" #include "command.h" #ifndef SUNOS_5 @@ -34,12 +36,24 @@ #ifdef HAVE_UCONTEXT_H #include <ucontext.h> #endif +#include "qpthreads.h" +#include "qfstring.h" +#include "sigevent.h" + +/* log is protected by the same mutext as vty, see comments in vty.c */ + +/* prototypes */ +static int uzlog_reset_file (struct zlog *zl); +static void zlog_abort (const char *mess) __attribute__ ((noreturn)); +static void vzlog (struct zlog *zl, int priority, const char *format, va_list args); +static void uzlog_backtrace(int priority); +static void uvzlog (struct zlog *zl, int priority, const char *format, va_list args); static int logfile_fd = -1; /* Used in signal handler. */ struct zlog *zlog_default = NULL; -const char *zlog_proto_names[] = +const char *zlog_proto_names[] = { "NONE", "DEFAULT", @@ -66,19 +80,56 @@ const char *zlog_priority[] = "debugging", NULL, }; - - -/* For time string format. */ +/*============================================================================== + * Time stamp handling -- gettimeofday(), localtime() and strftime(). + * + * Maintains a cached form of the current time (under the vty/log mutex), so + * that can avoid multiple calls of localtime() and strftime() per second. + * + * The value from gettimeofday() is in micro-seconds. Can provide timestamp + * with any number of decimal digits, but at most 6 will be significant. + */ + +static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ; -size_t +/*------------------------------------------------------------------------------ + * Fill buffer with current time, to given number of decimal digits. + * + * If given buffer is too small, provides as many characters as possible. + * + * Returns: number of characters in buffer, not including trailing '\0'. + * + * NB: does no rounding. + * + * NB: buflen MUST be > 1 and buf MUST NOT be NULL. + */ +extern size_t quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { + qf_str_t qfs ; + + VTY_LOCK() ; + + qfs_init(&qfs, buf, buflen) ; + uquagga_timestamp(&qfs, timestamp_precision) ; + + VTY_UNLOCK() ; + return qfs_len(&qfs) ; +} + +/*------------------------------------------------------------------------------ + * unprotected version for when mutex already held + */ +static void +uquagga_timestamp(qf_str qfs, int timestamp_precision) +{ static struct { time_t last; size_t len; - char buf[28]; + char buf[timestamp_buffer_len]; } cache; + struct timeval clock; /* would it be sufficient to use global 'recent_time' here? I fear not... */ @@ -87,134 +138,175 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) /* first, we update the cache if the time has changed */ if (cache.last != clock.tv_sec) { - struct tm *tm; + struct tm tm; cache.last = clock.tv_sec; - tm = localtime(&cache.last); - cache.len = strftime(cache.buf, sizeof(cache.buf), - "%Y/%m/%d %H:%M:%S", tm); + localtime_r(&cache.last, &tm); + cache.len = strftime(cache.buf, sizeof(cache.buf), TIMESTAMP_FORM, &tm) ; + assert(cache.len > 0) ; } + /* note: it's not worth caching the subsecond part, because - chances are that back-to-back calls are not sufficiently close together - for the clock not to have ticked forward */ - - if (buflen > cache.len) - { - memcpy(buf, cache.buf, cache.len); - if ((timestamp_precision > 0) && - (buflen > cache.len+1+timestamp_precision)) - { - /* should we worry about locale issues? */ - static const int divisor[] = {0, 100000, 10000, 1000, 100, 10, 1}; - int prec; - char *p = buf+cache.len+1+(prec = timestamp_precision); - *p-- = '\0'; - while (prec > 6) - /* this is unlikely to happen, but protect anyway */ - { - *p-- = '0'; - prec--; - } - clock.tv_usec /= divisor[prec]; - do - { - *p-- = '0'+(clock.tv_usec % 10); - clock.tv_usec /= 10; - } - while (--prec > 0); - *p = '.'; - return cache.len+1+timestamp_precision; - } - buf[cache.len] = '\0'; - return cache.len; - } - if (buflen > 0) - buf[0] = '\0'; - return 0; -} + * chances are that back-to-back calls are not sufficiently close together + * for the clock not to have ticked forward + */ + + qfs_append_n(qfs, cache.buf, cache.len) ; -/* Utility routine for current time printing. */ + /* Add decimal part as required. */ + if (timestamp_precision > 0) + { + /* should we worry about locale issues? */ + static const int divisor[] = { 1, /* 0 */ + 100000, 10000, 1000, /* 1, 2, 3 */ + 100, 10, 1}; /* 4, 5, 6 */ + int prec; + + prec = timestamp_precision ; + if (prec > 6) + prec = 6 ; + + qfs_append_n(qfs, ".", 1) ; + qfs_unsigned(qfs, clock.tv_usec / divisor[prec], 0, 0, prec) ; + + if (prec < timestamp_precision) + qfs_append_ch_x_n(qfs, '0', timestamp_precision - prec) ; + } ; +} ; + +/*============================================================================== + * va_list version of zlog + */ static void -time_print(FILE *fp, struct timestamp_control *ctl) +vzlog (struct zlog *zl, int priority, const char *format, va_list args) { - if (!ctl->already_rendered) - { - ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); - ctl->already_rendered = 1; - } - fprintf(fp, "%s ", ctl->buf); + VTY_LOCK() ; + uvzlog(zl, priority, format, args); + VTY_UNLOCK() ; } - - -/* va_list version of zlog. */ + +/* va_list version of zlog. Unprotected assumes mutex already held*/ static void -vzlog (struct zlog *zl, int priority, const char *format, va_list args) +uvzlog (struct zlog *zl, int priority, const char *format, va_list va) { - struct timestamp_control tsctl; - tsctl.already_rendered = 0; + struct logline ll ; /* prepares line for output, here */ + + VTY_ASSERT_LOCKED() ; - /* If zlog is not specified, use default one. */ + ll.p_nl = NULL ; /* Nothing generated, yet */ + + /* If zlog is not specified, use default one. */ if (zl == NULL) - zl = zlog_default; + zl = zlog_default ; - /* When zlog_default is also NULL, use stderr for logging. */ + /* When zlog_default is also NULL, use stderr for logging. */ if (zl == NULL) { - tsctl.precision = 0; - time_print(stderr, &tsctl); - fprintf (stderr, "%s: ", "unknown"); - vfprintf (stderr, format, args); - fprintf (stderr, "\n"); - fflush (stderr); - - /* In this case we return at here. */ - return; + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; + write(fileno(stderr), ll.line, ll.len) ; } - tsctl.precision = zl->timestamp_precision; - - /* Syslog output */ - if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) + else { - va_list ac; - va_copy(ac, args); - vsyslog (priority|zlog_default->facility, format, ac); - va_end(ac); - } + /* Syslog output */ + if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) + { + va_list ac; + va_copy(ac, va); + vsyslog (priority|zlog_default->facility, format, ac); + va_end(ac); + } - /* File output. */ - if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) - { - va_list ac; - time_print (zl->fp, &tsctl); - if (zl->record_priority) - fprintf (zl->fp, "%s: ", zlog_priority[priority]); - fprintf (zl->fp, "%s: ", zlog_proto_names[zl->protocol]); - va_copy(ac, args); - vfprintf (zl->fp, format, ac); - va_end(ac); - fprintf (zl->fp, "\n"); - fflush (zl->fp); + /* File output. */ + if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) + { + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; + write(fileno(zl->fp), ll.line, ll.len) ; + } + + /* stdout output. */ + if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) + { + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; + write(fileno(zl->fp), ll.line, ll.len) ; + } + + /* Terminal monitor. */ + if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) + uty_log(&ll, zl, priority, format, va) ; } +} - /* stdout output. */ - if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) +/*------------------------------------------------------------------------------ + * Preparation of line to send to logging: file, stdout or "monitor" terminals. + * + * Takes copy of va_list before using it, so the va_list is unchanged. + */ +extern void +uvzlog_line(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va, enum ll_term term) +{ + char* p ; + const char* q ; + + p = ll->p_nl ; + + if (p != NULL) { - va_list ac; - time_print (stdout, &tsctl); - if (zl->record_priority) - fprintf (stdout, "%s: ", zlog_priority[priority]); - fprintf (stdout, "%s: ", zlog_proto_names[zl->protocol]); - va_copy(ac, args); - vfprintf (stdout, format, ac); - va_end(ac); - fprintf (stdout, "\n"); - fflush (stdout); + /* we have the line -- just need to worry about the crlf state */ + if (term == ll->term) + return ; /* exit here if all set */ } + else + { + /* must construct the line */ + qf_str_t qfs ; + va_list vac ; - /* Terminal monitor. */ - if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) - vty_log ((zl->record_priority ? zlog_priority[priority] : NULL), - zlog_proto_names[zl->protocol], format, &tsctl, args); -} + qfs_init(&qfs, ll->line, sizeof(ll->line) - 2) ; + /* leave space for '\n' or '\r''\n' */ + /* "<time stamp>" */ + uquagga_timestamp(&qfs, (zl != NULL) ? zl->timestamp_precision : 0) ; + + qfs_append_n(&qfs, " ", 1) ; + + /* "<priority>: " if required */ + if ((zl != NULL) && (zl->record_priority)) + { + qfs_append(&qfs, zlog_priority[priority]) ; + qfs_append(&qfs, ": ") ; + } ; + + /* "<protocol>: " or "unknown: " */ + if (zl != NULL) + q = zlog_proto_names[zl->protocol] ; + else + q = "unknown" ; + + qfs_append(&qfs, q) ; + qfs_append(&qfs, ": ") ; + + /* Now the log line itself */ + va_copy(vac, va); + qfs_vprintf(&qfs, format, vac) ; + va_end(vac); + + /* Set pointer to where the '\0' is. */ + p = ll->p_nl = qfs_ptr(&qfs) ; + } ; + + /* finish off with '\r''\n''\0' or '\n''\0' as required */ + if (term == llt_crlf) + *p++ = '\r' ; + + if (term != llt_nul) + *p++ = '\n' ; + + *p = '\0' ; + + ll->len = p - ll->line ; + ll->term = term ; +} ; + +/*============================================================================*/ static char * str_append(char *dst, int len, const char *src) @@ -428,6 +520,13 @@ zlog_signal(int signo, const char *action #undef LOC } +/* Ring down the curtain -- turn of SIGABRT handler and abort() */ +void zabort_abort(void) +{ + quagga_sigabrt_no_trap() ; + abort() ; +} + /* Log a backtrace using only async-signal-safe functions. Needs to be enhanced to support syslog logging. */ void @@ -492,7 +591,7 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) for (i = 0; i < size; i++) { s = buf; - if (bt) + if (bt) s = str_append(LOC, bt[i]); else { s = str_append(LOC,"[bt "); @@ -518,8 +617,16 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) void zlog_backtrace(int priority) { + VTY_LOCK() ; + uzlog_backtrace(priority); + VTY_UNLOCK() ; +} + +static void +uzlog_backtrace(int priority) +{ #ifndef HAVE_GLIBC_BACKTRACE - zlog(NULL, priority, "No backtrace available on this platform."); + uzlog(NULL, priority, "No backtrace available on this platform."); #else void *array[20]; int size, i; @@ -528,27 +635,38 @@ zlog_backtrace(int priority) if (((size = backtrace(array,sizeof(array)/sizeof(array[0]))) <= 0) || ((size_t)size > sizeof(array)/sizeof(array[0]))) { - zlog_err("Cannot get backtrace, returned invalid # of frames %d " + uzlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d " "(valid range is between 1 and %lu)", size, (unsigned long)(sizeof(array)/sizeof(array[0]))); return; } - zlog(NULL, priority, "Backtrace for %d stack frames:", size); + uzlog(NULL, priority, "Backtrace for %d stack frames:", size); if (!(strings = backtrace_symbols(array, size))) { - zlog_err("Cannot get backtrace symbols (out of memory?)"); + uzlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)"); for (i = 0; i < size; i++) - zlog(NULL, priority, "[bt %d] %p",i,array[i]); + uzlog(NULL, priority, "[bt %d] %p",i,array[i]); } else { for (i = 0; i < size; i++) - zlog(NULL, priority, "[bt %d] %s",i,strings[i]); + uzlog(NULL, priority, "[bt %d] %s",i,strings[i]); free(strings); } #endif /* HAVE_GLIBC_BACKTRACE */ } +/* unlocked version */ +void +uzlog (struct zlog *zl, int priority, const char *format, ...) +{ + va_list args; + + va_start(args, format); + uvzlog (zl, priority, format, args); + va_end (args); +} + void zlog (struct zlog *zl, int priority, const char *format, ...) { @@ -605,20 +723,71 @@ PLOG_FUNC(plog_debug, LOG_DEBUG) void _zlog_assert_failed (const char *assertion, const char *file, - unsigned int line, const char *function) + unsigned int line, const char *function) { + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, + "Assertion `%s' failed in file %s, line %u, function %s", + assertion, file, line, (function ? function : "?")); + zlog_abort(buff); +} + +/* Abort with message */ +void +_zlog_abort_mess (const char *mess, const char *file, + unsigned int line, const char *function) +{ + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, "%s, in file %s, line %u, function %s", + mess, file, line, (function ? function : "?")); + zlog_abort(buff); +} + +/* Abort with message and errno and strerror() thereof */ +void +_zlog_abort_errno (const char *mess, const char *file, + unsigned int line, const char *function) +{ + _zlog_abort_err(mess, errno, file, line, function); +} + +/* Abort with message and given error and strerror() thereof */ +void +_zlog_abort_err (const char *mess, int err, const char *file, + unsigned int line, const char *function) +{ + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, + "%s, in file %s, line %u, function %s, %s", + mess, file, line, (function ? function : "?"), + errtoa(err, 0).str); + zlog_abort(buff); +} + + +static void +zlog_abort (const char *mess) +{ +#if VTY_DEBUG + /* May not be locked -- but that doesn't matter any more */ + ++vty_lock_count ; +#endif + /* Force fallback file logging? */ if (zlog_default && !zlog_default->fp && ((logfile_fd = open_crashlog()) >= 0) && ((zlog_default->fp = fdopen(logfile_fd, "w")) != NULL)) zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR; - zlog(NULL, LOG_CRIT, "Assertion `%s' failed in file %s, line %u, function %s", - assertion,file,line,(function ? function : "?")); - zlog_backtrace(LOG_CRIT); - abort(); + + uzlog(NULL, LOG_CRIT, "%s", mess); + uzlog_backtrace(LOG_CRIT); + zabort_abort(); } - + /* Open log stream */ struct zlog * openzlog (const char *progname, zlog_proto_t protocol, @@ -641,7 +810,7 @@ openzlog (const char *progname, zlog_proto_t protocol, zl->default_lvl = LOG_DEBUG; openlog (progname, syslog_flags, zl->facility); - + return zl; } @@ -663,10 +832,17 @@ closezlog (struct zlog *zl) void zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level) { + VTY_LOCK() ; + if (zl == NULL) zl = zlog_default; - zl->maxlvl[dest] = log_level; + if (zl != NULL) + { + zl->maxlvl[dest] = log_level; + } + + VTY_UNLOCK() ; } int @@ -674,46 +850,68 @@ zlog_set_file (struct zlog *zl, const char *filename, int log_level) { FILE *fp; mode_t oldumask; + int result = 1; + + VTY_LOCK() ; /* There is opend file. */ - zlog_reset_file (zl); + uzlog_reset_file (zl); /* Set default zl. */ if (zl == NULL) zl = zlog_default; - /* Open file. */ - oldumask = umask (0777 & ~LOGFILE_MASK); - fp = fopen (filename, "a"); - umask(oldumask); - if (fp == NULL) - return 0; - - /* Set flags. */ - zl->filename = strdup (filename); - zl->maxlvl[ZLOG_DEST_FILE] = log_level; - zl->fp = fp; - logfile_fd = fileno(fp); + if (zl != NULL) + { + /* Open file. */ + oldumask = umask (0777 & ~LOGFILE_MASK); + fp = fopen (filename, "a"); + umask(oldumask); + if (fp == NULL) + result = 0; + else + { + /* Set flags. */ + zl->filename = strdup (filename); + zl->maxlvl[ZLOG_DEST_FILE] = log_level; + zl->fp = fp; + logfile_fd = fileno(fp); + } + } - return 1; + VTY_UNLOCK() ; + return result; } /* Reset opend file. */ int zlog_reset_file (struct zlog *zl) { + int result; + VTY_LOCK() ; + result = uzlog_reset_file(zl); + VTY_UNLOCK() ; + return result; +} + +static int +uzlog_reset_file (struct zlog *zl) + { if (zl == NULL) zl = zlog_default; - if (zl->fp) - fclose (zl->fp); - zl->fp = NULL; - logfile_fd = -1; - zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; - - if (zl->filename) - free (zl->filename); - zl->filename = NULL; + if (zl != NULL) + { + if (zl->fp) + fclose (zl->fp); + zl->fp = NULL; + logfile_fd = -1; + zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; + + if (zl->filename) + free (zl->filename); + zl->filename = NULL; + } return 1; } @@ -723,54 +921,334 @@ int zlog_rotate (struct zlog *zl) { int level; + int result = 1; + + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; - if (zl->fp) - fclose (zl->fp); - zl->fp = NULL; - logfile_fd = -1; - level = zl->maxlvl[ZLOG_DEST_FILE]; - zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; + if (zl != NULL) + { + if (zl->fp) + fclose (zl->fp); + zl->fp = NULL; + logfile_fd = -1; + level = zl->maxlvl[ZLOG_DEST_FILE]; + zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; + + if (zl->filename) + { + mode_t oldumask; + int save_errno; + + oldumask = umask (0777 & ~LOGFILE_MASK); + zl->fp = fopen (zl->filename, "a"); + save_errno = errno; + umask(oldumask); + if (zl->fp == NULL) + { + /* can't call logging while locked */ + char *fname = strdup(zl->filename); + uzlog(NULL, LOG_ERR, + "Log rotate failed: cannot open file %s for append: %s", + fname, errtoa(save_errno, 0).str); + free(fname); + result = -1; + } + else + { + logfile_fd = fileno(zl->fp); + zl->maxlvl[ZLOG_DEST_FILE] = level; + } + } + } + VTY_UNLOCK() ; + return result; +} + +int +zlog_get_default_lvl (struct zlog *zl) +{ + int result = LOG_DEBUG; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; - if (zl->filename) + if (zl != NULL) { - mode_t oldumask; - int save_errno; + result = zl->default_lvl; + } - oldumask = umask (0777 & ~LOGFILE_MASK); - zl->fp = fopen (zl->filename, "a"); - save_errno = errno; - umask(oldumask); - if (zl->fp == NULL) - { - zlog_err("Log rotate failed: cannot open file %s for append: %s", - zl->filename, safe_strerror(save_errno)); - return -1; - } - logfile_fd = fileno(zl->fp); - zl->maxlvl[ZLOG_DEST_FILE] = level; + VTY_UNLOCK() ; + return result; +} + +void +zlog_set_default_lvl (struct zlog *zl, int level) +{ + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->default_lvl = level; } - return 1; + VTY_UNLOCK() ; +} + +/* Set logging level and default for all destinations */ +void +zlog_set_default_lvl_dest (struct zlog *zl, int level) +{ + int i; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->default_lvl = level; + + for (i = 0; i < ZLOG_NUM_DESTS; i++) + if (zl->maxlvl[i] != ZLOG_DISABLED) + zl->maxlvl[i] = level; + } + + VTY_UNLOCK() ; +} + +int +zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest) +{ + int result = ZLOG_DISABLED; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->maxlvl[dest]; + } + + VTY_UNLOCK() ; + return result; +} + +int +zlog_get_facility (struct zlog *zl) +{ + int result = LOG_DAEMON; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->facility; + } + + VTY_UNLOCK() ; + return result; +} + +void +zlog_set_facility (struct zlog *zl, int facility) +{ + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->facility = facility; + } + + VTY_UNLOCK() ; +} + +int +zlog_get_record_priority (struct zlog *zl) +{ + int result = 0; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->record_priority; + } + + VTY_UNLOCK() ; + return result; +} + +void +zlog_set_record_priority (struct zlog *zl, int record_priority) +{ + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->record_priority = record_priority; + } + VTY_UNLOCK() ; +} + +int +zlog_get_timestamp_precision (struct zlog *zl) +{ + int result = 0; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->timestamp_precision; + } + VTY_UNLOCK() ; + return result; +} + +void +zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision) +{ + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->timestamp_precision = timestamp_precision; + } + + VTY_UNLOCK() ; } - + +/* returns name of ZLOG_NONE if no zlog given and no default set */ +const char * +zlog_get_proto_name (struct zlog *zl) +{ + const char * result; + VTY_LOCK() ; + result = uzlog_get_proto_name(zl); + VTY_UNLOCK() ; + return result; +} + +/* unprotected version, assumes mutex held */ +const char * +uzlog_get_proto_name (struct zlog *zl) +{ + zlog_proto_t protocol = ZLOG_NONE; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + protocol = zl->protocol; + } + + return zlog_proto_names[protocol]; +} + +/* caller must free result */ +char * +zlog_get_filename (struct zlog *zl) +{ + char * result = NULL; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL && zl->filename != NULL) + { + result = strdup(zl->filename); + } + + VTY_UNLOCK() ; + return result; +} + +const char * +zlog_get_ident (struct zlog *zl) +{ + const char * result = NULL; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->ident; + } + + VTY_UNLOCK() ; + return result; +} + +/* logging to a file? */ +int +zlog_is_file (struct zlog *zl) +{ + int result = 0; + + VTY_LOCK() ; + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = (zl->fp != NULL); + } + + VTY_UNLOCK() ;; + return result; +} + /* Message lookup function. */ const char * lookup (const struct message *mes, int key) { const struct message *pnt; - for (pnt = mes; pnt->key != 0; pnt++) - if (pnt->key == key) + for (pnt = mes; pnt->key != 0; pnt++) + if (pnt->key == key) return pnt->str; return ""; } /* Older/faster version of message lookup function, but requires caller to pass - * in the array size (instead of relying on a 0 key to terminate the search). + * in the array size (instead of relying on a 0 key to terminate the search). * * The return value is the message string if found, or the 'none' pointer * provided otherwise. @@ -779,7 +1257,7 @@ const char * mes_lookup (const struct message *meslist, int max, int index, const char *none) { int pos = index - meslist[0].key; - + /* first check for best case: index is in range and matches the key * value in that slot. * NB: key numbering might be offset from 0. E.g. protocol constants @@ -798,7 +1276,7 @@ mes_lookup (const struct message *meslist, int max, int index, const char *none) if (meslist->key == index) { const char *str = (meslist->str ? meslist->str : none); - + zlog_debug ("message index %d [%s] found in position %d (max is %d)", index, str, i, max); return str; @@ -810,14 +1288,6 @@ mes_lookup (const struct message *meslist, int max, int index, const char *none) return none; } -/* Wrapper around strerror to handle case where it returns NULL. */ -const char * -safe_strerror(int errnum) -{ - const char *s = strerror(errnum); - return (s != NULL) ? s : "Unknown error"; -} - struct zebra_desc_table { unsigned int type; @@ -19,13 +19,14 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_LOG_H #define _ZEBRA_LOG_H #include <syslog.h> +#include "pthread_safe.h" /* Here is some guidance on logging levels to use: * @@ -43,7 +44,7 @@ * please use LOG_ERR instead. */ -typedef enum +typedef enum { ZLOG_NONE, ZLOG_DEFAULT, @@ -51,7 +52,7 @@ typedef enum ZLOG_RIP, ZLOG_BGP, ZLOG_OSPF, - ZLOG_RIPNG, + ZLOG_RIPNG, ZLOG_OSPF6, ZLOG_ISIS, ZLOG_MASC @@ -70,7 +71,7 @@ typedef enum } zlog_dest_t; #define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1) -struct zlog +struct zlog { const char *ident; /* daemon name (first arg to openlog) */ zlog_proto_t protocol; @@ -93,7 +94,11 @@ struct message const char *str; }; -/* Default logging strucutre. */ +/* module initialization */ +extern void zlog_init_r(void); +extern void zlog_destroy_r(void); + +/* Default logging structure. */ extern struct zlog *zlog_default; /* Open zlog function */ @@ -113,6 +118,9 @@ extern void closezlog (struct zlog *zl); /* Generic function for zlog. */ extern void zlog (struct zlog *zl, int priority, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); +/* assumed locked version for close friends */ +extern void uzlog (struct zlog *zl, int priority, const char *format, ...) + PRINTF_ATTRIBUTE(3, 4); /* Handy zlog functions. */ extern void zlog_err (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); @@ -147,11 +155,28 @@ extern int zlog_reset_file (struct zlog *zl); /* Rotate log. */ extern int zlog_rotate (struct zlog *); +/* getters & setters */ +extern int zlog_get_default_lvl (struct zlog *zl); +extern void zlog_set_default_lvl (struct zlog *zl, int level); +extern void zlog_set_default_lvl_dest (struct zlog *zl, int level); +extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest); +extern int zlog_get_facility (struct zlog *zl); +extern void zlog_set_facility (struct zlog *zl, int facility); +extern int zlog_get_record_priority (struct zlog *zl); +extern void zlog_set_record_priority (struct zlog *zl, int record_priority); +extern int zlog_get_timestamp_precision (struct zlog *zl); +extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision); +extern const char * zlog_get_ident (struct zlog *zl); +extern char * zlog_get_filename (struct zlog *zl); +extern int zlog_is_file (struct zlog *zl); +extern const char * zlog_get_proto_name (struct zlog *zl); +extern const char * uzlog_get_proto_name (struct zlog *zl); + /* For hackey massage lookup and check */ #define LOOKUP(x, y) mes_lookup(x, x ## _max, y, "(no item found)") extern const char *lookup (const struct message *, int); -extern const char *mes_lookup (const struct message *meslist, +extern const char *mes_lookup (const struct message *meslist, int max, int index, const char *no_item); @@ -168,6 +193,9 @@ extern void zlog_signal(int signo, const char *action #endif ); +/* Ring down the curtain -- turn of SIGABRT handler and abort() */ +extern void zabort_abort(void) __attribute__ ((noreturn)) ; + /* Log a backtrace. */ extern void zlog_backtrace(int priority); @@ -178,25 +206,66 @@ extern void zlog_backtrace(int priority); extern void zlog_backtrace_sigsafe(int priority, void *program_counter); /* Puts a current timestamp in buf and returns the number of characters - written (not including the terminating NUL). The purpose of - this function is to avoid calls to localtime appearing all over the code. - It caches the most recent localtime result and can therefore - avoid multiple calls within the same second. If buflen is too small, - *buf will be set to '\0', and 0 will be returned. */ + * written (not including the terminating NUL). The purpose of + * this function is to avoid calls to localtime appearing all over the code. + * It caches the most recent localtime result and can therefore + * avoid multiple calls within the same second. + * + * The buflen MUST be > 1 and the buffer address MUST NOT be NULL. + * + * If buflen is too small, writes buflen-1 characters followed by '\0'. + * + * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S + * + * This has a fixed length (leading zeros are included) of 19 characters + * (unless this code is still in use beyond the year 9999 !) + * + * Which may be followed by "." and a number of decimal digits, usually 1..6. + * + * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and + * rounding up for good measure -- buffer size = 32. + */ +#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S" + +enum { timestamp_buffer_len = 32 } ; + extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, char *buf, size_t buflen); -/* structure useful for avoiding repeated rendering of the same timestamp */ -struct timestamp_control { - size_t len; /* length of rendered timestamp */ - int precision; /* configuration parameter */ - int already_rendered; /* should be initialized to 0 */ - char buf[40]; /* will contain the rendered timestamp */ -}; +/* Generate line to be logged + * + * Structure used to hold line for log output -- so that need be generated + * just once even if output to multiple destinations. + * + * Note that the buffer length is a hard limit (including terminating '\n''\0' + * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging. + */ +enum { logline_buffer_len = 1008 } ; +enum ll_term +{ + llt_nul = 0, /* NB: also length of the terminator */ + llt_lf = 1, + llt_crlf = 2, +} ; + +struct logline { + char* p_nl ; /* address of the terminator */ + + enum ll_term term ; /* how line is terminated */ + + size_t len ; /* length including either '\r''\n' or '\n' */ + + char line[logline_buffer_len]; /* buffer */ +} ; + +extern void +uvzlog_line(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va, enum ll_term term) ; /* Defines for use in command construction: */ -#define LOG_LEVELS "(emergencies|alerts|critical|errors|warnings|notifications|informational|debugging)" +#define LOG_LEVELS "(emergencies|alerts|critical|errors|" \ + "warnings|notifications|informational|debugging)" #define LOG_LEVEL_DESC \ "System is unusable\n" \ diff --git a/lib/mem_tracker.c b/lib/mem_tracker.c new file mode 100644 index 00000000..b468ad2a --- /dev/null +++ b/lib/mem_tracker.c @@ -0,0 +1,590 @@ +/* Memory Allocation Tracker + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "vty.h" + +/*============================================================================== + * Memory Tracker + */ +typedef struct mem_descriptor* mem_descriptor ; +struct mem_descriptor +{ + void* addr ; + const char* name ; + + uint32_t next ; /* MS Type is encoded as MS 4 bits */ + uint32_t size ; /* LS Type is encoded as MS 4 bits */ +} ; + +typedef uint32_t md_index ; + +enum +{ + md_next_bits = 28, /* up to 256M allocated objects */ + md_next_mask = (1 << md_next_bits) - 1, + + md_index_max = md_next_mask + 1, + + md_size_bits = 28, /* up to 256M individual item */ + md_size_mask = (1 << md_size_bits) - 1, + + md_size_max = md_size_mask, + + md_next_type_bits = 32 - md_next_bits, + md_next_type_mask = (1 << md_next_type_bits) - 1, + md_size_type_bits = 32 - md_size_bits, + md_size_type_mask = (1 << md_size_type_bits) - 1, + + md_i_index_bits = 16, + md_i_index_count = 1 << md_i_index_bits, + md_i_index_mask = md_i_index_count - 1, + + md_page_bits = md_next_bits - md_i_index_bits, + md_page_count = 1 << md_page_bits, + md_page_mask = md_page_count - 1, +} ; + +CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ; + +static struct mem_type_tracker +{ + struct mem_tracker mt[MTYPE_MAX] ; +} mem_type_tracker ; + +static mem_descriptor mem_page_table[md_page_count] ; + +static mem_descriptor mem_free_descriptors ; +static md_index mem_next_index ; + +static struct mem_tracker mem ; + +uint32_t mem_base_count ; + +md_index* mem_bases ; + +inline static void +mem_md_set_type(mem_descriptor md, enum MTYPE mtype) +{ + uint32_t t_ms ; + uint32_t t_ls ; + + t_ms = mtype >> md_size_type_bits ; + t_ls = mtype ; + + t_ms = (t_ms & md_next_type_mask) << md_next_bits ; + t_ls = (t_ls & md_size_type_mask) << md_size_bits ; + + md->next = (md->next & md_next_mask) | t_ms ; + md->size = (md->size & md_size_mask) | t_ls ; +} ; + +inline static void +mem_md_set_next(mem_descriptor md, md_index next) +{ + md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ; +} ; + +inline static void +mem_md_set_size(mem_descriptor md, size_t size) +{ + md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ; +} ; + +inline static uint8_t +mem_md_type(mem_descriptor md) +{ + return ( (md->next >> (md_next_bits - md_size_type_bits)) + & (md_next_type_mask << md_size_type_bits) ) + | ( (md->size >> md_size_bits) & md_size_type_mask ) ; +} ; + +inline static md_index +mem_md_next(mem_descriptor md) +{ + return md->next & md_next_mask ; +} ; + +inline static size_t +mem_md_size(mem_descriptor md) +{ + return md->size & md_size_mask ; +} ; + +inline static mem_descriptor +mem_md_ptr(md_index mdi) +{ + mem_descriptor page ; + + if (mdi == 0) + return NULL ; + + page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ; + passert(page != NULL) ; + return page + (mdi & md_i_index_mask) ; +} ; + +static void mem_md_make_bases(void) ; + +inline static md_index* +mem_md_base(void* address) +{ + if (mem_bases == NULL) + mem_md_make_bases() ; + + return mem_bases + ((uintptr_t)address % mem_base_count) ; +} ; + +static void +mem_md_make_bases(void) +{ + md_index* bases_was = mem_bases ; + uint32_t count_was = mem_base_count ; + + mem_base_count += 256 * 1024 ; + mem_base_count |= 1 ; + mem_bases = calloc(mem_base_count, sizeof(md_index)) ; + + passert(mem_bases != NULL) ; + + if (bases_was == NULL) + passert(count_was == 0) ; + else + { + md_index* base = bases_was ; + md_index* new_base ; + md_index this ; + md_index next ; + mem_descriptor md ; + + while (count_was) + { + next = *base++ ; + while (next != 0) + { + this = next ; + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + new_base = mem_md_base(md->addr) ; + mem_md_set_next(md, *new_base) ; + *new_base = this ; + } ; + --count_was ; + } ; + + free(bases_was) ; + } ; +} ; + +static void +mem_md_make_descriptors(void) +{ + mem_descriptor md ; + md_index mdi ; + + mdi = mem_next_index ; + passert(mdi < md_index_max) ; + + mem_free_descriptors + = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] + = calloc(md_i_index_count, sizeof(struct mem_descriptor)) ; + + passert(mem_free_descriptors != NULL) ; + + mem_next_index += md_i_index_count ; + + if (mdi == 0) + { + ++mem_free_descriptors ; /* don't use index == 0 */ + ++mdi ; + } ; + + md = mem_free_descriptors ; + while (mdi < mem_next_index) + { + md->addr = md + 1 ; /* point at next entry */ + md->next = mdi ; /* set to point at self */ + ++md ; + ++mdi ; + } ; + (md-1)->addr = NULL ; /* set end of list */ +} ; + +inline static void +mem_md_malloc(enum MTYPE mtype, void* address, size_t size, const char* name) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md ; + md_index mdi ; + + passert(size <= md_size_max) ; + + if (mem_free_descriptors == NULL) + mem_md_make_descriptors() ; + + md = mem_free_descriptors ; + mem_free_descriptors = md->addr ; + mdi = md->next ; + + if (mem.tracked_count >= (mem_base_count * 4)) + mem_md_make_bases() ; + + base = mem_md_base(address) ; + + md->addr = address ; + md->name = name ; + md->size = size ; + md->next = *base ; + mem_md_set_type(md, mtype) ; + + *base = mdi ; + + ++mem.malloc_count ; + ++mem.tracked_count ; + + mem.tracked_size += size ; + + if (mem.tracked_max_count < mem.tracked_count) + mem.tracked_max_count = mem.tracked_count ; + + if (mem.tracked_max_size < mem.tracked_size) + mem.tracked_max_size = mem.tracked_size ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->malloc_count) ; + ++(mtt->tracked_count) ; + mtt->tracked_size += size ; + + if (mtt->tracked_max_count < mtt->tracked_count) + mtt->tracked_max_count = mtt->tracked_count ; + + if (mtt->tracked_max_size < mtt->tracked_size) + mtt->tracked_max_size = mtt->tracked_size ; +} ; + +inline static void +mem_md_free(enum MTYPE mtype, void* address) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md, prev_md ; + md_index this, next ; + + if (address == NULL) + return ; + + base = mem_md_base(address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + if (md->addr == address) + { + if (mem_md_type(md) != mtype) + zabort("memory type mismatch in free") ; + + ++mem.free_count ; + --mem.tracked_count ; + + mem.tracked_size -= mem_md_size(md) ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->free_count) ; + --(mtt->tracked_count) ; + mtt->tracked_size -= mem_md_size(md) ; + + if (prev_md == NULL) + *base = next ; + else + mem_md_set_next(prev_md, next) ; + + md->addr = mem_free_descriptors ; + mem_free_descriptors = md ; + md->next = this ; + + return ; + } + else + { + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being freed") ; +} ; + +inline static void +mem_md_realloc(enum MTYPE mtype, void* old_address, void* new_address, + size_t size, const char* name) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md, prev_md ; + md_index this, next ; + + if (old_address == NULL) + { + mem_md_malloc(mtype, new_address, size, name) ; + return ; + } ; + + passert(size <= md_size_max) ; + + base = mem_md_base(old_address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + if (md->addr == old_address) + { + if (mem_md_type(md) != mtype) + zabort("memory type mismatch in realloc") ; + + ++mem.realloc_count ; + + mem.tracked_size += size - mem_md_size(md) ; + + if (mem.tracked_max_size < mem.tracked_size) + mem.tracked_max_size = mem.tracked_size ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->realloc_count) ; + mtt->tracked_size += size - mem_md_size(md) ; + + if (mtt->tracked_max_size < mtt->tracked_size) + mtt->tracked_max_size = mtt->tracked_size ; + + md->name = name ; + mem_md_set_size(md, size) ; + + if (old_address == new_address) + return ; + + if (prev_md == NULL) + *base = next ; + else + mem_md_set_next(prev_md, next) ; + + base = mem_md_base(new_address) ; + mem_md_set_next(md, *base) ; + *base = this ; + + md->addr = new_address ; + + return ; + } + else + { + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being realloced") ; +} ; + +/*============================================================================== + * Memory Tracker Display + */ + +static const char* scale_d_tags [] = +{ + [0] = " " , + [1] = "k", + [2] = "m", + [3] = "g", +} ; + +static const char* scale_b_tags [] = +{ + [0] = " " , + [1] = "KiB", + [2] = "MiB", + [3] = "GiB", +} ; + +static char* +mem_show_commas(char* buff, size_t size, uint64_t val, const char* tag) +{ + char* p ; + const char* q ; + int n ; + + passert(size > 10) ; + + p = buff + size ; + *(--p) = '\0' ; + + q = tag + strlen(tag) ; + while ((p > buff) && (q > tag)) + *(--p) = *(--q) ; + + n = 3 ; + while (p > buff) + { + *(--p) = '0' + (val % 10) ; + val /= 10 ; + if (val == 0) + break ; + + if ((--n == 0) && (p > buff)) + { + *(--p) = ',' ; + n = 3 ; + } ; + } ; + + return p ; +} ; + +static char* +mem_show_count(char* buff, size_t size, uint64_t val, int scale) +{ + int i, r ; + + i = 0 ; + if (scale) + { + r = 0 ; + while ((i < 3) && (val >= 10000)) + { + r = (val % 1000) ; + val /= 1000 ; + ++i ; + } ; + if (r >= 500) { + val += 1 ; + if ((val == 10000) && (i < 3)) + { + val /= 1000 ; + ++i ; + } ; + } ; + } ; + + return mem_show_commas(buff, size, val, scale_d_tags[i]) ; +} ; + +static char* +mem_show_byte_count(char* buff, size_t size, uint64_t val, int scale) +{ + int i, r ; + + i = 0 ; + if (scale) + { + r = 0 ; + while ((i < 3) && (val >= 10000)) + { + r = (val % 1024) ; + val /= 1024 ; + ++i ; + } ; + if (r >= 512) { + val += 1 ; + if ((val == 10000) && (i < 3)) + { + val /= 1024 ; + ++i ; + } ; + } ; + } ; + + return mem_show_commas(buff, size, val, scale_b_tags[i]) ; +} ; + +static int +show_memory_tracker_summary(struct vty *vty) +{ + struct mem_tracker mt ; + enum { sbs = 100 } ; + char buf[sbs]; + size_t overhead ; + + LOCK ; + overhead = (sizeof(struct mem_descriptor) * mem_next_index) + + (sizeof(md_index) * mem_base_count) + + (sizeof(mem_descriptor) * md_page_count) ; + + mt = mem ; /* copy the overall memory information */ + UNLOCK ; + + vty_out (vty, "Memory Tracker Statistics:%s", VTY_NEWLINE); + vty_out (vty, " Current memory allocated: %10s%s", + mem_show_byte_count(buf, sbs, mt.tracked_size, 1), + VTY_NEWLINE); + vty_out (vty, " Current allocated objects: %8s%s", + mem_show_count (buf, sbs, mt.tracked_count, 1), + VTY_NEWLINE); + vty_out (vty, " Maximum memory allocated: %10s%s", + mem_show_byte_count(buf, sbs, mt.tracked_max_size, 1), + VTY_NEWLINE); + vty_out (vty, " Maximum allocated objects: %8s%s", + mem_show_count (buf, sbs, mt.tracked_max_count, 1), + VTY_NEWLINE); + vty_out (vty, " malloc/calloc call count: %8s%s", + mem_show_count (buf, sbs, mt.malloc_count, 1), + VTY_NEWLINE); + vty_out (vty, " realloc_call_count: %8s%s", + mem_show_count (buf, sbs, mt.realloc_count, 1), + VTY_NEWLINE); + vty_out (vty, " free call count: %8s%s", + mem_show_count (buf, sbs, mt.free_count, 1), + VTY_NEWLINE); + vty_out (vty, " Memory Tracker overhead: %10s%s", + mem_show_byte_count(buf, sbs, overhead, 1), + VTY_NEWLINE); + return 1; +} ; + +static int +show_memory_tracker_detail(struct vty *vty, struct mem_tracker* mt, + unsigned long alloc) +{ + enum { sbs = 100 } ; + char buf[sbs]; + + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_count, 1)) ; + vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_size, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_max_count, 1)) ; + vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_max_size, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->malloc_count, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->realloc_count, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->free_count, 1)) ; + + if (alloc != mt->tracked_count) + vty_out(vty, " %8s!!", mem_show_count(buf, sbs, alloc, 1)) ; + + return 1; +} ; diff --git a/lib/memory.c b/lib/memory.c index 4090fd90..49b20c14 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -28,131 +28,260 @@ #include "log.h" #include "memory.h" +#include "qpthreads.h" + +/* Needs to be qpthread safe. The system malloc etc are already + * thread safe, but we need to protect the stats + */ +static qpt_mutex_t memory_mutex; + +#define LOCK qpt_mutex_lock(&memory_mutex); +#define UNLOCK qpt_mutex_unlock(&memory_mutex); -static void alloc_inc (int); -static void alloc_dec (int); static void log_memstats(int log_priority); - + static const struct message mstr [] = { { MTYPE_THREAD, "thread" }, { MTYPE_THREAD_MASTER, "thread_master" }, { MTYPE_VECTOR, "vector" }, - { MTYPE_VECTOR_INDEX, "vector_index" }, + { MTYPE_VECTOR_BODY, "vector_index" }, { MTYPE_IF, "interface" }, { 0, NULL }, }; - + +/* If using the mem_tracker, include it now. */ + +typedef struct mem_tracker* mem_tracker ; +struct mem_tracker +{ + uint64_t malloc_count ; + uint64_t realloc_count ; + uint64_t free_count ; + + uint32_t tracked_count ; + size_t tracked_size ; + + uint32_t tracked_max_count ; + size_t tracked_max_size ; +} ; + +static void +mem_tracker_zeroise(struct mem_tracker* mem) +{ + memset(mem, 0, sizeof(struct mem_tracker)) ; +} ; + +#ifdef MEMORY_TRACKER +#include "mem_tracker.c" +#endif + +/*============================================================================== + * Keeping track of number of allocated objects of given type + */ + +static struct mstat +{ + struct + { + char *name ; + long alloc ; + } mt[MTYPE_MAX] ; +} mstat ; + +/*============================================================================== + * Memory allocation functions. + * + * NB: failure to allocate is FATAL -- so no need to test return value. + */ + /* Fatal memory allocation error occured. */ static void __attribute__ ((noreturn)) zerror (const char *fname, int type, size_t size) { - zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n", - fname, lookup (mstr, type), (int) size, safe_strerror(errno)); + zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n", + fname, lookup (mstr, type), (int) size, errtoa(errno, 0).str); log_memstats(LOG_WARNING); /* N.B. It might be preferable to call zlog_backtrace_sigsafe here, since that function should definitely be safe in an OOM condition. But unfortunately zlog_backtrace_sigsafe does not support syslog logging at this time... */ zlog_backtrace(LOG_WARNING); - abort(); + zabort_abort(); } -/* +/*------------------------------------------------------------------------------ + * Memory allocation. + * * Allocate memory of a given size, to be tracked by a given type. - * Effects: Returns a pointer to usable memory. If memory cannot - * be allocated, aborts execution. + * + * Returns: pointer to usable memory. + * + * NB: If memory cannot be allocated, aborts execution. */ void * -zmalloc (int type, size_t size) +zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = malloc (size); if (memory == NULL) - zerror ("malloc", type, size); - - alloc_inc (type); + { + UNLOCK ; + zerror ("malloc", mtype, size); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, memory, size, name) ; +#endif + UNLOCK ; + } ; return memory; } -/* - * Allocate memory as in zmalloc, and also clear the memory. +/*------------------------------------------------------------------------------ + * Memory allocation zeroising the allocated area. + * + * As zmalloc, plus zeroises the allocated memory. */ void * -zcalloc (int type, size_t size) +zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = calloc (1, size); if (memory == NULL) - zerror ("calloc", type, size); - - alloc_inc (type); + { + UNLOCK ; + zerror ("calloc", mtype, size); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, memory, size, name) ; +#endif + UNLOCK ; + } ; return memory; } -/* - * Given a pointer returned by zmalloc or zcalloc, free it and - * return a pointer to a new size, basically acting like realloc(). - * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the - * same type. - * Effects: Returns a pointer to the new memory, or aborts. +/*------------------------------------------------------------------------------ + * Memory reallocation. + * + * Given a pointer returned by zmalloc()/zcalloc()/zrealloc(), extend or + * contract the allocation to the given size -- retaining current type. + * The type given MUST be the original type. + * + * Given a NULL, allocate memory as zmalloc(). + * + * Returns: pointer to usable memory. + * + * NB: If memory cannot be allocated, aborts execution. */ void * -zrealloc (int type, void *ptr, size_t size) +zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = realloc (ptr, size); if (memory == NULL) - zerror ("realloc", type, size); - if (ptr == NULL) - alloc_inc (type); + { + UNLOCK ; + zerror ("realloc", mtype, size); /* NO RETURN ! */ + } + else + { + if (ptr == NULL) + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_realloc(mtype, ptr, memory, size, name) ; +#endif + UNLOCK ; + } ; return memory; -} +} ; -/* - * Free memory allocated by z*alloc or zstrdup. - * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the - * same type. - * Effects: The memory is freed and may no longer be referenced. +/*------------------------------------------------------------------------------ + * Memory free. + * + * Free memory allocated by zmalloc()/zcalloc()/zrealloc()/zstrdup(). + * The type given MUST be the original type. + * + * Does nothing if the given pointer is NULL. */ void -zfree (int type, void *ptr) +zfree (enum MTYPE mtype, void *ptr) { if (ptr != NULL) { - alloc_dec (type); + LOCK ; + + assert(mstat.mt[mtype].alloc > 0) ; + + mstat.mt[mtype].alloc--; +#ifdef MEMORY_TRACKER + mem_md_free(mtype, ptr) ; +#endif + free (ptr); - } -} -/* - * Duplicate a string, counting memory usage by type. - * Effects: The string is duplicated, and the return value must - * eventually be passed to zfree with the same type. The function will - * succeed or abort. + UNLOCK ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * String duplication. + * + * Memory is allocated as zmalloc() and must later be freed by zfree(). + * + * NB: If memory cannot be allocated, aborts execution. */ char * -zstrdup (int type, const char *str) +zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) { void *dup; + LOCK ; + dup = strdup (str); if (dup == NULL) - zerror ("strdup", type, strlen (str)); - alloc_inc (type); + { + UNLOCK ; + zerror ("strdup", mtype, strlen (str)); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, dup, strlen(str)+1, name) ; +#endif + UNLOCK ; + } ; + return dup; } - + +/*============================================================================== + * Memory allocation with built in logging + */ + #ifdef MEMORY_LOG -static struct + +static struct { const char *name; long alloc; @@ -163,10 +292,11 @@ static struct unsigned long t_realloc; unsigned long t_free; unsigned long c_strdup; -} mstat [MTYPE_MAX]; +} mlog_stat [MTYPE_MAX]; static void -mtype_log (char *func, void *memory, const char *file, int line, int type) +mtype_log (char *func, void *memory, const char *file, int line, + enum MTYPE type) { zlog_debug ("%s: %s %p %s %d", func, lookup (mstr, type), memory, file, line); } @@ -176,8 +306,10 @@ mtype_zmalloc (const char *file, int line, int type, size_t size) { void *memory; - mstat[type].c_malloc++; - mstat[type].t_malloc++; + LOCK + mlog_stat[type].c_malloc++; + mlog_stat[type].t_malloc++; + UNLOCK memory = zmalloc (type, size); mtype_log ("zmalloc", memory, file, line, type); @@ -186,12 +318,14 @@ mtype_zmalloc (const char *file, int line, int type, size_t size) } void * -mtype_zcalloc (const char *file, int line, int type, size_t size) +mtype_zcalloc (const char *file, int line, enum MTYPE type, size_t size) { void *memory; - mstat[type].c_calloc++; - mstat[type].t_calloc++; + LOCK + mlog_stat[type].c_calloc++; + mlog_stat[type].t_calloc++; + UNLOCK memory = zcalloc (type, size); mtype_log ("xcalloc", memory, file, line, type); @@ -200,12 +334,15 @@ mtype_zcalloc (const char *file, int line, int type, size_t size) } void * -mtype_zrealloc (const char *file, int line, int type, void *ptr, size_t size) +mtype_zrealloc (const char *file, int line, enum MTYPE type, void *ptr, + size_t size) { void *memory; /* Realloc need before allocated pointer. */ - mstat[type].t_realloc++; + LOCK + mlog_stat[type].t_realloc++; + UNLOCK memory = zrealloc (type, ptr, size); @@ -215,10 +352,12 @@ mtype_zrealloc (const char *file, int line, int type, void *ptr, size_t size) } /* Important function. */ -void -mtype_zfree (const char *file, int line, int type, void *ptr) +void +mtype_zfree (const char *file, int line, enum MTYPE type, void *ptr) { - mstat[type].t_free++; + LOCK + mlog_stat[type].t_free++; + UNLOCK mtype_log ("xfree", ptr, file, line, type); @@ -226,40 +365,26 @@ mtype_zfree (const char *file, int line, int type, void *ptr) } char * -mtype_zstrdup (const char *file, int line, int type, const char *str) +mtype_zstrdup (const char *file, int line, enum MTYPE type, const char *str) { char *memory; - mstat[type].c_strdup++; + LOCK + mlog_stat[type].c_strdup++; + UNLOCK memory = zstrdup (type, str); - + mtype_log ("xstrdup", memory, file, line, type); return memory; } -#else -static struct -{ - char *name; - long alloc; -} mstat [MTYPE_MAX]; -#endif /* MEMORY_LOG */ +#endif -/* Increment allocation counter. */ -static void -alloc_inc (int type) -{ - mstat[type].alloc++; -} +/*============================================================================== + * Showing memory allocation + */ -/* Decrement allocation counter. */ -static void -alloc_dec (int type) -{ - mstat[type].alloc--; -} - /* Looking up memory status from vty interface. */ #include "vector.h" #include "vty.h" @@ -268,47 +393,62 @@ alloc_dec (int type) static void log_memstats(int pri) { + struct mstat mst ; struct mlist *ml; + LOCK ; + mst = mstat ; + UNLOCK ; + for (ml = mlists; ml->list; ml++) { struct memory_list *m; zlog (NULL, pri, "Memory utilization in module %s:", ml->name); for (m = ml->list; m->index >= 0; m++) - if (m->index && mstat[m->index].alloc) - zlog (NULL, pri, " %-30s: %10ld", m->format, mstat[m->index].alloc); + { + unsigned long alloc = mst.mt[m->index].alloc ; + if (m->index && alloc) + zlog (NULL, pri, " %-30s: %10ld", m->format, alloc); + } } } void log_memstats_stderr (const char *prefix) { + struct mstat mst ; struct mlist *ml; struct memory_list *m; int i; int j = 0; + LOCK ; + mst = mstat ; + UNLOCK ; + for (ml = mlists; ml->list; ml++) { i = 0; - for (m = ml->list; m->index >= 0; m++) - if (m->index && mstat[m->index].alloc) - { - if (!i) + { + unsigned long alloc = mst.mt[m->index].alloc ; + if (m->index && alloc) + { + if (!i) + fprintf (stderr, + "%s: memstats: Current memory utilization in module %s:\n", + prefix, + ml->name); fprintf (stderr, - "%s: memstats: Current memory utilization in module %s:\n", - prefix, - ml->name); - fprintf (stderr, - "%s: memstats: %-30s: %10ld%s\n", - prefix, - m->format, - mstat[m->index].alloc, - mstat[m->index].alloc < 0 ? " (REPORT THIS BUG!)" : ""); - i = j = 1; - } + "%s: memstats: %-30s: %10ld%s\n", + prefix, + m->format, + alloc, + alloc < 0 ? " (REPORT THIS BUG!)" : ""); + i = j = 1; + } + } } if (j) @@ -323,33 +463,99 @@ log_memstats_stderr (const char *prefix) } static void -show_separator(struct vty *vty) +show_memory_type_vty (struct vty *vty, const char* name, + struct mem_tracker* mt, long int alloc, int sep) { - vty_out (vty, "-----------------------------\r\n"); -} + if (sep) + vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; + + vty_out (vty, "%-30s:", name) ; +#ifdef MEMORY_TRACKER + show_memory_tracker_detail(vty, mt, alloc) ; +#else + vty_out (vty, " %10ld", alloc) ; +#endif + vty_out (vty, "%s", VTY_NEWLINE); +} ; static int -show_memory_vty (struct vty *vty, struct memory_list *list) +show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, + int needsep) { - struct memory_list *m; - int needsep = 0; + int notempty = 0 ; - for (m = list; m->index >= 0; m++) - if (m->index == 0) - { - if (needsep) - { - show_separator (vty); - needsep = 0; - } - } - else if (mstat[m->index].alloc) - { - vty_out (vty, "%-30s: %10ld\r\n", m->format, mstat[m->index].alloc); - needsep = 1; - } - return needsep; -} + long int alloc ; + + struct mstat mst ; + struct mem_tracker mem_tot ; + struct mem_tracker mem_one ; + struct mem_tracker* mt ; + +#ifdef MEMORY_TRACKER + struct mem_type_tracker mem_tt ; +#endif + + LOCK ; + mst = mstat ; +#ifdef MEMORY_TRACKER + mem_tt = mem_type_tracker ; +#endif + UNLOCK ; + + mem_tracker_zeroise(&mem_tot) ; + mem_tracker_zeroise(&mem_one) ; + + if ((m == NULL) && (ml != NULL)) + m = (ml++)->list ; + + while (m != NULL) + { + if (m->index <= 0) + { + needsep = notempty ; + if (m->index < 0) + { + if (ml == NULL) + m = NULL ; + else + m = (ml++)->list ; + } + else + ++m ; + } + else + { + alloc = mst.mt[m->index].alloc ; +#ifdef MEMORY_TRACKER + mt = &(mem_tt.mt[m->index]) ; +#else + mt = &mem_one ; + mt->tracked_count = alloc ; +#endif + + mem_tot.malloc_count += mt->malloc_count ; + mem_tot.free_count += mt->free_count ; + mem_tot.realloc_count += mt->realloc_count ; + mem_tot.tracked_count += mt->tracked_count ; + mem_tot.tracked_max_count += mt->tracked_max_count ; + mem_tot.tracked_size += mt->tracked_size ; + mem_tot.tracked_max_size += mt->tracked_max_size ; + + if (alloc || mt->tracked_count) + { + show_memory_type_vty(vty, m->format, mt, alloc, needsep) ; + needsep = 0 ; + notempty = 1 ; + } ; + + ++m ; + } ; + } ; + + show_memory_type_vty(vty, "Total", &mem_tot, mem_tot.tracked_count, notempty); + + return 1 ; +} ; #ifdef HAVE_MALLINFO static int @@ -357,7 +563,7 @@ show_memory_mallinfo (struct vty *vty) { struct mallinfo minfo = mallinfo(); char buf[MTYPE_MEMSTR_LEN]; - + vty_out (vty, "System allocator statistics:%s", VTY_NEWLINE); vty_out (vty, " Total heap allocated: %s%s", mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.arena), @@ -388,131 +594,173 @@ show_memory_mallinfo (struct vty *vty) VTY_NEWLINE); vty_out (vty, "(see system documentation for 'mallinfo' for meaning)%s", VTY_NEWLINE); + return 1; } #endif /* HAVE_MALLINFO */ -DEFUN (show_memory_all, + +DEFUN_CALL (show_memory_summary, + show_memory_summary_cmd, + "show memory summary", + "Show running system information\n" + "Memory statistics\n" + "Summary memory statistics\n") +{ +#ifdef MEMORY_TRACKER + show_memory_tracker_summary(vty) ; +#else + long alloc = 0 ; + int mtype ; + +# ifdef HAVE_MALLINFO + show_memory_mallinfo (vty); +# endif /* HAVE_MALLINFO */ + + LOCK ; + for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype) + alloc += mstat.mt[mtype].alloc ; + UNLOCK + vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ; + +#endif /* MEMORY_TRACKER */ + + return CMD_SUCCESS; +} + +DEFUN_CALL (show_memory_all, show_memory_all_cmd, "show memory all", "Show running system information\n" "Memory statistics\n" "All memory statistics\n") { - struct mlist *ml; int needsep = 0; - + #ifdef HAVE_MALLINFO - needsep = show_memory_mallinfo (vty); + needsep |= show_memory_mallinfo (vty); #endif /* HAVE_MALLINFO */ - - for (ml = mlists; ml->list; ml++) - { - if (needsep) - show_separator (vty); - needsep = show_memory_vty (vty, ml->list); - } +#ifdef MEMORY_TRACKER + needsep |= show_memory_tracker_summary(vty) ; +#endif + + show_memory_vty (vty, NULL, mlists, needsep); return CMD_SUCCESS; } -ALIAS (show_memory_all, +ALIAS_CALL (show_memory_all, show_memory_cmd, "show memory", "Show running system information\n" "Memory statistics\n") -DEFUN (show_memory_lib, +DEFUN_CALL (show_memory_lib, show_memory_lib_cmd, "show memory lib", SHOW_STR "Memory statistics\n" "Library memory\n") { - show_memory_vty (vty, memory_list_lib); + show_memory_vty (vty, memory_list_lib, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_zebra, +DEFUN_CALL (show_memory_zebra, show_memory_zebra_cmd, "show memory zebra", SHOW_STR "Memory statistics\n" "Zebra memory\n") { - show_memory_vty (vty, memory_list_zebra); + show_memory_vty (vty, memory_list_zebra, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_rip, +DEFUN_CALL (show_memory_rip, show_memory_rip_cmd, "show memory rip", SHOW_STR "Memory statistics\n" "RIP memory\n") { - show_memory_vty (vty, memory_list_rip); + show_memory_vty (vty, memory_list_rip, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_ripng, +DEFUN_CALL (show_memory_ripng, show_memory_ripng_cmd, "show memory ripng", SHOW_STR "Memory statistics\n" "RIPng memory\n") { - show_memory_vty (vty, memory_list_ripng); + show_memory_vty (vty, memory_list_ripng, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_bgp, +DEFUN_CALL (show_memory_bgp, show_memory_bgp_cmd, "show memory bgp", SHOW_STR "Memory statistics\n" "BGP memory\n") { - show_memory_vty (vty, memory_list_bgp); + show_memory_vty (vty, memory_list_bgp, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_ospf, +DEFUN_CALL (show_memory_ospf, show_memory_ospf_cmd, "show memory ospf", SHOW_STR "Memory statistics\n" "OSPF memory\n") { - show_memory_vty (vty, memory_list_ospf); + show_memory_vty (vty, memory_list_ospf, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_ospf6, +DEFUN_CALL (show_memory_ospf6, show_memory_ospf6_cmd, "show memory ospf6", SHOW_STR "Memory statistics\n" "OSPF6 memory\n") { - show_memory_vty (vty, memory_list_ospf6); + show_memory_vty (vty, memory_list_ospf6, NULL, 0); return CMD_SUCCESS; } -DEFUN (show_memory_isis, +DEFUN_CALL (show_memory_isis, show_memory_isis_cmd, "show memory isis", SHOW_STR "Memory statistics\n" "ISIS memory\n") { - show_memory_vty (vty, memory_list_isis); + show_memory_vty (vty, memory_list_isis, NULL, 0); return CMD_SUCCESS; } +/* Second state initialisation if qpthreaded */ +void +memory_init_r (void) +{ + qpt_mutex_init(&memory_mutex, qpt_mutex_quagga); +} + +/* Finished with module */ +void +memory_finish (void) +{ + qpt_mutex_destroy(&memory_mutex, 0); +} + void memory_init (void) { + install_element (RESTRICTED_NODE, &show_memory_summary_cmd); install_element (RESTRICTED_NODE, &show_memory_cmd); install_element (RESTRICTED_NODE, &show_memory_all_cmd); install_element (RESTRICTED_NODE, &show_memory_lib_cmd); @@ -523,6 +771,7 @@ memory_init (void) install_element (RESTRICTED_NODE, &show_memory_ospf6_cmd); install_element (RESTRICTED_NODE, &show_memory_isis_cmd); + install_element (VIEW_NODE, &show_memory_summary_cmd); install_element (VIEW_NODE, &show_memory_cmd); install_element (VIEW_NODE, &show_memory_all_cmd); install_element (VIEW_NODE, &show_memory_lib_cmd); @@ -533,6 +782,7 @@ memory_init (void) install_element (VIEW_NODE, &show_memory_ospf6_cmd); install_element (VIEW_NODE, &show_memory_isis_cmd); + install_element (ENABLE_NODE, &show_memory_summary_cmd); install_element (ENABLE_NODE, &show_memory_cmd); install_element (ENABLE_NODE, &show_memory_all_cmd); install_element (ENABLE_NODE, &show_memory_lib_cmd); @@ -544,7 +794,7 @@ memory_init (void) install_element (ENABLE_NODE, &show_memory_ospf6_cmd); install_element (ENABLE_NODE, &show_memory_isis_cmd); } - + /* Stats querying from users */ /* Return a pointer to a human friendly string describing * the byte count passed in. E.g: @@ -557,13 +807,13 @@ const char * mtype_memstr (char *buf, size_t len, unsigned long bytes) { unsigned int t, g, m, k; - + /* easy cases */ if (!bytes) return "0 bytes"; if (bytes == 1) return "1 byte"; - + if (sizeof (unsigned long) >= 8) /* Hacked to make it not warn on ILP32 machines * Shift will always be 40 at runtime. See below too */ @@ -573,11 +823,11 @@ mtype_memstr (char *buf, size_t len, unsigned long bytes) g = bytes >> 30; m = bytes >> 20; k = bytes >> 10; - + if (t > 10) { /* The shift will always be 39 at runtime. - * Just hacked to make it not warn on 'smaller' machines. + * Just hacked to make it not warn on 'smaller' machines. * Static compiler analysis should mean no extra code */ if (bytes & (1UL << (sizeof (unsigned long) >= 8 ? 39 : 0))) @@ -604,12 +854,19 @@ mtype_memstr (char *buf, size_t len, unsigned long bytes) } else snprintf (buf, len, "%ld bytes", bytes); - + return buf; } unsigned long -mtype_stats_alloc (int type) +mtype_stats_alloc (enum MTYPE type) { - return mstat[type].alloc; + unsigned long result; + LOCK + result = mstat.mt[type].alloc; + UNLOCK + return result; } + +#undef UNLOCK +#undef LOCK diff --git a/lib/memory.h b/lib/memory.h index a7eddce4..6c95d73a 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -21,6 +21,8 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _ZEBRA_MEMORY_H #define _ZEBRA_MEMORY_H +#include <stddef.h> + /* For pretty printing of memory allocate information. */ struct memory_list { @@ -32,11 +34,11 @@ struct mlist { struct memory_list *list; const char *name; }; - -#include "lib/memtypes.h" extern struct mlist mlists[]; +#include "lib/memtypes.h" + /* #define MEMORY_LOG */ #ifdef MEMORY_LOG #define XMALLOC(mtype, size) \ @@ -53,40 +55,65 @@ extern struct mlist mlists[]; #define XSTRDUP(mtype, str) \ mtype_zstrdup (__FILE__, __LINE__, (mtype), (str)) #else -#define XMALLOC(mtype, size) zmalloc ((mtype), (size)) -#define XCALLOC(mtype, size) zcalloc ((mtype), (size)) -#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size)) + +#ifdef QDEBUG +#define MEMORY_TRACKER 1 +#endif + +#ifdef MEMORY_TRACKER +#define MEMORY_TRACKER_NAME , const char* name +#define MEMORY_TRACKER_FUNC , __func__ +#else +#define MEMORY_TRACKER_NAME +#define MEMORY_TRACKER_FUNC +#endif + +#define XMALLOC(mtype, size) zmalloc ((mtype), (size) \ + MEMORY_TRACKER_FUNC) +#define XCALLOC(mtype, size) zcalloc ((mtype), (size) \ + MEMORY_TRACKER_FUNC) +#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \ + MEMORY_TRACKER_FUNC) #define XFREE(mtype, ptr) do { \ zfree ((mtype), (ptr)); \ ptr = NULL; } \ while (0) -#define XSTRDUP(mtype, str) zstrdup ((mtype), (str)) +#define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \ + MEMORY_TRACKER_FUNC) + #endif /* MEMORY_LOG */ +#define SIZE(t,n) (sizeof(t) * (n)) + /* Prototypes of memory function. */ -extern void *zmalloc (int type, size_t size); -extern void *zcalloc (int type, size_t size); -extern void *zrealloc (int type, void *ptr, size_t size); -extern void zfree (int type, void *ptr); -extern char *zstrdup (int type, const char *str); +extern void *zmalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME); +extern void *zcalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME); +extern void *zrealloc (enum MTYPE type, void *ptr, size_t size + MEMORY_TRACKER_NAME); +extern void zfree (enum MTYPE type, void *ptr); +extern char *zstrdup (enum MTYPE type, const char *str MEMORY_TRACKER_NAME); -extern void *mtype_zmalloc (const char *file, int line, int type, size_t size); +extern void *mtype_zmalloc (const char *file, int line, enum MTYPE type, + size_t size); -extern void *mtype_zcalloc (const char *file, int line, int type, size_t size); +extern void *mtype_zcalloc (const char *file, int line, enum MTYPE type, + size_t size); -extern void *mtype_zrealloc (const char *file, int line, int type, void *ptr, - size_t size); +extern void *mtype_zrealloc (const char *file, int line, enum MTYPE type, + void *ptr, size_t size); -extern void mtype_zfree (const char *file, int line, int type, - void *ptr); +extern void mtype_zfree (const char *file, int line, enum MTYPE type, + void *ptr); -extern char *mtype_zstrdup (const char *file, int line, int type, - const char *str); +extern char *mtype_zstrdup (const char *file, int line, enum MTYPE type, + const char *str); extern void memory_init (void); +extern void memory_init_r (void); +extern void memory_finish (void); extern void log_memstats_stderr (const char *); /* return number of allocations outstanding for the type */ -extern unsigned long mtype_stats_alloc (int); +extern unsigned long mtype_stats_alloc (enum MTYPE); /* Human friendly string for given byte count */ #define MTYPE_MEMSTR_LEN 20 diff --git a/lib/memtypes.awk b/lib/memtypes.awk index 5429f6e8..a8004977 100644 --- a/lib/memtypes.awk +++ b/lib/memtypes.awk @@ -54,7 +54,7 @@ BEGIN { } END { - printf("enum\n{\n MTYPE_TMP = 1,\n"); + printf("enum MTYPE\n{\n MTYPE_TMP = 1,\n"); for (i = 0; i < tcount; i++) { if (mtype[i] != "" && mtype[i] != "MTYPE_TMP") printf (" %s,\n", mtype[i]); diff --git a/lib/memtypes.c b/lib/memtypes.c index 59020671..98b53209 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -1,6 +1,6 @@ /* * Memory type definitions. This file is parsed by memtypes.awk to extract - * MTYPE_ and memory_list_.. information in order to autogenerate + * MTYPE_ and memory_list_.. information in order to autogenerate * memtypes.h. * * The script is sensitive to the format (though not whitespace), see @@ -15,18 +15,47 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, - { MTYPE_STRVEC, "String vector" }, - { MTYPE_VECTOR, "Vector" }, - { MTYPE_VECTOR_INDEX, "Vector index" }, + { MTYPE_STRVEC, "String vector" }, + { MTYPE_VECTOR, "Vector structure" }, + { MTYPE_VECTOR_BODY, "Vector body" }, + { MTYPE_SYMBOL_TABLE, "Symbol Table structure" }, + { MTYPE_SYMBOL_BASES, "Symbol Table chain bases" }, + { MTYPE_SYMBOL, "Symbol" }, + { MTYPE_SYMBOL_REF, "Symbol Reference" }, + { MTYPE_HEAP, "Heap structure" }, { MTYPE_LINK_LIST, "Link List" }, { MTYPE_LINK_NODE, "Link Node" }, { MTYPE_THREAD, "Thread" }, { MTYPE_THREAD_MASTER, "Thread master" }, { MTYPE_THREAD_STATS, "Thread stats" }, - { MTYPE_THREAD_FUNCNAME, "Thread function name" }, + { MTYPE_THREAD_FUNCNAME, "Thread function name" }, + { MTYPE_QPT_THREAD_ATTR, "qpt thread attributes" }, + { MTYPE_QPT_MUTEX, "qpt mutex" }, + { MTYPE_QPT_COND, "qpt condition variable" }, + { MTYPE_MQUEUE_QUEUE, "Mqueue queue structure" }, + { MTYPE_MQUEUE_BLOCK, "Mqueue message block" }, + { MTYPE_MQUEUE_BLOCK_ARGV, "Mqueue message block argv" }, + { MTYPE_MQUEUE_THREAD_SIGNAL, "Mqueue thread signal" }, + { MTYPE_QPS_SELECTION, "qpselect selection" }, + { MTYPE_QPS_FILE, "qpselect file" }, + { MTYPE_QTIMER_PILE, "qtimer pile structure" }, + { MTYPE_QTIMER, "qtimer timer" }, + { MTYPE_QPN_NEXUS, "qtn nexus" }, + { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, + { MTYPE_CMD_PARSED, "Parsed command" }, + { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, - { MTYPE_VTY_HIST, "VTY history" }, + { MTYPE_VTY_HIST, "VTY history" }, + { MTYPE_VTY_NAME, "VTY name" }, + { MTYPE_KEY_STREAM, "Keystroke Stream" }, + { MTYPE_VIO_FIFO, "VTY IO FIFO" }, + { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, + { MTYPE_VIO_LC, "VTY IO Line Control" }, + { MTYPE_QSTRING, "qstring structure" }, + { MTYPE_QSTRING_BODY, "qstring body" }, + { MTYPE_QIOVEC, "qiovec structure" }, + { MTYPE_QIOVEC_VEC, "qiovec iovec vector" }, { MTYPE_IF, "Interface" }, { MTYPE_CONNECTED, "Connected" }, { MTYPE_CONNECTED_LABEL, "Connected interface label" }, @@ -75,7 +104,7 @@ struct memory_list memory_list_lib[] = { -1, NULL }, }; -struct memory_list memory_list_zebra[] = +struct memory_list memory_list_zebra[] = { { MTYPE_RTADV_PREFIX, "Router Advertisement Prefix" }, { MTYPE_VRF, "VRF" }, @@ -97,6 +126,12 @@ struct memory_list memory_list_bgp[] = { MTYPE_PEER_GROUP, "Peer group" }, { MTYPE_PEER_DESC, "Peer description" }, { MTYPE_PEER_PASSWORD, "Peer password string" }, + { MTYPE_BGP_PEER_ID_TABLE, "Peer ID table" }, + { MTYPE_BGP_SESSION, "BGP session" }, + { MTYPE_BGP_CONNECTION, "BGP connection" }, + { MTYPE_BGP_NOTIFY, "BGP notification" }, + { MTYPE_BGP_ROUTE_REFRESH, "BGP route refresh" }, + { MTYPE_BGP_ORF_ENTRY, "BGP ORF entry" }, { MTYPE_ATTR, "BGP attribute" }, { MTYPE_ATTR_EXTRA, "BGP extra attributes" }, { MTYPE_AS_PATH, "BGP aspath" }, @@ -108,7 +143,7 @@ struct memory_list memory_list_bgp[] = { MTYPE_BGP_NODE, "BGP node" }, { MTYPE_BGP_ROUTE, "BGP route" }, { MTYPE_BGP_ROUTE_EXTRA, "BGP ancillary route info" }, - { MTYPE_BGP_CONN, "BGP connected" }, + { MTYPE_BGP_CONN, "BGP connected" }, { MTYPE_BGP_STATIC, "BGP static" }, { MTYPE_BGP_ADVERTISE_ATTR, "BGP adv attr" }, { MTYPE_BGP_ADVERTISE, "BGP adv" }, @@ -151,6 +186,7 @@ struct memory_list memory_list_bgp[] = { MTYPE_BGP_DAMP_ARRAY, "BGP Dampening array" }, { MTYPE_BGP_REGEXP, "BGP regexp" }, { MTYPE_BGP_AGGREGATE, "BGP aggregate" }, + { MTYPE_BGP_OPEN_STATE, "BGP Open State" }, { -1, NULL } }; diff --git a/lib/miyagi.h b/lib/miyagi.h new file mode 100644 index 00000000..569d2da9 --- /dev/null +++ b/lib/miyagi.h @@ -0,0 +1,40 @@ +/* Kludge to discard "const" from pointer + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + *. + * This file is part of GNU Zebra. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_MIYAGI_H +#define _ZEBRA_MIYAGI_H + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Ghastly kludge to discard "const" from pointer + */ +Inline void* +miyagi(const void* ptr) +{ + union { + const void* waxon ; + void* waxoff ; + } shuffle ; + + shuffle.waxon = ptr ; + + return shuffle.waxoff ; +} ; + +#endif /* _ZEBRA_MIYAGI_H */ diff --git a/lib/mqueue.c b/lib/mqueue.c new file mode 100644 index 00000000..8fa9fbd5 --- /dev/null +++ b/lib/mqueue.c @@ -0,0 +1,1319 @@ +/* Message Queue data structure -- functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <string.h> + +#include "memory.h" +#include "mqueue.h" +#include "zassert.h" + +/*============================================================================== + * These message queues are designed for inter-qpthread communication. + * + * A message queue carries messages from one or more qpthreads to one or more + * other qpthreads. + * + * If !qpthreads_enabled, then a message queue holds messages for the program + * to consume later. There are never any waiters. Timeouts are ignored. + * + * A message queue has one ordinary priority queue and one high priority + * queue. + * + * There are four types of queue, depending on how qpthreads wait and how they + * are woken up: + * + * mqt_cond_unicast -- wait on condition variable, one waiter kicked + * mqt_cond_broadcast -- wait on condition variable, all waiters kicked + * mqt_signal_unicast -- wait for signal, one waiter kicked + * mqt_signal_broadcast -- wait for signal, all waiters kicked + * + * For condition variables there is a timeout mechanism so that waiters + * are woken up at least every now and then. The message queue maintains + * a timeout time and a timeout interval. The timeout time is a qtime_mono_t + * time -- so is monotonic. + * + * When waiting, an explicit timeout may be given, otherwise the stored timeout + * will be used: + * + * wait until explicit/stored timeout + * if times out and there is a stored interval: + * new stored timeout = stored timeout + stored interval + * if new stored timeout < time now + * new stored timeout = time now + stored interval + * + * Left to its own devices, this will produce a regular timeout every interval, + * assuming that the queue is waited on within the interval. Otherwise the + * "clock" will slip. + * + * There is a default timeout period. The period may be set "infinite". + * + * For waiters kicked by signal, the wait does not occur within the message + * queue code, but the need for a signal is recorded in the message queue. + * + *------------------------------------------------------------------------------ + * Message Blocks and Arguments + * + * Messages take the form of a small block of information which contain: + * + * * action -- void action(mqueue_block) message dispatch + * * arg0 -- void* argument + * * struct args -- embedded argument structure + * * argv -- optional array of union of: *void/uintptr_t/intptr_t + * + * There are set/get functions for action/arguments -- users should not poke + * around inside the structure. + * + * To send a message, first initialise/allocate a message block + * (see mqb_init_new), then fill in the arguments and enqueue it. + * + * NB: arg0 is expected to be used as the "context" for the message -- to + * point to some data common to both ends of the conversation. + * + * For specific revoke, arg0 is assumed to identify the messages to be + * revoked. + * + * NB: the struct args is expected to be a modest sized structure, carrying + * the key elements of the message. + * + * Some other structure must be overlaid on this, in the same way by sender + * and receiver of the message. So: + * + * mqueue_block mqb = mqb_init_new(NULL, arg0, action_func) ; + * + * struct my_message* args = mqb_get_args(mqb) ; + * + * allocates mqueue block, filling in arg0 and the action func. Then + * args can be used to fill in a "struct my_message" form of args. + * + * NB: the sizeof(struct my_message) MUST BE <= mqb_args_size_max !!! + * + * The argv is an optional, flexible list/array of optional array of + * union of: *void/uintptr_t/intptr_t -- see mqb_arg_t et al. + * + * May set any number of arguments in argv. + * + * A count of arguments is maintained, and is the highest index set + 1. That + * count can be fetched. (So there is no need to maintain it separately.) + * + * May get any argument by its index -- but it is a fatal error to attempt to + * access a non-existent argument (one beyond the known count). + * + * There is support for pushing values onto the argv "list" and for iterating + * along the "list". May also push and pop entire arrays of items. + * + *============================================================================== + * Local Queues + * + * A local queue may be used within a thread to requeue messages for later + * processing. + * + * Local queues are simple FIFO queues. + */ + +/*============================================================================== + * Message Block allocation statics + * + * Once a message block is allocated it is not deallocated, but kept ready + * for future use. + * + * Keeps a count of free message blocks. (Could at some later date reduce the + * number of free message blocks if it is known that some burst of messages has + * now passed.) + */ + +static pthread_mutex_t mqb_mutex ; /* for allocation of mqueue blocks */ + +static mqueue_block mqb_free_list = NULL ; +static unsigned mqb_free_count = 0 ; + +/*============================================================================== + * Initialise and shut down Message Queue and Message Block handling + */ + +/*------------------------------------------------------------------------------ + * Initialise Message Queue handling. + * + * Must be called before any qpt_threads are started. + * + * Freezes qpthreads_enabled. + */ +extern void +mqueue_initialise(void) +{ + if (qpthreads_enabled_freeze) + qpt_mutex_init_new(&mqb_mutex, qpt_mutex_quagga) ; +} ; + +/*------------------------------------------------------------------------------ + * Shut down Message Queue handling. + * + * Release all resources used. + * + * NB: all pthreads must have stopped -- mutex must be free and no further + * uses may be made. + */ +extern void +mqueue_finish(void) +{ + mqueue_block mqb ; + + while ((mqb = mqb_free_list) != NULL) + { + assert(mqb_free_count != 0) ; + mqb_free_count-- ; + mqb_free_list = mqb->next ; + XFREE(MTYPE_MQUEUE_BLOCK, mqb) ; + } ; + + assert(mqb_free_count == 0) ; + + qpt_mutex_destroy_keep(&mqb_mutex) ; +} ; + +/*============================================================================== + * Initialisation etc. for Message Queue + * + */ + +/*------------------------------------------------------------------------------ + * Initialise new Message Queue, if required (mq == NULL) allocating it. + * + * For mqt_cond_xxx type queues, sets the default timeout interval and the + * initial timeout time to now + that interval. + * + * NB: once any message queue has been initialised, it is TOO LATE to enable + * qpthreads. + */ +extern mqueue_queue +mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) +{ + if (mq == NULL) + mq = XCALLOC(MTYPE_MQUEUE_QUEUE, sizeof(struct mqueue_queue)) ; + else + memset(mq, 0, sizeof(struct mqueue_queue)) ; + + if (qpt_freeze_qpthreads_enabled()) + qpt_mutex_init_new(&mq->mutex, qpt_mutex_quagga) ; + + /* head, tail and tail_priority set NULL already */ + /* count set zero already */ + /* waiters set zero already */ + + mq->type = type ; + switch (type) + { + case mqt_cond_unicast: + case mqt_cond_broadcast: + qpt_cond_init_new(&mq->kick.cond.wait_here, qpt_cond_quagga) ; + + if (MQUEUE_DEFAULT_INTERVAL != 0) + { + mq->kick.cond.interval = MQUEUE_DEFAULT_INTERVAL ; + mq->kick.cond.timeout = qt_get_monotonic() + + MQUEUE_DEFAULT_INTERVAL ; + } ; + break; + + case mqt_signal_unicast: + case mqt_signal_broadcast: + /* head/tail pointers set NULL already */ + break; + + default: + zabort("Invalid mqueue queue type") ; + } ; + + return mq ; +} ; + +/*------------------------------------------------------------------------------ + * Empty message queue -- by revoking everything. + * + * Leaves queue ready for continued use with all existing settings. + * + * If there were any waiters, they are still waiting. + */ +extern void +mqueue_empty(mqueue_queue mq) +{ + mqueue_revoke(mq, NULL) ; + + assert((mq->head == NULL) && (mq->count == 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Reset message queue -- empty it out by revoking everything. + * + * Frees the structure if required, and returns NULL. + * Otherwise zeroises the structure, and returns address of same. + * + * NB: there MUST NOT be ANY waiters ! + */ +extern mqueue_queue +mqueue_reset(mqueue_queue mq, int free_structure) +{ + mqueue_empty(mq) ; + + passert(mq->waiters == 0) ; + + qpt_mutex_destroy_keep(&mq->mutex) ; + + switch (mq->type) + { + case mqt_cond_unicast: + case mqt_cond_broadcast: + qpt_cond_destroy_keep(&mq->kick.cond.wait_here) ; + break; + + case mqt_signal_unicast: + case mqt_signal_broadcast: + passert(mq->kick.signal.head == NULL) ; + break; + + default: + zabort("Invalid mqueue queue type") ; + } ; + + if (free_structure) + XFREE(MTYPE_MQUEUE_QUEUE, mq) ; /* sets mq == NULL */ + else + memset(mq, 0, sizeof(struct mqueue_queue)) ; + + return mq ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise new Local Message Queue, if required (lmq == NULL) allocating it. + * + * Returns address of Local Message Queue + */ +extern mqueue_local_queue +mqueue_local_init_new(mqueue_local_queue lmq) +{ + if (lmq == NULL) + lmq = XCALLOC(MTYPE_MQUEUE_QUEUE, sizeof(struct mqueue_local_queue)) ; + else + memset(lmq, 0, sizeof(struct mqueue_local_queue)) ; + + /* Zeroising the structure is enough to initialise: + * + * * head -- NULL + * * tail -- NULL + */ + + return lmq ; +} ; + +/*------------------------------------------------------------------------------ + * Reset Local Message Queue, and if required free it. + * + * Dequeues entries and dispatches them "mqb_destroy", to empty the queue. + * + * See: mqueue_local_reset_keep(lmq) + * mqueue_local_reset_free(lmq) + * + * Returns address of Local Message Queue + */ +extern mqueue_local_queue +mqueue_local_reset(mqueue_local_queue lmq, int free_structure) +{ + mqueue_block mqb ; + + while ((mqb = lmq->head) != NULL) + { + lmq->head = mqb->next ; + mqb_dispatch_destroy(mqb) ; + } ; + + if (free_structure) + XFREE(MTYPE_MQUEUE_QUEUE, lmq) ; /* sets lmq = NULL */ + else + memset(lmq, 0, sizeof(struct mqueue_local_queue)) ; + + return lmq ; +} ; + +/*------------------------------------------------------------------------------ + * Set new timeout interval (or unset by setting <= 0) + * + * Sets the next timeout to be the time now + new interval (or never). + * + * This is a waste of time if !qpthreads_enabled, but does no harm. The + * timeout is ignored. + */ +extern void +mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) +{ + qpt_mutex_lock(&mq->mutex) ; + + dassert( (mq->type == mqt_cond_unicast) || + (mq->type == mqt_cond_broadcast) ) ; + + mq->kick.cond.interval = interval ; + mq->kick.cond.timeout = (interval > 0) ? qt_add_monotonic(interval) + : 0 ; + qpt_mutex_unlock(&mq->mutex) ; +} ; + +/*============================================================================== + * Message Block memory management. + * + * Allocates message block structures when required. + * + * Places those structures on the free list when they are freed. + * + * Keeps a count of free structures. (Could at some later date reduce the + * number of free structures if it is known that some burst of messages has + * now passed.) + * + * mqueue_initialise MUST be called before the first message block is allocated. + */ + +inline static size_t mqb_argv_size(mqb_index_t alloc) +{ + return alloc * sizeof(mqb_arg_t) ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise message block (allocate if required) and set action & arg0. + * + * Zeroises the struct args. + * + * Returns address of message block. + */ +extern mqueue_block +mqb_init_new(mqueue_block mqb, mqueue_action action, void* arg0) +{ + if (mqb == NULL) + { + qpt_mutex_lock(&mqb_mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + + mqb = mqb_free_list ; + if (mqb == NULL) + { + dassert(mqb_free_count == 0) ; + mqb = XMALLOC(MTYPE_MQUEUE_BLOCK, sizeof(struct mqueue_block)) ; + } + else + { + dassert(mqb_free_count >= 0) ; + mqb_free_list = mqb->next ; + --mqb_free_count ; + } ; + + qpt_mutex_unlock(&mqb_mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + } ; + + memset(mqb, 0, sizeof(struct mqueue_block)) ; + + mqb->action = action ; + mqb->arg0 = arg0 ; + + /* Zeroising the mqb sets: + * + * next -- NULL + * + * args -- zeroised + * + * argv -- NULL -- empty list/array + * + * argv_count -- 0 -- empty + * argv_alloc -- 0 -- nothing allocated + * argv_next -- 0 -- iterator reset + */ + + return mqb ; +} ; + +/*------------------------------------------------------------------------------ + * Re-initialise message block (or allocate if required) and set action & arg0. + * + * NB: preserves any existing argv, but empties it. + * + * NB: it is the caller's responsibility to free the value of any argument that + * requires it. + */ +extern mqueue_block +mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) +{ + mqb_index_t argv_alloc ; + mqb_arg_t* argv ; + + /* Exactly mqb_init_new if mqb is NULL */ + if (mqb == NULL) + return mqb_init_new(NULL, action, arg0) ; + + /* Otherwise, need to put argv to one side first */ + argv = mqb->argv ; + argv_alloc = mqb->argv_alloc ; + + mqb_init_new(mqb, action, arg0) ; + + /* Now zeroize the argv, and restore it */ + memset(argv, 0, mqb_argv_size(argv_alloc)) ; + + mqb->argv = argv ; + mqb->argv_alloc = argv_alloc ; + + return mqb ; +} ; + +/*------------------------------------------------------------------------------ + * Free message block when done with it. + * + * Frees any argv argument vector. + * + * NB: it is the caller's responsibility to free the value of any argument that + * requires it. + */ +extern void +mqb_free(mqueue_block mqb) +{ + if (mqb->argv != NULL) + XFREE(MTYPE_MQUEUE_BLOCK_ARGV, mqb->argv) ; + + qpt_mutex_lock(&mqb_mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + + mqb->next = mqb_free_list ; + mqb_free_list = mqb ; + ++mqb_free_count ; + + qpt_mutex_unlock(&mqb_mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ +} ; + +/*============================================================================== + * Enqueue and dequeue messages. + */ + +static void mqueue_kick_signal(mqueue_queue mq, unsigned n) ; +static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ; + +/*------------------------------------------------------------------------------ + * Enqueue message. + * + * If priority, will enqueue after any previously enqueued priority + * messages. (See enum: mqb_priority and mqb_ordinary.) + * + * If there are any waiters, then we kick one or all of them. + * + * Note that we decrement or zero the waiters count here -- because if the + * waiter did it, they might not run before something else is enqueued. + * Similarly, if the kick uses a signal, the signal block is dequeued here. + * + * The waiter count is only incremented when a dequeue is attempted and the + * queue is empty, then: + * + * for a broadcast type message queue, the first message that arrives will + * kick all the waiters into action. + * + * for a signal type message queue, each message that arrives will kick one + * waiter. + * + * If mq is NULL, the message is not queued but is immediately destroyed. + * + * NB: this works perfectly well if !qpthreads enabled. Of course, there can + * never be any waiters... so no kicking is ever done. + */ +extern void +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) +{ + if (mq == NULL) + return mqb_dispatch_destroy(mqb) ; + + qpt_mutex_lock(&mq->mutex) ; + + if (mq->head == NULL) + { + assert(mq->count == 0) ; + mqb->next = NULL ; + mq->head = mqb ; + mq->tail_priority = priority ? mqb : NULL ; + mq->tail = mqb ; + } + else + { + assert(mq->count > 0) ; + if (priority) + { + mqueue_block after = mq->tail_priority ; + if (after == NULL) + { + mqb->next = mq->head ; + mq->head = mqb ; + /* mq non-empty, enchain at head, therefore tail unaffected */ + } + else + { + mqb->next = after->next ; + after->next = mqb ; + /* if only have priority messages then fix tail */ + if (mq->tail == after) + mq->tail = mqb; + } + mq->tail_priority = mqb ; + } + else + { + dassert(mq->tail != NULL) ; + mqb->next = NULL ; + mq->tail->next = mqb ; + mq->tail = mqb ; + } ; + } ; + + ++mq->count ; + + if (mq->waiters != 0) + { + dassert(qpthreads_enabled) ; /* waiters == 0 if !qpthreads_enabled */ + + switch (mq->type) + { + case mqt_cond_unicast: + qpt_cond_signal(&mq->kick.cond.wait_here) ; + --mq->waiters ; + break ; + + case mqt_cond_broadcast: + qpt_cond_broadcast(&mq->kick.cond.wait_here) ; + mq->waiters = 0 ; + break ; + + case mqt_signal_unicast: + mqueue_kick_signal(mq, 1) ; /* pick off first and kick it (MUST be */ + /* one) and decrement the waiters count */ + break ; + + case mqt_signal_broadcast: + mqueue_kick_signal(mq, mq->waiters) ; + dassert(mq->kick.signal.head == NULL) ; + break; + + default: + zabort("Invalid mqueue queue type") ; + } ; + } ; + + qpt_mutex_unlock(&mq->mutex) ; +} ; + +/*------------------------------------------------------------------------------ + * Dequeue message. + * + * If the queue is empty and wait != 0 (and qpthreads_enabled), will wait for a + * message. In which case for: + * + * * mqt_cond_xxxx type message queues, will wait on the condition variable, + * and may timeout. + * + * If the argument is NULL, uses the already set up timeout, if there is + * one. + * + * If the argument is not NULL, it is a pointer to a qtime_mono_t time, + * to be used as the new timeout time. + * + * * mqt_signal_xxxx type message queues, will register the given signal + * (mtsig argument MUST be provided), and return immediately. + * + * NB: if !qpthreads_enabled, will not wait on the queue. No how. + * + * Note this means that waiters == 0 all the time if !qpthreads_enabled ! + * + * NB: the argument is ignored if !wait or !qpthreads_enabled, so may be NULL. + * + * Returns a message block if one is available. (And not otherwise.) + * + * NB: if mq is NULL, returns NULL -- nothing available + */ +extern mqueue_block +mqueue_dequeue(mqueue_queue mq, int wait, void* arg) +{ + mqueue_block mqb ; + mqueue_thread_signal last ; + + mqueue_thread_signal mtsig ; + qtime_mono_t timeout_time ; + + if (mq == NULL) + return NULL ; + + qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + + while (1) + { + mqb = mq->head ; + if (mqb != NULL) + break ; /* Easy if queue not empty */ + + assert(mq->count == 0) ; + + if (!wait || !qpthreads_enabled) + goto done ; /* Easy if not waiting ! mqb == NULL */ + /* Short circuit if !qpthreads_enabled */ + + ++mq->waiters ; /* Another waiter */ + + switch (mq->type) + { + case mqt_cond_unicast: /* Now wait here */ + case mqt_cond_broadcast: + if ((arg == NULL) && (mq->kick.cond.interval <= 0)) + qpt_cond_wait(&mq->kick.cond.wait_here, &mq->mutex) ; + else + { + timeout_time = (arg != NULL) ? *(qtime_mono_t*)arg + : mq->kick.cond.timeout ; + + if (qpt_cond_timedwait(&mq->kick.cond.wait_here, &mq->mutex, + timeout_time) == 0) + { + /* Timed out -- update timeout time, if required */ + if (mq->kick.cond.interval > 0) + { + qtime_mono_t now = qt_get_monotonic() ; + timeout_time = mq->kick.cond.timeout + + mq->kick.cond.interval ; + if (timeout_time < now) + timeout_time = now + mq->kick.cond.interval ; + + mq->kick.cond.timeout = timeout_time ; + } ; + + goto done ; /* immediate return. mqb == NULL */ + } ; + } ; + break ; + + case mqt_signal_unicast: /* Register desire for signal */ + case mqt_signal_broadcast: + mtsig = arg ; + dassert(mtsig != NULL) ; + + if (mq->kick.signal.head == NULL) + { + mq->kick.signal.head = mtsig ; + mtsig->prev = (void*)mq ; + } + else + { + last = mq->kick.signal.tail ; + last->next = mtsig ; + mtsig->prev = last ; + } + mtsig->next = NULL ; + mq->kick.signal.tail = mtsig ; + + goto done ; /* BUT do not wait ! mqb == NULL */ + + default: + zabort("Invalid mqueue queue type") ; + } ; + } ; + + /* Have something to pull off the queue */ + + assert(mq->count > 0) ; + --mq->count ; + + mq->head = mqb->next ; + + /* fix tails if at tail */ + if (mqb == mq->tail) + mq->tail = NULL ; + if (mqb == mq->tail_priority) + mq->tail_priority = NULL ; + +done: + qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + + return mqb ; +} ; + +/*------------------------------------------------------------------------------ + * Revoke message(s) + * + * Revokes all messages, or only messages whose arg0 matches the given value. + * (If the given value is NULL revokes everything.) + * + * Revokes by calling mqb_dispatch_destroy(). + * + * During a revoke() operation more items may be enqueued, but no other mqueue + * operations may be performed. Enqueued items may promptly be revoked, except + * for priority items if the revoke operation has already moved past the last + * priority item. + * + * If mq is NULL, does nothing. + */ +extern void +mqueue_revoke(mqueue_queue mq, void* arg0) +{ + mqueue_block mqb ; + mqueue_block prev ; + + if (mq == NULL) + return ; + + qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + + prev = NULL ; + while (1) + { + if (prev == NULL) + mqb = mq->head ; + else + mqb = prev->next ; + + if (mqb == NULL) + break ; + + if ((arg0 == NULL) || (arg0 == mqb->arg0)) + { + assert(mq->count > 0) ; + + if (prev == NULL) + mq->head = mqb->next ; + else + prev->next = mqb->next ; + + if (mqb == mq->tail) + mq->tail = prev ; + + if (mqb == mq->tail_priority) + mq->tail_priority = prev ; + + --mq->count ; + + qpt_mutex_unlock(&mq->mutex) ; + mqb_dispatch_destroy(mqb) ; + qpt_mutex_lock(&mq->mutex) ; + } + else + prev = mqb ; + } ; + + qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ +} ; + +/*------------------------------------------------------------------------------ + * No longer waiting for a signal -- does nothing if !qpthreads_enabled. + * + * Returns true <=> signal has been kicked + * + * (Signal will never be kicked if !qpthreads_enabled.) + */ +extern int +mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) +{ + int kicked ; + + if (!qpthreads_enabled) + return 0 ; + + qpt_mutex_lock(&mq->mutex) ; + + dassert( (mq->type == mqt_signal_unicast) || + (mq->type == mqt_signal_broadcast) ) ; + dassert(mtsig != NULL) ; + + /* When the thread is signalled, the prev entry is set NULL and the */ + /* waiters count is decremented. */ + /* */ + /* So, only need to do something here if the prev is not NULL (ie the */ + /* mqueue_thread_signal is still on the list. */ + + kicked = (mtsig->prev == NULL) ; + + if (!kicked) + mqueue_dequeue_signal(mq, mtsig) ; + + qpt_mutex_unlock(&mq->mutex) ; + + return kicked ; +} ; + +/*------------------------------------------------------------------------------ + * Enqueue message on local queue -- at tail + */ +extern void +mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) +{ + if (lmq->head == NULL) + lmq->head = mqb ; + else + lmq->tail->next = mqb ; + lmq->tail = mqb ; + mqb->next = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Enqueue message on local queue -- at head + */ +extern void +mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) +{ + if (lmq->head == NULL) + lmq->tail = mqb ; + + mqb->next = lmq->head ; + lmq->head = mqb ; +} ; + +/*------------------------------------------------------------------------------ + * Dequeue message from local queue -- returns NULL if empty + */ +extern mqueue_block +mqueue_local_dequeue(mqueue_local_queue lmq) +{ + mqueue_block mqb = lmq->head ; + + if (mqb != NULL) + lmq->head = mqb->next ; + + return mqb ; +} ; + +/*============================================================================== + * Message queue signal handling + */ + +/*------------------------------------------------------------------------------ + * Initialise a message queue signal structure (struct mqueue_thread_signal). + * Allocate one if required. + * + * If !pthreads_enabled, then this structure is entirely redundant, but there + * is no harm in creating it -- but the signal will never be used. + * + * Returns address of the structure. + */ +extern mqueue_thread_signal +mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, + int signum) +{ + if (mqt == NULL) + mqt = XCALLOC(MTYPE_MQUEUE_THREAD_SIGNAL, + sizeof(struct mqueue_thread_signal)) ; + else + memset(mqt, 0, sizeof(struct mqueue_thread_signal)) ; + + /* next and prev fields set to NULL already. */ + + mqt->qpthread = thread ; + mqt->signum = signum ; + + return mqt ; +} ; + +/*------------------------------------------------------------------------------ + * Reset a message queue signal structure and release if required. + * + * NB: MUST NOT be queued as a waiter anywhere !! + * + * Frees the structure if required, and returns NULL. + * Otherwise zeroises the structure, and returns address of same. + */ +extern mqueue_thread_signal +mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) +{ + passert(mqt->prev == NULL) ; + + if (free_structure) + XFREE(MTYPE_MQUEUE_THREAD_SIGNAL, mqt) ; /* sets mqt = NULL */ + else + memset(mqt, 0, sizeof(struct mqueue_thread_signal)) ; + + return mqt ; +} ; + +/*------------------------------------------------------------------------------ + * Signal the first 'n' threads on the to be signalled list. + * + * Removes the threads from the list and reduces the waiters count. + * + * NB: must be qpthreads_enabled with at least 'n' waiters. + * + * NB: sets the prev entry in the mqueue_thread_signal block to NULL, so that + * the thread can tell that its signal has been kicked. + * + * NB: *** MUST own the mqueue_queue mutex. *** + */ +static void +mqueue_kick_signal(mqueue_queue mq, unsigned n) +{ + mqueue_thread_signal mtsig ; + + dassert( (qpthreads_enabled) && (mq->waiters >= n) ) ; + while (n--) + { + mqueue_dequeue_signal(mq, mtsig = mq->kick.signal.head) ; + qpt_thread_signal(mtsig->qpthread, mtsig->signum) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Remove given signal from given message queue. + * + * NB: sets the prev entry in the mqueue_thread_signal block to NULL, so that + * the thread can tell that its signal has been kicked. + * + * NB: *** MUST own the mqueue_queue mutex. *** + */ +static void +mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) +{ + mqueue_thread_signal next ; + mqueue_thread_signal prev ; + + next = mtsig->next ; + prev = mtsig->prev ; + + if (prev == (void*)mq) /* marker for head of list */ + { + dassert(mq->kick.signal.head == mtsig) ; + mq->kick.signal.head = next ; + } + else + { + dassert((prev != NULL) && (prev->next == mtsig)) ; + prev->next = next ; + } ; + + if (next != NULL) + next->prev = prev ; + + mtsig->next = NULL ; + mtsig->prev = NULL ; /* essential to show signal kicked */ + --mq->waiters ; /* one fewer waiter */ + + dassert( ((mq->kick.signal.head == NULL) && (mq->waiters == 0)) || + ((mq->kick.signal.head != NULL) && (mq->waiters != 0)) ) ; +} ; + +/*============================================================================== + * Message Queue Block Argument Handling + */ + +static void mqb_argv_extend(mqueue_block mqb, mqb_index_t iv) ; + +/*------------------------------------------------------------------------------ + * Get pointer to argv[iv] -- extending if required + */ +inline static mqb_arg_t* +mqb_p_arg_set(mqueue_block mqb, mqb_index_t iv) +{ + if (iv >= mqb->argv_count) + { + if (iv >= mqb->argv_alloc) + mqb_argv_extend(mqb, iv) ; + mqb->argv_count = iv + 1 ; + } ; + + return &mqb->argv[iv] ; +} ; + +/*------------------------------------------------------------------------------ + * Set pointer argv[iv] to given value. + */ +extern void +mqb_set_argv_p(mqueue_block mqb, mqb_index_t iv, mqb_ptr_t p) +{ + mqb_arg_t* p_arg = mqb_p_arg_set(mqb, iv) ; + p_arg->p = p ; +} ; + +/*------------------------------------------------------------------------------ + * Set integer argv[iv] to given value. + */ +extern void +mqb_set_argv_i(mqueue_block mqb, mqb_index_t iv, mqb_int_t i) +{ + mqb_arg_t* p_arg = mqb_p_arg_set(mqb, iv) ; + p_arg->i = i ; +} ; + +/*------------------------------------------------------------------------------ + * Set unsigned integer argv[iv] to given value. + */ +extern void +mqb_set_argv_u(mqueue_block mqb, mqb_index_t iv, mqb_uint_t u) +{ + mqb_arg_t* p_arg = mqb_p_arg_set(mqb, iv) ; + p_arg->u = u ; +} ; + +/*------------------------------------------------------------------------------ + * Set size of argv[]. + * + * This is entirely optional, and may be used to ensure that at least the given + * number of elements have been allocated. + * + * Does not change the "count". Will not reduce the allocated size. + * + * Just avoids repeated extensions of argv if it is known that it will become + * large. + */ +extern void +mqb_set_argv_size(mqueue_block mqb, unsigned n) +{ + if (n > mqb->argv_alloc) + mqb_argv_extend(mqb, n - 1) ; +} ; + +/*------------------------------------------------------------------------------ + * Push a pointer onto the argv "list" + */ +extern void +mqb_push_argv_p(mqueue_block mqb, mqb_ptr_t p) +{ + mqb_arg_t* p_arg ; + + p_arg = mqb_p_arg_set(mqb, mqb->argv_count) ; + p_arg->p = p ; +} ; + +/*------------------------------------------------------------------------------ + * Push an integer onto the argv "list" + */ +extern void +mqb_push_argv_i(mqueue_block mqb, mqb_int_t i) +{ + mqb_arg_t* p_arg ; + + p_arg = mqb_p_arg_set(mqb, mqb->argv_count) ; + p_arg->i = i ; +} ; + +/*------------------------------------------------------------------------------ + * Push an unsigned integer onto the argv "list" + */ +extern void +mqb_push_argv_u(mqueue_block mqb, mqb_uint_t u) +{ + mqb_arg_t* p_arg ; + + p_arg = mqb_p_arg_set(mqb, mqb->argv_count) ; + p_arg->u = u ; +} ; + +/*------------------------------------------------------------------------------ + * Push an array of 'n' void* pointers onto the argv "list" + */ +extern void +mqb_push_argv_array(mqueue_block mqb, unsigned n, void** array) +{ + mqb_index_t iv ; + + /* need do nothing if n == 0, get out now to avoid edge cases */ + if (n == 0) + return ; + + /* make sure we are allocated upto and including the last array item */ + iv = mqb->argv_count ; + mqb_set_argv_size(mqb, iv + n - 1) ; + + /* require that mqb_ptr_t values exactly fill mqb_arg_t entries */ + /* and that mqb_ptr_t values are exactly same as void* values */ + CONFIRM(sizeof(mqb_ptr_t) == sizeof(mqb_arg_t)) ; + CONFIRM(sizeof(mqb_ptr_t) == sizeof(void*)) ; + + /* copy the pointers */ + memcpy(&mqb->argv[iv], array, sizeof(void*) * n) ; +} ; + +/*------------------------------------------------------------------------------ + * Get pointer to argv[iv] -- which MUST exist + * + * NB: it is a FATAL error to reference an argument beyond the last one set. + */ +inline static mqb_arg_t* +mqb_p_arg_get(mqueue_block mqb, mqb_index_t iv) +{ + if (iv >= mqb->argv_count) + zabort("invalid message block argument index") ; + + return &mqb->argv[iv] ; +} ; + +/*------------------------------------------------------------------------------ + * Get pointer value of argv[iv] + * + * NB: it is a FATAL error to reference an argument beyond the last one set. + * + * mqb_get_argv_count() returns the number of arguments set in argv. + */ +extern mqb_ptr_t +mqb_get_argv_p(mqueue_block mqb, mqb_index_t iv) +{ + mqb_arg_t* p_arg = mqb_p_arg_get(mqb, iv) ; + return p_arg->p ; +} ; + +/*------------------------------------------------------------------------------ + * Get integer value of argv[iv] + * + * NB: it is a FATAL error to reference an argument beyond the last one set. + * + * mqb_get_argv_count() returns the number of arguments set in argv. + */ +extern mqb_int_t +mqb_get_argv_i(mqueue_block mqb, mqb_index_t iv) +{ + mqb_arg_t* p_arg = mqb_p_arg_get(mqb, iv) ; + return p_arg->i ; +} ; + +/*------------------------------------------------------------------------------ + * Get unsigned integer value of argv[iv] + * + * NB: it is a FATAL error to reference an argument beyond the last one set. + * + * mqb_get_argv_count() returns the number of arguments set in argv. + */ +extern mqb_uint_t +mqb_get_argv_u(mqueue_block mqb, mqb_index_t iv) +{ + mqb_arg_t* p_arg = mqb_p_arg_get(mqb, iv) ; + return p_arg->u ; +} ; + +/*------------------------------------------------------------------------------ + * Get pointer value of next argv "list" argument -- if any. + * + * There is a "next" counter in the message queue block, which is reset when + * the mqb is initialised or re-initialised, and by mqb_reset_argv_next(). + * + * NB: returns NULL if there is no "list" or if already at the end of same. + */ +extern mqb_ptr_t +mqb_next_argv_p(mqueue_block mqb) +{ + if (mqb->argv_next >= mqb->argv_count) + return NULL ; + + return mqb_get_argv_p(mqb, mqb->argv_next++) ; +} ; + +/*------------------------------------------------------------------------------ + * Get integer value of next argv "list" argument -- if any. + * + * There is a "next" counter in the message queue block, which is reset when + * the mqb is initialised or re-initialised, and by mqb_reset_argv_next(). + * + * NB: returns 0 if there is no "list" or if already at the end of same. + */ +extern mqb_int_t +mqb_next_argv_i(mqueue_block mqb) +{ + if (mqb->argv_next >= mqb->argv_count) + return 0 ; + + return mqb_get_argv_i(mqb, mqb->argv_next++) ; +} ; + +/*------------------------------------------------------------------------------ + * Get unsigned integer value of next argv "list" argument -- if any. + * + * There is a "next" counter in the message queue block, which is reset when + * the mqb is initialised or re-initialised, and by mqb_reset_argv_next(). + * + * NB: returns 0 if there is no "list" or if already at the end of same. + */ +extern mqb_uint_t +mqb_next_argv_u(mqueue_block mqb) +{ + if (mqb->argv_next >= mqb->argv_count) + return 0 ; + + return mqb_get_argv_u(mqb, mqb->argv_next++) ; +} ; + +/*------------------------------------------------------------------------------ + * Pop an array of 'n' void* pointers from the argv "list" + * + * There is a "next" counter in the message queue block, which is reset when + * the mqb is initialised or re-initialised, and by mqb_reset_argv_next(). + * + * Treats from "next" to the end of the "list" as an array of void* pointers. + * + * Creates a temporary void* [] array (MTYPE_TMP), which caller must free. + * + * NB: returns NULL if there is no "list" or if already at the end of same. + */ +extern void** +mqb_pop_argv_array(mqueue_block mqb) +{ + void** array ; + unsigned n ; + + mqb_index_t iv = mqb->argv_next ; + + /* worry about state of "next" and get out if nothing to do. */ + if (iv >= mqb->argv_count) + return NULL ; + + /* work out how much to pop and update "next" */ + n = mqb->argv_count - iv ; + + mqb->argv_next = mqb->argv_count ; + + /* construct target array */ + array = XMALLOC(MTYPE_TMP, sizeof(void*) * n) ; + + /* require that mqb_ptr_t values exactly fill mqb_arg_t entries */ + /* and that mqb_ptr_t values are exactly same as void* values */ + CONFIRM(sizeof(mqb_ptr_t) == sizeof(mqb_arg_t)) ; + CONFIRM(sizeof(mqb_ptr_t) == sizeof(void*)) ; + + /* now transfer pointers to the array */ + memcpy(array, mqb->argv + iv, sizeof(void*) * n) ; + + return array ; +} ; + +/*------------------------------------------------------------------------------ + * Extend the argv to include at least given iv. + * + * The number of argv slots allocated is arranged to be a multiple of + * mqb_argv_size_unit. + * + * Ensures that newly created slots are zeroised. + */ +static void +mqb_argv_extend(mqueue_block mqb, mqb_index_t iv) +{ + mqb_index_t need ; /* total slots required */ + mqb_index_t have ; + + have = mqb->argv_alloc ; + assert(have <= iv) ; + + need = ((iv / mqb_argv_size_unit) + 1) * mqb_argv_size_unit ; + + if (mqb->argv == NULL) + mqb->argv = XCALLOC(MTYPE_MQUEUE_BLOCK_ARGV, mqb_argv_size(need)) ; + else + { + mqb->argv = XREALLOC(MTYPE_MQUEUE_BLOCK_ARGV, mqb->argv, + mqb_argv_size(need)) ; + memset(&mqb->argv[have], 0, mqb_argv_size(need - have)) ; + } ; + + mqb->argv_alloc = need ; +} ; diff --git a/lib/mqueue.h b/lib/mqueue.h new file mode 100644 index 00000000..a28b6606 --- /dev/null +++ b/lib/mqueue.h @@ -0,0 +1,393 @@ +/* Message Queue data structure -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_MQUEUE_H +#define _ZEBRA_MQUEUE_H + +#include <stddef.h> +#include <stdbool.h> + +#include "qpthreads.h" +#include "qtime.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Message Queue Blocks -- mqb + * + * Messages in a message queue are held as Message Queue Blocks: + * + * * action -- function to call when message is dispatched + * + * * arg0 -- always a pointer -- used in specific revoke + * + * * args -- embedded structure -- to be overlaid by user structure + * + * * argv -- pointer to: list/array of pointer/integer/unsigned + * + * NB: the elements of argv are all exactly the same size and alignment. + * + * So, as well as using the access functions, it is possible to use the + * argv array directly, as any of: + * + * mqb_arg_t* argv = mqb_get_argv(mqb) ; + * + * void** argv = mqb_get_argv(mqb) ; + * char** argv = mqb_get_argv(mqb) ; + * + * mqb_ptr_t* argv = mqb_get_argv(mqb) ; + * mqb_int_t* argv = mqb_get_argv(mqb) ; + * mqb_uint_t* argv = mqb_get_argv(mqb) ; + */ + +typedef struct mqueue_block* mqueue_block ; + +typedef void* mqb_ptr_t ; +typedef intptr_t mqb_int_t ; +typedef uintptr_t mqb_uint_t ; + +typedef unsigned short mqb_index_t ; + +typedef union +{ + mqb_ptr_t p ; + mqb_int_t i ; + mqb_uint_t u ; +} mqb_arg_t ; + +/* argv is an array of mqb_arg_t, which is the same as an array of.... */ +CONFIRM(sizeof(mqb_arg_t) == sizeof(void*)) ; /* ... pointers */ +CONFIRM(sizeof(mqb_arg_t) == sizeof(mqb_ptr_t)) ; /* ... mqb_ptr_t */ +CONFIRM(sizeof(mqb_arg_t) == sizeof(mqb_int_t)) ; /* ... mqb_int_t */ +CONFIRM(sizeof(mqb_arg_t) == sizeof(mqb_uint_t)) ; /* ... mqb_uint_t */ + /* ... or any combination */ +enum mqb_flag +{ + mqb_destroy = 0, + mqb_action = 1 +} ; + +typedef enum mqb_flag mqb_flag_t ; + +typedef void mqueue_action(mqueue_block mqb, mqb_flag_t flag) ; + +enum { mqb_args_size_max = 64 } ; /* maximum size of struct args */ +enum { mqb_argv_size_unit = 16 } ; /* allocate argv in these units */ + +struct mqb_args +{ + char bytes[mqb_args_size_max] ; /* empty space */ +} ; + +#define MQB_ARGS_SIZE_OK(s) CONFIRM(sizeof(struct s) <= mqb_args_size_max) + +struct mqueue_block +{ + struct mqb_args args ; /* user structure */ + + mqueue_block next ; /* single linked list */ + + mqueue_action* action ; /* for message dispatch */ + + void* arg0 ; + mqb_arg_t* argv ; /* argv, if any */ + + mqb_index_t argv_count ; /* count of elements in argv */ + mqb_index_t argv_alloc ; /* count of elements allocated */ + mqb_index_t argv_next ; /* iterator */ +} ; + +/* mqueue_block structures are malloced. That guarantees maximum alignment. + * To guarantee maximum alignment for "struct args", it must be first item ! + * + * (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining + * about first argument of offsetof().) + */ +typedef struct mqueue_block mqueue_block_t ; +CONFIRM(offsetof(mqueue_block_t, args) == 0) ; + +/*============================================================================== + * The Message Queue itself + */ + +typedef struct mqueue_thread_signal* mqueue_thread_signal ; + +struct mqueue_thread_signal { + mqueue_thread_signal next ; /* NULL => last on list */ + mqueue_thread_signal prev ; /* NULL => NOT on list -- vital ! */ + + qpt_thread_t qpthread ; /* qpthread to kick */ + int signum ; /* signal to kick with */ +} ; + +enum mqueue_queue_type { + mqt_cond_unicast, /* use qpt_cond_signal to kick the queue */ + mqt_cond_broadcast, /* use qpt_cond_broadcast to kick the queue */ + mqt_signal_unicast, /* use single qpt_signal to kick the queue */ + mqt_signal_broadcast, /* use multiple qpt_signal to kick the queue */ +}; + +#ifndef MQUEUE_DEFAULT_INTERVAL +# define MQUEUE_DEFAULT_INTERVAL QTIME(5) +#endif + +struct mqueue_queue_cond { + qpt_cond_t wait_here ; + qtime_mono_t timeout ; + qtime_t interval ; +} ; + +struct mqueue_queue_signal { + mqueue_thread_signal head ; /* NULL => list is empty */ + mqueue_thread_signal tail ; +}; + +typedef struct mqueue_queue* mqueue_queue ; + +struct mqueue_queue +{ + qpt_mutex_t mutex ; + + mqueue_block head ; /* NULL => list is empty */ + mqueue_block tail_priority ; /* last priority message (if any & not empty) */ + mqueue_block tail ; /* last message (if not empty) */ + + unsigned count ; /* of items on the queue */ + + enum mqueue_queue_type type ; + + unsigned waiters ; + + union { + struct mqueue_queue_cond cond ; + struct mqueue_queue_signal signal ; + } kick ; +} ; + +typedef struct mqueue_local_queue* mqueue_local_queue ; + +struct mqueue_local_queue +{ + mqueue_block head ; /* NULL => list is empty */ + mqueue_block tail ; /* last message (if not empty) */ +} ; + +/*============================================================================== + * Functions + */ + +extern void +mqueue_initialise(void) ; + +extern void +mqueue_finish(void) ; + +extern mqueue_queue +mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ; + +extern void +mqueue_empty(mqueue_queue mq) ; + +extern mqueue_queue +mqueue_reset(mqueue_queue mq, int free_structure) ; + +#define mqueue_reset_keep(mq) mqueue_reset(mq, 0) +#define mqueue_reset_free(mq) mqueue_reset(mq, 1) + +extern mqueue_local_queue +mqueue_local_init_new(mqueue_local_queue lmq) ; + +extern mqueue_local_queue +mqueue_local_reset(mqueue_local_queue lmq, int free_structure) ; + +#define mqueue_local_reset_keep(lmq) mqueue_local_reset(lmq, 0) +#define mqueue_local_reset_free(lmq) mqueue_local_reset(lmq, 1) + +extern void +mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; + +extern mqueue_thread_signal +mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, + int signum) ; +mqueue_thread_signal +mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) ; + +#define mqueue_thread_signal_reset_keep(mqt) mqueue_thread_signal_reset(mqt, 0) +#define mqueue_thread_signal_reset_free(mqt) mqueue_thread_signal_reset(mqt, 1) + +extern mqueue_block +mqb_init_new(mqueue_block mqb, mqueue_action action, void* arg0) ; + +extern mqueue_block +mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) ; + +extern void +mqb_free(mqueue_block mqb) ; + +enum mqb_rank +{ + mqb_priority = true, + mqb_ordinary = false +} ; + +extern void +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) ; + +extern mqueue_block +mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; + +extern void +mqueue_revoke(mqueue_queue mq, void* arg0) ; + +extern int +mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; + +extern void +mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ; + +extern void +mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ; + +Inline mqueue_block +mqueue_local_head(mqueue_local_queue lmq) ; + +extern mqueue_block +mqueue_local_dequeue(mqueue_local_queue lmq) ; + +/*============================================================================== + * Access functions for mqueue_block fields -- mqb_set_xxx/mqb_get_xxx + * + * Users should not poke around inside the mqueue_block structure. + */ + +Inline void mqb_set_action(mqueue_block mqb, mqueue_action action) ; + +Inline void mqb_set_arg0(mqueue_block mqb, void* p) ; + +extern void mqb_set_argv_size(mqueue_block mqb, unsigned n) ; + +extern void mqb_set_argv_p(mqueue_block mqb, mqb_index_t iv, mqb_ptr_t p) ; +extern void mqb_set_argv_i(mqueue_block mqb, mqb_index_t iv, mqb_int_t i) ; +extern void mqb_set_argv_u(mqueue_block mqb, mqb_index_t iv, mqb_uint_t u) ; + +extern void mqb_push_argv_p(mqueue_block mqb, mqb_ptr_t p) ; +extern void mqb_push_argv_i(mqueue_block mqb, mqb_int_t i) ; +extern void mqb_push_argv_u(mqueue_block mqb, mqb_uint_t u) ; + +extern void mqb_push_argv_array(mqueue_block mqb, unsigned n, void** array) ; + +Inline void mqb_dispatch(mqueue_block mqb, mqb_flag_t flag) ; +Inline void mqb_dispatch_action(mqueue_block mqb) ; +Inline void mqb_dispatch_destroy(mqueue_block mqb) ; + +Inline void* mqb_get_arg0(mqueue_block mqb) ; +Inline void* mqb_get_args(mqueue_block mqb) ; +Inline void* mqb_get_argv(mqueue_block mqb) ; + +Inline mqb_index_t mqb_get_argv_count(mqueue_block mqb) ; + +extern mqb_ptr_t mqb_get_argv_p(mqueue_block mqb, mqb_index_t iv) ; +extern mqb_int_t mqb_get_argv_i(mqueue_block mqb, mqb_index_t iv) ; +extern mqb_uint_t mqb_get_argv_u(mqueue_block mqb, mqb_index_t iv) ; + +extern mqb_ptr_t mqb_next_argv_p(mqueue_block mqb) ; +extern mqb_int_t mqb_next_argv_i(mqueue_block mqb) ; +extern mqb_uint_t mqb_next_argv_u(mqueue_block mqb) ; + +extern void** mqb_pop_argv_array(mqueue_block mqb) ; + +/*============================================================================== + * The Inline functions. + */ + +Inline mqueue_block +mqueue_local_head(mqueue_local_queue lmq) +{ + return lmq->head ; +} ; + +/* Set operations. */ + +Inline void +mqb_set_action(mqueue_block mqb, mqueue_action action) +{ + mqb->action = action ; +} ; + +Inline void +mqb_set_arg0(mqueue_block mqb, void* arg0) +{ + mqb->arg0 = arg0 ; +} ; + +/* Get operations */ + +Inline void +mqb_dispatch(mqueue_block mqb, mqb_flag_t flag) +{ + mqb->action(mqb, flag) ; +} ; + +Inline void +mqb_dispatch_action(mqueue_block mqb) +{ + mqb->action(mqb, mqb_action) ; +} ; + +Inline void +mqb_dispatch_destroy(mqueue_block mqb) +{ + mqb->action(mqb, mqb_destroy) ; +} ; + +Inline void* +mqb_get_arg0(mqueue_block mqb) +{ + return mqb->arg0 ; +} ; + +Inline void* +mqb_get_args(mqueue_block mqb) +{ + return &mqb->args ; +} ; + +Inline void* +mqb_get_argv(mqueue_block mqb) +{ + return mqb->argv ; +} ; + +Inline mqb_index_t +mqb_get_argv_count(mqueue_block mqb) +{ + return mqb->argv_count ; +} ; + +Inline void +mqb_reset_argv_next(mqueue_block mqb) +{ + mqb->argv_next = 0 ; +} ; + +#endif /* _ZEBRA_MQUEUE_H */ diff --git a/lib/network.c b/lib/network.c index 3373983b..ae67a402 100644 --- a/lib/network.c +++ b/lib/network.c @@ -17,14 +17,23 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> #include "log.h" #include "network.h" -/* Read nbytes from fd and store into ptr. */ +/*------------------------------------------------------------------------------ + * Read nbytes from fd and store into ptr -- BLOCKING + * + * Loops internally if gets EINTR. + * + * Returns: >= 0 -- number of bytes read + * < 0 => error + * + * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK + */ int readn (int fd, u_char *ptr, int nbytes) { @@ -33,24 +42,86 @@ readn (int fd, u_char *ptr, int nbytes) nleft = nbytes; - while (nleft > 0) + while (nleft > 0) { nread = read (fd, ptr, nleft); - if (nread < 0) - return (nread); + if (nread > 0) + { + nleft -= nread; + ptr += nread; + } + else if (nread == 0) + break; else - if (nread == 0) - break; - - nleft -= nread; - ptr += nread; + { + if (errno != EINTR) + return (nread); + } } return nbytes - nleft; -} +} + +/*------------------------------------------------------------------------------ + * Read up to nbyte bytes into buf -- assuming NON-BLOCKING. + * + * Loops internally if gets EINTR -- so if does not read everything asked for, + * that must be because the read would otherwise block. + * + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately + * + * NB: if asked to write zero bytes, does nothing and will return 0. + * + * Reading zero bytes is defined for all types of files, and may be used + * to probe for error state. + */ +int +read_nb(int fd, void* buf, size_t nbyte) +{ + size_t nleft = nbyte ; + + do + { + int ret = read(fd, buf, nleft); + + if (ret > 0) + { + buf = (char*)buf + ret ; + nleft -= ret ; + } + else if (ret == 0) + { + if (nleft < nbyte) + break ; /* if read something before EOF */ + + return -2 ; /* hit EOF immediately */ + } + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; /* failed */ + } ; + } while (nleft > 0) ; + + return (nbyte - nleft) ; +} ; -/* Write nbytes from ptr to fd. */ +/*------------------------------------------------------------------------------ + * Write nbytes to fd from ptr -- BLOCKING + * + * Loops internally if gets EINTR. + * + * Returns: >= 0 -- number of bytes written + * < 0 => error + * + * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK + */ int writen(int fd, const u_char *ptr, int nbytes) { @@ -59,19 +130,75 @@ writen(int fd, const u_char *ptr, int nbytes) nleft = nbytes; - while (nleft > 0) + while (nleft > 0) { nwritten = write(fd, ptr, nleft); - - if (nwritten <= 0) - return (nwritten); - nleft -= nwritten; - ptr += nwritten; + if (nwritten > 0) + { + nleft -= nwritten; + ptr += nwritten; + } + else if (nwritten == 0) + break ; + else + { + if (errno != EINTR) + return (nwritten); + } } return nbytes - nleft; } +/*------------------------------------------------------------------------------ + * Write up to nbyte bytes from buf -- assuming NON-BLOCKING. + * + * Loops internally if gets EINTR. + * + * Returns: 0..n -- number of bytes written + * -1 => failed -- see errno + * + * NB: if asked to write zero bytes, does nothing and will return 0. + * + * Writing zero bytes is defined for "regular files", but not for anything + * else. + */ +int +write_nb(int fd, void* buf, size_t nbyte) +{ + size_t nleft = nbyte ; + + while (nleft > 0) + { + int ret = write(fd, buf, nleft); + + if (ret > 0) + { + buf = (char*)buf + ret ; + nleft -= ret ; + } + else if (ret == 0) + break ; /* not sure can happen... but + cannot assume will go away */ + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; /* failed */ + } ; + } ; + + return (nbyte - nleft) ; +} ; + +/*------------------------------------------------------------------------------ + * Set fd to non-blocking + * + * Returns: 0 => OK + * -1 => failed + */ int set_nonblocking(int fd) { @@ -82,13 +209,13 @@ set_nonblocking(int fd) if ((flags = fcntl(fd, F_GETFL)) < 0) { zlog_warn("fcntl(F_GETFL) failed for fd %d: %s", - fd, safe_strerror(errno)); + fd, errtoa(errno, 0).str); return -1; } if (fcntl(fd, F_SETFL, (flags | O_NONBLOCK)) < 0) { zlog_warn("fcntl failed setting fd %d non-blocking: %s", - fd, safe_strerror(errno)); + fd, errtoa(errno, 0).str); return -1; } return 0; diff --git a/lib/network.h b/lib/network.h index 4d9c2284..72d38b52 100644 --- a/lib/network.h +++ b/lib/network.h @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_NETWORK_H @@ -33,6 +33,10 @@ extern int writen (int, const u_char *, int); -1 on error. */ extern int set_nonblocking(int fd); +/* Non-Blocking versions of read/write */ +int read_nb(int fd, void* buf, size_t nbyte) ; +int write_nb(int fd, void* buf, size_t nbyte) ; + /* Does the I/O error indicate that the operation should be retried later? */ #define ERRNO_IO_RETRY(EN) \ (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) diff --git a/lib/node_type.h b/lib/node_type.h new file mode 100644 index 00000000..7ec1107d --- /dev/null +++ b/lib/node_type.h @@ -0,0 +1,81 @@ +/* Command handler node_type stuff -- header + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_NODE_TYPE_H +#define _ZEBRA_NODE_TYPE_H + +/* There are some command levels which called from command node. */ +enum node_type +{ + AUTH_NODE, /* Authentication mode of vty interface. */ + RESTRICTED_NODE, /* Restricted view mode */ + VIEW_NODE, /* View node. Default mode of vty interface. */ + AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ + ENABLE_NODE, /* Enable node. */ + + MIN_DO_SHORTCUT_NODE = ENABLE_NODE, + /* May not "do xxx" at any node lower */ + MAX_NON_CONFIG_NODE = ENABLE_NODE, + /* May not be higher than this without owning + * the configuration symbol of power */ + + CONFIG_NODE, /* Config node. Default mode of config file. */ + + MIN_CONTEXT_NODE = CONFIG_NODE, + /* May not change context to any node lower */ + + SERVICE_NODE, /* Service node. */ + DEBUG_NODE, /* Debug node. */ + AAA_NODE, /* AAA node. */ + KEYCHAIN_NODE, /* Key-chain node. */ + KEYCHAIN_KEY_NODE, /* Key-chain key node. */ + INTERFACE_NODE, /* Interface mode node. */ + ZEBRA_NODE, /* zebra connection node. */ + TABLE_NODE, /* rtm_table selection node. */ + RIP_NODE, /* RIP protocol mode node. */ + RIPNG_NODE, /* RIPng protocol mode node. */ + BGP_NODE, /* BGP protocol mode which includes BGP4+ */ + BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ + BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ + BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ + BGP_IPV6_NODE, /* BGP IPv6 address family */ + BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ + OSPF_NODE, /* OSPF protocol mode */ + OSPF6_NODE, /* OSPF protocol for IPv6 mode */ + ISIS_NODE, /* ISIS protocol mode */ + MASC_NODE, /* MASC for multicast. */ + IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ + IP_NODE, /* Static ip route node. */ + ACCESS_NODE, /* Access list node. */ + PREFIX_NODE, /* Prefix list node. */ + ACCESS_IPV6_NODE, /* Access list node. */ + PREFIX_IPV6_NODE, /* Prefix list node. */ + AS_LIST_NODE, /* AS list node. */ + COMMUNITY_LIST_NODE, /* Community list node. */ + RMAP_NODE, /* Route map node. */ + SMUX_NODE, /* SNMP configuration node. */ + DUMP_NODE, /* Packet dump node. */ + FORWARDING_NODE, /* IP forwarding node. */ + PROTOCOL_NODE, /* protocol filtering node */ + VTY_NODE, /* Vty node. */ +}; + +#endif /* _ZEBRA_NODE_TYPE_H */ diff --git a/lib/pid_output.c b/lib/pid_output.c index 5261babc..df1862bb 100644 --- a/lib/pid_output.c +++ b/lib/pid_output.c @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -39,7 +39,7 @@ pid_output (const char *path) oldumask = umask(0777 & ~PIDFILE_MASK); fp = fopen (path, "w"); - if (fp != NULL) + if (fp != NULL) { fprintf (fp, "%d\n", (int) pid); fclose (fp); @@ -49,7 +49,7 @@ pid_output (const char *path) /* XXX Why do we continue instead of exiting? This seems incompatible with the behavior of the fcntl version below. */ zlog_warn("Can't fopen pid lock file %s (%s), continuing", - path, safe_strerror(errno)); + path, errtoa(errno, 0).str); umask(oldumask); return -1; } @@ -59,50 +59,74 @@ pid_output (const char *path) pid_t pid_output (const char *path) { - int tmp; - int fd; - pid_t pid; - char buf[16]; - struct flock lock; - mode_t oldumask; + const char* fail ; + int err ; + + pid_t pid ; + mode_t oldumask ; + int fd ; + struct flock lock ; + char buf[32] ; + size_t pidsize ; pid = getpid (); oldumask = umask(0777 & ~PIDFILE_MASK); - fd = open (path, O_RDWR | O_CREAT, PIDFILE_MASK); + + fail = "Failed to open pid lock file '%s' for pid %d (%s)" ; + fd = open (path, O_RDWR | O_CREAT, PIDFILE_MASK) ; + err = errno ; + + umask(oldumask); + if (fd < 0) - { - zlog_err("Can't create pid lock file %s (%s), exiting", - path, safe_strerror(errno)); - umask(oldumask); - exit(1); - } - else - { - size_t pidsize; + goto failed_err ; - umask(oldumask); - memset (&lock, 0, sizeof(lock)); + memset (&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &lock) < 0) + { + fail = "Failed to write lock pid lock file '%s' for pid %d (%s)" ; + err = errno ; - if (fcntl(fd, F_SETLK, &lock) < 0) + if ((err == EACCES) || (err == EAGAIN)) { - zlog_err("Could not lock pid_file %s, exiting", path); - exit(1); - } - - sprintf (buf, "%d\n", (int) pid); - pidsize = strlen(buf); - if ((tmp = write (fd, buf, pidsize)) != (int)pidsize) - zlog_err("Could not write pid %d to pid_file %s, rc was %d: %s", - (int)pid,path,tmp,safe_strerror(errno)); - else if (ftruncate(fd, pidsize) < 0) - zlog_err("Could not truncate pid_file %s to %u bytes: %s", - path,(u_int)pidsize,safe_strerror(errno)); - } + fail = "Failed to write lock pid lock file '%s', " + "blocked by pid %d (%s)" ; + fcntl(fd, F_GETLK, &lock) ; + pid = lock.l_pid ; + } ; + + goto failed_err ; + } ; + + pidsize = sprintf (buf, "%d\n", (int)pid) ; + + fail = "Failed to write pid to pid lock file '%s' for pid %d (%s)" ; + if (write(fd, buf, pidsize) != (ssize_t)pidsize) + goto failed ; + + fail = "Failed to truncate pid lock file '%s' to length for pid %d (%s)" ; + if (ftruncate(fd, pidsize) < 0) + goto failed ; + + fail = "Failed to fsync pid lock file '%s' for pid %d (%s)" ; + if (fsync(fd) < 0) + goto failed ; + return pid; + +failed: + err = errno ; + +failed_err: + zlog_err(fail, path, (int)pid, errtoa(err, 0).str) ; + fprintf(stderr, fail, path, (int)pid, errtoa(err, 0).str) ; + fprintf(stderr, "\n") ; + + exit(1) ; } #endif /* HAVE_FCNTL */ diff --git a/lib/plist.c b/lib/plist.c index 0f802a83..e5c71d35 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -1,6 +1,10 @@ /* Prefix list functions. * Copyright (C) 1999 Kunihiro Ishiguro * + * 24-Nov-2009 -- substantially re-cast to speed up the handling of very + * large prefix-lists (10,000+ entries). + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify @@ -29,494 +33,1005 @@ #include "buffer.h" #include "stream.h" #include "log.h" +#include "symtab.h" +#include "vector.h" -/* Each prefix-list's entry. */ -struct prefix_list_entry +/* This implements ip prefix-list functions. + * + * A prefix-list is referred to by name, where a name is an arbitrary string, + * case-sensitive. When showing prefix-lists the names are sorted + * "alphabetically", except for any digit sections, which sort numerically. + * Note that leading zeros are significant... "01" is not the same as "1", + * and sorts after it. +*/ + +enum prefix_flags { + PREFIX_ANY = 0x01, /* prefix declared as 'any' */ + PREFIX_LE = 0x02, /* explicit 'le' */ + PREFIX_GE = 0x04, /* explicit 'ge' */ + PREFIX_SEQ = 0x10, /* explicit sequence number */ +} ; + +struct prefix_list ; +struct prefix_list_entry ; + +/* Master structure of prefix_list. + * + * Each address family has it's own distinct set of prefix lists. (Including + * the fake AFI_ORF_PREFIX "family".) + * + * This means that a prefix_list name is local to an address family, but + * global wrt router instances. + * */ +struct prefix_master { - int seq; + struct symbol_table table ; /* table of prefix_list by name. */ - int le; - int ge; - - enum prefix_list_type type; + int seqnum_flag ; /* ip prefix-list sequence-number state. */ - int any; - struct prefix prefix; + struct prefix_list *recent ; /* the latest update. */ - unsigned long refcnt; - unsigned long hitcnt; + struct prefix_list *cache_owner ; + /* prefix_list that owns the dup_cache */ + struct vector dup_cache ; /* the dup_cache vector */ - struct prefix_list_entry *next; - struct prefix_list_entry *prev; + void (*add_hook) (struct prefix_list *); + /* executed when new prefix_list is added. */ + void (*delete_hook) (struct prefix_list *); + /* executed when a prefix_list is deleted. */ }; -/* List of struct prefix_list. */ -struct prefix_list_list +/* Each prefix_list is described by one of these.*/ +struct prefix_list { - struct prefix_list *head; - struct prefix_list *tail; -}; + struct prefix_master *master ; /* parent table: scope of this list. */ + struct symbol* sym ; /* symbol in parent's symbol table */ -/* Master structure of prefix_list. */ -struct prefix_master + char *desc ; /* ip prefix-list description */ + + afi_t afi ; /* address family for all prefixes */ + /* this is the *real* address family, so */ + /* not "AFI_ORF_PREFIX" or similar. */ + + struct vector list ; /* the actual list of prefix matches */ + + int (*cmp)(struct prefix_list_entry** p_a, struct prefix_list_entry** p_b) ; + /* used when searching for duplicates */ + + int rangecount; /* XXX TODO: discover what this is for ?? */ + /* Is not changed anywhere !! */ +} ; + +/* Each prefix-list's entry. */ +struct prefix_list_entry { - /* List of prefix_list which name is number. */ - struct prefix_list_list num; + int seq; - /* List of prefix_list which name is string. */ - struct prefix_list_list str; + enum prefix_list_type type; - /* Whether sequential number is used. */ - int seqnum; + int flags ; /* zero or more of PREFIX_ANY, PREFIX_LE and PREFIX_GE */ + int le ; /* for exact match, set to prefix length */ + int ge ; /* ditto */ - /* The latest update. */ - struct prefix_list *recent; + struct prefix prefix; - /* Hook function which is executed when new prefix_list is added. */ - void (*add_hook) (struct prefix_list *); + u_int32_t mask ; /* for IPv4 -- host order. */ + /* for last significant word of IPv6 -- network order */ + int last ; /* for IPv4 -- not used. */ + /* for IPv6 -- word to apply mask to. */ - /* Hook function which is executed when prefix_list is deleted. */ - void (*delete_hook) (struct prefix_list *); + unsigned long refcnt; + unsigned long hitcnt; }; /* Static structure of IPv4 prefix_list's master. */ -static struct prefix_master prefix_master_ipv4 = -{ - {NULL, NULL}, - {NULL, NULL}, - 1, - NULL, - NULL, -}; +static struct prefix_master prefix_master_ipv4 ; #ifdef HAVE_IPV6 /* Static structure of IPv6 prefix-list's master. */ -static struct prefix_master prefix_master_ipv6 = -{ - {NULL, NULL}, - {NULL, NULL}, - 1, - NULL, - NULL, -}; +static struct prefix_master prefix_master_ipv6 ; #endif /* HAVE_IPV6*/ /* Static structure of BGP ORF prefix_list's master. */ -static struct prefix_master prefix_master_orf = -{ - {NULL, NULL}, - {NULL, NULL}, - 1, - NULL, - NULL, -}; - -static struct prefix_master * -prefix_master_get (afi_t afi) +static struct prefix_master prefix_master_orf ; + +/* For real afi, the choice is strictly limited, and includes IPv6 + * only if HAVE_IPV6 ! */ +#ifdef HAVE_IPV6 +#define assert_afi_real(a) assert(((a) == AFI_IP) || ((a) == AFI_IP6)) +#else +#define assert_afi_real(a) assert((a) == AFI_IP) +#endif + +/* Map afi to prefix_master. + * + * Note: there is no ipv6 master if not HAVE_IPV6. + * + * Returns address of prefix_master, or NULL if unknown afi. + */ +static inline struct prefix_master * +prefix_master_get(afi_t afi) { - if (afi == AFI_IP) - return &prefix_master_ipv4; + switch (afi) + { + case AFI_IP: + return &prefix_master_ipv4; #ifdef HAVE_IPV6 - else if (afi == AFI_IP6) - return &prefix_master_ipv6; + case AFI_IP6: + return &prefix_master_ipv6; #endif /* HAVE_IPV6 */ - else if (afi == AFI_ORF_PREFIX) - return &prefix_master_orf; - return NULL; -} + case AFI_ORF_PREFIX: + return &prefix_master_orf; + default: + return NULL; + } ; +} ; -/* Lookup prefix_list from list of prefix_list by name. */ -struct prefix_list * -prefix_list_lookup (afi_t afi, const char *name) +/* Map afi to maximum prefix length. Implied assert_afi_real(). */ +static int +prefix_max_length(afi_t afi) { - struct prefix_list *plist; - struct prefix_master *master; + switch (afi) + { + case AFI_IP: + return IPV4_MAX_BITLEN ; +#ifdef HAVE_IPV6 + case AFI_IP6: + return IPV6_MAX_BITLEN ; +#endif + default: + zabort("invalid address family") ; + return 0 ; + } ; +} ; - if (name == NULL) - return NULL; +/* Compare prefix list entries, taking into account: + * + * -- prefix value -- assumes is masked down correctly + * -- prefix length + * -- ge + * -- le + * -- type + * + * ... everything *except* the sequence number. + */ - master = prefix_master_get (afi); - if (master == NULL) - return NULL; +#define PREFIX_LIST_CMP_RET(f) \ + if ((*p_a)->f != (*p_b)->f) \ + return ( (*p_a)->f < (*p_b)->f) ? -1 : +1 +#define PREFIX_LIST_CMP_RET_NL(f) \ + if ((*p_a)->f != (*p_b)->f) \ + return (ntohl((*p_a)->f) < ntohl((*p_b)->f)) ? -1 : +1 +#define PREFIX_LIST_CMP_REST \ +PREFIX_LIST_CMP_RET(prefix.prefixlen) ; \ +PREFIX_LIST_CMP_RET(ge) ; \ +PREFIX_LIST_CMP_RET(le) ; \ +PREFIX_LIST_CMP_RET(type) - for (plist = master->num.head; plist; plist = plist->next) - if (strcmp (plist->name, name) == 0) - return plist; +static int +prefix_list_ipv4_cmp(struct prefix_list_entry** p_a, + struct prefix_list_entry** p_b) +{ + PREFIX_LIST_CMP_RET_NL(prefix.u.prefix4.s_addr) ; + PREFIX_LIST_CMP_REST ; + return 0 ; +} ; - for (plist = master->str.head; plist; plist = plist->next) - if (strcmp (plist->name, name) == 0) - return plist; +#ifdef HAVE_IPV6 /*----------------------------------------------------*/ +static int +prefix_list_ipv6_cmp(struct prefix_list_entry** p_a, + struct prefix_list_entry** p_b) +{ +#ifdef s6_addr32 + PREFIX_LIST_CMP_RET_NL(prefix.u.prefix6.s6_addr32[0]) ; + PREFIX_LIST_CMP_RET_NL(prefix.u.prefix6.s6_addr32[1]) ; + PREFIX_LIST_CMP_RET_NL(prefix.u.prefix6.s6_addr32[2]) ; + PREFIX_LIST_CMP_RET_NL(prefix.u.prefix6.s6_addr32[3]) ; +#else + int c ; + if ((c = memcmp(&(*p_a)->prefix.u.prefix6.s6_addr, + &(*p_b)->prefix.u.prefix6.s6_addr, 16)) != 0) + return c ; +#endif + PREFIX_LIST_CMP_REST ; + return 0 ; +} ; +#endif /*----------------------------------------------------*/ + +/*============================================================================== + * Operations on prefix_master. + */ - return NULL; -} +static void prefix_list_delete (struct prefix_list* plist) ; +static void prefix_dup_cache_free(struct prefix_master* pm) ; -static struct prefix_list * -prefix_list_new (void) +/* Initialise given prefix_master. */ +static void +prefix_master_init(struct prefix_master * pm) { - struct prefix_list *new; + memset(pm, 0, sizeof(struct prefix_master)) ; - new = XCALLOC (MTYPE_PREFIX_LIST, sizeof (struct prefix_list)); - return new; -} + symbol_table_init_new(&pm->table, pm, 20, 200, NULL, NULL) ; + pm->seqnum_flag = 1 ; /* Default is to generate sequence numbers. */ +} ; +/* Reset given prefix_master. + * + * Frees all prefix lists and empties the symbol table. Any references to + * prefix lists are the responsibility of the reference owners. + * + * Resets to the default sequence numbering state. + * + * Retains current add_hook and delete_hook functions. + */ static void -prefix_list_free (struct prefix_list *plist) +prefix_master_reset(struct prefix_master * pm) { - XFREE (MTYPE_PREFIX_LIST, plist); -} + struct prefix_list* plist ; + while ((plist = symbol_table_ream_keep(&(pm->table)))) + prefix_list_delete(plist) ; -static struct prefix_list_entry * -prefix_list_entry_new (void) -{ - struct prefix_list_entry *new; + pm->seqnum_flag = 1 ; /* Default is to generate sequence numbers. */ - new = XCALLOC (MTYPE_PREFIX_LIST_ENTRY, sizeof (struct prefix_list_entry)); - return new; + pm->recent = NULL ; + prefix_dup_cache_free(pm) ; +} ; + +/* Add hook function. */ +void +prefix_list_add_hook (void (*func)(struct prefix_list *plist)) +{ + prefix_master_ipv4.add_hook = func; +#ifdef HAVE_IPV6 + prefix_master_ipv6.add_hook = func; +#endif /* HAVE_IPV6 */ } -static void -prefix_list_entry_free (struct prefix_list_entry *pentry) +/* Delete hook function. */ +void +prefix_list_delete_hook (void (*func)(struct prefix_list *plist)) { - XFREE (MTYPE_PREFIX_LIST_ENTRY, pentry); + prefix_master_ipv4.delete_hook = func; +#ifdef HAVE_IPV6 + prefix_master_ipv6.delete_hook = func; +#endif /* HAVE_IPVt6 */ } -/* Insert new prefix list to list of prefix_list. Each prefix_list - is sorted by the name. */ -static struct prefix_list * -prefix_list_insert (afi_t afi, const char *name) +/*============================================================================== + * Prefix List Use. + * + * The prefix_list_ref type is a struct symbol_ref*, so we can operate on + * prefix_list_ref* arguments, keeping the stored reference values up to date. + */ + +const char* +prefix_list_get_name(struct prefix_list* plist) { - unsigned int i; - long number; - struct prefix_list *plist; - struct prefix_list *point; - struct prefix_list_list *list; - struct prefix_master *master; + return symbol_get_name(plist->sym) ; +} ; - master = prefix_master_get (afi); - if (master == NULL) - return NULL; +/* Set reference to prefix_list, by name. + * Replaces any existing reference. + * + * Returns the new value of the prefix_list_ref. + * + * NB: if reference existed, the parent and tag fields are preserved. + * Otherwise they are set to 0. + */ +prefix_list_ref +prefix_list_set_ref(prefix_list_ref* p_ref, afi_t afi, const char* name) +{ + struct prefix_master* pm = prefix_master_get(afi) ; - /* Allocate new prefix_list and copy given name. */ - plist = prefix_list_new (); - plist->name = XSTRDUP (MTYPE_PREFIX_LIST_STR, name); - plist->master = master; + if (pm == NULL) + return NULL ; - /* If name is made by all digit character. We treat it as - number. */ - for (number = 0, i = 0; i < strlen (name); i++) - { - if (isdigit ((int) name[i])) - number = (number * 10) + (name[i] - '0'); - else - break; - } + return *p_ref = symbol_set_ref(*p_ref, symbol_find(&(pm->table), name)) ; +} ; - /* In case of name is all digit character */ - if (i == strlen (name)) - { - plist->type = PREFIX_TYPE_NUMBER; +/* Copy reference to prefix_list. + * Replaces any existing reference (by NULL if reference is NULL). + * + * Returns the new value of the prefix_list_ref. + * + * NB: if reference existed, the parent and tag fields are preserved. + * Otherwise they are set to 0. + */ +prefix_list_ref +prefix_list_copy_ref(prefix_list_ref* p_dst, prefix_list_ref src) +{ + return *p_dst = symbol_set_ref(*p_dst, sym_ref_symbol(src)) ; +} ; - /* Set prefix_list to number list. */ - list = &master->num; +/* Unset reference to prefix_list (does nothing if reference is NULL). + * + * Returns the new value of the prefix_list_ref -- ie NULL. + */ +prefix_list_ref +prefix_list_unset_ref(prefix_list_ref* p_ref) +{ + return *p_ref = symbol_unset_ref(*p_ref, 1) ; +} ; - for (point = list->head; point; point = point->next) - if (atol (point->name) >= number) - break; - } - else - { - plist->type = PREFIX_TYPE_STRING; - - /* Set prefix_list to string list. */ - list = &master->str; - - /* Set point to insertion point. */ - for (point = list->head; point; point = point->next) - if (strcmp (point->name, name) >= 0) - break; - } +/* Get name of prefix_list to which given reference (if any) refers. + * + * Returns NULL if the reference is NULL. + */ +const char* +prefix_list_ref_name(prefix_list_ref ref) +{ + return sym_ref_name(ref) ; +} ; - /* In case of this is the first element of master. */ - if (list->head == NULL) - { - list->head = list->tail = plist; - return plist; - } +/* Return "identity" of prefix_list referred to by the given reference. + * Will be NULL if the reference is NULL. + * + * Two references to the same prefix_list will have the same "identity". + */ +void* prefix_list_ref_ident(prefix_list_ref ref) +{ + return (void*)sym_ref_symbol(ref) ; +} ; - /* In case of insertion is made at the tail of access_list. */ - if (point == NULL) - { - plist->prev = list->tail; - list->tail->next = plist; - list->tail = plist; - return plist; - } +/* Return prefix_list referred to by the given reference. + * Will be NULL If the reference is NULL *OR* if the prefix_list is undefined. + */ +struct prefix_list* prefix_list_ref_plist(prefix_list_ref ref) +{ + return (struct prefix_list*)sym_ref_value(ref) ; +} ; - /* In case of insertion is made at the head of access_list. */ - if (point == list->head) - { - plist->next = list->head; - list->head->prev = plist; - list->head = plist; - return plist; - } - /* Insertion is made at middle of the access_list. */ - plist->next = point; - plist->prev = point->prev; +/* Apply a prefix_list to the given prefix. */ +enum prefix_list_type +prefix_list_apply (struct prefix_list *plist, void *object) +{ + struct prefix *p ; + int plen ; + struct prefix_list_entry* pe ; + vector_index i ; + + in_addr_t ip ; +#ifdef s6_addr32 + u_int32_t* pp ; + u_int32_t* pep ; +#else + unsigned char* pp ; + unsigned char* pep ; +#endif + /* Deny if prefix_list is undefined or empty */ + if ((plist == NULL) || (vector_end(&plist->list) == 0)) + return PREFIX_DENY; - if (point->prev) - point->prev->next = plist; - point->prev = plist; + p = object ; + plen = p->prefixlen ; - return plist; -} + /* For maximum performance we have separate loops for IPv4 and IPv6 */ + assert_afi_real(plist->afi) ; -static struct prefix_list * -prefix_list_get (afi_t afi, const char *name) -{ - struct prefix_list *plist; + switch (plist->afi) + { + case AFI_IP: + ip = p->u.prefix4.s_addr ; + for (VECTOR_ITEMS(&plist->list, pe, i)) + { + ++pe->refcnt ; + if ((plen < pe->ge) || (plen > pe->le)) + continue ; + if (((pe->prefix.u.prefix4.s_addr ^ ip) & pe->mask) == 0) + { + ++pe->hitcnt; + return pe->type ; + } ; + } + break ; + + case AFI_IP6: +#ifdef s6_addr32 + pp = p->u.prefix6.s6_addr32 ; +#else + pp = p->u.prefix6.s6_addr ; +#endif + for (VECTOR_ITEMS(&plist->list, pe, i)) + { + int j, l ; + ++pe->refcnt ; + if ((plen < pe->ge) || (plen > pe->le)) + continue ; +#ifdef s6_addr32 + pep = pe->prefix.u.prefix6.s6_addr32 ; +#else + pep = pe->prefix.u.prefix6.s6_addr ; +#endif + l = pe->last ; + for (j = 0 ; j < l ; j++) + if (pep[j] != pp[j]) + goto NEXT ; + if (((pep[l] ^ pp[l]) & pe->mask) == 0) + { + ++pe->hitcnt; + return pe->type ; + } ; + NEXT: ; + } + break ; - plist = prefix_list_lookup (afi, name); + default: + zabort("invalid address family") ; + } ; - if (plist == NULL) - plist = prefix_list_insert (afi, name); - return plist; + return PREFIX_DENY; } +/*============================================================================== + * Basic constructors and destructors. + */ -/* Delete prefix-list from prefix_list_master and free it. */ -static void -prefix_list_delete (struct prefix_list *plist) +/* Construct a new prefix_list and set the the associated symbol's value. + * + * Implied assert_afi_real(). + */ +static struct prefix_list * +prefix_list_new(struct prefix_master* pm, struct symbol* sym, afi_t afi) { - struct prefix_list_list *list; - struct prefix_master *master; - struct prefix_list_entry *pentry; - struct prefix_list_entry *next; + struct prefix_list* new ; - /* If prefix-list contain prefix_list_entry free all of it. */ - for (pentry = plist->head; pentry; pentry = next) - { - next = pentry->next; - prefix_list_entry_free (pentry); - plist->count--; - } - - master = plist->master; - - if (plist->type == PREFIX_TYPE_NUMBER) - list = &master->num; - else - list = &master->str; + new = XCALLOC (MTYPE_PREFIX_LIST, sizeof (struct prefix_list)); + /* NB: implicitly sets the list empty. */ - if (plist->next) - plist->next->prev = plist->prev; - else - list->tail = plist->prev; + new->sym = symbol_inc_ref(sym) ; + new->master = pm ; - if (plist->prev) - plist->prev->next = plist->next; - else - list->head = plist->next; + new->afi = afi ; + switch (afi) + { + case AFI_IP: + new->cmp = prefix_list_ipv4_cmp ; + break ; +#ifdef HAVE_IPV6 + case AFI_IP6: + new->cmp = prefix_list_ipv6_cmp ; + break ; +#endif + default: + zabort("invalid address family") ; + } ; - if (plist->desc) - XFREE (MTYPE_TMP, plist->desc); + symbol_set_value(sym, new) ; - /* Make sure master's recent changed prefix-list information is - cleared. */ - master->recent = NULL; + return new ; +} ; - if (plist->name) - XFREE (MTYPE_PREFIX_LIST_STR, plist->name); - - prefix_list_free (plist); - - if (master->delete_hook) - (*master->delete_hook) (NULL); +/* Initialise prefix_list entry -- cleared to zeros. */ +static struct prefix_list_entry * +prefix_list_entry_init(struct prefix_list_entry * pe) +{ + return memset(pe, 0, sizeof(struct prefix_list_entry)); } +/* Allocate new prefix_list entry -- cleared to zeros. */ static struct prefix_list_entry * -prefix_list_entry_make (struct prefix *prefix, enum prefix_list_type type, - int seq, int le, int ge, int any) +prefix_list_entry_new (void) { - struct prefix_list_entry *pentry; - - pentry = prefix_list_entry_new (); - - if (any) - pentry->any = 1; - - prefix_copy (&pentry->prefix, prefix); - pentry->type = type; - pentry->seq = seq; - pentry->le = le; - pentry->ge = ge; - - return pentry; + return XCALLOC (MTYPE_PREFIX_LIST_ENTRY, sizeof (struct prefix_list_entry)); } -/* Add hook function. */ -void -prefix_list_add_hook (void (*func) (struct prefix_list *plist)) +/* Free given prefix list entry. */ +static void +prefix_list_entry_free (struct prefix_list_entry* pe) { - prefix_master_ipv4.add_hook = func; -#ifdef HAVE_IPV6 - prefix_master_ipv6.add_hook = func; -#endif /* HAVE_IPV6 */ + XFREE (MTYPE_PREFIX_LIST_ENTRY, pe); } -/* Delete hook function. */ -void -prefix_list_delete_hook (void (*func) (struct prefix_list *plist)) +/* Set cache owned by nobody, and free the contents of the cache (if any). */ +static void +prefix_dup_cache_free(struct prefix_master* pm) { - prefix_master_ipv4.delete_hook = func; -#ifdef HAVE_IPV6 - prefix_master_ipv6.delete_hook = func; -#endif /* HAVE_IPVt6 */ + pm->cache_owner = NULL ; + vector_reset(&pm->dup_cache, 0) ; } -/* Calculate new sequential number. */ -static int -prefix_new_seq_get (struct prefix_list *plist) +/* Delete prefix_list from prefix_list_master and free it and its contents. */ +/* The prefix_list's symbol is set undefined. */ +static void +prefix_list_delete (struct prefix_list* plist) { - int maxseq; - int newseq; - struct prefix_list_entry *pentry; + struct prefix_master* pm = plist->master ; + unsigned int i ; + struct prefix_list_entry* pe ; - maxseq = newseq = 0; + /* Free all the prefix_list_entries, then free the vector they live in. */ + for (VECTOR_ITEMS(&plist->list, pe, i)) + prefix_list_entry_free(pe) ; + vector_reset(&plist->list, 0) ; - for (pentry = plist->head; pentry; pentry = pentry->next) - { - if (maxseq < pentry->seq) - maxseq = pentry->seq; - } + /* If there is a description, release that now. */ + if (plist->desc) + XFREE (MTYPE_TMP, plist->desc); - newseq = ((maxseq / 5) * 5) + 5; - - return newseq; -} + /* Can no longer own the dup_cache. */ + if (pm->cache_owner == plist) + prefix_dup_cache_free(pm) ; -/* Return prefix list entry which has same seq number. */ -static struct prefix_list_entry * -prefix_seq_check (struct prefix_list *plist, int seq) + /* Symbol no longer has a value & drop reference. */ + symbol_unset_value(plist->sym) ; + plist->sym = symbol_dec_ref(plist->sym) ; + + /* Finally, release the prefix_list structure. */ + XFREE (MTYPE_PREFIX_LIST, plist) ; + + /* No longer have a recently changed prefix-list */ + pm->recent = NULL ; + + /* Tell the world. */ + if (pm->delete_hook) + (*pm->delete_hook) (NULL); +} ; + +/*============================================================================== + * Operations on prefix_lists + */ + +/* Seek prefix_list by name in give prefix master. Does NOT create. */ +static struct prefix_list * +prefix_list_seek (struct prefix_master* pm, const char *name) { - struct prefix_list_entry *pentry; + return symbol_get_value(symbol_seek(&(pm->table), name)) ; +} ; - for (pentry = plist->head; pentry; pentry = pentry->next) - if (pentry->seq == seq) - return pentry; - return NULL; -} +/* Lookup prefix_list by afi and name -- if afi is known, and name not NULL. + * + * Returns NULL if no prefix_list by that afi and name. Tolerates unknown afi + * and allows "fake" afi (eg. AFI_ORF_PREFIX). + */ +struct prefix_list * +prefix_list_lookup (afi_t afi, const char *name) +{ + struct prefix_master* pm = prefix_master_get(afi) ; -static struct prefix_list_entry * -prefix_list_entry_lookup (struct prefix_list *plist, struct prefix *prefix, - enum prefix_list_type type, int seq, int le, int ge) + if ((name == NULL) || (pm == NULL)) + return NULL; + + return prefix_list_seek(pm, name) ; +} ; + +/* Get prefix_list -- creating empty one if required. */ +static struct prefix_list * +prefix_list_get (struct prefix_master* pm, const char *name, afi_t afi) { - struct prefix_list_entry *pentry; + struct symbol* sym ; + struct prefix_list* plist ; - for (pentry = plist->head; pentry; pentry = pentry->next) - if (prefix_same (&pentry->prefix, prefix) && pentry->type == type) - { - if (seq >= 0 && pentry->seq != seq) - continue; + assert((pm != NULL) && (name != NULL)) ; - if (pentry->le != le) - continue; - if (pentry->ge != ge) - continue; + sym = symbol_find(&(pm->table), name) ; /* creates if required */ + plist = symbol_get_value(sym) ; - return pentry; - } + return plist ? plist : prefix_list_new(pm, sym, afi) ; +} ; - return NULL; -} +/*============================================================================== + * Operations on prefix_list_entry + */ -static void -prefix_list_entry_delete (struct prefix_list *plist, - struct prefix_list_entry *pentry, - int update_list) +/* Sequence comparison function -- used in prefix_list_entry_lookup_seq */ +static int +prefix_seq_cmp(const int** seq, const struct prefix_list_entry** pe) { - if (plist == NULL || pentry == NULL) - return; - if (pentry->prev) - pentry->prev->next = pentry->next; + if (**seq != (*pe)->seq) + return (**seq < (*pe)->seq) ? -1 : + 1 ; + return 0 ; +} ; + +/* Check any ge and le settings -- set defaults as required. + * + * -- sets the implied le for "any" or ge, if no explicit le set + * + * -- checks le & ge and updates as required the filter. + * + * Note that filter requires le = ge = prefix-length for an exact match. + * + * Returns: CMD_SUCCESS -- it's OK + * CMD_WARNING -- something amiss with the ge and/or le setting + * + * Cisco say: + * + * If ge: must be <= maximum prefix length and > actual prefix length + * else: set to prefix length + * + * If le: must be <= maximum prefix length and > actual prefix length + * else: if ge or any set to maximum prefix length + * else: set to prefix length + * + * If both ge and le: must have length < ge < le <= maximum + * + * But Cisco will apparently allow: length < ge <= le <= maximum + * + * We allow: length <= ge <= le <= maximum + * + * GMCH TODO: check on wisdom of all this. + */ +static int +prefix_list_entry_ge_le_check(struct prefix_list_entry* pe, afi_t afi) +{ + int pl_max = prefix_max_length(afi) ; + int pl = pe->prefix.prefixlen ; + + /* If we had ge, check in range, otherwise set to prefixlen. */ + if (pe->flags & PREFIX_GE) + { + if ( !( (pl <= pe->ge) && (pe->ge <= pl_max) ) ) + return CMD_WARNING ; + } else - plist->head = pentry->next; - if (pentry->next) - pentry->next->prev = pentry->prev; + pe->ge = pl ; + + /* If we had le, check in range, otherwise set as required. + * + * Note that if had ge, then we've checked that already, otherwise + * we have set ge = pl -- so can check ge <= le. + */ + if (pe->flags & PREFIX_LE) + { + if ( !( (pe->ge <= pe->le) && (pe->le <= pl_max) ) ) + return CMD_WARNING ; + } else - plist->tail = pentry->prev; + pe->le = (pe->flags & (PREFIX_ANY | PREFIX_GE)) ? pl_max : pl ; - prefix_list_entry_free (pentry); + return CMD_SUCCESS ; +} ; - plist->count--; +/* Lookup prefix_list_entry by its sequence number. Returns index of an entry + * in the prefix_list, and sets: + * + * result < 0 -- not found. index returned is of first entry in the + * prefix list, and this sequence number comes + * before it. (Or list is empty.) + * result == 0 -- found. index is of the entry found. + * result > 0 -- not found. index returned is of the entry with the largest + * sequence number smaller than the given one. + */ +static vector_index +prefix_list_entry_lookup_seq(struct prefix_list *plist, int seq, int* result) +{ + return vector_bsearch(&plist->list, (vector_bsearch_cmp*)prefix_seq_cmp, + &seq, result) ; +} ; - if (update_list) +/* Lookup prefix_list_entry by its contents. + * + * For large prefix_lists this uses the "dup_cache", which is an auxiliary + * vector, sorted by prefix value. Each prefix_master has a dup_cache, + * which is co-opted by the last prefix_list to use it. + * + * Returns index of an entry in the prefix_list or the dup_cache, and sets: + * + * cache -- NULL if not using dup_cache for this prefix_list. + * The index and result values refer to the main prefix_list. + * + * result < 0 -- not found. prefix list is empty, index == 0 + * result == 0 -- found. index is of the prefix list entry + * result > 0 -- not found. prefix is not empty, index == last + * + * -- address of the cache (a vector). + * The index and result values refer to the *cache*. << NB + * + * result < 0 -- not found. index == 0, prefix value given is + * less than first entry in the *cache* + * result == 0 -- found. index is of the *cache* entry + * result > 0 -- not found. index is of entry in the *cache* + * with the largest prefix value less + * than the given prefix value. + * + * Note that the cache is never empty. + */ +static vector_index +prefix_list_entry_lookup_val(struct prefix_list *plist, + struct prefix_list_entry* temp, + vector* cache, + int* result) +{ + /* See if we already have an entry like this one. */ + if (vector_end(&plist->list) > 10) { - if (plist->master->delete_hook) - (*plist->master->delete_hook) (plist); - - if (plist->head == NULL && plist->tail == NULL && plist->desc == NULL) - prefix_list_delete (plist); - else - plist->master->recent = plist; + struct prefix_master* pm = plist->master ; + + if (pm->cache_owner != plist) + { + /* Create new cache by copying vector. Releases any old cache. */ + vector_copy_here(&pm->dup_cache, &plist->list) ; + /* Sort the result so can binary chop it. */ + vector_sort(&pm->dup_cache, (vector_sort_cmp*)plist->cmp) ; + /* Now we own the cache. */ + pm->cache_owner = plist ; + } ; + + *cache = &pm->dup_cache ; + return vector_bsearch(*cache, (vector_bsearch_cmp*)plist->cmp, temp, + result) ; } -} - -static void -prefix_list_entry_add (struct prefix_list *plist, - struct prefix_list_entry *pentry) -{ - struct prefix_list_entry *replace; - struct prefix_list_entry *point; + else + { + struct prefix_list_entry* pe ; + vector_index i ; + *cache = NULL ; /* Not found in cache. */ + *result = 0 ; /* Assume found ! */ + for (VECTOR_ITEMS(&plist->list, pe, i)) + { + if (plist->cmp(&pe, &temp) == 0) + return i ; /* Found ! */ + } ; - /* Automatic asignment of seq no. */ - if (pentry->seq == -1) - pentry->seq = prefix_new_seq_get (plist); + *result = (vector_end(&plist->list) == 0) ? -1 : +1 ; + /* Not found. */ + return i ; + } ; +} ; - /* Is there any same seq prefix list entry? */ - replace = prefix_seq_check (plist, pentry->seq); - if (replace) - prefix_list_entry_delete (plist, replace, 0); +/* Look up prefix_list_entry looking for an exact match. + * + * If we have an explicit sequence number, then we look that up and then + * see if that prefix_list_entry is identical. + * + * Otherwise, look for entry whose value is identical, if any. + * + * Returns an index of a prefix_list entry and sets: + * + * -- result == 0 found -- index is of entry found + * -- result != 0 not found -- index is immaterial + * + * */ +static vector_index +prefix_list_entry_lookup (struct prefix_list* plist, + struct prefix_list_entry* pe_seek, int* result) +{ + struct prefix_list_entry* pe_found ; + vector_index i ; - /* Check insert point. */ - for (point = plist->head; point; point = point->next) - if (point->seq >= pentry->seq) - break; + if (pe_seek->flags & PREFIX_SEQ) + { + /* Lookup by sequence number. */ + i = prefix_list_entry_lookup_seq(plist, pe_seek->seq, result) ; + if (*result == 0) + { + /* found by sequence number, now see if value matches. */ + pe_found = vector_get_item(&plist->list, i) ; + *result = plist->cmp(&pe_seek, &pe_found) ; + } ; + } + else + { + /* Lookup by value. */ + vector cache ; + i = prefix_list_entry_lookup_val(plist, pe_seek, &cache, result) ; + if ((*result == 0) && cache) + { + /* Found in the cache. We need it's position in prefix_list */ + pe_found = vector_get_item(cache, i) ; + i = prefix_list_entry_lookup_seq(plist, pe_found->seq, result) ; + assert(*result == 0) ; /* MUST Find it !! */ + } ; + } - /* In case of this is the first element of the list. */ - pentry->next = point; + return i ; +} ; - if (point) +/* Insert prefix_list_entry or replace an existing one, if we can. + * + * May NOT insert or replace if an entry already exists with the same value, + * (where the value excludes the sequence number). + * + * Except that, if a sequence number is given, it is (trivially) possible to + * "replace" an entry with the same sequence number and the same value. + * + * Then, if no sequence number is given, one is allocated. The allocation + * rounds the last sequence number up to a multiple of 5 and adds 5. + * + * The prefix_list_entry is then put in the list by sequence number, replacing + * any existing entry. + * + * Returns: CMD_SUCCESS -- OK + * CMD_WARNING -- Nope, and NB: temp->seq set to sequence number + * of existing prefix_list_entry. + */ +static int +prefix_list_entry_insert(struct prefix_list *plist, + struct prefix_list_entry *temp) +{ + struct prefix_list_entry* pe ; + vector cache ; + vector_index i, ic ; + int ret, retc ; + u_int32_t mask ; + int pl, sh ; + + /* See if we have an entry like this one, if we do: + * + * OK if sequence number is the same too -- nothing more to do ! + * Fail if sequence number differs or was not set. + * + * If not found, and we own the cache, ic and retc tell us where in the + * cache the new entry belongs. + */ + ic = prefix_list_entry_lookup_val(plist, temp, &cache, &retc) ; + if (retc == 0) { - if (point->prev) - point->prev->next = pentry; - else - plist->head = pentry; - - pentry->prev = point->prev; - point->prev = pentry; - } + pe = vector_get_item(cache ? cache : &plist->list, ic) ; + if ((temp->flags & PREFIX_SEQ) && (pe->seq == temp->seq)) + return CMD_SUCCESS ; + temp->seq = pe->seq ; /* capture clashing sequence number */ + return CMD_WARNING ; + } ; + + /* Now we need to find where to insert in the list. */ + /* If required, we set implied sequence number, and insert at end. */ + if (temp->flags & PREFIX_SEQ) + i = prefix_list_entry_lookup_seq(plist, temp->seq, &ret) ; else { - if (plist->tail) - plist->tail->next = pentry; + int last_seq ; + i = vector_end(&plist->list) ; + if (i == 0) + { + last_seq = 0 ; /* initial value for empty list */ + ret = -1 ; /* insert before first item of empty list */ + } else - plist->head = pentry; - - pentry->prev = plist->tail; - plist->tail = pentry; + { + --i ; /* step back to last entry */ + pe = vector_get_item(&plist->list, i) ; + last_seq = pe->seq ; + ret = 1 ; /* insert after last entry */ + } ; + temp->seq = (((last_seq + 5 - 1) / 5) * 5) + 5 ; + } ; + + /* If we found it with same sequence number, then replace entry, + * otherwise we need to create a new entry and insert it. i & ret show + * where we need to put the value in the list. + * + * If a dup cache exists, that must be kept up to date. ic & retc show + * where need to put the new value in the cache. + */ + if (ret == 0) + { + /* We are going to replace an existing list entry. */ + pe = vector_get_item(&plist->list, i) ; /* address of current entry */ + /* If we have a cache, need to move the entry to it's new place */ + if (cache) + { + /* We need to know where the old value was. */ + vector_index io ; + int reto ; + io = vector_bsearch(cache, (vector_bsearch_cmp*)plist->cmp, pe, + &reto) ; + assert(reto == 0) ; /* MUST find it !! */ + vector_move_item_here(cache, ic, retc, io) ; + } ; } + else + { + /* We are going to insert a new list entry item. */ + pe = prefix_list_entry_new() ; + vector_insert_item_here(&plist->list, i, ret, pe) ; + /* If we have a cache, need to insert the entry to it's place */ + if (cache) + vector_insert_item_here(cache, ic, retc, pe) ; + } ; + + /* Now we can set the value of the entry. */ + *pe = *temp ; + + /* Set mask and last ready to apply the filter. + * + * pl sh mask last + * 0 0 0x00000000 0 case sh == 0 && pl == 0 + * 1 1 0x80000000 0 + * .. .. . + * 31 31 0xFFFFFFFE 0 + * 32 0 0xFFFFFFFF 0 case sh == 0 + * 33 1 0x80000000 1 + * .. .. . + * 64 0 0xFFFFFFFF 1 case sh == 0 + * 65 1 0x80000000 2 + * .. .. . + * 128 0 0xFFFFFFFF 3 case sh == 0 + * + * Note: if we don't have s6_addr32, we must handle IPv6 byte-wise ! + */ + pl = pe->prefix.prefixlen ; + + sh = pl & 0x1F ; + if (sh == 0) + mask = (pl == 0) ? 0x00000000 : 0xFFFFFFFF ; + else + mask = (0xFFFFFFFF >> sh) ^ 0xFFFFFFFF ; - /* Increment count. */ - plist->count++; + switch (plist->afi) + { + case AFI_IP: + pe->mask = htonl(mask) ; + pe->last = 0 ; + break ; + + case AFI_IP6: +#ifdef s6_addr32 + pe->mask = htonl(mask) ; + pe->last = (pl == 0) ? 0 : (pl - 1) >> 5 ; +#else + /* Need to shift 32 bit mask to 8 bit mask + * + * For pl == 0 mask == 0, otherwise: + * + * (pl - 1) & 0x18 -> 0 for pl = 1.. 8, 33..40, 65..72, 97..104 + * 8 for pl = 9..16, etc + * (0x10) 16 for pl = 17..24, etc + * (0x18) 24 for pl = 25..32, etc + * + * So need to shift down by 24 - that, and then mask to byte. + */ + if (pl != 0) + mask >>= (24 - ((pl - 1) & 0x18)) ; + pe->mask = mask & 0xFF ; + pe->last = (pl == 0) ? 0 : (pl - 1) >> 3 ; +#endif + break ; + + default: + zabort("invalid address family") ; + } ; /* Run hook function. */ if (plist->master->add_hook) (*plist->master->add_hook) (plist); plist->master->recent = plist; + + return CMD_SUCCESS ; } +/* Delete prefix_list_entry, if we can. + * + * To delete an entry the caller must specify the exact value of an existing + * entry. If a sequence number is specified, that entry must exist, and its + * value must exactly match the given value. If no sequence number is + * specified, an entry must exist with exactly the given value. + * + * Returns: CMD_SUCCESS -- OK + * CMD_WARNING -- entry not found. + */ +static int +prefix_list_entry_delete (struct prefix_list *plist, + struct prefix_list_entry *pe_seek) +{ + struct prefix_list_entry* pe ; + vector_index i ; + int ret ; + + i = prefix_list_entry_lookup (plist, pe_seek, &ret) ; + if (ret) + return CMD_WARNING ; + + pe = vector_delete_item(&plist->list, i) ; + assert(pe != NULL) ; + + prefix_list_entry_free(pe) ; /* now release memory */ + + if (plist->master->delete_hook) + (*plist->master->delete_hook) (plist); + + if ((vector_end(&plist->list) == 0) && (plist->desc == NULL)) + prefix_list_delete (plist); + else + plist->master->recent = plist; + + return CMD_SUCCESS ; +} ; + +/*============================================================================== + * Common printing operations + */ + /* Return string of prefix_list_type. */ static const char * prefix_list_type_str (struct prefix_list_entry *pentry) @@ -532,179 +1047,192 @@ prefix_list_type_str (struct prefix_list_entry *pentry) } } -static int -prefix_list_entry_match (struct prefix_list_entry *pentry, struct prefix *p) +/* Map afi to name of same: "ip" or "ipv6". Implied assert_afi_real(). */ +static const char* +prefix_afi_name_str(afi_t afi) { - int ret; + switch (afi) + { + case AFI_IP: + return "ip" ; +#ifdef HAVE_IPV6 + case AFI_IP6: + return "ipv6" ; +#endif + default: + zabort("invalid address family") ; + return "?" ; + } ; +} ; - ret = prefix_match (&pentry->prefix, p); - if (! ret) - return 0; - - /* In case of le nor ge is specified, exact match is performed. */ - if (! pentry->le && ! pentry->ge) - { - if (pentry->prefix.prefixlen != p->prefixlen) - return 0; - } - else - { - if (pentry->le) - if (p->prefixlen > pentry->le) - return 0; - - if (pentry->ge) - if (p->prefixlen < pentry->ge) - return 0; - } - return 1; -} +/* Print: "(ip|ipv6) prefix-list NAME" <post> */ +static void +vty_prefix_list_name_print(struct vty* vty, struct prefix_list* plist, + const char* post) +{ + vty_out(vty, "%s prefix-list %s%s", prefix_afi_name_str(plist->afi), + (const char*)symbol_get_name(plist->sym), post) ; +} ; -enum prefix_list_type -prefix_list_apply (struct prefix_list *plist, void *object) +/* Print: "(ip|ipv6) prefix-list NAME: 99 entries" <post> */ +static void +vty_prefix_list_name_count_print(struct vty* vty, struct prefix_list* plist, + const char* post) { - struct prefix_list_entry *pentry; - struct prefix *p; + vty_prefix_list_name_print(vty, plist, "") ; + vty_out(vty, ": %d entries%s", vector_end(&plist->list), post); +} ; - p = (struct prefix *) object; +/* Print: "(ip|ipv6) prefix-list NAME" UNDEFINED<post> */ +static void +vty_prefix_list_undefined_print(struct vty* vty, afi_t afi, struct symbol* sym, + const char* post) +{ + vty_out(vty, "%s prefix-list %s UNDEFINED%s", prefix_afi_name_str(afi), + (const char*)symbol_get_name(sym), post) ; +} ; - if (plist == NULL) - return PREFIX_DENY; +/* Print: <indent>"Description: xxxx"<post>, if there is a description */ +static void +vty_prefix_list_desc_print(struct vty* vty, struct prefix_list* plist, + int indent, const char* post) +{ + if (plist->desc) + vty_out (vty, "%sDescription: %s%s", VTY_SPACES(indent), plist->desc, + VTY_NEWLINE) ; +} + +/* Size of buffer to hold either IPv4 or IPv6 string. */ +#ifndef INETX_ADDRSTRLEN +# if INET_ADDRSTRLEN < INET6_ADDRSTRLEN +# define INETX_ADDRSTRLEN INET6_ADDRSTRLEN +# else +# define INETX_ADDRSTRLEN INET_ADDRSTLEN +# endif +#endif + +/* Print value of given prefix_list_entry: + * + * "[seq 999 ](permit|deny) (any|XXXXX/999)[ ge 99][ le 99]" + * "[ '('hit count: 999, refcount: 999')']" <post> + * + * where: sequence number is included if "with_seq" specified + * ge and/or le are included if explicitly set + * the hit count and refcount are included if "with_stats" specified + */ +static void +vty_prefix_list_value_print(struct vty* vty, struct prefix_list_entry* pe, + const char* post, int with_seq, int with_stats) +{ + if (with_seq) + vty_out(vty, "seq %d ", pe->seq) ; - if (plist->count == 0) - return PREFIX_PERMIT; + vty_out(vty, "%s ", prefix_list_type_str(pe)) ; - for (pentry = plist->head; pentry; pentry = pentry->next) + if (pe->flags & PREFIX_ANY) + vty_out(vty, "any"); + else { - pentry->refcnt++; - if (prefix_list_entry_match (pentry, p)) - { - pentry->hitcnt++; - return pentry->type; - } - } + struct prefix *p = &pe->prefix ; + char buf[INETX_ADDRSTRLEN]; + vty_out(vty, "%s/%d", + inet_ntop (p->family, &p->u.prefix, buf, INETX_ADDRSTRLEN), + p->prefixlen); + } ; - return PREFIX_DENY; + if (pe->flags & PREFIX_GE) + vty_out(vty, " ge %d", pe->ge); + if (pe->flags & PREFIX_LE) + vty_out(vty, " le %d", pe->le); + + if (with_stats) + vty_out (vty, " (hit count: %lu, refcount: %lu)", pe->hitcnt, pe->refcnt); + + vty_out(vty, post) ; } static void __attribute__ ((unused)) prefix_list_print (struct prefix_list *plist) { - struct prefix_list_entry *pentry; + struct prefix_list_entry* pe ; + vector_index i ; + struct vty* vty = NULL ; if (plist == NULL) return; - printf ("ip prefix-list %s: %d entries\n", plist->name, plist->count); - - for (pentry = plist->head; pentry; pentry = pentry->next) - { - if (pentry->any) - printf ("any %s\n", prefix_list_type_str (pentry)); - else - { - struct prefix *p; - char buf[BUFSIZ]; - - p = &pentry->prefix; - - printf (" seq %d %s %s/%d", - pentry->seq, - prefix_list_type_str (pentry), - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); - if (pentry->ge) - printf (" ge %d", pentry->ge); - if (pentry->le) - printf (" le %d", pentry->le); - printf ("\n"); - } - } -} - -/* Retrun 1 when plist already include pentry policy. */ -static struct prefix_list_entry * -prefix_entry_dup_check (struct prefix_list *plist, - struct prefix_list_entry *new) -{ - struct prefix_list_entry *pentry; - int seq = 0; - - if (new->seq == -1) - seq = prefix_new_seq_get (plist); - else - seq = new->seq; + vty_prefix_list_name_count_print(vty, plist, VTY_NEWLINE) ; - for (pentry = plist->head; pentry; pentry = pentry->next) + for (VECTOR_ITEMS(&plist->list, pe, i)) { - if (prefix_same (&pentry->prefix, &new->prefix) - && pentry->type == new->type - && pentry->le == new->le - && pentry->ge == new->ge - && pentry->seq != seq) - return pentry; + vty_out_indent(vty, 2) ; + vty_prefix_list_value_print(vty, pe, VTY_NEWLINE, 1, 1) ; } - return NULL; } +/*============================================================================== + * vty prefix_list operations. + */ -static int -vty_invalid_prefix_range (struct vty *vty, const char *prefix) +/* Look up given prefix_list -- complain if not found. */ +static struct prefix_list* +vty_prefix_list_lookup(struct vty *vty, afi_t afi, const char* name) { - vty_out (vty, "%% Invalid prefix range for %s, make sure: len < ge-value <= le-value%s", - prefix, VTY_NEWLINE); - return CMD_WARNING; -} + struct prefix_list* plist = prefix_list_lookup(afi, name); + if (plist == NULL) + vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); + return plist ; +} ; +/* Process parameters for (ip|ipv6) prefix-list and no (ip|ipv6) prefix-list + * + * Fills in the given prefix_list_entry structure, ready for looking up, + * inserting or deleting prefix_list_entry. + * + * Checks parameters for validity/legality. + * + * Returns a CMD_xxxx return code. CMD_SUCCESS => OK ! + */ static int -vty_prefix_list_install (struct vty *vty, afi_t afi, const char *name, - const char *seq, const char *typestr, - const char *prefix, const char *ge, const char *le) +vty_prefix_list_process(struct vty *vty, struct prefix_list_entry* pe, + struct prefix_list* plist, + afi_t afi, const char *seq_str, const char *type_str, + const char *prefix_str, + const char *ge_str, const char *le_str) { - int ret; - enum prefix_list_type type; - struct prefix_list *plist; - struct prefix_list_entry *pentry; - struct prefix_list_entry *dup; - struct prefix p; - int any = 0; - int seqnum = -1; - int lenum = 0; - int genum = 0; + int ret ; - /* Sequential number. */ - if (seq) - seqnum = atoi (seq); + assert_afi_real(afi) ; /* require real (and supported) afi */ - /* ge and le number */ - if (ge) - genum = atoi (ge); - if (le) - lenum = atoi (le); + prefix_list_entry_init(pe) ; /* clears everything, including flags */ + + /* Sequence number. */ + if (seq_str) + { + pe->flags |= PREFIX_SEQ ; + pe->seq = atoi(seq_str) ; + } ; /* Check filter type. */ - if (strncmp ("permit", typestr, 1) == 0) - type = PREFIX_PERMIT; - else if (strncmp ("deny", typestr, 1) == 0) - type = PREFIX_DENY; + if (strncmp ("permit", type_str, 1) == 0) + pe->type = PREFIX_PERMIT; + else if (strncmp ("deny", type_str, 1) == 0) + pe->type = PREFIX_DENY; else { vty_out (vty, "%% prefix type must be permit or deny%s", VTY_NEWLINE); return CMD_WARNING; } - /* "any" is special token for matching any IPv4 addresses. */ + /* Watch out for "any" */ + if (strncmp ("any", prefix_str, strlen (prefix_str)) == 0) + pe->flags |= PREFIX_ANY ; + + /* Process the prefix. */ if (afi == AFI_IP) { - if (strncmp ("any", prefix, strlen (prefix)) == 0) - { - ret = str2prefix_ipv4 ("0.0.0.0/0", (struct prefix_ipv4 *) &p); - genum = 0; - lenum = IPV4_MAX_BITLEN; - any = 1; - } - else - ret = str2prefix_ipv4 (prefix, (struct prefix_ipv4 *) &p); - + if (pe->flags & PREFIX_ANY) + prefix_str = "0.0.0.0/0" ; + ret = str2prefix_ipv4 (prefix_str, (struct prefix_ipv4 *)&pe->prefix); if (ret <= 0) { vty_out (vty, "%% Malformed IPv4 prefix%s", VTY_NEWLINE); @@ -714,16 +1242,9 @@ vty_prefix_list_install (struct vty *vty, afi_t afi, const char *name, #ifdef HAVE_IPV6 else if (afi == AFI_IP6) { - if (strncmp ("any", prefix, strlen (prefix)) == 0) - { - ret = str2prefix_ipv6 ("::/0", (struct prefix_ipv6 *) &p); - genum = 0; - lenum = IPV6_MAX_BITLEN; - any = 1; - } - else - ret = str2prefix_ipv6 (prefix, (struct prefix_ipv6 *) &p); - + if (pe->flags & PREFIX_ANY) + prefix_str = "::/0" ; + ret = str2prefix_ipv6 (prefix_str, (struct prefix_ipv6 *)&pe->prefix); if (ret <= 0) { vty_out (vty, "%% Malformed IPv6 prefix%s", VTY_NEWLINE); @@ -732,155 +1253,147 @@ vty_prefix_list_install (struct vty *vty, afi_t afi, const char *name, } #endif /* HAVE_IPV6 */ - /* ge and le check. */ - if (genum && genum <= p.prefixlen) - return vty_invalid_prefix_range (vty, prefix); + /* ge and le number */ + if (ge_str) + { + pe->ge = atoi (ge_str) ; + pe->flags |= PREFIX_GE ; + } - if (lenum && lenum <= p.prefixlen) - return vty_invalid_prefix_range (vty, prefix); + if (le_str) + { + pe->le = atoi (le_str); + pe->flags |= PREFIX_LE ; + } ; - if (lenum && genum > lenum) - return vty_invalid_prefix_range (vty, prefix); + /* Complete the entry we've constructed, and check ge and le. */ + ret = prefix_list_entry_ge_le_check(pe, afi) ; - if (genum && lenum == (afi == AFI_IP ? 32 : 128)) - lenum = 0; + if (ret != CMD_SUCCESS) + vty_out (vty, "%% Invalid prefix range for %s, make sure: " + "len <= ge-value <= le-value%s", + prefix_str, VTY_NEWLINE); - /* Get prefix_list with name. */ - plist = prefix_list_get (afi, name); + return ret ; +} ; - /* Make prefix entry. */ - pentry = prefix_list_entry_make (&p, type, seqnum, lenum, genum, any); - - /* Check same policy. */ - dup = prefix_entry_dup_check (plist, pentry); +/* Install a prefix_list_entry. + * + * Deals with all of ip prefix-list and ipv6 prefix-list commands. + * + * Note: + * + */ - if (dup) +static int +vty_prefix_list_install (struct vty *vty, afi_t afi, const char *name, + const char *seq_str, const char *type_str, + const char *prefix_str, + const char *ge_str, const char *le_str) +{ + struct prefix_master* pm ; + struct prefix_list *plist; + struct prefix_list_entry temp ; + int ret; + + assert_afi_real(afi) ; /* UI stuff should ensure this */ + pm = prefix_master_get(afi) ; + + /* Get prefix_list with name. Make new list if required. */ + plist = prefix_list_get(pm, name, afi) ; + + /* Do the grunt work on the parameters. + * Completely fill in the temp prefix_list_entry structure. + */ + ret = vty_prefix_list_process(vty, &temp, plist, afi, seq_str, type_str, + prefix_str, ge_str, le_str) ; + if (ret != CMD_SUCCESS) + return ret ; + + /* Insert into the list, unless list contains an entry which is the same + * apart from the sequence number. + * If fails, sets the sequence no. in temp to the sequence number found. + */ + ret = prefix_list_entry_insert(plist, &temp); + + if (ret != CMD_SUCCESS) { - prefix_list_entry_free (pentry); vty_out (vty, "%% Insertion failed - prefix-list entry exists:%s", VTY_NEWLINE); - vty_out (vty, " seq %d %s %s", dup->seq, typestr, prefix); - if (! any && genum) - vty_out (vty, " ge %d", genum); - if (! any && lenum) - vty_out (vty, " le %d", lenum); - vty_out (vty, "%s", VTY_NEWLINE); - return CMD_WARNING; + vty_out_indent(vty, 2) ; + vty_prefix_list_value_print(vty, &temp, VTY_NEWLINE, 1, 0) ; } - /* Install new filter to the access_list. */ - prefix_list_entry_add (plist, pentry); - return CMD_SUCCESS; } +/* Remove a prefix_list_entry. */ static int -vty_prefix_list_uninstall (struct vty *vty, afi_t afi, const char *name, - const char *seq, const char *typestr, - const char *prefix, const char *ge, const char *le) +vty_prefix_list_uninstall(struct vty *vty, afi_t afi, const char *name, + const char *seq_str, const char *type_str, + const char *prefix_str, + const char *ge_str, const char *le_str) { - int ret; - enum prefix_list_type type; struct prefix_list *plist; - struct prefix_list_entry *pentry; - struct prefix p; - int seqnum = -1; - int lenum = 0; - int genum = 0; + struct prefix_list_entry temp ; + int ret; - /* Check prefix list name. */ - plist = prefix_list_lookup (afi, name); - if (! plist) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } + assert_afi_real(afi) ; /* UI should guarantee this. */ + + /* Seek prefix_list with name -- error if not found. */ + plist = vty_prefix_list_lookup(vty, afi, name); + if (plist == NULL) + return CMD_WARNING ; /* Only prefix-list name specified, delete the entire prefix-list. */ - if (seq == NULL && typestr == NULL && prefix == NULL && - ge == NULL && le == NULL) + if (seq_str == NULL && type_str == NULL && prefix_str == NULL && + ge_str == NULL && le_str == NULL) { prefix_list_delete (plist); return CMD_SUCCESS; } /* We must have, at a minimum, both the type and prefix here */ - if ((typestr == NULL) || (prefix == NULL)) + if ((type_str == NULL) || (prefix_str == NULL)) { vty_out (vty, "%% Both prefix and type required%s", VTY_NEWLINE); return CMD_WARNING; } - /* Check sequence number. */ - if (seq) - seqnum = atoi (seq); + /* Do the grunt work on the parameters. + * Completely fill in the temp prefix_list_entry structure. + */ + ret = vty_prefix_list_process(vty, &temp, plist, afi, seq_str, type_str, + prefix_str, ge_str, le_str) ; + if (ret != CMD_SUCCESS) + return ret ; - /* ge and le number */ - if (ge) - genum = atoi (ge); - if (le) - lenum = atoi (le); - - /* Check of filter type. */ - if (strncmp ("permit", typestr, 1) == 0) - type = PREFIX_PERMIT; - else if (strncmp ("deny", typestr, 1) == 0) - type = PREFIX_DENY; - else - { - vty_out (vty, "%% prefix type must be permit or deny%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Remove prefix_list_entry if we can. */ + ret = prefix_list_entry_delete (plist, &temp); - /* "any" is special token for matching any IPv4 addresses. */ - if (afi == AFI_IP) - { - if (strncmp ("any", prefix, strlen (prefix)) == 0) - { - ret = str2prefix_ipv4 ("0.0.0.0/0", (struct prefix_ipv4 *) &p); - genum = 0; - lenum = IPV4_MAX_BITLEN; - } - else - ret = str2prefix_ipv4 (prefix, (struct prefix_ipv4 *) &p); + if (ret != CMD_SUCCESS) + vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - if (ret <= 0) - { - vty_out (vty, "%% Malformed IPv4 prefix%s", VTY_NEWLINE); - return CMD_WARNING; - } - } -#ifdef HAVE_IPV6 - else if (afi == AFI_IP6) - { - if (strncmp ("any", prefix, strlen (prefix)) == 0) - { - ret = str2prefix_ipv6 ("::/0", (struct prefix_ipv6 *) &p); - genum = 0; - lenum = IPV6_MAX_BITLEN; - } - else - ret = str2prefix_ipv6 (prefix, (struct prefix_ipv6 *) &p); + return ret; +} - if (ret <= 0) - { - vty_out (vty, "%% Malformed IPv6 prefix%s", VTY_NEWLINE); - return CMD_WARNING; - } - } -#endif /* HAVE_IPV6 */ +static int +vty_prefix_list_desc_set (struct vty *vty, afi_t afi, const char *name, + char* desc) +{ + struct prefix_master* pm ; + struct prefix_list *plist; - /* Lookup prefix entry. */ - pentry = prefix_list_entry_lookup(plist, &p, type, seqnum, lenum, genum); + assert_afi_real(afi) ; /* UI stuff should ensure this */ + pm = prefix_master_get(afi) ; - if (pentry == NULL) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Get prefix_list with name. Make new list if required. */ + plist = prefix_list_get(pm, name, afi) ; - /* Install new filter to the access_list. */ - prefix_list_entry_delete (plist, pentry, 1); + if (plist->desc) + XFREE (MTYPE_TMP, plist->desc) ; /* Discard any existing value */ + + plist->desc = desc ; return CMD_SUCCESS; } @@ -890,21 +1403,15 @@ vty_prefix_list_desc_unset (struct vty *vty, afi_t afi, const char *name) { struct prefix_list *plist; - plist = prefix_list_lookup (afi, name); - if (! plist) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } + plist = vty_prefix_list_lookup(vty, afi, name); + if (plist == NULL) + return CMD_WARNING ; if (plist->desc) - { - XFREE (MTYPE_TMP, plist->desc); - plist->desc = NULL; - } + XFREE (MTYPE_TMP, plist->desc) ; /* sets plist->dec to NULL */ - if (plist->head == NULL && plist->tail == NULL && plist->desc == NULL) - prefix_list_delete (plist); + if (vector_end(&plist->list) == 0) + prefix_list_delete(plist) ; /* delete list if all gone now */ return CMD_SUCCESS; } @@ -916,143 +1423,135 @@ enum display_type detail_display, sequential_display, longer_display, - first_match_display + first_match_display, }; +/* Show given prefix_list */ static void -vty_show_prefix_entry (struct vty *vty, afi_t afi, struct prefix_list *plist, - struct prefix_master *master, enum display_type dtype, +vty_show_prefix_entry (struct vty *vty, struct prefix_list *plist, + struct prefix_master* pm, enum display_type dtype, int seqnum) { - struct prefix_list_entry *pentry; - /* Print the name of the protocol */ if (zlog_default) - vty_out (vty, "%s: ", zlog_proto_names[zlog_default->protocol]); - + vty_out (vty, "%s: ", zlog_get_proto_name(NULL)); + if (dtype == normal_display) { - vty_out (vty, "ip%s prefix-list %s: %d entries%s", - afi == AFI_IP ? "" : "v6", - plist->name, plist->count, VTY_NEWLINE); - if (plist->desc) - vty_out (vty, " Description: %s%s", plist->desc, VTY_NEWLINE); + vty_prefix_list_name_count_print(vty, plist, VTY_NEWLINE) ; + vty_prefix_list_desc_print(vty, plist, 3, VTY_NEWLINE) ; } else if (dtype == summary_display || dtype == detail_display) { - vty_out (vty, "ip%s prefix-list %s:%s", - afi == AFI_IP ? "" : "v6", plist->name, VTY_NEWLINE); + struct prefix_list_entry* p_f = vector_get_first_item(&plist->list) ; + struct prefix_list_entry* p_l = vector_get_last_item(&plist->list) ; + + vty_prefix_list_name_print(vty, plist, ":") ; + vty_out(vty, VTY_NEWLINE) ; - if (plist->desc) - vty_out (vty, " Description: %s%s", plist->desc, VTY_NEWLINE); + vty_prefix_list_desc_print(vty, plist, 3, VTY_NEWLINE) ; vty_out (vty, " count: %d, range entries: %d, sequences: %d - %d%s", - plist->count, plist->rangecount, - plist->head ? plist->head->seq : 0, - plist->tail ? plist->tail->seq : 0, + vector_end(&plist->list), plist->rangecount, + p_f ? p_f->seq : 0, + p_l ? p_l->seq : 0, VTY_NEWLINE); - } + } ; if (dtype != summary_display) { - for (pentry = plist->head; pentry; pentry = pentry->next) + struct prefix_list_entry* pe ; + vector_index i ; + int with_seq = pm->seqnum_flag ; + int with_stats = (dtype == detail_display) + ||(dtype == sequential_display) ; + + for (VECTOR_ITEMS(&plist->list, pe, i)) { - if (dtype == sequential_display && pentry->seq != seqnum) + if ((dtype == sequential_display) && (pe->seq != seqnum)) continue; - - vty_out (vty, " "); - if (master->seqnum) - vty_out (vty, "seq %d ", pentry->seq); - - vty_out (vty, "%s ", prefix_list_type_str (pentry)); - - if (pentry->any) - vty_out (vty, "any"); - else - { - struct prefix *p = &pentry->prefix; - char buf[BUFSIZ]; - - vty_out (vty, "%s/%d", - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); - - if (pentry->ge) - vty_out (vty, " ge %d", pentry->ge); - if (pentry->le) - vty_out (vty, " le %d", pentry->le); - } - - if (dtype == detail_display || dtype == sequential_display) - vty_out (vty, " (hit count: %ld, refcount: %ld)", - pentry->hitcnt, pentry->refcnt); - - vty_out (vty, "%s", VTY_NEWLINE); + vty_out_indent(vty, 3); + vty_prefix_list_value_print(vty, pe, VTY_NEWLINE, + with_seq, with_stats) ; } } } +/* Show given prefix list in given afi, or all prefix lists in given afi. */ + static int vty_show_prefix_list (struct vty *vty, afi_t afi, const char *name, - const char *seq, enum display_type dtype) + const char *seq_str, enum display_type dtype) { struct prefix_list *plist; - struct prefix_master *master; - int seqnum = 0; + struct prefix_master *pm; + int seq = 0; - master = prefix_master_get (afi); - if (master == NULL) + pm = prefix_master_get(afi) ; + if (pm == NULL) return CMD_WARNING; - if (seq) - seqnum = atoi (seq); + if (seq_str) + seq = atoi (seq_str); if (name) { - plist = prefix_list_lookup (afi, name); - if (! plist) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } - vty_show_prefix_entry (vty, afi, plist, master, dtype, seqnum); + /* Note that asking after an undefined prefix_list is an error. */ + /* Does not mention references to an undefined prefix_list. */ + plist = vty_prefix_list_lookup(vty, afi, name); + if (plist == NULL) + return CMD_WARNING; + vty_show_prefix_entry (vty, plist, pm, dtype, seq); } else { + vector extract ; + vector_index i ; + struct symbol* sym ; + if (dtype == detail_display || dtype == summary_display) { - if (master->recent) + if (pm->recent) vty_out (vty, "Prefix-list with the last deletion/insertion: %s%s", - master->recent->name, VTY_NEWLINE); + (const char*)symbol_get_name(pm->recent->sym), VTY_NEWLINE) ; } - for (plist = master->num.head; plist; plist = plist->next) - vty_show_prefix_entry (vty, afi, plist, master, dtype, seqnum); + /* Extract a vector of all prefix_list symbols, in name order. */ + extract = symbol_table_extract(&pm->table, NULL, NULL, 0, + symbol_mixed_name_cmp) ; + + for (VECTOR_ITEMS(extract, sym, i)) + { + plist = symbol_get_value(sym) ; + if (plist != NULL) + vty_show_prefix_entry(vty, plist, pm, dtype, seq); + else + vty_prefix_list_undefined_print(vty, afi, sym, VTY_NEWLINE) ; + } - for (plist = master->str.head; plist; plist = plist->next) - vty_show_prefix_entry (vty, afi, plist, master, dtype, seqnum); + vector_free(extract) ; /* throw away temporary vector */ } return CMD_SUCCESS; } static int -vty_show_prefix_list_prefix (struct vty *vty, afi_t afi, const char *name, +vty_show_prefix_list_prefix (struct vty *vty, afi_t afi, const char *name, const char *prefix, enum display_type type) { struct prefix_list *plist; - struct prefix_list_entry *pentry; + struct prefix_list_entry* pe ; + vector_index i ; struct prefix p; int ret; int match; + int with_stats ; - plist = prefix_list_lookup (afi, name); - if (! plist) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Error if cannot find prefix list. */ + plist = vty_prefix_list_lookup(vty, afi, name); + if (plist == NULL) + return CMD_WARNING; ret = str2prefix (prefix, &p); if (ret <= 0) @@ -1061,88 +1560,71 @@ vty_show_prefix_list_prefix (struct vty *vty, afi_t afi, const char *name, return CMD_WARNING; } - for (pentry = plist->head; pentry; pentry = pentry->next) + with_stats = (type == normal_display) || (type == first_match_display) ; + + for (VECTOR_ITEMS(&plist->list, pe, i)) { match = 0; - if (type == normal_display || type == first_match_display) - if (prefix_same (&p, &pentry->prefix)) - match = 1; - - if (type == longer_display) - if (prefix_match (&p, &pentry->prefix)) - match = 1; + if ((type == normal_display || type == first_match_display)) + match = prefix_same(&p, &pe->prefix) ; + else if (type == longer_display) + match = (prefix_match (&p, &pe->prefix)) ; if (match) { - vty_out (vty, " seq %d %s ", - pentry->seq, - prefix_list_type_str (pentry)); - - if (pentry->any) - vty_out (vty, "any"); - else - { - struct prefix *p = &pentry->prefix; - char buf[BUFSIZ]; - - vty_out (vty, "%s/%d", - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); - - if (pentry->ge) - vty_out (vty, " ge %d", pentry->ge); - if (pentry->le) - vty_out (vty, " le %d", pentry->le); - } - - if (type == normal_display || type == first_match_display) - vty_out (vty, " (hit count: %ld, refcount: %ld)", - pentry->hitcnt, pentry->refcnt); - - vty_out (vty, "%s", VTY_NEWLINE); + vty_out_indent(vty, 3); + vty_prefix_list_value_print(vty, pe, VTY_NEWLINE, 1, with_stats) ; if (type == first_match_display) - return CMD_SUCCESS; + break ; } } return CMD_SUCCESS; } +/* Clear hit counters in all prefix_list_entries: + * + * a) in all prefix_lists -- name NULL + * b) in given prefix list -- prefix NULL + * c) that match given prefix, in given prefix_list + */ static int -vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name, +vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name, const char *prefix) { - struct prefix_master *master; + struct prefix_master *pm; struct prefix_list *plist; - struct prefix_list_entry *pentry; int ret; struct prefix p; + struct prefix_list_entry* pe ; + vector_index i ; - master = prefix_master_get (afi); - if (master == NULL) + pm = prefix_master_get (afi); + if (pm == NULL) return CMD_WARNING; - if (name == NULL && prefix == NULL) + if (name == NULL) { - for (plist = master->num.head; plist; plist = plist->next) - for (pentry = plist->head; pentry; pentry = pentry->next) - pentry->hitcnt = 0; - - for (plist = master->str.head; plist; plist = plist->next) - for (pentry = plist->head; pentry; pentry = pentry->next) - pentry->hitcnt = 0; + struct symbol_walker walker ; + symbol_walk_start(&pm->table, &walker) ; + while ((plist = symbol_get_value(symbol_walk_next(&walker)))) + { + if (plist == NULL) + continue ; + + for (VECTOR_ITEMS(&plist->list, pe, i)) + pe->hitcnt = 0 ; + } ; } else { - plist = prefix_list_lookup (afi, name); - if (! plist) - { - vty_out (vty, "%% Can't find specified prefix-list%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Error if cannot find prefix list. */ + plist = vty_prefix_list_lookup(vty, afi, name); + if (plist == NULL) + return CMD_WARNING; - if (prefix) + if (prefix != NULL) { ret = str2prefix (prefix, &p); if (ret <= 0) @@ -1152,20 +1634,13 @@ vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name, } } - for (pentry = plist->head; pentry; pentry = pentry->next) - { - if (prefix) - { - if (prefix_match (&pentry->prefix, &p)) - pentry->hitcnt = 0; - } - else - pentry->hitcnt = 0; - } - } - return CMD_SUCCESS; + for (VECTOR_ITEMS(&plist->list, pe, i)) + if ((prefix == NULL) || prefix_match(&pe->prefix, &p)) + pe->hitcnt = 0; + } ; +return CMD_SUCCESS; } - + DEFUN (ip_prefix_list, ip_prefix_list_cmd, "ip prefix-list WORD (deny|permit) (A.B.C.D/M|any)", @@ -1177,7 +1652,7 @@ DEFUN (ip_prefix_list, "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n" "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n") { - return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, + return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, argv[1], argv[2], NULL, NULL); } @@ -1193,7 +1668,7 @@ DEFUN (ip_prefix_list_ge, "Minimum prefix length to be matched\n" "Minimum prefix length\n") { - return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, argv[1], + return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, argv[1], argv[2], argv[3], NULL); } @@ -1211,7 +1686,7 @@ DEFUN (ip_prefix_list_ge_le, "Maximum prefix length to be matched\n" "Maximum prefix length\n") { - return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, argv[1], + return vty_prefix_list_install (vty, AFI_IP, argv[0], NULL, argv[1], argv[2], argv[3], argv[4]); } @@ -1547,7 +2022,7 @@ DEFUN (ip_prefix_list_sequence_number, PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { - prefix_master_ipv4.seqnum = 1; + prefix_master_ipv4.seqnum_flag = 1; return CMD_SUCCESS; } @@ -1559,7 +2034,7 @@ DEFUN (no_ip_prefix_list_sequence_number, PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { - prefix_master_ipv4.seqnum = 0; + prefix_master_ipv4.seqnum_flag = 0; return CMD_SUCCESS; } @@ -1572,19 +2047,9 @@ DEFUN (ip_prefix_list_description, "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { - struct prefix_list *plist; - - plist = prefix_list_get (AFI_IP, argv[0]); - - if (plist->desc) - { - XFREE (MTYPE_TMP, plist->desc); - plist->desc = NULL; - } - plist->desc = argv_concat(argv, argc, 1); - - return CMD_SUCCESS; -} + return vty_prefix_list_desc_set (vty, AFI_IP, argv[0], + argv_concat(argv, argc, 1)); +} ; DEFUN (no_ip_prefix_list_description, no_ip_prefix_list_description_cmd, @@ -1759,7 +2224,7 @@ DEFUN (clear_ip_prefix_list_name_prefix, { return vty_clear_prefix_list (vty, AFI_IP, argv[0], argv[1]); } - + #ifdef HAVE_IPV6 DEFUN (ipv6_prefix_list, ipv6_prefix_list_cmd, @@ -1772,7 +2237,7 @@ DEFUN (ipv6_prefix_list, "IPv6 prefix <network>/<length>, e.g., 3ffe::/16\n" "Any prefix match. Same as \"::0/0 le 128\"\n") { - return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, + return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, argv[1], argv[2], NULL, NULL); } @@ -1788,7 +2253,7 @@ DEFUN (ipv6_prefix_list_ge, "Minimum prefix length to be matched\n" "Minimum prefix length\n") { - return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, argv[1], + return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, argv[1], argv[2], argv[3], NULL); } @@ -1807,7 +2272,7 @@ DEFUN (ipv6_prefix_list_ge_le, "Maximum prefix length\n") { - return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, argv[1], + return vty_prefix_list_install (vty, AFI_IP6, argv[0], NULL, argv[1], argv[2], argv[3], argv[4]); } @@ -2143,7 +2608,7 @@ DEFUN (ipv6_prefix_list_sequence_number, PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { - prefix_master_ipv6.seqnum = 1; + prefix_master_ipv6.seqnum_flag = 1; return CMD_SUCCESS; } @@ -2155,7 +2620,7 @@ DEFUN (no_ipv6_prefix_list_sequence_number, PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { - prefix_master_ipv6.seqnum = 0; + prefix_master_ipv6.seqnum_flag = 0; return CMD_SUCCESS; } @@ -2168,19 +2633,9 @@ DEFUN (ipv6_prefix_list_description, "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { - struct prefix_list *plist; - - plist = prefix_list_get (AFI_IP6, argv[0]); - - if (plist->desc) - { - XFREE (MTYPE_TMP, plist->desc); - plist->desc = NULL; - } - plist->desc = argv_concat(argv, argc, 1); - - return CMD_SUCCESS; -} + return vty_prefix_list_desc_set (vty, AFI_IP6, argv[0], + argv_concat(argv, argc, 1)); +} DEFUN (no_ipv6_prefix_list_description, no_ipv6_prefix_list_description_cmd, @@ -2355,190 +2810,162 @@ DEFUN (clear_ipv6_prefix_list_name_prefix, return vty_clear_prefix_list (vty, AFI_IP6, argv[0], argv[1]); } #endif /* HAVE_IPV6 */ - + /* Configuration write function. */ static int config_write_prefix_afi (afi_t afi, struct vty *vty) { struct prefix_list *plist; - struct prefix_list_entry *pentry; - struct prefix_master *master; + struct prefix_list_entry *pe; + struct prefix_master *pm; int write = 0; + vector extract ; + vector_index i, ipe ; + struct symbol* sym ; - master = prefix_master_get (afi); - if (master == NULL) + pm = prefix_master_get (afi); + if (pm == NULL) return 0; - if (! master->seqnum) + if (! pm->seqnum_flag) { - vty_out (vty, "no ip%s prefix-list sequence-number%s", + vty_out (vty, "no ip%s prefix-list sequence-number%s", afi == AFI_IP ? "" : "v6", VTY_NEWLINE); vty_out (vty, "!%s", VTY_NEWLINE); } - for (plist = master->num.head; plist; plist = plist->next) + /* Extract a vector of all prefix_list symbols, in name order. */ + extract = symbol_table_extract(&pm->table, NULL, NULL, 0, + symbol_mixed_name_cmp) ; + for (VECTOR_ITEMS(extract, sym, i)) { - if (plist->desc) - { - vty_out (vty, "ip%s prefix-list %s description %s%s", - afi == AFI_IP ? "" : "v6", - plist->name, plist->desc, VTY_NEWLINE); - write++; - } - - for (pentry = plist->head; pentry; pentry = pentry->next) + plist = symbol_get_value(sym) ; + if (plist) { - vty_out (vty, "ip%s prefix-list %s ", - afi == AFI_IP ? "" : "v6", - plist->name); - - if (master->seqnum) - vty_out (vty, "seq %d ", pentry->seq); - - vty_out (vty, "%s ", prefix_list_type_str (pentry)); - - if (pentry->any) - vty_out (vty, "any"); - else + if (plist->desc) { - struct prefix *p = &pentry->prefix; - char buf[BUFSIZ]; - - vty_out (vty, "%s/%d", - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); - - if (pentry->ge) - vty_out (vty, " ge %d", pentry->ge); - if (pentry->le) - vty_out (vty, " le %d", pentry->le); + vty_prefix_list_name_print(vty, plist, "") ; + vty_out (vty, " description %s%s", plist->desc, VTY_NEWLINE) ; + write++ ; } - vty_out (vty, "%s", VTY_NEWLINE); - write++; - } - /* vty_out (vty, "!%s", VTY_NEWLINE); */ - } - for (plist = master->str.head; plist; plist = plist->next) - { - if (plist->desc) - { - vty_out (vty, "ip%s prefix-list %s description %s%s", - afi == AFI_IP ? "" : "v6", - plist->name, plist->desc, VTY_NEWLINE); - write++; + for (VECTOR_ITEMS(&plist->list, pe, ipe)) + { + vty_prefix_list_name_print(vty, plist, " ") ; + vty_prefix_list_value_print(vty, pe, VTY_NEWLINE, + pm->seqnum_flag, 0) ; + write++ ; + } } - - for (pentry = plist->head; pentry; pentry = pentry->next) + else { - vty_out (vty, "ip%s prefix-list %s ", - afi == AFI_IP ? "" : "v6", - plist->name); - - if (master->seqnum) - vty_out (vty, "seq %d ", pentry->seq); - - vty_out (vty, "%s", prefix_list_type_str (pentry)); - - if (pentry->any) - vty_out (vty, " any"); - else - { - struct prefix *p = &pentry->prefix; - char buf[BUFSIZ]; + vty_out(vty, "!! ") ; + vty_prefix_list_undefined_print(vty, afi, sym, VTY_NEWLINE) ; + write++ ; + } ; + } ; - vty_out (vty, " %s/%d", - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); + vector_free(extract) ; /* discard temporary vector */ - if (pentry->ge) - vty_out (vty, " ge %d", pentry->ge); - if (pentry->le) - vty_out (vty, " le %d", pentry->le); - } - vty_out (vty, "%s", VTY_NEWLINE); - write++; - } - } - return write; } struct stream * -prefix_bgp_orf_entry (struct stream *s, struct prefix_list *plist, +prefix_bgp_orf_entry (struct stream *s, prefix_list_ref ref, u_char init_flag, u_char permit_flag, u_char deny_flag) { - struct prefix_list_entry *pentry; + struct prefix_list_entry *pe; + vector_index i ; + + struct prefix_list *plist = prefix_list_ref_plist(ref) ; if (! plist) return s; - for (pentry = plist->head; pentry; pentry = pentry->next) + for (VECTOR_ITEMS(&plist->list, pe, i)) { - u_char flag = init_flag; - struct prefix *p = &pentry->prefix; - - flag |= (pentry->type == PREFIX_PERMIT ? - permit_flag : deny_flag); - stream_putc (s, flag); - stream_putl (s, (u_int32_t)pentry->seq); - stream_putc (s, (u_char)pentry->ge); - stream_putc (s, (u_char)pentry->le); - stream_put_prefix (s, p); + stream_putc (s, init_flag | (pe->type == PREFIX_PERMIT ? permit_flag + : deny_flag)); + stream_putl (s, (u_int32_t)pe->seq); + stream_putc (s, (u_char)pe->ge); + stream_putc (s, (u_char)pe->le); + stream_put_prefix (s, &pe->prefix); } return s; } +/* Get the i'th BGP ORF prefix from the given list. + * return 1 - got ORF prefix. + * return 0 - no such entry + */ +int +prefix_bgp_orf_get(struct prefix_list *plist, vector_index i, + struct orf_prefix *orfpe, enum prefix_list_type *pe_type) +{ + struct prefix_list_entry *pe = NULL; + + if (!plist || i >= plist->list.end) + return 0; + + pe = vector_slot(&plist->list, i); + orfpe->seq = pe->seq; + orfpe->ge = pe->ge; + orfpe->le = pe->le; + orfpe->p = pe->prefix; + + *pe_type = pe->type; + + return 1; +} + +/* Set or Unset a BGP ORF entry. */ int prefix_bgp_orf_set (char *name, afi_t afi, struct orf_prefix *orfp, int permit, int set) { - struct prefix_list *plist; - struct prefix_list_entry *pentry; - - /* ge and le value check */ - if (orfp->ge && orfp->ge <= orfp->p.prefixlen) - return CMD_WARNING; - if (orfp->le && orfp->le <= orfp->p.prefixlen) - return CMD_WARNING; - if (orfp->le && orfp->ge > orfp->le) - return CMD_WARNING; + struct prefix_list *plist ; + struct prefix_list_entry temp ; + int ret ; - if (orfp->ge && orfp->le == (afi == AFI_IP ? 32 : 128)) - orfp->le = 0; + assert_afi_real(afi) ; - plist = prefix_list_get (AFI_ORF_PREFIX, name); - if (! plist) - return CMD_WARNING; + /* Transfer the values from the orf_prefix */ + prefix_list_entry_init(&temp) ; - if (set) + temp.type = permit ? PREFIX_PERMIT : PREFIX_DENY ; + temp.prefix = orfp->p ; + temp.seq = orfp->seq ; /* NB: U32 and may be zero */ + if (orfp->ge) + { + temp.flags |= PREFIX_GE ; + temp.ge = orfp->ge ; + } + if (orfp->le) { - pentry = prefix_list_entry_make (&orfp->p, - (permit ? PREFIX_PERMIT : PREFIX_DENY), - orfp->seq, orfp->le, orfp->ge, 0); + temp.flags |= PREFIX_LE ; + temp.le = orfp->le ; + } - if (prefix_entry_dup_check (plist, pentry)) - { - prefix_list_entry_free (pentry); - return CMD_WARNING; - } + /* Make sure ge & le are acceptable and set as required */ + ret = prefix_list_entry_ge_le_check(&temp, afi) ; + if (ret != CMD_SUCCESS) + return ret ; - prefix_list_entry_add (plist, pentry); + /* Now insert or delete */ + if (set) + { + plist = prefix_list_get(&prefix_master_orf, name, afi); + return prefix_list_entry_insert(plist, &temp) ; } else { - pentry = prefix_list_entry_lookup (plist, &orfp->p, - (permit ? PREFIX_PERMIT : PREFIX_DENY), - orfp->seq, orfp->le, orfp->ge); + plist = prefix_list_seek(&prefix_master_orf, name) ; + if (plist == NULL) + return CMD_WARNING ; - if (! pentry) - return CMD_WARNING; - - prefix_list_entry_delete (plist, pentry, 1); + return prefix_list_entry_delete(plist, &temp) ; } - - return CMD_SUCCESS; } void @@ -2555,70 +2982,33 @@ prefix_bgp_orf_remove_all (char *name) int prefix_bgp_show_prefix_list (struct vty *vty, afi_t afi, char *name) { - struct prefix_list *plist; - struct prefix_list_entry *pentry; + struct prefix_list *plist ; plist = prefix_list_lookup (AFI_ORF_PREFIX, name); if (! plist) return 0; - if (! vty) - return plist->count; - - vty_out (vty, "ip%s prefix-list %s: %d entries%s", - afi == AFI_IP ? "" : "v6", - plist->name, plist->count, VTY_NEWLINE); - - for (pentry = plist->head; pentry; pentry = pentry->next) + if (vty) { - struct prefix *p = &pentry->prefix; - char buf[BUFSIZ]; - - vty_out (vty, " seq %d %s %s/%d", pentry->seq, - prefix_list_type_str (pentry), - inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), - p->prefixlen); + struct prefix_list_entry* pe ; + vector_index i ; - if (pentry->ge) - vty_out (vty, " ge %d", pentry->ge); - if (pentry->le) - vty_out (vty, " le %d", pentry->le); + vty_prefix_list_name_count_print(vty, plist, VTY_NEWLINE) ; - vty_out (vty, "%s", VTY_NEWLINE); + for (VECTOR_ITEMS(&plist->list, pe, i)) + { + vty_out_indent(vty, 3) ; + vty_prefix_list_value_print(vty, pe, VTY_NEWLINE, 1, 1) ; + } } - return plist->count; + + return vector_end(&plist->list); } static void prefix_list_reset_orf (void) { - struct prefix_list *plist; - struct prefix_list *next; - struct prefix_master *master; - - master = prefix_master_get (AFI_ORF_PREFIX); - if (master == NULL) - return; - - for (plist = master->num.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - for (plist = master->str.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - - assert (master->num.head == NULL); - assert (master->num.tail == NULL); - - assert (master->str.head == NULL); - assert (master->str.tail == NULL); - - master->seqnum = 1; - master->recent = NULL; + prefix_master_reset(&prefix_master_orf) ; } @@ -2639,38 +3029,14 @@ config_write_prefix_ipv4 (struct vty *vty) static void prefix_list_reset_ipv4 (void) { - struct prefix_list *plist; - struct prefix_list *next; - struct prefix_master *master; - - master = prefix_master_get (AFI_IP); - if (master == NULL) - return; - - for (plist = master->num.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - for (plist = master->str.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - - assert (master->num.head == NULL); - assert (master->num.tail == NULL); - - assert (master->str.head == NULL); - assert (master->str.tail == NULL); - - master->seqnum = 1; - master->recent = NULL; + prefix_master_reset(&prefix_master_ipv4) ; } static void prefix_list_init_ipv4 (void) { + prefix_master_init(&prefix_master_ipv4) ; + install_node (&prefix_node, config_write_prefix_ipv4); install_element (CONFIG_NODE, &ip_prefix_list_cmd); @@ -2734,10 +3100,10 @@ prefix_list_init_ipv4 (void) /* Prefix-list node. */ static struct cmd_node prefix_ipv6_node = { - PREFIX_IPV6_NODE, - "", /* Prefix list has no interface. */ - 1 -}; + PREFIX_IPV6_NODE, + "", /* Prefix list has no interface. */ + 1 + }; static int config_write_prefix_ipv6 (struct vty *vty) @@ -2748,38 +3114,16 @@ config_write_prefix_ipv6 (struct vty *vty) static void prefix_list_reset_ipv6 (void) { - struct prefix_list *plist; - struct prefix_list *next; - struct prefix_master *master; - - master = prefix_master_get (AFI_IP6); - if (master == NULL) - return; - - for (plist = master->num.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - for (plist = master->str.head; plist; plist = next) - { - next = plist->next; - prefix_list_delete (plist); - } - - assert (master->num.head == NULL); - assert (master->num.tail == NULL); - - assert (master->str.head == NULL); - assert (master->str.tail == NULL); - - master->seqnum = 1; - master->recent = NULL; -} +#ifdef HAVE_IPV6 + prefix_master_reset(&prefix_master_ipv6) ; +#endif +} ; static void prefix_list_init_ipv6 (void) { + prefix_master_init(&prefix_master_ipv6) ; + install_node (&prefix_ipv6_node, config_write_prefix_ipv6); install_element (CONFIG_NODE, &ipv6_prefix_list_cmd); @@ -2847,6 +3191,7 @@ prefix_list_init () #ifdef HAVE_IPV6 prefix_list_init_ipv6 (); #endif /* HAVE_IPV6 */ + prefix_master_init(&prefix_master_orf) ; } void diff --git a/lib/plist.h b/lib/plist.h index fb3168a6..4f383417 100644 --- a/lib/plist.h +++ b/lib/plist.h @@ -23,38 +23,22 @@ #ifndef _QUAGGA_PLIST_H #define _QUAGGA_PLIST_H +#include "prefix.h" +#include "symtab.h" +#include "vector.h" +#include "vty.h" + #define AFI_ORF_PREFIX 65535 -enum prefix_list_type +enum prefix_list_type { PREFIX_DENY, PREFIX_PERMIT, }; -enum prefix_name_type -{ - PREFIX_TYPE_STRING, - PREFIX_TYPE_NUMBER -}; - -struct prefix_list -{ - char *name; - char *desc; - - struct prefix_master *master; - - enum prefix_name_type type; +struct prefix_list ; - int count; - int rangecount; - - struct prefix_list_entry *head; - struct prefix_list_entry *tail; - - struct prefix_list *next; - struct prefix_list *prev; -}; +typedef struct symbol_ref* prefix_list_ref ; struct orf_prefix { @@ -73,11 +57,25 @@ extern void prefix_list_delete_hook (void (*func) (struct prefix_list *)); extern struct prefix_list *prefix_list_lookup (afi_t, const char *); extern enum prefix_list_type prefix_list_apply (struct prefix_list *, void *); +extern const char* prefix_list_get_name(struct prefix_list* plist) ; + extern struct stream * prefix_bgp_orf_entry (struct stream *, - struct prefix_list *, + prefix_list_ref ref, u_char, u_char, u_char); +extern int prefix_bgp_orf_get(struct prefix_list *plist, vector_index i, + struct orf_prefix *orfpe, enum prefix_list_type *pe_type); extern int prefix_bgp_orf_set (char *, afi_t, struct orf_prefix *, int, int); extern void prefix_bgp_orf_remove_all (char *); extern int prefix_bgp_show_prefix_list (struct vty *, afi_t, char *); +extern prefix_list_ref prefix_list_set_ref(prefix_list_ref* p_ref, afi_t afi, + const char* name) ; +extern prefix_list_ref prefix_list_copy_ref(prefix_list_ref* p_dst, + prefix_list_ref src) ; +extern prefix_list_ref prefix_list_unset_ref(prefix_list_ref* p_ref) ; + +extern const char* prefix_list_ref_name(prefix_list_ref ref) ; +extern void* prefix_list_ref_ident(prefix_list_ref ref) ; +extern struct prefix_list* prefix_list_ref_plist(prefix_list_ref ref) ; + #endif /* _QUAGGA_PLIST_H */ diff --git a/lib/prefix.c b/lib/prefix.c index 61a278ca..867c86a3 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -27,10 +27,10 @@ #include "sockunion.h" #include "memory.h" #include "log.h" - + /* Maskbit. */ -static const u_char maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, - 0xf8, 0xfc, 0xfe, 0xff}; +static const u_char maskbit[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff }; /* Number of bits in prefix type. */ #ifndef PNBBY @@ -86,7 +86,7 @@ prefix_match (const struct prefix *n, const struct prefix *p) if (shift) if (maskbit[shift] & (np[offset] ^ pp[offset])) return 0; - + while (offset--) if (np[offset] != pp[offset]) return 0; @@ -119,7 +119,7 @@ prefix_copy (struct prefix *dest, const struct prefix *src) } } -/* +/* * Return 1 if the address/netmask contained in the prefix structure * is the same, and else return 0. For this routine, 'same' requires * that not only the prefix length and the network part be the same, @@ -181,6 +181,46 @@ prefix_cmp (const struct prefix *p1, const struct prefix *p2) return 0; } +/* + * Count the number of common bits in 2 prefixes. The prefix length is + * ignored for this function; the whole prefix is compared. If the prefix + * address families don't match, return -1; otherwise the return value is + * in range 0 ... maximum prefix length for the address family. + */ +int +prefix_common_bits (const struct prefix *p1, const struct prefix *p2) +{ + int pos, bit; + int length = 0; + u_char xor; + + /* Set both prefix's head pointer. */ + const u_char *pp1 = (const u_char *)&p1->u.prefix; + const u_char *pp2 = (const u_char *)&p2->u.prefix; + + if (p1->family == AF_INET) + length = IPV4_MAX_BYTELEN; +#ifdef HAVE_IPV6 + if (p1->family == AF_INET6) + length = IPV6_MAX_BYTELEN; +#endif + if (p1->family != p2->family || !length) + return -1; + + for (pos = 0; pos < length; pos++) + if (pp1[pos] != pp2[pos]) + break; + if (pos == length) + return pos * 8; + + xor = pp1[pos] ^ pp2[pos]; + for (bit = 0; bit < 8; bit++) + if (xor & (1 << (7 - bit))) + break; + + return pos * 8 + bit; +} + /* Return prefix family type string. */ const char * prefix_family_str (const struct prefix *p) @@ -215,50 +255,58 @@ prefix_ipv4_free (struct prefix_ipv4 *p) prefix_free((struct prefix *)p); } -/* When string format is invalid return 0. */ +/* When string format is valid return 1 otherwise return 0. + * + * inet_aton() returns 1 <=> valid, 0 <=> invalid. + * inet_pton() returns 1 <=> valid, 0 <=> invalid, -1 <=> error + * where error => unknown address family argument + * + * Callers of this function vary in how they test the return: + * + * 1) some treat non-0 as OK and 0 as invalid -- consistent with inet_aton(). + * + * 2) some treat > 0 as OK and <= 0 as invalid -- consistent with inet_pton(). + * + * Since this function returns 1 <=> valid and 0 <=> invalid, both the above + * work. + */ int str2prefix_ipv4 (const char *str, struct prefix_ipv4 *p) { - int ret; - int plen; - char *pnt; - char *cp; + char* pnt ; + char* cp ; + int ret ; + unsigned plen ; - /* Find slash inside string. */ pnt = strchr (str, '/'); - /* String doesn't contail slash. */ - if (pnt == NULL) + if (pnt == NULL) { - /* Convert string to prefix. */ + /* No / => simple address */ + plen = IPV4_MAX_BITLEN; ret = inet_aton (str, &p->prefix); - if (ret == 0) - return 0; - - /* If address doesn't contain slash we assume it host address. */ - p->family = AF_INET; - p->prefixlen = IPV4_MAX_BITLEN; - - return ret; } else { + /* With / => prefix */ + plen = (unsigned)atoi (pnt + 1) ; + if (plen > IPV4_MAX_PREFIXLEN) + return 0; + cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1); strncpy (cp, str, pnt - str); *(cp + (pnt - str)) = '\0'; ret = inet_aton (cp, &p->prefix); XFREE (MTYPE_TMP, cp); + } - /* Get prefix length. */ - plen = (u_char) atoi (++pnt); - if (plen > IPV4_MAX_PREFIXLEN) - return 0; + if (ret <= 0) /* should not return < 0, but it would not be valid ! */ + return 0; - p->family = AF_INET; - p->prefixlen = plen; - } + p->family = AF_INET; + p->prefixlen = plen; - return ret; + return 1 ; } /* Convert masklen into IP address's netmask. */ @@ -274,7 +322,7 @@ masklen2ip (int masklen, struct in_addr *netmask) offset = masklen / 8; bit = masklen % 8; - + while (offset--) *pnt++ = 0xff; @@ -300,7 +348,7 @@ ip_masklen (struct in_addr netmask) { len+= 8; pnt++; - } + } if (pnt < end) { @@ -343,7 +391,7 @@ prefix_ipv4_any (const struct prefix_ipv4 *p) { return (p->prefix.s_addr == 0 && p->prefixlen == 0); } - + #ifdef HAVE_IPV6 /* Allocate a new ip version 6 route */ @@ -366,43 +414,61 @@ prefix_ipv6_free (struct prefix_ipv6 *p) prefix_free((struct prefix *)p); } -/* If given string is valid return pin6 else return NULL */ +/* If given string is valid IPv6 address or prefix return 1 else return 0 + * + * inet_aton() returns 1 <=> valid, 0 <=> invalid. + * inet_pton() returns 1 <=> valid, 0 <=> invalid, -1 <=> error + * where error => unknown address family argument + * + * Any error returned by inet_pton() is reported as an invalid address or + * prefix. So best not to call this if IPv6 is not supported. + * + * Callers of this function vary in how they test the return: + * + * 1) some treat non-0 as OK and 0 as invalid -- consistent with inet_aton(). + * + * 2) some treat > 0 as OK and <= 0 as invalid -- consistent with inet_pton(). + * + * Since this function returns 1 <=> valid and 0 <=> invalid, both the above + * work. + */ int str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p) { - char *pnt; - char *cp; - int ret; + char* pnt ; + char* cp ; + int ret ; + unsigned plen ; pnt = strchr (str, '/'); - /* If string doesn't contain `/' treat it as host route. */ - if (pnt == NULL) + if (pnt == NULL) { - ret = inet_pton (AF_INET6, str, &p->prefix); - if (ret == 0) - return 0; - p->prefixlen = IPV6_MAX_BITLEN; + /* No / => simple address */ + plen = IPV6_MAX_BITLEN; + ret = inet_pton (AF_INET6, str, &p->prefix); } - else + else { - int plen; + /* With / => prefix */ + plen = (unsigned) atoi (pnt + 1) ; + if (plen > IPV6_MAX_PREFIXLEN) + return 0 ; - cp = XMALLOC (0, (pnt - str) + 1); + cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1); strncpy (cp, str, pnt - str); *(cp + (pnt - str)) = '\0'; ret = inet_pton (AF_INET6, cp, &p->prefix); - free (cp); - if (ret == 0) - return 0; - plen = (u_char) atoi (++pnt); - if (plen > 128) - return 0; - p->prefixlen = plen; + XFREE (MTYPE_TMP, cp); } - p->family = AF_INET6; - return ret; + if (ret <= 0) + return 0 ; + + p->family = AF_INET6; + p->prefixlen = plen; + + return 1 ; } /* Convert struct in6_addr netmask into integer. @@ -413,19 +479,19 @@ ip6_masklen (struct in6_addr netmask) int len = 0; unsigned char val; unsigned char *pnt; - + pnt = (unsigned char *) & netmask; - while ((*pnt == 0xff) && len < 128) + while ((*pnt == 0xff) && len < 128) { len += 8; pnt++; - } - - if (len < 128) + } + + if (len < 128) { val = *pnt; - while (val) + while (val) { len++; val <<= 1; @@ -570,10 +636,23 @@ sockunion2hostprefix (const union sockunion *su) return NULL; } +void +prefix2sockunion (const struct prefix *p, union sockunion *su) { + memset (su, 0, sizeof (*su)); + + su->sa.sa_family = p->family; + if (p->family == AF_INET) + su->sin.sin_addr = p->u.prefix4; +#ifdef HAVE_IPV6 + if (p->family == AF_INET6) + memcpy (&su->sin6.sin6_addr, &p->u.prefix6, sizeof (struct in6_addr)); +#endif /* HAVE_IPV6 */ +} + int prefix_blen (const struct prefix *p) { - switch (p->family) + switch (p->family) { case AF_INET: return IPV4_MAX_BYTELEN; @@ -587,25 +666,28 @@ prefix_blen (const struct prefix *p) return 0; } -/* Generic function for conversion string to struct prefix. */ +/* Generic function for conversion string to struct prefix. + * + * Accepts addresses without '/' and prefixes with. + * + * Returns 1 <=> valid IPv4 or (if HAVE_IPV6) IPv6 address or prefix. + * 0 <=> not a a valid address or prefix + */ int str2prefix (const char *str, struct prefix *p) { int ret; - /* First we try to convert string to struct prefix_ipv4. */ + /* First we try to convert string to struct prefix_ipv4. */ ret = str2prefix_ipv4 (str, (struct prefix_ipv4 *) p); - if (ret) - return ret; #ifdef HAVE_IPV6 - /* Next we try to convert string to struct prefix_ipv6. */ - ret = str2prefix_ipv6 (str, (struct prefix_ipv6 *) p); - if (ret) - return ret; + /* If not IPv4, try to convert to struct prefix_ipv6. */ + if (ret == 0) + ret = str2prefix_ipv6 (str, (struct prefix_ipv6 *) p); #endif /* HAVE_IPV6 */ - return 0; + return ret; } int @@ -651,22 +733,22 @@ void apply_classful_mask_ipv4 (struct prefix_ipv4 *p) { u_int32_t destination; - + destination = ntohl (p->prefix.s_addr); - + if (p->prefixlen == IPV4_MAX_PREFIXLEN); /* do nothing for host routes */ - else if (IN_CLASSC (destination)) + else if (IN_CLASSC (destination)) { p->prefixlen=24; apply_mask_ipv4(p); } - else if (IN_CLASSB(destination)) + else if (IN_CLASSB(destination)) { p->prefixlen=16; apply_mask_ipv4(p); } - else + else { p->prefixlen=8; apply_mask_ipv4(p); @@ -695,7 +777,7 @@ ipv4_broadcast_addr (in_addr_t hostaddr, int masklen) (hostaddr ^ ~mask.s_addr); } -/* Utility function to convert ipv4 netmask to prefixes +/* Utility function to convert ipv4 netmask to prefixes ex.) "1.1.0.0" "255.255.0.0" => "1.1.0.0/16" ex.) "1.0.0.0" NULL => "1.0.0.0/8" */ int @@ -720,7 +802,7 @@ netmask_str2prefix_str (const char *net_str, const char *mask_str, prefixlen = ip_masklen (mask); } - else + else { destination = ntohl (network.s_addr); @@ -752,3 +834,4 @@ inet6_ntoa (struct in6_addr addr) return buf; } #endif /* HAVE_IPV6 */ + diff --git a/lib/prefix.h b/lib/prefix.h index 5f1ff05c..ca7ee687 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_PREFIX_H @@ -25,6 +25,12 @@ #include "sockunion.h" +#ifndef Inline +#define Inline static inline +#endif + +typedef const union sockunion* const_sockunion ; + /* * A struct prefix contains an address family, a prefix length, and an * address. This can represent either a 'network prefix' as defined @@ -37,16 +43,16 @@ /* IPv4 and IPv6 unified prefix structure. */ struct prefix { - u_char family; - u_char prefixlen; - union + sa_family_t family; + u_char prefixlen; + union { u_char prefix; struct in_addr prefix4; #ifdef HAVE_IPV6 struct in6_addr prefix6; #endif /* HAVE_IPV6 */ - struct + struct { struct in_addr id; struct in_addr adv_router; @@ -58,36 +64,66 @@ struct prefix /* IPv4 prefix structure. */ struct prefix_ipv4 { - u_char family; - u_char prefixlen; + sa_family_t family; + u_char prefixlen; struct in_addr prefix __attribute__ ((aligned (8))); }; +CONFIRM(offsetof(struct prefix_ipv4, family) + == offsetof(struct prefix, family)) ; +CONFIRM(offsetof(struct prefix_ipv4, prefixlen) + == offsetof(struct prefix, prefixlen)) ; +CONFIRM(offsetof(struct prefix_ipv4, prefix) + == offsetof(struct prefix, u.prefix4)) ; +CONFIRM(sizeof(struct prefix_ipv4) <= sizeof(struct prefix)) ; /* IPv6 prefix structure. */ #ifdef HAVE_IPV6 struct prefix_ipv6 { - u_char family; - u_char prefixlen; + sa_family_t family; + u_char prefixlen; struct in6_addr prefix __attribute__ ((aligned (8))); }; +CONFIRM(offsetof(struct prefix_ipv6, family) + == offsetof(struct prefix, family)) ; +CONFIRM(offsetof(struct prefix_ipv6, prefixlen) + == offsetof(struct prefix, prefixlen)) ; +CONFIRM(offsetof(struct prefix_ipv6, prefix) + == offsetof(struct prefix, u.prefix6)) ; +CONFIRM(sizeof(struct prefix_ipv6) <= sizeof(struct prefix)) ; #endif /* HAVE_IPV6 */ struct prefix_ls { - u_char family; - u_char prefixlen; + sa_family_t family; + u_char prefixlen; struct in_addr id __attribute__ ((aligned (8))); struct in_addr adv_router; }; +CONFIRM(offsetof(struct prefix_ls, family) + == offsetof(struct prefix, family)) ; +CONFIRM(offsetof(struct prefix_ls, prefixlen) + == offsetof(struct prefix, prefixlen)) ; +CONFIRM(offsetof(struct prefix_ls, id) + == offsetof(struct prefix, u.lp.id)) ; +CONFIRM(offsetof(struct prefix_ls, adv_router) + == offsetof(struct prefix, u.lp.adv_router)) ; +CONFIRM(sizeof(struct prefix_ls) <= sizeof(struct prefix)) ; /* Prefix for routing distinguisher. */ struct prefix_rd { - u_char family; - u_char prefixlen; - u_char val[8] __attribute__ ((aligned (8))); + sa_family_t family; + u_char prefixlen; + u_char val[8] __attribute__ ((aligned (8))); }; +CONFIRM(offsetof(struct prefix_rd, family) + == offsetof(struct prefix, family)) ; +CONFIRM(offsetof(struct prefix_rd, prefixlen) + == offsetof(struct prefix, prefixlen)) ; +CONFIRM(offsetof(struct prefix_rd, val) + == offsetof(struct prefix, u.val)) ; +CONFIRM(sizeof(struct prefix_rd) <= sizeof(struct prefix)) ; #ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 @@ -156,12 +192,14 @@ extern int prefix2str (const struct prefix *, char *, int); extern int prefix_match (const struct prefix *, const struct prefix *); extern int prefix_same (const struct prefix *, const struct prefix *); extern int prefix_cmp (const struct prefix *, const struct prefix *); +extern int prefix_common_bits (const struct prefix *, const struct prefix *); extern void prefix_copy (struct prefix *dest, const struct prefix *src); extern void apply_mask (struct prefix *); -extern struct prefix *sockunion2prefix (const union sockunion *dest, - const union sockunion *mask); -extern struct prefix *sockunion2hostprefix (const union sockunion *); +extern struct prefix *sockunion2prefix (const_sockunion dest, + const_sockunion mask); +extern struct prefix *sockunion2hostprefix (const_sockunion src); +extern void prefix2sockunion (const struct prefix *, union sockunion *); extern struct prefix_ipv4 *prefix_ipv4_new (void); extern void prefix_ipv4_free (struct prefix_ipv4 *); @@ -171,6 +209,12 @@ extern void apply_mask_ipv4 (struct prefix_ipv4 *); #define PREFIX_COPY_IPV4(DST, SRC) \ *((struct prefix_ipv4 *)(DST)) = *((const struct prefix_ipv4 *)(SRC)); +Inline void +prefix_copy_ipv4(struct prefix* dst, struct prefix* src) +{ + *dst = *src ; +} ; + extern int prefix_ipv4_any (const struct prefix_ipv4 *); extern void apply_classful_mask_ipv4 (struct prefix_ipv4 *); @@ -193,7 +237,13 @@ extern int str2prefix_ipv6 (const char *, struct prefix_ipv6 *); extern void apply_mask_ipv6 (struct prefix_ipv6 *); #define PREFIX_COPY_IPV6(DST, SRC) \ - *((struct prefix_ipv6 *)(DST)) = *((const struct prefix_ipv6 *)(SRC)); + *((struct prefix_ipv6 *)(DST)) = *((const struct prefix_ipv6 *)(SRC)); + +Inline void +prefix_copy_ipv6(struct prefix* dst, struct prefix* src) +{ + *dst = *src ; +} ; extern int ip6_masklen (struct in6_addr); extern void masklen2ip6 (int, struct in6_addr *); diff --git a/lib/privs.c b/lib/privs.c index 69606f57..be3265ed 100644 --- a/lib/privs.c +++ b/lib/privs.c @@ -1,4 +1,4 @@ -/* +/* * Zebra privileges. * * Copyright (C) 2003 Paul Jakma. @@ -19,12 +19,18 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> #include "log.h" #include "privs.h" #include "memory.h" +#include "qpthreads.h" + +/* Needs to be qpthread safe */ +static qpt_mutex_t privs_mutex; +#define LOCK qpt_mutex_lock(&privs_mutex); +#define UNLOCK qpt_mutex_unlock(&privs_mutex); #ifdef HAVE_CAPABILITIES /* sort out some generic internal types for: @@ -37,7 +43,7 @@ * sets are mostly opaque, to hold a set of privileges, related in some way. * storage binds together a set of sets we're interested in. * (in reality: cap_value_t and priv_t are ints) - */ + */ #ifdef HAVE_LCAPS /* Linux doesn't have a 'set' type: a set of related privileges */ struct _pset { @@ -47,7 +53,7 @@ struct _pset { typedef cap_value_t pvalue_t; typedef struct _pset pset_t; typedef cap_t pstorage_t; - + #elif defined (HAVE_SOLARIS_CAPABILITIES) typedef priv_t pvalue_t; typedef priv_set_t pset_t; @@ -56,11 +62,12 @@ typedef priv_set_t *pstorage_t; #error "HAVE_CAPABILITIES defined, but neither LCAPS nor Solaris Capabilties!" #endif /* HAVE_LCAPS */ #endif /* HAVE_CAPABILITIES */ - + /* the default NULL state we report is RAISED, but could be LOWERED if * zprivs_terminate is called and the NULL handler is installed. */ static zebra_privs_current_t zprivs_null_state = ZPRIVS_RAISED; +static int raise_count = 0; /* keep raised until all pthreads have lowered */ /* internal privileges state */ static struct _zprivs_t @@ -128,7 +135,7 @@ static struct [ZCAP_CHROOT] = { 1, (pvalue_t []) { PRIV_PROC_CHROOT }, }, [ZCAP_NICE] = { 1, (pvalue_t []) { PRIV_PROC_PRIOCNTL }, }, [ZCAP_PTRACE] = { 1, (pvalue_t []) { PRIV_PROC_SESSION }, }, - [ZCAP_DAC_OVERRIDE] = { 2, (pvalue_t []) { PRIV_FILE_DAC_EXECUTE, + [ZCAP_DAC_OVERRIDE] = { 2, (pvalue_t []) { PRIV_FILE_DAC_EXECUTE, PRIV_FILE_DAC_READ, PRIV_FILE_DAC_SEARCH, PRIV_FILE_DAC_WRITE, @@ -139,7 +146,7 @@ static struct [ZCAP_FOWNER] = { 1, (pvalue_t []) { PRIV_FILE_OWNER }, }, #endif /* HAVE_SOLARIS_CAPABILITIES */ }; - + #ifdef HAVE_LCAPS /* Linux forms of capabilities methods */ /* convert zebras privileges to system capabilities */ @@ -148,64 +155,81 @@ zcaps2sys (zebra_capabilities_t *zcaps, int num) { pset_t *syscaps; int i, j = 0, count = 0; - + if (!num) return NULL; - + /* first count up how many system caps we have */ for (i= 0; i < num; i++) count += cap_map[zcaps[i]].num; - + if ( (syscaps = XCALLOC (MTYPE_PRIVS, (sizeof(pset_t) * num))) == NULL) { fprintf (stderr, "%s: could not allocate syscaps!", __func__); return NULL; } - + syscaps->caps = XCALLOC (MTYPE_PRIVS, (sizeof (pvalue_t) * count)); - + if (!syscaps->caps) { fprintf (stderr, "%s: could not XCALLOC caps!", __func__); return NULL; } - + /* copy the capabilities over */ count = 0; for (i=0; i < num; i++) for (j = 0; j < cap_map[zcaps[i]].num; j++) syscaps->caps[count++] = cap_map[zcaps[i]].system_caps[j]; - + /* iterations above should be exact same as previous count, obviously.. */ syscaps->num = count; - + return syscaps; } /* set or clear the effective capabilities to/from permitted */ -int +int zprivs_change_caps (zebra_privs_ops_t op) { cap_flag_value_t cflag; - + int result = 0; + int change = 0; + + LOCK + /* should be no possibility of being called without valid caps */ assert (zprivs_state.syscaps_p && zprivs_state.caps); if (! (zprivs_state.syscaps_p && zprivs_state.caps)) - exit (1); - + { + UNLOCK + exit (1); + } + if (op == ZPRIVS_RAISE) - cflag = CAP_SET; + { + cflag = CAP_SET; + change = (raise_count++ == 0); + } else if (op == ZPRIVS_LOWER) - cflag = CAP_CLEAR; + { + cflag = CAP_CLEAR; + change = (--raise_count == 0); + } else - return -1; + { + result = -1; + } - if ( !cap_set_flag (zprivs_state.caps, CAP_EFFECTIVE, - zprivs_state.syscaps_p->num, - zprivs_state.syscaps_p->caps, + if ( change && !cap_set_flag (zprivs_state.caps, CAP_EFFECTIVE, + zprivs_state.syscaps_p->num, + zprivs_state.syscaps_p->caps, cflag)) - return cap_set_proc (zprivs_state.caps); - return -1; + result = cap_set_proc (zprivs_state.caps); + + UNLOCK + return result; } zebra_privs_current_t @@ -213,25 +237,37 @@ zprivs_state_caps (void) { int i; cap_flag_value_t val; + zebra_privs_current_t result = ZPRIVS_LOWERED; + + LOCK /* should be no possibility of being called without valid caps */ assert (zprivs_state.syscaps_p && zprivs_state.caps); if (! (zprivs_state.syscaps_p && zprivs_state.caps)) - exit (1); - + { + UNLOCK + exit (1); + } + for (i=0; i < zprivs_state.syscaps_p->num; i++) { - if ( cap_get_flag (zprivs_state.caps, zprivs_state.syscaps_p->caps[i], + if ( cap_get_flag (zprivs_state.caps, zprivs_state.syscaps_p->caps[i], CAP_EFFECTIVE, &val) ) { zlog_warn ("zprivs_state_caps: could not cap_get_flag, %s", - safe_strerror (errno) ); - return ZPRIVS_UNKNOWN; + errtoa(errno, 0).str) ; + result = ZPRIVS_UNKNOWN; + break; } if (val == CAP_SET) - return ZPRIVS_RAISED; + { + result = ZPRIVS_RAISED; + break; + } } - return ZPRIVS_LOWERED; + + UNLOCK + return result; } static void @@ -244,7 +280,7 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) if ( prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1 ) { fprintf (stderr, "privs_init: could not set PR_SET_KEEPCAPS, %s\n", - safe_strerror (errno) ); + errtostr(errno, 0).str) ; exit(1); } @@ -259,50 +295,50 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) { if ( setreuid (zprivs_state.zuid, zprivs_state.zuid) ) { - fprintf (stderr, "zprivs_init (cap): could not setreuid, %s\n", - safe_strerror (errno)); - exit (1); + fprintf (stderr, "zprivs_init (cap): could not setreuid, %s\n", + errtostr(errno, 0).str) ; + exit (1); } } - + if ( !(zprivs_state.caps = cap_init()) ) { - fprintf (stderr, "privs_init: failed to cap_init, %s\n", - safe_strerror (errno)); + fprintf (stderr, "privs_init: failed to cap_init, %s\n", + errtostr(errno, 0).str) ; exit (1); } if ( cap_clear (zprivs_state.caps) ) { - fprintf (stderr, "privs_init: failed to cap_clear, %s\n", - safe_strerror (errno)); + fprintf (stderr, "privs_init: failed to cap_clear, %s\n", + errtostr(errno, 0).str) ; exit (1); } - + /* set permitted caps */ - cap_set_flag(zprivs_state.caps, CAP_PERMITTED, + cap_set_flag(zprivs_state.caps, CAP_PERMITTED, zprivs_state.syscaps_p->num, zprivs_state.syscaps_p->caps, CAP_SET); - + /* set inheritable caps, if any */ if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) { - cap_set_flag(zprivs_state.caps, CAP_INHERITABLE, - zprivs_state.syscaps_i->num, - zprivs_state.syscaps_i->caps, + cap_set_flag(zprivs_state.caps, CAP_INHERITABLE, + zprivs_state.syscaps_i->num, + zprivs_state.syscaps_i->caps, CAP_SET); } - - /* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as + + /* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as * and when, and only when, they are needed. */ - if ( cap_set_proc (zprivs_state.caps) ) + if ( cap_set_proc (zprivs_state.caps) ) { fprintf (stderr, "privs_init: initial cap_set_proc failed\n"); exit (1); } - + /* set methods for the caller to use */ zprivs->change = zprivs_change_caps; zprivs->current_state = zprivs_state_caps; @@ -316,12 +352,12 @@ zprivs_caps_terminate (void) cap_clear (zprivs_state.caps); /* and boom, capabilities are gone forever */ - if ( cap_set_proc (zprivs_state.caps) ) + if ( cap_set_proc (zprivs_state.caps) ) { fprintf (stderr, "privs_terminate: cap_set_proc failed, %s", - safe_strerror (errno) ); + errtostr(errno, 0).str) ; exit (1); - } + } /* free up private state */ if (zprivs_state.syscaps_p->num) @@ -329,18 +365,18 @@ zprivs_caps_terminate (void) XFREE (MTYPE_PRIVS, zprivs_state.syscaps_p->caps); XFREE (MTYPE_PRIVS, zprivs_state.syscaps_p); } - + if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) { XFREE (MTYPE_PRIVS, zprivs_state.syscaps_i->caps); XFREE (MTYPE_PRIVS, zprivs_state.syscaps_i); } - + cap_free (zprivs_state.caps); } #elif defined (HAVE_SOLARIS_CAPABILITIES) /* !HAVE_LCAPS */ - -/* Solaris specific capability/privilege methods + +/* Solaris specific capability/privilege methods * * Resources: * - the 'privileges' man page @@ -354,84 +390,105 @@ zcaps2sys (zebra_capabilities_t *zcaps, int num) { pset_t *syscaps; int i, j = 0; - + if ((syscaps = priv_allocset()) == NULL) { fprintf (stderr, "%s: could not allocate syscaps!\n", __func__); exit (1); } - + priv_emptyset (syscaps); - + for (i=0; i < num; i++) for (j = 0; j < cap_map[zcaps[i]].num; j++) priv_addset (syscaps, cap_map[zcaps[i]].system_caps[j]); - + return syscaps; } /* callback exported to users to RAISE and LOWER effective privileges * from nothing to the given permitted set and back down */ -int +int zprivs_change_caps (zebra_privs_ops_t op) { - + int result = 0; + + LOCK + /* should be no possibility of being called without valid caps */ assert (zprivs_state.syscaps_p); if (!zprivs_state.syscaps_p) { fprintf (stderr, "%s: Eek, missing caps!", __func__); + UNLOCK exit (1); } - + /* to raise: copy original permitted into our working effective set * to lower: just clear the working effective set */ if (op == ZPRIVS_RAISE) - priv_copyset (zprivs_state.syscaps_p, zprivs_state.caps); + { + if (raise_count++ == 0) + { + priv_copyset (zprivs_state.syscaps_p, zprivs_state.caps); + if (setppriv (PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps) != 0) + result = -1; + } + } else if (op == ZPRIVS_LOWER) - priv_emptyset (zprivs_state.caps); + { + if (--raise_count == 0) + { + priv_emptyset (zprivs_state.caps); + if (setppriv (PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps) != 0) + result = -1; + } + } else - return -1; - - if (setppriv (PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps) != 0) - return -1; - - return 0; + result = -1; + + UNLOCK + + return result; } /* Retrieve current privilege state, is it RAISED or LOWERED? */ -zebra_privs_current_t +zebra_privs_current_t zprivs_state_caps (void) { - zebra_privs_current_t result; + zebra_privs_current_t result = ZPRIVS_UNKNOWN; pset_t *effective; - + + LOCK + if ( (effective = priv_allocset()) == NULL) { fprintf (stderr, "%s: failed to get priv_allocset! %s\n", __func__, - safe_strerror (errno)); - return ZPRIVS_UNKNOWN; - } - - if (getppriv (PRIV_EFFECTIVE, effective)) - { - fprintf (stderr, "%s: failed to get state! %s\n", __func__, - safe_strerror (errno)); - result = ZPRIVS_UNKNOWN; + errtoa(errno, 0).str); } else { - if (priv_isemptyset (effective) == B_TRUE) - result = ZPRIVS_LOWERED; + + if (getppriv (PRIV_EFFECTIVE, effective)) + { + fprintf (stderr, "%s: failed to get state! %s\n", __func__, + errtoa(errno, 0).str); + } else - result = ZPRIVS_RAISED; + { + if (priv_isemptyset (effective) == B_TRUE) + result = ZPRIVS_LOWERED; + else + result = ZPRIVS_RAISED; + } + + if (effective) + priv_freeset (effective); } - - if (effective) - priv_freeset (effective); - + + UNLOCK return result; } @@ -440,11 +497,11 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) { pset_t *basic; pset_t *empty; - + /* the specified sets */ zprivs_state.syscaps_p = zcaps2sys (zprivs->caps_p, zprivs->cap_num_p); zprivs_state.syscaps_i = zcaps2sys (zprivs->caps_i, zprivs->cap_num_i); - + /* nonsensical to have gotten here but not have capabilities */ if (!zprivs_state.syscaps_p) { @@ -452,7 +509,7 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) "but no valid capabilities supplied\n", __func__); } - + /* We retain the basic set in our permitted set, as Linux has no * equivalent. The basic set on Linux hence is implicit, always * there. @@ -462,11 +519,11 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) fprintf (stderr, "%s: couldn't get basic set!\n", __func__); exit (1); } - + /* Add the basic set to the permitted set */ priv_union (basic, zprivs_state.syscaps_p); priv_freeset (basic); - + /* we need an empty set for 'effective', potentially for inheritable too */ if ( (empty = priv_allocset()) == NULL) { @@ -474,20 +531,20 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) exit (1); } priv_emptyset (empty); - - /* Hey kernel, we know about privileges! + + /* Hey kernel, we know about privileges! * this isn't strictly required, use of setppriv should have same effect */ if (setpflags (PRIV_AWARE, 1)) { fprintf (stderr, "%s: error setting PRIV_AWARE!, %s\n", __func__, - safe_strerror (errno) ); + errtoa(errno, 0).str ); exit (1); } - + /* need either valid or empty sets for both p and i.. */ assert (zprivs_state.syscaps_i && zprivs_state.syscaps_p); - + /* we have caps, we have no need to ever change back the original user * change real, effective and saved to the specified user. */ @@ -495,25 +552,25 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) { if ( setreuid (zprivs_state.zuid, zprivs_state.zuid) ) { - fprintf (stderr, "%s: could not setreuid, %s\n", - __func__, safe_strerror (errno)); + fprintf (stderr, "%s: could not setreuid, %s\n", + __func__, errtoa(errno, 0).str); exit (1); } } - + /* set the permitted set */ if (setppriv (PRIV_SET, PRIV_PERMITTED, zprivs_state.syscaps_p)) { fprintf (stderr, "%s: error setting permitted set!, %s\n", __func__, - safe_strerror (errno) ); + errtoa(errno, 0).str ); exit (1); } - + /* set the inheritable set */ if (setppriv (PRIV_SET, PRIV_INHERITABLE, zprivs_state.syscaps_i)) { fprintf (stderr, "%s: error setting inheritable set!, %s\n", __func__, - safe_strerror (errno) ); + errtoa(errno, 0).str ); exit (1); } @@ -521,13 +578,13 @@ zprivs_caps_init (struct zebra_privs_t *zprivs) if (setppriv (PRIV_SET, PRIV_EFFECTIVE, empty)) { fprintf (stderr, "%s: error setting effective set!, %s\n", __func__, - safe_strerror (errno) ); + errtoa(errno, 0).str ); exit (1); } - + /* we'll use this as our working-storage privset */ zprivs_state.caps = empty; - + /* set methods for the caller to use */ zprivs->change = zprivs_change_caps; zprivs->current_state = zprivs_state_caps; @@ -537,42 +594,65 @@ static void zprivs_caps_terminate (void) { assert (zprivs_state.caps); - + /* clear all capabilities */ priv_emptyset (zprivs_state.caps); setppriv (PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps); setppriv (PRIV_SET, PRIV_PERMITTED, zprivs_state.caps); setppriv (PRIV_SET, PRIV_INHERITABLE, zprivs_state.caps); - + /* free up private state */ if (zprivs_state.syscaps_p) priv_freeset (zprivs_state.syscaps_p); if (zprivs_state.syscaps_i) priv_freeset (zprivs_state.syscaps_i); - + priv_freeset (zprivs_state.caps); } #else /* !HAVE_LCAPS && ! HAVE_SOLARIS_CAPABILITIES */ #error "Neither Solaris nor Linux capabilities, dazed and confused..." #endif /* HAVE_LCAPS */ #endif /* HAVE_CAPABILITIES */ - + int zprivs_change_uid (zebra_privs_ops_t op) { + int result = 0; + + LOCK if (op == ZPRIVS_RAISE) - return seteuid (zprivs_state.zsuid); + { + if (raise_count++ == 0) + { + result = seteuid (zprivs_state.zsuid); + } + } else if (op == ZPRIVS_LOWER) - return seteuid (zprivs_state.zuid); + { + if (--raise_count == 0) + { + result = seteuid (zprivs_state.zuid); + } + } else - return -1; + { + result = -1; + } + + UNLOCK + return result; } zebra_privs_current_t zprivs_state_uid (void) { - return ( (zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED); + zebra_privs_current_t result; + + LOCK + result = ( (zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED); + UNLOCK + return result; } int @@ -584,7 +664,24 @@ zprivs_change_null (zebra_privs_ops_t op) zebra_privs_current_t zprivs_state_null (void) { - return zprivs_null_state; + int result; + + LOCK + result = zprivs_null_state; + UNLOCK + return result; +} + +void +zprivs_init_r() +{ + qpt_mutex_init(&privs_mutex, qpt_mutex_quagga); +} + +void +zprivs_finish(void) +{ + qpt_mutex_destroy(&privs_mutex, 0); } void @@ -599,12 +696,15 @@ zprivs_init(struct zebra_privs_t *zprivs) exit (1); } + LOCK + /* NULL privs */ - if (! (zprivs->user || zprivs->group + if (! (zprivs->user || zprivs->group || zprivs->cap_num_p || zprivs->cap_num_i) ) { zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; + UNLOCK return; } @@ -619,6 +719,7 @@ zprivs_init(struct zebra_privs_t *zprivs) /* cant use log.h here as it depends on vty */ fprintf (stderr, "privs_init: could not lookup user %s\n", zprivs->user); + UNLOCK exit (1); } } @@ -634,18 +735,20 @@ zprivs_init(struct zebra_privs_t *zprivs) if ( setgroups (1, &zprivs_state.vtygrp) ) { fprintf (stderr, "privs_init: could not setgroups, %s\n", - safe_strerror (errno) ); + errtostr(errno, 0).str) ; + UNLOCK exit (1); - } + } } else { fprintf (stderr, "privs_init: could not lookup vty group %s\n", zprivs->vty_group); + UNLOCK exit (1); } } - + if (zprivs->group) { if ( (grentry = getgrnam (zprivs->group)) ) @@ -656,43 +759,48 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not lookup group %s\n", zprivs->group); + UNLOCK exit (1); } /* change group now, forever. uid we do later */ if ( setregid (zprivs_state.zgid, zprivs_state.zgid) ) { fprintf (stderr, "zprivs_init: could not setregid, %s\n", - safe_strerror (errno) ); + errtostr(errno, 0).str) ; + UNLOCK exit (1); } } - + #ifdef HAVE_CAPABILITIES zprivs_caps_init (zprivs); #else /* !HAVE_CAPABILITIES */ /* we dont have caps. we'll need to maintain rid and saved uid - * and change euid back to saved uid (who we presume has all neccessary + * and change euid back to saved uid (who we presume has all necessary * privileges) whenever we are asked to raise our privileges. * * This is not worth that much security wise, but all we can do. */ - zprivs_state.zsuid = geteuid(); + zprivs_state.zsuid = geteuid(); if ( zprivs_state.zuid ) { if ( setreuid (-1, zprivs_state.zuid) ) { - fprintf (stderr, "privs_init (uid): could not setreuid, %s\n", - safe_strerror (errno)); + fprintf (stderr, "privs_init (uid): could not setreuid, %s\n", + errtoa(errno, 0).str); + UNLOCK exit (1); } } - + zprivs->change = zprivs_change_uid; zprivs->current_state = zprivs_state_uid; #endif /* HAVE_CAPABILITIES */ + + UNLOCK } -void +void zprivs_terminate (struct zebra_privs_t *zprivs) { if (!zprivs) @@ -700,7 +808,9 @@ zprivs_terminate (struct zebra_privs_t *zprivs) fprintf (stderr, "%s: no privs struct given, terminating", __func__); exit (0); } - + + LOCK + #ifdef HAVE_CAPABILITIES zprivs_caps_terminate(); #else /* !HAVE_CAPABILITIES */ @@ -708,8 +818,9 @@ zprivs_terminate (struct zebra_privs_t *zprivs) { if ( setreuid (zprivs_state.zuid, zprivs_state.zuid) ) { - fprintf (stderr, "privs_terminate: could not setreuid, %s", - safe_strerror (errno) ); + fprintf (stderr, "privs_terminate: could not setreuid, %s", + errtoa(errno, 0).str ); + UNLOCK exit (1); } } @@ -718,20 +829,25 @@ zprivs_terminate (struct zebra_privs_t *zprivs) zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; zprivs_null_state = ZPRIVS_LOWERED; + raise_count = 0; + + UNLOCK return; } void zprivs_get_ids(struct zprivs_ids_t *ids) { + LOCK - ids->uid_priv = getuid(); - (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) + ids->uid_priv = getuid(); + (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) : (ids->uid_normal = -1); - (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) + (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) : (ids->gid_normal = -1); - (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp) + (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp) : (ids->gid_vty = -1); - + + UNLOCK return; } diff --git a/lib/privs.h b/lib/privs.h index 46d614e0..45c5f49a 100644 --- a/lib/privs.h +++ b/lib/privs.h @@ -81,9 +81,13 @@ struct zprivs_ids_t }; /* initialise zebra privileges */ +extern void zprivs_init_r (void); extern void zprivs_init (struct zebra_privs_t *zprivs); +extern void zprivs_finish (void); + /* drop all and terminate privileges */ extern void zprivs_terminate (struct zebra_privs_t *); + /* query for runtime uid's and gid's, eg vty needs this */ extern void zprivs_get_ids(struct zprivs_ids_t *); diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c new file mode 100644 index 00000000..c4ab9679 --- /dev/null +++ b/lib/pthread_safe.c @@ -0,0 +1,516 @@ +/* Quagga Pthreads support -- thread safe versions of standard functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* We use thread specific storeage to provide buufers for the _r versions + * of standard functions, so that the callers don't need to provide + * their own. The contents of a buffer will remain intact until another + * safe_ function is called on the same thread + */ + +#include "pthread_safe.h" +#include "qpthreads.h" +#include "memory.h" + +#include <pthread.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> + +#include "qfstring.h" +#include "errno_names.h" + +/* prototypes */ +static void destructor(void* data); +static char * thread_buff(void); + +static pthread_key_t tsd_key; +static const int buff_size = 1024; + +/* Module initialization, before any threads have been created */ +void +safe_init_r(void) +{ + if (qpthreads_enabled) + { + int status; + status = pthread_key_create(&tsd_key, destructor); + if (status != 0) + zabort("Can't create thread specific data key"); + } +} + +/* Clean up */ +void +safe_finish(void) +{ + if (qpthreads_enabled) + pthread_key_delete(tsd_key); +} + +/* called when thread terminates, clean up */ +static void +destructor(void* data) +{ + XFREE(MTYPE_TSD, data); +} + +/* Thread safe version of strerror. Never returns NULL. + * Contents of result remains intact until another call of + * a safe_ function. + */ +const char * +safe_strerror(int errnum) +{ + static const char * unknown = "Unknown error"; + if (qpthreads_enabled) + { + char * buff = thread_buff(); + int ret = strerror_r(errnum, buff, buff_size); + + return (ret >= 0) + ? buff + : unknown; + } + else + { + const char *s = strerror(errnum); + return (s != NULL) + ? s + : unknown; + } +} + +/*============================================================================== + * Alternative error number handling. + * + * The descriptive strings for error numbers are all very well, but for some + * purposes knowing the name for the error is more useful -- the name, not the + * number, as the number is system dependent. + * + * The following provides: + * + * * errtoa() -- which maps error number to: ENAME '<strerror>' + * + * * errtoname() -- which maps error number to: ENAME + * + * * errtostr() -- which maps error number to: <strerror> + * + * where: + * + * * if name is not known gives: ERRNO=999 + * + * * if strerror rejects the number gives: *unknown error* + * + * * err == 0 gives: EOK -- for the name + * 'no error' -- for the <strerror> + * + * These functions take a 'len' argument, and truncates the result to the given + * len (0 => no truncation) -- silently imposing the maximum length allowed by + * the strerror_t. + * + * If has to truncate the <strerror>, places "..." at the end of the message + * to show this has happened. + */ + +static void errtox(strerror_t* st, int err, int len, int want) ; + +/*------------------------------------------------------------------------------ + * Construct string to describe the given error of the form: + * + * ENAME '<strerror>' + * + * Thread safe extension to strerror. Never returns NULL. + */ +extern strerror_t +errtoa(int err, int len) +{ + strerror_t st ; + + errtox(&st, err, len, 3) ; /* name and message */ + + return st ; +} ; + +/*------------------------------------------------------------------------------ + * Convert error number to its name + * + * Thread-safe. Never returns NULL. + */ +extern strerror_t +errtoname(int err, int len) +{ + strerror_t st ; + + errtox(&st, err, len, 1) ; /* name */ + + return st ; +} ; + +/*------------------------------------------------------------------------------ + * Alternative thread-safe safe_strerror() + * + * Thread safe replacement for strerror. Never returns NULL. + */ +extern strerror_t +errtostr(int err, int len) +{ + strerror_t st ; + + errtox(&st, err, len, 2) ; /* message */ + + return st ; +} ; + +/*----------------------------------------------------------------------------- + * Common code for errto<x> above. + * + * want == 1 -- return just name + * want == 2 -- return just the strerror() + * want == 3 -- return both, with the strerror() in single quotes. + */ +static void +errtox(strerror_t* st, int err, int len, int want) +{ + qf_str_t qfs ; + + const char* q ; + int ql ; + + /* Prepare. */ + int errno_saved = errno ; + + if ((len <= 0) || (len >= (int)sizeof(st->str))) + len = sizeof(st->str) - 1 ; + qfs_init(&qfs, st->str, len + 1) ; + + q = "" ; + ql = 0 ; + + /* If want the error name, do that now. */ + if (want & 1) + { + const char* name = errno_name_lookup(err) ; + + if (name != NULL) + qfs_append(&qfs, name) ; + else + qfs_printf(&qfs, "ERRNO=%d", err) ; + } ; + + /* name and string ? */ + if (want == 3) + { + qfs_append(&qfs, " ") ; + q = "'" ; + ql = 2 ; + } ; + + /* If want the error string, do that now */ + if (want & 2) + { + char buf[400] ; /* impossibly vast */ + int ret ; + const char* errm ; + + if (err == 0) + errm = "no error" ; + else + { + if (qpthreads_enabled) + { + /* POSIX is not explicit about what happens if the buffer is not + * big enough to accommodate the message, except that an ERANGE + * error may be raised. + * + * By experiment: glibc-2.10.2-1.x86_64 returns -1, with errno + * set to ERANGE and no string at all if the buffer is too small. + * + * A huge buffer is used to get the message, and that is later + * truncated, if necessary, to fit in the strerror_t structure. + */ + + buf[0] = '\0' ; /* make sure starts empty */ + ret = strerror_r(err, buf, sizeof(buf)) ; + errm = buf ; + if (ret != 0) + ret = errno ; + } + else + { + /* POSIX says that strerror *will* return something, but it is + * known that it may return NULL if the error number is not + * recognised. + */ + errno = 0 ; + errm = strerror(err) ; + ret = errno ; + if ((ret == 0) && ((errm == NULL) || (*errm == '\0'))) + ret = EINVAL ; + } ; + + /* Deal with errors, however exotic. */ + if (ret != 0) + { + q = "*" ; + ql = 2 ; /* force "*" "quotes" */ + if (ret == EINVAL) + errm = "unknown error" ; + else if (ret == ERANGE) + { + if (*errm == '\0') + errm = "vast error message" ; + } + else + { + qf_str_t qfs_b ; + qfs_init(&qfs_b, buf, sizeof(buf)) ; + qfs_printf(&qfs_b, "strerror%s(%d) returned error %d", + qpthreads_enabled ? "_r" : "", err, ret) ; + errm = buf ; + } ; + } ; + } ; + + /* Add strerror to the result... looking out for overflow. */ + len = strlen(errm) ; + + if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ + qfs_printf(&qfs, "%s%s%s", q, errm, q) ; + else + qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, errm, q) ; + /* -ve precision is ignored ! */ + } ; + + /* Put back errno */ + errno = errno_saved ; +} ; + +/*============================================================================== + * getaddrinfo() and getnameinfo() "EAI_XXXX" error number handling. + * + * This is similar to the above for errno. + * + * The following provides: + * + * * eaitoa() -- which maps error number to: EAI_XXX '<gai_strerror>' + * or: as errtoa() + * + * * eaitoname() -- which maps error number to: EAI_XXX + * or: as errtoname() + * + * * eaitostr() -- which maps error number to: <gai_strerror> + * or: as errtostr() + * + * where: + * + * * if given EAI_SYSTEM, and given a non-zero errno type error number, + * produce the errno string. + * + * * if name is not known gives: EAI=999 + * + * * gai_strerror returns a string saying the error is not known if that is + * the case. + * + * * eai == 0 gives: EAI_OK -- for the name + * 'no error' -- for the <sgai_strerror> + * + * NB: EAI_SYSTEM is an invitation to look at errno to discover the true + * error. + */ + +static void eaitox(strerror_t* st, int eai, int err, int len, int want) ; + +/*------------------------------------------------------------------------------ + * Construct string to describe the given EAI_XXX error of the form: + * + * EAI_XXX '<gai_strerror>' + * or: ENAME '<strerror>' -- if EAI_SYSTEM and err != 0 + * + * Thread safe. Never returns NULL. + */ +extern strerror_t +eaitoa(int eai, int err, int len) +{ + strerror_t st ; + + eaitox(&st, eai, err, len, 3) ; /* name and message */ + + return st ; +} ; + +/*------------------------------------------------------------------------------ + * Convert EAI_XXX error number to its name... + * + * ...or, if EAI_SYSTEM and err != 0, convert err to its name. + * + * Thread-safe. Never returns NULL. + */ +extern strerror_t +eaitoname(int eai, int err, int len) +{ + strerror_t st ; + + eaitox(&st, eai, err, len, 1) ; /* name */ + + return st ; +} ; + +/*------------------------------------------------------------------------------ + * Alternative to gai_strerror()... + * + * ...or, if EAI_SYSTEM and err != 0, do strerror(err) or strerror_r(err). + * + * Thread-safe. Never returns NULL. + */ +extern strerror_t +eaitostr(int eai, int err, int len) +{ + strerror_t st ; + + eaitox(&st, eai, err, len, 2) ; /* message */ + + return st ; +} ; + +/*----------------------------------------------------------------------------- + * Common code for eaito<x> above. + * + * want == 1 -- return just name + * want == 2 -- return just the gai_strerror() + * want == 3 -- return both, with the gai_strerror() in single quotes. + * + * err != 0 => if EAI_SYSTEM, return result for errno == err instead. + */ +static void +eaitox(strerror_t* st, int eai, int err, int len, int want) +{ + qf_str_t qfs ; + + const char* q ; + int ql ; + + /* Look out for mapping EAI_SYSTEM */ + if ((eai == EAI_SYSTEM) && (err != 0)) + return errtox(st, err, len, want) ; + + /* Prepare. */ + int errno_saved = errno ; + + if ((len <= 0) || (len >= (int)sizeof(st->str))) + len = sizeof(st->str) - 1 ; + qfs_init(&qfs, st->str, len + 1) ; + + q = "" ; + ql = 0 ; + + /* If want the error name, do that now. */ + if (want & 1) + { + const char* name = eaino_name_lookup(eai) ; + + if (name != NULL) + qfs_append(&qfs, name) ; + else + qfs_printf(&qfs, "EAI=%d", eai) ; + } ; + + /* name and string ? */ + if (want == 3) + { + qfs_append(&qfs, " ") ; + q = "'" ; + ql = 2 ; + } ; + + /* If want the error string, do that now */ + if (want & 2) + { + const char* eaim ; + + if (eai == 0) + eaim = "no error" ; + else + eaim = gai_strerror(eai) ; + + /* Add strerror to the result... looking out for overflow. */ + len = strlen(eaim) ; + + if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ + qfs_printf(&qfs, "%s%s%s", q, eaim, q) ; + else + qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, eaim, q) ; + /* -ve precision is ignored ! */ + } ; + + /* Put back errno */ + errno = errno_saved ; +} ; + +/*============================================================================*/ + +/* Thread safe version of inet_ntoa. Never returns NULL. + * Contents of result remains intact until another call of + * a safe_ function. + */ +const char * +safe_inet_ntoa (struct in_addr in) +{ + static const char * unknown = "Unknown address"; + const char * buff; + + buff = (qpthreads_enabled) + ? inet_ntop(AF_INET, &in, thread_buff(), buff_size) + : inet_ntoa(in); + + return buff != NULL + ? buff + : unknown; +} + +/* Return the thread's buffer, create it if necessary. + * (pthread Thread Specific Data) + */ +static char * +thread_buff(void) +{ + int ret; + char * buff = pthread_getspecific(tsd_key); + if (buff == NULL) + { + buff = XMALLOC(MTYPE_TSD, buff_size); + ret = pthread_setspecific(tsd_key, buff); + if (ret != 0) + zabort("Can't set thread specific data"); + } + + return buff; +} + + + + + + + diff --git a/lib/pthread_safe.h b/lib/pthread_safe.h new file mode 100644 index 00000000..9518f3d1 --- /dev/null +++ b/lib/pthread_safe.h @@ -0,0 +1,46 @@ +/* Quagga Pthreads support -- thread safe versions of standard functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef PTHREAD_SAFE_H_ +#define PTHREAD_SAFE_H_ + +#include <netinet/in.h> + +typedef struct strerror strerror_t ; +struct strerror +{ + char str[121] ; /* cannot imagine anything as big */ +} ; + +extern void safe_init_r(void); +extern void safe_finish(void); +extern const char * safe_strerror(int errnum); +extern const char * safe_inet_ntoa (struct in_addr in); + +extern strerror_t errtoa(int err, int len) ; +extern strerror_t errtoname(int err, int len) ; +extern strerror_t errtostr(int err, int len) ; + +extern strerror_t eaitoa(int eai, int err, int len) ; +extern strerror_t eaitoname(int eai, int err, int len) ; +extern strerror_t eaitostr(int eai, int err, int len) ; + +#endif /* PTHREAD_SAFE_H_ */ diff --git a/lib/qafi_safi.h b/lib/qafi_safi.h new file mode 100644 index 00000000..337532d5 --- /dev/null +++ b/lib/qafi_safi.h @@ -0,0 +1,172 @@ +/* Quagga AFI/SAFI + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _QUAGGA_AFI_SAFI_H +#define _QUAGGA_AFI_SAFI_H + +#include <stdint.h> +#include "lib/zassert.h" + +/*============================================================================== + * Generic AFI and SAFI types. + */ +typedef uint16_t afi_t; +typedef uint8_t safi_t; + +/*============================================================================== + * iAFI and iSAFI + * + * These are the standard IANA registered AFI and SAFI values that Quagga is + * at all interested in. + */ + +typedef enum iAFI iAFI_t ; + +enum iAFI +{ + iAFI_Reserved = 0, /* No meaning defined by IANA */ + + iAFI_IP = 1, /* IP (IP version 4) */ + iAFI_IP6 = 2, /* IP6 (IP version 6) */ + + iAFI_IPV4 = iAFI_IP, /* locally AKA */ + iAFI_IPV6 = iAFI_IP6 /* locally AKA */ +} ; + + +typedef enum iSAFI iSAFI_t ; + +enum iSAFI +{ + iSAFI_Reserved = 0, /* No meaning defined by IANA */ + + iSAFI_Unicast = 1, /* unicast forwarding */ + iSAFI_Multicast = 2, /* multicast forwarding */ + + iSAFI_Unused = 3, /* also Reserved by IANA */ + + iSAFI_MPLS_VPN = 128 /* MPLS-labeled VPN address */ +} ; + +/*============================================================================== + * qAFI and qSAFI + * + * These are the AFI and SAFI values that Quagga uses internally. + * + * They are almost the same as the IANA numbers, but different where that + * is required to produce a dense set. + */ + +typedef enum qAFI qAFI_t ; + +enum qAFI +{ + qAFI_min = 0, /* minimum valid qAFI */ + qAFI_undef = 0, /* undefined AFI */ + + qAFI_first = 1, /* first real qAFI */ + + qAFI_IP = 1, + qAFI_IP6 = 2, + + qAFI_last = 2, /* last real qAFI */ + + qAFI_max = 2, /* maximum valid qAFI */ + qAFI_count, /* number of distinct qAFI */ + + qAFI_IPV4 = qAFI_IP, + qAFI_IPV6 = qAFI_IP6 +} ; + +typedef enum qSAFI qSAFI_t ; + +enum qSAFI +{ + qSAFI_min = 0, /* minimum valid qSAFI */ + qSAFI_undef = 0, /* undefined SAFI */ + + qSAFI_first = 1, /* first real qSAFI */ + + qSAFI_Unicast = 1, + qSAFI_Multicast = 2, + qSAFI_Unused = 3, + qSAFI_MPLS_VPN = 4, + + qSAFI_last = 4, /* last real qSAFI */ + + qSAFI_max = 4, /* maximum valid qSAFI */ + qSAFI_count /* number of distinct qSAFI */ +} ; + +/*============================================================================== + * iAFI_SAFI and qAFI_SAFI structures + */ +struct iAFI_SAFI +{ + iAFI_t afi ; + iSAFI_t safi ; +} ; + +typedef struct iAFI_SAFI iAFI_SAFI_t ; +typedef struct iAFI_SAFI* iAFI_SAFI ; + +struct qAFI_SAFI +{ + qAFI_t afi ; + qSAFI_t safi ; +} ; + +typedef struct qAFI_SAFI qAFI_SAFI_t ; +typedef struct qAFI_SAFI* qAFI_SAFI ; + +/*============================================================================== + * Quagga AFI/SAFI values -- original macro definitions + */ + +/* Address family numbers from RFC1700. */ +#define AFI_IP 1 +#define AFI_IP6 2 +#define AFI_MAX 3 + +CONFIRM( (AFI_IP == qAFI_IP) + && (AFI_IP == iAFI_IP) ) ; +CONFIRM( (AFI_IP6 == qAFI_IP6) + && (AFI_IP6 == iAFI_IP6) ) ; +CONFIRM(AFI_MAX == qAFI_count) ; + +/* Subsequent Address Family Identifier. */ +#define SAFI_UNICAST 1 +#define SAFI_MULTICAST 2 +#define SAFI_UNICAST_MULTICAST 3 +#define SAFI_MPLS_VPN 4 +#define SAFI_MAX 5 + +CONFIRM( (SAFI_UNICAST == qSAFI_Unicast) + && (SAFI_UNICAST == iSAFI_Unicast) ) ; +CONFIRM( (SAFI_MULTICAST == qSAFI_Multicast) + && (SAFI_MULTICAST == iSAFI_Multicast) ) ; +CONFIRM( (SAFI_UNICAST_MULTICAST == qSAFI_Unused) + && (SAFI_UNICAST_MULTICAST == iSAFI_Unused) ) ; +CONFIRM(SAFI_MPLS_VPN == qSAFI_MPLS_VPN) ; +CONFIRM(SAFI_MAX == qSAFI_count) ; + +#endif /* _QUAGGA_AFI_SAFI_H */ diff --git a/lib/qfstring.c b/lib/qfstring.c new file mode 100644 index 00000000..35708640 --- /dev/null +++ b/lib/qfstring.c @@ -0,0 +1,1204 @@ +/* Some string handling + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qfstring.h" +#include "zassert.h" +#include <stdbool.h> + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Initialise qf_str -- to given size (which includes the '\0') + * + * Sets pointers and terminates an empty string with one byte reserved for the + * terminating '\0'. + * + * This operation is async-signal-safe. + */ +extern void +qfs_init(qf_str qfs, char* str, size_t size) +{ + assert(size > 0) ; + + qfs->str = str ; + qfs->end = str + size - 1 ; + + *str = '\0' ; + qfs->ptr = str ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise qf_str which already contains string -- to given size (which + * includes the '\0') + * + * This may be used to prepare for appending to a buffer which already contains + * something. + * + * Sets pointers, setting the write pointer to the existing terminating '\0'. + * + * This operation is async-signal-safe. + */ +extern void +qfs_init_as_is(qf_str qfs, char* str, size_t size) +{ + assert(size > 0) ; + + qfs->str = str ; + qfs->end = str + size - 1 ; + + qfs->ptr = strchr(str, '\0') ; +} ; + +/*------------------------------------------------------------------------------ + * Terminate string with the given string. + * + * If necessary, characters are discarded from the end of the string in order + * to fit in the terminating stuff. + * + * If the terminating stuff won't fit, as much of the end if the terminating + * stuff as possible is copied to the string -- displacing any existing + * contents. + * + * This operation is async-signal-safe. + */ +extern void +qfs_term(qf_str qfs, const char* src) +{ + int len ; + int excess ; + + if ((src == NULL) || (*src == '\0')) + { + *qfs->ptr = '\0' ; /* should be true anyway */ + return ; + } ; + + len = strlen(src) ; + excess = qfs_len(qfs) - len ; + if (excess > 0) + { + if (excess <= (qfs->ptr - qfs->str)) + qfs->ptr -= excess ; + else + { + int want = len ; + len = qfs->end - qfs->str ; /* take what can... */ + src += (want - len) ; /* ... from the end */ + qfs->ptr = qfs->str ; + } ; + } ; + + memcpy(qfs->ptr, src, len + 1) ; /* include the '\0' */ + qfs->ptr += len ; +} ; + +/*============================================================================== + * Appending to the string + */ + +/*------------------------------------------------------------------------------ + * Append as much as possible of the source string to the given qf_str. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append(qf_str qfs, const char* src) +{ + int n ; + + if ((src == NULL) || (*src == '\0')) + return ; + + n = strlen(src) ; + + if (n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n == 0) + return ; + + memcpy(qfs->ptr, src, n + 1) ; + qfs->ptr += n ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the first 'n' bytes of the source string to + * the given qf_str. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_n(qf_str qfs, const char* src, size_t n) +{ + if ((int)n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n <= 0) + return ; + + memcpy(qfs->ptr, src, n) ; + qfs->ptr += n ; + + *qfs->ptr = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Append upto 'n' copies of the given character to the qf_str + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_ch_x_n(qf_str qfs, char ch, size_t n) +{ + if ((int)n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n <= 0) + return ; + + while (n--) + *qfs->ptr++ = ch ; + + *qfs->ptr = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the source string to the given qf_str, left or + * right justified to the given width. + * + * Ignores the width if the string is longer than it. + * + * Negative width => left justify. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_justified(qf_str qfs, const char* src, int width) +{ + size_t n ; + + if ((src == NULL) || (*src == '\0')) + n = 0 ; + else + n = strlen(src) ; + + qfs_append_justified_n(qfs, src, n, width) ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the first 'n' bytes of the source string to + * the given qf_str, left or right justified to the given width. + * + * Ignores the width if the string is longer than it. + * + * Negative width => left justify. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_justified_n(qf_str qfs, const char* src, size_t n, int width) +{ + if ((int)n >= abs(width)) + width = 0 ; + + if (width > 0) + qfs_append_ch_x_n(qfs, ' ', width - n) ; + + qfs_append_n(qfs, src, n) ; + + if (width < 0) + qfs_append_ch_x_n(qfs, ' ', - width - n) ; +} ; + +/*============================================================================== + * Number conversion + */ + +static void +qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags, + int width, int precision) ; + +/*------------------------------------------------------------------------------ + * Signed integer -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags, + int width, int precision) +{ + uintmax_t u_val ; + int sign ; + + if (s_val < 0) + { + sign = -1 ; + u_val = (uintmax_t)(-(s_val + 1)) + 1 ; + } + else + { + sign = +1 ; + u_val = s_val ; + } ; + + qfs_number(qfs, u_val, sign, flags & ~pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Unsigned integer -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags, + int width, int precision) +{ + qfs_number(qfs, u_val, 0, flags | pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Address -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, + int width, int precision) +{ + confirm(sizeof(uintmax_t) >= sizeof(uintptr_t)) ; + qfs_number(qfs, (uintptr_t)p_val, 0, flags | pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Number conversion function. + * + * All number conversion ends up here. + * + * Accepts: pf_commas -- format with commas + * pf_plus -- requires '+' or '-' + * pf_space -- requires space or '-' + * pf_zeros -- zero fill to width + * pf_alt -- add '0x' or '0X' if hex -- depending on pf_uc + * add '0' if octal and not zero. + * no effect otherwise + * + * pf_precision -- explicit precision (needed if precision == 0) + * + * pf_hex -- render in hex + * pf_uc -- render in upper case + * + * pf_unsigned -- value is unsigned + * pf_ptr -- value is a void* pointer + * + * NB: pf_hex does NOT imply pf_unsigned. + * pf_uc does NOT imply pf_hex + * + * If the width is < 0 -- left justify in abs(width) -- zero fill ignored + * == 0 -- no width -- zero fill ignored + * > 0 -- right justify in width -- zero filling if req. + * + * If the precision is < 0 it is ignored (unless pf_hex, see below). + * + * If the precision is 0 it is ignored unless pf_precision is set. + * + * Precedence issues: + * + * * precision comes first. Disables zero fill. + * + * * commas come before zero fill. + * + * * signs and prefixes come before zero fill + * + * * pf_plus takes precedence over pf_space + * + * * pf_unsigned or sign == 0 takes precedence over pf_plus and pf_space. + * + * For decimal output, pf_commas groups digits in 3's, separated by ','. + * For hex output, pf_commas groups digits in 4's, separated by '_'. + * For oct output, pf_commas is ignored. + * + * Note that pf_commas is a glibc extension, which does not apply to hex ! + * + * For hex output if precision is: + * + * -1 set precision to multiple of 2, just long enough for the value + * -2 set precision to multiple of 4, just long enough for the value + * + * (under all other conditions, -ve precision is ignored). + * + * Note: if the precision is explicitly 0, and the value is 0, and no other + * characters are to be generated -- ie no: pf_plus, pf_space, pf_zeros, + * or pf_alt (with pf_hex) -- then nothing is generated. + * + * This operation is async-signal-safe. + */ +static void +qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags, + int width, int precision) +{ + enum + { + max_bits = 256, /* size of number can convert */ + max_digits = 90, /* could do octal ! */ + buf_size = 128, /* buffer to use for that */ + } ; + + confirm((sizeof(uintmax_t) * 8) <= max_bits) ; /* check max_bits */ + confirm((max_digits * 3) >= max_bits) ; /* check max_digits */ + + /* Buffer requires space for sign, '0x', digits, '00', commas, '\0' + * + * The '00' is for zero fill will commas, and is enough to extend the + * number to "000,...." -- that is, a full leading triple. + */ + confirm(buf_size > (1 + 2 + max_digits + (2 + (max_digits / 3)) + 1)) ; + + /* For hex commas the sum is similar, but smaller. */ + confirm((3 + (max_digits / 4)) < (2 + (max_digits / 3))) ; + + unsigned base ; + const char* digits ; + const char* radix_str ; + const char* sign_str ; + char num[buf_size] ; + char* p ; + char* e ; + int len ; + int radix_len ; + int sign_len ; + uintmax_t v ; + + char comma ; + int interval ; + + int zeros ; + + static const char lc[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' } ; + static const char uc[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' } ; + + /* Tidy up the options */ + if (precision < 0) + { + if ((flags & pf_hex) && (precision >= -2)) + { + /* special precision for hex output */ + int unit = (precision == -1) ? 2 : 4 ; + v = val | 1 ; + precision = 0 ; + while (v != 0) + { + precision += unit ; + v >>= (unit * 4) ; + } ; + } + else + { + /* mostly, -ve precision is ignored */ + precision = 0 ; + flags &= ~pf_precision ; /* ignore precision < 0 */ + } ; + } ; + + if (precision > 0) + flags |= pf_precision ; /* act on precision > 0 */ + + if ((flags & pf_precision) || (width <= 0)) + flags &= ~pf_zeros ; /* turn off zero fill */ + + if (flags & pf_oct) + flags &= ~pf_commas ; /* turn off commas */ + + /* Set up any required sign and radix prefix */ + if ((flags & pf_unsigned) || (sign == 0)) + sign_str = "" ; + else if (sign < 0) + sign_str = "-" ; + else if (flags & pf_plus) + sign_str = "+" ; + else if (flags & pf_space) + sign_str = " " ; + else + sign_str = "" ; + + sign_len = strlen(sign_str) ; + + radix_str = "" ; + if (flags & pf_alt) + { + if (flags & pf_hex) + radix_str = (flags & pf_uc) ? "0X" : "0x" ; + else if ((flags & pf_oct) && (val != 0)) + radix_str = "0" ; + + confirm(pf_uc != 0) ; + } ; + + radix_len = strlen(radix_str) ; + + /* Turn off zero fill if left justify (width < 0) */ + if (width < 0) + flags &= ~pf_zeros ; + + /* Special case of explicit zero precision and value == 0 */ + if ((flags & pf_precision) && (precision == 0) && (val == 0)) + { + if (((flags & pf_zeros) == 0) && (sign_len == 0) && (radix_len == 0)) + { + qfs_append_justified_n(qfs, NULL, 0, width) ; + return ; + } ; + } ; + + /* Start with the basic digit conversion. */ + base = 10 ; + if (flags & pf_hex) + base = 16 ; + else if (flags & pf_oct) + base = 8 ; + + digits = (flags & pf_uc) ? uc : lc ; + confirm(pf_uc != 0) ; + + e = p = num + sizeof(num) - 1 ; + *p = '\0' ; + v = val ; + do + { + *--p = digits[v % base] ; + v /= base ; + } while ((v > 0) && (p > num)) ; + + assert(v == 0) ; + + len = e - p ; + + /* Worry about the precision */ + while ((precision > len) && (len < max_digits)) + { + *--p = '0' ; + ++len ; + } ; + + /* Worry about commas */ + comma = (flags & pf_hex) ? '_' : ',' ; + interval = (flags & pf_hex) ? 4 : 3 ; + + if (flags & pf_commas) + { + int c ; + int t ; + char* cq ; + char* cp ; + + c = (len - 1) / interval ; /* number of commas to insert */ + t = len % interval ; /* digits before first comma */ + if (t == 0) + t = interval ; + + len += c ; /* account for the commas */ + + cq = p ; + p -= c ; + cp = p ; + + assert(p > num) ; + + while (c--) + { + while (t--) + *cp++ = *cq++ ; + *cp++ = comma ; + } ; + + assert(len == (e - p)) ; + + /* commas and zero fill interact. Here fill the leading group. */ + zeros = width - (sign_len + radix_len + len) ; + if ((flags & pf_zeros) && (zeros > 0)) + { + int group_fill = interval - (len % (interval + 1)) ; + assert(group_fill < interval) ; + if (group_fill > zeros) + group_fill = zeros ; + + len += group_fill ; + while (group_fill--) + { + assert(p > num) ; + *--p = '0' ; + } ; + } ; + } ; + + assert(len == (e - p)) ; + + /* See if still need to worry about zero fill */ + zeros = width - (sign_len + radix_len + len) ; + if ((flags & pf_zeros) && (zeros > 0)) + { + /* Need to insert zeros and possible commas between sign and radix + * and the start of the number. + * + * Note that for commas the number has been arranged to have a full + * leading group. + * + * The width can be large... so do this by appending any sign and + * radix to the qf_str, and then the required leading zeros (with or + * without commas). + */ + if (sign_len != 0) + qfs_append_n(qfs, sign_str, sign_len) ; + + if (radix_len != 0) + qfs_append_n(qfs, radix_str, radix_len) ; + + if (flags & pf_commas) + { + /* Leading zeros with commas ! + * + * Start with ',', '0,', '00,' etc to complete the first group. + * Thereafter add complete groups. + */ + int g ; + int r ; + g = (zeros + interval - 1) / (interval + 1) ; + r = (zeros - 1) % (interval + 1) ; + + if (r == 0) + { + qfs_append_ch_x_n(qfs, comma, 1) ; + r = interval ; + } + + while (g--) + { + qfs_append_ch_x_n(qfs, '0', r) ; + qfs_append_ch_x_n(qfs, comma, 1) ; + r = interval ; + } ; + } + else + qfs_append_ch_x_n(qfs, '0', zeros) ; + + width = 0 ; /* have dealt with the width. */ + } + else + { + /* No leading zeros, so complete the number by adding any sign + * and radix. + */ + char* cp ; + + p -= sign_len + radix_len ; + len += sign_len + radix_len ; + assert(p >= num) ; + + cp = p ; + while (sign_len--) + *cp++ = *sign_str++ ; + while (radix_len--) + *cp++ = *radix_str++ ; + } ; + + /* Finally, can append the number -- respecting any remaining width */ + assert(len == (e - p)) ; + + qfs_append_justified_n(qfs, p, len, width) ; +} ; + +/*============================================================================== + * printf() and vprintf() type functions + */ + +enum pf_phase +{ + pfp_null, /* in ascending order */ + pfp_flags, + pfp_width, + pfp_precision, + pfp_int_type, + pfp_float_type, + + pfp_done, + pfp_failed +} ; + +CONFIRM(pfp_float_type > pfp_int_type) ; + +/* Number types for printing */ +enum arg_num_type +{ + ant_char, /* hh */ + ant_short, /* h */ + ant_int, /* default */ + ant_long, /* l */ + ant_long_long, /* ll */ + ant_intmax_t, /* j */ + ant_size_t, /* z */ + ant_ptr_t, /* void* */ + ant_long_double, /* L for float */ + + ant_default = ant_int, +}; + +static enum pf_phase qfs_arg_string(qf_str qfs, const char* src, + enum pf_flags flags, int width, int precision) ; +static enum pf_phase qfs_arg_char(qf_str qfs, char ch, + enum pf_flags flags, int width, int precision) ; +static enum pf_phase qfs_arg_integer(qf_str qfs, va_list* p_va, + enum pf_flags flags, int width, int precision, enum arg_num_type ant) ; +static enum pf_phase qfs_arg_float(qf_str qfs, va_list* p_va, + const char* start, size_t flen, enum arg_num_type ant) ; + +/*------------------------------------------------------------------------------ + * Formatted print to qf_str -- cf printf() + * + * This operation is async-signal-safe -- EXCEPT for floating point values. + */ +extern void +qfs_printf(qf_str qfs, const char* format, ...) +{ + va_list va ; + + va_start (va, format); + qfs_vprintf(qfs, format, va); + va_end (va); +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to qf_str -- cf vprintf() + * + * This operation is async-signal-safe -- EXCEPT for floating point values. + * + * Operates on a copy of the va_list -- so the original is *unchanged*. + */ +extern void +qfs_vprintf(qf_str qfs, const char *format, va_list va) +{ + va_list vac ; + + if (format == NULL) + return ; + + va_copy(vac, va) ; + + while ((qfs->ptr < qfs->end) && (*format != '\0')) + { + /* Have space for one byte and current format byte is not '\0' */ + if (*format != '%') + *qfs->ptr++ = *format++ ; + else + { + const char* start = format++ ; /* start points at the '%' ... + ... step past it now */ + bool star = false ; + bool digit = false ; + int d = 0 ; + int width_sign = +1 ; + int width = 0 ; + int precision = 0 ; + enum arg_num_type ant = ant_default ; + enum pf_flags flags = pf_none ; + enum pf_phase phase = pfp_null ; + + while (phase < pfp_done) + { + switch (*format++) /* get next and step past it */ + { + case '%': /* %% only */ + if (phase == pfp_null) + *qfs->ptr++ = '%' ; + phase = (phase == pfp_null) ? pfp_done : pfp_failed ; + break ; + + case '\'': + flags |= pf_commas ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '-': + width_sign = -1 ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '+': + flags |= pf_plus ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '#': + flags |= pf_alt ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case ' ': + flags |= pf_space ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '0': + if (phase <= pfp_flags) + { + flags |= pf_zeros ; + phase = pfp_flags ; + break ; + } ; + /* fall through */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d = *(format - 1) - '0' ; + if (!star && (phase <= pfp_width)) + { + phase = pfp_width ; + width = (width * 10) + (d * width_sign) ; + } + else if (!star && (phase == pfp_precision)) + precision = (precision * 10) + d ; + else + phase = pfp_failed ; + + digit = true ; + break ; + + case '*': + if (!star && !digit && (phase <= pfp_width)) + { + phase = pfp_width ; + width = va_arg(vac, int) ; + } + else if (!star && !digit && (phase == pfp_precision)) + { + precision = va_arg(vac, int) ; + if (precision < 0) + { + precision = 0 ; + flags &= ~pf_precision ; /* completely ignore */ + } ; + } + else + phase = pfp_failed ; + + star = true ; + break ; + + case '.': + phase = (phase < pfp_precision) ? pfp_precision : pfp_failed ; + flags |= pf_precision ; + precision = 0 ; + break ; + + case 'l': /* 1 or 2 'l', not 'h', 'j' or 'z' */ + phase = (phase <= pfp_int_type) ? pfp_int_type : pfp_failed ; + if (ant == ant_default) + ant = ant_long ; + else if (ant == ant_long) + ant = ant_long_long ; + else + phase = pfp_failed ; + break ; + + case 'h': /* 1 or 2 'h', not 'l', 'j' or 'z' */ + phase = (phase <= pfp_int_type) ? pfp_int_type : pfp_failed ; + if (ant == ant_default) + ant = ant_short ; + else if (ant == ant_short) + ant = ant_char ; + else + phase = pfp_failed ; + break ; + + case 'j': /* 1 'j', not 'h', 'l' or 'z' */ + phase = (phase <= pfp_int_type) ? pfp_int_type : pfp_failed ; + ant = ant_intmax_t ; + break ; + + case 'z': /* 1 'z', not 'h', 'l' or 'j' */ + phase = (phase <= pfp_int_type) ? pfp_int_type : pfp_failed ; + ant = ant_size_t ; + break ; + + case 'L': /* 1 'L', not for integers ! */ + phase = (phase < pfp_int_type) ? pfp_float_type : pfp_failed ; + ant = ant_long_double ; + break ; + + case 's': + if (phase == pfp_int_type) + phase = pfp_failed ; /* don't do 'l' etc. */ + else + phase = qfs_arg_string(qfs, va_arg(vac, char*), + flags, width, precision) ; + break ; + + case 'c': + if (phase == pfp_int_type) + phase = pfp_failed ; /* don't do 'l' etc. */ + else + phase = qfs_arg_char(qfs, (char)va_arg(vac, int), + flags, width, precision) ; + break ; + + case 'd': + case 'i': + phase = qfs_arg_integer(qfs, &vac, flags, width, precision, + ant) ; + break ; + + case 'u': + phase = qfs_arg_integer(qfs, &vac, flags | pf_unsigned, width, + precision, ant) ; + break ; + + case 'o': + phase = qfs_arg_integer(qfs, &vac, flags | pf_oct, width, + precision, ant) ; + break ; + + case 'x': + phase = qfs_arg_integer(qfs, &vac, flags | pf_hex_x, width, + precision, ant) ; + break ; + + case 'X': + phase = qfs_arg_integer(qfs, &vac, flags | pf_hex_X, width, + precision, ant) ; + break ; + + case 'p': + if (phase == pfp_int_type) + phase = pfp_failed ; + else + phase = qfs_arg_integer(qfs, &vac, flags | pf_void_p, width, + precision, ant_ptr_t) ; + break ; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + if (phase == pfp_int_type) + phase = pfp_failed ; + else + phase = qfs_arg_float(qfs, &vac, start, format - start, + ant) ; + break ; + + default: /* unrecognised format */ + phase = pfp_failed ; + break ; + } ; + } ; + + if (phase == pfp_failed) + { + format = start ; /* back to the start */ + *qfs->ptr++ = *format++ ; /* copy the '%' */ + } ; + } ; + } ; + + *qfs->ptr = '\0' ; + + va_end(vac) ; +} ; + +/*------------------------------------------------------------------------------ + * %s handler -- tolerates NULL pointer + * + * Accepts: width + * precision -- ignored if < 0 + * pf_precision -- explicit precision + * + * Rejects: pf_commas -- "'" seen + * pf_plus -- "+" seen + * pf_space -- " " seen + * pf_zeros -- "0" seen + * pf_alt -- "#" seen + * + * Won't get: pf_hex + * pf_uc + * pf_unsigned + * pf_ptr + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags, + int width, int precision) +{ + int len ; + + if (flags != (flags & pf_precision)) + return pfp_failed ; + + if (precision < 0) /* make sure */ + { + precision = 0 ; + flags &= ~pf_precision ; + } ; + + len = (src != NULL) ? strlen(src) : 0 ; + if (((precision > 0) || (flags & pf_precision)) && (len > precision)) + len = precision ; + + qfs_append_justified_n(qfs, src, len, width) ; + + return pfp_done ; +} ; + +/*------------------------------------------------------------------------------ + * %c handler + * + * Accepts: width + * + * Rejects: precision + * pf_precision -- explicit precision + * pf_commas -- "'" seen + * pf_plus -- "+" seen + * pf_space -- " " seen + * pf_zeros -- "0" seen + * pf_alt -- "#" seen + * + * Won't get: pf_hex + * pf_uc + * pf_unsigned + * pf_ptr + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision) +{ + if ((flags != 0) || (precision != 0)) + return pfp_failed ; + + qfs_append_justified_n(qfs, (char*)&ch, 1, width) ; + + return pfp_done ; +} ; + +/*------------------------------------------------------------------------------ + * %d, %i, %u, %o, %x, %X and %p handler + * + * Accepts: pf_commas -- format with commas or '_' for hex (non-standard) + * ignored for octal. + * pf_minus -- left justify (any width will be -ve) + * pf_plus -- requires sign + * pf_space -- requires space or '-' + * pf_zeros -- zero fill to width + * pf_alt -- '0x' or '0X' for hex + * '0' for octal + * + * pf_precision -- precision specified + * + * pf_unsigned -- value is unsigned + * pf_ptr -- value is a void* pointer + * pf_hex -- render in hex + * pf_uc -- render hex in upper case + * + * and: all the number argument types. + * + * Rejects: ant == ant_long_double -- which is how the parser spots an + * erroneous %Ld for example. + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_integer(qf_str qfs, va_list* p_va, enum pf_flags flags, + int width, int precision, enum arg_num_type ant) +{ + uintmax_t u_val ; + intmax_t s_val ; + + /* Reject if seen an 'L' + */ + if (ant == ant_long_double) + return pfp_failed ; + + /* Special for hex with '0... if no explicit precision, set -1 for byte + * and -2 for everything else -- see qfs_number(). + */ + if ((flags & (pf_hex | pf_precision)) == pf_hex) + { + if ((flags & (pf_commas | pf_zeros)) == (pf_commas | pf_zeros)) + { + precision = (ant == ant_char) ? -1 : -2 ; + flags |= pf_precision ; + } ; + } ; + + /* It is assumed that all values can be mapped to a uintmax_t */ + confirm(sizeof(uintmax_t) >= sizeof(uintptr_t)) ; + + if (flags & pf_unsigned) + { + switch (ant) + { + case ant_char: + case ant_short: + case ant_int: + u_val = va_arg(*p_va, unsigned int) ; + break ; + + case ant_long: + u_val = va_arg(*p_va, unsigned long) ; + break ; + + case ant_long_long: + u_val = va_arg(*p_va, unsigned long long) ; + break ; + + case ant_intmax_t: + u_val = va_arg(*p_va, uintmax_t) ; + break ; + + case ant_size_t: + u_val = va_arg(*p_va, size_t) ; + break ; + + case ant_ptr_t: + u_val = va_arg(*p_va, uintptr_t) ; + break ; + + default: + zabort("impossible integer size") ; + } ; + + qfs_unsigned(qfs, u_val, flags, width, precision) ; + } + else + { + switch (ant) + { + case ant_char: + case ant_short: + case ant_int: + s_val = va_arg(*p_va, signed int) ; + break ; + + case ant_long: + s_val = va_arg(*p_va, signed long) ; + break ; + + case ant_long_long: + s_val = va_arg(*p_va, signed long long) ; + break ; + + case ant_intmax_t: + s_val = va_arg(*p_va, intmax_t) ; + break ; + + case ant_size_t: + s_val = va_arg(*p_va, ssize_t) ; + break ; + + case ant_ptr_t: + s_val = va_arg(*p_va, intptr_t) ; + break ; + + default: + zabort("impossible integer size") ; + } ; + + qfs_signed(qfs, s_val, flags, width, precision) ; + } ; + + return pfp_done ; +} ; + +/*------------------------------------------------------------------------------ + * %e, %E, %f, %F, %g, %G, %a and %A handler + * + * This uses the standard library sprintf() to do the business, so this is + * NOT async-signal-safe. This means that we get the full precision supported + * by the system ! Attempting to construct async-signal-safe conversion is + * doomed to failure, because any floating point operation may affect flags + * and other state in the processor :-( + * + * This operation is *NOT* async-signal-safe. + */ +static enum pf_phase +qfs_arg_float(qf_str qfs, va_list* p_va, const char* start, size_t flen, + enum arg_num_type ant) +{ + char format[flen + 1] ; + int want ; + int have ; + + memcpy(format, start, flen) ; + format[flen + 1] = '\0' ; + + have = qfs_left(qfs) + 1 ; + + if (ant == ant_default) + { + double val ; + val = va_arg(*p_va, double) ; + want = snprintf(qfs_ptr(qfs), have, format, val) ; + } + else + { + long double val ; + assert(ant == ant_long_double) ; + val = va_arg(*p_va, long double) ; + want = snprintf(qfs_ptr(qfs), have, format, val) ; + } ; + + if (want < 0) + return pfp_failed ; + + if (want < have) + qfs->ptr += want ; + else + qfs->ptr = qfs->end ; + + return pfp_done ; +} ; diff --git a/lib/qfstring.h b/lib/qfstring.h new file mode 100644 index 00000000..d9a51d21 --- /dev/null +++ b/lib/qfstring.h @@ -0,0 +1,163 @@ +/* Some string handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QFSTRING_H +#define _ZEBRA_QFSTRING_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#ifndef Inline +#define Inline static inline +#endif + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +/*============================================================================== + * These "qfstring" address the issues of dealing with *fixed* length + * strings, particularly where the string handling must be async-signal-safe. + * + * All operations that can possibly be async-signal-safe, are. Notable + * exception is anything involving floating point values -- because of the + * state contain in floating point status/option registers ! + */ + +typedef struct qf_str qf_str_t ; +typedef struct qf_str* qf_str ; + +/* When initialised a qf_string is set: + * + * str = start of string -- and this is never changed + * ptr = start of string -- this is moved as stuff is appended + * end = last possible position for terminating '\0' + * -- and this is never changed + */ +struct qf_str +{ + char* str ; /* start of string */ + char* ptr ; /* current position */ + char* end ; /* end of string */ +} ; + +/*------------------------------------------------------------------------------ + * Print format flags for number printing + */ +enum pf_flags +{ + pf_none = 0, + + /* The following correspond to the "flags" */ + pf_commas = 1 << 0, /* "'" seen */ + pf_plus = 1 << 1, /* "+" seen */ + pf_space = 1 << 2, /* " " seen */ + pf_zeros = 1 << 3, /* "0" seen */ + pf_alt = 1 << 4, /* "#" seen */ + + pf_precision = 1 << 7, /* '.' seen */ + + /* The following signal how to render the value */ + pf_oct = 1 << 8, /* octal */ + pf_hex = 1 << 9, /* hex */ + pf_uc = 1 << 10, /* upper-case */ + + /* The following signal the type of value */ + pf_ptr = 1 << 14, /* is a pointer */ + pf_unsigned = 1 << 15, /* unsigned value */ + + /* Common combination */ + pf_hex_x = pf_unsigned | pf_hex, + pf_hex_X = pf_unsigned | pf_hex | pf_uc, + + pf_void_p = pf_ptr | pf_hex_x, +} ; + +/*============================================================================== + * Functions + */ + +extern void qfs_init(qf_str qfs, char* str, size_t size) ; +extern void qfs_init_as_is(qf_str qfs, char* str, size_t size) ; + +extern void qfs_term(qf_str qfs, const char* src) ; + +Inline int qfs_len(qf_str qfs) ; +Inline void* qfs_ptr(qf_str qfs) ; +Inline int qfs_left(qf_str qfs) ; + +extern void qfs_append(qf_str qfs, const char* src) ; +extern void qfs_append_n(qf_str qfs, const char* src, size_t n) ; + +extern void qfs_append_ch_x_n(qf_str qfs, char ch, size_t n) ; +extern void qfs_append_justified(qf_str qfs, const char* src, int width) ; +extern void qfs_append_justified_n(qf_str qfs, const char* src, + size_t n, int width) ; + +extern void qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags, + int width, int precision) ; +extern void qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags, + int width, int precision) ; +extern void qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, + int width, int precision) ; + +extern void qfs_printf(qf_str qfs, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern void qfs_vprintf(qf_str qfs, const char *format, va_list args) ; + +/*============================================================================== + * The Inline functions. + */ + +/*------------------------------------------------------------------------------ + * Current length of qf_str, not counting the terminating '\0'. + */ +Inline int +qfs_len(qf_str qfs) +{ + return qfs->ptr - qfs->str ; +} ; + +/*------------------------------------------------------------------------------ + * Address of the terminating '\0'. + */ +Inline void* +qfs_ptr(qf_str qfs) +{ + return qfs->ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Current space left in the qstr, given what has been reserved for terminating + * '\0' and any other reservation. + */ +Inline int +qfs_left(qf_str qfs) +{ + return qfs->end - qfs->ptr ; +} ; + +#endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qiovec.c b/lib/qiovec.c new file mode 100644 index 00000000..546dfcb0 --- /dev/null +++ b/lib/qiovec.c @@ -0,0 +1,261 @@ +/* Flexible iovec handler + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "zebra.h" + +#include "memory.h" +#include "zassert.h" +#include "miyagi.h" + +#include "qiovec.h" + +/*============================================================================== + * Initialise, allocate and reset qiovec + */ + +/*------------------------------------------------------------------------------ + * Initialise new qiovec -- allocate if required. + * + * This is for initialising a new structure. Any pre-exiting contents are + * lost. + * + * Returns: address of qiovec + */ +extern qiovec +qiovec_init_new(qiovec viov) +{ + if (viov == NULL) + viov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ; + else + memset(viov, 0, sizeof(struct qiovec)) ; + + /* Zeroising has set: + * + * vec = NULL - no array, yet + * writing = false -- no writing going on + * + * i_get = 0 -- next entry to get + * i_put = 0 -- next entry to put + * + * i_alloc = 0; -- no entries allocated + * + * Nothing more is required. + */ + + return viov ; +} ; + +/*------------------------------------------------------------------------------ + * Reset qiovec (if any) -- release body and (if required) the structure. + * + * Returns: address of qiovec (if any) -- NULL if structure released + */ +extern qiovec +qiovec_reset(qiovec viov, bool free_structure) +{ + if (viov != NULL) + { + if (viov->vec != NULL) + XFREE(MTYPE_QIOVEC_VEC, viov->vec) ; + + if (free_structure) + XFREE(MTYPE_QIOVEC, viov) ; /* sets viov = NULL */ + else + qiovec_init_new(viov) ; /* re-initialise */ + } ; + + return viov ; +} ; + +/*------------------------------------------------------------------------------ + * Clear given qiovec. + */ +extern void +qiovec_clear(qiovec viov) +{ + viov->i_get = 0 ; + viov->i_put = 0 ; + viov->writing = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Push item to given qiovec + * + * NB: avoids pushing zero length items. + */ +extern void +qiovec_push(qiovec viov, const void* base, size_t len) +{ + struct iovec* p_iov ; + + if (len == 0) + return ; + + if (viov->i_put >= viov->i_alloc) + { + size_t size ; + assert(viov->i_put == viov->i_alloc) ; + + assert( ((viov->i_alloc == 0) && (viov->vec == NULL)) + || ((viov->i_alloc != 0) && (viov->vec != NULL)) ) ; + + if (viov->i_get > 200) /* keep in check */ + { + size = (viov->i_put - viov->i_get) * sizeof(struct iovec) ; + if (size != 0) + memmove(viov->vec, &viov->vec[viov->i_get], size) ; + viov->i_put -= viov->i_get ; + viov->i_get = 0 ; + } + else + { + viov->i_alloc += 100 ; /* a sizable chunk */ + + size = viov->i_alloc * sizeof(struct iovec) ; + if (viov->vec == NULL) + viov->vec = XMALLOC(MTYPE_QIOVEC_VEC, size) ; + else + viov->vec = XREALLOC(MTYPE_QIOVEC_VEC, viov->vec, size) ; + } ; + } ; + + p_iov = &viov->vec[viov->i_put++] ; + + p_iov->iov_base = miyagi(base) ; + p_iov->iov_len = len ; +} ; + +/*------------------------------------------------------------------------------ + * Write given qiovec -- assuming NON-BLOCKING. + * + * Does nothing if the qiovec is empty. + * + * Loops internally if gets EINTR. + * + * When there is nothing left to output, resets the i_put & i_get to zero. + * + * Returns: > 0 => one or more bytes left to output + * 0 => all done -- zero bytes left to output + * -1 => failed -- see errno + */ +extern int +qiovec_write_nb(int fd, qiovec viov) +{ + int n ; + int l ; + + n = viov->i_put - viov->i_get ; + + l = iovec_write_nb(fd, &viov->vec[viov->i_get], n) ; + + if (l == 0) + { + viov->writing = 0 ; + viov->i_get = viov->i_put = 0 ; + } + else + { + viov->writing = 1 ; + viov->i_get += (n - l) ; + } ; + + return l ; +} ; + +/*------------------------------------------------------------------------------ + * Write given iovec -- assuming NON-BLOCKING. + * + * Does nothing if given zero iovec entries (and array may be NULL). + * + * Loops internally if gets EINTR. + * + * If does not manage to write everything, then: + * + * -- updates the length field of all entries up to and including the + * last one for which data has been written. + * + * -- updates the address field of the first entry that still has some + * data to be output. + * + * Can call this again with the same 'p_iov' and the same 'n' -- the entries + * which have zero lengths will be stepped over. Output will continue from + * where it left off. + * + * Alternatively, if this returns 'l', then do "p_iov += n - l", and set + * "n = l" before calling this again. + * + * Returns: > 0 => number of entries left to output + * 0 => all done -- nothing left to output + * -1 => failed -- see errno + */ +extern int +iovec_write_nb(int fd, struct iovec p_iov[], int n) +{ + ssize_t ret ; + + assert(n >= 0) ; + + /* Skip past any leading zero length entries */ + while ((n > 0) && (p_iov->iov_len == 0)) + { + ++p_iov ; + --n ; + } ; + + while (n > 0) + { + ret = writev(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ; + + if (ret > 0) + { + while (ret > 0) + { + if (ret >= (ssize_t)p_iov->iov_len) + { + assert(n > 0) ; + ret -= p_iov->iov_len ; + p_iov->iov_len = 0 ; + ++p_iov ; + --n ; + } + else + { + p_iov->iov_base = (char*)p_iov->iov_base + ret ; + p_iov->iov_len -= ret ; + ret = 0 ; + } ; + } ; + } + else if (ret == 0) + break ; /* not sure can happen... but + cannot assume will go away */ + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; /* failed */ + } ; + } ; + + return n ; +} ; diff --git a/lib/qiovec.h b/lib/qiovec.h new file mode 100644 index 00000000..ee03d2f2 --- /dev/null +++ b/lib/qiovec.h @@ -0,0 +1,99 @@ +/* Flexible iovec -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QIOVEC_H +#define _ZEBRA_QIOVEC_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Flexible size "struct iovec" + * + * NB: a completely zero structure is a valid, empty qiovec. + */ +typedef struct qiovec* qiovec ; +typedef struct qiovec qiovec_t ; +struct qiovec +{ + struct iovec* vec ; /* the actual iovec array */ + + bool writing ; /* started, but not finished */ + + unsigned i_get ; /* next entry to get */ + unsigned i_put ; /* next entry to put */ + + unsigned i_alloc ; /* number of entries allocated */ +} ; + +/*============================================================================== + * Functions + */ + +extern qiovec qiovec_init_new(qiovec qiov) ; +extern qiovec qiovec_reset(qiovec qiov, bool free_structure) ; + +#define qiovec_reset_keep(qiov) qiovec_reset(qiov, 0) +#define qiovec_reset_free(qiov) qiovec_reset(qiov, 1) + +Inline bool qiovec_empty(qiovec qiov) ; +extern void qiovec_clear(qiovec qiov) ; +extern void qiovec_push(qiovec qiov, const void* base, size_t len) ; +extern int qiovec_write_nb(int fd, qiovec qiov) ; + +extern int iovec_write_nb(int fd, struct iovec* p_iov, int n) ; +Inline void iovec_set(struct iovec* p_iov, const void* base, size_t len) ; + +/*------------------------------------------------------------------------------ + * Is given qiov empty ? + * + * NB: arranges to never add zero length entries to the iovec vector, so + * is empty when there are no active entries. + */ +Inline bool +qiovec_empty(qiovec qiov) +{ + return (qiov->i_get == qiov->i_put) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a given struct iovec + * + * Gets around the fact that the standard structure does not have a const + * pointer ! + */ +#include "miyagi.h" + +Inline void +iovec_set(struct iovec* p_iov, const void* base, size_t len) +{ + p_iov->iov_base = miyagi(base) ; + p_iov->iov_len = len ; +} ; + +#endif /* _ZEBRA_QIOVEC_H */ diff --git a/lib/qlib_init.c b/lib/qlib_init.c new file mode 100644 index 00000000..c44de575 --- /dev/null +++ b/lib/qlib_init.c @@ -0,0 +1,98 @@ +/* Quagga library initialise/closedown -- functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qlib_init.h" +#include "zassert.h" +#include "memory.h" +#include "qpthreads.h" +#include "qpselect.h" +#include "thread.h" +#include "privs.h" +#include "mqueue.h" +#include "pthread_safe.h" + +/*============================================================================== + * Quagga Library Initialise/Closedown + * + * This gathers together the essential initialisation and closedown for the + * library. This ensures that any changes in the library are contained here, + * and do not require changes in all users of the library. + * + * There are two stages of initialisation: + * + * 1) first stage + * + * this is expected to be called before the program does anything at all. + * + * This performs all initialisation required to support asserts, logging, + * basic I/O (but not the remote console), trap signals... and so on. + * + * After this has been done, the system is in good shape to deal with + * command line options, configuration files and so on. + * + * 2) second stage + * + * this is expected to be called before the program does any serious work. + * + * This performs all initialisation required to support socket I/O, + * thread handling, timers, and so on. + * + * In particular, at this stage the system is set into Pthread Mode, if + * required. No pthreads may be started before this. Up to this point + * the system operates in non-Pthread Mode -- all mutexes are implicitly + * free. + * + * There is one stage of closedown. This is expected to be called last, and + * is passed the exit code. + * + * + */ + +void +qlib_init_first_stage(void) +{ + qps_start_up() ; +} + +void +qlib_init_second_stage(int pthreads) +{ + qpt_set_qpthreads_enabled(pthreads); + memory_init_r(); + thread_init_r(); + zprivs_init_r(); + mqueue_initialise(); + safe_init_r(); +} + + +void +qexit(int exit_code) +{ + safe_finish(); + mqueue_finish(); + zprivs_finish(); + thread_finish(); + memory_finish(); + exit (exit_code); +} + + diff --git a/lib/qlib_init.h b/lib/qlib_init.h new file mode 100644 index 00000000..0d5fbd7e --- /dev/null +++ b/lib/qlib_init.h @@ -0,0 +1,40 @@ +/* Quagga library initialise/closedown -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QLIB_INIT_H +#define _ZEBRA_QLIB_INIT_H + +/*============================================================================== + * Quagga Library Initialise/Closedown + * + * This gathers together the essential initialisation and closedown for the + * library. + * + * See qlib_init.c. + */ + +extern void qlib_init_first_stage(void) ; + +extern void qlib_init_second_stage(int pthreads) ; + +extern void qexit(int exit_code) ; + +#endif /* _ZEBRA_QLIB_INIT_H */ diff --git a/lib/qpnexus.c b/lib/qpnexus.c new file mode 100644 index 00000000..3b014be2 --- /dev/null +++ b/lib/qpnexus.c @@ -0,0 +1,327 @@ +/* Quagga Pthreads support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <zebra.h> +#include <stdbool.h> + +#include "qpnexus.h" +#include "memory.h" +#include "thread.h" +#include "sigevent.h" + +/* prototypes */ +static void* qpn_start(void* arg); +static void qpn_in_thread_init(qpn_nexus qpn); + +/*============================================================================== + * Quagga Nexus Interface -- qpn_xxxx + * + + */ + +/*============================================================================== + * Initialisation, add hook, free etc. + * + */ + +/*------------------------------------------------------------------------------ + * Initialise a nexus -- allocating it if required. + * + * If main_thread is set then no new thread will be created + * when qpn_exec() is called, instead the finite state machine will be + * run in the calling thread. The main thread will only block the + * message queue's signal. Non-main threads will block most signals. + * + * Returns the qpn_nexus. + */ +extern qpn_nexus +qpn_init_new(qpn_nexus qpn, bool main_thread) +{ + if (qpn == NULL) + qpn = XCALLOC(MTYPE_QPN_NEXUS, sizeof(struct qpn_nexus)) ; + else + memset(qpn, 0, sizeof(struct qpn_nexus)) ; + + qpn->selection = qps_selection_init_new(qpn->selection); + qpn->pile = qtimer_pile_init_new(qpn->pile); + qpn->queue = mqueue_init_new(qpn->queue, mqt_signal_unicast); + qpn->main_thread = main_thread; + qpn->start = qpn_start; + + if (main_thread) + qpn->thread_id = qpt_thread_self(); + + return qpn; +} + +/*------------------------------------------------------------------------------ + * Add a hook function to the given nexus. + */ +extern void +qpn_add_hook_function(qpn_hook_list list, void* hook) +{ + passert(list->count < qpn_hooks_max) ; + list->hooks[list->count++] = hook ; +} ; + +/*------------------------------------------------------------------------------ + * Reset given nexus and, if required, free the nexus structure. + * + * Free timers, selection, message queue and its thread signal. + * + * Leaves all pointers to these things NULL -- which generally means that the + * object is empty or otherwise out of action. + */ +extern qpn_nexus +qpn_reset(qpn_nexus qpn, bool free_structure) +{ + qps_file qf; + qtimer qtr; + + if (qpn == NULL) + return NULL; + + /* timers and the pile */ + if (qpn->pile != NULL) + { + while ((qtr = qtimer_pile_ream(qpn->pile, 1))) + qtimer_free(qtr); + qpn->pile = NULL ; + } + + /* files and selection */ + if (qpn->selection != NULL) + { + while ((qf = qps_selection_ream(qpn->selection, 1))) + qps_file_free(qf); + qpn->selection = NULL ; + } + + if (qpn->queue != NULL) + qpn->queue = mqueue_reset(qpn->queue, 1); + + if (qpn->mts != NULL) + qpn->mts = mqueue_thread_signal_reset(qpn->mts, 1); + + if (free_structure) + XFREE(MTYPE_QPN_NEXUS, qpn) ; /* sets qpn = NULL */ + + return qpn ; +} ; + +/*============================================================================== + * Execution of a nexus + */ + +/*------------------------------------------------------------------------------ + * If not main qpthread create new qpthread. + * + * For all qpthreads: start the thread ! + */ +extern void +qpn_exec(qpn_nexus qpn) +{ + if (qpn->main_thread) + qpn->start(qpn); + else + qpt_thread_create(qpn->start, qpn, NULL) ; +} ; + +/*------------------------------------------------------------------------------ + * Pthread routine + * + * Processes: + * + * 1) Main thread only -- signals. + * + * 2) High priority pending work -- event hooks. + * + * 3) Messages coming from other pthreads -- mqueue_queue. + * + * 4) All priority pending work -- event hooks. + * + * 5) I/O -- qpselect + * + * This deals with all active sockets for read/write/connect/accept. + * + * Each time a socket is readable, one message is read and dispatched. + * The pselect timeout is set to be when the next timer is due. + * + * 6) Timers -- qtimers + * + */ +static void* +qpn_start(void* arg) +{ + qpn_nexus qpn = arg; + mqueue_block mqb; + int actions; + qtime_mono_t now ; + qtime_t max_wait ; + unsigned i; + unsigned done ; + unsigned wait ; + + /* now in our thread, complete initialisation */ + qpn_in_thread_init(qpn); + + /* custom in-thread initialization */ + for (i = 0; i < qpn->in_thread_init.count ;) + ((qpn_init_function*)(qpn->in_thread_init.hooks[i++]))() ; + + /* Until required to terminate, loop */ + done = 1 ; + while (!qpn->terminate) + { + /* Signals are highest priority -- only execute for main thread */ + if (qpn->main_thread) + done |= quagga_sigevent_process() ; + + /* Foreground hooks, if any. */ + for (i = 0; i < qpn->foreground.count ;) + done |= ((qpn_hook_function*)(qpn->foreground.hooks[i++]))() ; + + /* take stuff from the message queue + * + * If nothing done the last time around the loop then may wait this + * time if the queue is empty first time through. + */ + wait = (done == 0) ; /* may wait this time only if nothing + found to do on the last pass */ + done = 0 ; + do + { + mqb = mqueue_dequeue(qpn->queue, wait, qpn->mts) ; + if (mqb == NULL) + break; + + mqb_dispatch(mqb, mqb_action); + + ++done ; /* done another */ + wait = 0 ; /* done something, so turn off wait */ + } while (done < 200) ; + + /* block for some input, output, signal or timeout + * + * wait will be true iff did nothing the last time round the loop, and + * not found anything to be done up to this point either. + */ + if (wait) + max_wait = qtimer_pile_top_wait(qpn->pile, QTIME(MAX_PSELECT_WAIT)) ; + else + max_wait = 0 ; + + actions = qps_pselect(qpn->selection, max_wait) ; + done |= actions ; + + if (wait) + mqueue_done_waiting(qpn->queue, qpn->mts); + + /* process I/O actions */ + while (actions) + actions = qps_dispatch_next(qpn->selection) ; + + /* process timers */ + now = qt_get_monotonic() ; + while (qtimer_pile_dispatch_next(qpn->pile, now)) + done = 1 ; + + /* If nothing done in this pass, see if anything in the background */ + if (done == 0) + for (i = 0; i < qpn->background.count ; ++i) + done |= ((qpn_hook_function*)(qpn->background.hooks[i]))() ; + } ; + + /* custom in-thread finalization */ + for (i = qpn->in_thread_final.count; i > 0 ;) + ((qpn_init_function*)(qpn->in_thread_final.hooks[--i]))() ; + + return NULL; +} + +/*------------------------------------------------------------------------------ + * Now running in our thread, do common initialisation + */ +static void +qpn_in_thread_init(qpn_nexus qpn) +{ + sigset_t newmask; + + qpn->thread_id = qpt_thread_self(); + + if (qpn->main_thread) + { + /* Main thread, block the message queue's signal */ + sigemptyset (&newmask); + sigaddset (&newmask, SIGMQUEUE); + } + else + { + /* + * Not main thread. Block most signals, but be careful not to + * defer SIGTRAP because doing so breaks gdb, at least on + * NetBSD 2.0. Avoid asking to block SIGKILL, just because + * we shouldn't be able to do so. Avoid blocking SIGFPE, + * SIGILL, SIGSEGV, SIGBUS as this is undefined by POSIX. + * Don't block SIGPIPE so that is gets ignored on this thread. + */ + sigfillset (&newmask); + sigdelset (&newmask, SIGTRAP); + sigdelset (&newmask, SIGKILL); + sigdelset (&newmask, SIGPIPE); + sigdelset (&newmask, SIGFPE); + sigdelset (&newmask, SIGILL); + sigdelset (&newmask, SIGSEGV); + sigdelset (&newmask, SIGBUS); + } + + if (qpthreads_enabled) + qpt_thread_sigmask(SIG_BLOCK, &newmask, NULL); + else + { + if (sigprocmask(SIG_BLOCK, &newmask, NULL) != 0) + zabort_errno("sigprocmask failed") ; + } + + /* Now we have thread_id and mask, prep for using message queue. */ + if (qpn->queue != NULL) + qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, SIGMQUEUE); + if (qpn->selection != NULL) + qps_set_signal(qpn->selection, SIGMQUEUE, newmask); +} + +/*------------------------------------------------------------------------------ + * Ask the thread to terminate itself quickly and cleanly. + * + * Does nothing if terminate already set. + */ +void +qpn_terminate(qpn_nexus qpn) +{ + if (!qpn->terminate) + { + qpn->terminate = true ; + + /* wake up any pselect */ + if (qpthreads_enabled) + qpt_thread_signal(qpn->thread_id, SIGMQUEUE); + } ; +} diff --git a/lib/qpnexus.h b/lib/qpnexus.h new file mode 100644 index 00000000..0cf5a824 --- /dev/null +++ b/lib/qpnexus.h @@ -0,0 +1,158 @@ +/* Quagga Pthreads support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QPNEXUS_H +#define _ZEBRA_QPNEXUS_H + +#include <stdint.h> +#include <time.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> + +#include "zassert.h" +#include "qpthreads.h" +#include "qtimers.h" +#include "mqueue.h" +#include "qpselect.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Quagga Nexus Interface -- qpn_xxxx + * + * Object to hold, a qpthread, a qps_selection, a qtimer_pile, a mqueue_queue + * together with the thread routine to poll and dispatch their respective + * action routines. + * + */ + +/* maximum time in seconds to sit in a pselect */ +#define MAX_PSELECT_WAIT 10 + +/* signal for message queues */ +#define SIGMQUEUE SIGUSR2 + +/* number of hooks per hook list */ +enum { qpn_hooks_max = 4 } ; + +/*============================================================================== + * Data Structures. + */ + +typedef int qpn_hook_function(void) ; /* dispatch of tasks */ +typedef int qpn_init_function(void) ; /* start/stop work */ + +typedef struct qpn_hook_list* qpn_hook_list ; +struct qpn_hook_list +{ + void* hooks[qpn_hooks_max] ; + unsigned count ; +} ; + +typedef struct qpn_nexus* qpn_nexus ; + +struct qpn_nexus +{ + /* set true to terminate the thread (eventually) */ + bool terminate; + + /* true if this is the main thread */ + bool main_thread; + + /* thread ID */ + qpt_thread_t thread_id; + + /* pselect handler */ + qps_selection selection; + + /* timer pile */ + qtimer_pile pile; + + /* message queue */ + mqueue_queue queue; + mqueue_thread_signal mts; + + /* qpthread routine, can override */ + void* (*start)(void*); + + /* in-thread initialise, can override. Called within the thread after all + * other initialisation just before thread loop + * + * These are typedef int qpn_init_function(void). + * + * These are executed in the order given. + */ + struct qpn_hook_list in_thread_init ; + + /* in-thread finalise, can override. Called within thread just before + * thread dies. Nexus components all exist but thread loop is no longer + * executed + * + * These are typedef int qpn_init_function(void). + * + * These are executed in the reverse of the order given. + */ + struct qpn_hook_list in_thread_final ; + + /* in-thread queue(s) of events or other work. + * + * The hook function(s) are called in the qpnexus loop, at the top of the + * loop. So in addition to the mqueue, I/O, timers and any background stuff, + * the thread may have other queue(s) of things to be done. + * + * These are typedef int qpn_hook_function(void). + * + * Hook function can process some queue(s) of things to be done. It does not + * have to empty its queues, but it MUST only return 0 if all queues are now + * empty. + */ + struct qpn_hook_list foreground ; + + /* in-thread background queue(s) of events or other work. + * + * The hook functions are called at the bottom of the qpnexus loop, but only + * when there is absolutely nothing else to do. + * + * These are typedef int qpn_hook_function(void). + * + * The hook function should do some unit of background work (if there is any) + * and return. MUST return 0 iff there is no more work to do. + */ + struct qpn_hook_list background ; +}; + +/*============================================================================== + * Functions + */ + +extern qpn_nexus qpn_init_new(qpn_nexus qpn, bool main_thread); +extern void qpn_add_hook_function(qpn_hook_list list, void* hook) ; +extern void qpn_exec(qpn_nexus qpn); +extern void qpn_terminate(qpn_nexus qpn); +extern qpn_nexus qpn_reset(qpn_nexus qpn, bool free_structure); + +#define qpn_reset_free(qpn) qpn_reset(qpn, 1) +#define qpn_reset_keep(qpn) qpn_reset(qpn, 0) + +#endif /* _ZEBRA_QPNEXUS_H */ diff --git a/lib/qpselect.c b/lib/qpselect.c new file mode 100644 index 00000000..fd20d421 --- /dev/null +++ b/lib/qpselect.c @@ -0,0 +1,1414 @@ +/* Quagga pselect support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <signal.h> +#include <string.h> + +#include "zassert.h" +#include "qpselect.h" +#include "qpthreads.h" +#include "qtime.h" +#include "memory.h" +#include "vector.h" + +enum { qdebug = +#ifdef QDEBUG + 1 +#else + 0 +#endif +}; + +/*============================================================================== + * Quagga pselect -- qps_xxxx + * + * Here and in qpselect.h is a data structure for managing multiple file + * descriptors and running pselect to wait for I/O activity and to multiplex + * between the file descriptors. + * + * The qps_selection structure manages a collection of file descriptors which + * are to be waited on together in a pselect statement. + * + * NB: it is ASSUMED that a qps_selection will be private to the thread in + * which it is created and used. + * + * There is NO mutex handling here. + * + * This supports pselect, so supports: + * + * * waiting for file descriptors, which may each be expecting any combination + * of error/read/write events. + * + * Files may be added or removed from the selection. Files in the selection + * may then be enabled/disabled for any combination of error/read/write + * "mode" events. + * + * * a timeout *time* + * + * This is a qtime monotonic time at which to time out. (This is unlike + * pselect() itself, which takes a timeout interval.) + * + * Infinite timeouts are not supported. + * + * * an optional signal number and sigmask + * + * So that a signal may be used to interrupt a waiting pselect. + * + * For this to work there must be a signal which is generally masked, and + * is unmasked for the duration of the pselect. + * + * When a pselect returns there may be a number of files with events pending. + * The qps_dispatch_next() calls the action routine for the next event to be + * dealt with. Events are dispatched in the order: error, read and write, and + * then in file descriptor order. (So all error events in fd order, then all + * read events, and so on.) + * + * Note that at no time are any modes automatically disabled. So the system is + * level triggered. So, for example, a read event that is not dealt with will + * be triggered again on the next pselect -- unless the read mode is explicitly + * disabled for the file. + * + * Action Functions + * ---------------- + * + * There is a separate action function for each mode. Each file has its own + * set of action functions -- so these may be used to implement a form of + * state machine for the file. + * + * When the action function is called it is passed the qps_file structure and + * the file_info pointer from that structure. + * + * During an action function modes may be enabled/disabled, actions changed, + * the file removed from the selection... there are no restrictions. + */ + +/*============================================================================== + * qps_selection handling + */ + +/* See qps_make_super_set_map() below. */ +static short fd_byte_count[FD_SETSIZE] ; /* number of bytes for fds 0..fd */ + +/* Forward references */ +static void qps_make_super_set_map(void) ; +static void qps_selection_re_init(qps_selection qps) ; +static qps_file qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert) ; +static void qps_file_remove(qps_selection qps, qps_file qf) ; +static void qps_super_set_zero(fd_super_set* p_set, int n) ; +static int qps_super_set_cmp(fd_super_set* p_a, fd_super_set* p_b, int n) ; +static int qps_next_fd_pending(fd_super_set* pending, int fd, int fd_last) ; +static void qps_selection_validate(qps_selection qps) ; + +/*------------------------------------------------------------------------------ + * Initialise a selection -- allocating it if required. + * + * Returns the qps_selection. + */ + +extern void +qps_start_up(void) +{ + qps_make_super_set_map() ; /* map the fd_super_set */ +} ; + +/*------------------------------------------------------------------------------ + * Initialise a selection -- allocating it if required. + * + * Returns the qps_selection. + * + * NB: when initialising an existing selection which has been used before, it + * is the caller's responsibility to have dealt with its contents before + * calling this. + */ +qps_selection +qps_selection_init_new(qps_selection qps) +{ + if (qps == NULL) + qps = XCALLOC(MTYPE_QPS_SELECTION, sizeof(struct qps_selection)) ; + + qps_selection_re_init(qps) ; + + return qps ; +} ; + +/*------------------------------------------------------------------------------ + * Re-initialise a selection. + * + * It is the caller's responsibility to have dealt with any active files before + * calling this. + */ +static void +qps_selection_re_init(qps_selection qps) +{ + memset(qps, 0, sizeof(struct qps_selection)) ; + + /* Zeroising initialises: + * + * fd_count -- no fd's yet + * fd_direct -- not direct lookup + * + * files -- empty vector + * + * enabled_count -- no fd's enabled in any mode + * enabled -- empty bit vectors + * + * tried_fd_last -- nothing tried yet + * tried_count -- nothing tried yet + * results -- empty bit vectors + * + * pend_count -- no results to dispatch + * pend_mnum -- unset + * pend_fd -- unset + * + * signum -- no signal to be enabled + * sigmask -- unset + * + * So nothing much else to do: + */ + qps->fd_last = -1 ; /* not an fd in sight. */ +} ; + +/*------------------------------------------------------------------------------ + * Add given file to the selection, setting its fd and pointer to further + * file information. All modes are disabled. + * + * This initialises most of the qps_file structure, but not the actions. + * + * Adding a file using the same fd as an existing file is a FATAL error. + * + * Adding a file which is already a member a selection is a FATAL error. + */ +void +qps_add_file(qps_selection qps, qps_file qf, int fd, void* file_info) +{ + passert(qf->selection == NULL) ; + + qf->selection = qps ; + + qf->file_info = file_info ; + qf->fd = fd ; + + qf->enabled_bits = 0 ; + + qps_file_lookup_fd(qps, fd, qf) ; /* Add. */ +} ; + +/*------------------------------------------------------------------------------ + * Remove given file from its selection, if any. + * + * It is the callers responsibility to ensure that the file is in a suitable + * state to be removed from the selection. + * + * When the file is removed it is disabled in all modes. + */ +void +qps_remove_file(qps_file qf) +{ + if (qf->selection != NULL) + qps_file_remove(qf->selection, qf) ; +} ; + +/*------------------------------------------------------------------------------ + * Ream (another) file out of the selection. + * + * If selection is empty, release the qps_selection structure, if required. + * + * See: #define qps_selection_ream_free(qps) + * #define qps_selection_ream_keep(qps) + * + * Useful for emptying out and discarding a selection: + * + * while ((qf = qps_selection_ream_free(qps))) + * ... do what's required to release the qps_file + * + * The file is removed from the selection before being returned. + * + * Returns NULL when selection is empty (and has been released, if required). + * + * If the selection is not released, it may be reused without reinitialisation. + * + * NB: once reaming has started, the selection MUST NOT be used for anything, + * and the process MUST be run to completion. + */ +qps_file +qps_selection_ream(qps_selection qps, int free_structure) +{ + qps_file qf ; + + qf = vector_get_last_item(&qps->files) ; + if (qf != NULL) + qps_file_remove(qps, qf) ; + else + { + passert(qps->fd_count == 0) ; + + if (free_structure) + XFREE(MTYPE_QPS_SELECTION, qps) ; + else + qps_selection_re_init(qps) ; + } ; + + return qf ; +} ; + +/*------------------------------------------------------------------------------ + * Set the signal mask for the selection. + * + * This supports the unmasking of a single signal for the duration of the + * pselect operation. + * + * It is assumed that the set of signals generally masked by a thread is + * essentially static. So this function is passed that set. (So the sigmask + * argument must have the signum signal masked.) + * + * If the set of signals masked by the thread changes, then this function + * should be called again. + * + * Setting a signum == 0 turns OFF the use of the sigmask. + */ +void +qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) +{ + qps->signum = signum ; + + if (signum != 0) + { + assert(sigismember(&sigmask, signum)) ; + sigdelset(&sigmask, signum) ; + qps->sigmask = sigmask ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Execute a pselect for the given selection -- subject to the given maximum + * time to wait. + * + * There is no support for an infinite timeout. + * + * Returns: -1 => EINTR occurred -- ie a signal has gone off + * 0 => hit timeout -- no files are ready + * > 0 => there are this many files ready in one or more modes + * + * All other errors are FATAL. + * + * The qps_dispatch_next() processes the returns from pselect(). + */ +int +qps_pselect(qps_selection qps, qtime_t max_wait) +{ + struct timespec ts ; + qps_mnum_t mnum ; + fd_set* p_fds[qps_mnum_count] ; + int n ; + + if (qdebug) + qps_selection_validate(qps) ; + + /* If there is stuff still pending, tidy up by zeroising the result */ + /* vectors. This is to make sure that when bits are copied from */ + /* the enabled vectors, there are none from a previous run of pselect */ + /* left hanging about. (pselect SHOULD ignore everything above the */ + /* given count of fds -- but it does no harm to be tidy, and should */ + /* not have to do this often.) */ + if (qps->pend_count != 0) + qps_super_set_zero(qps->results, qps_mnum_count) ; + + /* Prepare the argument/result bitmaps */ + /* Capture pend_mnum and tried_count[] */ + + n = (qps->fd_last >= 0) + ? fd_byte_count[qps->fd_last] /* copy up to last sig. byte */ + : 0 ; + + qps->pend_mnum = qps_mnum_count ; + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if ((qps->tried_count[mnum] = qps->enabled_count[mnum]) != 0) + { + dassert(n != 0) ; /* n == 0 => no fds ! */ + + memcpy(&(qps->results[mnum].bytes), &(qps->enabled[mnum].bytes), n) ; + p_fds[mnum] = &(qps->results[mnum].fdset) ; + + if (mnum < qps->pend_mnum) + qps->pend_mnum = mnum ; /* collect first active mode */ + } + else + p_fds[mnum] = NULL ; + + /* Capture tried_fd_last and set initial pend_fd. */ + qps->tried_fd_last = qps->fd_last ; + qps->pend_fd = 0 ; + + /* Make sure not trying to do something stupid */ + if (max_wait < 0) + max_wait = 0 ; + + /* Finally ready for the main event */ + n = pselect(qps->fd_last + 1, p_fds[qps_read_mnum], + p_fds[qps_write_mnum], + p_fds[qps_error_mnum], + qtime2timespec(&ts, max_wait), + (qps->signum != 0) ? &qps->sigmask : NULL) ; + + /* If have something, set and return the pending count. */ + if (n > 0) + { + assert(qps->pend_mnum < qps_mnum_count) ; /* expected something */ + + return qps->pend_count = n ; /* set and return pending count */ + } ; + + /* Nothing pending at all */ + qps->pend_count = 0 ; /* qps_dispatch_next() does nothing */ + + /* Flush the results vectors -- not apparently done if n <= 0) */ + qps_super_set_zero(qps->results, qps_mnum_count) ; + + /* Return appropriately, if we can */ + if ((n == 0) || (errno == EINTR)) + return n ; + + zabort_errno("Failed in pselect") ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch the next errored/readable/writeable file, as returned by the + * most recent qps_pselect(). + * + * Processes the errored files, then the readable and lastly the writeable. + * + * Processes one file per call of this function, by invoking the file's + * "action" routine. + * + * If a given file is ready in more than one mode, all modes will be processed, + * unless the action routine for one mode disables the file for other modes, or + * removes it from the selection. + * + * Returns the number of files left to process (after the one just processed). + * + * NB: clears result bits as it finds them -- so at end of process, the + * result bit vectors should be zeroised again. Also, this allows the + * search to proceed from the last known fd -- won't find it again ! + */ +int +qps_dispatch_next(qps_selection qps) +{ + int fd ; + qps_file qf ; + qps_mnum_t mnum ; + + if (qdebug) + qps_selection_validate(qps) ; + + if (qps->pend_count == 0) + return 0 ; /* quit immediately of nothing to do. */ + + fd = qps->pend_fd ; /* look for fd >= this */ + mnum = qps->pend_mnum ; /* starting with this mode */ + + dassert( (mnum >= 0) && (mnum < qps_mnum_count) + && (qps->tried_count[mnum] != 0) + && (qps->pend_count > 0) + && (qps->tried_fd_last >= 0)) ; + + while (1) + { + fd = qps_next_fd_pending(&(qps->results[mnum]), fd, qps->tried_fd_last) ; + if (fd >= 0) + break ; /* easy if have another fd in current mode. */ + + do /* step to next mode that was not empty */ + { + qps->tried_count[mnum] = 0 ; /* tidy up as we go */ + ++mnum ; + if (mnum >= qps_mnum_count) + zabort("Unexpectedly ran out of pending stuff") ; + } while (qps->tried_count[mnum] == 0) ; + + qps->pend_mnum = mnum ; /* update mode */ + fd = 0 ; /* back to the beginning */ + } ; + + qps->pend_count -= 1 ; /* one less pending */ + qps->pend_fd = fd ; /* update scan */ + + qf = qps_file_lookup_fd(qps, fd, NULL) ; + + dassert( ((qf->enabled_bits && qps_mbit(mnum)) != 0) && + (qf->actions[mnum] != NULL) ) ; + + qf->actions[mnum](qf, qf->file_info) ; /* dispatch the required action */ + + return qps->pend_count ; +} ; + +/*============================================================================== + * qps_file structure handling + */ + +/*------------------------------------------------------------------------------ + * Initialise qps_file structure -- allocating one if required. + * + * If a template is given, then the action functions are copied from there to + * the new structure. See above for discussion of action functions. + * + * Once initialised, the file may be added to a selection. + * + * Returns the qps_file. + */ +qps_file +qps_file_init_new(qps_file qf, qps_file template) +{ + if (qf == NULL) + qf = XCALLOC(MTYPE_QPS_FILE, sizeof(struct qps_file)) ; + else + memset(qf, 0, sizeof(struct qps_file)) ; + + /* Zeroising has initialised: + * + * selection -- NULL -- ditto + * + * file_info -- NULL -- is set by qps_add_file() + * fd -- unset -- ditto + * + * enabled_bits -- nothing enabled + * + * actions[] -- all set to NULL + */ + + qf->fd = fd_undef ; /* no fd set yet */ + + if (template != NULL) + memcpy(qf->actions, template->actions, sizeof(qf->actions)) ; + + return qf ; +} ; + +/*------------------------------------------------------------------------------ + * Free dynamically allocated qps_file structure -- if any. + * + * Removes from any selection may be a member of. + * + * If there is a valid fd -- close it ! + * + * Returns: NULL + */ +extern qps_file +qps_file_free(qps_file qf) +{ + if (qf != NULL) + { + if (qf->selection != NULL) + qps_remove_file(qf) ; + + if (qf->fd >= 0) + { + close(qf->fd) ; + qf->fd = fd_undef ; + } ; + + XFREE(MTYPE_QPS_FILE, qf) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Enable (or re-enable) file for the given mode. + * + * If the action argument is not NULL, set the action for the mode. + * + * NB: It is a FATAL error to enable a mode with a NULL action. + * + * NB: It is a FATAL error to enable modes for a file which is not in a + * selection. + */ +void +qps_enable_mode(qps_file qf, qps_mnum_t mnum, qps_action* action) +{ + qps_mbit_t mbit = qps_mbit(mnum) ; + qps_selection qps = qf->selection ; + + dassert(qps != NULL) ; + dassert((mnum >= 0) && (mnum <= qps_mnum_count)) ; + + if (action != NULL) + qf->actions[mnum] = action ; + else + dassert(qf->actions[mnum] != NULL) ; + + if (qf->enabled_bits & mbit) + dassert(FD_ISSET(qf->fd, &(qps->enabled[mnum].fdset))) ; + else + { + dassert( ! FD_ISSET(qf->fd, &(qps->enabled[mnum].fdset))) ; + FD_SET(qf->fd, &(qps->enabled[mnum].fdset)) ; + ++qps->enabled_count[mnum] ; + qf->enabled_bits |= mbit ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set action for given mode -- does not enable/disable. + * + * May unset an action by setting it NULL ! + * + * See above for discussion of action functions. + * + * NB: it is a fatal error to unset an action for a mode which is enabled. + */ +void +qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) +{ + dassert((mnum >= 0) && (mnum <= qps_mnum_count)) ; + + if (action == NULL) + passert((qf->enabled_bits & qps_mbit(mnum)) == 0) ; + + qf->actions[mnum] = action ; +} ; + +/*------------------------------------------------------------------------------ + * Disable file for one or more modes. + * + * If there are any pending results for the modes, those are discarded. + * + * Note that this is modestly "optimised" to deal with disabling a single mode. + * (Much of the time only the write mode will be being disabled !) + * + * NB: it is safe to disable modes which are not enabled -- even if the file + * is not currently a member of a selection. (If it is not a member of a + * collection no modes should be enabled !) + */ + +static qps_mnum_t qps_first_mnum[qps_mbit(qps_mnum_count)] = + { + -1, /* 0 -> -1 -- no bit set */ + 0, /* 1 -> 0 -- B0 is first bit */ + 1, /* 2 -> 1 -- B1 is first bit */ + 1, /* 3 -> 1 -- B1 is first bit */ + 2, /* 4 -> 2 -- B2 is first bit */ + 2, /* 5 -> 2 -- B2 is first bit */ + 2, /* 6 -> 2 -- B2 is first bit */ + 2 /* 7 -> 2 -- B2 is first bit */ + } ; + +CONFIRM(qps_mbit(qps_mnum_count) == 8) ; + +void +qps_disable_modes(qps_file qf, qps_mbit_t mbits) +{ + qps_mnum_t mnum ; + + qps_selection qps = qf->selection ; + + dassert((mbits >= 0) && (mbits <= qps_all_mbits)) ; + + mbits &= qf->enabled_bits ; /* don't bother with any not enabled */ + qf->enabled_bits ^= mbits ; /* unset what we're about to disable */ + + while (mbits != 0) + { + mnum = qps_first_mnum[mbits] ; + + dassert(qps->enabled_count[mnum] > 0) ; + dassert(FD_ISSET(qf->fd, &(qps->enabled[mnum].fdset))) ; + + FD_CLR(qf->fd, &(qps->enabled[mnum].fdset)) ; + --qps->enabled_count[mnum] ; + + if ((qps->pend_count != 0) && (qps->tried_count[mnum] != 0) + && (FD_ISSET(qf->fd, &(qps->results[mnum].fdset)))) + { + FD_CLR(qf->fd, &(qps->results[mnum].fdset)) ; + --qps->pend_count ; + } ; + + mbits ^= qps_mbit(mnum) ; + } ; +} ; + +/*============================================================================== + * Handling the files vector. + * + * For small numbers of fd's, the files vector is kept as a list, in fd order. + * Files are found by binary chop, and added/removed by insert/delete in the + * list. + * + * For large numbers of fd's, the files vector is kept as an array, indexed by + * fd. + */ + +/*------------------------------------------------------------------------------ + * Comparison function for binary chop + */ +static int +qps_fd_cmp(const int** pp_fd, const qps_file* p_qf) +{ + if (**pp_fd < (*p_qf)->fd) + return -1 ; + if (**pp_fd > (*p_qf)->fd) + return +1 ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Lookup/Insert file by file-descriptor. + * + * Inserts if insert argument is not NULL. + * + * Returns the file we found (if any) or the file we just inserted. + * + * NB: FATAL error to insert file with same fd as an existing one. + */ +static qps_file +qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert) +{ + qps_file qf ; + vector_index i ; + int ret ; + + dassert((fd >= 0) && (fd < (int)FD_SETSIZE)) ; + + /* Look-up */ + /* */ + /* Set i = index for entry in files vector */ + /* Set ret = 0 <=> i is exact index. */ + /* < 0 <=> i is just after where entry may be inserted */ + /* > 0 <=> i is just before where entry may be inserted */ + if (qps->fd_direct) + { + i = fd ; /* index of entry */ + ret = 0 ; /* how to insert, if do */ + } + else + i = vector_bsearch(&qps->files, (vector_bsearch_cmp*)qps_fd_cmp, + &fd, &ret) ; + if (ret == 0) + qf = vector_get_item(&qps->files, i) ; /* NULL if not there */ + else + qf = NULL ; /* not there */ + + /* Insert now, if required and can: keep fd_count and fd_last up to date. */ + if (insert != NULL) + { + if (qf != NULL) + zabort("File with given fd already exists in qps_selection") ; + + /* If required, change up to a directly addressed files vector. */ + if (!qps->fd_direct && (qps->fd_count > 9)) + { + vector tmp ; + + tmp = vector_move_here(NULL, &qps->files) ; + + while ((qf = vector_pop_item(tmp)) != NULL) + vector_set_item(&qps->files, qf->fd, qf) ; + + vector_free(tmp) ; + + qps->fd_direct = 1 ; + + i = fd ; /* index is now the fd */ + ret = 0 ; /* and insert there */ + } ; + + /* Now can insert accordint to i & ret */ + vector_insert_item_here(&qps->files, i, ret, insert) ; + + ++qps->fd_count ; + if (fd > qps->fd_last) + qps->fd_last = fd ; + + qf = insert ; /* will return what we just inserted. */ + } ; + + /* Sanity checking. */ + dassert( (qf == NULL) || ((qps == qf->selection) && (fd == qf->fd)) ) ; + + /* Return the file we found or inserted. */ + return qf ; +} ; + +/*------------------------------------------------------------------------------ + * Remove file from selection. + * + * NB: FATAL error if file is not in the selection, or the file-descriptor + * is invalid (or refers to some other file !). + */ +static void +qps_file_remove(qps_selection qps, qps_file qf) +{ + qps_file qfd ; + int fd_last ; + + passert((qf->fd >= 0) && (qf->fd <= qps->fd_last) && (qps == qf->selection)) ; + + /* Look-up and remove. */ + if (qps->fd_direct) + { + qfd = vector_unset_item(&qps->files, qf->fd) ; /* NULL if not there */ + fd_last = vector_end(&qps->files) - 1 ; + } + else + { + qps_file qf_last ; + int ret ; + vector_index i = vector_bsearch(&qps->files, + (vector_bsearch_cmp*)qps_fd_cmp, + &qf->fd, &ret) ; + if (ret == 0) + qfd = vector_delete_item(&qps->files, i) ; + else + qfd = NULL ; + + qf_last = vector_get_last_item(&qps->files) ; + if (qf_last != NULL) + fd_last = qf_last->fd ; + else + fd_last = -1 ; + } ; + + passert(qfd == qf) ; /* must have been there and be the expected file */ + + /* Keep fd_count and fd_last up to date. */ + dassert(qps->fd_count > 0) ; + --qps->fd_count ; + + dassert(fd_last >= (qps->fd_count - 1)) ; + + qps->fd_last = fd_last ; + + /* Also, remove the from all vectors. */ + qps_disable_modes(qf, qps_all_mbits) ; + + /* Is no longer in the selection. */ + qf->selection = NULL ; +} ; + +/*============================================================================== + * fd_super_set support. + * + * For large sets of file descriptors something faster than testing for all + * possible bits is required. The fd_super_set assumes that the fd_set is a + * straightforward bit-vector, and overlays a 32-bit word array and a byte + * array over that. + * + * Cannot tell if the underlying bit vector is arranged in bytes, or some + * longer words. Cannot tell if words are held big or little endian. Cannot + * tell if lowest numbered fd will be highest or lowest in whatever unit it's + * held in. + * + * So... we have maps for fd -> our word index, and fd -> byte index. + * + * we have a map for fd -> mask for bit used in its byte. + * + * We require that fds will be numbered consistently in bytes. That is, + * every (fd mod 8) == n will appear in the same bit in a byte, for all fd ( + * for n = 0..7). This allows the final map, which takes a byte value and + * returns the lowest numbered fd in the byte, mod 8. + * + * To copy all the bytes for all descriptors 0..fd, also construct + * fd_byte_count[] -- which copes with the fact that on a big-endian machine + * it is possible that descriptor fd - 8 may be in a higher numbered byte than + * fd ! Using this count assumes that the underlying system really does not + * look at bits beyond the given maximum fd. + */ + +static short fd_word_map[FD_SETSIZE] ; /* maps fd to word index */ +static short fd_byte_map[FD_SETSIZE] ; /* maps fd to byte index */ +static uint8_t fd_bit_map [FD_SETSIZE] ; /* maps fd to bit in byte */ + +static int8_t fd_first_map[256] ; /* maps byte value to 0..7, where that */ + /* is the lowest fd bit set in byte. */ + +/*------------------------------------------------------------------------------ + * Cross Check + * + * Where the shape of the bit map is known, this will test that the correct + * bit map has been deduced. + * + * Requires the following to be defined: + * + * QPS_CROSS_CHECK -- weebb + * + * where: w -- number of bytes per word, 1.. + * ee -- 10 => big-endian bytes in word + * 01 => little-endian + * bb -- 70 => b7 is MS bit, b0 is LS bit + * 07 => b0 is MS bit, b7 is LS bit + * + * So: + * + * 10170 => a bit map handled as bytes + * + * 40170 => a bit map handled as little-endian 32-bit words + * + * ...though this is actually no different to handling the bit map + * as bytes. + * + * 41070 => a bit map handled as big-endian 32-bit words + * + * 10107 => a bit map handled as bytes, where the "leftmost" bit is the first + * bit in the bitmap: + * + * ...a big-endian machine, where the bit map is handled as n-bit + * words, with the "leftmost" bit being the first would be like + * this too. + */ + +#define QPS_CROSS_CHECK 40170 + +enum { +#ifdef QPS_CROSS_CHECK + qps_cross_check = 1, + qps_cc_word_bytes = QPS_CROSS_CHECK / 10000, + qps_cc_byte_ord = (QPS_CROSS_CHECK / 100) % 100, + qps_cc_bit_ord = QPS_CROSS_CHECK % 100, +#else + qps_cross_check = 0, /* no cross check */ + qps_cc_word_bytes = 1, /* byte_wise */ + qps_cc_byte_ord = 1, /* little-endian */ + qps_cc_bit_ord = 70, /* standard bit order */ +#endif + qps_cc_word_bits = qps_cc_word_bytes * 8 +} ; + +CONFIRM((qps_cc_word_bytes == 16) || (qps_cc_word_bytes == 8) + || (qps_cc_word_bytes == 4) + || (qps_cc_word_bytes == 2) + || (qps_cc_word_bytes == 1)) ; +CONFIRM((qps_cc_byte_ord == 10) || (qps_cc_byte_ord == 1)) ; +CONFIRM((qps_cc_bit_ord == 70) || (qps_cc_bit_ord == 7)) ; + +/* Functions required for the cross check. */ + +static inline int +qpd_cc_word(int fd) +{ + return fd / qps_cc_word_bits ; +} ; + +static inline int +qps_cc_byte(int fd) +{ + if (qps_cc_byte_ord == 10) + return (qpd_cc_word(fd) * qps_cc_word_bytes) + + qps_cc_word_bytes - 1 - ((fd % qps_cc_word_bits) / 8) ; + else + return fd / 8 ; +} ; + +static inline uint8_t +qps_cc_bit(int fd) +{ + if (qps_cc_bit_ord == 70) + return 0x01 << (fd & 0x7) ; + else + return 0x80 >> (fd & 0x7) ; +} ; + +static int +ccFD_ISSET(int fd, fd_set* set) +{ + return (*((uint8_t*)set + qps_cc_byte(fd)) & qps_cc_bit(fd)) != 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Scan for next fd in given fd set, and clear it. + * + * Starts at the given fd, will not consider anything above fd_last. + * + * Returns next fd, or -1 if none. + */ +static int +qps_next_fd_pending(fd_super_set* pending, int fd, int fd_last) +{ + uint8_t b ; + + dassert((fd >= 0) && (fd <= fd_last)) ; + + while (pending->words[fd_word_map[fd]] == 0) /* step past zero words */ + { + fd = (fd & ~ (FD_WORD_BITS - 1)) + FD_WORD_BITS ; + /* step to start of next word */ + if (fd > fd_last) + return -1 ; /* quit if past last */ + } ; + + fd &= ~0x0007 ; /* step back to first in byte */ + while ((b = pending->bytes[fd_byte_map[fd]]) == 0) + { + fd += 8 ; + if (fd > fd_last) + return -1 ; + } ; + + fd += fd_first_map[b] ; + + dassert(fd <= fd_last) ; + dassert((b & fd_bit_map[fd]) == fd_bit_map[fd]) ; + + FD_CLR(fd, &pending->fdset) ; + + dassert((b ^ fd_bit_map[fd]) == pending->bytes[fd_byte_map[fd]]) ; + + return fd ; +} ; + +/*------------------------------------------------------------------------------ + * Make a map of the fd_super_set. + * + * The form of an fd_set is not defined. This code verifies that it is, in + * fact a bit vector, and hence that the fd_super_set works here ! + * + * It is a FATAL error if things don't work out. + */ +static void +qps_make_super_set_map(void) +{ + fd_super_set test ; + int fd, i, iw, ib ; + + /* (1) check that a zeroised fd_super_set is an empty one. */ + qps_super_set_zero(&test, 1) ; + + for (fd = 0 ; fd < (int)FD_SETSIZE ; ++fd) + if (FD_ISSET(fd, &test.fdset)) + zabort("Zeroised fd_super_set is not empty") ; + + /* (2) check that zeroising the fd_set doesn't change things */ + FD_ZERO(&test.fdset) ; + for (iw = 0 ; iw < (int)FD_SUPER_SET_WORD_SIZE ; ++iw) + if (test.words[iw] != 0) + zabort("Zeroised fd_super_set is not all zero words") ; + + /* (3) check that setting one fd sets one bit, and construct the */ + /* fd_word_map[], fd_byte_map[] and fd_bit_map[]. */ + for (fd = 0 ; fd < (int)FD_SETSIZE ; ++fd) + { + fd_word_t w ; + + FD_SET(fd, &test.fdset) ; + + w = 0 ; + for (iw = 0 ; iw < (int)FD_SUPER_SET_WORD_SIZE ; ++iw) + { + if (test.words[iw] != 0) + { + if (w != 0) + zabort("FD_SET set a bit in more than one word") ; + + w = test.words[iw] ; + if ((w == 0) || ((w & (w - 1)) != 0)) + zabort("FD_SET set more than one bit in a word") ; + + fd_word_map[fd] = iw ; + + ib = iw * FD_WORD_BYTES ; + while (test.bytes[ib] == 0) + { + ++ib ; + if (ib >= ((iw + 1) * FD_WORD_BYTES)) + zabort("FD_SET set something beyond the expected bytes") ; + } ; + fd_byte_map[fd] = ib ; + fd_bit_map[fd] = test.bytes[ib] ; + } ; + } ; + + if (w == 0) + zabort("FD_SET did not set any bit in any word") ; + + FD_CLR(fd, &test.fdset) ; + + for (iw = 0 ; iw < (int)FD_SUPER_SET_WORD_SIZE ; ++iw) + if (test.words[iw] != 0) + zabort("FD_CLR did not leave the fd_super_set empty") ; + } ; + + /* (4) check the fd_byte_map. */ + /* make sure that have 8 contiguous fd to a byte. */ + /* make sure that have 32 contiguous fd to a word. */ + + for (fd = 0 ; fd < (int)FD_SETSIZE ; fd += 8) + { + int fds ; + ib = fd_byte_map[fd] ; + iw = fd_word_map[fd] ; + + /* Must share the same byte as the next 7 fds */ + for (fds = fd + 1 ; fds < (fd + 8) ; ++fds) + if (fd_byte_map[fds] != ib) + zabort("Broken fd_byte_map -- not 8 contiguous fd's in a byte") ; + + /* Must not share the same byte as any other set of 8 fd's */ + for (fds = 0 ; fds < (int)FD_SETSIZE ; fds += 8) + if ((fd_byte_map[fds] == ib) && (fds != fd)) + zabort("Broken fd_byte_map -- fd's not in expected bytes") ; + + /* Must be one of the bytes in the current word's fd's */ + if ( (ib < (iw * FD_WORD_BYTES)) || (ib >= ((iw + 1) * FD_WORD_BYTES)) ) + zabort("Broken fd_byte_map -- fd's not in expected words") ; + } ; + + /* (5) check the fd_bit_map */ + /* make sure that all fd mod 8 map to the same byte value */ + + for (i = 0 ; i < 8 ; ++i) + { + uint8_t b = fd_bit_map[i] ; + for (fd = 8 + i ; fd < (int)FD_SETSIZE ; fd += 8) + if (fd_bit_map[fd] != b) + zabort("Broken fd_bit_map -- inconsistent bit mapping") ; + } ; + + /* (6) construct fd_first_map, to get lowest numbered fd (mod 8) from */ + /* a given byte value. */ + + for (i = 0 ; i < 256 ; ++i) + fd_first_map[i] = -1 ; + + for (fd = 0 ; fd < 8 ; ++fd) + { + uint8_t fdb = fd_bit_map[fd] ; + for (i = 1 ; i < 256 ; ++i) + if ((fd_first_map[i] == -1) && ((i & fdb) != 0)) + fd_first_map[i] = fd ; + } ; + + if (fd_first_map[0] != -1) + zabort("Broken fd_first_map -- invalid result for 0") ; + + for (i = 1 ; i < 256 ; ++i) + if (fd_first_map[i] == -1) + zabort("Broken fd_first_map -- missing bits") ; + + /* (7) construct fd_byte_count[] -- number of bytes required to */ + /* include fds 0..fd. */ + + i = 0 ; + for (fd = 0 ; fd < (int)FD_SETSIZE ; ++fd) + { + int c = fd_byte_map[fd] + 1 ; + + if (c < i) + c = i ; /* use largest so far. => big-endian */ + else + i = c ; /* keep largest so far up to date */ + + fd_byte_count[fd] = c ; + } ; + + if (!qps_cross_check) + return ; + + /*---------------------------------------------------------------------------- + * Checking that the maps have been correctly deduced -- where know what + * the mapping really is ! + */ + for (fd = 0 ; fd < (int)FD_SETSIZE ; ++fd) + { + uint8_t b ; + short c ; + + FD_ZERO(&test.fdset) ; + FD_SET(fd, &test.fdset) ; + if (!ccFD_ISSET(fd, &test.fdset)) + zabort("FD_SET and ccFD_ISSET differ") ; + + iw = qpd_cc_word(fd) ; + ib = qps_cc_byte(fd) ; + b = qps_cc_bit(fd) ; + + if (qps_cc_byte_ord == 10) + c = (iw + 1) * 4 ; + else + c = ib + 1 ; + + if (fd_word_map[fd] != iw) + zabort("Broken fd_word_map") ; + if (fd_byte_map[fd] != ib) + zabort("Broken fd_byte_map") ; + if (fd_bit_map[fd] != b) + zabort("Broken fd_bit_map") ; + if (fd_byte_count[fd] != c) + zabort("Broken fd_byte_count") ; + } ; + + for (i = 1 ; i < 256 ; ++i) + { + uint8_t b = i ; + fd = 0 ; + if (qps_cc_bit_ord == 70) + { + while ((b & 1) == 0) + { + b >>= 1 ; + ++fd ; + } ; + } + else + { + while ((b & 0x80) == 0) + { + b <<= 1 ; + ++fd ; + } ; + } ; + + if (fd_first_map[i] != fd) + zabort("Broken fd_first_map") ; + } ; + + return ; +} ; + +/*------------------------------------------------------------------------------ + * Zeroise 'n' contiguous fd_super_sets + * + * NB: this MUST be used in place of FD_ZERO because the fd_set may be shorter + * than the overlayed words/bytes vectors. + * + * NB: it is CONFIRMED elsewhere that the fd_set is no longer than the overlays. + */ +static void +qps_super_set_zero(fd_super_set* p_set, int n) +{ + memset(p_set, 0, SIZE(fd_super_set, n)) ; +} ; + +/*------------------------------------------------------------------------------ + * Compare 'n' contiguous fd_super_sets + * + * Returns 0 <=> equal + */ +static int +qps_super_set_cmp(fd_super_set* p_a, fd_super_set* p_b, int n) +{ + return memcmp(p_a, p_b, SIZE(fd_super_set, n)) ; +} ; + +/*------------------------------------------------------------------------------ + * Count the number of bits set in 'n' contiguous fd_super_sets. + */ +static int +qps_super_set_count(fd_super_set* p_set, int n) +{ + fd_word_t* p ; + int count = 0 ; + + n *= FD_SUPER_SET_WORD_SIZE ; + confirm(sizeof(fd_super_set) == (FD_SUPER_SET_WORD_SIZE * FD_WORD_BYTES)) ; + + p = (fd_word_t*)p_set ; + while (n--) + { + fd_word_t w = *p++ ; + while (w != 0) + { + ++count ; + w &= (w - 1) ; + } ; + } ; + + return count ; +} ; + +/*============================================================================== + * Selection state check -- for debug purposes. + * + * Runs a check across a given selection and verifies that: + * + * 1) for !fd_direct that the files are in fd order in the vector + * and are unique, and there are no NULL entries. + * 2) for fd_direct that the file fd and the index match + * and the last entry is not NULL + * 3) that all files point at the selection + * 4) that the enabled modes in each file are valid + * 5) the number of files in the selection matches fd_count. + * 6) the highest numbered fd matches fd_last + * 7) that the enabled counts in the selection are correct + * 8) that the enabled modes in each file match the enabled modes in the + * selection + * 9) that no extraneous fds are set in the enabled vectors + * + * If there are no pending fds: + * + * 10) if there are no pending fds, that the results vectors are empty. + * + * If there are pending fds: + * + * 11) that pend_mnum is valid and pend_fd <= tried_fd_last. + * + * 12) that the tried_count for modes 0..pend_mnum-1 is zero, + * and the tried_count for pend_mnum is not. + * + * 13) that the result vectors for modes where tried count == 0 are empty. + * + * 14) that the remaining result bits are a subset of the enabled bits. + * + * 15) that no bits beyond tried_fd_last are set in the result vectors. + * + * 16) that no bits before pend_fd are set in the pemd_mnum result vector. + * + * 17) that the number of bits remaining matches pend_count. + */ +static void +qps_selection_validate(qps_selection qps) +{ + int fd_last ; + int enabled_count[qps_mnum_count] ; + fd_full_set enabled ; + + qps_file qf ; + int fd, n, mnum, p_mnum ; + vector_index i ; + + /* 1..4) Run down the selection vector and check. */ + /* Collect new enabled_count and enabled bit vectors. */ + + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + enabled_count[mnum] = 0 ; + qps_super_set_zero(enabled, qps_mnum_count) ; + + n = 0 ; + fd_last = -1 ; + for (VECTOR_ITEMS(&qps->files, qf, i)) + { + if (qf != NULL) + { + ++n ; /* Number of files */ + + if (qps->fd_direct) + { + if (qf->fd != (int)i) /* index and fd must match */ + zabort("File vector index and fd mismatch") ; + } + else + { + if (qf->fd <= fd_last) /* must be unique and in order */ + zabort("File vector not in order") ; + } ; + + fd_last = qf->fd ; /* keep track of last fd */ + + if (qf->selection != qps) /* file must refer to selection */ + zabort("File does not refer to its selection") ; + + if ((qf->enabled_bits < 0) || (qf->enabled_bits > qps_all_mbits)) + zabort("File enabled bits are invalid") ; + + /* Capture enabled state of all files. */ + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if (qf->enabled_bits & qps_mbit(mnum)) + { + ++enabled_count[mnum] ; + FD_SET(qf->fd, &enabled[mnum].fdset) ; + } ; + } + else + if (!qps->fd_direct) + zabort("Found NULL entry in !fd_direct files vector") ; + } ; + + if ((n != 0) && (vector_get_last_item(&qps->files) == NULL)) + zabort("Last entry in file vector is NULL") ; + + /* 5) check that the number of files tallies. */ + if (n != qps->fd_count) + zabort("Number of files in the selection does not tally") ; + + /* 6) check the last fd */ + if ( (qps->fd_last < (n - 1)) || (fd_last != qps->fd_last) ) + zabort("The last fd does not tally") ; + + /* 7) check that the enabled counts tally. */ + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if (enabled_count[mnum] != qps->enabled_count[mnum]) + zabort("Enabled counts do not tally") ; + + /* 8..9) Check that the enabled vectors are the same as the ones just */ + /* created by scanning the files. */ + if (qps_super_set_cmp(enabled, qps->enabled, qps_mnum_count) != 0) + zabort("Enabled bit vectors do not tally") ; + + /* 10) if there are no pending fds, check result vectors empty. */ + if (qps->pend_count == 0) + { + if (qps_super_set_count(qps->results, qps_mnum_count) != 0) + zabort("Nothing pending, but result vectors not empty") ; + + return ; + } ; + + /* This is to stop gcc whining about signed/unsigned comparisons. */ + p_mnum = qps->pend_mnum ; + + /* 11) that pend_mnum is valid and pend_fd <= tried_fd_last. */ + if ( (p_mnum < 0) || (p_mnum > qps_mnum_count) + || (qps->pend_fd < 0) + || (qps->pend_fd > qps->tried_fd_last) ) + zabort("Invalid pend_mnum or pend_fd") ; + + /* 12) check tried_count[] */ + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + { + if ((mnum < p_mnum) && (qps->tried_count[mnum] != 0)) + zabort("Non-zero tried_count for mode < pend_mnum") ; + if ((mnum == p_mnum) && (qps->tried_count[qps->pend_mnum] <= 0)) + zabort("Zero tried_count for pend_mnum") ; + if ((mnum > p_mnum) && (qps->tried_count[mnum] < 0)) + zabort("Invalid tried_count for mode > pend_mnum") ; + } ; + + /* 13) check result vectors for modes where tried count == 0 */ + n = (qps_super_set_count(qps->results, qps_mnum_count) != 0) ; + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if ((qps->tried_count[mnum] == 0) + && (qps_super_set_count(&qps->results[mnum], 1) != 0)) + zabort("Non-empty bit vector where tried count == 0") ; + + /* 14) check remaining results are a subset of the enableds. */ + /* 15) check no bit beyond tried_fd_last is set in the results. */ + /* 16) check no bit before pend_fd is set in the pemd_mnum results. */ + /* 17) check the number of bits remaining matches pend_count. */ + + n = 0 ; + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if (qps->tried_count[mnum] != 0) + { + for (fd = 0 ; fd < (int)FD_SETSIZE ; ++fd) + if (FD_ISSET(fd, &qps->results[mnum].fdset)) + { + ++n ; + if (fd > qps->tried_fd_last) + zabort("Found pending fd beyond tried_fd_last") ; + if ( ! FD_ISSET(fd, &qps->results[mnum].fdset)) + zabort("Found pending fd which is not enabled") ; + if ((mnum == p_mnum) && (fd < qps->pend_fd)) + zabort("Found pending fd < current next pending") ; + } ; + } ; + + if (n != qps->pend_count) + zabort("Non-empty bit vector where tried count == 0") ; +} ; diff --git a/lib/qpselect.h b/lib/qpselect.h new file mode 100644 index 00000000..8901ea36 --- /dev/null +++ b/lib/qpselect.h @@ -0,0 +1,241 @@ +/* Quagga pselect support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QPSELECT_H +#define _ZEBRA_QPSELECT_H + +#include <sys/select.h> +#include <errno.h> + +#include "zassert.h" +#include "qtime.h" +#include "vector.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Quagga pselect -- qps_xxxx + * + * Here and in qpselect.c is a data structure for managing multiple file + * descriptors and running pselect to wait for I/O activity and to multiplex + * between the file descriptors. + */ + +enum qps_mnum /* "mode" numbers: error/read/write */ +{ + qps_mnum_first = 0, + + qps_error_mnum = 0, + qps_read_mnum = 1, + qps_write_mnum = 2, + + qps_mnum_count = 3 +} ; + +typedef enum qps_mnum qps_mnum_t ; + +#define qps_mbit(mnum) (1 << mnum) + +enum qps_mbits /* "mode" bits: error/read/write */ +{ + qps_error_mbit = qps_mbit(qps_error_mnum), + qps_read_mbit = qps_mbit(qps_read_mnum), + qps_write_mbit = qps_mbit(qps_write_mnum), + + qps_all_mbits = qps_mbit(qps_mnum_count) - 1 +} ; + +typedef enum qps_mbits qps_mbit_t ; + +/* "fd_undef" -- used when fd is undefined */ +enum { fd_undef = -1 } ; + +/* Forward references */ +typedef struct qps_selection* qps_selection ; +typedef struct qps_file* qps_file ; + +/*============================================================================== + * fd_super_set. + * + * To speed up scanning of large fd_set's this structure overlays a 32-bit + * word and a byte array over the (assumed) fd_set bit vector. + * + * There is no guarantee that FD_SETSIZE is a multiple of 32 (or of 8, for + * that matter) -- so some care must be taken. + */ + +typedef uint32_t fd_word_t ; + +#define FD_WORD_BITS 32 +#define FD_WORD_BYTES (FD_WORD_BITS / 8) + +CONFIRM(FD_WORD_BITS == (FD_WORD_BYTES * 8)) ; /* for completeness */ + +#define FD_SUPER_SET_WORD_SIZE ((FD_SETSIZE + FD_WORD_BITS - 1) / FD_WORD_BITS) +#define FD_SUPER_SET_BYTE_SIZE (FD_SUPER_SET_WORD_SIZE * FD_WORD_BYTES) + +/* Make sure that the overlay is at least as big as the fd_set ! */ +CONFIRM(FD_SUPER_SET_BYTE_SIZE >= sizeof(fd_set)) ; + +typedef union /* see qps_make_super_set_map() */ +{ + fd_word_t words[FD_SUPER_SET_WORD_SIZE] ; + uint8_t bytes[FD_SUPER_SET_BYTE_SIZE] ; + fd_set fdset ; +} fd_super_set ; + +/* Make sure that the fd_super_set is an exact number of fd_word_t words */ +CONFIRM(sizeof(fd_super_set) == (FD_SUPER_SET_WORD_SIZE * FD_WORD_BYTES)) ; + +/*============================================================================== + * Action function. + * + * Each file has three action functions, to be called in qps_dispatch_next() + * when pselect() has reported error/read/write for the file. + * + * For further discussion, see: qps_file_init. + */ + +typedef void qps_action(qps_file qf, void* file_info) ; + +/*============================================================================== + * Data Structures. + */ + +typedef fd_super_set fd_full_set[qps_mnum_count] ; + +struct qps_selection +{ + int fd_count ; /* number of fds we are looking after */ + int fd_direct ; /* direct lookup in vector or not */ + + struct vector files ; /* mapping fd to qps_file */ + + int fd_last ; /* highest numbered fd; -1 => none at all */ + int enabled_count[qps_mnum_count] ; /* no. enabled fds in each mode */ + fd_full_set enabled ; /* bit vectors for pselect enabled stuff */ + + int tried_fd_last ; /* highest numbered fd on last pselect */ + int tried_count[qps_mnum_count] ; /* enabled_count on last pselect */ + fd_full_set results ; /* last set of results from pselect */ + + int pend_count ; /* results pending (if any) */ + qps_mnum_t pend_mnum ; /* error/read/write mode pending (if any) */ + int pend_fd ; /* fd pending (if any) */ + + int signum ; /* signal that sigmask is enabling -- 0 => none */ + sigset_t sigmask ; /* sigmask to use for duration of pselect */ +} ; + +struct qps_file +{ + qps_selection selection ; + + void* file_info ; + int fd ; + + qps_mbit_t enabled_bits ; + + qps_action* actions[qps_mnum_count] ; +} ; + +/*============================================================================== + * qps_selection handling + */ + +extern void +qps_start_up(void) ; + +extern qps_selection +qps_selection_init_new(qps_selection qps) ; + +extern void +qps_add_file(qps_selection qps, qps_file qf, int fd, void* file_info) ; + +extern void +qps_remove_file(qps_file qf) ; + +extern qps_file +qps_selection_ream(qps_selection qps, int free_structure) ; + +/* Ream out selection and free the selection structure. */ +#define qps_selection_ream_free(qps) qps_selection_ream(qps, 1) +/* Ream out selection but keep the selection structure. */ +#define qps_selection_ream_keep(qps) qps_selection_ream(qps, 0) + +extern void +qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ; + +extern int +qps_pselect(qps_selection qps, qtime_mono_t timeout) ; + +extern int +qps_dispatch_next(qps_selection qps) ; + +/*============================================================================== + * qps_file structure handling + */ + +extern qps_file +qps_file_init_new(qps_file qf, qps_file template) ; + +extern qps_file +qps_file_free(qps_file qf) ; + +extern void +qps_enable_mode(qps_file qf, qps_mnum_t mnum, qps_action* action) ; + +extern void +qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) ; + +extern void +qps_disable_modes(qps_file qf, qps_mbit_t mbits) ; + +Inline void* +qps_file_info(qps_file qf) +{ + return qf->file_info ; +} ; + +Inline int +qps_file_fd(qps_file qf) +{ + return qf->fd ; +} ; + +Inline int +qps_file_unset_fd(qps_file qf) +{ + int fd = qf->fd ; + qf->fd = fd_undef ; + + return fd ; +} ; + +Inline void +qps_set_file_info(qps_file qf, void* info) +{ + qf->file_info = info ; +} ; + +#endif /* _ZEBRA_QPSELECT_H */ diff --git a/lib/qpthreads.c b/lib/qpthreads.c new file mode 100644 index 00000000..baa34d52 --- /dev/null +++ b/lib/qpthreads.c @@ -0,0 +1,775 @@ +/* Quagga Pthreads support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This MUST come first... otherwise we don't get __USE_UNIX98, which is */ +/* essential if glibc is to allow pthread_mutexattr_settype() to be used. */ +#include "config.h" + +#include <signal.h> +#include <string.h> + +#include "qpthreads.h" +#include "memory.h" + +/* If this is not set, will get errors later. */ +//#ifndef __USE_UNIX98 +//#error "_USE_UNIX98 not defined" +//#endif + +/*============================================================================== + * Quagga Pthread Interface -- qpt_xxxx + * + * Here (and in qpthreads.h) are captured all the pthreads features used in + * Quagga. + * + * This provides: + * + * * "wrappers" around functions which should not fail, but whose return + * code it is best to check... at least in a debug environment. + * + * * the possibility of a separate no pthreads build where pthread facilities + * are either dummied out or otherwise dealt with. + * + * * the ability to add any work-arounds which may be required if poorly + * conforming pthreads implementations are encountered + * + * Continued Working Without Pthreads + * ================================== + * + * A big Global Switch -- qpthreads_enabled -- is used to control whether the + * system is pthreaded or not. + * + * The initial state is qpthreads_enabled == false (0). + * + * The function qpt_set_qpthreads_enabled() should be called when the + * application has decided whether to use qpthreads or not. (But does not have + * to call this if it is happy to proceed in the default -- disabled -- state.) + * + * If this is never set, then the system runs without pthreads, and all the + * mutex and condition variable functions are NOPs. This allows, for example, + * mutex operations to be placed where they are needed for thread-safety, + * without affecting the code when running without pthreads. + * + * There are a very few operations which require qpthreads_enabled: + * + * * qpt_thread_attr_init + * * qpt_thread_create + * + * A few operations "freeze" the state of qpthreads_enabled. Any call of these + * before qpthreads are enabled, causes the state to be frozen, disabled. This + * means that any later attempt to enable qpthreads will be refused. These + * operations are: + * + * * qpt_mutex_init_new + * * qpt_cond_init_new + * + * This allows the application to decide as late as possible (but no later) + * whether to enable pthreads. If a mutex or a condition variable has been + * initialised before the application gets around to enabling qpthreads, that + * will be trapped when qpthreads is finally enabled. + * + * Pthread Requirements + * ==================== + * + * This is assuming support for 1003.1-2004 -- XOPEN Issue 6, with [THR] and + * [XSI] options. + * + * The [XSI] is required for pthread_mutexattr_settype(), only. + * + * If qpt_thread_attr_init() uses: + * + * pthread_attr_getinheritsched()/_setinheritshed() [TPS] + * pthread_attr_getscope()/_setscope() [TPS] + * pthread_attr_getschedpolicy()/_setschedpolicy() [TPS] + * pthread_attr_getschedparam()/_setschedparam() [THR] + * + * but they are only required if explicit scheduling attributes are being set. + * (So, could be dropped where not supported.) + * + * Amongst the things which are NOT required: + * + * pthread_attr_getguardsize()/_setguardsize() [XSI] + * pthread_attr_getstack()/_setstack() [TSA TSS] + * pthread_attr_getstackaddr()/_setstackaddr() [TSA OB] + * pthread_attr_getstacksize()/_setstacksize() [TSA TSS] + * + * pthread_barrier_xxx() [BAR] + * + * pthread_condattr_getpshared()/_setpshared() [TSH] + * + * pthread_mutex_getprioceiling()/_setprioceiling() [TPP] + * pthread_mutex_timedlock() [TMO] pro tem + * pthread_mutexattr_getprioceiling()/_setprioceiling() [TPP] + * pthread_mutexattr_getprotocol()/_setprotocol() [TPP TPI] + * pthread_mutexattr_getpshared()/_setpshared() [TSH] + * + * pthread_rwlock_xxx() [THR] pro tem + * pthread_rwlockattr_init()/_destroy() [THR] pro tem + * pthread_rwlockattr_getpshared()/_setpshared() [TSH] + * + * pthread_spin_xxx() [SPI] + * + * [CS] (Clock Select) is assumed if HAVE_CLOCK_MONOTONIC. + * + * In 1003.1-2008, XOPEN issue 7, [THR] and pthread_mutexattr_settype() have + * been moved to Base. + * + * NB: it is essential that pthread_kill() delivers the signal to the target + * thread only -- ie, it must be POSIX compliant. That rules out the old + * (2.4) LinuxThreads. For Linux, 2.6 (or greater) is required, with + * NPTL (these days generally included in glibc). + * + * NB: for glibc to give all the required features, either _GNU_SOURCE or + * _XOPEN_SOURCE must be set *before* the first #include <features.h>. + * _XOPEN_SOURCE=600 is sufficient. + * + * Pthread Thread Attributes -- Scheduling + * ======================================= + * + * Pthreads defines some useful looking real-time scheduling features. + * + * One would like to be able to give I/O intensive threads an advantage over + * CPU bound threads. + * + * Unfortunately, conformance allows a system to have its own scheduling + * system -- so long as the standard ones are implemented. Further, there is + * no way of telling what priority values are reasonable, even in the standard + * scheduling policies. + * + * The approach taken here is that by default a thread will be created with + * the system default attributes -- which may mean inheriting the creating + * thread's scheduling attributes. + * + * It is also possible to construct a set of attributes, using the most + * obviously useful properties. It is envisaged that this may be used when a + * configuration file is used to set locally sensible values. The attributes + * supported are: + * + * * attr_detached -- whether to start detached or not + * * attr_inherit_sched -- whether to inherit scheduling attributes + * * attr_sched_scope -- scheduling scope + * * attr_sched_policy -- scheduling policy + * * attr_sched_priority -- scheduling priority + * + * See qpt_thread_attr_init, below. + * + * Not supported here are: + * + * * attr_guardsize + * * attr_stack + * * attr_stacksize + * + * Pthread Mutex Attributes -- Error Checking + * ========================================== + * + * Mutexes are kept simple, only attr_type is used, and that by default. + * + * POSIX defines four types of mutex: + * + * _NORMAL no ownership check -- owner will deadlock if locks mutex ! + * -- undefined what happens if unlock + * mutex not owned by self ! + * no recursive locking + * + * _ERRORCHECK checks for ownership on lock and unlock + * no recursive locking + * + * _RECURSIVE checks for ownership on lock and unlock + * counts up locks and counts down unlocks + * + * This looks useful, but goes wrong with condition variables ! + * + * _DEFAULT undefined whether checks owner or not, on lock and/or unlock. + * no recursive locking + * + * See qpthreads.h for discussion of Quagga's standard type (QPT_MUTEX_TYPE). + * + * Other attributes are left in their default state: + * + * * attr_prioceiling -- default undefined + * * attr_protocol -- default undefined + * * attr_pshared -- defaults to _PROCESS_PRIVATE + * + * For the time being it is assumed that these are too exotic. + * + * Pthread Condition Variable Attributes + * ===================================== + * + * Condition variables have only two attributes: + * + * * attr_clock -- which clock to use + * * attr_pshared -- defaults to _PROCESS_PRIVATE + * + * The use a clock other than Quagga's standard (QPT_COND_CLOCK_ID) is possible, + * but not recommended. (See qpthreads.h for discussion of this.) + * + * Pthread Specific Signal Handling + * ================================ + * + * In a threaded application, need to use pthread_sigmask (not sigproc_mask). + * (Can use pthread_sigmask in a single threaded application.) + * + * To direct a signal at a given thread need pthread_kill. * + */ + +/*============================================================================== + * The Global Switch + * + * The state of the switch is: unset -- implicitly not enabled + * set_frozen -- implicitly not enabled & frozen + * set_disabled -- explicitly not enabled + * set_enabled -- explicitly set enabled + * + * "set_frozen" means that "qpthreads_freeze_enabled_state()" has been called, + * and the state was unset at the time. This means that some initialisation + * has been done on the basis of !qpthreads_enabled, and it is TOO LATE to + * enable qpthreads afterwards. + */ + +enum qpthreads_enabled_state +{ + qpt_state_unset = 0, + qpt_state_set_frozen = 1, + qpt_state_set_disabled = 2, + qpt_state_set_enabled = 3, +} ; + +static enum qpthreads_enabled_state qpthreads_enabled_state = qpt_state_unset ; + +uint8_t qpthreads_enabled_flag = 0 ; +uint8_t qpthreads_thread_created_flag = 0 ; + +/* Function to set qpthreads_enabled, one way or the other. + * + * Returns: true <=> successful set the required state. + * false <=> it is too late to enable qpthreads :-( + * + * NB: can repeatedly set to the same state, but not change state once set. + */ +extern int +qpt_set_qpthreads_enabled(int how) +{ + switch (qpthreads_enabled_state) + { + case qpt_state_unset: + break ; + case qpt_state_set_frozen: + if (how != 0) + return 0 ; + break ; + case qpt_state_set_disabled: + if (how != 0) + zabort("qpthreads_enabled is already set: cannot set enabled") ; + break ; + case qpt_state_set_enabled: + if (how == 0) + zabort("qpthreads_enabled is already set: cannot set disabled") ; + break ; + default: + break ; + } + + qpthreads_enabled_flag = (how != 0) ; + qpthreads_enabled_state = (how != 0) ? qpt_state_set_enabled + : qpt_state_set_disabled ; + return 1 ; +} ; + +/* Get state of qpthreads_enabled, and freeze if not yet explictly set. + * + * Where some initialisation depends on the state of qpthreads_enabled(), this + * returns the state and freezes it if it is implicitly not enabled. + */ +extern int +qpt_freeze_qpthreads_enabled(void) +{ + if (qpthreads_enabled_state == qpt_state_unset) + qpthreads_enabled_state = qpt_state_set_frozen ; + + return qpthreads_enabled_flag ; +} ; + +/*============================================================================== + * Thread creation and attributes. + * + * Threads may be created with a given set of attributes if required. + * + * qpt_thread_attr_init() will initialise a set of attributes including the + * current standard scheduling attributes. It is envisaged that configuration + * options may be used to specify these. + * + * qpt_thread_create() creates a thread using the given attributes. If those + * are NULL, then the system defaults are used. + */ + +/* Initialise a set of attributes -- setting the scheduling options. + * + * Options: + * + * qpt_attr_joinable -- the default if nothing specified. + * qpt_attr_detached -- overrides qpt_attr_joinable. + * + * qpt_attr_sched_inherit -- all scheduling attributes are to be inherited. + * No explicit scheduling attributes may be set. + * + * qpt_attr_sched_scope -- set explicit, given, scope. + * qpt_attr_sched_policy -- set explicit, given, policy + * qpt_attr_sched_priority -- set explicit, given, priority + * + * If none of the _sched_ options are given, then the scheduling attributes are + * left to whatever default values the system chooses. + * + * If the _sched_inherit option is specified, none of the other _sched_ options + * may be specified. + * + * If any of the explicit scheduling options are given, they are set in this + * order. If only some of these options are given, then the caller is + * assuming that the system will choose sensible defaults. + * + * The scope, policy and priority arguments are use only if the corresponding + * option is specified. + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the address of the qpt_thread_attr_t structure. + */ +qpt_thread_attr_t* +qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, + int scope, int policy, int priority) +{ + int err ; + + assert((opts & ~qpt_attr_known) == 0) ; + passert(qpthreads_enabled) ; + + /* Initialise thread attributes structure (allocating if required.) */ + if (attr == NULL) + attr = XMALLOC(MTYPE_QPT_THREAD_ATTR, sizeof(qpt_thread_attr_t)) ; + + err = pthread_attr_init(attr) ; + if (err != 0) + zabort_err("pthread_attr_init failed", err) ; + + /* If not qpt_attr_detached, then set joinable. */ + err = pthread_attr_setdetachstate(attr, + (opts & qpt_attr_detached) ? PTHREAD_CREATE_DETACHED + : PTHREAD_CREATE_JOINABLE) ; + if (err != 0) + zabort_err("pthread_attr_setdetachstate failed", err) ; + + /* If setting anything to do with scheduling... */ + if (opts & qpt_attr_sched_setting) + { + /* Either we inherit or we set explicit parameters. */ + + err = pthread_attr_setinheritsched(attr, + (opts & qpt_attr_sched_inherit) ? PTHREAD_INHERIT_SCHED + : PTHREAD_EXPLICIT_SCHED) ; + if (err != 0) + zabort_err("pthread_attr_setinheritsched", err) ; + + if (opts & qpt_attr_sched_inherit) + assert((opts & qpt_attr_sched_explicit) == 0) ; + else + { + if (opts & qpt_attr_sched_scope) + { + err = pthread_attr_setscope(attr, scope) ; + if (err != 0) + zabort_err("pthread_attr_setscope failed", err) ; + } ; + if (opts & qpt_attr_sched_policy) + { + err = pthread_attr_setschedpolicy(attr, scope) ; + if (err != 0) + zabort_err("pthread_attr_setschedpolicy failed", err) ; + } ; + if (opts & qpt_attr_sched_priority) + { + struct sched_param sparm ; + err = pthread_attr_getschedparam(attr, &sparm) ; + if (err != 0) + zabort_err("pthread_attr_getschedparam failed", err) ; + sparm.sched_priority = priority ; + err = pthread_attr_setschedparam(attr, &sparm) ; + if (err != 0) + zabort_err("pthread_attr_setschedparam failed", err) ; + } ; + } ; + } ; + + /* Done -- return qpt_thread_attr_t* */ + return attr ; +} ; + +/* Create Thread with given attributes (if any). + * + * If no attributes are given (attr == NULL) the thread is created with system + * default attributes -- *except* that it is created joinable. + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the qpt_thread_t "thread id". + */ +qpt_thread_t +qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) +{ + qpt_thread_attr_t thread_attr ; + qpt_thread_t thread_id ; + int default_attr ; + int err ; + + passert(qpthreads_enabled) ; + qpthreads_thread_created_flag = 1 ; /* and at least one thread created */ + + default_attr = (attr == NULL) ; + if (default_attr) + attr = qpt_thread_attr_init(&thread_attr, qpt_attr_joinable, 0, 0, 0) ; + + err = pthread_create(&thread_id, attr, start, arg) ; + if (err != 0) + zabort_err("pthread_create failed", err) ; + + if (default_attr) + { + err = pthread_attr_destroy(attr) ; /* being tidy */ + if (err != 0) + zabort_err("pthread_attr_destroy failed", err) ; + } ; + + return thread_id ; +} ; + +/* Join given thread -- do nothing if !qpthreads_enabled + * + * Tolerates ESRCH (no thread known by given id). + * + * Returns whatever the thread returns, NULL otherwise. + * + * NB: all other errors are FATAL. + */ +extern void* +qpt_thread_join(qpt_thread_t thread_id) +{ + int err ; + void* ret ; + + if (!qpthreads_enabled) + return NULL ; + + err = pthread_join(thread_id, &ret) ; + + if (err == 0) + return ret ; + + if (err == ESRCH) + return NULL ; + + zabort_err("pthread_join failed", err) ; +} ; + +/*============================================================================== + * Mutex initialise and destroy. + */ + +/* Initialise Mutex (allocating if required) + * + * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to + * later enable qpthreads will be a FATAL error). + * + * Options: + * + * qpt_mutex_quagga -- see qpthreads.h for discussion of this. + * qpt_mutex_normal -- ie PTHREAD_MUTEX_NORMAL + * qpt_mutex_recursive -- ie PTHREAD_MUTEX_RECURSIVE + * qpt_mutex_errorcheck -- ie PTHREAD_MUTEX_ERRORCHECK + * qpt_mutex_default -- system default + * + * Of these _recursive is the most likely alternative to _quagga... BUT do + * remember that such mutexes DO NOT play well with condition variables. + * + * Returns the mutex -- or original mx if !qpthreads_enabled. + */ +qpt_mutex +qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) +{ + pthread_mutexattr_t mutex_attr ; + int type ; + int err ; + + if (!qpthreads_enabled_freeze) + { + if (mx != NULL) + memset(mx, 0x0F, sizeof(qpt_mutex_t)) ; + return mx ; + } ; + + if (mx == NULL) + mx = XMALLOC(MTYPE_QPT_MUTEX, sizeof(qpt_mutex_t)) ; + + /* Set up attributes so we can set the mutex type */ + err = pthread_mutexattr_init(&mutex_attr); + if (err != 0) + zabort_err("pthread_mutexattr_init failed", err) ; + + switch(opts) + { + case qpt_mutex_quagga: + type = QPT_MUTEX_TYPE ; + break ; + case qpt_mutex_normal: + type = PTHREAD_MUTEX_NORMAL ; + break ; + case qpt_mutex_recursive: + type = PTHREAD_MUTEX_RECURSIVE ; + break ; + case qpt_mutex_errorcheck: + type = PTHREAD_MUTEX_ERRORCHECK ; + break ; + case qpt_mutex_default: + type = PTHREAD_MUTEX_DEFAULT ; + break ; + default: + zabort("Invalid qpt_mutex option") ; + } ; + + err = pthread_mutexattr_settype(&mutex_attr, type); + if (err != 0) + zabort_err("pthread_mutexattr_settype failed", err) ; + + /* Now we're ready to initialize the mutex itself */ + err = pthread_mutex_init(mx, &mutex_attr) ; + if (err != 0) + zabort_err("pthread_mutex_init failed", err) ; + + /* Be tidy with the attributes */ + err = pthread_mutexattr_destroy(&mutex_attr) ; + if (err != 0) + zabort_err("pthread_mutexattr_destroy failed", err) ; + + /* Done: return the mutex */ + return mx ; +} ; + +/* Destroy given mutex, and (if required) free it. + * -- or do nothing if !qpthreads_enabled. + * + * Returns NULL if freed the mutex, otherwise the address of same. + * + * NB: if !qpthreads_enabled qpt_mutex_init_new() will not have allocated + * anything, so there can be nothing to release -- so does nothing, but + * returns the original mutex address (if any). + */ +qpt_mutex +qpt_mutex_destroy(qpt_mutex mx, int free_mutex) +{ + int err ; + + if (qpthreads_enabled) + { + err = pthread_mutex_destroy(mx) ; + if (err != 0) + zabort_err("pthread_mutex_destroy failed", err) ; + + if (free_mutex) + XFREE(MTYPE_QPT_MUTEX, mx) ; /* sets mx == NULL */ + } ; + + return mx ; +} ; + +/*============================================================================== + * Condition Variable initialise and destroy. + */ + +/* Initialise Condition Variable (allocating if required). + * + * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to + * later enable qpthreads will be a FATAL error). + * + * Options: + * + * qpt_cond_quagga -- use Quagga's default clock + * qpt_cond_realtime -- force CLOCK_REALTIME + * qpt_cond_monotonic -- force CLOCK_MONOTONIC (if available) + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the condition variable -- or original cv id !qpthreads_enabled. + */ +qpt_cond +qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) +{ + pthread_condattr_t cond_attr ; + int err ; + + if (!qpthreads_enabled_freeze) + { + if (cv != NULL) + memset(cv, 0x0F, sizeof(qpt_cond_t)) ; + return cv ; + } ; + + if (cv == NULL) + cv = XMALLOC(MTYPE_QPT_COND, sizeof(qpt_cond_t)) ; + + /* Set up attributes so we can set the type */ + err = pthread_condattr_init(&cond_attr); + if (err != 0) + zabort_err("pthread_condattr_init failed", err) ; + + switch(opts) + { + case qpt_cond_quagga: + break ; + default: + zabort("Invalid qpt_cond option") ; + } ; + + err = pthread_condattr_setclock(&cond_attr, QPT_COND_CLOCK_ID); + if (err != 0) + zabort_err("pthread_condattr_setclock failed", err) ; + + /* Now we're ready to initialize the condition variable itself */ + err = pthread_cond_init(cv, &cond_attr) ; + if (err != 0) + zabort_err("pthread_cond_init failed", err) ; + + /* Be tidy with the attributes */ + err = pthread_condattr_destroy(&cond_attr) ; + if (err != 0) + zabort_err("pthread_condattr_destroy failed", err) ; + + /* Done: return the condition variable */ + return cv ; +} ; + +/* Destroy given condition variable, and (if required) free it + * -- or do nothing if !qpthreads_enabled. + * + * NB: if !qpthreads_enabled qpt_cond_init_new() will not have allocated + * anything, so there can be nothing to release -- so does nothing, but + * returns the original condition variable address (if any). + * + * Returns NULL if freed the condition variable, otherwise the address of same. + */ +qpt_cond +qpt_cond_destroy(qpt_cond cv, int free_cond) +{ + int err ; + + if (qpthreads_enabled) + { + err = pthread_cond_destroy(cv) ; + if (err != 0) + zabort_err("pthread_cond_destroy failed", err) ; + + if (free_cond) + XFREE(MTYPE_QPT_COND, cv) ; /* sets cv == NULL */ + } ; + + return cv ; +} ; + +/* Wait for given condition variable or time-out + * -- or return immediate success if !qpthreads_enabled. + * + * Returns: wait succeeded (1 => success, 0 => timed-out). + * + * NB: timeout time is a qtime_mono_t (monotonic time). + * + * Has to check the return value, so zabort_errno if not EBUSY. + */ + +int +qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time) +{ + struct timespec ts ; + int err ; + + if (qpthreads_enabled) + { + if (QPT_COND_CLOCK_ID != CLOCK_MONOTONIC) + { + timeout_time = qt_clock_gettime(QPT_COND_CLOCK_ID) + + (timeout_time - qt_get_monotonic()) ; + } ; + + err = pthread_cond_timedwait(cv, mx, qtime2timespec(&ts, timeout_time)) ; + if (err == 0) + return 1 ; /* got condition */ + if (err == ETIMEDOUT) + return 0 ; /* got time-out */ + + zabort_err("pthread_cond_timedwait failed", err) ; + } + else + return 0 ; +} ; + +/*============================================================================== + * Signal Handling. + */ + +/* Set thread signal mask -- requires qpthreads_enabled. + * + * Thin wrapper around pthread_sigmask. + * + * zaborts if gets any error. + * + * NB: it is a FATAL error to do this if !qpthreads_enabled. + * + * This is mostly because wish to avoid all pthreads_xxx calls when not + * using pthreads. There is no reason not to use this in a single threaded + * program. + */ +void +qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) +{ + int err ; + + passert(qpthreads_enabled) ; + + if (oset != NULL) + sigemptyset(oset) ; /* to make absolutely sure */ + + err = pthread_sigmask(how, set, oset) ; + if (err != 0) + zabort_err("pthread_sigmask failed", err) ; +} ; + +/* Send given thread the given signal -- requires qpthreads_enabled (!) + * + * Thin wrapper around pthread_kill. + * + * zaborts if gets any error. + */ +void +qpt_thread_signal(qpt_thread_t thread, int signum) +{ + int err ; + + passert(qpthreads_enabled) ; + + err = pthread_kill(thread, signum) ; + if (err != 0) + zabort_err("pthread_kill failed", err) ; +} ; diff --git a/lib/qpthreads.h b/lib/qpthreads.h new file mode 100644 index 00000000..d73182ef --- /dev/null +++ b/lib/qpthreads.h @@ -0,0 +1,443 @@ +/* Quagga Pthreads support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QPTHREADS_H +#define _ZEBRA_QPTHREADS_H + +#include <stdint.h> +#include <time.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> +#include <stdbool.h> + +#include "zassert.h" +#include "qtime.h" + +#ifndef Inline +#define Inline static inline +#endif + +#ifndef private +#define private extern +#endif + +/*============================================================================== + * Quagga Pthread Interface -- qpt_xxxx + * + * Here are captured all the pthreads features used in Quagga. + * + * This provides: + * + * * "wrappers" around functions which should not fail, but whose return + * code it is best to check... at least in a debug environment. + * + * * the possibility of a separate no pthreads build where pthread facilities + * are either dummied out or otherwise dealt with. + * + * * the ability to add any work-arounds which may be required if poorly + * conforming pthreads implementations are encountered + */ + +#if !defined(_POSIX_THREADS) || (_POSIX_THREADS <= 0) +#error Require _POSIX_THREADS +#endif + +/*============================================================================== + * Global Switch -- this allows the library to be run WITHOUT pthreads ! + * + * Nearly every qpthreads function is a NOP if !qpthreads_enabled. + * + * Early in the morning a decision may be made to enable qpthreads -- that must + * be done before any threads are created (or will zabort) and before any + * mutexes and condition variables are initialised (or it will be too late). + * + * Use: qpthreads_enabled -- to test for the enabled-ness + * qpthreads_enabled_freeze -- to test and freeze unset if not yet enabled + */ + +#define qpthreads_enabled ((const uint8_t)qpthreads_enabled_flag) +#define qpthreads_enabled_freeze qpt_freeze_qpthreads_enabled() + +#define qpthreads_thread_created ((const uint8_t) \ + qpthreads_thread_created_flag) + +/*============================================================================== + * Data types + */ + +typedef pthread_t qpt_thread_t ; +typedef pthread_mutex_t qpt_mutex_t ; +typedef pthread_cond_t qpt_cond_t ; + +typedef pthread_attr_t qpt_thread_attr_t ; + +typedef qpt_mutex_t* qpt_mutex ; +typedef qpt_cond_t* qpt_cond ; + +/*============================================================================== + * Thread Creation -- see qpthreads.c for further discussion. + * + * NB: it is a FATAL error to attempt these if !qpthreads_enabled. + */ + +enum qpt_attr_options +{ + qpt_attr_joinable = 0, /* the default for Quagga */ + + qpt_attr_detached = 0x0001, /* otherwise will set joinable */ + + qpt_attr_sched_inherit = 0x0002, /* otherwise will use default */ + + qpt_attr_sched_scope = 0x0004, /* otherwise inherit/default */ + qpt_attr_sched_policy = 0x0008, /* otherwise inherit/default */ + qpt_attr_sched_priority = 0x0010, /* otherwise inherit/default */ +} ; + +#define qpt_attr_sched_explicit ( qpt_attr_sched_scope \ + | qpt_attr_sched_policy \ + | qpt_attr_sched_priority ) + +#define qpt_attr_sched_setting ( qpt_attr_sched_inherit \ + | qpt_attr_sched_explicit ) + +#define qpt_attr_known ( qpt_attr_detached | qpt_attr_sched_setting ) + +extern qpt_thread_attr_t* /* FATAL error if !qpthreads_enabled */ +qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, + int scope, int policy, int priority) ; +extern qpt_thread_t /* FATAL error if !qpthreads_enabled */ +qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) ; + +extern void* /* do nothing if !qpthreads_enabled */ +qpt_thread_join(qpt_thread_t thread_id) ; + +/*============================================================================== + * qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION ! + */ +private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */ +private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */ + +private int +qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */ + +private int +qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */ + +/*============================================================================== + * Thread self knowledge -- even when !qpthreads_enabled there is one thread + */ +Inline qpt_thread_t qpt_thread_self(void) +{ + return pthread_self() ; +} ; + +/*------------------------------------------------------------------------------ + * Thread equality -- returns true iff threads are *equal* + * -- even when !qpthreads_enabled there is one thread + */ +Inline bool qpt_threads_equal(qpt_thread_t a_id, qpt_thread_t b_id) +{ + return pthread_equal(a_id, b_id) != 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Thread identity -- returns true iff current thread is the given thread + * -- even when !qpthreads_enabled there is one thread + */ +Inline bool qpt_thread_is_self(qpt_thread_t id) +{ + pthread_t self = pthread_self() ; + return pthread_equal(self, id) != 0 ; +} ; + +/*============================================================================== + * Mutex handling. + * + * Quagga's default mutex type is: + * + * * PTHREAD_MUTEX_ERRORCHECK unless NDEBUG && NDEBUG_QPTHREADS + * * QPT_MUTEX_TYPE_DEFAULT + * + * QPT_MUTEX_TYPE_DEFAULT may be set elsewhere. If it is not set then it is + * set here to be PTHREAD_MUTEX_NORMAL. + * + * NB: on the face of it PTHREAD_MUTEX_NORMAL should be the fastest. It is + * possible that PTHREAD_MUTEX_DEFAULT may have system specific semantics + * that make it faster than the standard _NORMAL. It is also possible that + * a given system may elect to provide a safer mutex than the _NORMAL by + * default. + * + * If _DEFAULT is faster than _NORMAL, then QPT_MUTEX_TYPE_DEFAULT may be + * used to override this choice. + * + * NB: if NOT qpthreads_enabled, all mutex actions are EMPTY. This allows + * code to be made thread-safe for when pthreads is running, but to work + * perfectly well without pthreads. + * + * NB: do not (currently) support pthread_mutex_timedlock(). + */ + +enum qpt_mutex_options +{ + qpt_mutex_quagga = 0x0000, /* Quagga's default */ + qpt_mutex_normal = 0x0001, + qpt_mutex_recursive = 0x0002, + qpt_mutex_errorcheck = 0x0003, + qpt_mutex_default = 0x0004, /* system default */ +} ; + +#ifndef QPT_MUTEX_TYPE_DEFAULT +# define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL +#endif + +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) +# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT +#else +# define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK +#endif + +extern qpt_mutex /* freezes qpthreads_enabled */ +qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) ; + +#define qpt_mutex_init qpt_mutex_init_new + +extern qpt_mutex /* do nothing if !qpthreads_enabled */ +qpt_mutex_destroy(qpt_mutex mx, int free_mutex) ; + +#define qpt_mutex_destroy_keep(mx) qpt_mutex_destroy(mx, 0) +#define qpt_mutex_destroy_free(mx) qpt_mutex_destroy(mx, 1) + +Inline void +qpt_mutex_lock(qpt_mutex mx) ; /* do nothing if !qpthreads_enabled */ + +Inline int +qpt_mutex_trylock(qpt_mutex mx) ; /* always succeeds if !qpthreads_enabled */ + +Inline void +qpt_mutex_unlock(qpt_mutex mx) ; /* do nothing if !qpthreads_enabled */ + +/*============================================================================== + * Condition Variable handling + * + * Quagga's clock for condition variables is QPT_COND_CLOCK_ID, which + * may be set elsewhere. If it is not set then it is set here to be: + * + * * CLOCK_MONOTONIC if available + * * CLOCK_REALTIME otherwise -- this is the standard default. + * + * QPT_COND_CLOCK_MONOTONIC is set if CLOCK_MONOTONIC is used (and must be set + * if QPT_COND_CLOCK_ID is set elsewhere to something that is monotonic). + * + * NB: the time-out time passed to qpt_cond_timedwait() is a qtime_mono_t + * time (so based on qtime's monotonic time, which is CLOCK_MONOTONIC if + * that is available. + * + * Otherwise, there is a conversion step from qtime_mono_t to whatever the + * timebase for the condition variable is. + * + * NB: static initialisation of condition variables is not supported, to avoid + * confusion between the standard default and Quagga's default. + + * NB: if NOT qpthreads_enabled, all condition actions are EMPTY. This allows + * code to be made thread-safe for when pthreads is running, but to work + * perfectly well without pthreads. + */ + +#ifndef QPT_COND_CLOCK_ID +# ifdef HAVE_CLOCK_MONOTONIC +# define QPT_COND_CLOCK_ID CLOCK_MONOTONIC +# define QPT_COND_CLOCK_MONOTONIC 1 +# else +# define QPT_COND_CLOCK_ID CLOCK_REALTIME +# undef QPT_COND_CLOCK_MONOTONIC +# endif +#endif + +enum qpt_cond_options +{ + qpt_cond_quagga = 0x0000, /* Quagga's default */ +} ; + +extern qpt_cond /* freezes qpthreads_enabled */ +qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) ; + +extern qpt_cond /* do nothing if !qpthreads_enabled */ +qpt_cond_destroy(qpt_cond cv, int free_cond) ; + +#define qpt_cond_destroy_keep(cv) qpt_cond_destroy(cv, 0) +#define qpt_cond_destroy_free(cv) qpt_cond_destroy(cv, 1) + +Inline void /* do nothing if !qpthreads_enabled */ +qpt_cond_wait(qpt_cond cv, qpt_mutex mx) ; + +extern int /* returns !qpthreads_enabled */ +qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time) ; + +Inline void /* do nothing if !qpthreads_enabled */ +qpt_cond_signal(qpt_cond cv) ; + +Inline void /* do nothing if !qpthreads_enabled */ +qpt_cond_broadcast(qpt_cond cv) ; + +/*============================================================================== + * Mutex inline functions + */ + +/* Lock given mutex -- or do nothing if !qpthreads_enabled. + * + * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the + * return value is valid -- zabort_errno if it isn't. + */ + +Inline void +qpt_mutex_lock(qpt_mutex mx) +{ + if (qpthreads_enabled) + { +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) + pthread_mutex_lock(mx) ; +#else + int err = pthread_mutex_lock(mx) ; + if (err != 0) + zabort_err("pthread_mutex_lock failed", err) ; +#endif + } ; +} ; + +/* Try to lock given mutex -- every time a winner if !qpthreads_enabled. + * + * Returns: lock succeeded (1 => have locked, 0 => unable to lock). + * + * Has to check the return value, so zabort_errno if not EBUSY. + */ + +Inline int +qpt_mutex_trylock(qpt_mutex mx) +{ + if (qpthreads_enabled) + { + int err = pthread_mutex_trylock(mx) ; + if (err == 0) + return 1 ; /* success: it's locked. */ + if (err == EBUSY) + return 0 ; /* unable to lock */ + + zabort_err("pthread_mutex_trylock failed", err) ; + } + else + return 1 ; +} ; + +/* Unlock given mutex -- or do nothing if !qpthreads_enabled. + * + * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the + * return value is valid -- zabort_errno if it isn't. + */ +Inline void +qpt_mutex_unlock(qpt_mutex mx) +{ + if (qpthreads_enabled) + { +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) + pthread_mutex_unlock(mx) ; +#else + int err = pthread_mutex_unlock(mx) ; + if (err != 0) + zabort_err("pthread_mutex_unlock failed", err) ; +#endif + } ; +} ; + +/*============================================================================== + * Condition variable inline functions + */ + +/* Wait for given condition variable -- do nothing if !qpthreads_enabled + * + * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the + * return value is valid -- zabort_errno if it isn't. + */ +Inline void +qpt_cond_wait(qpt_cond cv, qpt_mutex mx) +{ + if (qpthreads_enabled) + { +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) + pthread_cond_wait(cv, mx) ; +#else + int err = pthread_cond_wait(cv, mx) ; + if (err != 0) + zabort_err("pthread_cond_wait failed", err) ; +#endif + } ; +} ; + +/* Signal given condition -- do nothing if !qpthreads_enabled + * + * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the + * return value is valid -- zabort_errno if it isn't. + */ +Inline void +qpt_cond_signal(qpt_cond cv) +{ + if (qpthreads_enabled) + { +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) + pthread_cond_signal(cv) ; +#else + int err = pthread_cond_signal(cv) ; + if (err != 0) + zabort_err("pthread_cond_signal failed", err) ; +#endif + } ; +} ; + +/* Broadcast given condition -- do nothing if !qpthreads_enabled + * + * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the + * return value is valid -- zabort_errno if it isn't. + */ +Inline void +qpt_cond_broadcast(qpt_cond cv) +{ + if (qpthreads_enabled) + { +#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) + pthread_cond_broadcast(cv) ; +#else + int err = pthread_cond_broadcast(cv) ; + if (err != 0) + zabort_err("pthread_cond_broadcast failed", err) ; +#endif + } ; +} ; + +/*============================================================================== + * Signal Handling. + */ +void /* FATAL error if !qpthreads_enabled */ +qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) ; + +void /* FATAL error if !qpthreads_enabled */ +qpt_thread_signal(qpt_thread_t thread, int signum) ; + +#endif /* _ZEBRA_QPTHREADS_H */ diff --git a/lib/qstring.c b/lib/qstring.c new file mode 100644 index 00000000..5ca4d868 --- /dev/null +++ b/lib/qstring.c @@ -0,0 +1,511 @@ +/* Some string handling + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qstring.h" + +#include "memory.h" +#include "zassert.h" + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Initialise qstring -- allocate if required. + * + * If non-zero len is given, a body is allocated (for at least len + 1). + * + * Returns: address of qstring + * + * NB: assumes initialising a new structure. If not, then caller should + * use qs_reset() or qs_clear(). + */ +extern qstring +qs_init_new(qstring qs, size_t len) +{ + if (qs == NULL) + qs = qs_new() ; + else + memset(qs, 0, sizeof(qstring_t)) ; /* see qs_new() */ + + if (len != 0) + return qs_make_to_length(qs, len) ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Allocate or reallocate so that string is big enough for the given length. + * + * Allocate qstring if required. Returns with a body with size > 0. + * + * Allocates to 16 byte boundaries with space for '\0' beyond given length. + * + * Returns: address of qstring + * + * NB: allocates new body if the size == 0. + * + * If the qstring is a "dummy", its contents are now copied to the new + * real qstring body -- up to a maximum of the new length. + */ +extern qstring +qs_make_to_length(qstring qs, size_t len) +{ + size_t size = (len + 0x10) & ~(size_t)(0x10 - 1) ; + + if (qs == NULL) + qs = qs_new() ; + + if (size > qs->size) + { + if (qs->size == 0) + { + void* old ; + old = qs->body ; + + qs->size = size ; + qs->body = XMALLOC(MTYPE_QSTRING_BODY, qs->size) ; + + if ((qs->len != 0) && (old != NULL)) + memcpy(qs->body, old, (qs->len <= len) ? qs->len : len) ; + } + else + { + qs->size *= 2 ; + if (qs->size < size) + qs->size = size ; + qs->body = XREALLOC(MTYPE_QSTRING_BODY, qs->body, qs->size) ; + } ; + }; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Add 'n' to the current string length, allocating or extending the body as + * required. + * + * Allocate qstring if required. Returns with a body with size > 0. + * + * Allocates to 16 byte boundaries with space for '\0' beyond new length. + * + * Returns: address of qstring + * + * also: sets char** p_ep to point at the *end* of the new len. + * + * NB: allocates new body if the size == 0. + * + * If the qstring is a "dummy", its contents are now copied to the new + * real qstring body -- up to a maximum of the new length. + */ +extern qstring +qs_add_len(qstring qs, size_t n, char** p_ep) +{ + size_t len ; + len = (qs != NULL) ? qs->len + n : n ; + + qs = qs_make_to_length(qs, len) ; + + qs->len = len ; + + *p_ep = (char*)qs->body + len ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Free body of qstring -- zeroise size, len and cp + * + * Does nothing if qstring is NULL + * + * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain + * the old body. + */ +extern void +qs_free_body(qstring qs) +{ + if (qs != NULL) + { + if (qs->size != 0) + XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + + qs->size = 0 ; + qs->len = 0 ; + qs->cp = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Reset qstring -- free body and, if required, free the structure. + * + * If not freeing the structure, zeroise size, len and cp -- qs_free_body() + * + * Returns: NULL if freed the structure + * address of structure (if any), otherwise + * + * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain + * the old body. + */ +extern qstring +qs_reset(qstring qs, int free_structure) +{ + if (qs != NULL) + { + if (qs->size != 0) + XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + + if (free_structure) + XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ + else + { + qs->size = 0 ; + qs->len = 0 ; + qs->cp = 0 ; + } ; + } ; + return qs ; +} ; + +/*============================================================================== + * printf() and vprintf() type functions + */ + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf printf() + * + * Allocate qstring if required. + * + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) + */ +extern qstring +qs_printf(qstring qs, const char* format, ...) +{ + va_list args; + + va_start (args, format); + qs = qs_vprintf(qs, format, args); + va_end (args); + + return qs; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf vprintf() + * + * Allocate qstring if required. + * + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) + */ +extern qstring +qs_vprintf(qstring qs, const char *format, va_list args) +{ + va_list ac ; + int len ; + qstring qqs ; + + qqs = qs ; + if (qs == NULL) + qs = qs_new() ; + + while (1) + { + /* Note that vsnprintf() returns the length of what it would like to have + * produced, if it had the space. That length does not include the + * trailing '\0'. + * + * Also note that given a zero length the string address may be NULL, and + * the result is still the length required. + */ + va_copy(ac, args); + qs->len = len = vsnprintf (qs->body, qs->size, format, ac) ; + va_end(ac); + + if (len < 0) + break ; + + if (len < (int)qs->size) + return qs ; + + qs_make_to_length(qs, len) ; + } ; + + if (qqs == NULL) + qs_reset_free(qs) ; /* discard what was allocated */ + else + qs->len = 0 ; + + return NULL ; +} ; + +/*============================================================================== + * Other operations + */ + +/*------------------------------------------------------------------------------ + * Set qstring to be copy of the given string. + * + * Allocates a qstring, if required. + * + * Sets qs->len to the length of the string (excluding trailing '\0') + * + * NB: if src == NULL, sets qstring to be zero length string. + * + * Returns: address of the qstring copied to. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_set(qstring qs, const char* src) +{ + qs = qs_set_len(qs, (src != NULL) ? strlen(src) : 0) ; + if (qs->len != 0) + memcpy(qs->body, src, qs->len + 1) ; + else + *((char*)qs->body) = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be leading 'n' bytes of given string. + * + * Allocates qstring if required. + * + * Inserts '\0' terminator after the 'n' bytes copied. + * + * Returns: address of the qstring copied to. + * + * NB: src string MUST be at least 'n' bytes long. + * + * NB: src may not be NULL unless n == 0. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_set_n(qstring qs, const char* src, size_t n) +{ + qs = qs_set_len(qs, n) ; /* ensures have body > n */ + if (n != 0) + memcpy(qs->body, src, n) ; + + *((char*)qs->body + n) = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Append given string to a qstring. + * + * Allocates a qstring, if required. + * + * Sets qs->len to the length of the result (excluding trailing '\0') + * + * NB: if src == NULL, appends nothing -- but result will be '\0' terminated. + * + * Returns: address of the qstring copied to. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring qs_append(qstring qs, const char* src) +{ + size_t n ; + char* ep ; + + n = (src != NULL) ? strlen(src) : 0 ; + + qs = qs_add_len(qs, n, &ep) ; + ep = (char*)qs->body + qs->len ; + + if (n != 0) + memcpy(ep - n, src, n + 1) ; + else + *ep = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be leading 'n' bytes of given string. + * + * Allocates qstring if required. + * + * Returns: address of the qstring copied to. + * + * NB: src string MUST be at least 'n' bytes long. + * + * NB: src may not be NULL unless n == 0. + * + * NB: if n == 0, appends nothing -- but result will be '\0' terminated. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_append_n(qstring qs, const char* src, size_t n) +{ + char* ep ; + + qs = qs_add_len(qs, n, &ep) ; + + if (n != 0) + memcpy(ep - n, src, n) ; + + *ep = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Copy one qstring to another + * + * If both are NULL, returns NULL. + * + * Otherwise if dst is NULL, creates a new qstring. + * + * Sets dst: body = copy of src->len bytes of src->body -- '\0' terminated. + * cp = src->cp + * len = src->len + * + * Where a NULL src has zero cp and len. + * + * If not NULL, the destination is guaranteed to have a body, and that will be + * '\0' terminated. + * + * Returns: the destination qstring + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_copy(qstring dst, qstring src) +{ + size_t n ; + + if (src == NULL) + { + if (dst == NULL) + return dst ; + + n = 0 ; + dst->cp = 0 ; + } + else + { + if (dst == NULL) + dst = qs_new() ; + + n = src->len ; + dst->cp = src->cp ; + } ; + + qs_set_len(dst, n) ; + + if (n > 0) + memcpy(dst->body, src->body, n) ; + + *((char*)dst->body + n) = '\0' ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Compare significant parts of two qstrings. + * + * By significant, mean excluding leading/trailing isspace() and treating + * multiple isspace() as single isspace(). + * + * Compares the 'len' portions of the strings. + * + * If either is NULL, it is deemed to be an empty string. + * + * Returns: -1 => a < b + * 0 => a == b + * +1 => a > b + */ +extern int +qs_cmp_sig(qstring a, qstring b) +{ + const unsigned char* p_a ; + const unsigned char* e_a ; + const unsigned char* p_b ; + const unsigned char* e_b ; + + /* Set up pointers and dispense with leading and trailing isspace() + * + * Dummy up if NULL + */ + if (a != NULL) + { + p_a = a->body ; + e_a = p_a + a->len ; + + while ((p_a < e_a) && isspace(*p_a)) + ++p_a ; + while ((p_a < e_a) && isspace(*(e_a - 1))) + --e_a ; + } + else + { + p_a = NULL ; + e_a = NULL ; + } + + if (b != NULL) + { + p_b = b->body ; + e_b = p_b + b->len ; + + while ((p_b < e_b) && isspace(*p_b)) + ++p_b ; + while ((p_b < e_b) && isspace(*(e_b - 1))) + --e_b ; + } + else + { + p_b = NULL ; + e_b = NULL ; + } ; + + /* Now set about finding the first difference */ + while ((p_a != e_a) && (p_b != e_b)) + { + if (isspace(*p_a) && isspace(*p_b)) + { + do { ++p_a ; } while (isspace(*p_a)) ; + do { ++p_b ; } while (isspace(*p_b)) ; + } ; + + if (*p_a != *p_b) + return (*p_a < *p_b) ? -1 : +1 ; + + ++p_a ; + ++p_b ; + } ; + + /* No difference before ran out of one or both */ + if (p_a != e_a) + return +1 ; + else if (p_b != e_b) + return -1 ; + else + return 0 ; +} ; diff --git a/lib/qstring.h b/lib/qstring.h new file mode 100644 index 00000000..0597eda8 --- /dev/null +++ b/lib/qstring.h @@ -0,0 +1,468 @@ +/* Some string handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QSTRING_H +#define _ZEBRA_QSTRING_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#include "memory.h" + +#ifndef Inline +#define Inline static inline +#endif + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +/*============================================================================== + * These "qstrings" address address the lack of a flexible length string in 'C'. + * + * This is not a general purpose strings module, but provides a limited number + * of useful string operations such that the caller does not need to worry + * about the length of the string, and allocating space and so on. + * + * The caller does, however, have to explicitly release the contents of a + * qstring when it is done with. + */ + +typedef struct qstring qstring_t ; +typedef struct qstring* qstring ; + +struct qstring +{ + union + { + void* body ; + const void* const_body ; + char* char_body ; + unsigned char* uchar_body ; + } ; + size_t size ; + + size_t len ; + size_t cp ; +} ; + +/*------------------------------------------------------------------------------ + * Access functions for body of qstring -- to take care of casting pointers + * + * NB: if the body has not yet been allocated, these functions will return + * NULL or NULL + the offset. + */ +Inline char* /* pointer to body of qstring */ +qs_chars(qstring qs) +{ + return (char*)qs->body ; +} ; + +Inline unsigned char* /* pointer to body of qstring */ +qs_bytes(qstring qs) +{ + return (unsigned char*)qs->body ; +} ; + +Inline char* /* pointer to given offset in qstring */ +qs_chars_at(qstring qs, size_t off) +{ + return qs_chars(qs) + off ; +} ; + +Inline unsigned char* /* pointer to given offset in qstring */ +qs_bytes_at(qstring qs, size_t off) +{ + return qs_bytes(qs) + off ; +} ; + +Inline char* /* pointer to 'cp' offset in qstring */ +qs_cp_char(qstring qs) +{ + return qs_chars_at(qs, qs->cp) ; +} ; + +Inline unsigned char* /* pointer to 'cp' offset in qstring */ +qs_cp_byte(qstring qs) +{ + return qs_bytes_at(qs, qs->cp) ; +} ; + +Inline char* /* pointer to 'len' offset in qstring */ +qs_ep_char(qstring qs) +{ + return qs_chars_at(qs, qs->len) ; +} ; + +Inline unsigned char* /* pointer to 'len' offset in qstring */ +qs_ep_byte(qstring qs) +{ + return qs_bytes_at(qs, qs->len) ; +} ; + +/*============================================================================== + * Functions + */ + +extern qstring qs_init_new(qstring qs, size_t len) ; +extern qstring qs_make_to_length(qstring qs, size_t len) ; +extern void qs_free_body(qstring qs) ; +extern qstring qs_reset(qstring qs, int free_structure) ; + +#define qs_reset_keep(qs) qs_reset(qs, 0) +#define qs_reset_free(qs) qs_reset(qs, 1) + +Inline qstring qs_new(void) ; +Inline qstring qs_dummy(qstring qs, const char* src, int pos) ; + +extern qstring qs_printf(qstring qs, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; + +extern qstring qs_set(qstring qs, const char* src) ; +extern qstring qs_set_n(qstring qs, const char* src, size_t n) ; + +extern qstring qs_append(qstring qs, const char* src) ; +extern qstring qs_append_n(qstring qs, const char* src, size_t n) ; + +Inline qstring qs_need(qstring qs, size_t len) ; +Inline qstring qs_set_len(qstring qs, size_t len) ; +extern qstring qs_add_len(qstring qs, size_t n, char** p_ep) ; +Inline void qs_clear(qstring qs) ; +Inline size_t qs_len(qstring qs) ; +Inline size_t qs_size(qstring qs) ; +Inline void* qs_term(qstring qs) ; + +Inline size_t qs_insert(qstring qs, const void* src, size_t n) ; +Inline void qs_replace(qstring qs, const void* src, size_t n) ; +Inline size_t qs_delete(qstring qs, size_t n) ; + +extern qstring qs_copy(qstring dst, qstring src) ; +extern int qs_cmp_sig(qstring a, qstring b) ; + +/*============================================================================== + * The Inline functions. + */ + +/*------------------------------------------------------------------------------ + * Make a brand new, completely empty qstring + */ +Inline qstring +qs_new(void) +{ + /* Zeroising has set: + * + * body = NULL -- no body + * size = 0 -- no body + * + * len = 0 + * cp = 0 + * + * Nothing more to do unless initial size != 0 + */ + return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; +} ; + +/*------------------------------------------------------------------------------ + * Construct a "dummy" qstring from the given string. + * + * Allocates a qstring if required. + * + * This sets: body = the src + * len = strlen(src) (0 if src is NULL) + * cp = 0 if 'pos' is zero + * len otherwise + * size = 0 + * + * The zero size means that the qstring handling will not attempt to free + * the body, nor will it write to it... Operations which require the qstring + * to have a size will allocate a new body, and discard this one. + * + * Returns: the address of the dummy qstring. + */ +Inline qstring +qs_dummy(qstring qs, const char* src, int pos) +{ + if (qs == NULL) + qs = qs_new() ; + + qs->const_body = src ; + qs->len = (src != NULL) ? strlen(src) : 0 ; + qs->cp = (pos == 0) ? 0 : qs->len ; + qs->size = 0 ; + + return qs ; +} + +/*------------------------------------------------------------------------------ + * Need space for a string of 'len' characters (plus possible '\0'). + * + * Allocates the qstring, if required. + * + * Returns: address of qstring + * + * NB: asking for 0 bytes will cause a body to be allocated, ready for any + * '\0' ! + * + * NB: has no effect on 'cp' or 'len'. (Will be zero if new qstring allocated.) + */ +Inline qstring +qs_need(qstring qs, size_t len) +{ + if ((qs == NULL) || (len >= qs->size)) + return qs_make_to_length(qs, len) ; + + assert(qs->body != NULL) ; + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Set 'len' -- allocate or extend body as required. + * + * Allocates the qstring, if required. + * + * Returns: address of qstring + * + * NB: setting len == 0 bytes will cause a body to be allocated, ready for any + * '\0' ! + * + * NB: has no effect on 'cp' -- even if 'cp' > 'len'. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + */ +Inline qstring +qs_set_len(qstring qs, size_t len) +{ + qs = qs_need(qs, len) ; + qs->len = len ; + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Reset contents of qstring. + * + * Does nothing if qstring is NULL + * + * Sets 'cp' = 'len' = 0. Sets first byte of body (if any) to NULL. + * + * For "dummy" qstring, discards the body. + */ +Inline void +qs_clear(qstring qs) +{ + if (qs != NULL) + { + qs->len = 0 ; + qs->cp = 0 ; + if (qs->size > 0) + *((char*)qs->body) = '\0' ; + else + qs->body = NULL ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Get length of qstring -- by doing strlen() -- and record it in qs->len. + * + * Returns: the string length + * + * NB: if no body has been allocated, length = 0 + */ +Inline size_t +qs_len(qstring qs) +{ + return (qs != NULL) ? (qs->len = (qs->body != NULL) ? strlen(qs_chars(qs)) + : 0) + : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Get size of qstring body. + * + * NB: if no body has been allocated, size == 0 + * if qstring is NULL, size == 0 + * + * NB: if this is a "dummy" qstring, size == 0. + */ +Inline size_t +qs_size(qstring qs) +{ + return (qs != NULL) ? qs->size : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Get address of current end of qstring body -- ie byte at 'len'. + * + * NB: allocates body if required. + * + * There will be space for '\0' after 'len', so the address returned + * is within the real body of the string. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + * + * NB: address of qstring may NOT be NULL. + */ +Inline void* +qs_end(qstring qs) +{ + if (qs->len >= qs->size) + qs_make_to_length(qs, qs->len) ; /* allows for trailing '\0' */ + + return (char*)qs->body + qs->len ; +} ; + +/*------------------------------------------------------------------------------ + * Set '\0' at qs->len -- allocate or extend body as required. + * + * Returns address of body -- NULL if the qstring is NULL + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + */ +Inline void* +qs_term(qstring qs) +{ + size_t len ; + + if (qs == NULL) + return NULL ; + + if ((len = qs->len) >= qs->size) + qs_make_to_length(qs, len) ; + + *qs_chars_at(qs, len) = '\0' ; + + return qs->body ; +} ; + +/*------------------------------------------------------------------------------ + * Insert 'n' bytes at 'cp' -- moves anything cp..len up. + * + * Increases 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + */ +Inline size_t +qs_insert(qstring qs, const void* src, size_t n) +{ + size_t after ; + char* p ; + + if (qs->len < qs->cp) + qs->len = qs->cp ; + after = qs->len - qs->cp ; + + qs_set_len(qs, qs->len + n) ; /* set len and ensure have space */ + + p = qs_cp_char(qs) ; + if (after > 0) + memmove (p + n, p, after) ; + + if (n > 0) + memmove(p, src, n) ; + + return after ; +} ; + +/*------------------------------------------------------------------------------ + * Replace 'n' bytes at 'cp' -- extending if required. + * + * May increase 'len'. but does not affect 'cp'. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + */ +Inline void +qs_replace(qstring qs, const void* src, size_t n) +{ + if ((qs->len < qs->cp + n) || (qs->size == 0)) + qs_set_len(qs, qs->cp + n) ; /* set len and ensure have space */ + + if (n > 0) + memmove(qs_cp_char(qs), src, n) ; +} ; + +/*------------------------------------------------------------------------------ + * Remove 'n' bytes at 'cp' -- extending if required. + * + * May change 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + */ +Inline size_t +qs_delete(qstring qs, size_t n) +{ + size_t after ; + char* p ; + + /* Watch out for "dummy" */ + if (qs->size == 0) + qs_make_to_length(qs, qs->len) ; + + /* If deleting up to or beyond len, then simply set len == cp */ + if ((qs->cp + n) >= qs->len) + { + qs_set_len(qs, qs->cp) ; /* set len, looks after cp > len */ + return 0 ; /* nothing after */ + } + + /* There is at least one byte after cp (so body must exist) */ + after = qs->len - (qs->cp + n) ; + + if (n > 0) + { + p = qs_cp_char(qs) ; + memmove (p, p + n, after) ; + + qs->len -= n ; + } ; + + return after ; +} ; + + +#endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtime.c b/lib/qtime.c new file mode 100644 index 00000000..42b903da --- /dev/null +++ b/lib/qtime.c @@ -0,0 +1,205 @@ +/* Quagga realtime and monotonic clock handling -- functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <sys/times.h> +#include <errno.h> + +#include "zassert.h" +#include "qtime.h" + +/*============================================================================== + * This is a collection of functions and (in qtime.h) macros and inline + * functions which support system time and a monotonic clock. + * + * TODO: introduce mutex for crafted monotonic time, and initialisation + * routine for that: which can preset the various variables... but + * unless is guaranteed to be called, must leave the on-the-fly + * initialisation... could also start a watchdog at that point. + */ + +/*============================================================================== + * Replacement for CLOCK_MONOTONIC. + * + * With thanks to Joakim Tjernlund for reminding everyone of the return value + * from times() ! + * + * times() is defined to return a value which is the time since some fixed time + * before the application started (or when the application started). This time + * is measured in units of sysconf(_SC_CLK_TCK) ticks per second. + * + * The only tricky bit is that the value returned (of type clock_t) is a + * signed integer, which can overflow. It is not defined exactly how it + * does this... This code assumes that the system will wrap around in some + * obvious way. The base of the time for this clock may be when the *system* + * started... so when it overflows may depend on how long the system has been + * up... which suggests that some sensible wrap around is likely (?). + * + * The qtime_t value is in nano-seconds. + * + * The result from times() is in units of sysconf(_SC_CLK_TCK) ticks per second. + * + * If clock_t is a signed 32-bit integer, which is kept +ve, then the clock + * overflows/wraps round in 2^31 ticks which is: + * + * at 100 ticks/sec: > 248 days + * at 1,000 ticks/sec: > 24 days + * at 10,000 ticks/sec: > 59 hours + * + * For safety, this asserts that sysconf(_SC_CLK_TCK) <= 1,000,000 for + * sizeof(clock_t) > 4, but <= 1,000 for sizeof(clock_t) == 4. + * + * (It appears that 60, 100, 250 and 1,000 ticks/sec. are popular options.) + * + * If sizeof(clock_t) > 4, it is assumed large enough never to wrap around. + * + * When clock_t is a 32-bit integer must be at least ready for wrap around. + * There are two cases: + * + * * +ve wrap around. new < old value, and new >= 0 + * + * step = (INT32_MAX - old + 1) + new + * + * * -ve wrap around. new < old value, and new < 0 (and old > 0) + * + * step = (INT32_MAX - old + 1) - (INT32_MIN - new) + * + * In any event, a step > 24 hours is taken to means that something has gone + * very, very badly wrong. + * + * NB: it is assumed that qt_craft_monotonic will be called often enough to + * ensure that the check on the step size will not be triggered ! + * + * NB: it is assumed that times() does not simply stick if it overflows. + * + * TODO: Add a watchdog to monitor the behaviour of this clock ? + */ + +CONFIRM((sizeof(clock_t) >= 4) && (sizeof(clock_t) <= 8)) ; + +#ifdef GNU_LINUX +#define TIMES_TAKES_NULL 1 +#else +#undef TIMES_TAKES_NULL +#endif + +static uint64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */ +static int64_t last_times_sample = 0 ; /* last value returned by times() */ + +static uint64_t step_limit = 0 ; /* for sanity check */ + +static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */ +static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */ +static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */ + +qtime_mono_t +qt_craft_monotonic(void) { + struct tms dummy ; + int64_t this_times_sample ; + uint64_t step ; + + /* Set up times_scale_q & times_scale_q if not yet done. */ + if (times_clk_tcks == 0) /* Is zero until it's initialized */ + { + lldiv_t qr ; + confirm(sizeof(qtime_t) <= sizeof(long long int)) ; + + times_clk_tcks = sysconf(_SC_CLK_TCK) ; + passert((times_clk_tcks > 0) && + (times_clk_tcks <= (sizeof(clock_t) > 4) ? 1000000 + : 1000)) ; + + qr = lldiv(QTIME_SECOND, times_clk_tcks) ; + times_scale_q = qr.quot ; + times_scale_r = qr.rem ; + + step_limit = (uint64_t)24 * 60 * 60 * times_clk_tcks ; + } ; + + /* No errors are defined for times(), but a return of -1 is defined */ + /* to indicate an error condition, with errno saying what it is ! */ + /* */ + /* The following deals carefully with this -- cannot afford for the */ + /* clock either to jump or to get stuck ! */ + +#ifdef TIMES_TAKES_NULL + this_times_sample = times(NULL) ; /* assume this saves effort ! */ +#else + this_times_sample = times(&dummy) ; +#endif + + if (this_times_sample == -1) /* deal with theoretical error */ + { + errno = 0 ; + this_times_sample = times(&dummy) ; + if (errno != 0) + zabort_errno("times() failed") ; + } ; + + /* Calculate the step and verify sensible. */ + /* */ + /* Watch out for huge jumps and/or time going backwards. */ + /* For 32-bit clock_t, look out for wrap-around. */ + + if ((sizeof(clock_t) > 4) || (this_times_sample > last_times_sample)) + /* time going backwards will appear as HUGE step forwards. */ + step = (uint64_t)(this_times_sample - last_times_sample) ; + else + { + if (this_times_sample > 0) + /* both samples +ve => +ve wrap around. */ + step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1) + + this_times_sample ) ; + else + /* this sample -ve and last sample +ve => -ve wrap round */ + /* this sample -ve and last sample -ve => time gone backwards */ + /* (which appears as a HUGE step forwards). */ + step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1) + - ((int64_t)INT32_MIN - this_times_sample) ) ; + } ; + + /* TODO: better error messaging for large clock jumps. */ + if (step > step_limit) + zabort("Sudden large monotonic clock jump") ; + + /* Advance the monotonic clock in sysconf(_SC_CLK_TCK) units. */ + monotonic += step ; + + /* Remember what we got, for next time. */ + last_times_sample = this_times_sample ; + + /* Scale to qtime_t units. */ + if (times_scale_r == 0) + return monotonic * times_scale_q ; + else + return (monotonic * times_scale_q) + + ((monotonic * times_scale_r) / times_clk_tcks) ; +} ; + +/*------------------------------------------------------------------------------ + * Get crafted monotonic time -- in seconds + */ +extern time_t +qt_craft_mono_secs(void) +{ + qt_craft_monotonic() ; /* update the monotonic counter */ + + return monotonic / times_clk_tcks ; +} ; diff --git a/lib/qtime.h b/lib/qtime.h new file mode 100644 index 00000000..df486dfd --- /dev/null +++ b/lib/qtime.h @@ -0,0 +1,333 @@ +/* Quagga realtime and monotonic clock handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QTIME_H +#define _ZEBRA_QTIME_H + +#include <stdint.h> +#include <stdlib.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> + +#include "zassert.h" +#include "config.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * qtime_t -- signed 64-bit integer. + * + * The various time functions work in terms of the structures: + * + * timespec -- tv_secs seconds + * tv_nsecs nano-seconds + * + * timeval -- tv_secs seconds + * tv_usecs micro-seconds + * + * Given a 64-bit integer it is much easier to do operations on a 64 bit + * (signed) nano-second value. That gives > 34 bits for the seconds count, + * and counts from zero to > 290 years. + */ + +typedef int64_t qtime_t ; + +typedef qtime_t qtime_real_t ; /* qtime_t value, realtime time-base */ +typedef qtime_t qtime_mono_t ; /* qtime_t value, monotonic time-base */ + +typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */ + /* ...just in case != CLOCK_REALTIME ! */ + +/* A qtime_t second 123456789 -- nano-seconds */ +#define QTIME_SECOND 1000000000 +#define TIMESPEC_SECOND 1000000000 +#define TIMEVAL_SECOND 1000000 + +/* Macro to convert time in seconds to a qtime_t */ +/* Note that the time to convert may be a float. */ +#define QTIME(s) ((qtime_t)((s) * (qtime_t)QTIME_SECOND)) + +Inline qtime_t +timespec2qtime(struct timespec* p_ts) ; + +Inline qtime_t +timeval2qtime(struct timeval* p_tv) ; + +Inline struct timespec* +qtime2timespec(struct timespec* p_ts, qtime_t qt) ; + +Inline struct timeval* +qtime2timeval(struct timeval* p_tv, qtime_t qt) ; + +/*============================================================================== + * Clocks. + * + * Here is support for: + * + * * System Clock + * + * This can be read using either clock_gettime(CLOCK_REALTIME, &ts) or + * gettimeofday(&tv, NULL) -- which (are believed to) return the same clock, + * but in different units. + * + * * Monotonic Clock + * + * Using clock_gettime(CLOCK_MONOTONIC, &ts) if it is available, otherwise + * a manufactured equivalent using times() -- see qt_craft_monotonic(). + */ + +Inline qtime_real_t +qt_get_realtime(void) ; /* clock_gettime(CLOCK_REALTIME, ...) */ + +Inline qtime_mono_t +qt_add_realtime(qtime_t interval) ; /* qt_get_realtime() + interval */ + +Inline qtime_mono_t +qt_get_monotonic(void) ; /* clock_gettime(CLOCK_MONOTONIC, ...) */ + /* OR equivalent using times() */ +Inline qtime_mono_t +qt_add_monotonic(qtime_t interval) ; /* qt_get_monotonic() + interval */ +Inline time_t qt_get_mono_secs(void) ; + /* clock_gettime(CLOCK_MONOTONIC, ...) */ + /* OR equivalent using times() */ + +Inline qtime_mono_t /* monotonic time from CLOCK_REALTIME */ +qt_realtime2monotonic(qtime_real_t realtime) ; +Inline qtime_real_t /* CLOCK_REALTIME from monotonic time */ +qt_monotonic2realtime(qtime_mono_t monotonic) ; + +/* Function to manufacture a monotonic clock. */ +extern qtime_mono_t qt_craft_monotonic(void) ; +extern time_t qt_craft_mono_secs(void) ; + +/* These are provided just in case gettimeofday() != CLOCK_REALTIME */ +Inline qtime_tod_t +qt_get_timeofday(void) ; /* gettimeofday(&tv, NULL) */ + +Inline qtime_tod_t +qt_add_timeofday(qtime_t interval) ; /* qt_get_timeofday() + interval */ + +Inline qtime_mono_t /* monotonic time from timeofday */ +qt_timeofday2monotonic(qtime_tod_t timeofday) ; +Inline qtime_tod_t /* timeofday from monotonic time */ +qt_monotonic2timeofday(qtime_mono_t monotonic) ; + +/*============================================================================== + * Inline conversion functions + */ + +/* Convert timespec to qtime_t + * + * Returns qtime_t value. + */ +Inline qtime_t +timespec2qtime(struct timespec* p_ts) +{ + return QTIME(p_ts->tv_sec) + p_ts->tv_nsec ; + confirm(QTIME_SECOND == TIMESPEC_SECOND) ; +} ; + +/* Convert timeval to qtime_t + * + * Returns qtime_t value. + */ +Inline qtime_t +timeval2qtime(struct timeval* p_tv) +{ + return QTIME(p_tv->tv_sec) + (p_tv->tv_usec * 1000) ; + confirm(QTIME_SECOND == TIMEVAL_SECOND * 1000) ; +} ; + +/* Convert qtime_t to timespec + * + * Takes address of struct timespec and returns that address. + */ +Inline struct timespec* +qtime2timespec(struct timespec* p_ts, qtime_t qt) +{ + lldiv_t imd = lldiv(qt, QTIME_SECOND) ; + confirm(sizeof(long long) >= sizeof(qtime_t)) ; + + p_ts->tv_sec = imd.quot ; + p_ts->tv_nsec = imd.rem ; + confirm(TIMESPEC_SECOND == QTIME_SECOND) ; + + return p_ts ; +} ; + +/* Convert timespec to qtime_t + * + * Takes address of struct timespec and returns that address. + */ +Inline struct timeval* +qtime2timeval(struct timeval* p_tv, qtime_t qt) +{ + lldiv_t imd = lldiv(qt, QTIME_SECOND) ; + confirm(sizeof(long long) >= sizeof(qtime_t)) ; + + p_tv->tv_sec = imd.quot ; + p_tv->tv_usec = imd.rem / 1000 ; + confirm(TIMEVAL_SECOND * 1000 == QTIME_SECOND) ; + + return p_tv ; +} ; + +/*============================================================================== + * Inline Clock Functions. + */ + +/* Read given clock & return a qtime_t value. + * + * While possibility of error is essentially theoretical, must treat it as a + * FATAL error -- cannot continue with broken time value ! + */ + +Inline qtime_t +qt_clock_gettime(clockid_t clock_id) +{ + struct timespec ts ; + + if (clock_gettime(clock_id, &ts) != 0) + zabort_errno("clock_gettime failed") ; + + return timespec2qtime(&ts) ; +} ; + +/* clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value + * + * While possibility of error is essentially theoretical, must treat it as a + * FATAL error -- cannot continue with broken time value ! + */ +Inline qtime_real_t +qt_get_realtime(void) +{ + return qt_clock_gettime(CLOCK_REALTIME) ; +} ; + +/* qt_get_realtime() + interval + */ +Inline qtime_real_t +qt_add_realtime(qtime_t interval) +{ + return qt_get_realtime() + interval; +} ; + +/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() + * -- returning qtime_t value + * + * While possibility of error is essentially theoretical, must treat it as a + * FATAL error -- cannot continue with broken time value ! + */ +Inline qtime_mono_t +qt_get_monotonic(void) +{ +#ifdef HAVE_CLOCK_MONOTONIC + return qt_clock_gettime(CLOCK_MONOTONIC) ; +#else + return qt_craft_monotonic() ; +#endif +} ; + +/* qt_get_monotonic() + interval + */ +Inline qtime_mono_t +qt_add_monotonic(qtime_t interval) +{ + return qt_get_monotonic() + interval; +} ; + +/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() + * -- returning time_t value + * + * Value returned is in seconds -- for coarser grain timings. + * + * While possibility of error is essentially theoretical, must treat it as a + * FATAL error -- cannot continue with broken time value ! + */ +Inline time_t +qt_get_mono_secs(void) +{ +#ifdef HAVE_CLOCK_MONOTONIC + struct timespec ts ; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + zabort_errno("clock_gettime failed") ; + + return ts.tv_sec ; +#else + return qt_craft_mono_secs() ; +#endif +} ; + +/* gettimeofday(&tv, NULL) -- returning qtime_t value + */ +Inline qtime_tod_t +qt_get_timeofday(void) +{ + struct timeval tv ; + gettimeofday(&tv, NULL) ; + return timeval2qtime(&tv) ; +} + +/* qt_get_timeofday() + interval + */ +Inline qtime_tod_t +qt_add_timeofday(qtime_t interval) +{ + return qt_get_timeofday() + interval; +} ; + +/*============================================================================== + * Conversion between realtime/timeofday and monotonic + */ + +/* Convert a CLOCK_REALTIME time to our local monotonic time. */ +Inline qtime_mono_t +qt_realtime2monotonic(qtime_real_t realtime) +{ + return qt_get_monotonic() + (realtime - qt_get_realtime()) ; +} ; + +/* Convert a local monotonic time to CLOCK_REALTIME time. */ +Inline qtime_real_t +qt_monotonic2realtime(qtime_mono_t monotonic) +{ + return qt_get_realtime() + (monotonic - qt_get_monotonic()) ; +} ; + +/* Convert a gettimeofday() time to our local monotonic time. */ +Inline qtime_mono_t +qt_timeofday2monotonic(qtime_tod_t timeofday) +{ + return qt_get_monotonic() + (timeofday - qt_get_timeofday()) ; +} ; + +/* Convert a local monotonic time to gettimeofday() time. */ +Inline qtime_tod_t +qt_monotonic2timeofday(qtime_mono_t monotonic) +{ + return qt_get_timeofday() + (monotonic - qt_get_monotonic()) ; +} ; + +#endif /* _ZEBRA_QTIME_H */ diff --git a/lib/qtimers.c b/lib/qtimers.c new file mode 100644 index 00000000..8c08a6bc --- /dev/null +++ b/lib/qtimers.c @@ -0,0 +1,450 @@ +/* Quagga timers support -- functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stddef.h> +#include <string.h> + +#include "zassert.h" +#include "qtimers.h" +#include "memory.h" +#include "heap.h" + +enum { qdebug = +#ifdef QDEBUG + 1 +#else + 0 +#endif +}; + +/*============================================================================== + * Quagga Timers -- qtimer_xxxx + * + * Here and in qtimers.h is a data structure for managing multiple timers + * each with an action to be executed when the timer expires. + * + * The qtime_pile structure manages a "pile" of qtimer structures which are + * waiting for the right time to go off. + * + * NB: it is ASSUMED that a qtime_pile will be private to the thread in which + * it is created and used. + * + * There is NO mutex handling here. + * + * Timers are triggered by calling qtimer_dispatch_next(). This is given the + * current qtimer time (see below), and it dispatches the first timer whose + * time has come (or been passed). Dispatching a timer means calling its + * action function (see below). Each call of qtimer_dispatch_next() triggers + * at most one timer. + * + * Time Base + * --------- + * + * The time base for qtimers is the monotonic time provided in qtime.c/.h. + * + * Interval + * -------- + * + * There is an optional interval associated with each timer. + * + * The timer may be set to "now + interval", and the interval is stored with + * the timer. + * + * The timer may be set to its current time + stored interval (to provide a + * "steady" clock). + * + * Action Functions + * ---------------- + * + * There is a separate action function for each timer. + * + * When the action function is called it is passed the qtimer structure, the + * timer_info pointer from that structure and the time which triggered the + * timer (which may, or may not, be the current qtimer time). + * + * During an action function timers may be set/unset, actions changed, and so + * on... there are no restrictions EXCEPT that may NOT recurse into the + * dispatch function. + * + * If nothing is done with the time during the action function, the timer is + * implicitly unset when the action function returns. + */ + +static int +qtimer_cmp(qtimer* a, qtimer* b) /* the heap discipline */ +{ + if ((**a).time < (**b).time) + return -1 ; + if ((**a).time > (**b).time) + return +1 ; + return 0 ; +} ; + +/*============================================================================== + * qtimer_pile handling + */ + +/*------------------------------------------------------------------------------ + * Initialise a timer pile -- allocating it if required. + * + * Returns the qtimer_pile. + */ +qtimer_pile +qtimer_pile_init_new(qtimer_pile qtp) +{ + if (qtp == NULL) + qtp = XCALLOC(MTYPE_QTIMER_PILE, sizeof(struct qtimer_pile)) ; + else + memset(qtp, 0, sizeof(struct qtimer_pile)) ; + + /* Zeroising has initialised: + * + * timers -- invalid heap -- need to properly initialise + * current = NULL -- no current timer + */ + + /* (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining + * about first argument of offsetof().) + */ + typedef struct qtimer qtimer_t ; + + heap_init_new_backlinked(&qtp->timers, 0, (heap_cmp*)qtimer_cmp, + offsetof(qtimer_t, backlink)) ; + return qtp ; +} ; + +/*------------------------------------------------------------------------------ + * Get the timer time for the first timer due to go off in the given pile. + * + * The caller must provide a maximum acceptable time. If the qtimer pile is + * empty, or the top entry times out after the maximum time, then the maximum + * is returned. + */ +extern qtime_t +qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) +{ + qtime_t top_wait ; + qtimer qtr = heap_top_item(&qtp->timers) ; + + if (qtr == NULL) + return max_wait ; + + top_wait = qtr->time - qt_get_monotonic() ; + + return (top_wait < max_wait) ? top_wait : max_wait ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch the next timer whose time is <= the given "upto" time. + * + * The upto time must be a qtimer time (!) -- see qtimer_time_now(). + * + * The upto argument allows the caller to get a qtimer_time_now() value, and + * then process all timers upto that time. + * + * Returns true <=> dispatched a timer, and there may be more to do. + * false <=> nothing to do (and nothing done). + * + * NB: it is a sad, very sad, mistake to recurse into this ! + */ +extern bool +qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) +{ + qtimer qtr ; + + if (qdebug) + qtimer_pile_verify(qtp) ; + + qtr = heap_top_item(&qtp->timers) ; + + if ((qtr == NULL) || (qtr->time > upto)) + return 0 ; + + passert((qtp == qtr->pile) && (qtr->active)) ; + + qtp->implicit_unset = qtr ; /* Timer must be unset if is still here + when the action function returns */ + qtr->action(qtr, qtr->timer_info, upto) ; + + if (qtp->implicit_unset == qtr) + qtimer_unset(qtr) ; + else + assert(qtp->implicit_unset == NULL) ; /* check for tidy-ness */ + + return 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Ream out (another) item from qtimer_pile. + * + * If pile is empty, release the qtimer_pile structure, if required. + * + * See: #define qtimer_pile_ream_free(qtp) + * #define qtimer_pile_ream_keep(qtp) + * + * Useful for emptying out and discarding a pile of timers: + * + * while ((p_qtr = qtimer_pile_ream_free(qtp))) + * ... do what's required to release the item p_qtr + * + * Returns NULL when timer pile is empty (and has been released, if required). + * + * If the timer pile is not released, it may be reused without reinitialisation. + * + * NB: once reaming has started, the timer pile MUST NOT be used for anything, + * and the process MUST be run to completion. + */ +qtimer +qtimer_pile_ream(qtimer_pile qtp, int free_structure) +{ + qtimer qtr ; + + qtr = heap_ream_keep(&qtp->timers) ; /* ream, keeping the heap structure */ + if (qtr != NULL) + qtr->active = false ; /* has been removed from pile */ + else + if (free_structure) /* pile is empty, may now free it */ + XFREE(MTYPE_QTIMER_PILE, qtp) ; + + return qtr ; +} ; + +/*============================================================================== + * qtimer handling + */ + +/*------------------------------------------------------------------------------ + * Initialise qtimer structure -- allocating one if required. + * + * Associates qtimer with the given pile of timers, and sets up the action and + * the timer_info. + * + * Once initialised, the timer may be set. + * + * Returns the qtimer. + */ +qtimer +qtimer_init_new(qtimer qtr, qtimer_pile qtp, + qtimer_action* action, void* timer_info) +{ + if (qtr == NULL) + qtr = XCALLOC(MTYPE_QTIMER, sizeof(struct qtimer)) ; + else + memset(qtr, 0, sizeof(struct qtimer)) ; + + /* Zeroising has initialised: + * + * pile -- NULL -- not in any pile (yet) + * backlink -- unset + * + * active -- false + * + * time -- unset + * action -- NULL -- no action set (yet) + * timer_info -- NULL -- no timer info set (yet) + * + * interval -- unset + */ + + qtr->pile = qtp ; + qtr->action = action ; + qtr->timer_info = timer_info ; + + return qtr ; +} ; + +/*------------------------------------------------------------------------------ + * Free given timer -- if any. + * + * Unsets it first if it is active or pending unset. + * + * Returns: NULL + */ +extern qtimer +qtimer_free(qtimer qtr) +{ + /* Note that if is the current dispatched timer and an unset is still + * pending, then it must still be active. + */ + if (qtr != NULL) + { + if (qtr->active) + qtimer_unset(qtr) ; + + XFREE(MTYPE_QTIMER, qtr) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Set pile in which given timer belongs. + * + * Does nothing if timer already belongs to the given pile. + * + * Unsets the timer if active in another pile, before reassigning it. + */ +extern void +qtimer_set_pile(qtimer qtr, qtimer_pile qtp) +{ + if (qtr->pile == qtp) + return ; + + /* Note that if is the current dispatched timer and an unset is still + * pending, then it must still be active. + */ + if (qtr->active) + qtimer_unset(qtr) ; + + qtr->pile = qtp ; +} + +/*------------------------------------------------------------------------------ + * Set given timer. + * + * Setting a -ve time => qtimer_unset. + * + * Sets any given action -- if the action given is NULL, retains previously set + * action. + * + * If the timer is already active, sets the new time & updates pile. + * + * Otherwise, sets the time and adds to pile -- making timer active. + * + * It is an error to set a timer which has a NULL action. + */ +extern void +qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) +{ + qtimer_pile qtp ; + + if (when < 0) + return qtimer_unset(qtr) ; + + qtp = qtr->pile ; + assert(qtp != NULL) ; + + if (qdebug) + qtimer_pile_verify(qtp) ; + + qtr->time = when ; + + if (qtr->active) + { + /* Is active, so update the timer in the pile. */ + heap_update_item(&qtp->timers, qtr) ; + + if (qtr == qtp->implicit_unset) + qtp->implicit_unset = NULL ; /* no unset required, now */ + } + else + { + /* Is not active, so insert the timer into the pile. */ + heap_push_item(&qtp->timers, qtr) ; + + assert(qtr != qtp->implicit_unset) ; /* because it's not active */ + + qtr->active = true ; + } ; + + if (action != NULL) + qtr->action = action ; + else + assert(qtr->action != NULL) ; + + if (qdebug) + qtimer_pile_verify(qtp) ; +} ; + +/*------------------------------------------------------------------------------ + * Unset given timer + * + * If the timer is active, removes from pile and sets inactive. + */ +extern void +qtimer_unset(qtimer qtr) +{ + qtimer_pile qtp = qtr->pile ; + + assert(qtp != NULL) ; + + if (qdebug) + qtimer_pile_verify(qtp) ; + + if (qtr->active) + { + if (qtr == qtp->implicit_unset) + qtp->implicit_unset = NULL ; /* no unset required, now */ + + heap_delete_item(&qtp->timers, qtr) ; + + if (qdebug) + qtimer_pile_verify(qtp) ; + + qtr->active = false ; + } + else + assert(qtr != qtp->implicit_unset) ; +} ; + +/*============================================================================== + * Verification code for debug purposes. + */ +extern void +qtimer_pile_verify(qtimer_pile qtp) +{ + heap th = &qtp->timers ; + vector v ; + vector_index i ; + vector_index e ; + qtimer qtr ; + bool seen ; + + assert(qtp != NULL) ; + + /* (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining + * about first argument of offsetof().) + */ + typedef struct qtimer qtimer_t ; + + assert(th->cmp == (heap_cmp*)qtimer_cmp) ; + assert(th->state == Heap_Has_Backlink) ; + assert(th->backlink_offset == offsetof(qtimer_t, backlink)) ; + + v = &th->v ; + e = vector_end(v) ; + for (i = 0 ; i < e ; ++i) + { + qtr = vector_get_item(v, i) ; + assert(qtr != NULL) ; + + if (qtr == qtp->implicit_unset) + seen = 1 ; + + assert(qtr->active) ; + + assert(qtr->pile == qtp) ; + assert(qtr->backlink == i) ; + assert(qtr->action != NULL) ; + } ; + + assert(seen || (qtp->implicit_unset == NULL)) ; +} ; diff --git a/lib/qtimers.h b/lib/qtimers.h new file mode 100644 index 00000000..5beb931b --- /dev/null +++ b/lib/qtimers.h @@ -0,0 +1,174 @@ +/* Quagga timers support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QTIMERS_H +#define _ZEBRA_QTIMERS_H + +#include <stdbool.h> + +#include "zassert.h" +#include "qtime.h" +#include "heap.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Quagga Timers -- qtimer_xxxx + * + * Here and in qtimers.c is a data structure for managing multiple timers + * each with an action to be executed when the timer expires. + */ + +/*============================================================================== + * Data Structures. + */ + +typedef struct qtimer* qtimer ; +typedef struct qtimer_pile* qtimer_pile ; + +typedef void (qtimer_action)(qtimer qtr, void* timer_info, qtime_mono_t when) ; + +struct qtimer +{ + qtimer_pile pile ; /* pile currently allocated to */ + heap_backlink_t backlink ; + + bool active ; /* true => in the pile */ + + qtime_mono_t time ; /* current time to trigger action */ + qtimer_action* action ; + void* timer_info ; + + qtime_t interval ; /* optional timer interval */ +} ; + +struct qtimer_pile +{ + struct heap timers ; + + qtimer implicit_unset ; /* used during dispatch */ +} ; + +/*============================================================================== + * Functions + */ + +extern qtimer_pile qtimer_pile_init_new(qtimer_pile qtp) ; +extern bool qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ; +extern qtime_t qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) ; +extern qtimer qtimer_pile_ream(qtimer_pile qtp, int free_structure) ; + +/* Ream out qtimer pile and free the qtimer structure. */ +#define qtimer_pile_ream_free(qtp) qtimer_pile_ream(qtp, 1) +/* Ream out qtimer pile but keep the qtimer structure. */ +#define qtimer_pile_ream_keep(qtp) qtimer_pile_ream(qtp, 0) + +extern qtimer qtimer_init_new(qtimer qtr, qtimer_pile qtp, + qtimer_action* action, void* timer_info) ; +extern void qtimer_set_pile(qtimer qtr, qtimer_pile qtp) ; +Inline void qtimer_set_action(qtimer qtr, qtimer_action* action) ; +Inline void qtimer_set_info(qtimer qtr, void* timer_info) ; + +extern qtimer qtimer_free(qtimer qtr) ; +extern void qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) ; +extern void qtimer_unset(qtimer qtr) ; + +Inline void qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) ; +Inline qtime_mono_t qtimer_get(qtimer qtr) ; +Inline void qtimer_set_interval(qtimer qtr, qtime_t interval, + qtimer_action* action) ; +Inline void qtimer_add_interval(qtimer qtr, qtimer_action* action) ; + +Inline qtime_t qtimer_get_interval(qtimer qtr) ; +extern void qtimer_pile_verify(qtimer_pile qtp) ; + +/*============================================================================== + * Inline functions + */ + +/*------------------------------------------------------------------------------ + * Set given timer to given time later than *its* current time. + */ +Inline void +qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) +{ + qtimer_set(qtr, qtimer_get(qtr) + interval, action); +} ; + +/*------------------------------------------------------------------------------ + * Get the given timer's time. + */ +Inline qtime_mono_t +qtimer_get(qtimer qtr) +{ + return qtr->time ; +} ; + +/*------------------------------------------------------------------------------ + * Set action for given timer -- setting a NULL action unsets the timer. + */ +Inline void +qtimer_set_action(qtimer qtr, qtimer_action* action) +{ + if (action == NULL) + qtimer_unset(qtr) ; + qtr->action = action ; +} ; + +/*------------------------------------------------------------------------------ + * Set timer_info for given timer. + */ +Inline void +qtimer_set_info(qtimer qtr, void* timer_info) +{ + qtr->timer_info = timer_info ; +} ; + + +/* Interval handling ---------------------------------------------------------*/ + +/* Set the interval field + */ + +Inline void +qtimer_set_interval(qtimer qtr, qtime_t interval, qtimer_action* action) +{ + qtr->interval = interval ; + qtimer_set(qtr, qt_add_monotonic(interval), action) ; +} ; + +Inline void +qtimer_add_interval(qtimer qtr, qtimer_action* action) +{ + qtimer_add(qtr, qtr->interval, action) ; +} ; + +/* Get the current value of the interval field + */ +Inline qtime_t +qtimer_get_interval(qtimer qtr) +{ + return qtr->interval ; +} ; + +#endif /* _ZEBRA_QTIMERS_H */ diff --git a/lib/routemap.c b/lib/routemap.c index 4f4e6d62..b452530d 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -28,7 +28,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "command.h" #include "vty.h" #include "log.h" - + /* Vector for route match rules. */ static vector route_match_vec; @@ -60,7 +60,7 @@ struct route_map_list void (*add_hook) (const char *); void (*delete_hook) (const char *); - void (*event_hook) (route_map_event_t, const char *); + void (*event_hook) (route_map_event_t, const char *); }; /* Master list of route map. */ @@ -72,7 +72,7 @@ route_map_rule_delete (struct route_map_rule_list *, static void route_map_index_delete (struct route_map_index *, int); - + /* New route map allocation. Please note route map's name must be specified. */ static struct route_map * @@ -94,7 +94,7 @@ route_map_add (const char *name) map = route_map_new (name); list = &route_map_master; - + map->next = NULL; map->prev = list->tail; if (list->tail) @@ -117,7 +117,7 @@ route_map_delete (struct route_map *map) struct route_map_list *list; struct route_map_index *index; char *name; - + while ((index = map->head) != NULL) route_map_index_delete (index, 0); @@ -207,44 +207,58 @@ vty_show_route_map_entry (struct vty *vty, struct route_map *map) /* Print the name of the protocol */ if (zlog_default) - vty_out (vty, "%s:%s", zlog_proto_names[zlog_default->protocol], + vty_out (vty, "%s:%s", zlog_get_proto_name(NULL), VTY_NEWLINE); for (index = map->head; index; index = index->next) { - vty_out (vty, "route-map %s, %s, sequence %d%s", + vty_out (vty, "route-map %s, %s, sequence %lu%s", map->name, route_map_type_str (index->type), - index->pref, VTY_NEWLINE); + (unsigned long)index->seq, VTY_NEWLINE); + confirm(sizeof(index->seq) <= sizeof(unsigned long)) ; /* Description */ if (index->description) vty_out (vty, " Description:%s %s%s", VTY_NEWLINE, index->description, VTY_NEWLINE); - + /* Match clauses */ vty_out (vty, " Match clauses:%s", VTY_NEWLINE); for (rule = index->match_list.head; rule; rule = rule->next) - vty_out (vty, " %s %s%s", + vty_out (vty, " %s %s%s", rule->cmd->str, rule->rule_str, VTY_NEWLINE); - + vty_out (vty, " Set clauses:%s", VTY_NEWLINE); for (rule = index->set_list.head; rule; rule = rule->next) vty_out (vty, " %s %s%s", rule->cmd->str, rule->rule_str, VTY_NEWLINE); - + /* Call clause */ vty_out (vty, " Call clause:%s", VTY_NEWLINE); if (index->nextrm) vty_out (vty, " Call %s%s", index->nextrm, VTY_NEWLINE); - + /* Exit Policy */ vty_out (vty, " Action:%s", VTY_NEWLINE); - if (index->exitpolicy == RMAP_GOTO) - vty_out (vty, " Goto %d%s", index->nextpref, VTY_NEWLINE); - else if (index->exitpolicy == RMAP_NEXT) - vty_out (vty, " Continue to next entry%s", VTY_NEWLINE); - else if (index->exitpolicy == RMAP_EXIT) - vty_out (vty, " Exit routemap%s", VTY_NEWLINE); + switch (index->exitpolicy) + { + case RMAP_GOTO: + vty_out (vty, " Goto %lu%s", (unsigned long)index->goto_seq, + VTY_NEWLINE); + confirm(sizeof(index->goto_seq) <= sizeof(unsigned long)) ; + break ; + + case RMAP_NEXT: + vty_out (vty, " Continue to next entry%s", VTY_NEWLINE); + break ; + + case RMAP_EXIT: + vty_out (vty, " Exit routemap%s", VTY_NEWLINE); + break ; + + default: + zabort("invalid route-map 'exitpolicy'") ; + } ; } } @@ -329,13 +343,12 @@ route_map_index_delete (struct route_map_index *index, int notify) /* Lookup index from route map. */ static struct route_map_index * route_map_index_lookup (struct route_map *map, enum route_map_type type, - int pref) + route_map_seq_t seq) { struct route_map_index *index; for (index = map->head; index; index = index->next) - if ((index->type == type || type == RMAP_ANY) - && index->pref == pref) + if ((index->type == type || type == RMAP_ANY) && index->seq == seq) return index; return NULL; } @@ -343,20 +356,20 @@ route_map_index_lookup (struct route_map *map, enum route_map_type type, /* Add new index to route map. */ static struct route_map_index * route_map_index_add (struct route_map *map, enum route_map_type type, - int pref) + route_map_seq_t seq) { struct route_map_index *index; struct route_map_index *point; - /* Allocate new route map inex. */ + /* Allocate new route map index. */ index = route_map_index_new (); - index->map = map; + index->map = map; index->type = type; - index->pref = pref; - - /* Compare preference. */ + index->seq = seq; + + /* Compare sequence number */ for (point = map->head; point; point = point->next) - if (point->pref >= pref) + if (point->seq >= seq) break; if (map->head == NULL) @@ -394,12 +407,12 @@ route_map_index_add (struct route_map *map, enum route_map_type type, /* Get route map index. */ static struct route_map_index * -route_map_index_get (struct route_map *map, enum route_map_type type, - int pref) +route_map_index_get (struct route_map *map, enum route_map_type type, + route_map_seq_t seq) { struct route_map_index *index; - index = route_map_index_lookup (map, RMAP_ANY, pref); + index = route_map_index_lookup (map, RMAP_ANY, seq); if (index && index->type != type) { /* Delete index from route map. */ @@ -407,7 +420,7 @@ route_map_index_get (struct route_map *map, enum route_map_type type, index = NULL; } if (index == NULL) - index = route_map_index_add (map, type, pref); + index = route_map_index_add (map, type, seq); return index; } @@ -420,7 +433,7 @@ route_map_rule_new (void) new = XCALLOC (MTYPE_ROUTE_MAP_RULE, sizeof (struct route_map_rule)); return new; } - + /* Install rule command to the match list. */ void route_map_install_match (struct route_map_rule_cmd *cmd) @@ -552,7 +565,7 @@ route_map_add_match (struct route_map_index *index, const char *match_name, { next = rule->next; if (rule->cmd == cmd) - { + { route_map_rule_delete (&index->match_list, rule); replaced = 1; } @@ -591,9 +604,9 @@ route_map_delete_match (struct route_map_index *index, const char *match_name, cmd = route_map_lookup_match (match_name); if (cmd == NULL) return 1; - + for (rule = index->match_list.head; rule; rule = rule->next) - if (rule->cmd == cmd && + if (rule->cmd == cmd && (rulecmp (rule->rule_str, match_arg) == 0 || match_arg == NULL)) { route_map_rule_delete (&index->match_list, rule); @@ -677,7 +690,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, cmd = route_map_lookup_set (set_name); if (cmd == NULL) return 1; - + for (rule = index->set_list.head; rule; rule = rule->next) if ((rule->cmd == cmd) && (rulecmp (rule->rule_str, set_arg) == 0 || set_arg == NULL)) @@ -698,7 +711,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, The matrix for a route-map looks like this: (note, this includes the description for the "NEXT" and "GOTO" frobs now - + Match | No Match | permit action | cont @@ -707,22 +720,22 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, | deny deny | cont | - + action) -Apply Set statements, accept route -If Call statement is present jump to the specified route-map, if it denies the route we finish. -If NEXT is specified, goto NEXT statement - -If GOTO is specified, goto the first clause where pref > nextpref + -If GOTO is specified, goto the first clause where seq > goto_seq -If nothing is specified, do as Cisco and finish deny) -Route is denied by route-map. cont) -Goto Next index - + If we get no matches after we've processed all updates, then the route is dropped too. - + Some notes on the new "CALL", "NEXT" and "GOTO" call WORD - If this clause is matched, then the set statements are executed and then we jump to route-map 'WORD'. If @@ -735,7 +748,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, first clause greater than this. In order to ensure route-maps *always* exit, you cannot jump backwards. Sorry ;) - + We need to make sure our route-map processing matches the above */ @@ -757,7 +770,7 @@ route_map_apply_match (struct route_map_rule_list *match_list, for (match = match_list->head; match; match = match->next) { /* Try each match statement in turn, If any do not return - RMAP_MATCH, return, otherwise continue on to next match + RMAP_MATCH, return, otherwise continue on to next match statement. All match statements must match for end-result to be a match. */ ret = (*match->cmd->func_apply) (match->value, prefix, @@ -827,7 +840,7 @@ route_map_apply (struct route_map *map, struct prefix *prefix, if (ret == RMAP_DENYMATCH) return ret; } - + switch (index->exitpolicy) { case RMAP_EXIT: @@ -838,9 +851,9 @@ route_map_apply (struct route_map *map, struct prefix *prefix, { /* Find the next clause to jump to */ struct route_map_index *next = index->next; - int nextpref = index->nextpref; + route_map_seq_t goto_seq = index->goto_seq; - while (next && next->pref < nextpref) + while (next && next->seq < goto_seq) { index = next; next = next->next; @@ -898,11 +911,11 @@ route_map_finish (void) vector_free (route_set_vec); route_set_vec = NULL; } - + /* VTY related functions. */ DEFUN (route_map, route_map_cmd, - "route-map WORD (deny|permit) <1-65535>", + "route-map WORD (deny|permit) <1-4294967295>", "Create route-map or enter route-map command mode\n" "Route map tag\n" "Route map denies set operations\n" @@ -910,7 +923,7 @@ DEFUN (route_map, "Sequence to insert to/delete from existing route-map entry\n") { int permit; - unsigned long pref; + unsigned long seq; struct route_map *map; struct route_map_index *index; char *endptr = NULL; @@ -926,26 +939,27 @@ DEFUN (route_map, return CMD_WARNING; } - /* Preference check. */ - pref = strtoul (argv[2], &endptr, 10); - if (pref == ULONG_MAX || *endptr != '\0') + /* Sequence number check. */ + seq = strtoul (argv[2], &endptr, 10); + confirm(sizeof(route_map_seq_t) <= sizeof(unsigned long)) ; + if (seq == ULONG_MAX || *endptr != '\0') { vty_out (vty, "the fourth field must be positive integer%s", VTY_NEWLINE); return CMD_WARNING; } - if (pref == 0 || pref > 65535) + if (seq == 0 || seq > 4294967295) { - vty_out (vty, "the fourth field must be <1-65535>%s", VTY_NEWLINE); + vty_out (vty, "the fourth field must be <1-4294967295>%s", VTY_NEWLINE); return CMD_WARNING; } /* Get route map. */ map = route_map_get (argv[0]); - index = route_map_index_get (map, permit, pref); + index = route_map_index_get (map, permit, seq); vty->index = index; - vty->node = RMAP_NODE; + vty_set_node(vty, RMAP_NODE) ; return CMD_SUCCESS; } @@ -973,7 +987,7 @@ DEFUN (no_route_map_all, DEFUN (no_route_map, no_route_map_cmd, - "no route-map WORD (deny|permit) <1-65535>", + "no route-map WORD (deny|permit) <1-4294967295>", NO_STR "Create route-map or enter route-map command mode\n" "Route map tag\n" @@ -982,7 +996,7 @@ DEFUN (no_route_map, "Sequence to insert to/delete from existing route-map entry\n") { int permit; - unsigned long pref; + unsigned long seq; struct route_map *map; struct route_map_index *index; char *endptr = NULL; @@ -998,17 +1012,18 @@ DEFUN (no_route_map, return CMD_WARNING; } - /* Preference. */ - pref = strtoul (argv[2], &endptr, 10); - if (pref == ULONG_MAX || *endptr != '\0') + /* Sequence number */ + seq = strtoul (argv[2], &endptr, 10); + confirm(sizeof(route_map_seq_t) <= sizeof(unsigned long)) ; + if (seq == ULONG_MAX || *endptr != '\0') { vty_out (vty, "the fourth field must be positive integer%s", VTY_NEWLINE); return CMD_WARNING; } - if (pref == 0 || pref > 65535) + if (seq == 0 || seq > 4294967295) { - vty_out (vty, "the fourth field must be <1-65535>%s", VTY_NEWLINE); + vty_out (vty, "the fourth field must be <1-4294967295>%s", VTY_NEWLINE); return CMD_WARNING; } @@ -1022,10 +1037,10 @@ DEFUN (no_route_map, } /* Lookup route map index. */ - index = route_map_index_lookup (map, permit, pref); + index = route_map_index_lookup (map, permit, seq); if (index == NULL) { - vty_out (vty, "%% Could not find route-map entry %s %s%s", + vty_out (vty, "%% Could not find route-map entry %s %s%s", argv[0], argv[2], VTY_NEWLINE); return CMD_WARNING; } @@ -1066,7 +1081,7 @@ DEFUN (no_rmap_onmatch_next, struct route_map_index *index; index = vty->index; - + if (index) index->exitpolicy = RMAP_EXIT; @@ -1075,32 +1090,33 @@ DEFUN (no_rmap_onmatch_next, DEFUN (rmap_onmatch_goto, rmap_onmatch_goto_cmd, - "on-match goto <1-65535>", + "on-match goto <1-4294967295>", "Exit policy on matches\n" "Goto Clause number\n" "Number\n") { struct route_map_index *index = vty->index; - int d = 0; + route_map_seq_t d = 0; if (index) { if (argc == 1 && argv[0]) - VTY_GET_INTEGER_RANGE("route-map index", d, argv[0], 1, 65536); + /* TODO: why did the on-match goto range include 65536 ? */ + VTY_GET_INTEGER_RANGE("route-map index", d, argv[0], 1, 4294967295); else - d = index->pref + 1; - - if (d <= index->pref) + d = index->seq + 1; + + if (d <= index->seq) { /* Can't allow you to do that, Dave */ - vty_out (vty, "can't jump backwards in route-maps%s", + vty_out (vty, "can't jump backwards in route-maps%s", VTY_NEWLINE); return CMD_WARNING; } else { index->exitpolicy = RMAP_GOTO; - index->nextpref = d; + index->goto_seq = d; } } return CMD_SUCCESS; @@ -1119,7 +1135,7 @@ DEFUN (no_rmap_onmatch_goto, if (index) index->exitpolicy = RMAP_EXIT; - + return CMD_SUCCESS; } @@ -1138,13 +1154,13 @@ ALIAS (no_rmap_onmatch_goto, /* GNU Zebra compatible */ ALIAS (rmap_onmatch_goto, rmap_continue_seq_cmd, - "continue <1-65535>", + "continue <1-4294967295>", "Continue on a different entry within the route-map\n" "Route-map entry sequence number\n") ALIAS (no_rmap_onmatch_goto, no_rmap_continue_seq, - "no continue <1-65535>", + "no continue <1-4294967295>", NO_STR "Continue on a different entry within the route-map\n" "Route-map entry sequence number\n") @@ -1164,7 +1180,7 @@ DEFUN (rmap_show_name, ALIAS (rmap_onmatch_goto, rmap_continue_index_cmd, - "continue <1-65536>", + "continue <1-4294967295>", "Exit policy on matches\n" "Goto Clause number\n") @@ -1259,16 +1275,17 @@ route_map_config_write (struct vty *vty) else first = 0; - vty_out (vty, "route-map %s %s %d%s", + vty_out (vty, "route-map %s %s %lu%s", map->name, route_map_type_str (index->type), - index->pref, VTY_NEWLINE); + (unsigned long)index->seq, VTY_NEWLINE); + confirm(sizeof(index->seq) <= sizeof(unsigned long)) ; if (index->description) vty_out (vty, " description %s%s", index->description, VTY_NEWLINE); for (rule = index->match_list.head; rule; rule = rule->next) - vty_out (vty, " match %s %s%s", rule->cmd->str, + vty_out (vty, " match %s %s%s", rule->cmd->str, rule->rule_str ? rule->rule_str : "", VTY_NEWLINE); @@ -1276,13 +1293,16 @@ route_map_config_write (struct vty *vty) vty_out (vty, " set %s %s%s", rule->cmd->str, rule->rule_str ? rule->rule_str : "", VTY_NEWLINE); - if (index->nextrm) - vty_out (vty, " call %s%s", index->nextrm, VTY_NEWLINE); - if (index->exitpolicy == RMAP_GOTO) - vty_out (vty, " on-match goto %d%s", index->nextpref, VTY_NEWLINE); - if (index->exitpolicy == RMAP_NEXT) - vty_out (vty," on-match next%s", VTY_NEWLINE); - + + if (index->nextrm) + vty_out (vty, " call %s%s", index->nextrm, VTY_NEWLINE); + if (index->exitpolicy == RMAP_GOTO) + vty_out (vty, " on-match goto %lu%s", (unsigned long)index->goto_seq, + VTY_NEWLINE); + confirm(sizeof(index->goto_seq) <= sizeof(unsigned long)) ; + if (index->exitpolicy == RMAP_NEXT) + vty_out (vty," on-match next%s", VTY_NEWLINE); + write++; } return write; @@ -1315,12 +1335,12 @@ route_map_init_vty (void) install_element (RMAP_NODE, &no_rmap_onmatch_next_cmd); install_element (RMAP_NODE, &rmap_onmatch_goto_cmd); install_element (RMAP_NODE, &no_rmap_onmatch_goto_cmd); - + /* Install the continue stuff (ALIAS of on-match). */ install_element (RMAP_NODE, &rmap_continue_cmd); install_element (RMAP_NODE, &no_rmap_continue_cmd); install_element (RMAP_NODE, &rmap_continue_index_cmd); - + /* Install the call stuff. */ install_element (RMAP_NODE, &rmap_call_cmd); install_element (RMAP_NODE, &no_rmap_call_cmd); @@ -1328,7 +1348,7 @@ route_map_init_vty (void) /* Install description commands. */ install_element (RMAP_NODE, &rmap_description_cmd); install_element (RMAP_NODE, &no_rmap_description_cmd); - + /* Install show command */ install_element (ENABLE_NODE, &rmap_show_name_cmd); } diff --git a/lib/routemap.h b/lib/routemap.h index 1402f5c8..ac6cb999 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -16,12 +16,14 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_ROUTEMAP_H #define _ZEBRA_ROUTEMAP_H +#include <stdint.h> + /* Route map's type. */ enum route_map_type { @@ -30,7 +32,9 @@ enum route_map_type RMAP_ANY }; -typedef enum +typedef enum route_map_type route_map_type_t ; + +typedef enum { RMAP_MATCH, RMAP_DENYMATCH, @@ -78,7 +82,7 @@ struct route_map_rule_cmd const char *str; /* Function for value set or match. */ - route_map_result_t (*func_apply)(void *, struct prefix *, + route_map_result_t (*func_apply)(void *, struct prefix *, route_map_object_t, void *); /* Compile argument and return result as void *. */ @@ -105,25 +109,28 @@ struct route_map_rule_list struct route_map_rule *tail; }; -/* Route map index structure. */ +/* Route map sequence number */ +typedef uint32_t route_map_seq_t ; + +/* Route map index structure. */ struct route_map_index { struct route_map *map; char *description; /* Preference of this route map rule. */ - int pref; + route_map_seq_t seq; /* Route map type permit or deny. */ - enum route_map_type type; + enum route_map_type type; /* Do we follow old rules, or hop forward? */ - route_map_end_t exitpolicy; + route_map_end_t exitpolicy; /* If we're using "GOTO", to where do we go? */ - int nextpref; + route_map_seq_t goto_seq; - /* If we're using "CALL", to which route-map do ew go? */ + /* If we're using "CALL", to which route-map do we go? */ char *nextrm; /* Matching rule list. */ @@ -166,7 +173,7 @@ extern int route_map_delete_match (struct route_map_index *index, const char *match_arg); /* Add route-map set statement to the route map. */ -extern int route_map_add_set (struct route_map_index *index, +extern int route_map_add_set (struct route_map_index *index, const char *set_name, const char *set_arg); diff --git a/lib/sigevent.c b/lib/sigevent.c index 30e9a3d1..18fcffb0 100644 --- a/lib/sigevent.c +++ b/lib/sigevent.c @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with Quagga; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -41,13 +41,13 @@ struct quagga_sigevent_master_t { struct thread *t; - struct quagga_signal_t *signals; + struct quagga_signal_t *signals; int sigc; - + volatile sig_atomic_t caught; } sigmaster; -/* Generic signal handler +/* Generic signal handler * Schedules signal event thread */ static void @@ -55,24 +55,30 @@ quagga_signal_handler (int signo) { int i; struct quagga_signal_t *sig; - + for (i = 0; i < sigmaster.sigc; i++) { sig = &(sigmaster.signals[i]); - + if (sig->signal == signo) sig->caught = 1; } - + sigmaster.caught = 1; -} +} -/* check if signals have been caught and run appropriate handlers */ +/* check if signals have been caught and run appropriate handlers + * + * Returns: 0 => nothing to do + * -1 => failed + * > 0 => done this many signals + */ int quagga_sigevent_process (void) { struct quagga_signal_t *sig; int i; + int done ; #ifdef SIGEVENT_BLOCK_SIGNALS /* shouldnt need to block signals, but potentially may be needed */ sigset_t newmask, oldmask; @@ -85,7 +91,7 @@ quagga_sigevent_process (void) sigfillset (&newmask); sigdelset (&newmask, SIGTRAP); sigdelset (&newmask, SIGKILL); - + if ( (sigprocmask (SIG_BLOCK, &newmask, &oldmask)) < 0) { zlog_err ("quagga_signal_timer: couldnt block signals!"); @@ -93,13 +99,14 @@ quagga_sigevent_process (void) } #endif /* SIGEVENT_BLOCK_SIGNALS */ + done = 0 ; if (sigmaster.caught > 0) { sigmaster.caught = 0; /* must not read or set sigmaster.caught after here, * race condition with per-sig caught flags if one does */ - + for (i = 0; i < sigmaster.sigc; i++) { sig = &(sigmaster.signals[i]); @@ -108,6 +115,7 @@ quagga_sigevent_process (void) { sig->caught = 0; sig->handler (); + ++done ; } } } @@ -117,7 +125,7 @@ quagga_sigevent_process (void) return -1; #endif /* SIGEVENT_BLOCK_SIGNALS */ - return 0; + return done ; } #ifdef SIGEVENT_SCHEDULE_THREAD @@ -159,7 +167,7 @@ signal_set (int signo) } ret = sigaction (signo, &sig, &osig); - if (ret < 0) + if (ret < 0) return ret; else return 0; @@ -212,18 +220,25 @@ core_handler(int signo , siginfo, program_counter(context) #endif ); - abort(); + zabort_abort(); } +/* For the signals known to Quagga, and which are in their default state, + * set a Quagga default handler. + */ static void trap_default_signals(void) { static const int core_signals[] = { SIGQUIT, SIGILL, + SIGABRT, #ifdef SIGEMT SIGEMT, #endif +#ifdef SIGIOT + SIGIOT, +#endif SIGFPE, SIGBUS, SIGSEGV, @@ -237,6 +252,7 @@ trap_default_signals(void) SIGXFSZ, #endif }; + static const int exit_signals[] = { SIGHUP, SIGINT, @@ -245,18 +261,20 @@ trap_default_signals(void) SIGUSR1, SIGUSR2, #ifdef SIGPOLL - SIGPOLL, + SIGPOLL, #endif #ifdef SIGVTALRM SIGVTALRM, #endif #ifdef SIGSTKFLT - SIGSTKFLT, + SIGSTKFLT, #endif }; + static const int ignore_signals[] = { SIGPIPE, }; + static const struct { const int *sigs; u_int nsigs; @@ -279,38 +297,49 @@ trap_default_signals(void) for (j = 0; j < sigmap[i].nsigs; j++) { struct sigaction oact; - if ((sigaction(sigmap[i].sigs[j],NULL,&oact) == 0) && - (oact.sa_handler == SIG_DFL)) + if (sigaction(sigmap[i].sigs[j], NULL, &oact) < 0) + zlog_warn("Unable to get signal handler for signal %d: %s", + sigmap[i].sigs[j], errtoa(errno, 0).str); + else { +#ifdef SA_SIGINFO + if (oact.sa_flags & SA_SIGINFO) + continue ; /* Don't set again */ +#endif + if (oact.sa_handler != SIG_DFL) + continue ; /* Don't set again */ + } + if ( (sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) && + (oact.sa_handler == SIG_DFL) ) { struct sigaction act; sigfillset (&act.sa_mask); if (sigmap[i].handler == NULL) { act.sa_handler = SIG_IGN; - act.sa_flags = 0; + act.sa_flags = 0; } else { #ifdef SA_SIGINFO /* Request extra arguments to signal handler. */ act.sa_sigaction = sigmap[i].handler; - act.sa_flags = SA_SIGINFO; + act.sa_flags = SA_SIGINFO; #else - act.sa_handler = sigmap[i].handler; - act.sa_flags = 0; + act.sa_handler = sigmap[i].handler; + act.sa_flags = 0; #endif } - if (sigaction(sigmap[i].sigs[j],&act,NULL) < 0) + if (sigaction(sigmap[i].sigs[j], &act, NULL) < 0) zlog_warn("Unable to set signal handler for signal %d: %s", - sigmap[i].sigs[j],safe_strerror(errno)); + sigmap[i].sigs[j], errtoa(errno, 0).str); } } } } -void -signal_init (struct thread_master *m, int sigc, +void +signal_init (struct thread_master *m, int sigc, struct quagga_signal_t signals[]) { @@ -320,7 +349,7 @@ signal_init (struct thread_master *m, int sigc, /* First establish some default handlers that can be overridden by the application. */ trap_default_signals(); - + while (i < sigc) { sig = &signals[i]; @@ -332,9 +361,29 @@ signal_init (struct thread_master *m, int sigc, sigmaster.sigc = sigc; sigmaster.signals = signals; -#ifdef SIGEVENT_SCHEDULE_THREAD - sigmaster.t = - thread_add_timer (m, quagga_signal_timer, &sigmaster, +#ifdef SIGEVENT_SCHEDULE_THREAD + sigmaster.t = + thread_add_timer (m, quagga_signal_timer, &sigmaster, QUAGGA_SIGNAL_TIMER_INTERVAL); #endif /* SIGEVENT_SCHEDULE_THREAD */ } + +/* turn off trap for SIGABRT ! */ +extern void quagga_sigabrt_no_trap(void) +{ + struct sigaction new_act ; + sigset_t set ; + + sigfillset(&set) ; + + new_act.sa_handler = SIG_DFL ; + new_act.sa_mask = set ; + new_act.sa_flags = 0 ; + sigaction(SIGABRT, &new_act, NULL) ; + + sigemptyset(&set) ; + sigaddset(&set, SIGABRT) ; + sigprocmask(SIG_UNBLOCK, &set, NULL) ; + +} ; + diff --git a/lib/sigevent.h b/lib/sigevent.h index 62b944a7..57486bc2 100644 --- a/lib/sigevent.h +++ b/lib/sigevent.h @@ -1,4 +1,4 @@ -/* +/* * Quagga Signal handling header. * * Copyright (C) 2004 Paul Jakma. @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Quagga; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _QUAGGA_SIGNAL_H @@ -44,10 +44,13 @@ struct quagga_signal_t * - array of quagga_signal_t's describing signals to handle * and handlers to use for each signal */ -extern void signal_init (struct thread_master *m, int sigc, +extern void signal_init (struct thread_master *m, int sigc, struct quagga_signal_t *signals); /* check whether there are signals to handle, process any found */ extern int quagga_sigevent_process (void); +/* turn off trap for SIGABRT ! */ +extern void quagga_sigabrt_no_trap(void) ; + #endif /* _QUAGGA_SIGNAL_H */ @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -38,6 +38,7 @@ #include <lib/version.h> #include "memory.h" #include "sockunion.h" +#include "sockopt.h" #include "smux.h" #define min(A,B) ((A) < (B) ? (A) : (B)) @@ -45,7 +46,7 @@ enum smux_event {SMUX_SCHEDULE, SMUX_CONNECT, SMUX_READ}; void smux_event (enum smux_event, int); - + /* SMUX socket. */ int smux_sock = -1; @@ -81,7 +82,7 @@ static struct cmd_node smux_node = /* thread master */ static struct thread_master *master; - + void * oid_copy (void *dest, const void *src, size_t size) { @@ -93,7 +94,7 @@ oid2in_addr (oid oid[], int len, struct in_addr *addr) { int i; u_char *pnt; - + if (len == 0) return; @@ -108,7 +109,7 @@ oid_copy_addr (oid oid[], struct in_addr *addr, int len) { int i; u_char *pnt; - + if (len == 0) return; @@ -155,7 +156,7 @@ oid_compare_part (oid *o1, int o1_len, oid *o2, int o2_len) return 0; } - + static void smux_oid_dump (const char *prefix, const oid *oid, size_t oid_len) { @@ -205,7 +206,7 @@ smux_socket (void) } for(res=res0; res; res=res->ai_next) { - if (res->ai_family != AF_INET + if (res->ai_family != AF_INET #ifdef HAVE_IPV6 && res->ai_family != AF_INET6 #endif /* HAVE_IPV6 */ @@ -215,8 +216,8 @@ smux_socket (void) sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) continue; - sockopt_reuseaddr (sock); - sockopt_reuseport (sock); + setsockopt_reuseaddr (sock); + setsockopt_reuseport (sock); ret = connect (sock, res->ai_addr, res->ai_addrlen); if (ret < 0) { @@ -244,15 +245,15 @@ smux_socket (void) #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ sp = getservbyname ("smux", "tcp"); - if (sp != NULL) + if (sp != NULL) serv.sin_port = sp->s_port; else serv.sin_port = htons (SMUX_PORT_DEFAULT); serv.sin_addr.s_addr = htonl (INADDR_LOOPBACK); - sockopt_reuseaddr (sock); - sockopt_reuseport (sock); + setsockopt_reuseaddr (sock); + setsockopt_reuseport (sock); ret = connect (sock, (struct sockaddr *) &serv, sizeof (struct sockaddr_in)); if (ret < 0) @@ -289,7 +290,7 @@ smux_getresp_send (oid objid[], size_t objid_len, long reqid, long errstat, /* Place holder h1 for complete sequence */ ptr = asn_build_sequence (ptr, &len, (u_char) SMUX_GETRSP, 0); h1e = ptr; - + ptr = asn_build_int (ptr, &len, (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), &reqid, sizeof (reqid)); @@ -309,12 +310,12 @@ smux_getresp_send (oid objid[], size_t objid_len, long reqid, long errstat, h2 = ptr; /* Place holder h2 for one variable */ - ptr = asn_build_sequence (ptr, &len, + ptr = asn_build_sequence (ptr, &len, (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0); h2e = ptr; - ptr = snmp_build_var_op (ptr, objid, &objid_len, + ptr = snmp_build_var_op (ptr, objid, &objid_len, val_type, arg_len, arg, &len); /* Now variable size is known, fill in size */ @@ -325,7 +326,7 @@ smux_getresp_send (oid objid[], size_t objid_len, long reqid, long errstat, if (debug_smux) zlog_debug ("SMUX getresp send: %td", (ptr - buf)); - + ret = send (smux_sock, buf, (ptr - buf), 0); } @@ -345,17 +346,17 @@ smux_var (u_char *ptr, size_t len, oid objid[], size_t *objid_len, /* Parse header. */ ptr = asn_parse_header (ptr, &len, &type); - + if (debug_smux) { zlog_debug ("SMUX var parse: type %d len %zd", type, len); - zlog_debug ("SMUX var parse: type must be %d", + zlog_debug ("SMUX var parse: type must be %d", (ASN_SEQUENCE | ASN_CONSTRUCTOR)); } /* Parse var option. */ *objid_len = MAX_OID_LEN; - ptr = snmp_parse_var_op(ptr, objid, objid_len, &val_type, + ptr = snmp_parse_var_op(ptr, objid, objid_len, &val_type, &val_len, &val, &len); if (var_val_len) @@ -473,7 +474,7 @@ smux_set (oid *reqid, size_t *reqid_len, { if (debug_smux) zlog_debug ("SMUX function call index is %d", v->magic); - + statP = (*v->findVar) (v, suffix, &suffix_len, 1, &val_len, &write_method); @@ -499,7 +500,7 @@ smux_set (oid *reqid, size_t *reqid_len, } static int -smux_get (oid *reqid, size_t *reqid_len, int exact, +smux_get (oid *reqid, size_t *reqid_len, int exact, u_char *val_type,void **val, size_t *val_len) { int j; @@ -515,7 +516,7 @@ smux_get (oid *reqid, size_t *reqid_len, int exact, /* Check */ for (ALL_LIST_ELEMENTS (treelist, node, nnode,subtree)) { - subresult = oid_compare_part (reqid, *reqid_len, + subresult = oid_compare_part (reqid, *reqid_len, subtree->name, subtree->name_len); /* Subtree matched. */ @@ -565,7 +566,7 @@ smux_get (oid *reqid, size_t *reqid_len, int exact, } static int -smux_getnext (oid *reqid, size_t *reqid_len, int exact, +smux_getnext (oid *reqid, size_t *reqid_len, int exact, u_char *val_type,void **val, size_t *val_len) { int j; @@ -588,7 +589,7 @@ smux_getnext (oid *reqid, size_t *reqid_len, int exact, /* Check */ for (ALL_LIST_ELEMENTS (treelist, node, nnode, subtree)) { - subresult = oid_compare_part (reqid, *reqid_len, + subresult = oid_compare_part (reqid, *reqid_len, subtree->name, subtree->name_len); /* If request is in the tree. The agent has to make sure we @@ -722,10 +723,10 @@ smux_parse_get (u_char *ptr, size_t len, int exact) if (debug_smux) zlog_debug ("SMUX GET message parse: len %zd", len); - + /* Parse GET message header. */ ptr = smux_parse_get_header (ptr, &len, &reqid); - + /* Parse GET message object ID. We needn't the value come */ ptr = smux_var (ptr, len, oid, &oid_len, NULL, NULL, NULL); @@ -762,7 +763,7 @@ smux_parse_rrsp (u_char *ptr, size_t len) { u_char val; long errstat; - + ptr = asn_parse_int (ptr, &len, &val, &errstat, sizeof (errstat)); if (debug_smux) @@ -818,7 +819,7 @@ process_rest: /* see note below: YYY */ else zlog_warn ("SMUX_SOUT sout_save_len=%d - invalid", (int) sout_save_len); - if (len_income > 3) + if (len_income > 3) { /* YYY: this strange code has to solve the "slow peer" problem: When agent sends SMUX_SOUT message it doesn't @@ -903,7 +904,7 @@ smux_read (struct thread *t) if (len < 0) { - zlog_warn ("Can't read all SMUX packet: %s", safe_strerror (errno)); + zlog_warn ("Can't read all SMUX packet: %s", errtoa(errno, 0).str); close (sock); smux_sock = -1; smux_event (SMUX_CONNECT, 0); @@ -963,24 +964,24 @@ smux_open (int sock) /* SMUX Open. */ version = 0; - ptr = asn_build_int (ptr, &len, + ptr = asn_build_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), &version, sizeof (version)); /* SMUX connection oid. */ ptr = asn_build_objid (ptr, &len, - (u_char) + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID), smux_oid, smux_oid_len); /* SMUX connection description. */ - ptr = asn_build_string (ptr, &len, + ptr = asn_build_string (ptr, &len, (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), - progname, strlen (progname)); + progname, strlen ((void*)progname)); /* SMUX connection password. */ - ptr = asn_build_string (ptr, &len, + ptr = asn_build_string (ptr, &len, (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), (u_char *)smux_passwd, strlen (smux_passwd)); @@ -1019,38 +1020,38 @@ smux_trap (const oid *name, size_t namelen, /* Sub agent enterprise oid. */ ptr = asn_build_objid (ptr, &len, - (u_char) + (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID), smux_oid, smux_oid_len); /* IP address. */ addr.s_addr = 0; - ptr = asn_build_string (ptr, &len, + ptr = asn_build_string (ptr, &len, (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_IPADDRESS), (u_char *)&addr, sizeof (addr)); /* Generic trap integer. */ val = SNMP_TRAP_ENTERPRISESPECIFIC; - ptr = asn_build_int (ptr, &len, + ptr = asn_build_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), (long *)&val, sizeof (val)); /* Specific trap integer. */ val = sptrap; - ptr = asn_build_int (ptr, &len, + ptr = asn_build_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), (long *)&val, sizeof (val)); /* Timeticks timestamp. */ val = 0; - ptr = asn_build_unsigned_int (ptr, &len, + ptr = asn_build_unsigned_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_TIMETICKS), &val, sizeof (val)); - + /* Variables. */ h1 = ptr; - ptr = asn_build_sequence (ptr, &len, + ptr = asn_build_sequence (ptr, &len, (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), 0); @@ -1067,27 +1068,27 @@ smux_trap (const oid *name, size_t namelen, u_char val_type; /* Make OID. */ - if (trapobj[i].namelen > 0) + if (trapobj[i].namelen > 0) { oid_copy (oid, name, namelen); oid_copy (oid + namelen, trapobj[i].name, trapobj[i].namelen); oid_copy (oid + namelen + trapobj[i].namelen, iname, inamelen); oid_len = namelen + trapobj[i].namelen + inamelen; } - else + else { oid_copy (oid, name, namelen); oid_copy (oid + namelen, trapobj[i].name, trapobj[i].namelen * (-1)); oid_len = namelen + trapobj[i].namelen * (-1) ; } - if (debug_smux) + if (debug_smux) { smux_oid_dump ("Trap", name, namelen); if (trapobj[i].namelen < 0) - smux_oid_dump ("Trap", + smux_oid_dump ("Trap", trapobj[i].name, (- 1) * (trapobj[i].namelen)); - else + else { smux_oid_dump ("Trap", trapobj[i].name, (trapobj[i].namelen)); smux_oid_dump ("Trap", iname, inamelen); @@ -1148,13 +1149,13 @@ smux_register (int sock) /* Priority. */ priority = -1; - ptr = asn_build_int (ptr, &len, + ptr = asn_build_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), &priority, sizeof (priority)); /* Operation. */ operation = 2; /* Register R/W */ - ptr = asn_build_int (ptr, &len, + ptr = asn_build_int (ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER), &operation, sizeof (operation)); @@ -1199,7 +1200,7 @@ smux_connect (struct thread *t) ret = smux_open (smux_sock); if (ret < 0) { - zlog_warn ("SMUX open message send failed: %s", safe_strerror (errno)); + zlog_warn ("SMUX open message send failed: %s", errtoa(errno, 0).str); close (smux_sock); smux_sock = -1; if (++fail < SMUX_MAX_FAILURE) @@ -1211,7 +1212,7 @@ smux_connect (struct thread *t) ret = smux_register (smux_sock); if (ret < 0) { - zlog_warn ("SMUX register message send failed: %s", safe_strerror (errno)); + zlog_warn ("SMUX register message send failed: %s", errtoa(errno, 0).str); close (smux_sock); smux_sock = -1; if (++fail < SMUX_MAX_FAILURE) @@ -1247,7 +1248,7 @@ smux_stop (void) smux_sock = -1; } } - + void @@ -1268,7 +1269,7 @@ smux_event (enum smux_event event, int sock) break; } } - + static int smux_str2oid (const char *str, oid *oid, size_t *oid_len) { @@ -1395,7 +1396,7 @@ smux_peer_default (void) free (smux_oid); smux_oid = NULL; } - + /* careful, smux_passwd might be pointing at string constant */ if (smux_passwd) { @@ -1488,8 +1489,8 @@ config_write_smux (struct vty *vty) /* Register subtree to smux master tree. */ void -smux_register_mib (const char *descr, struct variable *var, - size_t width, int num, +smux_register_mib (const char *descr, struct variable *var, + size_t width, int num, oid name[], size_t namelen) { struct subtree *tree; @@ -1508,7 +1509,7 @@ smux_register_mib (const char *descr, struct variable *var, static int smux_tree_cmp(struct subtree *tree1, struct subtree *tree2) { - return oid_compare(tree1->name, tree1->name_len, + return oid_compare(tree1->name, tree1->name_len, tree2->name, tree2->name_len); } @@ -1518,7 +1519,7 @@ smux_init (struct thread_master *tm) { /* copy callers thread master */ master = tm; - + /* Make MIB tree. */ treelist = list_new(); treelist->cmp = (int (*)(void *, void *))smux_tree_cmp; diff --git a/lib/sockopt.c b/lib/sockopt.c index 55c6226b..083cafc2 100644 --- a/lib/sockopt.c +++ b/lib/sockopt.c @@ -1,4 +1,4 @@ -/* setsockopt functions +/* Setting and getting socket options -- utility functions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -16,173 +16,589 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> + #include "log.h" #include "sockopt.h" #include "sockunion.h" +#include "pthread_safe.h" -int -setsockopt_so_recvbuf (int sock, int size) +/*------------------------------------------------------------------------------ + * Set socket SO_REUSEADDR option + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ +extern int +setsockopt_reuseaddr (int sock_fd) { int ret; - - if ( (ret = setsockopt (sock, SOL_SOCKET, SO_RCVBUF, (char *) - &size, sizeof (int))) < 0) - zlog_err ("fd %d: can't setsockopt SO_RCVBUF to %d: %s", - sock,size,safe_strerror(errno)); + int on = 1; - return ret; + ret = setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (ret < 0) + { + int err = errno ; + zlog_warn ("cannot set sockopt SO_REUSEADDR on socket %d: %s", sock_fd, + errtoa(err, 0).str) ; + errno = err ; + } ; + + return ret ; } -int -setsockopt_so_sendbuf (const int sock, int size) +/*------------------------------------------------------------------------------ + * Set socket SO_REUSEPORT option -- if it is locally supported. + * + * Returns: >= 0 => OK -- or not supported + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ +extern int +setsockopt_reuseport (int sock_fd) { - int ret = setsockopt (sock, SOL_SOCKET, SO_SNDBUF, - (char *)&size, sizeof (int)); - + int ret; + +#ifdef SO_REUSEPORT + int on = 1; + ret = setsockopt (sock_fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); +#else + ret = 0 ; +#endif + if (ret < 0) - zlog_err ("fd %d: can't setsockopt SO_SNDBUF to %d: %s", - sock, size, safe_strerror (errno)); + { + int err = errno ; + zlog_warn ("cannot set sockopt SO_REUSEPORT on socket %d: %s", sock_fd, + errtoa(err, 0).str) ; + errno = err ; + } ; - return ret; -} + return ret ; +} ; -int -getsockopt_so_sendbuf (const int sock) +/*------------------------------------------------------------------------------ + * Set socket SO_BROADCAST option + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ +extern int +setsockopt_broadcast (int sock_fd) { - u_int32_t optval; - socklen_t optlen = sizeof (optval); - int ret = getsockopt (sock, SOL_SOCKET, SO_SNDBUF, - (char *)&optval, &optlen); + int ret; + int on = 1; + + ret = setsockopt (sock_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); if (ret < 0) - { - zlog_err ("fd %d: can't getsockopt SO_SNDBUF: %d (%s)", - sock, errno, safe_strerror (errno)); - return ret; - } - return optval; + { + int err = errno ; + zlog_warn ("cannot set sockopt SO_BROADCAST on socket %d: %s", sock_fd, + errtoa(err, 0).str) ; + errno = err ; + } + return ret ; } -static void * -getsockopt_cmsg_data (struct msghdr *msgh, int level, int type) +/*------------------------------------------------------------------------------ + * Set TCP_CORK, if available. + * + * Returns: >= 0 => OK -- or not supported + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ +extern int +setsockopt_cork (int sock_fd, int onoff) { - struct cmsghdr *cmsg; - void *ptr = NULL; - - for (cmsg = ZCMSG_FIRSTHDR(msgh); - cmsg != NULL; - cmsg = CMSG_NXTHDR(msgh, cmsg)) - if (cmsg->cmsg_level == level && cmsg->cmsg_type) - return (ptr = CMSG_DATA(cmsg)); +#ifdef TCP_CORK + int ret; - return NULL; + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_CORK, &onoff, sizeof(onoff)); + if (ret < 0) + { + int err = errno ; + zlog_warn ("cannot set sockopt TCP_CORK to %d on socket %d: %s", onoff, + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } + return ret ; +#else + return 0; +#endif } -#ifdef HAVE_IPV6 -/* Set IPv6 packet info to the socket. */ -int -setsockopt_ipv6_pktinfo (int sock, int val) +/*------------------------------------------------------------------------------ + * Set IP_TTL/IPV6_UNICAST_HOPS for socket, if available + * + * The ttl given is the maximum number of hops to allow -- so will generally + * be 1 or 255 or some small number. + * + * NB: This code treats any ttl outside the range 1..MAXTTL as MAXTTL. + * + * Returns: >= 0 => OK -- or not supported + * < 0 => failed, see errno. + * + * Logs a LOG_WARNING message if fails. + * + * NB: for AF_INET6 where have: IN6_IS_ADDR_V4MAPPED, there is the question + * of whether to use IPPROTO_IP/IP_TTL or IPPROTO_IPV6/IPV6_UNICAST_HOPS. + * + * Here we try first to use the socket family, and if that fails on + * AF_INET6, then if the protocol family is AF_INET, then tries that. + */ +extern int +setsockopt_ttl (int sock_fd, int ttl) { - int ret; - -#ifdef IPV6_RECVPKTINFO /*2292bis-01*/ - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)); - if (ret < 0) - zlog_warn ("can't setsockopt IPV6_RECVPKTINFO : %s", safe_strerror (errno)); -#else /*RFC2292*/ - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val)); + const char* name ; + int af ; + int ret ; + + af = sockunion_getsockfamily(sock_fd) ; + if (af < 0) + return af ; + + if ((ttl < 1) || (ttl > MAXTTL)) + ttl = MAXTTL ; + + ret = 0 ; + name = NULL ; + + while (1) + { + switch (af) + { + case AF_INET: +#ifdef IP_TTL + ret = setsockopt (sock_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + name = "IP_TTL" ; +#endif /* IP_TTL */ + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &ttl, sizeof(ttl)); + name = "IPV6_UNICAST_HOPS" ; + + if (ret < 0) + { + af = sockunion_getprotofamily(sock_fd) ; + if (af == AF_INET) + continue ; + } ; + break ; +#endif + + default: /* ignore unknown family */ + break ; + } ; + + break ; + } ; + if (ret < 0) - zlog_warn ("can't setsockopt IPV6_PKTINFO : %s", safe_strerror (errno)); -#endif /* INIA_IPV6 */ - return ret; -} + { + int err = errno ; + zlog_warn("cannot set sockopt %s to %d on socket %d: %s", name, ttl, + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + + return ret ; +} ; -/* Set multicast hops val to the socket. */ -int -setsockopt_ipv6_checksum (int sock, int val) +/*------------------------------------------------------------------------------ + * Set IP_MINTTL/IPV6_MINHOPCOUNT (GTSM), if available. + * + * The ttl given is the maximum number of hops to allow -- so will generally + * be 1 -- which is the same as IP_TTL/IPV6_UNICAST_HOPS. + * + * NB: to turn off GTSM, need to set ttl = MAXTTL. This code treats any ttl + * outside the range 1..MAXTTL as MAXTTL. + * + * The underlying mechanics want MAX_TTL - (ttl - 1) -- and may not + * accept a value of zero. + * + * Returns: >= 0 => OK + * < 0 => failed or not supported -- see errno + * EOPNOTSUPP if not supported + * + * Logs a LOG_WARNING message if fails (and is supported). + * + * NB: for AF_INET6 where have: IN6_IS_ADDR_V4MAPPED, there is the question + * of whether to use IPPROTO_IP/IP_TTL or IPPROTO_IPV6/IPV6_UNICAST_HOPS. + * + * Here we try first to use the socket family, and if that fails on + * AF_INET6, then if the protocol family is AF_INET, then tries that. + */ +extern int +setsockopt_minttl (int sock_fd, int ttl) { - int ret; + const char* name ; + int af ; + int minttl ; + int ret ; -#ifdef GNU_LINUX - ret = setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); + af = sockunion_getsockfamily(sock_fd) ; + if (af < 0) + return af ; + + ret = 0 ; + name = NULL ; + + if ((ttl < 1) || (ttl > MAXTTL)) + ttl = MAXTTL ; + + minttl = MAXTTL - (ttl - 1) ; + + while (1) + { + enum + { +#ifdef IP_MINTTL + have_ip_minttl = true, #else - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &val, sizeof(val)); -#endif /* GNU_LINUX */ + have_ip_minttl = false, +#endif + ip_minttl = IP_MINTTL + 0 + } ; + +#ifdef HAVE_IPV6 + +# ifdef GNU_LINUX + /* The #include to bring in IPV6_MINHOPCOUNT is buried more or less as + * deep as we can get it, because it also redefines a number of things + * that we do not want redefined. + */ + #include <linux/in6.h> +# endif + + enum + { +# ifdef IPV6_MINHOPCOUNT + have_ipv6_minhopcount = true, +# else + have_ipv6_minhopcount = false, +# endif + ipv6_minhopcount = IPV6_MINHOPCOUNT + 0 + } ; +#endif /* HAVE_IPV6 */ + + switch (af) + { + case AF_INET: + name = "IP_MINTTL" ; + if (have_ip_minttl) + ret = setsockopt (sock_fd, IPPROTO_IP, ip_minttl, + &minttl, sizeof(minttl)); + else + { + ret = -1 ; + errno = EOPNOTSUPP ; + } ; + + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + name = "IPV6_MINHOPCOUNT" ; + if (have_ipv6_minhopcount) + ret = setsockopt (sock_fd, IPPROTO_IPV6, ipv6_minhopcount, + &minttl, sizeof(minttl)); + else + { + ret = -1 ; + errno = EOPNOTSUPP ; + } ; + + if (ret < 0) + { + af = sockunion_getprotofamily(sock_fd) ; + if (af == AF_INET) + continue ; + } ; + + break ; +#endif /* HAVE_IPV6 */ + + default: /* ignore unknown family */ + break ; + } ; + + break ; + } ; + if (ret < 0) - zlog_warn ("can't setsockopt IPV6_CHECKSUM"); - return ret; -} + { + int err = errno ; + zlog_warn("cannot set sockopt %s to %d on socket %d: %s", name, minttl, + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; -/* Set multicast hops val to the socket. */ -int -setsockopt_ipv6_multicast_hops (int sock, int val) + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Set TCP MD5 signature socket option, if available. + * + * A NULL password or an empty password both signify unsetting the MD5 + * signature. + * + * Returns: >= 0 => OK (or not supported, but password NULL or empty) + * < 0 => failed or not supported, see errno. + * + * NB: returns EOPNOTSUPP if TCP MD5 is not supported and password is not NULL + * and is not empty. + * + * Logs a LOG_ERR message if fails (and is supported). + */ +extern int +setsockopt_tcp_signature (int sock_fd, sockunion su, const char *password) { - int ret; + int ret ; + + if ((password != NULL) && (*password == '\0')) + password = NULL ; + + ret = 0 ; /* so far, so good */ + +#if defined(HAVE_TCP_MD5_LINUX24) && defined(GNU_LINUX) + /* Support for the old Linux 2.4 TCP-MD5 patch, taken from Hasso Tepper's + * version of the Quagga patch (based on work by Rick Payne, and Bruce + * Simpson) + */ +#define TCP_MD5_AUTH 13 +#define TCP_MD5_AUTH_ADD 1 +#define TCP_MD5_AUTH_DEL 2 + struct tcp_rfc2385_cmd { + u_int8_t command; /* Command - Add/Delete */ + u_int32_t address; /* IPV4 address associated */ + u_int8_t keylen; /* MD5 Key len (do NOT assume 0 terminated ascii) */ + void *key; /* MD5 Key */ + } cmd; + struct in_addr *addr = &su->sin.sin_addr; + + cmd.command = (password != NULL ? TCP_MD5_AUTH_ADD : TCP_MD5_AUTH_DEL); + cmd.address = addr->s_addr; + cmd.keylen = (password != NULL ? strlen (password) : 0); + cmd.key = password; - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)); + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_MD5_AUTH, &cmd, sizeof cmd) ; if (ret < 0) - zlog_warn ("can't setsockopt IPV6_MULTICAST_HOPS"); - return ret; -} + { + int err = errno ; + zlog_err("sockopt_tcp_signature: setsockopt(%d): %s", + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } + +#elif HAVE_DECL_TCP_MD5SIG + +# ifdef GNU_LINUX + struct tcp_md5sig md5sig ; +# ifdef HAVE_IPV6 + int saf ; + union sockunion sux[1] ; +# endif + + /* Testing reveals that in order to set an MD5 password for an AF_INET6 + * socket, the address passed in must be AF_INET6, even if what we are + * dealing with here is an IN6_IS_ADDR_V4MAPPED socket. + */ +# ifdef HAVE_IPV6 + saf = sockunion_getsockfamily(sock_fd) ; + if (saf < 0) + return saf ; + + if ((saf == AF_INET6) && (sockunion_family(su) == AF_INET)) + { + sockunion_copy (sux, su) ; + sockunion_map_ipv4 (sux) ; + su = sux ; /* substitute v4 mapped address */ + } ; +# endif + + /* Set address to AF_UNSPEC and key length and everything else to zero, + * then copy in the address and the key. + */ + memset (&md5sig, 0, sizeof (md5sig)) ; + confirm(AF_UNSPEC == 0) ; + + memcpy (&md5sig.tcpm_addr, &su->sa, sockunion_get_len(su)) ; + + if (password != NULL) + { + size_t keylen = strlen(password) ; + + if (md5sig.tcpm_keylen <= TCP_MD5SIG_MAXKEYLEN) + { + md5sig.tcpm_keylen = keylen ; + memcpy (md5sig.tcpm_key, password, keylen); + } + else + { + errno = EINVAL ; /* manufactured error */ + ret = -1 ; + } ; + } ; + +# else + /* + * XXX Need to do PF_KEY operation here to add/remove an SA entry, + * and add/remove an SP entry for this peer's packet flows also. + */ + int md5sig = (password != NULL) ? 1 : 0; -/* Set multicast hops val to the socket. */ -int -setsockopt_ipv6_unicast_hops (int sock, int val) +# endif /* GNU_LINUX */ + + if (ret >= 0) + { + ret = setsockopt(sock_fd, IPPROTO_TCP, TCP_MD5SIG, + &md5sig, sizeof(md5sig)) ; + if (ret < 0) + /* ENOENT is harmless. It is returned when we clear a password where + * one was not previously set. + */ + if ((errno == ENOENT) && (password == NULL)) + ret = 0 ; + } ; + +#else + + /* TCP MD5 is not supported */ + + if (password != NULL) + { + errno = EOPNOTSUPP ; /* manufactured error */ + ret = -1 ; + } ; + +#endif /* !HAVE_TCP_MD5SIG */ + + if (ret < 0) + { + int err = errno ; + zlog_err("sockopt_tcp_signature: setsockopt(%d): %s", + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Set SO_RCVBUF option on socket. + * + * Returns: >= 0 => OK + * < 0 => failed, see errno. + * + * Logs a LOG_ERR message if fails + */ +extern int +setsockopt_so_recvbuf (int sock_fd, int size) { int ret; - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)); + ret = setsockopt (sock_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) ; if (ret < 0) - zlog_warn ("can't setsockopt IPV6_UNICAST_HOPS"); + { + int err = errno ; + zlog_err ("cannot set sockopt SO_RCVBUF to %d on socket %d: %s", + size, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + return ret; } -int -setsockopt_ipv6_hoplimit (int sock, int val) +/*------------------------------------------------------------------------------ + * Set SO_SNDBUF option on socket. + * + * Returns: >= 0 => OK + * < 0 => failed, see errno. + * + * Logs a LOG_ERR message if fails + */ +extern int +setsockopt_so_sendbuf (int sock_fd, int size) { - int ret; + int ret ; + + ret = setsockopt (sock_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); -#ifdef IPV6_RECVHOPLIMIT /*2292bis-01*/ - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val)); - if (ret < 0) - zlog_warn ("can't setsockopt IPV6_RECVHOPLIMIT"); -#else /*RFC2292*/ - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_HOPLIMIT"); -#endif + { + int err = errno ; + zlog_err("cannot set sockopt SO_SNDBUF to %d on socket %d: %s", + size, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + return ret; } -/* Set multicast loop zero to the socket. */ -int -setsockopt_ipv6_multicast_loop (int sock, int val) +/*------------------------------------------------------------------------------ + * Get SO_SNDBUF option value from socket. + * + * Returns: >= 0 => OK == value of option + * < 0 => failed, see errno. + * + * Logs a LOG_ERR message if fails + */ +extern int +getsockopt_so_sendbuf (int sock_fd) { - int ret; - - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, - sizeof (val)); + u_int32_t optval; + socklen_t optlen = sizeof (optval); + + int ret = getsockopt (sock_fd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_MULTICAST_LOOP"); - return ret; + { + int err = errno ; + zlog_err ("cannot get sockopt SO_SNDBUF on socket %d: %s", + sock_fd, errtoa(err, 0).str) ; + errno = err ; + return ret; + } + + return optval; } -static int -getsockopt_ipv6_ifindex (struct msghdr *msgh) +/*------------------------------------------------------------------------------ + * Set IP_TOS option for AF_INET socket. + * + * Returns: >= 0 => OK + * < 0 => failed, see errno. + * + * Logs a LOG_ERR message if fails + */ +extern int +setsockopt_ipv4_tos(int sock_fd, int tos) { - struct in6_pktinfo *pktinfo; - - pktinfo = getsockopt_cmsg_data (msgh, IPPROTO_IPV6, IPV6_PKTINFO); - - return pktinfo->ipi6_ifindex; -} -#endif /* HAVE_IPV6 */ + int ret; + ret = setsockopt (sock_fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt IP_TOS option %#x on socket %d: %s", + tos, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + return ret; +} ; -/* +/*------------------------------------------------------------------------------ * Process multicast socket options for IPv4 in an OS-dependent manner. * Supported options are IP_MULTICAST_IF and IP_{ADD,DROP}_MEMBERSHIP. * @@ -203,20 +619,22 @@ getsockopt_ipv6_ifindex (struct msghdr *msgh) * but this behavior should not be harmful if they behave the same way, * allow leaves, or implicitly leave all groups joined to down interfaces. */ -int -setsockopt_multicast_ipv4(int sock, - int optname, - struct in_addr if_addr /* required */, - unsigned int mcast_addr, - unsigned int ifindex /* optional: if non-zero, may be - used instead of if_addr */) +extern int +setsockopt_multicast_ipv4(int sock_fd, + int optname, + struct in_addr if_addr /* required */, + unsigned int mcast_addr, + unsigned int ifindex /* optional: if non-zero, + may be used instead of + if_addr */ + ) { #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX /* This is better because it uses ifindex directly */ struct ip_mreqn mreqn; int ret; - + switch (optname) { case IP_MULTICAST_IF: @@ -226,37 +644,34 @@ setsockopt_multicast_ipv4(int sock, if (mcast_addr) mreqn.imr_multiaddr.s_addr = mcast_addr; - + if (ifindex) mreqn.imr_ifindex = ifindex; else mreqn.imr_address = if_addr; - - ret = setsockopt(sock, IPPROTO_IP, optname, - (void *)&mreqn, sizeof(mreqn)); + + ret = setsockopt(sock_fd, IPPROTO_IP, optname, &mreqn, sizeof(mreqn)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back up */ char buf[2][INET_ADDRSTRLEN]; zlog_info("setsockopt_multicast_ipv4 attempting to drop and " "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)", - sock, + sock_fd, inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &mreqn.imr_multiaddr, buf[1], sizeof(buf[1])), ifindex); - setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, - (void *)&mreqn, sizeof(mreqn)); - ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, - (void *)&mreqn, sizeof(mreqn)); + setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mreqn, sizeof(mreqn)); + ret = setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreqn, sizeof(mreqn)); } return ret; - break; default: /* Can out and give an understandable error */ errno = EINVAL; return -1; - break; } /* Example defines for another OS, boilerplate off other code in this @@ -264,7 +679,7 @@ setsockopt_multicast_ipv4(int sock, /* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */ /* Add your favourite OS here! */ -#else /* #if OS_TYPE */ +#else /* #if OS_TYPE */ /* standard BSD API */ struct in_addr m; @@ -281,7 +696,7 @@ setsockopt_multicast_ipv4(int sock, switch (optname) { case IP_MULTICAST_IF: - return setsockopt (sock, IPPROTO_IP, optname, (void *)&m, sizeof(m)); + return setsockopt (sock_fd, IPPROTO_IP, optname, (void *)&m, sizeof(m)); break; case IP_ADD_MEMBERSHIP: @@ -289,94 +704,194 @@ setsockopt_multicast_ipv4(int sock, memset (&mreq, 0, sizeof(mreq)); mreq.imr_multiaddr.s_addr = mcast_addr; mreq.imr_interface = m; - - ret = setsockopt (sock, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq)); + + ret = setsockopt (sock_fd, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back up */ char buf[2][INET_ADDRSTRLEN]; zlog_info("setsockopt_multicast_ipv4 attempting to drop and " "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)", - sock, + sock_fd, inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &mreq.imr_multiaddr, buf[1], sizeof(buf[1])), ifindex); - setsockopt (sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, - (void *)&mreq, sizeof(mreq)); - ret = setsockopt (sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, - (void *)&mreq, sizeof(mreq)); + setsockopt (sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)); + ret = setsockopt (sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); } return ret; - break; - + default: /* Can out and give an understandable error */ errno = EINVAL; return -1; - break; } #endif /* #if OS_TYPE */ } +/*============================================================================== + * Set pktinfo and get ifindex etc + */ + +static int setsockopt_ipv4_pktinfo (int sock_fd, int val) ; +static int getsockopt_ipv4_ifindex (struct msghdr *msgh) ; + +#ifdef HAVE_IPV6 +static int getsockopt_ipv6_ifindex (struct msghdr *msgh) ; +#endif + +static void * getsockopt_cmsg_data (struct msghdr *msgh, int level, int type) ; + +/*------------------------------------------------------------------------------ + * Set IP_PKTINFO/IP_RECVIF or IPV6_RECVPKTINFO/IPV6_PKTINFO -- if available. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_pktinfo (int af, int sock_fd, int val) +{ + int ret = -1; + + switch (af) + { + case AF_INET: + ret = setsockopt_ipv4_pktinfo (sock_fd, val); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + ret = setsockopt_ipv6_pktinfo (sock_fd, val); + break; +#endif + default: + zlog_warn("setsockopt_ifindex: unknown address family %d", af) ; + ret = -1 ; + errno = EINVAL; + break ; + } + return ret; +} + +/*------------------------------------------------------------------------------ + * Set IP_PKTINFO or IP_RECVIF -- if available. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ static int -setsockopt_ipv4_ifindex (int sock, int val) +setsockopt_ipv4_pktinfo (int sock_fd, int val) { int ret; -#if defined (IP_PKTINFO) - if ((ret = setsockopt (sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof (val))) < 0) - zlog_warn ("Can't set IP_PKTINFO option for fd %d to %d: %s", - sock,val,safe_strerror(errno)); -#elif defined (IP_RECVIF) - if ((ret = setsockopt (sock, IPPROTO_IP, IP_RECVIF, &val, sizeof (val))) < 0) - zlog_warn ("Can't set IP_RECVIF option for fd %d to %d: %s", - sock,val,safe_strerror(errno)); +#if defined(IP_PKTINFO) || defined(IP_RECVIF) + + int opt ; + const char* name ; + +# if defined(IP_PKTINFO) + opt = IP_PKTINFO ; + name = "IP_PKTINFO" ; +# else + opt = IP_RECVIF ; + name = "IP_RECVIF" ; +# endif + + ret = setsockopt (sock_fd, IPPROTO_IP, opt, &val, sizeof (val)) ; + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt %s to %d on socket %d: %s", name, + val, sock_fd, errtoa(err, 0).str); + errno = err ; + } ; #else #warning "Neither IP_PKTINFO nor IP_RECVIF is available." #warning "Will not be able to receive link info." #warning "Things might be seriously broken.." /* XXX Does this ever happen? Should there be a zlog_warn message here? */ - ret = -1; + ret = -1; + errno = EOPNOTSUPP ; /* manufactured error */ #endif return ret; } -int -setsockopt_ipv4_tos(int sock, int tos) +/*------------------------------------------------------------------------------ + * Set IPV6_RECVPKTINFO (RFC3542) or IPV6_RECVIF (RFC2292) -- if available. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +#ifdef HAVE_IPV6 +extern int +setsockopt_ipv6_pktinfo (int sock_fd, int val) { int ret; + int opt ; + const char* name ; - ret = setsockopt (sock, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); +# ifdef IPV6_RECVPKTINFO + opt = IPV6_RECVPKTINFO ; /* RFC3542 == RFC2292-bis */ + name = "IPV6_RECVPKTINFO" ; +# else + opt = IPV6_PKTINFO ; /* RFC2292 */ + name = "IPV6_PKTINFO" ; +# endif + + ret = setsockopt(sock_fd, IPPROTO_IPV6, opt, &val, sizeof(val)); if (ret < 0) - zlog_warn ("Can't set IP_TOS option for fd %d to %#x: %s", - sock, tos, safe_strerror(errno)); + { + int err = errno ; + zlog_err("cannot set sockopt %s to %d on socket %d: %s", name, + val, sock_fd, errtoa(err, 0).str); + errno = err ; + } ; + return ret; } +#endif - -int -setsockopt_ifindex (int af, int sock, int val) +/*------------------------------------------------------------------------------ + * Given a struct msghdr*, extract and return ifindex. + * + * Returns: > 0 => OK == ifindex + * 0 => none found or error + * + * Note: this is badly named, since it is not really a getsockopt() operation, + * but extracting data from a sendmsg/recvmsg struct msghdr. + * + * To have the ifindex returned, need to have setsockopt_pktinfo(). + */ +extern int +getsockopt_ifindex (int af, struct msghdr *msgh) { - int ret = -1; - switch (af) { case AF_INET: - ret = setsockopt_ipv4_ifindex (sock, val); - break; + return (getsockopt_ipv4_ifindex (msgh)); + #ifdef HAVE_IPV6 case AF_INET6: - ret = setsockopt_ipv6_pktinfo (sock, val); - break; + return (getsockopt_ipv6_ifindex (msgh)); #endif + default: - zlog_warn ("setsockopt_ifindex: unknown address family %d", af); + zlog_warn ("getsockopt_ifindex: unknown address family %d", af); + return 0 ; } - return ret; } - -/* + +/*------------------------------------------------------------------------------ + * AF_INET: extract ifindex from struct msghdr, if can + * * Requires: msgh is not NULL and points to a valid struct msghdr, which * may or may not have control data about the incoming interface. * @@ -386,18 +901,18 @@ setsockopt_ifindex (int af, int sock, int val) static int getsockopt_ipv4_ifindex (struct msghdr *msgh) { - /* XXX: initialize to zero? (Always overwritten, so just cosmetic.) */ - int ifindex = -1; + int ifindex ; #if defined(IP_PKTINFO) /* Linux pktinfo based ifindex retrieval */ struct in_pktinfo *pktinfo; - - pktinfo = - (struct in_pktinfo *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_PKTINFO); - /* XXX Can pktinfo be NULL? Clean up post 0.98. */ - ifindex = pktinfo->ipi_ifindex; - + + pktinfo = getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_PKTINFO); + if (pktinfo != NULL) + ifindex = pktinfo->ipi_ifindex ; + else + ifindex = 0 ; + #elif defined(IP_RECVIF) /* retrieval based on IP_RECVIF */ @@ -412,7 +927,7 @@ getsockopt_ipv4_ifindex (struct msghdr *msgh) #ifndef SUNOS_5 /* BSD */ - sdl = + sdl = (struct sockaddr_dl *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF); if (sdl != NULL) ifindex = sdl->sdl_index; @@ -424,7 +939,7 @@ getsockopt_ipv4_ifindex (struct msghdr *msgh) * enable it fails with errno=99, and the struct msghdr has * controllen 0. */ - ifindex_p = (uint_t *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF); + ifindex_p = (uint_t *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF); if (ifindex_p != NULL) ifindex = *ifindex_p; else @@ -442,39 +957,78 @@ getsockopt_ipv4_ifindex (struct msghdr *msgh) #warning "Some daemons may fail to operate correctly!" ifindex = 0; -#endif /* IP_PKTINFO */ +#endif /* IP_PKTINFO */ return ifindex; } -/* return ifindex, 0 if none found */ -int -getsockopt_ifindex (int af, struct msghdr *msgh) -{ - int ifindex = 0; - - switch (af) - { - case AF_INET: - return (getsockopt_ipv4_ifindex (msgh)); - break; +/*------------------------------------------------------------------------------ + * AF_INET6: extract ifindex from struct msghdr, if can + * + * Requires: msgh is not NULL and points to a valid struct msghdr, which + * may or may not have control data about the incoming interface. + * + * Returns the interface index (small integer >= 1) if it can be + * determined, or else 0. + */ #ifdef HAVE_IPV6 - case AF_INET6: - return (getsockopt_ipv6_ifindex (msgh)); - break; +static int +getsockopt_ipv6_ifindex (struct msghdr *msgh) +{ + struct in6_pktinfo *pktinfo; + + pktinfo = getsockopt_cmsg_data (msgh, IPPROTO_IPV6, IPV6_PKTINFO); + + if (pktinfo != NULL) + return pktinfo->ipi6_ifindex; + else + return 0 ; +} #endif - default: - zlog_warn ("getsockopt_ifindex: unknown address family %d", af); - return (ifindex = 0); - } + +/*------------------------------------------------------------------------------ + * Scan msg_control portion of struct msghdr, looking for a cmsg with the given + * level and type. + * + * Requires: msgh is not NULL and points to a valid struct msghdr, which + * may or may not have control data about the incoming interface. + * + * Returns: address of data part of cmsg + * or: NULL => not found + */ +static void * +getsockopt_cmsg_data (struct msghdr *msgh, int level, int type) +{ + struct cmsghdr *cmsg; + + for (cmsg = ZCMSG_FIRSTHDR(msgh); + cmsg != NULL; + cmsg = CMSG_NXTHDR(msgh, cmsg)) + if ((cmsg->cmsg_level == level) && (cmsg->cmsg_type == type)) + return (void*)CMSG_DATA(cmsg); + + return NULL; } -/* swab iph between order system uses for IP_HDRINCL and host order */ -void +/*------------------------------------------------------------------------------ + * swab iph between order system uses for IP_HDRINCL and host order + * + * This is done before handing struct ip to the system. + * + * There are four u_short fields in the IPv4 header: + * + * u_short ip_len -- convert to network order, except as noted below + * u_short ip_id -- convert to network order + * u_short ip_off -- convert to network order, except as noted below + * u_short ip_sum -- which we don't touch -- set by kernel + */ +extern void sockopt_iphdrincl_swab_htosys (struct ip *iph) { - /* BSD and derived take iph in network order, except for - * ip_len and ip_off + /* BSD and derived take iph in network order, except for ip_len and ip_off. + * + * So if *not* BSD-like, then need to convert ip_len and ip_off to network + * order. */ #ifndef HAVE_IP_HDRINCL_BSD_ORDER iph->ip_len = htons(iph->ip_len); @@ -484,7 +1038,12 @@ sockopt_iphdrincl_swab_htosys (struct ip *iph) iph->ip_id = htons(iph->ip_id); } -void +/*------------------------------------------------------------------------------ + * swab iph between order system uses for IP_HDRINCL and host order + * + * This is done after receiving struct ip from the system -- see notes above. + */ +extern void sockopt_iphdrincl_swab_systoh (struct ip *iph) { #ifndef HAVE_IP_HDRINCL_BSD_ORDER @@ -495,103 +1054,172 @@ sockopt_iphdrincl_swab_systoh (struct ip *iph) iph->ip_id = ntohs(iph->ip_id); } -int -sockopt_tcp_signature (int sock, union sockunion *su, const char *password) +/*============================================================================== + * IPv6 Stuff + */ +#ifdef HAVE_IPV6 + +/*------------------------------------------------------------------------------ + * Set IPV6_V6ONLY. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_v6only(int sock_fd) { -#if defined(HAVE_TCP_MD5_LINUX24) && defined(GNU_LINUX) - /* Support for the old Linux 2.4 TCP-MD5 patch, taken from Hasso Tepper's - * version of the Quagga patch (based on work by Rick Payne, and Bruce - * Simpson) - */ -#define TCP_MD5_AUTH 13 -#define TCP_MD5_AUTH_ADD 1 -#define TCP_MD5_AUTH_DEL 2 - struct tcp_rfc2385_cmd { - u_int8_t command; /* Command - Add/Delete */ - u_int32_t address; /* IPV4 address associated */ - u_int8_t keylen; /* MD5 Key len (do NOT assume 0 terminated ascii) */ - void *key; /* MD5 Key */ - } cmd; - struct in_addr *addr = &su->sin.sin_addr; - - cmd.command = (password != NULL ? TCP_MD5_AUTH_ADD : TCP_MD5_AUTH_DEL); - cmd.address = addr->s_addr; - cmd.keylen = (password != NULL ? strlen (password) : 0); - cmd.key = password; - - return setsockopt (sock, IPPROTO_TCP, TCP_MD5_AUTH, &cmd, sizeof cmd); - -#elif HAVE_DECL_TCP_MD5SIG int ret; -#ifndef GNU_LINUX - /* - * XXX Need to do PF_KEY operation here to add/remove an SA entry, - * and add/remove an SP entry for this peer's packet flows also. - */ - int md5sig = password && *password ? 1 : 0; -#else - int keylen = password ? strlen (password) : 0; - struct tcp_md5sig md5sig; - union sockunion *su2, *susock; - - /* Figure out whether the socket and the sockunion are the same family.. - * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think.. - */ - if (!(susock = sockunion_getsockname (sock))) - return -1; - - if (susock->sa.sa_family == su->sa.sa_family) - su2 = su; - else + int on = 1 ; + + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if (ret < 0) { - /* oops.. */ - su2 = susock; - - if (su2->sa.sa_family == AF_INET) - { - sockunion_free (susock); - return 0; - } - -#ifdef HAVE_IPV6 - /* If this does not work, then all users of this sockopt will need to - * differentiate between IPv4 and IPv6, and keep seperate sockets for - * each. - * - * Sadly, it doesn't seem to work at present. It's unknown whether - * this is a bug or not. - */ - if (su2->sa.sa_family == AF_INET6 - && su->sa.sa_family == AF_INET) - { - su2->sin6.sin6_family = AF_INET6; - /* V4Map the address */ - memset (&su2->sin6.sin6_addr, 0, sizeof (struct in6_addr)); - su2->sin6.sin6_addr.s6_addr32[2] = htonl(0xffff); - memcpy (&su2->sin6.sin6_addr.s6_addr32[3], &su->sin.sin_addr, 4); - } -#endif + int err = errno ; + zlog_err ("cannot set sockopt IPV6_V6ONLY on socket %d: %s", + sock_fd, errtoa(err, 0).str) ; + errno = err ; } - - memset (&md5sig, 0, sizeof (md5sig)); - memcpy (&md5sig.tcpm_addr, su2, sizeof (*su2)); - md5sig.tcpm_keylen = keylen; - if (keylen) - memcpy (md5sig.tcpm_key, password, keylen); - sockunion_free (susock); + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Set IPV6_CHECKSUM + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_checksum (int sock_fd, int val) +{ + int ret; + +#ifdef GNU_LINUX + ret = setsockopt(sock_fd, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); +#else + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, sizeof(val)); #endif /* GNU_LINUX */ - if ((ret = setsockopt (sock, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof md5sig)) < 0) + if (ret < 0) { - /* ENOENT is harmless. It is returned when we clear a password for which - one was not previously set. */ - if (ENOENT == errno) - ret = 0; - else - zlog_err ("sockopt_tcp_signature: setsockopt(%d): %s", - sock, safe_strerror(errno)); - } + int err = errno ; + zlog_err("cannot set sockopt IPV6_CHECKSUM to %d on socket %d: %s", val, + sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + return ret; +} + +/*------------------------------------------------------------------------------ + * Set unicast hops val to the socket (cf IP_TTL). + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_unicast_hops (int sock_fd, int val) +{ + int ret; + + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)); + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt IPV6_UNICAST_HOPS to %d on socket %d: %s", + val, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; + return ret; +} + +/*------------------------------------------------------------------------------ + * Set multicast hops val to the socket. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_multicast_hops (int sock_fd, int val) +{ + int ret; + + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)); + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt IPV6_MULTICAST_HOPS to %d on socket %d: %s", + val, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; return ret; -#else /* HAVE_TCP_MD5SIG */ - return -2; -#endif /* !HAVE_TCP_MD5SIG */ } + +/*------------------------------------------------------------------------------ + * Set IPV6_RECVHOPLIMIT option (or IPV6_HOPLIMIT) + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_hoplimit (int sock_fd, int val) +{ + int ret; + int opt ; + const char* name ; + +# ifdef IPV6_RECVHOPLIMIT + opt = IPV6_RECVHOPLIMIT ; /* RFC3542 == RFC2292-bis */ + name = "IPV6_RECVHOPLIMIT" ; +# else + opt = IPV6_HOPLIMIT ; /* RFC2292 */ + name = "IPV6_HOPLIMIT" ; +# endif + + ret = setsockopt(sock_fd, IPPROTO_IPV6, opt, &val, sizeof(val)); + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt %s to %d on socket %d: %s", name, + val, sock_fd, errtoa(err, 0).str); + errno = err ; + } ; + + return ret; +} + +/*------------------------------------------------------------------------------ + * Set IPV6_MULTICAST_LOOP option + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +setsockopt_ipv6_multicast_loop (int sock_fd, int val) +{ + int ret; + + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof (val)); + if (ret < 0) + { + int err = errno ; + zlog_err("cannot set sockopt IPV6_MULTICAST_LOOP to %d on socket %d: %s", + val, sock_fd, errtoa(err, 0).str); + errno = err ; + } ; + return ret; +} +#endif /* HAVE_IPV6 */ + + diff --git a/lib/sockopt.h b/lib/sockopt.h index cb05c6fb..c706a74f 100644 --- a/lib/sockopt.h +++ b/lib/sockopt.h @@ -1,4 +1,4 @@ -/* Router advertisement +/* Setting and getting socket options -- utility functions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_SOCKOPT_H @@ -24,18 +24,20 @@ #include "sockunion.h" -extern int setsockopt_so_recvbuf (int sock, int size); -extern int setsockopt_so_sendbuf (const int sock, int size); -extern int getsockopt_so_sendbuf (const int sock); +extern int setsockopt_reuseaddr (int sock_fd) ; +extern int setsockopt_reuseport (int sock_fd) ; +extern int setsockopt_broadcast (int sock_fd) ; -#ifdef HAVE_IPV6 -extern int setsockopt_ipv6_pktinfo (int, int); -extern int setsockopt_ipv6_checksum (int, int); -extern int setsockopt_ipv6_multicast_hops (int, int); -extern int setsockopt_ipv6_unicast_hops (int, int); -extern int setsockopt_ipv6_hoplimit (int, int); -extern int setsockopt_ipv6_multicast_loop (int, int); -#endif /* HAVE_IPV6 */ +extern int setsockopt_ttl (int sock_fd, int ttl); +extern int setsockopt_minttl (int sock_fd, int ttl); +extern int setsockopt_cork (int sock_fd, int onoff); + +extern int setsockopt_so_recvbuf (int sock_fd, int size); +extern int setsockopt_so_sendbuf (int sock_fd, int size); +extern int getsockopt_so_sendbuf (int sock_fd); + +extern int setsockopt_tcp_signature(int sock_fd, union sockunion *su, + const char *password); /* * It is OK to reference in6_pktinfo here without a protecting #if @@ -82,25 +84,35 @@ extern int setsockopt_ipv6_multicast_loop (int, int); (((af) == AF_INET) : SOPT_SIZE_CMSG_IFINDEX_IPV4() \ ? SOPT_SIZE_CMSG_PKTINFO_IPV6()) -extern int setsockopt_multicast_ipv4(int sock, int optname, +extern int setsockopt_multicast_ipv4(int sock_fd, int optname, struct in_addr if_addr /* required: interface to join on */, unsigned int mcast_addr, unsigned int ifindex /* optional: if non-zero, may be used instead of if_addr */); -extern int setsockopt_ipv4_tos(int sock, int tos); +extern int setsockopt_ipv4_tos(int sock_fd, int tos); /* Ask for, and get, ifindex, by whatever method is supported. */ -extern int setsockopt_ifindex (int, int, int); +extern int setsockopt_pktinfo (int, int, int); extern int getsockopt_ifindex (int, struct msghdr *); -/* swab the fields in iph between the host order and system order expected +/* swab the fields in iph between the host order and system order expected * for IP_HDRINCL. */ extern void sockopt_iphdrincl_swab_htosys (struct ip *iph); extern void sockopt_iphdrincl_swab_systoh (struct ip *iph); -extern int sockopt_tcp_signature(int sock, union sockunion *su, - const char *password); +#ifdef HAVE_IPV6 + +extern int setsockopt_ipv6_v6only(int sock_fd) ; +extern int setsockopt_ipv6_pktinfo (int, int); +extern int setsockopt_ipv6_checksum (int, int); +extern int setsockopt_ipv6_multicast_hops (int, int); +extern int setsockopt_ipv6_unicast_hops (int, int); +extern int setsockopt_ipv6_hoplimit (int, int); +extern int setsockopt_ipv6_multicast_loop (int, int); + +#endif /* HAVE_IPV6 */ + #endif /*_ZEBRA_SOCKOPT_H */ diff --git a/lib/sockunion.c b/lib/sockunion.c index a5382a72..b34d7047 100644 --- a/lib/sockunion.c +++ b/lib/sockunion.c @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -28,6 +28,8 @@ #include "str.h" #include "log.h" +#include "symtab.h" + #ifndef HAVE_INET_ATON int inet_aton (const char *cp, struct in_addr *inaddr) @@ -95,13 +97,13 @@ inet_ntop (int family, const void *addrptr, char *strptr, size_t len) { unsigned char *p = (unsigned char *) addrptr; - if (family == AF_INET) + if (family == AF_INET) { char temp[INET_ADDRSTRLEN]; snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); - if (strlen(temp) >= len) + if (strlen(temp) >= len) { errno = ENOSPC; return NULL; @@ -115,460 +117,563 @@ inet_ntop (int family, const void *addrptr, char *strptr, size_t len) } #endif /* ! HAVE_INET_NTOP */ -const char * -inet_sutop (union sockunion *su, char *str) +/*------------------------------------------------------------------------------ + * Set the sockunion size (sin_len or sin6_len), if required. + * + * NB: POSIX does not require this and Stevens et al say that even where it + * is supported, the application need not worry about it. + * + * However... the code as found does this. + * + * TODO: is it *really* necessary to set sin_len or sin6_len ?? + * + * Returns: the sockunion size + */ +inline static int +sockunion_sin_len(sockunion su) +{ + return +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = +#endif + sizeof(struct sockaddr_in); +} ; + +#ifdef HAVE_IPV6 +inline static int +sockunion_sin6_len(sockunion su) +{ + return +#ifdef SIN6_LEN + su->sin6.sin6_len = +#endif + sizeof(struct sockaddr_in6); +} ; +#endif + +/*------------------------------------------------------------------------------ + * Set the address family for the given sockunion. + * + * If sin_len or sin6_len entry is present, fill that in too. + * + * Assumes the address family is valid ! + * + * Returns: 0 + */ +inline static int +sockunion_set_family(sockunion su, sa_family_t family) +{ + su->sa.sa_family = family ; + +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + if (family == AF_INET) + sockunion_sin_len(su) ; +#endif +#if defined(HAVE_IPV6) && defined(SIN6_LEN) + if (family == AF_INET6) + sockunion_sin6_len(su) ; +#endif + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Set the given sockunion address to "any" + */ +static void +sockunion_set_addr_any(sockunion su) { switch (su->sa.sa_family) - { + { case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN); - break; + su->sin.sin_addr.s_addr = htonl (INADDR_ANY); + return ; + #ifdef HAVE_IPV6 case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN); - break; -#endif /* HAVE_IPV6 */ - } - return str; -} +# if defined(LINUX_IPV6) || defined(NRL) + memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); +# else + su->sin6.sin6_addr = in6addr_any; +# endif /* LINUX_IPV6 || defined(NRL) */ + return ; +#endif -int -str2sockunion (const char *str, union sockunion *su) -{ - int ret; + default: + return ; + } ; +} ; - memset (su, 0, sizeof (union sockunion)); +/*------------------------------------------------------------------------------ + * Set the port number in the given sockunion. + * + * For good measure, set the size (if that's required) and return same. + */ +extern int +sockunion_set_port(sockunion su, in_port_t port) +{ + switch (su->sa.sa_family) + { + case AF_INET: + su->sin.sin_port = htons(port) ; + return sockunion_sin_len(su) ; - ret = inet_pton (AF_INET, str, &su->sin.sin_addr); - if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return 0; - } #ifdef HAVE_IPV6 - ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); - if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return 0; - } -#endif /* HAVE_IPV6 */ - return -1; -} + case AF_INET6: + su->sin6.sin6_port = htons(port) ; + return sockunion_sin6_len(su) ; +#endif -const char * -sockunion2str (union sockunion *su, char *buf, size_t len) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise a new sockunion -- for the given address family (if any) + * + * Allocates a sockunion if required. + * + * Result is set "any". + * + * Advice is to zeroize sockaddr_in6, in particular. + */ +extern sockunion +sockunion_init_new(sockunion su, sa_family_t family) { - if (su->sa.sa_family == AF_INET) - return inet_ntop (AF_INET, &su->sin.sin_addr, buf, len); + if (su == NULL) + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)) ; + else + memset(su, 0, sizeof(union sockunion)) ; + + if (family != AF_UNSPEC) + sockunion_set_family(su, family) ; + else + confirm(AF_UNSPEC == 0) ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Get the length of the address in the given sockunion. + * + * Returns zero if AF_UNSPEC or not any known address family. + */ +extern int +sockunion_get_len(sockunion su) +{ + switch (su->sa.sa_family) + { + case AF_INET: + return sizeof(struct sockaddr_in) ; + #ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) - return inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, len); -#endif /* HAVE_IPV6 */ - return NULL; -} + case AF_INET6: + return sizeof(struct sockaddr_in6) ; +#endif -union sockunion * -sockunion_str2su (const char *str) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * From the given string, fill in the given sockunion. + * + * Returns: 0 => OK -- sockunion filled in + * -1 => not a valid address (or not a known address family) + */ +int +str2sockunion (const char *str, union sockunion *su) { int ret; - union sockunion *su; - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); + assert(su != NULL) ; + + sockunion_init_new(su, AF_UNSPEC) ; ret = inet_pton (AF_INET, str, &su->sin.sin_addr); if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return su; - } + return sockunion_set_family(su, AF_INET) ; + #ifdef HAVE_IPV6 ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return su; - } + return sockunion_set_family(su, AF_INET6) ; #endif /* HAVE_IPV6 */ - XFREE (MTYPE_SOCKUNION, su); - return NULL; + return -1; } -char * -sockunion_su2str (union sockunion *su) +/*------------------------------------------------------------------------------ + * Construct string for sockunion IP address. + * + * Requires buffer of at least SU_ADDRSTRLEN characters. + */ +const char * +sockunion2str (union sockunion *su, char *buf, size_t size) { - char str[SU_ADDRSTRLEN]; + assert(size >= SU_ADDRSTRLEN) ; switch (su->sa.sa_family) - { + { case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, sizeof (str)); + inet_ntop (AF_INET, &su->sin.sin_addr, buf, size); break; #ifdef HAVE_IPV6 case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, sizeof (str)); + inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, size); break; #endif /* HAVE_IPV6 */ - } - return XSTRDUP (MTYPE_TMP, str); + default: + snprintf (buf, size, "?af=%d?", (int)su->sa.sa_family) ; + } ; + + return buf; } -/* Convert IPv4 compatible IPv6 address to IPv4 address. */ -static void -sockunion_normalise_mapped (union sockunion *su) +/*------------------------------------------------------------------------------ + * Fill in and return a sockunion_string + */ +extern sockunion_string_t +sutoa(sockunion su) { - struct sockaddr_in sin; - -#ifdef HAVE_IPV6 - if (su->sa.sa_family == AF_INET6 - && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr)) - { - memset (&sin, 0, sizeof (struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = su->sin6.sin6_port; - memcpy (&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); - memcpy (su, &sin, sizeof (struct sockaddr_in)); - } -#endif /* HAVE_IPV6 */ -} + sockunion_string_t sus ; -/* Return socket of sockunion. */ -int -sockunion_socket (union sockunion *su) + sockunion2str(su, sus.str, sizeof(sus.str)) ; + return sus ; +} ; + +/*------------------------------------------------------------------------------ + * From the given string, construct and fill in a sockunion. + * + * Returns: NULL => not a valid address (or not a known address family) + * otherwise is address of new sockunion. + * + * NB: the caller is responsible for freeing the sockunion created. + */ +union sockunion * +sockunion_str2su (const char *str) { - int sock; + union sockunion *su; - sock = socket (su->sa.sa_family, SOCK_STREAM, 0); - if (sock < 0) - { - zlog (NULL, LOG_WARNING, "Can't make socket : %s", safe_strerror (errno)); - return -1; - } + su = XMALLOC (MTYPE_SOCKUNION, sizeof(union sockunion)); - return sock; + if (str2sockunion (str, su) != 0) + XFREE (MTYPE_SOCKUNION, su); /* sets su = NULL */ + + return su ; } -/* Return accepted new socket file descriptor. */ -int -sockunion_accept (int sock, union sockunion *su) +/*------------------------------------------------------------------------------ + * Convert given sockunion to string, and return a new piece of memory + * containing same. + * + * It is the callers responsibility to free the memory in due course. + */ +extern char * +sockunion_su2str (union sockunion *su, enum MTYPE type) { - socklen_t len; - int client_sock; - - len = sizeof (union sockunion); - client_sock = accept (sock, (struct sockaddr *) su, &len); - - sockunion_normalise_mapped (su); - return client_sock; + return XSTRDUP (type, sutoa(su).str) ; } -/* Return sizeof union sockunion. */ -static int -sockunion_sizeof (union sockunion *su) +/*------------------------------------------------------------------------------ + * If have an IPv6 mapped IPv4 address, convert it to an IPv4 address. + */ +extern void +sockunion_unmap_ipv4 (sockunion su) { - int ret; - - ret = 0; - switch (su->sa.sa_family) - { - case AF_INET: - ret = sizeof (struct sockaddr_in); - break; #ifdef HAVE_IPV6 - case AF_INET6: - ret = sizeof (struct sockaddr_in6); - break; -#endif /* AF_INET6 */ + if ( (sockunion_family(su) == AF_INET6) + && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr) ) + { + union sockunion sux[1] ; + + sockunion_init_new(sux, AF_INET) ; + memcpy (&sux->sin.sin_addr, &su->sin6.sin6_addr.s6_addr[12], 4) ; + sux->sin.sin_port = su->sin6.sin6_port ; + memcpy (su, sux, sizeof(*sux)) ; + confirm(sizeof(*su) == sizeof(*sux)) ; } - return ret; +#endif /* HAVE_IPV6 */ } -/* return sockunion structure : this function should be revised. */ -static char * -sockunion_log (union sockunion *su) +/*------------------------------------------------------------------------------ + * If have an IPv4 address, convert it to an IPv6 mapped IPv4 address. + */ +extern void +sockunion_map_ipv4 (sockunion su) { - static char buf[SU_ADDRSTRLEN]; - - switch (su->sa.sa_family) - { - case AF_INET: - snprintf (buf, SU_ADDRSTRLEN, "%s", inet_ntoa (su->sin.sin_addr)); - break; #ifdef HAVE_IPV6 - case AF_INET6: - snprintf (buf, SU_ADDRSTRLEN, "%s", - inet_ntop (AF_INET6, &(su->sin6.sin6_addr), buf, SU_ADDRSTRLEN)); - break; -#endif /* HAVE_IPV6 */ - default: - snprintf (buf, SU_ADDRSTRLEN, "af_unknown %d ", su->sa.sa_family); - break; + if (sockunion_family(su) == AF_INET) + { + union sockunion sux[1] ; + + sockunion_init_new(sux, AF_INET6) ; + memset (&sux->sin6.sin6_addr.s6_addr[10], 0xFF, 2) ; + memcpy (&sux->sin6.sin6_addr.s6_addr[12], &su->sin.sin_addr, 4) ; + sux->sin6.sin6_port = su->sin.sin_port ; + memcpy (su, sux, sizeof(*sux)) ; + confirm(sizeof(*su) == sizeof(*sux)) ; } - return (XSTRDUP (MTYPE_TMP, buf)); +#endif /* HAVE_IPV6 */ } -/* sockunion_connect returns - -1 : error occured - 0 : connect success - 1 : connect is in progress */ -enum connect_result -sockunion_connect (int fd, union sockunion *peersu, unsigned short port, - unsigned int ifindex) +/*------------------------------------------------------------------------------ + * Return accepted new socket file descriptor. + * + * The following errors should be ignored: + * + * EAGAIN, EWOULDBLOCK or ECONNABORTED -- connection aborted before got + * around to it (or not ready, anyway). + * + * EINTR -- the usual suspect. + * + * Sets the given sockunion to the result of the accept(), converting any + * IPv6 mapped IPv4 addresses to IPv4 form. (Hiding the family for the socket.) + * + * Returns: >= 0 -- OK, this is the fd (socket) + * -1 -- error -- not one of the above + * -2 -- error -- one of the above + */ +extern int +sockunion_accept (int sock_fd, union sockunion *su) { - int ret; - int val; - union sockunion su; - - memcpy (&su, peersu, sizeof (union sockunion)); - - switch (su.sa.sa_family) - { - case AF_INET: - su.sin.sin_port = port; - break; -#ifdef HAVE_IPV6 - case AF_INET6: - su.sin6.sin6_port = port; -#ifdef KAME - if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) - { -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID - /* su.sin6.sin6_scope_id = ifindex; */ -#ifdef MUSICA - su.sin6.sin6_scope_id = ifindex; -#endif -#endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ -#ifndef MUSICA - SET_IN6_LINKLOCAL_IFINDEX (su.sin6.sin6_addr, ifindex); -#endif - } -#endif /* KAME */ - break; -#endif /* HAVE_IPV6 */ - } - - /* Make socket non-block. */ - val = fcntl (fd, F_GETFL, 0); - fcntl (fd, F_SETFL, val|O_NONBLOCK); + socklen_t len; + int new_fd, err ; - /* Call connect function. */ - ret = connect (fd, (struct sockaddr *) &su, sockunion_sizeof (&su)); + len = sizeof(*su); + memset(su, 0, len) ; + new_fd = accept(sock_fd, &su->sa, &len) ; - /* Immediate success */ - if (ret == 0) + if (new_fd >= 0) { - fcntl (fd, F_SETFL, val); - return connect_success; - } + sockunion_unmap_ipv4(su); + return new_fd ; /* OK -- got socket */ + } ; + + err = errno ; + return ( (err == EAGAIN) + || (err == EWOULDBLOCK) + || (err == ECONNABORTED) + || (err == EINTR) ) ? -2 : -1 ; +} ; + +/*------------------------------------------------------------------------------ + * Make socket for given family, type and protocol + * + * Returns: -1 : failed -- see errno + * otherwise : socket + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_socket(sockunion su, int type, int protocol) +{ + int sock_fd ; + int err ; - /* If connect is in progress then return 1 else it's real error. */ - if (ret < 0) - { - if (errno != EINPROGRESS) - { - zlog_info ("can't connect to %s fd %d : %s", - sockunion_log (&su), fd, safe_strerror (errno)); - return connect_error; - } - } + sock_fd = socket(sockunion_family(su), type, protocol); - fcntl (fd, F_SETFL, val); + if (sock_fd >= 0) + return sock_fd ; - return connect_in_progress; + err = errno ; + zlog_err("Cannot make socket family=%d, type=%d, protocol=%d: %s", + (int)sockunion_family(su), type, protocol, errtoa(err, 0).str) ; + errno = err ; + return -1; } -/* Make socket from sockunion union. */ -int -sockunion_stream_socket (union sockunion *su) +/*------------------------------------------------------------------------------ + * Make socket for family from given sockunion, type=SOCK_STREAM, protocol=0. + * + * Returns: -1 : failed -- see errno + * otherwise : socket + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_stream_socket (sockunion su) { - int sock; - if (su->sa.sa_family == 0) su->sa.sa_family = AF_INET_UNION; - sock = socket (su->sa.sa_family, SOCK_STREAM, 0); - - if (sock < 0) - zlog (NULL, LOG_WARNING, "can't make socket sockunion_stream_socket"); - - return sock; + return sockunion_socket (su, SOCK_STREAM, 0); } -/* Bind socket to specified address. */ -int -sockunion_bind (int sock, union sockunion *su, unsigned short port, - union sockunion *su_addr) +/*------------------------------------------------------------------------------ + * Initiate a connection + * + * Reports EINPROGRESS as success. + * + * TODO: discover how the ifindex thing is supposed to work !! + * + * Returns: 0 : OK (so far so good) + * < 0 : failed -- see errno + * + * Logs a LOG_INFO message if fails. + */ +extern int +sockunion_connect(int sock_fd, union sockunion* peer_su, unsigned short port, + unsigned int ifindex) { - int size = 0; - int ret; + union sockunion su ; + int ret, err ; + int sa_len ; + + memcpy(&su, peer_su, sizeof(union sockunion)) ; + + sa_len = sockunion_set_port(&su, port) ; - if (su->sa.sa_family == AF_INET) - { - size = sizeof (struct sockaddr_in); - su->sin.sin_port = htons (port); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = size; -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - if (su_addr == NULL) - su->sin.sin_addr.s_addr = htonl (INADDR_ANY); - } #ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) +# ifdef KAME + if (su.sa.sa_family == AF_INET6) { - size = sizeof (struct sockaddr_in6); - su->sin6.sin6_port = htons (port); -#ifdef SIN6_LEN - su->sin6.sin6_len = size; -#endif /* SIN6_LEN */ - if (su_addr == NULL) + if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) { -#if defined(LINUX_IPV6) || defined(NRL) - memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); -#else - su->sin6.sin6_addr = in6addr_any; -#endif /* LINUX_IPV6 */ +# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID + /* su.sin6.sin6_scope_id = ifindex; */ +# ifdef MUSICA + su.sin6.sin6_scope_id = ifindex; +# endif +# endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ +# ifndef MUSICA + SET_IN6_LINKLOCAL_IFINDEX (su.sin6.sin6_addr, ifindex); +# endif } - } + } ; +# endif /* KAME */ #endif /* HAVE_IPV6 */ - - ret = bind (sock, (struct sockaddr *)su, size); - if (ret < 0) - zlog (NULL, LOG_WARNING, "can't bind socket : %s", safe_strerror (errno)); + ret = connect(sock_fd, &su.sa, sa_len) ; + err = (ret >= 0) ? 0 : errno ; - return ret; -} + if ((err == 0) || (err == EINPROGRESS)) + return 0 ; /* instant success or EINPROGRESS as expected */ -int -sockopt_reuseaddr (int sock) -{ - int ret; - int on = 1; + zlog_info("cannot connect to %s port %d socket %d: %s", + sutoa(&su).str, port, sock_fd, errtoa(err, 0).str) ; + errno = err ; - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, - (void *) &on, sizeof (on)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEADDR to socket %d", sock); - return -1; - } - return 0; -} + return ret ; +} ; -#ifdef SO_REUSEPORT -int -sockopt_reuseport (int sock) +/*------------------------------------------------------------------------------ + * Start listening on given socket + * + * Returns: >= 0 : OK (so far so good) + * < 0 : failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_listen(int sock_fd, int backlog) { - int ret; - int on = 1; + int ret ; + + ret = listen(sock_fd, backlog) ; - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, - (void *) &on, sizeof (on)); if (ret < 0) { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEPORT to socket %d", sock); - return -1; - } - return 0; -} -#else -int -sockopt_reuseport (int sock) -{ - return 0; -} -#endif /* 0 */ + int err = errno ; + zlog_err("cannot listen on socket %d: %s", sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; -int -sockopt_ttl (int family, int sock, int ttl) + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Bind socket to address/port. + * + * If the 'any' parameter is true, sets the given sockunion to INADDR_ANY or + * the *socket* address family equivalent. + * + * Sets the given port into the sockunion su. + * + * For good measure, sets sin_len or family equivalent if required. + * + * If not 'any', and the given su does not have the same address family as the + * socket, then attempts to convert the su to the same family as the socket, + * by mapping or unmapping IPv4. + * + * Performs bind() and logs a LOG_WARNING message if fails. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + */ +extern int +sockunion_bind(int sock_fd, sockunion su, unsigned short port, bool any) { - int ret; + int sa_len ; + int ret ; + int sock_family ; -#ifdef IP_TTL - if (family == AF_INET) + sock_family = sockunion_getsockfamily(sock_fd) ; + + if (any) { - ret = setsockopt (sock, IPPROTO_IP, IP_TTL, - (void *) &ttl, sizeof (int)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt IP_TTL %d to socket %d", ttl, sock); - return -1; - } - return 0; + /* Create an "any" -- of same family as the socket */ + sockunion_init_new(su, sock_family) ; + sockunion_set_addr_any(su) ; } -#endif /* IP_TTL */ -#ifdef HAVE_IPV6 - if (family == AF_INET6) + else { - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - (void *) &ttl, sizeof (int)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d", - ttl, sock); - return -1; - } - return 0; - } -#endif /* HAVE_IPV6 */ - return 0; -} - -int -sockopt_cork (int sock, int onoff) -{ -#ifdef TCP_CORK - return setsockopt (sock, IPPROTO_TCP, TCP_CORK, &onoff, sizeof(onoff)); -#else - return 0; + /* Want to bind to a specific address. + * + * We provide bind with an address which matches the address family of + * the *socket*. + * + * If the socket is AF_INET, address may be AF_INET, or an AF_INET6 + * *provided* it is an IPv4 mapped address. + * + * If the socket is AF_INET6, address may be AF_INET or AF_NET6, and + * will map any IPv4 address. + * + * If we don't HAVE_IPV6, or we don't recognise an address family, + * then do nothing and let bind() return some sort of error. + */ +#ifdef HAVE_IPV6 + if (sock_family != sockunion_family(su)) + { + switch (sock_family) + { + case AF_INET: + sockunion_unmap_ipv4(su) ; /* unmap if AF_INET6 mapped IPv4 */ + break ; + + case AF_INET6: + sockunion_map_ipv4(su) ; /* map if AF_INET */ + break ; + + default: + break ; + } ; + } ; #endif -} + } ; -int -sockopt_minttl (int family, int sock, int minttl) -{ -#ifdef IP_MINTTL - if (family == AF_INET) - { - int ret = setsockopt (sock, IPPROTO_IP, IP_MINTTL, &minttl, sizeof(minttl)); - if (ret < 0) - zlog (NULL, LOG_WARNING, - "can't set sockopt IP_MINTTL to %d on socket %d: %s", - minttl, sock, safe_strerror (errno)); - return ret; - } -#endif /* IP_MINTTL */ -#ifdef IPV6_MINHOPCNT - if (family == AF_INET6) + sa_len = sockunion_set_port(su, port) ; + + ret = bind (sock_fd, &su->sa, sa_len); + if (ret < 0) { - int ret = setsockopt (sock, IPPROTO_IPV6, IPV6_MINHOPCNT, &minttl, sizeof(minttl)); - if (ret < 0) - zlog (NULL, LOG_WARNING, - "can't set sockopt IPV6_MINHOPCNT to %d on socket %d: %s", - minttl, sock, safe_strerror (errno)); - return ret; - } -#endif + int err = errno ; + zlog_warn("cannot bind to %s port %d socket %d: %s", + sutoa(su).str, port, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; - errno = EOPNOTSUPP; - return -1; + return ret; } -/* If same family and same prefix return 1. */ -int +/*------------------------------------------------------------------------------ + * If same (known) family and same prefix return 1, otherwise return 0. + * + * Returns 0 if same family, but not a known family. + */ +extern int sockunion_same (union sockunion *su1, union sockunion *su2) { int ret = 0; @@ -577,126 +682,174 @@ sockunion_same (union sockunion *su1, union sockunion *su2) return 0; switch (su1->sa.sa_family) - { + { case AF_INET: - ret = memcmp (&su1->sin.sin_addr, &su2->sin.sin_addr, - sizeof (struct in_addr)); - break; + return (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) ; + #ifdef HAVE_IPV6 case AF_INET6: ret = memcmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, - sizeof (struct in6_addr)); - break; + sizeof (struct in6_addr)); + return (ret == 0) ; #endif /* HAVE_IPV6 */ - } - if (ret == 0) - return 1; - else - return 0; -} -/* After TCP connection is established. Get local address and port. */ -union sockunion * -sockunion_getsockname (int fd) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Get local (getsockname) or remote (getpeername) address and port. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * If "unmap": if address is an IPv4 mapped IPv6 address, returns AF_INET. + * + * NB: returns EAFNOSUPPORT if don't recognise the address family. + * + * Logs a LOG_ERR message if fails in getsockname/getpeername. + */ +static int +sockunion_get_name(int sock_fd, union sockunion* su, bool local, bool unmap) { - int ret; - socklen_t len; + int ret ; + socklen_t len ; union { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_IPV6 - struct sockaddr_in6 sin6; -#endif /* HAVE_IPV6 */ + union sockunion su ; char tmp_buffer[128]; - } name; - union sockunion *su; + } name ; + + memset(su, 0, sizeof(union sockunion)) ; + + confirm(AF_UNSPEC == 0) ; - memset (&name, 0, sizeof name); - len = sizeof name; + if (sock_fd < 0) + return AF_UNSPEC ; + + len = sizeof(name) ; + memset(&name, 0, len); + + if (local) + ret = getsockname(sock_fd, &name.su.sa, &len) ; + else + ret = getpeername(sock_fd, &name.su.sa, &len) ; - ret = getsockname (fd, (struct sockaddr *)&name, &len); if (ret < 0) { - zlog_warn ("Can't get local address and port by getsockname: %s", - safe_strerror (errno)); - return NULL; + int err = errno ; + zlog_err("failed in %s for socket %d: %s", + local ? "getsockname" : "getpeername", + sock_fd, errtoa(err, 0).str) ; + errno = err ; } - - if (name.sa.sa_family == AF_INET) + else { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in)); - return su; - } + ret = name.su.sa.sa_family ; + + switch (ret) + { + case AF_INET: + su->sin = name.su.sin ; + break ; + #ifdef HAVE_IPV6 - if (name.sa.sa_family == AF_INET6) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in6)); - sockunion_normalise_mapped (su); - return su; - } + case AF_INET6: + su->sin6 = name.su.sin6 ; + if (unmap) + sockunion_unmap_ipv4(su) ; + break ; #endif /* HAVE_IPV6 */ - return NULL; -} -/* After TCP connection is established. Get remote address and port. */ -union sockunion * -sockunion_getpeername (int fd) + default: + errno = EAFNOSUPPORT ; + ret = -1 ; + } ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Get the address family the given socket is set to. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * NB: gets the actual address family -- does NOT look for mapped IPv4. + */ +extern int +sockunion_getsockfamily(int sock_fd) { - int ret; - socklen_t len; - union - { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_IPV6 - struct sockaddr_in6 sin6; -#endif /* HAVE_IPV6 */ - char tmp_buffer[128]; - } name; - union sockunion *su; + union sockunion su[1] ; + int ret ; - memset (&name, 0, sizeof name); - len = sizeof name; - ret = getpeername (fd, (struct sockaddr *)&name, &len); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "Can't get remote address and port: %s", - safe_strerror (errno)); - return NULL; - } + ret = sockunion_get_name(sock_fd, su, true, /* true => local */ + false) ; /* false => don't unmap */ + return (ret >= 0) ? sockunion_family(su) : ret ; +} ; - if (name.sa.sa_family == AF_INET) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in)); - return su; - } -#ifdef HAVE_IPV6 - if (name.sa.sa_family == AF_INET6) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in6)); - sockunion_normalise_mapped (su); - return su; - } -#endif /* HAVE_IPV6 */ - return NULL; -} +/*------------------------------------------------------------------------------ + * Get the address family the given socket's protocol is set to. + * + * If this is an AF_INET, that's easy. + * + * If this is an AF_INET6, then needs to look out for IN6_IS_ADDR_V4MAPPED. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * NB: gets the underlying address family -- ie: looks for mapped IPv4. + */ +extern int +sockunion_getprotofamily(int sock_fd) +{ + union sockunion su[1] ; + int ret ; + + ret = sockunion_get_name(sock_fd, su, true, /* true => local */ + true) ; /* true => unmap */ + return (ret >= 0) ? sockunion_family(su) : ret ; +} ; + +/*------------------------------------------------------------------------------ + * Get local address and port -- ie getsockname(), except unmaps IPv4 mapped. + * + * See: sockunion_get_name() + */ +extern int +sockunion_getsockname(int sock_fd, sockunion su_local) +{ + return sockunion_get_name(sock_fd, su_local, true, /* true => local */ + true) ; /* true => unmap */ +} ; + +/*------------------------------------------------------------------------------ + * Get remote address and port -- ie getpeername(), except unmaps IPv4 mapped. + * + * See: sockunion_get_name() + */ +extern int +sockunion_getpeername (int sock_fd, sockunion su_remote) +{ + return sockunion_get_name(sock_fd, su_remote, false, /* false => remote */ + true) ; /* true => unmap */ +} ; -/* Print sockunion structure */ +/*------------------------------------------------------------------------------ + * Print sockunion structure to stdout + */ static void __attribute__ ((unused)) sockunion_print (union sockunion *su) { if (su == NULL) return; - switch (su->sa.sa_family) + switch (su->sa.sa_family) { case AF_INET: - printf ("%s\n", inet_ntoa (su->sin.sin_addr)); + printf ("%s\n", safe_inet_ntoa (su->sin.sin_addr)); break; #ifdef HAVE_IPV6 case AF_INET6: @@ -725,27 +878,15 @@ sockunion_print (union sockunion *su) } } -#ifdef HAVE_IPV6 -static int -in6addr_cmp (struct in6_addr *addr1, struct in6_addr *addr2) -{ - unsigned int i; - u_char *p1, *p2; - - p1 = (u_char *)addr1; - p2 = (u_char *)addr2; - - for (i = 0; i < sizeof (struct in6_addr); i++) - { - if (p1[i] > p2[i]) - return 1; - else if (p1[i] < p2[i]) - return -1; - } - return 0; -} -#endif /* HAVE_IPV6 */ - +/*------------------------------------------------------------------------------ + * Compare two sockunion values + * + * Compares family values first, then the body of the address. + * + * Returns: +1 => su1 > su2 + * 0 => su1 == su2 (or same, but unknown, family) + * -1 => su1 < su2 + */ int sockunion_cmp (union sockunion *su1, union sockunion *su2) { @@ -754,23 +895,33 @@ sockunion_cmp (union sockunion *su1, union sockunion *su2) if (su1->sa.sa_family < su2->sa.sa_family) return -1; - if (su1->sa.sa_family == AF_INET) - { - if (ntohl (su1->sin.sin_addr.s_addr) == ntohl (su2->sin.sin_addr.s_addr)) - return 0; - if (ntohl (su1->sin.sin_addr.s_addr) > ntohl (su2->sin.sin_addr.s_addr)) - return 1; + switch (su1->sa.sa_family) + { + case AF_INET: + if (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) + return 0; + if (ntohl(su1->sin.sin_addr.s_addr) > ntohl(su2->sin.sin_addr.s_addr)) + return +1; else return -1; - } + #ifdef HAVE_IPV6 - if (su1->sa.sa_family == AF_INET6) - return in6addr_cmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr); + case AF_INET6: + return memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, + sizeof(struct in6_addr)) ; #endif /* HAVE_IPV6 */ - return 0; + + default: + return 0 ; + } ; } -/* Duplicate sockunion. */ +/*------------------------------------------------------------------------------ + * Create copy of existing sockunion. + * + * It is the caller's responsibility to free the sockunion at some point --see + * sockunion_free() + */ union sockunion * sockunion_dup (union sockunion *su) { @@ -779,8 +930,188 @@ sockunion_dup (union sockunion *su) return dup; } +/*------------------------------------------------------------------------------ + * Copy one sockunion to another + */ +extern void +sockunion_copy (sockunion dst, sockunion src) +{ + memcpy (dst, src, sizeof(*dst)) ; +} ; + +/*------------------------------------------------------------------------------ + * Free given sockunion (if any). + */ void sockunion_free (union sockunion *su) { - XFREE (MTYPE_SOCKUNION, su); + if (su != NULL) + XFREE (MTYPE_SOCKUNION, su); +} + +/*============================================================================== + * Sockunion reference utilities + */ + +/*------------------------------------------------------------------------------ + * Set sockunion from given prefix -- allocate new sockunion, if required. + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. + */ +extern sockunion +sockunion_new_prefix(sockunion su, struct prefix* p) +{ + sa_family_t family ; + + family = (p != NULL) ? p->family : 0 ; + + su = sockunion_init_new(su, family) ; + + switch (family) + { + case 0: + break ; + + case AF_INET: + su->sin.sin_addr = p->u.prefix4 ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6.sin6_addr = p->u.prefix6 ; + break ; +#endif + + default: + break ; + } ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Create new sockunion from given sockaddr -- taking only the address part + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. + */ +extern sockunion +sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) +{ + sa_family_t family ; + + family = (sa != NULL) ? sa->sa_family : AF_UNSPEC ; + + su = sockunion_init_new(su, family) ; + + switch (family) + { + case AF_UNSPEC: + break ; + + case AF_INET: + su->sin.sin_addr = ((struct sockaddr_in*)sa)->sin_addr ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6.sin6_addr = ((struct sockaddr_in6*)sa)->sin6_addr ; + break ; +#endif + + default: + break ; + } ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Unset pointer to sockunion -- free any sockunion referenced + * + * Does nothing if there is no sockunion + */ +extern void +sockunion_unset(sockunion* p_su) +{ + if (*p_su != NULL) + XFREE(MTYPE_SOCKUNION, *p_su) ; /* sets *p_su NULL */ +} ; + +/*------------------------------------------------------------------------------ + * Set pointer to sockunion (if any) + * + * Frees any existing sockunion at the destination. + * + * NB: copies the source pointer -- so must be clear about responsibility + * for the sockunion. + */ +extern void +sockunion_set(sockunion* p_dst, sockunion su) +{ + sockunion_unset(p_dst) ; + *p_dst = su ; } + +/*------------------------------------------------------------------------------ + * Set pointer to a *copy* of the given sockunion + * + * Frees any existing sockunion at the destination. + * + * NB: copies the source pointer -- so must be clear about responsibility + * for the sockunion structure. + */ +extern void +sockunion_set_dup(sockunion* p_dst, sockunion su) +{ + sockunion_set(p_dst, sockunion_dup(su)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set pointer to sockunion (if any) and unset source pointer. + * + * Frees any existing sockunion at the destination. + * + * NB: responsibility for the sockunion passes to the destination. + */ +extern void +sockunion_set_mov(sockunion* p_dst, sockunion* p_src) +{ + sockunion_unset(p_dst) ; + *p_dst = *p_src ; + *p_src = NULL ; +} ; + +/*============================================================================== + * Symbol Table Hash function -- for symbols whose name is an address. + */ +extern void +sockunion_symbol_hash(symbol_hash p_hash, const void* name) +{ + const union sockunion* su = name ; + + switch (su->sa.sa_family) + { + case AF_INET: + confirm(sizeof(p_hash->hash) == sizeof(su->sin.sin_addr.s_addr)) ; + p_hash->hash = su->sin.sin_addr.s_addr ; + p_hash->name = (const void*)&su->sin.sin_addr.s_addr ; + p_hash->name_len = sizeof(su->sin.sin_addr.s_addr) ; + p_hash->name_copy_len = sizeof(su->sin.sin_addr.s_addr) ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + symbol_hash_bytes(p_hash, (const void*)&su->sin6.sin6_addr, + sizeof(su->sin6.sin6_addr)) ; + break ; +#endif /* HAVE_IPV6 */ + default: + zabort("Unknown address family") ; + } ; +} ; diff --git a/lib/sockunion.h b/lib/sockunion.h index 0ee2d63b..eeae72d5 100644 --- a/lib/sockunion.h +++ b/lib/sockunion.h @@ -17,12 +17,18 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_SOCKUNION_H #define _ZEBRA_SOCKUNION_H +#include "zebra.h" +#include <stdbool.h> +#include "symtab.h" +#include "prefix.h" +#include "memory.h" + #if 0 union sockunion { struct sockinet { @@ -38,7 +44,10 @@ union sockunion { #define su_port su_si.si_port #endif /* 0 */ -union sockunion +typedef struct prefix* prefix ; + +typedef union sockunion* sockunion ; +union sockunion { struct sockaddr sa; struct sockaddr_in sin; @@ -47,13 +56,6 @@ union sockunion #endif /* HAVE_IPV6 */ }; -enum connect_result -{ - connect_error, - connect_success, - connect_in_progress -}; - /* Default address family. */ #ifdef HAVE_IPV6 #define AF_INET_UNION AF_INET6 @@ -61,9 +63,21 @@ enum connect_result #define AF_INET_UNION AF_INET #endif -/* Sockunion address string length. Same as INET6_ADDRSTRLEN. */ +/* Sockunion address string length. Accommodate either IPv4 or IPv6. */ #define SU_ADDRSTRLEN 46 +CONFIRM(SU_ADDRSTRLEN >= INET_ADDRSTRLEN) ; +#if HAVE_IPV6 +CONFIRM(SU_ADDRSTRLEN >= INET6_ADDRSTRLEN) ; +#endif + +/* Sockunion String Object */ +typedef struct sockunion_string sockunion_string_t ; +struct sockunion_string +{ + char str[SU_ADDRSTRLEN] ; +}; + /* Macro to set link local index to the IPv6 address. For KAME IPv6 stack. */ #ifdef KAME @@ -87,35 +101,47 @@ enum connect_result #define sockunion_family(X) (X)->sa.sa_family /* Prototypes. */ -extern int str2sockunion (const char *, union sockunion *); -extern const char *sockunion2str (union sockunion *, char *, size_t); -extern int sockunion_cmp (union sockunion *, union sockunion *); -extern int sockunion_same (union sockunion *, union sockunion *); - -extern char *sockunion_su2str (union sockunion *su); -extern union sockunion *sockunion_str2su (const char *str); -extern struct in_addr sockunion_get_in_addr (union sockunion *su); -extern int sockunion_accept (int sock, union sockunion *); -extern int sockunion_stream_socket (union sockunion *); -extern int sockopt_reuseaddr (int); -extern int sockopt_reuseport (int); -extern int sockunion_bind (int sock, union sockunion *, - unsigned short, union sockunion *); -extern int sockopt_ttl (int family, int sock, int ttl); -extern int sockopt_minttl (int family, int sock, int minttl); -extern int sockopt_cork (int sock, int onoff); -extern int sockunion_socket (union sockunion *su); -extern const char *inet_sutop (union sockunion *su, char *str); -extern enum connect_result sockunion_connect (int fd, union sockunion *su, - unsigned short port, - unsigned int); -extern union sockunion *sockunion_getsockname (int); -extern union sockunion *sockunion_getpeername (int); -extern union sockunion *sockunion_dup (union sockunion *); -extern void sockunion_free (union sockunion *); +extern sockunion sockunion_init_new(sockunion su, sa_family_t family) ; +extern int sockunion_get_len(sockunion su) ; +extern int sockunion_set_port(sockunion su, in_port_t port) ; +extern int str2sockunion (const char * str, sockunion su); +extern const char *sockunion2str (sockunion su, char* buf, size_t size); +extern sockunion_string_t sutoa(sockunion su) ; +extern int sockunion_cmp (sockunion su1, sockunion su2); +extern int sockunion_same (sockunion su1, sockunion su2); + +extern char* sockunion_su2str (sockunion su, enum MTYPE type) ; +extern sockunion sockunion_str2su (const char *str); +extern struct in_addr sockunion_get_in_addr (sockunion su); +extern int sockunion_accept (int sock_fd, sockunion su); +extern int sockunion_stream_socket (sockunion su); +extern int sockunion_bind (int sock_fd, sockunion su, + unsigned short port, bool any) ; +extern int sockunion_socket (sockunion su, int type, int protocol) ; +extern int sockunion_connect (int sock_fd, sockunion su, + unsigned short port, unsigned int ifindex) ; +extern int sockunion_listen(int sock_fd, int backlog) ; + +extern int sockunion_getsockfamily(int sock_fd) ; +extern int sockunion_getprotofamily(int sock_fd) ; +extern int sockunion_getsockname (int sock_fd, sockunion su); +extern int sockunion_getpeername (int sock_fd, sockunion su); +extern void sockunion_unmap_ipv4 (sockunion su) ; +extern void sockunion_map_ipv4 (sockunion su) ; + +extern sockunion sockunion_dup (sockunion src); +extern void sockunion_copy (sockunion dst, sockunion src) ; +extern void sockunion_free (sockunion su); + +extern sockunion sockunion_new_prefix(sockunion su, prefix p) ; +extern sockunion sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) ; +extern void sockunion_unset(sockunion* p_su) ; +extern void sockunion_set(sockunion* p_dst, sockunion su) ; +extern void sockunion_set_dup(sockunion* p_dst, sockunion su) ; +extern void sockunion_set_mov(sockunion* p_dst, sockunion* p_src) ; #ifndef HAVE_INET_NTOP -extern const char * inet_ntop (int family, const void *addrptr, +extern const char * inet_ntop (int family, const void *addrptr, char *strptr, size_t len); #endif /* HAVE_INET_NTOP */ @@ -127,4 +153,7 @@ extern int inet_pton (int family, const char *strptr, void *addrptr); extern int inet_aton (const char *cp, struct in_addr *inaddr); #endif +extern void +sockunion_symbol_hash(symbol_hash p_hash, const void* name) ; + #endif /* _ZEBRA_SOCKUNION_H */ diff --git a/lib/stream.c b/lib/stream.c index 983330ff..1fce7103 100644 --- a/lib/stream.c +++ b/lib/stream.c @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <stddef.h> @@ -29,7 +29,7 @@ #include "prefix.h" #include "log.h" -/* Tests whether a position is valid */ +/* Tests whether a position is valid */ #define GETP_VALID(S,G) \ ((G) <= (S)->endp) #define PUT_AT_VALID(S,G) GETP_VALID(S,G) @@ -92,24 +92,24 @@ stream_new (size_t size) struct stream *s; assert (size > 0); - + if (size == 0) { zlog_warn ("stream_new(): called with 0 size!"); return NULL; } - + s = XCALLOC (MTYPE_STREAM, sizeof (struct stream)); if (s == NULL) return s; - + if ( (s->data = XMALLOC (MTYPE_STREAM_DATA, size)) == NULL) { XFREE (MTYPE_STREAM, s); return NULL; } - + s->size = size; return s; } @@ -120,7 +120,7 @@ stream_free (struct stream *s) { if (!s) return; - + XFREE (MTYPE_STREAM_DATA, s->data); XFREE (MTYPE_STREAM, s); } @@ -129,15 +129,15 @@ struct stream * stream_copy (struct stream *new, struct stream *src) { STREAM_VERIFY_SANE (src); - + assert (new != NULL); assert (STREAM_SIZE(new) >= src->endp); new->endp = src->endp; new->getp = src->getp; - + memcpy (new->data, src->data, src->endp); - + return new; } @@ -154,30 +154,52 @@ stream_dup (struct stream *s) return (stream_copy (new, s)); } +struct stream * +stream_dup_pending (struct stream *s) +{ + struct stream *new; + size_t new_endp ; + + STREAM_VERIFY_SANE (s); + + new_endp = s->endp - s->getp ; + if ( (new = stream_new(new_endp)) == NULL) + return NULL; + + assert (STREAM_SIZE(new) >= new_endp); + + new->endp = new_endp ; + new->getp = 0 ; + + memcpy (new->data, s->data + s->getp, new_endp) ; + + return new ; +} + size_t stream_resize (struct stream *s, size_t newsize) { u_char *newdata; STREAM_VERIFY_SANE (s); - + newdata = XREALLOC (MTYPE_STREAM_DATA, s->data, newsize); - + if (newdata == NULL) return s->size; - + s->data = newdata; s->size = newsize; - + if (s->endp > s->size) s->endp = s->size; if (s->getp > s->endp) s->getp = s->endp; - + STREAM_VERIFY_SANE (s); - + return s->size; } - + size_t stream_get_getp (struct stream *s) { @@ -193,6 +215,13 @@ stream_get_endp (struct stream *s) } size_t +stream_get_left (struct stream *s) +{ + STREAM_VERIFY_SANE(s); + return s->endp - s->getp ; +} + +size_t stream_get_size (struct stream *s) { STREAM_VERIFY_SANE(s); @@ -204,7 +233,7 @@ void stream_set_getp (struct stream *s, size_t pos) { STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, pos)) { STREAM_BOUND_WARN (s, "set getp"); @@ -214,18 +243,32 @@ stream_set_getp (struct stream *s, size_t pos) s->getp = pos; } +void +stream_set_endp (struct stream *s, size_t pos) +{ + STREAM_VERIFY_SANE(s); + + if (!ENDP_VALID (s, pos)) + { + STREAM_BOUND_WARN (s, "set endp"); + return ; + } + + s->endp = pos; +} + /* Forward pointer. */ void stream_forward_getp (struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, s->getp + size)) { STREAM_BOUND_WARN (s, "seek getp"); return; } - + s->getp += size; } @@ -233,28 +276,28 @@ void stream_forward_endp (struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); - + if (!ENDP_VALID (s, s->endp + size)) { STREAM_BOUND_WARN (s, "seek endp"); return; } - + s->endp += size; } - + /* Copy from stream to destination. */ void stream_get (void *dst, struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); - + if (STREAM_READABLE(s) < size) { STREAM_BOUND_WARN (s, "get"); return; } - + memcpy (dst, s->data + s->getp, size); s->getp += size; } @@ -264,7 +307,7 @@ u_char stream_getc (struct stream *s) { u_char c; - + STREAM_VERIFY_SANE (s); if (STREAM_READABLE(s) < sizeof (u_char)) @@ -273,7 +316,7 @@ stream_getc (struct stream *s) return 0; } c = s->data[s->getp++]; - + return c; } @@ -284,15 +327,15 @@ stream_getc_from (struct stream *s, size_t from) u_char c; STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, from + sizeof (u_char))) { STREAM_BOUND_WARN (s, "get char"); return 0; } - + c = s->data[from]; - + return c; } @@ -309,10 +352,10 @@ stream_getw (struct stream *s) STREAM_BOUND_WARN (s, "get "); return 0; } - + w = s->data[s->getp++] << 8; w |= s->data[s->getp++]; - + return w; } @@ -323,16 +366,16 @@ stream_getw_from (struct stream *s, size_t from) u_int16_t w; STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, from + sizeof (u_int16_t))) { STREAM_BOUND_WARN (s, "get "); return 0; } - + w = s->data[from++] << 8; w |= s->data[from]; - + return w; } @@ -343,18 +386,18 @@ stream_getl_from (struct stream *s, size_t from) u_int32_t l; STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, from + sizeof (u_int32_t))) { STREAM_BOUND_WARN (s, "get long"); return 0; } - + l = s->data[from++] << 24; l |= s->data[from++] << 16; l |= s->data[from++] << 8; l |= s->data[from]; - + return l; } @@ -364,18 +407,18 @@ stream_getl (struct stream *s) u_int32_t l; STREAM_VERIFY_SANE(s); - + if (STREAM_READABLE (s) < sizeof (u_int32_t)) { STREAM_BOUND_WARN (s, "get long"); return 0; } - + l = s->data[s->getp++] << 24; l |= s->data[s->getp++] << 16; l |= s->data[s->getp++] << 8; l |= s->data[s->getp++]; - + return l; } @@ -386,22 +429,22 @@ stream_getq_from (struct stream *s, size_t from) uint64_t q; STREAM_VERIFY_SANE(s); - + if (!GETP_VALID (s, from + sizeof (uint64_t))) { STREAM_BOUND_WARN (s, "get quad"); return 0; } - + q = ((uint64_t) s->data[from++]) << 56; q |= ((uint64_t) s->data[from++]) << 48; q |= ((uint64_t) s->data[from++]) << 40; - q |= ((uint64_t) s->data[from++]) << 32; + q |= ((uint64_t) s->data[from++]) << 32; q |= ((uint64_t) s->data[from++]) << 24; q |= ((uint64_t) s->data[from++]) << 16; q |= ((uint64_t) s->data[from++]) << 8; q |= ((uint64_t) s->data[from++]); - + return q; } @@ -411,22 +454,22 @@ stream_getq (struct stream *s) uint64_t q; STREAM_VERIFY_SANE(s); - + if (STREAM_READABLE (s) < sizeof (uint64_t)) { STREAM_BOUND_WARN (s, "get quad"); return 0; } - + q = ((uint64_t) s->data[s->getp++]) << 56; q |= ((uint64_t) s->data[s->getp++]) << 48; q |= ((uint64_t) s->data[s->getp++]) << 40; - q |= ((uint64_t) s->data[s->getp++]) << 32; + q |= ((uint64_t) s->data[s->getp++]) << 32; q |= ((uint64_t) s->data[s->getp++]) << 24; q |= ((uint64_t) s->data[s->getp++]) << 16; q |= ((uint64_t) s->data[s->getp++]) << 8; q |= ((uint64_t) s->data[s->getp++]); - + return q; } @@ -437,19 +480,19 @@ stream_get_ipv4 (struct stream *s) u_int32_t l; STREAM_VERIFY_SANE(s); - + if (STREAM_READABLE (s) < sizeof(u_int32_t)) { STREAM_BOUND_WARN (s, "get ipv4"); return 0; } - + memcpy (&l, s->data + s->getp, sizeof(u_int32_t)); s->getp += sizeof(u_int32_t); return l; } - + /* Copy to source to stream. * * XXX: This uses CHECK_SIZE and hence has funny semantics -> Size will wrap @@ -463,15 +506,15 @@ stream_put (struct stream *s, const void *src, size_t size) /* XXX: CHECK_SIZE has strange semantics. It should be deprecated */ CHECK_SIZE(s, size); - + STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < size) { STREAM_BOUND_WARN (s, "put"); return; } - + if (src) memcpy (s->data + s->endp, src, size); else @@ -485,13 +528,13 @@ int stream_putc (struct stream *s, u_char c) { STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < sizeof(u_char)) { STREAM_BOUND_WARN (s, "put"); return 0; } - + s->data[s->endp++] = c; return sizeof (u_char); } @@ -507,7 +550,7 @@ stream_putw (struct stream *s, u_int16_t w) STREAM_BOUND_WARN (s, "put"); return 0; } - + s->data[s->endp++] = (u_char)(w >> 8); s->data[s->endp++] = (u_char) w; @@ -525,7 +568,7 @@ stream_putl (struct stream *s, u_int32_t l) STREAM_BOUND_WARN (s, "put"); return 0; } - + s->data[s->endp++] = (u_char)(l >> 24); s->data[s->endp++] = (u_char)(l >> 16); s->data[s->endp++] = (u_char)(l >> 8); @@ -545,7 +588,7 @@ stream_putq (struct stream *s, uint64_t q) STREAM_BOUND_WARN (s, "put quad"); return 0; } - + s->data[s->endp++] = (u_char)(q >> 56); s->data[s->endp++] = (u_char)(q >> 48); s->data[s->endp++] = (u_char)(q >> 40); @@ -562,15 +605,15 @@ int stream_putc_at (struct stream *s, size_t putp, u_char c) { STREAM_VERIFY_SANE(s); - + if (!PUT_AT_VALID (s, putp + sizeof (u_char))) { STREAM_BOUND_WARN (s, "put"); return 0; } - + s->data[putp] = c; - + return 1; } @@ -578,16 +621,16 @@ int stream_putw_at (struct stream *s, size_t putp, u_int16_t w) { STREAM_VERIFY_SANE(s); - + if (!PUT_AT_VALID (s, putp + sizeof (u_int16_t))) { STREAM_BOUND_WARN (s, "put"); return 0; } - + s->data[putp] = (u_char)(w >> 8); s->data[putp + 1] = (u_char) w; - + return 2; } @@ -595,7 +638,7 @@ int stream_putl_at (struct stream *s, size_t putp, u_int32_t l) { STREAM_VERIFY_SANE(s); - + if (!PUT_AT_VALID (s, putp + sizeof (u_int32_t))) { STREAM_BOUND_WARN (s, "put"); @@ -605,7 +648,7 @@ stream_putl_at (struct stream *s, size_t putp, u_int32_t l) s->data[putp + 1] = (u_char)(l >> 16); s->data[putp + 2] = (u_char)(l >> 8); s->data[putp + 3] = (u_char)l; - + return 4; } @@ -613,7 +656,7 @@ int stream_putq_at (struct stream *s, size_t putp, uint64_t q) { STREAM_VERIFY_SANE(s); - + if (!PUT_AT_VALID (s, putp + sizeof (uint64_t))) { STREAM_BOUND_WARN (s, "put"); @@ -627,7 +670,7 @@ stream_putq_at (struct stream *s, size_t putp, uint64_t q) s->data[putp + 5] = (u_char)(q >> 16); s->data[putp + 6] = (u_char)(q >> 8); s->data[putp + 7] = (u_char)q; - + return 8; } @@ -636,7 +679,7 @@ int stream_put_ipv4 (struct stream *s, u_int32_t l) { STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < sizeof (u_int32_t)) { STREAM_BOUND_WARN (s, "put"); @@ -653,7 +696,7 @@ int stream_put_in_addr (struct stream *s, struct in_addr *addr) { STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < sizeof (u_int32_t)) { STREAM_BOUND_WARN (s, "put"); @@ -671,24 +714,24 @@ int stream_put_prefix (struct stream *s, struct prefix *p) { size_t psize; - + STREAM_VERIFY_SANE(s); - + psize = PSIZE (p->prefixlen); - + if (STREAM_WRITEABLE (s) < psize) { STREAM_BOUND_WARN (s, "put"); return 0; } - + stream_putc (s, p->prefixlen); memcpy (s->data + s->endp, &p->u.prefix, psize); s->endp += psize; - + return psize; } - + /* Read size from fd. */ int stream_read (struct stream *s, int fd, size_t size) @@ -696,18 +739,18 @@ stream_read (struct stream *s, int fd, size_t size) int nbytes; STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < size) { STREAM_BOUND_WARN (s, "put"); return 0; } - + nbytes = readn (fd, s->data + s->endp, size); if (nbytes > 0) s->endp += nbytes; - + return nbytes; } @@ -717,15 +760,15 @@ stream_read_unblock (struct stream *s, int fd, size_t size) { int nbytes; int val; - + STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < size) { STREAM_BOUND_WARN (s, "put"); return 0; } - + val = fcntl (fd, F_GETFL, 0); fcntl (fd, F_SETFL, val|O_NONBLOCK); nbytes = read (fd, s->data + s->endp, size); @@ -733,17 +776,67 @@ stream_read_unblock (struct stream *s, int fd, size_t size) if (nbytes > 0) s->endp += nbytes; - + return nbytes; } +/*------------------------------------------------------------------------------ + * Read up to size bytes into stream -- assuming non-blocking socket. + * + * Loops internally if gets EINTR -- so if does not read everything asked for, + * that must be because the read would otherwise block. + * + * Returns: 0..size -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met + * + * NB: if asks for zero bytes, will return 0 or error (if any). + */ +int +stream_read_nonblock(struct stream *s, int fd, size_t size) +{ + int ret ; + int want = size ; + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE (s) < size) + { + STREAM_BOUND_WARN (s, "put"); + return 0; + } + + do + { + ret = read(fd, s->data + s->endp, want); + + if (ret > 0) + { + s->endp += ret ; + want -= ret ; + } + else if (ret == 0) + return (want == 0) ? 0 : -2 ; + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; + } ; + } while (want > 0) ; + + return size - want ; +} + ssize_t stream_read_try(struct stream *s, int fd, size_t size) { ssize_t nbytes; STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN (s, "put"); @@ -760,21 +853,21 @@ stream_read_try(struct stream *s, int fd, size_t size) /* Error: was it transient (return -2) or fatal (return -1)? */ if (ERRNO_IO_RETRY(errno)) return -2; - zlog_warn("%s: read failed on fd %d: %s", __func__, fd, safe_strerror(errno)); + zlog_warn("%s: read failed on fd %d: %s", __func__, fd, errtoa(errno, 0).str); return -1; } /* Read up to size bytes into the stream from the fd, using recvmsgfrom * whose arguments match the remaining arguments to this function */ -ssize_t +ssize_t stream_recvfrom (struct stream *s, int fd, size_t size, int flags, - struct sockaddr *from, socklen_t *fromlen) + struct sockaddr *from, socklen_t *fromlen) { ssize_t nbytes; STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN (s, "put"); @@ -783,7 +876,7 @@ stream_recvfrom (struct stream *s, int fd, size_t size, int flags, return -1; } - if ((nbytes = recvfrom (fd, s->data + s->endp, size, + if ((nbytes = recvfrom (fd, s->data + s->endp, size, flags, from, fromlen)) >= 0) { s->endp += nbytes; @@ -792,7 +885,7 @@ stream_recvfrom (struct stream *s, int fd, size_t size, int flags, /* Error: was it transient (return -2) or fatal (return -1)? */ if (ERRNO_IO_RETRY(errno)) return -2; - zlog_warn("%s: read failed on fd %d: %s", __func__, fd, safe_strerror(errno)); + zlog_warn("%s: read failed on fd %d: %s", __func__, fd, errtoa(errno, 0).str); return -1; } @@ -802,15 +895,15 @@ stream_recvfrom (struct stream *s, int fd, size_t size, int flags, * Stream need not be empty. */ ssize_t -stream_recvmsg (struct stream *s, int fd, struct msghdr *msgh, int flags, +stream_recvmsg (struct stream *s, int fd, struct msghdr *msgh, int flags, size_t size) { int nbytes; struct iovec *iov; - + STREAM_VERIFY_SANE(s); - assert (msgh->msg_iovlen > 0); - + assert (msgh->msg_iovlen > 0); + if (STREAM_WRITEABLE (s) < size) { STREAM_BOUND_WARN (s, "put"); @@ -818,19 +911,19 @@ stream_recvmsg (struct stream *s, int fd, struct msghdr *msgh, int flags, to hold the desired data! */ return -1; } - + iov = &(msgh->msg_iov[0]); iov->iov_base = (s->data + s->endp); iov->iov_len = size; - + nbytes = recvmsg (fd, msgh, flags); - + if (nbytes > 0) s->endp += nbytes; - + return nbytes; } - + /* Write data to buffer. */ size_t stream_write (struct stream *s, const void *ptr, size_t size) @@ -839,20 +932,20 @@ stream_write (struct stream *s, const void *ptr, size_t size) CHECK_SIZE(s, size); STREAM_VERIFY_SANE(s); - + if (STREAM_WRITEABLE (s) < size) { STREAM_BOUND_WARN (s, "put"); return 0; } - + memcpy (s->data + s->endp, ptr, size); s->endp += size; return size; } -/* Return current read pointer. +/* Return current read pointer. * DEPRECATED! * Use stream_get_pnt_to if you must, but decoding streams properly * is preferred @@ -882,26 +975,57 @@ stream_reset (struct stream *s) s->getp = s->endp = 0; } +/* Number of bytes pending to be written */ +int +stream_pending(struct stream* s) +{ + STREAM_VERIFY_SANE(s); + + return s->endp - s->getp ; +} + /* Write stream contens to the file discriptor. */ int -stream_flush (struct stream *s, int fd) +stream_flush (struct stream* s, int fd) { int nbytes; - + STREAM_VERIFY_SANE(s); - + nbytes = write (fd, s->data + s->getp, s->endp - s->getp); - + return nbytes; } - + +/*------------------------------------------------------------------------------ + * Transfer contents of stream to given buffer and reset stream. + * + * Transfers *entire* stream buffer. + * + * Returns pointer to next byte in given buffer + */ +void* +stream_transfer(void* p, struct stream* s, void* limit) +{ + size_t have = s->endp ; + + STREAM_VERIFY_SANE(s); + assert(((uint8_t*)p + have) <= (uint8_t*)limit) ; + + memcpy(p, s->data, have) ; + + s->getp = s->endp = 0; + + return (uint8_t*)p + have ; +} ; + /* Stream first in first out queue. */ struct stream_fifo * stream_fifo_new (void) { struct stream_fifo *new; - + new = XCALLOC (MTYPE_STREAM_FIFO, sizeof (struct stream_fifo)); return new; } @@ -914,7 +1038,7 @@ stream_fifo_push (struct stream_fifo *fifo, struct stream *s) fifo->tail->next = s; else fifo->head = s; - + fifo->tail = s; fifo->count++; @@ -925,20 +1049,20 @@ struct stream * stream_fifo_pop (struct stream_fifo *fifo) { struct stream *s; - - s = fifo->head; + + s = fifo->head; if (s) - { + { fifo->head = s->next; if (fifo->head == NULL) fifo->tail = NULL; - } - fifo->count--; + fifo->count--; + } - return s; + return s; } /* Return first fifo entry. */ @@ -949,6 +1073,13 @@ stream_fifo_head (struct stream_fifo *fifo) } void +stream_fifo_reset (struct stream_fifo *fifo) +{ + fifo->head = fifo->tail = NULL; + fifo->count = 0; +} + +void stream_fifo_clean (struct stream_fifo *fifo) { struct stream *s; diff --git a/lib/stream.h b/lib/stream.h index 3e4ba7b4..e7303652 100644 --- a/lib/stream.h +++ b/lib/stream.h @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_STREAM_H @@ -78,7 +78,7 @@ * The stream is empty from endp to size. Without adjusting getp, there are * still endp-getp bytes of valid data to be read from the stream. * - * Methods are provided to get and put to/from the stream, as well as + * Methods are provided to get and put to/from the stream, as well as * retrieve the values of the 3 markers and manipulate the getp marker. * * Note: @@ -98,7 +98,7 @@ struct stream /* Remainder is ***private*** to stream * direct access is frowned upon! - * Use the appropriate functions/macros + * Use the appropriate functions/macros */ size_t getp; /* next get position */ size_t endp; /* last valid data position */ @@ -127,7 +127,7 @@ struct stream_fifo #define STREAM_DATA(S) ((S)->data) #define STREAM_REMAIN(S) STREAM_WRITEABLE((S)) -/* Stream prototypes. +/* Stream prototypes. * For stream_{put,get}S, the S suffix mean: * * c: character (unsigned byte) @@ -139,13 +139,16 @@ extern struct stream *stream_new (size_t); extern void stream_free (struct stream *); extern struct stream * stream_copy (struct stream *, struct stream *src); extern struct stream *stream_dup (struct stream *); +extern struct stream* stream_dup_pending(struct stream*) ; extern size_t stream_resize (struct stream *, size_t); extern size_t stream_get_getp (struct stream *); extern size_t stream_get_endp (struct stream *); +extern size_t stream_get_left (struct stream *s) ; extern size_t stream_get_size (struct stream *); extern u_char *stream_get_data (struct stream *); extern void stream_set_getp (struct stream *, size_t); +extern void stream_set_endp (struct stream *, size_t); extern void stream_forward_getp (struct stream *, size_t); extern void stream_forward_endp (struct stream *, size_t); @@ -177,7 +180,7 @@ extern u_int32_t stream_get_ipv4 (struct stream *); #undef stream_read #undef stream_write -/* Deprecated: assumes blocking I/O. Will be removed. +/* Deprecated: assumes blocking I/O. Will be removed. Use stream_read_try instead. */ extern int stream_read (struct stream *, int, size_t); @@ -185,6 +188,8 @@ extern int stream_read (struct stream *, int, size_t); Will be removed. Use stream_read_try instead. */ extern int stream_read_unblock (struct stream *, int, size_t); +extern int stream_read_nonblock (struct stream *s, int fd, size_t size) ; + /* Read up to size bytes into the stream. Return code: >0: number of bytes read @@ -197,8 +202,8 @@ extern ssize_t stream_read_try(struct stream *s, int fd, size_t size); extern ssize_t stream_recvmsg (struct stream *s, int fd, struct msghdr *, int flags, size_t size); -extern ssize_t stream_recvfrom (struct stream *s, int fd, size_t len, - int flags, struct sockaddr *from, +extern ssize_t stream_recvfrom (struct stream *s, int fd, size_t len, + int flags, struct sockaddr *from, socklen_t *fromlen); extern size_t stream_write (struct stream *, const void *, size_t); @@ -207,6 +212,10 @@ extern void stream_reset (struct stream *); extern int stream_flush (struct stream *, int); extern int stream_empty (struct stream *); /* is the stream empty? */ +extern int stream_pending(struct stream* s) ; +extern int stream_flush_try(struct stream* s, int fd) ; +extern void* stream_transfer(void* p, struct stream* s, void* limit) ; + /* deprecated */ extern u_char *stream_pnt (struct stream *); @@ -215,6 +224,7 @@ extern struct stream_fifo *stream_fifo_new (void); extern void stream_fifo_push (struct stream_fifo *fifo, struct stream *s); extern struct stream *stream_fifo_pop (struct stream_fifo *fifo); extern struct stream *stream_fifo_head (struct stream_fifo *fifo); +extern void stream_fifo_reset (struct stream_fifo *fifo); extern void stream_fifo_clean (struct stream_fifo *fifo); extern void stream_fifo_free (struct stream_fifo *fifo); diff --git a/lib/symtab.c b/lib/symtab.c new file mode 100644 index 00000000..57a49396 --- /dev/null +++ b/lib/symtab.c @@ -0,0 +1,1186 @@ +/* Symbol Table data structure -- functions + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <zebra.h> +#include <stddef.h> + +#include "symtab.h" +#include "memory.h" + +/* A symbol table maps symbol "names" to symbol values and, for each symbol, + * has two ways of keeping track of references to the symbol. + * + * The symbol name can be an arbitrary collection of bytes, or a string. All + * names in a symbol table are unique. + * + * The symbol value is a void* -- whose contents are no concern of this code. + * + * A symbol table comprises: + * + * * symbol table structure -- containing all "red-tape" + * * array of chain-bases -- for the hash table + * * symbol entries -- each containing name and value of a symbol + * + * The symbol table structure may be statically allocated, embedded in another + * structure, or allocated dynamically. In any case the symbol table operations + * require the address of the symbol table structure -- see typedef for + * symbol_table. + * + * A symbol table may point to its "parent" -- which could be an enclosing + * structure, or any other higher level data. The symbol table code does not + * use or need this -- it is for the convenience of the caller. + * + * A symbol table structure which is zeroised is implicitly an empty symbol + * table, using the default symbol name type -- a null terminated string. + * + * Each Symbol Table requires a hash function, which takes a pointer to + * whatever and returns a 32-bit hash value and a canonical form of the + * symbol "name". When a new symbol is created the canonical form of the name + * is copied to the symbol entry. + * + * The array of chain-bases is dynamically allocated and will grow to maintain + * an approximate given maximum number of symbols per chain base. This density + * is set when the symbol table is initialised. The array does not + * automatically reduce in size. + * + * The number of chain bases is always odd. The hash function returns a 32-bit + * unsigned value, which is mapped to the chain bases modulo their number. + * Since that is odd, it is co-prime with the hash, so contributes to the hash + * process. + * + * Each symbol in the table has a dynamically allocated entry, which includes: + * + * * symbol value -- void* + * * symbol name + * * count of references + * * list of references + * + * Symbols may have the value NULL. Deleting a symbol is not the same as + * setting its value to NULL. If the value is set NULL and later set to + * something else, all references to the symbol will see the new value. If the + * symbol is deleted, all references will see its value set to NULL, but if a + * new symbol with the same name is later created, all references to the old + * symbol are unchanged, and continue to see the (orphan) NULL. + * + * Keeping track of references is important because it ensures that symbols + * can be deleted without leaving dangling references. When a symbol is + * deleted, it is removed from the symbol table and its value is set to NULL. + * If there are no references to the symbol, the symbol entry is released. + * If there are references to the symbol, it is preserved (as an orphan) until + * all references are unset. The value of an orphaned symbol should not be + * changed (but may be unset, since it is already unset). + * + * There are two, parallel mechanisms for keeping track of references to a + * symbol: + * + * 1. reference count -- this is for when all that is required is a pointer + * to the symbol, ie: + * + * * ptr_to_symbol = symbol_inc_ref(sym) -- set reference & count up + * * ptr_to_symbol = symbol_dec_ref(sym) -- unset reference & count down + * + * 2. list of references -- this is for when it is useful to visit all + * references to a given symbol, for example when the value of the symbol + * is set and all users of the symbol need to adjust to the new state. + * + * A symbol reference contains a pointer to the symbol, and lives on the + * list of references to the symbol. When the value is set, a call-back + * associated with the table is called (if it is set). That call-back may + * walk the list of references to the symbol. Each reference contains a + * pointer and a pointer/long int value, which may be used to identify the + * reference. + * + * A symbol reference may be statically allocated, embedded in another + * structure, or allocated dynamically. In any case the symbol reference + * operations require the address of the symbol reference structure -- see + * typedef for symbol_ref. + * + * Symbol references are the responsibility of of the owner of the reference, + * who must look after setting, unsetting and (if required) releasing them. + * + * It may seem profligate to have two mechanisms. However, it is simpler than + * having two types of symbol, or two types of symbol table. It may also be + * useful to have both simple references and references for dealing with + * value changes. Suppose, for instance, that the value of a symbol has a + * pointer to the symbol -- so that the value of the symbol can refer back to + * its name, for example. This requires only a simple reference, even if + * more generally a list of references is required. + * + * A symbol table can be walked to visit all symbols, and there is support for + * extracting a vector of all symbols which satisfy a given criterion. + */ + +static void symbol_extend_bases(symbol_table table) ; +static void symbol_free(symbol sym) ; + +/* Return true iff there are no references to the given symbol */ +inline static int +symbol_no_references(symbol sym) +{ + return (sym->ref_count == 0) && (sym->ref_list == NULL) ; +} ; + +/* If symbol is an orphan with no references/bookmarks, free it. + * + * NB: if symbol is an orphan, then it implicitly has a NULL value, because + * to become an orphan it must have been deleted, which unsets the value. + */ +inline static void +symbol_free_if_redundant(symbol sym) +{ + if ((sym->table == NULL) && symbol_no_references(sym)) + symbol_free(sym) ; +} ; + +/* Return chain base for given hash value. */ +inline static symbol* +symbol_base(symbol_table table, u_int32_t hash) +{ + return &table->bases[hash % table->base_count] ; +} ; + +/* Initialise a new symbol table -- allocate if required. + * + * table -- address of table to initialise. NULL -> allocate. + * NB: if allocated, it is the caller's responsibility to free. + * + * parent -- address of some parent or other higher level data structure. + * This is not used by the symbol table code and may be NULL if + * the caller has no use for it. + * + * bases -- number of list bases to start the symbol table at. + * Symbol table grows as required, but can set initial size if + * have some expectations and wish to avoid growth steps. + * + * density -- %-age of entries/bases. 0 => use default. + * + * hash_function + * -- function to fill in symbol_hash from a given "name". + * see: description of struct symbol_hash and default function, + * symbol_hash_string, below. + * NULL => the names in this symbol table are null terminated + * strings, so use symbol_hash_string. + * + * value_call_back + * -- function to be called when the symbol value is set or unset. + * (Or the symbol is about to be deleted -- which starts by + * unsetting it). + * + * The function is passed the new state of the symbol and its + * previous value. + * + * The value of the symbol is a pointer to some data. If the + * data changes symbol_set_value() must be called if its + * address changes and may be called even if its address doesn't + * change. + * + * In any event, value_call_back is called when symbol_set_value() + * is called -- except where the symbol is being set to NULL and + * the value is already NULL. + * + * During the call-back the symbol may be set again or unset, + * which will cause the call-back to be called inside itself. + * Also, references may be set or unset. + * + * In the call-back the reference list may be walked to signal + * to all holders of the reference that something has changed. + * + * NB: A completely zeroized symbol_table structure is a valid, empty + * symbol table. The first time it is used it will be set up to default + * state -- in particular: symbol names are null terminated strings (a + * state which *cannot* then be changed). + * + * NB: when this is used to re-initialising an existing symbol table structure, + * any existing chain base array, symbols and symbol references are simply + * discarded -- which will leak memory and is probably a mistake. + */ +symbol_table +symbol_table_init_new(symbol_table table, + void* parent, + unsigned int base_count, + unsigned int density, + symbol_hash_function* hash_function, + symbol_call_back_function* value_call_back) +{ + assert(base_count <= SYMBOL_TABLE_BASES_MAX) ; + + if (table == NULL) + table = XCALLOC(MTYPE_SYMBOL_TABLE, sizeof (struct symbol_table)) ; + + table->parent = parent ; + + table->bases = NULL ; /* Allocated when required */ + table->base_count = base_count ; + + table->entry_count = 0 ; + table->extend_thresh = density ; /* Fixed up when required */ + + table->hash_function = hash_function ; + symbol_table_set_value_call_back(table, value_call_back) ; + + return table ; +} ; + +/* Set "parent" of symbol table. */ +void +symbol_table_set_parent(symbol_table table, void* parent) +{ + table->parent = parent ; +} ; + +/* Get "parent" of symbol table. */ +void* +symbol_table_get_parent(symbol_table table) +{ + return table->parent ; +} ; + +/* Set the value_call_back */ +void +symbol_table_set_value_call_back(symbol_table table, + symbol_call_back_function* value_call_back) +{ + table->value_call_back = value_call_back ; +} ; + +/* Create and set new chain bases and threshold for next extension. */ +/* */ +/* Ensures that the base count is at least the minimum and is odd, */ +/* and returns the value set. */ +static unsigned int +symbol_table_new_bases(symbol_table table, + unsigned int new_base_count, float density) +{ + if (new_base_count < SYMBOL_TABLE_BASES_MIN) + new_base_count = SYMBOL_TABLE_BASES_MIN ; + new_base_count |= 1 ; + + table->bases = XCALLOC(MTYPE_SYMBOL_BASES, new_base_count * sizeof(symbol)) ; + table->extend_thresh = new_base_count * density ; + return table->base_count = new_base_count ; +} ; + +/* Setup symbol table body for use. + * + * Used for "lazy" allocation of chain bases and allows symbol_lookup + * to operate on a completely zeroized symbol_table structure. + */ +static void +symbol_table_setup(symbol_table table) +{ + float density ; + + /* If density was set explicitly, extend_thresh entry is a %age. */ + + if (table->extend_thresh != 0) + density = (float)table->extend_thresh / (float)100 ; + else + density = (float)2 ; /* Default density */ + + /* Initialise the chain bases -- enforces minimum base_count and odd-ness */ + symbol_table_new_bases(table, table->base_count, density) ; + + /* make default hash_function explicit. */ + if (table->hash_function == NULL) + table->hash_function = (symbol_hash_function*)symbol_hash_string ; +} ; + +/* Reset symbol table. + * + * Free the symbol table body, and free the symbol table structure or reset it. + * + * Return NULL if frees symbol table structure, otherwise the address of same. + * + * NB: must only be done when the table is empty -- see assertion ! + */ +symbol_table +symbol_table_reset(symbol_table table, int free_structure) +{ + if (table== NULL) + return NULL ; /* allow for already freed table */ + + assert(table->entry_count == 0) ; + + if (table->bases) + XFREE(MTYPE_SYMBOL_BASES, table->bases); + + if (free_structure) + { + XFREE(MTYPE_VECTOR, table) ; + return NULL ; + } + else + return memset(table, 0, sizeof(struct symbol_table)) ; +} ; + +/* Remove symbol from its symbol table (if any). */ + +static void +symbol_remove(symbol sym) +{ + symbol_table table ; + symbol* base ; + symbol prev ; + + table = sym->table ; + if (table != NULL) /* Deleted symbols have no parent table. */ + { + assert(table->entry_count != 0) ; + + base = symbol_base(table, sym->hash) ; + if (*base == sym) + *base = sym->next ; + else + { + prev = *base ; + while (prev->next != sym) + prev = prev->next ; + prev->next = sym->next ; + } ; + + sym->table = NULL ; /* Symbol is now an orphan. */ + --table->entry_count ; + } ; +} ; + +/* Free symbol, removing it from the symbol table. + * + * NB: the value and all references MUST already have been unset, because: + * + * * any value may well need to be released, and have no idea how to do + * that here. + * + * * similarly, references may need to be released and should not, in + * any case, be left dangling. + */ +static void +symbol_free(symbol sym) +{ + assert((sym->value == NULL) && symbol_no_references(sym)) ; + + symbol_remove(sym) ; /* Remove from table, if any. */ + + XFREE(MTYPE_SYMBOL, sym) ; +} ; + +/* Ream out symbols. + * + * Delete symbols -- but do not invoke the value_call_back. + * + * When the table is (or becomes) empty, the chain bases are freed, and the + * structure freed or reset (depending on the free_structure argument). + * + * This is intended for use when the symbol table is being destroyed, and all + * references have been, or will be unset. + * + * Returns the value of the next non-NULL symbol (if any). So may be used, for + * example: + * + * xxxx* val ; + * ... + * while ((val = symbol_table_ream(table, free_structure)) != NULL) + * { + * ... do what's required to release the value ... + * } + * + * Noting that the symbol may already have been released when its value is + * returned. (If the symbol is required when the value is released, then the + * value should hold a simple reference to the symbol.) + * + * Returns NULL when the table is empty. + * + * Symbols which have one or more references when they are deleted are left as + * orphans, which will be freed when all their references are unset. + * + * NB: do NOT attempt to do anything else with the symbol table once reaming + * has started. + * + * NB: it is the caller's responsibility to unset all references and release + * any that need to be released -- either before or after this operation. + */ +void* +symbol_table_ream(symbol_table table, int free_structure) +{ + void* value ; + symbol sym ; + unsigned int i ; + + /* There are no actual bases until they have been allocated. */ + i = (table->bases != NULL) ? table->base_count : 0 ; + + while (i--) + { + while ((sym = table->bases[i]) != NULL) + { + assert(table->entry_count != 0) ; + + /* the following is effectively symbol_delete, but avoids the */ + /* value_call_back and returns only if the value is not NULL. */ + + table->bases[i] = sym->next ; /* remove from table */ + --table->entry_count ; /* count down */ + + sym->table = NULL ; /* orphan symbol */ + value = sym->value ; /* pick up value. */ + sym->value = NULL ; /* and set symbol undefined */ + + if (symbol_no_references(sym)) + symbol_free(sym) ; /* not in table, no value, no references */ + + if (value != NULL) + { + table->base_count = i + 1 ; /* where we've got to */ + return value ; /* <<< RETURN: caller must deal with value */ + } ; + } ; + } ; + + symbol_table_reset(table, free_structure) ; + /* asserts(table->entry_count == 0) */ + return NULL ; +} ; + +/* Look-up name in given symbol table. Add if required. + * + * Returns NULL if not found and not required to add. + * + * NB: the name argument is passed to the symbol table's hash function. That + * function is required to return with a 32-bit hash + * + * NB: if required to add, the caller cannot distinguish between a symbol + * which did not previously exist, and one which did exist but had no + * value and no references. Where that distinction matters, it is + * necessary to do an extra lookup. + */ +symbol +symbol_lookup(symbol_table table, const void* name, int add) +{ + struct symbol* this ; + struct symbol** base ; + struct symbol_hash hash ; + + assert(table != NULL) ; + if (table->bases == NULL) + symbol_table_setup(table) ; /* Lazy allocation of chain bases etc. */ + + table->hash_function(&hash, name) ; + + base = symbol_base(table, hash.hash) ; + this = *base ; + while (this) + { + if ((this->hash == hash.hash) + && (this->name_len == hash.name_len) + && (memcmp(this->name, hash.name, this->name_len) == 0)) + return this ; + this = this->next ; + } ; + + /* Not found -- quit now if not required to add */ + if (!add) return NULL ; + + /* Adding: first, carve a new, empty symbol entry */ + this = XCALLOC(MTYPE_SYMBOL, sizeof(struct symbol) + hash.name_copy_len) ; + + this->table = table ; + this->value = NULL ; + this->ref_list = NULL ; + this->ref_count = 0 ; + this->hash = hash.hash ; + this->name_len = hash.name_len ; + memcpy(this->name, hash.name, hash.name_copy_len) ; + + /* Second, if required, extend the array of list bases. We extend if */ + /* we have a collision *and* we exceed threshold of number of entries. */ + if ((*base != NULL) && (table->entry_count > table->extend_thresh)) + { + symbol_extend_bases(table) ; + base = symbol_base(table, hash.hash) ; + } ; + + /* Third, chain in the new entry, count it in and return */ + this->next = *base ; + *base = this ; + + ++table->entry_count ; + + return this ; +} ; + +/* Delete symbol. + * + * The first effect of this is to set the symbol value to NULL, which may + * trigger a value_call_back etc. + * + * Then the symbol is removed from the table (and the symbol becomes an orphan). + * + * Then, if there are no (remaining) references the symbol is freed. Otherwise + * the symbol entry remains in existence until there are no more references + * (at which point it will finally be destroyed). + * + * Returns the last value of the symbol -- which may itself need to be + * destroyed -- noting that the symbol may already have been released. (If the + * symbol is required when the value is released, then the value should hold a + * simple reference to the symbol.) + * + * NB: the effect of deleting a symbol is to leave all remaining references + * pointing at an NULL value, orphaned symbol. + * + * If a new symbol is created with the same name, that will be a + * completely different symbol -- references to the old symbol will + * continue to be to the vestigial NULL value. + * + * This is different from setting the symbol value to NULL and later + * giving it a new value. + * + * NB: orphan symbols can be deleted. The effect is to free the symbol if + * possible. + */ +void* +symbol_delete(symbol sym) +{ + void* old_value = symbol_unset_value(sym) ; + + if (symbol_no_references(sym)) + symbol_free(sym) ; /* free symbol now if no references */ + else + symbol_remove(sym) ; /* else just remove it from the table -- will be */ + /* freed when all references are unset. */ + return old_value ; +} ; + +/* The hash functions provided here use CRC32 as a hash. + * + * CRC32 is not intended as a hash function, and is not a perfect one. + * However it is fast -- requiring a few simple operations per byte. Taken + * with the secondary effect of using the hash produced modulo an odd number, + * experience suggests this is sufficient. + */ + +static u_int32_t crc_table[] ; + +/* Standard symbol string hash function. */ +void +symbol_hash_string(symbol_hash p_hash, const char* string) { + u_int32_t h = 0 ; + const char* p = string ; + + while (*p != 0) + h = crc_table[(h & 0xFF) ^ (u_int8_t)*p++] ^ (h >> 8) ; + + assert((p - string) < 0xFFFF) ; + + p_hash->hash = h ; + p_hash->name = string ; + p_hash->name_len = (p - string) ; + p_hash->name_copy_len = p_hash->name_len + 1 ; +} ; + +/* Standard symbol byte vector hash function. */ +void +symbol_hash_bytes(symbol_hash p_hash, const void* bytes, size_t len) { + assert(len < 0xFFFF) ; + + u_int32_t h = len ; /* So strings of zeros don't CRC the same ! */ + const u_int8_t* p = bytes ; + const u_int8_t* e = p + len ; + + while (p < e) + h = crc_table[(h & 0xFF) ^ *p++] ^ (h >> 8) ; + + p_hash->hash = h ; + p_hash->name = (const void*)bytes ; + p_hash->name_len = len ; + p_hash->name_copy_len = len ; +} ; + +/* Extend the array of list bases. */ +static void +symbol_extend_bases(symbol_table table) +{ + symbol this ; + symbol next ; + symbol* old_bases ; + symbol* new_bases ; + symbol* base ; + unsigned int new_base_count ; + unsigned int old_base_count ; + + old_bases = table->bases ; + old_base_count = table->base_count ; + + assert((old_bases != NULL) && (old_base_count != 0)) ; + + /* TODO: should look out for overflowing base_count and requiring */ + /* impossible amounts of memory ?! */ + + new_base_count = (table->base_count | 1) - 1 ; /* trim enforced odd-ness */ + + if (new_base_count <= SYMBOL_TABLE_BASES_DOUBLE_MAX) + new_base_count *= 2 ; + else + new_base_count += SYMBOL_TABLE_BASES_DOUBLE_MAX ; + + new_base_count = symbol_table_new_bases(table, new_base_count, + (float)table->extend_thresh / table->base_count) ; + + /* Rehome everything on the new chain bases. */ + new_bases = table->bases ; + while (old_base_count--) + { + next = old_bases[old_base_count] ; + while (next != NULL) + { + this = next ; + next = this->next ; + base = &new_bases[this->hash % new_base_count] ; + this->next = *base ; + *base = this ; + } ; + } ; + + /* Release the old chain bases, and we're done. */ + XFREE(MTYPE_SYMBOL_BASES, old_bases) ; +} ; + +/*============================================================================== + * Reference count handling. + * + * symbol_inc_ref(sym) -- declared Inline + * symbol_dec_ref(sym) -- declared Inline + */ + +/* Zeroise the reference count.*/ + +symbol +symbol_zero_ref(symbol sym, int force) +{ + assert((sym->ref_count == 1) || force) ; + + sym->ref_count = 0 ; + symbol_free_if_redundant(sym) ; + + return NULL ; +} ; + +/*============================================================================== + * Reference list handling. + * + * References are added at the head of the list -- which is significant when + * adding references during a symbol reference walk. + */ + +/* Insert symbol_ref at head of symbol's list of references. */ +static inline void +symbol_add_ref(symbol sym, symbol_ref ref) +{ + symbol_ref next = sym->ref_list ; + sym->ref_list = ref ; + if (next) + next->prev = ref ; + ref->next = next ; + ref->prev = (void*)sym ; /* marker for first on list */ +} ; + +/* Clip symbol_ref from symbol's list of references. + * + * If symbol_ref has already been deleted the prev pointer is NULL, and this + * function copes -- and does not need the symbol to be valid (sym may be NULL). + */ +static inline void +symbol_del_ref(symbol sym, symbol_ref ref) +{ + symbol_ref prev = ref->prev ; + symbol_ref next = ref->next ; + + if (prev != NULL) + { + if (prev == (void*)sym) + { + assert(sym->ref_list == ref) ; + sym->ref_list = next ; + } + else + prev->next = next ; + + if (next != NULL) + next->prev = prev ; + } ; + ref->next = ref->prev = NULL ; +} ; + +/*============================================================================== + * The value_call_back handling and symbol reference list walking. + * + * If there is one, the value_call_back function is called when the value of + * a symbol is set -- except when it is set NULL and is already NULL. Note + * that setting the same non-NULL value *does* invoke the value_call_back. + * + * The value_call_back function is passed the current state of the symbol, + * complete with new value, and the old value of the symbol. + * + * During the value_call_back the symbol reference list may be walked, so that + * users of the value may be updated. + * + * During the value_call_back the symbol may be set, unset or deleted, and + * references added or taken away. This may cause nested calls of the + * call-back. Note that each call-back holds a reference to the symbol, so if + * the symbol is deleted it won't be freed until the outermost call-back + * returns. + * + * Procedure for walking the references to a symbol: + * + * struct symbol_ref walk ; + * symbol sym ; + * symbol_ref ref ; + * symbol_ref_walk_start(sym, &walk) ; + * while ((ref = symbol_ref_walk_step(&walk)) != NULL) + * .... whatever + * symbol_ref_walk_end(&walk) ; + * + * NB: it is *essential* to call symbol_ref_walk_end() exactly once at some + * time after symbol_ref_walk_start. + * + * The symbol table walk uses a "bookmark" which is a special from of entry in + * the symbol's reference list. This mechanism: + * + * (a) prevents the symbol being freed while the reference walk is in + * progress -- that may happen during symbol_ref_walk_end. + * + * (b) allows for the current and other references to be set or unset. + * + * Setting a reference inserts it upstream of the bookmark -- so it will + * not be visited during the walk. + * + * Unsetting a reference that has yet to be visited eliminates it from + * the walk. + * + * Note that setting a reference to refer to the symbol it already + * refers to has no effect at all. + * + * (c) allows the symbol to be defined, undefined or redefined during a + * symbol reference walk. + * + * If that triggers another symbol reference walk, then that walk will + * proceed until it hits the point reached by the walk it is nested + * inside, and then stop. + * + * Suppose the outer walk was dealing with the value having changed from + * 'A' to 'B'. The inner walk will do from 'B' to the latest value 'C' + * for the references that have already seen 'A' to 'B'. When the outer + * walk resumes, it will deal with the change 'A' to 'C', unaware of the + * intermediate step. + * + * If that does not suit, don't fiddle with symbol values during a + * symbol reference walk. + */ + +/* Bookmarks are symbol_ref structures, distinguished from ordinary symbol_ref + * structures by setting the sym field to point at the bookmark symbol_ref + * itself. + * + * (It would be nicer to use the parent field for this... but what is put + * there in ordinary symbol_ref structures is not guaranteed...) + */ +static inline int +symbol_ref_is_bookmark(symbol_ref ref) +{ + return (void*)ref->sym == (void*)ref ; +} ; + +/* Start walk of symbol references */ +void +symbol_ref_walk_start(symbol sym, symbol_ref walk) +{ + symbol_init_ref(walk) ; /* keeping things tidy */ + walk->sym = (void*)walk ; /* bookmark signature */ + walk->parent = sym ; + symbol_add_ref(sym, walk) ; /* insert bookmark at head of list */ +} ; + +/* Step walk and return the next reference (if any). */ +symbol_ref +symbol_ref_walk_step(symbol_ref walk) +{ + symbol_ref next_ref ; + + assert(symbol_ref_is_bookmark(walk)) ; /* must be a bookmark ! */ + + /* Pick up reference following the bookmark, before deleting it. */ + next_ref = walk->next ; + symbol_del_ref((symbol)walk->parent, walk) ; + + /* Stop immediately if bookmark was at the end of the list or the next */ + /* item is a bookmark (for a walk that started earlier). */ + if ((next_ref == NULL) || symbol_ref_is_bookmark(next_ref)) + return NULL ; + + /* Now we move the bookmark from where it is now to after next_ref. */ + + walk->next = next_ref->next ; + next_ref->next = walk ; + walk->prev = next_ref ; + if (walk->next != NULL) + walk->next->prev = walk ; + + /* Return the next real reference to be processed. */ + return next_ref ; +} ; + +/* End of symbol reference walk. + * + * NB: if the symbol is not defined and has no references or bookmarks it + * will now be freed. + */ +void +symbol_ref_walk_end(symbol_ref walk) +{ + assert(symbol_ref_is_bookmark(walk)) ; /* must be a bookmark ! */ + + symbol_del_ref((symbol)(walk->parent), walk) ; /* make sure */ + + symbol_free_if_redundant((symbol)(walk->parent)) ; +} ; + +/*============================================================================== + * Symbol Value handling. + */ + +/* Set symbol value. NB: setting to NULL == symbol_unset_value. + * NB: setting same value as currently looks like a change. + * (except for setting NULL to NULL !) + * + * Invokes change call-back, if any -- except when setting to NULL and is + * already NULL. + * + * It is possible for the call-back to set the value again, to unset it, to + * change references, etc. + * + * Returns previous value -- which may require releasing. + */ +void* +symbol_set_value(symbol sym, void* new_value) +{ + void* old_value ; + + old_value = sym->value ; + sym->value = new_value ; + + if (sym->table == NULL) /* watch out for orphans */ + { + assert((new_value == NULL) && (old_value == NULL)) ; + return NULL ; + } ; + + /* Invoke value_call_back (if any). */ + /* Note that the value_call_back may set/unset references and/or */ + /* define/undefine the value. */ + if (((sym)->table->value_call_back != NULL) + && ( (new_value != NULL) || (old_value != NULL) )) + { + symbol_inc_ref(sym) ; /* preserve until call-back returns */ + sym->table->value_call_back(sym, old_value) ; + symbol_dec_ref(sym) ; /* may now free if has been deleted */ + } ; + + return old_value ; +} ; + +/*============================================================================== + * Symbol Reference handling. + * + * Implementation note: the next and prev pointers in the symbol_ref structure + * are significant only if the sym pointer is not NULL. + */ + +/* Initialise symbol reference -- allocate if required. */ +symbol_ref +symbol_init_ref(symbol_ref ref) +{ + if (ref == NULL) + return XCALLOC(MTYPE_SYMBOL_REF, sizeof(struct symbol_ref)) ; + else + return memset(ref, 0, sizeof(struct symbol_ref)) ; +} ; + +/* Set symbol reference -- allocate if required (ref == NULL). + * + * NB: does nothing if reference already set to the given symbol. + * + * NB: unsets (but does not free) reference if was not NULL (and is not + * same as symbol being set to) before setting new reference. + * + * NB: setting reference to NULL unsets any existing reference, but does NOT + * release the reference structure. + * + * NB: if reference is allocated, the parent is set NULL and the tag is set + * NULL/0. + * + * if reference is not allocated, the parent and tag are unchanged. + */ +symbol_ref +symbol_set_ref(symbol_ref ref, struct symbol* sym) +{ + if (ref != NULL) + { + if (ref->sym == sym) + return ref ; /* Nothing more to do if already set to given value */ + if (ref->sym != NULL) + symbol_unset_ref_keep(ref) ; + } + else + ref = symbol_init_ref(NULL) ; + + ref->sym = sym ; + if (sym != NULL) + symbol_add_ref(sym, ref) ; + + return ref ; +} ; + +/* Unset symbol reference. Free the structure if required. + * + * NB: does nothing if address of reference is NULL. + * + * NB: if reference is not freed, the parent and tag are unchanged. + * + * NB: removing the last reference to an symbol that has been deleted causes + * the symbol to be freed. + * + * NB: copes if the reference is already unset, of course. + */ +symbol_ref +symbol_unset_ref(symbol_ref ref, int free_ref_structure) +{ + if (ref == NULL) return ref ; + + if (ref->sym != NULL) /* NULL => reference already unset */ + { + symbol_del_ref(ref->sym, ref) ; + symbol_free_if_redundant(ref->sym) ; + ref->sym = NULL ; + } ; + + if (free_ref_structure) + XFREE(MTYPE_SYMBOL_REF, ref) ; /* ref is set to NULL */ + + return ref ; +} ; + +/*============================================================================== + * Walking a symbol table + * + * Simple walk: visits all entries in the table, in the order they are hashed + * to. Simple iterator. + * + * Extract: makes vector of pointers to selected entries, and sorts that + * vector as required. + */ + +/* Walk the given symbol table. Usage: + * + * struct symbol_walker walker ; + * symbol sym ; + * .... + * symbol_walk_start(table, &walker) ; + * while ((sym = symbol_walk_next(&walker))) + * .... + * + * NB: it is possible to change the current symbol while the walk is in + * progress -- up to and including deleting it. Any other changes to + * the table must NOT be attempted. + */ +void +symbol_walk_start(symbol_table table, struct symbol_walker* walker) +{ + walker->next = NULL ; + walker->base = table->bases ; + walker->base_count = table->base_count ; +} ; + +symbol +symbol_walk_next(struct symbol_walker* walker) +{ + symbol this = walker->next ; + + while (this == NULL) + { + if (walker->base_count == 0) + return NULL ; + --walker->base_count ; + this = *(walker->base++) ; + } ; + + walker->next = this->next ; + return this ; +} ; + +/* Extract Symbols. + * + * Walk symbol table and select symbols to add to a new vector. Then sort the + * vector, if required. Takes: + * + * -- selector: NULL => select all + * -- p_val: pointer is passed to the select function (if any) + * -- most: if there is a select function, this flag hints that most of + * the symbols will be selected -- so it is worth preallocating + * a vector big enough for all symbols. + * -- sort: NULL => no sort (!) + * + * NB: the vector contains pointers to the selected symbols. It is the + * caller's responsibility to avoid deleting any symbol whose pointer + * in the vector they expect to rely on ! + */ +vector +symbol_table_extract(symbol_table table, + symbol_select_cmp* selector, const void* p_val, int most, + symbol_sort_cmp* sort) +{ + vector extract ; + symbol* base ; + unsigned int count ; + symbol sym ; + + extract = vector_init_new(NULL, (most || (selector == NULL)) + ? table->entry_count : 8) ; + base = table->bases ; + + if (base == NULL) + return extract ; /* Quit if symbol table is "reset" */ + + count = table->base_count ; + while (count--) + { + sym = *base++ ; + while (sym != NULL) + { + if ((selector == NULL) || selector(sym, p_val)) + vector_push_item(extract, sym) ; + sym = sym->next ; + } ; + } ; + + if (sort != NULL) + vector_sort(extract, (vector_sort_cmp*)sort) ; + + return extract ; +} ; + +/*============================================================================== + * Some common comparison functions for symbol table extracts. + */ + +/* Comparison function to sort names which are a mixture of digits and other + * characters. + * + * This comparison treats substrings of digits as numbers, so "a10" is > "a1". + */ +int +symbol_mixed_name_cmp(const symbol* p_a, + const symbol* p_b) +{ + const char* a = symbol_get_name(*p_a) ; + const char* b = symbol_get_name(*p_b) ; + int la, lb ; + + while (1) { + if (isdigit(*a) && isdigit(*b)) + { + char* as ; /* Required to stop the compiler whining */ + char* bs ; + unsigned long int na = strtoul(a, (char** restrict)&as, 10) ; + unsigned long int nb = strtoul(b, (char** restrict)&bs, 10) ; + if (na != nb) + return (na < nb) ? -1 : +1 ; + a = as ; + b = bs ; + } + else + { + if (*a != *b) + return (*a < *b) ? -1 : +1 ; + if (*a == '\0') + break ; + ++a ; + ++b ; + } + } ; + + /* Looks like the names are equal. + * But may be different lengths if have number part(s) with leading zeros, + */ + + la = symbol_get_name_len(*p_a) ; + lb = symbol_get_name_len(*p_b) ; + if (la != lb) + return (la < lb) ? -1 : +1 ; + return 0 ; +} ; + +/*============================================================================== + * Table for generating CRC-32 -- Standard (0x1_04C1_1DB7 0xEDB8_8320) + */ +static u_int32_t crc_table[] = +{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, +} ; diff --git a/lib/symtab.h b/lib/symtab.h new file mode 100644 index 00000000..a8a6e622 --- /dev/null +++ b/lib/symtab.h @@ -0,0 +1,320 @@ +/* Symbol Table data structure -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_SYMTAB_H +#define _ZEBRA_SYMTAB_H + +#include "vector.h" +#include <stddef.h> +#include <stdint.h> + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/* Maximum number of symbol table bases -- something has gone tragically wrong + * if we hit this. Assume can multiply this by 2 and get valid size_t result. + */ +#define SYMBOL_TABLE_BASES_MAX (1024 * 1024 * 1024) + +/* Minimum number of symbol table bases. */ +#define SYMBOL_TABLE_BASES_MIN 10 +/* Point at which stops doubling the symbol table size (bases) */ +#define SYMBOL_TABLE_BASES_DOUBLE_MAX 2000 + +/* Structures defined below. */ +struct symbol_table ; +struct symbol ; +struct symbol_ref ; +struct symbol_hash ; + +typedef struct symbol_table* symbol_table ; +typedef struct symbol* symbol ; +typedef struct symbol_ref* symbol_ref ; +typedef struct symbol_hash* symbol_hash ; + +/* Function types used. */ +typedef void symbol_hash_function(symbol_hash hash, const void* name) ; +typedef void symbol_call_back_function(symbol sym, void* value) ; +typedef void symbol_destroy_function(symbol sym) ; + +/* Symbol Table. + * + * Don't fiddle with this directly... see access functions below. + */ + +struct symbol_table +{ + void* parent ; /* to identify the table. */ + + symbol* bases ; /* ref:array of chain bases */ + unsigned int base_count ; /* number of chain bases */ + + unsigned int entry_count ; /* number of entries in the table */ + unsigned int extend_thresh ; /* when to extend the hash table */ + + symbol_hash_function* hash_function ; /* function to hash given "name" */ + /* NULL => use default */ + symbol_call_back_function* value_call_back ; + /* called when symbol value is set */ +} ; + +/* Symbol Table Entry. + * + * Don't fiddle with this directly... see access macros/functions below. + */ + +struct symbol +{ + symbol_table table ; /* so can go from symbol to enclosing table */ + /* NULL => orphan symbol, with NULL value. */ + + symbol next ; /* assume chains are short and seldom remove */ + /* symbols -- so single pointer will do. */ + + void* value ; /* see: symbol_get_value(sym) etc. */ + + symbol_ref ref_list ; /* list of symbol_ref references */ + unsigned ref_count ; /* count of simple references */ + + uint32_t hash ; /* used in lookup and when extending bases. */ + + uint16_t name_len ; /* see: symbol_get_name_len(sym) */ + char name[] ; /* see: symbol_get_name(sym) */ +} ; + +/* Symbol Reference (or "bookmark"). + * + * Don't fiddle with this directly... see access macros/functions below + */ + +typedef union +{ + void* p ; + unsigned long u ; + signed long i ; +} symbol_ref_tag_t ; + +struct symbol_ref +{ + symbol sym ; /* Address of symbol referred to (if any). */ + /* (In "bookmark" this points to self.) */ + + symbol_ref next ; /* fellow references to the symbol ... */ + symbol_ref prev ; /* ... ignore if sym is NULL. */ + + void* parent ; /* see: sym_ref_parent(sym_ref) etc. */ + symbol_ref_tag_t tag ; /* see: sym_ref_tag(sym_ref) etc. */ +} ; + +/* Result of a hash function for a symbol name. */ +struct symbol_hash +{ + uint32_t hash ; /* the hash value ! */ + const void* name ; /* symbol name as byte vector */ + uint16_t name_len ; /* length in chars for comparison purposes */ + uint16_t name_copy_len ; /* number of chars to copy to store name. */ +} ; + +/* Symbol Walk Iterator */ +struct symbol_walker +{ + symbol next ; /* next symbol to return (if any) */ + symbol* base ; /* next chain base to process (if any) */ + unsigned base_count ; /* number of chain bases left to process */ +} ; + +/* Symbol Table Operations. */ + +extern symbol_table +symbol_table_init_new(symbol_table table, + void* parent, + unsigned bases, + unsigned density, + symbol_hash_function* hash_function, + symbol_call_back_function* value_call_back) ; +void symbol_table_set_parent(symbol_table table, void* parent) ; +void* symbol_table_get_parent(symbol_table table) ; +void* symbol_table_ream(symbol_table table, int free_structure) ; +#define symbol_table_ream_free(table) symbol_table_ream(table, 1) +#define symbol_table_ream_keep(table) symbol_table_ream(table, 0) +symbol_table symbol_table_reset(symbol_table table, int free_structure) ; +#define symbol_table_reset_free(table) symbol_table_reset(table, 1) +#define symbol_table_reset_keep(table) symbol_table_reset(table, 0) + +extern void symbol_hash_string(struct symbol_hash* p_hash, const char* string) ; +extern void symbol_hash_bytes(struct symbol_hash* p_hash, const void* bytes, + size_t len) ; +extern void symbol_table_set_value_call_back(symbol_table table, + symbol_call_back_function* value_call_back) ; + +extern void symbol_table_free(symbol_table) ; + +extern symbol symbol_lookup(symbol_table table, const void* name, int add) ; + +#define symbol_seek(table, name) symbol_lookup(table, name, 0) +#define symbol_find(table, name) symbol_lookup(table, name, 1) + +extern void* symbol_delete(symbol sym) ; + +extern void* symbol_set_value(symbol sym, void* new_value) ; +#define symbol_unset_value(sym) symbol_set_value(sym, NULL) + +void symbol_ref_walk_start(symbol sym, symbol_ref walk) ; +symbol_ref symbol_ref_walk_step(symbol_ref walk) ; +void symbol_ref_walk_end(symbol_ref walk) ; + +void symbol_walk_start(symbol_table table, struct symbol_walker* walker) ; +symbol symbol_walk_next(struct symbol_walker* walker) ; + +typedef int symbol_select_cmp(const symbol, const void*) ; +typedef int symbol_sort_cmp(const symbol*, const symbol*) ; +vector symbol_table_extract(symbol_table table, + symbol_select_cmp* select, const void* p_value, + int most, symbol_sort_cmp* sort) ; + +extern symbol_sort_cmp symbol_mixed_name_cmp ; + +/* Access functions -- argument is address of symbol (may be NULL). */ +Inline void* +symbol_get_value(const symbol sym) +{ + return (sym != NULL) ? sym->value : NULL ; +} ; + +Inline const void* +symbol_get_name(const symbol sym) +{ + return (sym != NULL) ? sym->name : NULL ; +} ; + +Inline unsigned +symbol_get_name_len(const symbol sym) +{ + return (sym != NULL) ? sym->name_len : 0 ; +} ; + +Inline struct symbol_table* +symbol_get_table(const symbol sym) +{ + return (sym != NULL) ? sym->table : NULL ; +} ; + +Inline symbol +symbol_inc_ref(symbol sym) +{ + ++sym->ref_count ; + return sym ; +} ; + +extern symbol symbol_zero_ref(symbol sym, int force) ; + +Inline symbol +symbol_dec_ref(symbol sym) +{ + if (sym->ref_count <= 1) + return symbol_zero_ref(sym, 0) ; + + --sym->ref_count ; + return sym ; +} ; + +extern symbol_ref +symbol_init_ref(symbol_ref ref) ; + +extern symbol_ref +symbol_set_ref(symbol_ref ref, symbol sym) ; + +extern symbol_ref +symbol_unset_ref(symbol_ref ref, int free_ref_structure) ; + +#define symbol_unset_ref_free(ref) symbol_unset_ref(ref, 1) ; +#define symbol_unset_ref_keep(ref) symbol_unset_ref(ref, 0) ; + +/* Access functions -- argument is address of symbol_ref. */ +/* These cope if address of symbol_ref is null, or reference is undefined. */ +Inline void* +sym_ref_symbol(symbol_ref ref) +{ + return (ref != NULL) ? ref->sym : NULL ; +} +Inline void* +sym_ref_value(symbol_ref ref) +{ + return symbol_get_value(sym_ref_symbol(ref)) ; +} +Inline const void* +sym_ref_name(symbol_ref ref) +{ + return symbol_get_name(sym_ref_symbol(ref)) ; +} +Inline uint16_t +sym_ref_name_len(symbol_ref ref) +{ + return symbol_get_name_len(sym_ref_symbol(ref)) ; +} + +Inline void* +sym_ref_parent(symbol_ref ref) +{ + return (ref != NULL) ? ref->parent : NULL ; +} +Inline void* +sym_ref_p_tag(symbol_ref ref) +{ + return (ref != NULL) ? ref->tag.p : NULL ; +} +Inline unsigned long int +sym_ref_u_tag(symbol_ref ref) +{ + return (ref != NULL) ? ref->tag.u : 0 ; +} +Inline signed long int +sym_ref_i_tag(symbol_ref ref) +{ + return (ref != NULL) ? ref->tag.i : 0 ; +} + +/* Set properties of reference -- argument is address of symbol_ref, which is */ +/* assumed to NOT be NULL. */ +Inline void +sym_ref_set_parent(symbol_ref ref, void* pa) +{ + ref->parent = pa ; +} +Inline void +sym_ref_set_p_tag(symbol_ref ref, void* p_tag) +{ + ref->tag.p = p_tag ; +} +Inline void +sym_ref_set_u_tag(symbol_ref ref, unsigned long int u_tag) +{ + ref->tag.u = u_tag ; +} +Inline void +sym_ref_set_i_tag(symbol_ref ref, signed long int i_tag) +{ + ref->tag.i = i_tag ; +} + +#endif /* _ZEBRA_SYMTAB_H */ diff --git a/lib/thread.c b/lib/thread.c index fd841c21..a15df557 100644 --- a/lib/thread.c +++ b/lib/thread.c @@ -16,12 +16,13 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ /* #define DEBUG */ #include <zebra.h> +#include "miyagi.h" #include "thread.h" #include "memory.h" @@ -29,7 +30,9 @@ #include "hash.h" #include "command.h" #include "sigevent.h" - +#include "qpthreads.h" +#include "qtimers.h" + /* Recent absolute time of day */ struct timeval recent_time; static struct timeval last_recent_time; @@ -38,10 +41,19 @@ static struct timeval relative_time; static struct timeval relative_time_base; /* init flag */ static unsigned short timers_inited; - + +/* cpu stats needs to be qpthread safe. */ +static qpt_mutex_t thread_mutex; +#define LOCK qpt_mutex_lock(&thread_mutex); +#define UNLOCK qpt_mutex_unlock(&thread_mutex); static struct hash *cpu_record = NULL; - -/* Struct timeval's tv_usec one second value. */ + +/* Pointer to qtimer pile to be used, if any */ +static qtimer_pile use_qtimer_pile = NULL ; +static qtimer spare_qtimers = NULL ; +static unsigned used_standard_timer = 0 ; + +/* Struct timeval's tv_usec one second value. */ #define TIMER_SECOND_MICRO 1000000L /* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO). @@ -92,7 +104,7 @@ timeval_elapsed (struct timeval a, struct timeval b) return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) + (a.tv_usec - b.tv_usec)); } - + #ifndef HAVE_CLOCK_MONOTONIC static void quagga_gettimeofday_relative_adjust (void) @@ -119,9 +131,9 @@ static int quagga_gettimeofday (struct timeval *tv) { int ret; - + assert (tv); - + if (!(ret = gettimeofday (&recent_time, NULL))) { /* init... */ @@ -196,7 +208,7 @@ quagga_gettime (enum quagga_clkid clkid, struct timeval *tv) } } -/* time_t value in terms of stabilised absolute time. +/* time_t value in terms of stabilised absolute time. * replacement for POSIX time() */ time_t @@ -215,14 +227,17 @@ recent_relative_time (void) { return relative_time; } - + +/* Uses the address of the function (or at least ls part of same) as the hash + * key. (The function name is for display, only.) + */ static unsigned int cpu_record_hash_key (struct cpu_thread_history *a) { return (uintptr_t) a->func; } -static int +static int cpu_record_hash_cmp (const struct cpu_thread_history *a, const struct cpu_thread_history *b) { @@ -232,25 +247,58 @@ cpu_record_hash_cmp (const struct cpu_thread_history *a, static void * cpu_record_hash_alloc (struct cpu_thread_history *a) { - struct cpu_thread_history *new; + const char* b ; + const char* e ; + char* n ; + int l ; + struct cpu_thread_history *new ; + + /* Establish start and length of name, removing leading/trailing + * spaces and any enclosing (...) -- recursively. + */ + b = a->funcname ; + e = b + strlen(b) - 1 ; + + while (1) + { + while (*b == ' ') + ++b ; /* strip leading spaces */ + if (*b == '\0') + break ; /* quit if now empty */ + while (*e == ' ') + --e ; /* strip trailing spaces */ + if ((*b != '(') || (*e != ')')) + break ; /* quit if not now (...) */ + ++b ; + --e ; /* discard ( and ) */ + } ; + + l = (e + 1) - b ; /* length excluding trailing \0 */ + + n = XMALLOC(MTYPE_THREAD_FUNCNAME, l + 1) ; + memcpy(n, b, l) ; + n[l] = '\0' ; + + /* Allocate empty structure and set address and name */ new = XCALLOC (MTYPE_THREAD_STATS, sizeof (struct cpu_thread_history)); - new->func = a->func; - new->funcname = XSTRDUP(MTYPE_THREAD_FUNCNAME, a->funcname); - return new; + new->func = a->func; + new->funcname = n ; + + return new ; } static void cpu_record_hash_free (void *a) { struct cpu_thread_history *hist = a; - + XFREE (MTYPE_THREAD_FUNCNAME, hist->funcname); XFREE (MTYPE_THREAD_STATS, hist); } -static inline void +static inline void vty_out_cpu_thread_history(struct vty* vty, - struct cpu_thread_history *a) + const struct cpu_thread_history *a) { #ifdef HAVE_RUSAGE vty_out(vty, "%7ld.%03ld %9d %8ld %9ld %8ld %9ld", @@ -273,14 +321,14 @@ vty_out_cpu_thread_history(struct vty* vty, } static void -cpu_record_hash_print(struct hash_backet *bucket, +cpu_record_hash_print(struct hash_backet *bucket, void *args[]) { struct cpu_thread_history *totals = args[0]; struct vty *vty = args[1]; thread_type *filter = args[2]; struct cpu_thread_history *a = bucket->data; - + a = bucket->data; if ( !(a->types & *filter) ) return; @@ -303,7 +351,9 @@ cpu_record_print(struct vty *vty, thread_type filter) void *args[3] = {&tmp, vty, &filter}; memset(&tmp, 0, sizeof tmp); - tmp.funcname = (char *)"TOTAL"; + tmp.funcname = miyagi("TOTAL"); /* NB: will not free tmp in the usual way, + in particular, will not attempt + to free this !! */ tmp.types = filter; #ifdef HAVE_RUSAGE @@ -315,15 +365,19 @@ cpu_record_print(struct vty *vty, thread_type filter) vty_out(vty, " Avg uSec Max uSecs"); #endif vty_out(vty, " Type Thread%s", VTY_NEWLINE); + + LOCK hash_iterate(cpu_record, (void(*)(struct hash_backet*,void*))cpu_record_hash_print, args); if (tmp.total_calls > 0) vty_out_cpu_thread_history(vty, &tmp); + + UNLOCK } -DEFUN(show_thread_cpu, +DEFUN_CALL(show_thread_cpu, show_thread_cpu_cmd, "show thread cpu [FILTER]", SHOW_STR @@ -384,16 +438,15 @@ DEFUN(show_thread_cpu, } static void -cpu_record_hash_clear (struct hash_backet *bucket, - void *args) +cpu_record_hash_clear (struct hash_backet *bucket, void *args) { thread_type *filter = args; struct cpu_thread_history *a = bucket->data; - + a = bucket->data; if ( !(a->types & *filter) ) return; - + hash_release (cpu_record, bucket->data); } @@ -402,8 +455,8 @@ cpu_record_clear (thread_type filter) { thread_type *tmp = &filter; hash_iterate (cpu_record, - (void (*) (struct hash_backet*,void*)) cpu_record_hash_clear, - tmp); + (void (*) (struct hash_backet*,void*)) cpu_record_hash_clear, + tmp); } DEFUN(clear_thread_cpu, @@ -421,51 +474,51 @@ DEFUN(clear_thread_cpu, { filter = 0; while (argv[0][i] != '\0') - { - switch ( argv[0][i] ) - { - case 'r': - case 'R': - filter |= (1 << THREAD_READ); - break; - case 'w': - case 'W': - filter |= (1 << THREAD_WRITE); - break; - case 't': - case 'T': - filter |= (1 << THREAD_TIMER); - break; - case 'e': - case 'E': - filter |= (1 << THREAD_EVENT); - break; - case 'x': - case 'X': - filter |= (1 << THREAD_EXECUTE); - break; - case 'b': - case 'B': - filter |= (1 << THREAD_BACKGROUND); - break; - default: - break; - } - ++i; - } + { + switch ( argv[0][i] ) + { + case 'r': + case 'R': + filter |= (1 << THREAD_READ); + break; + case 'w': + case 'W': + filter |= (1 << THREAD_WRITE); + break; + case 't': + case 'T': + filter |= (1 << THREAD_TIMER); + break; + case 'e': + case 'E': + filter |= (1 << THREAD_EVENT); + break; + case 'x': + case 'X': + filter |= (1 << THREAD_EXECUTE); + break; + case 'b': + case 'B': + filter |= (1 << THREAD_BACKGROUND); + break; + default: + break; + } + ++i; + } if (filter == 0) - { - vty_out(vty, "Invalid filter \"%s\" specified," + { + vty_out(vty, "Invalid filter \"%s\" specified," " must contain at least one of 'RWTEXB'%s", - argv[0], VTY_NEWLINE); - return CMD_WARNING; - } + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } } cpu_record_clear (filter); return CMD_SUCCESS; } - + /* List allocation and head/tail print out. */ static void thread_list_debug (struct thread_list *list) @@ -494,16 +547,21 @@ thread_master_debug (struct thread_master *m) printf ("total alloc: [%ld]\n", m->alloc); printf ("-----------\n"); } - + /* Allocate new thread master. */ struct thread_master * thread_master_create () { - if (cpu_record == NULL) - cpu_record - = hash_create_size (1011, (unsigned int (*) (void *))cpu_record_hash_key, +#ifdef USE_MQUEUE + sigfillset (&newmask); + sigdelset (&newmask, SIGMQUEUE); +#endif + + if (cpu_record == NULL) + cpu_record + = hash_create_size (1011, (unsigned int (*) (void *))cpu_record_hash_key, (int (*) (const void *, const void *))cpu_record_hash_cmp); - + return (struct thread_master *) XCALLOC (MTYPE_THREAD_MASTER, sizeof (struct thread_master)); } @@ -524,8 +582,8 @@ thread_list_add (struct thread_list *list, struct thread *thread) /* Add a new thread just before the point. */ static void -thread_list_add_before (struct thread_list *list, - struct thread *point, +thread_list_add_before (struct thread_list *list, + struct thread *point, struct thread *thread) { thread->next = point; @@ -564,7 +622,6 @@ thread_add_unuse (struct thread_master *m, struct thread *thread) assert (thread->prev == NULL); assert (thread->type == THREAD_UNUSED); thread_list_add (&m->unuse, thread); - /* XXX: Should we deallocate funcname here? */ } /* Free all unused thread. */ @@ -577,8 +634,13 @@ thread_list_free (struct thread_master *m, struct thread_list *list) for (t = list->head; t; t = next) { next = t->next; - if (t->funcname) - XFREE (MTYPE_THREAD_FUNCNAME, t->funcname); + + if ( (use_qtimer_pile != NULL) + && ( (t->type == THREAD_TIMER || t->type == THREAD_BACKGROUND) ) + && (t->u.qtr != NULL) + ) + qtimer_free(t->u.qtr) ; + XFREE (MTYPE_THREAD, t); list->count--; m->alloc--; @@ -589,6 +651,8 @@ thread_list_free (struct thread_master *m, struct thread_list *list) void thread_master_free (struct thread_master *m) { + qtimer qtr ; + thread_list_free (m, &m->read); thread_list_free (m, &m->write); thread_list_free (m, &m->timer); @@ -596,15 +660,23 @@ thread_master_free (struct thread_master *m) thread_list_free (m, &m->ready); thread_list_free (m, &m->unuse); thread_list_free (m, &m->background); - + XFREE (MTYPE_THREAD_MASTER, m); + LOCK if (cpu_record) { hash_clean (cpu_record, cpu_record_hash_free); hash_free (cpu_record); cpu_record = NULL; } + UNLOCK + + while ((qtr = spare_qtimers) != NULL) + { + spare_qtimers = (void*)(qtr->pile) ; + qtimer_free(qtr) ; + } ; } /* Thread list is empty or not. */ @@ -628,41 +700,40 @@ unsigned long thread_timer_remain_second (struct thread *thread) { quagga_get_relative (NULL); - + if (thread->u.sands.tv_sec - relative_time.tv_sec > 0) return thread->u.sands.tv_sec - relative_time.tv_sec; else return 0; } -/* Trim blankspace and "()"s */ -static char * -strip_funcname (const char *funcname) -{ - char buff[100]; - char tmp, *ret, *e, *b = buff; - - strncpy(buff, funcname, sizeof(buff)); - buff[ sizeof(buff) -1] = '\0'; - e = buff +strlen(buff) -1; - - /* Wont work for funcname == "Word (explanation)" */ +/* Get new cpu history */ - while (*b == ' ' || *b == '(') - ++b; - while (*e == ' ' || *e == ')') - --e; - e++; +static struct cpu_thread_history* +thread_get_hist(struct thread* thread, const char* funcname) +{ + struct cpu_thread_history tmp ; + struct cpu_thread_history* hist ; + + tmp.func = thread->func ; + tmp.funcname = miyagi(funcname); /* NB: will not free tmp in the usual way, + in particular, will not attempt + to free this !! */ + LOCK + + /* This looks up entry which matches the tmp just set up. + * + * If does not find one, allocates a new one -- taking a copy of the + * funcname. + */ + hist = hash_get (cpu_record, &tmp, + (void * (*) (void *))cpu_record_hash_alloc); + UNLOCK - tmp = *e; - *e = '\0'; - ret = XSTRDUP (MTYPE_THREAD_FUNCNAME, b); - *e = tmp; + return hist ; +} ; - return ret; -} - -/* Get new thread. */ +/* Get new thread. */ static struct thread * thread_get (struct thread_master *m, u_char type, int (*func) (struct thread *), void *arg, const char* funcname) @@ -672,28 +743,27 @@ thread_get (struct thread_master *m, u_char type, if (!thread_empty (&m->unuse)) { thread = thread_trim_head (&m->unuse); - if (thread->funcname) - XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname); + memset(thread, 0, sizeof (struct thread)) ; } else { thread = XCALLOC (MTYPE_THREAD, sizeof (struct thread)); m->alloc++; } - thread->type = type; + thread->type = type; thread->add_type = type; - thread->master = m; - thread->func = func; - thread->arg = arg; - - thread->funcname = strip_funcname(funcname); + thread->master = m; + thread->func = func; + thread->arg = arg; - return thread; + thread->hist = thread_get_hist(thread, funcname) ; + + return thread ; } /* Add new read thread. */ struct thread * -funcname_thread_add_read (struct thread_master *m, +funcname_thread_add_read (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd, const char* funcname) { struct thread *thread; @@ -737,111 +807,266 @@ funcname_thread_add_write (struct thread_master *m, return thread; } +/*============================================================================== + * Timer Threads -- THREAD_TIMER and THREAD_BACKGROUND + * + * Standard Timer Threads are sorted by the "struct timeval sands", and + * processed by thread_timer_process() -- which moves any expired timer + * threads onto the THREAD_READY queue. So, the scheduling of background stuff + * is done by not processing the THREAD_BACKGROUND queue until there is + * nothing else to do. + * + * When using a qtimer_pile: + * + * * THREAD_TIMER threads have an associated qtimer. + * + * When the timer expires, the qtimer is cut from the thread (and put onto + * the spare_qtimers list). The thread is then queued on the THREAD_READY + * queue (as before). + * + * * THREAD_BACKGROUND threads which have a non-zero delay are treated much + * as THREAD_TIMER, except that when the timer expires, the thread is + * queued on the THREAD_BACKGROUND queue. + * + * The THREAD_BACKGROUND queue is visited only when there is nothing else + * to do. + * + * Note that when using a qtimer_pile, and there is an active qtimer associated + * with the thread, the thread will be on the THREAD_TIMER queue -- so that it + * can be collected up and released if required. + * + * NB: when using a qtimer_pile, if there is a qtimer associated with a + * THREAD_TIMER or a THREAD_BACKGROUND thread, then thread->u.qtr points + * at the qtimer. + * + * AND, conversely, if there is no qtimer, then thread->u.ptr == NULL. + */ + +/*------------------------------------------------------------------------------ + * Set use_qtimer_pile ! + */ +extern void +thread_set_qtimer_pile(qtimer_pile pile) +{ + passert(!used_standard_timer) ; + + use_qtimer_pile = pile ; +} ; + +/*------------------------------------------------------------------------------ + * Unset qtimer associated with the given THREAD_TIMER or THREAD_BACKGROUND + * thread -- if any. + * + * Moves any qtimer onto the spare_qtimers list. + */ +static void +thread_qtimer_unset(struct thread* thread) +{ + qtimer qtr ; + assert (thread->type == THREAD_TIMER || thread->type == THREAD_BACKGROUND); + assert (use_qtimer_pile != NULL) ; + + qtr = thread->u.qtr ; + if (qtr != NULL) + { + qtimer_unset(qtr) ; + + qtr->pile = (void*)spare_qtimers ; + spare_qtimers = qtr ; + + thread->u.qtr = NULL ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * The qtimer action function -- when using qtimer pile (!) + * + * Remove thread from the THREAD_TIMER queue and unset the qtimer, place + * thread on the THREAD_READY or the THREAD_BACKGROUND queue as required. + */ +static void +thread_qtimer_dispatch(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + struct thread* thread = timer_info ; + + thread_list_delete (&thread->master->timer, thread) ; + thread_qtimer_unset(thread) ; + + switch (thread->type) + { + case THREAD_TIMER: + thread->type = THREAD_READY; + thread_list_add (&thread->master->ready, thread); + break ; + + case THREAD_BACKGROUND: + thread_list_add (&thread->master->background, thread); + break ; + + default: + zabort("invalid thread type in thread_qtimer_dispatch") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * For standard timers, return time left on first timer on the given list. + */ +static struct timeval * +thread_timer_wait (struct thread_list *tlist, struct timeval *timer_val) +{ + if (!thread_empty (tlist)) + { + *timer_val = timeval_subtract (tlist->head->u.sands, relative_time); + return timer_val; + } + return NULL; +} + +/*------------------------------------------------------------------------------ + * Add timer of given type -- either standard or qtimer_pile as required. + * + * Timer interval is given as a struct timeval. + */ static struct thread * -funcname_thread_add_timer_timeval (struct thread_master *m, - int (*func) (struct thread *), +funcname_thread_add_timer_timeval(struct thread_master *m, + int (*func) (struct thread *), int type, - void *arg, - struct timeval *time_relative, + void *arg, + struct timeval *time_relative, const char* funcname) { struct thread *thread; - struct thread_list *list; - struct timeval alarm_time; - struct thread *tt; assert (m != NULL); + assert (time_relative != NULL); assert (type == THREAD_TIMER || type == THREAD_BACKGROUND); - assert (time_relative); - - list = ((type == THREAD_TIMER) ? &m->timer : &m->background); - thread = thread_get (m, type, func, arg, funcname); - /* Do we need jitter here? */ - quagga_get_relative (NULL); - alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec; - alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec; - thread->u.sands = timeval_adjust(alarm_time); + thread = thread_get (m, type, func, arg, funcname); - /* Sort by timeval. */ - for (tt = list->head; tt; tt = tt->next) - if (timeval_cmp (thread->u.sands, tt->u.sands) <= 0) - break; + if (use_qtimer_pile == NULL) + { + struct thread_list *list; + struct timeval alarm_time; + struct thread *tt; - if (tt) - thread_list_add_before (list, tt, thread); + /* Do we need jitter here? */ + quagga_get_relative (NULL); + alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec; + alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec; + thread->u.sands = timeval_adjust(alarm_time); + + /* Sort by timeval. */ + list = ((type == THREAD_TIMER) ? &m->timer : &m->background); + for (tt = list->head; tt; tt = tt->next) + if (timeval_cmp (thread->u.sands, tt->u.sands) <= 0) + break; + + if (tt) + thread_list_add_before (list, tt, thread); + else + thread_list_add (list, thread); + + used_standard_timer = 1 ; + } else - thread_list_add (list, thread); + { + qtimer qtr = spare_qtimers ; + if (qtr != NULL) + spare_qtimers = (qtimer)(qtr->pile) ; + + qtr = qtimer_init_new(qtr, use_qtimer_pile, NULL, thread) ; + thread->u.qtr = qtr ; + + qtimer_set_interval(qtr, timeval2qtime(time_relative), + thread_qtimer_dispatch) ; + thread_list_add(&m->timer, thread) ; + } ; return thread; } - -/* Add timer event thread. */ +/*------------------------------------------------------------------------------ + * Add a THREAD_TIMER timer -- either standard or qtimer_pile as required. + * + * Timer interval is given in seconds. + */ struct thread * funcname_thread_add_timer (struct thread_master *m, - int (*func) (struct thread *), + int (*func) (struct thread *), void *arg, long timer, const char* funcname) { struct timeval trel; - assert (m != NULL); - - trel.tv_sec = timer; + trel.tv_sec = timer; trel.tv_usec = 0; - return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER, arg, + return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER, arg, &trel, funcname); } -/* Add timer event thread with "millisecond" resolution */ +/*------------------------------------------------------------------------------ + * Add a THREAD_TIMER timer -- either standard or qtimer_pile as required. + * + * Timer interval is given in milliseconds. + */ struct thread * funcname_thread_add_timer_msec (struct thread_master *m, - int (*func) (struct thread *), + int (*func) (struct thread *), void *arg, long timer, const char* funcname) { struct timeval trel; - assert (m != NULL); - - trel.tv_sec = timer / 1000; - trel.tv_usec = 1000*(timer % 1000); + trel.tv_sec = timer / 1000 ; + trel.tv_usec = (timer % 1000) * 1000 ; - return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER, - arg, &trel, funcname); + return funcname_thread_add_timer_timeval (m, func, THREAD_TIMER, + arg, &trel, funcname); } -/* Add a background thread, with an optional millisec delay */ +/*------------------------------------------------------------------------------ + * Add a THREAD_BACKGROUND thread -- either standard or qtimer_pile as required. + * + * Timer interval is given in milliseconds. + * + * For qtimer_pile, if the delay is zero, the thread is placed straight onto + * the THREAD_BACKGROUND queue. + */ struct thread * funcname_thread_add_background (struct thread_master *m, int (*func) (struct thread *), - void *arg, long delay, + void *arg, long delay, const char *funcname) { - struct timeval trel; - - assert (m != NULL); - - if (delay) + if ((delay != 0) || (use_qtimer_pile == NULL)) { - trel.tv_sec = delay / 1000; - trel.tv_usec = 1000*(delay % 1000); + struct timeval trel; + + trel.tv_sec = delay / 1000; + trel.tv_usec = (delay % 1000) * 1000 ; + + return funcname_thread_add_timer_timeval (m, func, THREAD_BACKGROUND, + arg, &trel, funcname); } else { - trel.tv_sec = 0; - trel.tv_usec = 0; - } + struct thread* thread ; + + assert (m != NULL); + + thread = thread_get (m, THREAD_BACKGROUND, func, arg, funcname); + thread_list_add (&m->background, thread) ; - return funcname_thread_add_timer_timeval (m, func, THREAD_BACKGROUND, - arg, &trel, funcname); + return thread ; + } ; } +/*----------------------------------------------------------------------------*/ /* Add simple event thread. */ struct thread * funcname_thread_add_event (struct thread_master *m, - int (*func) (struct thread *), void *arg, int val, const char* funcname) + int (*func) (struct thread *), void *arg, int val, + const char* funcname) { struct thread *thread; @@ -854,12 +1079,16 @@ funcname_thread_add_event (struct thread_master *m, return thread; } -/* Cancel thread from scheduler. */ +/*------------------------------------------------------------------------------ + * Cancel thread from scheduler. + * + * Note that when using qtimer_pile need to unset any associated qtimer. + */ void thread_cancel (struct thread *thread) { struct thread_list *list; - + switch (thread->type) { case THREAD_READ: @@ -873,6 +1102,8 @@ thread_cancel (struct thread *thread) list = &thread->master->write; break; case THREAD_TIMER: + if ((use_qtimer_pile != NULL) && (thread->u.qtr != NULL)) + thread_qtimer_unset(thread) ; list = &thread->master->timer; break; case THREAD_EVENT: @@ -882,13 +1113,21 @@ thread_cancel (struct thread *thread) list = &thread->master->ready; break; case THREAD_BACKGROUND: - list = &thread->master->background; + if ((use_qtimer_pile != NULL) && (thread->u.qtr != NULL)) + { + thread_qtimer_unset(thread) ; + list = &thread->master->timer; + } + else + list = &thread->master->background; break; + default: - return; - break; + return ; } + thread_list_delete (list, thread); + thread->type = THREAD_UNUSED; thread_add_unuse (thread->master, thread); } @@ -919,24 +1158,12 @@ thread_cancel_event (struct thread_master *m, void *arg) return ret; } -static struct timeval * -thread_timer_wait (struct thread_list *tlist, struct timeval *timer_val) -{ - if (!thread_empty (tlist)) - { - *timer_val = timeval_subtract (tlist->head->u.sands, relative_time); - return timer_val; - } - return NULL; -} - static struct thread * thread_run (struct thread_master *m, struct thread *thread, struct thread *fetch) { *fetch = *thread; thread->type = THREAD_UNUSED; - thread->funcname = NULL; /* thread_call will free fetch's copied pointer */ thread_add_unuse (m, thread); return fetch; } @@ -947,9 +1174,9 @@ thread_process_fd (struct thread_list *list, fd_set *fdset, fd_set *mfdset) struct thread *thread; struct thread *next; int ready = 0; - + assert (list); - + for (thread = list->head; thread; thread = next) { next = thread->next; @@ -973,7 +1200,7 @@ thread_timer_process (struct thread_list *list, struct timeval *timenow) { struct thread *thread; unsigned int ready = 0; - + for (thread = list->head; thread; thread = thread->next) { if (timeval_cmp (*timenow, thread->u.sands) < 0) @@ -986,13 +1213,15 @@ thread_timer_process (struct thread_list *list, struct timeval *timenow) return ready; } -/* process a list en masse, e.g. for event thread lists */ +/*------------------------------------------------------------------------------ + * Move the given list of threads to the back of the THREAD_READY queue. + */ static unsigned int thread_process (struct thread_list *list) { struct thread *thread; unsigned int ready = 0; - + for (thread = list->head; thread; thread = thread->next) { thread_list_delete (list, thread); @@ -1003,8 +1232,11 @@ thread_process (struct thread_list *list) return ready; } - -/* Fetch next ready thread. */ +/*------------------------------------------------------------------------------ + * Fetch next ready thread -- for standard thread handing. + * + * (This is not used when using qtimer_pile, or qnexus stuff.) + */ struct thread * thread_fetch (struct thread_master *m, struct thread *fetch) { @@ -1012,57 +1244,63 @@ thread_fetch (struct thread_master *m, struct thread *fetch) fd_set readfd; fd_set writefd; fd_set exceptfd; - struct timeval timer_val = { .tv_sec = 0, .tv_usec = 0 }; + struct timeval timer_val ; struct timeval timer_val_bg; - struct timeval *timer_wait = &timer_val; + struct timeval *timer_wait ; struct timeval *timer_wait_bg; while (1) { int num = 0; - + /* Signals pre-empt everything */ quagga_sigevent_process (); - + /* Drain the ready queue of already scheduled jobs, before scheduling * more. */ if ((thread = thread_trim_head (&m->ready)) != NULL) return thread_run (m, thread, fetch); - + /* To be fair to all kinds of threads, and avoid starvation, we * need to be careful to consider all thread types for scheduling * in each quanta. I.e. we should not return early from here on. */ - + /* Normal event are the next highest priority. */ thread_process (&m->event); - + /* Structure copy. */ readfd = m->readfd; writefd = m->writefd; exceptfd = m->exceptfd; - + /* Calculate select wait timer if nothing else to do */ if (m->ready.count == 0) { quagga_get_relative (NULL); timer_wait = thread_timer_wait (&m->timer, &timer_val); timer_wait_bg = thread_timer_wait (&m->background, &timer_val_bg); - + if (timer_wait_bg && (!timer_wait || (timeval_cmp (*timer_wait, *timer_wait_bg) > 0))) timer_wait = timer_wait_bg; } - + else + { + timer_val.tv_sec = 0 ; + timer_val.tv_usec = 0 ; + timer_wait = &timer_val ; + } ; + num = select (FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait); - + /* Signals should get quick treatment */ if (num < 0) { if (errno == EINTR) - continue; /* signal received - process it */ - zlog_warn ("select() error: %s", safe_strerror (errno)); + continue; /* signal received - process it */ + zlog_warn ("select() error: %s", errtoa(errno, 0).str); return NULL; } @@ -1071,7 +1309,7 @@ thread_fetch (struct thread_master *m, struct thread *fetch) list in front of the I/O threads. */ quagga_get_relative (NULL); thread_timer_process (&m->timer, &relative_time); - + /* Got IO, process it */ if (num > 0) { @@ -1092,12 +1330,73 @@ thread_fetch (struct thread_master *m, struct thread *fetch) /* Background timer/events, lowest priority */ thread_timer_process (&m->background, &relative_time); - + if ((thread = thread_trim_head (&m->ready)) != NULL) return thread_run (m, thread, fetch); } } +/*------------------------------------------------------------------------------ + * Empties the event and ready queues. + * + * This is used when qnexus is managing most things, including I/O. Must be + * using qtimer_pile ! + * + * This runs "legacy" event and ready queues only. + * + * Returns: the number of threads dispatched. + * + * Legacy timers are handled by the qtimer_pile, and their related threads will + * be placed on the ready queue when they expire. + * + * The background queue is handled separately. + */ +extern int +thread_dispatch(struct thread_master *m) +{ + struct thread_list* list ; + struct thread fetch ; + int count = 0 ; + + while (1) + { + if (thread_empty(list = &m->event)) + if (thread_empty(list = &m->ready)) + return count ; + + thread_call(thread_run(m, thread_list_delete(list, list->head), &fetch)) ; + + ++count ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch first item on the background queue, if any. + * + * This is used when qnexus is managing most things. + * + * Background threads spend their lives being cycled around the background + * queue -- possibly via the timer queue, if a delay is put in before the next + * invocation. + * + * Returns: 1 if dispatched a background thread + * 0 if there are no background threads + */ +extern int +thread_dispatch_background(struct thread_master *m) +{ + struct thread* thread ; + struct thread fetch ; + + if ((thread = thread_trim_head (&m->background)) == NULL) + return 0 ; + + thread_call(thread_run(m, thread, &fetch)) ; + + return 1 ; +} ; + + unsigned long thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) { @@ -1111,13 +1410,13 @@ thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) return timeval_elapsed (now->real, start->real); } -/* We should aim to yield after THREAD_YIELD_TIME_SLOT milliseconds. +/* We should aim to yield after THREAD_YIELD_TIME_SLOT milliseconds. Note: we are using real (wall clock) time for this calculation. It could be argued that CPU time may make more sense in certain contexts. The things to consider are whether the thread may have blocked (in which case wall time increases, but CPU time does not), or whether the system is heavily loaded with other processes competing - for CPU time. On balance, wall clock time seems to make sense. + for CPU time. On balance, wall clock time seems to make sense. Plus it has the added benefit that gettimeofday should be faster than calling getrusage. */ int @@ -1155,23 +1454,6 @@ thread_call (struct thread *thread) unsigned long realtime, cputime; RUSAGE_T ru; - /* Cache a pointer to the relevant cpu history thread, if the thread - * does not have it yet. - * - * Callers submitting 'dummy threads' hence must take care that - * thread->cpu is NULL - */ - if (!thread->hist) - { - struct cpu_thread_history tmp; - - tmp.func = thread->func; - tmp.funcname = thread->funcname; - - thread->hist = hash_get (cpu_record, &tmp, - (void * (*) (void *))cpu_record_hash_alloc); - } - GETRUSAGE (&thread->ru); (*thread->func) (thread); @@ -1179,17 +1461,23 @@ thread_call (struct thread *thread) GETRUSAGE (&ru); realtime = thread_consumed_time (&ru, &thread->ru, &cputime); - thread->hist->real.total += realtime; - if (thread->hist->real.max < realtime) - thread->hist->real.max = realtime; + + if (thread->hist != NULL) + { + LOCK + thread->hist->real.total += realtime; + if (thread->hist->real.max < realtime) + thread->hist->real.max = realtime; #ifdef HAVE_RUSAGE - thread->hist->cpu.total += cputime; - if (thread->hist->cpu.max < cputime) - thread->hist->cpu.max = cputime; + thread->hist->cpu.total += cputime; + if (thread->hist->cpu.max < cputime) + thread->hist->cpu.max = cputime; #endif - ++(thread->hist->total_calls); - thread->hist->types |= (1 << thread->add_type); + ++(thread->hist->total_calls); + thread->hist->types |= (1 << thread->add_type); + UNLOCK + } ; #ifdef CONSUMED_TIME_CHECK if (realtime > CONSUMED_TIME_CHECK) @@ -1200,24 +1488,23 @@ thread_call (struct thread *thread) * to fix. */ zlog_warn ("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)", - thread->funcname, + (thread->hist != NULL) ? thread->hist->funcname : "??", (unsigned long) thread->func, realtime/1000, cputime/1000); } #endif /* CONSUMED_TIME_CHECK */ - XFREE (MTYPE_THREAD_FUNCNAME, thread->funcname); } /* Execute thread */ struct thread * funcname_thread_execute (struct thread_master *m, - int (*func)(struct thread *), + int (*func)(struct thread *), void *arg, int val, const char* funcname) { - struct thread dummy; + struct thread dummy; memset (&dummy, 0, sizeof (struct thread)); @@ -1227,10 +1514,24 @@ funcname_thread_execute (struct thread_master *m, dummy.func = func; dummy.arg = arg; dummy.u.val = val; - dummy.funcname = strip_funcname (funcname); + dummy.hist = thread_get_hist(&dummy, funcname) ; thread_call (&dummy); - XFREE (MTYPE_THREAD_FUNCNAME, dummy.funcname); - return NULL; } + +/* Second state initialisation if qpthreaded */ +void +thread_init_r (void) +{ + qpt_mutex_init(&thread_mutex, qpt_mutex_quagga); +} + +/* Finished with module */ +void +thread_finish (void) +{ + qpt_mutex_destroy(&thread_mutex, 0); +} + +#undef USE_MQUEUE diff --git a/lib/thread.h b/lib/thread.h index 978aa6b0..a38fb19a 100644 --- a/lib/thread.h +++ b/lib/thread.h @@ -16,12 +16,17 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_THREAD_H #define _ZEBRA_THREAD_H +#include <sys/resource.h> +#include "qtime.h" +#include "qpnexus.h" +#include "qtimers.h" + struct rusage_t { #ifdef HAVE_RUSAGE @@ -64,22 +69,22 @@ struct thread { thread_type type; /* thread type */ thread_type add_type; /* thread type */ - struct thread *next; /* next pointer of the thread */ + struct thread *next; /* next pointer of the thread */ struct thread *prev; /* previous pointer of the thread */ struct thread_master *master; /* pointer to the struct thread_master. */ int (*func) (struct thread *); /* event function */ void *arg; /* event argument */ union { - int val; /* second argument of the event. */ + int val; /* second argument of the event. */ int fd; /* file descriptor in case of read/write. */ - struct timeval sands; /* rest of time sands value. */ + struct timeval sands; /* rest of time sands value. */ + qtimer qtr ; /* pointer to related qtimer */ } u; RUSAGE_T ru; /* Indepth usage info. */ struct cpu_thread_history *hist; /* cache pointer to cpu_history */ - char* funcname; }; -struct cpu_thread_history +struct cpu_thread_history { int (*func)(struct thread *); char *funcname; @@ -163,8 +168,11 @@ enum quagga_clkid { /* Prototypes. */ extern struct thread_master *thread_master_create (void); extern void thread_master_free (struct thread_master *); +extern void thread_init_r (void); +extern void thread_finish (void); +extern void thread_set_qtimer_pile(qtimer_pile pile) ; -extern struct thread *funcname_thread_add_read (struct thread_master *, +extern struct thread *funcname_thread_add_read (struct thread_master *, int (*)(struct thread *), void *, int, const char*); extern struct thread *funcname_thread_add_write (struct thread_master *, @@ -190,6 +198,8 @@ extern struct thread *funcname_thread_execute (struct thread_master *, extern void thread_cancel (struct thread *); extern unsigned int thread_cancel_event (struct thread_master *, void *); extern struct thread *thread_fetch (struct thread_master *, struct thread *); +extern int thread_dispatch(struct thread_master *m) ; +extern int thread_dispatch_background(struct thread_master *m) ; extern void thread_call (struct thread *); extern unsigned long thread_timer_remain_second (struct thread *); extern int thread_should_yield (struct thread *); diff --git a/lib/uty.h b/lib/uty.h new file mode 100644 index 00000000..d1a8b584 --- /dev/null +++ b/lib/uty.h @@ -0,0 +1,239 @@ +/* VTY internal stuff -- header + * Copyright (C) 1997 Kunihiro Ishiguro + * + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_UTY_H +#define _ZEBRA_UTY_H + +#include <stdbool.h> + +#include "qpthreads.h" +#include "qpnexus.h" +#include "thread.h" +#include "list_util.h" +#include "vty.h" +#include "node_type.h" + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*============================================================================== + * This is stuff which is used by the close family of: + * + * vty + * command + * command_queue + * log + * + * and which is not for use elsewhere. + * + * There are also: + * + * vty_io + * vty_cli + * + * Which are the "immediate family" of vty: + * + * * *nothing* in their ".h" is for use anywhere except the immediate family. + * + * * things for use within the rest of the family are published here. + */ + +/*============================================================================== + * Variables in vty.c -- used in any of the family + */ +extern vty_io vio_list_base ; +extern vty_io vio_monitors_base ; +extern vty_io vio_death_watch ; + +union vty_watch_dog +{ + qtimer qnexus ; /* when running qnexus */ + struct thread* thread; /* when running threads */ + void* anon ; +}; + +extern union vty_watch_dog vty_watch_dog ; + +extern struct thread_master* vty_master ; + +extern unsigned long vty_timeout_val ; + +extern bool vty_config ; + +extern bool no_password_check ; +extern const bool restricted_mode_default ; +extern bool restricted_mode ; + +char *vty_accesslist_name ; +char *vty_ipv6_accesslist_name ; + +extern qpn_nexus vty_cli_nexus ; +extern qpn_nexus vty_cmd_nexus ; + +/*============================================================================== + * To make vty qpthread safe we use a single mutex. + * + * vty and log recurse through each other, so the same mutex is used + * for both, i.e. they are treated as being part of the same monitor. + * + * A recursive mutex is used. This simplifies the calling from log to vty and + * back again. It also allows for the vty internals to call each other. + * + * There are some "uty" functions which assume the mutex is locked. + * + * vty is closely bound to the command handling -- the main vty structure + * contains the context in which commands are parsed and executed. + */ + +extern qpt_mutex_t vty_mutex ; + +#ifdef NDEBUG +# define VTY_DEBUG 0 /* NDEBUG override */ +#else +# ifndef VTY_DEBUG +# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */ +# endif +#endif + +#if VTY_DEBUG + +extern int vty_lock_count ; +extern int vty_assert_fail ; + +#endif + +Inline void +VTY_LOCK(void) +{ + qpt_mutex_lock(&vty_mutex) ; + if (VTY_DEBUG) + ++vty_lock_count ; +} ; + +Inline void +VTY_UNLOCK(void) +{ + if (VTY_DEBUG) + --vty_lock_count ; + qpt_mutex_unlock(&vty_mutex) ; +} ; + +/* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that + * is required. + * + * In some cases, need also to be running in the CLI thread as well. + */ +#if VTY_DEBUG + +Inline void +VTY_ASSERT_FAILED(void) +{ + if (vty_assert_fail == 0) ; + { + vty_assert_fail = 1 ; + assert(0) ; + } ; +} ; + +Inline void +VTY_ASSERT_LOCKED(void) +{ + if (vty_lock_count == 0) + VTY_ASSERT_FAILED() ; +} ; + +Inline void +VTY_ASSERT_CLI_THREAD(void) +{ + if (qpthreads_enabled) + if (!qpt_thread_is_self(vty_cli_nexus->thread_id)) + VTY_ASSERT_FAILED() ; +} ; + +#else + +#define VTY_ASSERT_LOCKED() +#define VTY_ASSERT_CLI_THREAD() + +#endif + +/*============================================================================== + * Shared definitions + */ + +enum cli_do +{ + cli_do_nothing = 0, /* no action required */ + + cli_do_command, /* dispatch the current command line */ + cli_do_ctrl_c, /* received ^c */ + cli_do_ctrl_d, /* received ^d on empty line */ + cli_do_ctrl_z, /* received ^z */ + + cli_do_eof, /* hit "EOF" */ + + cli_do_count /* number of different cli_do_xxx */ +} ; + +/*============================================================================== + * Functions in vty.c -- used in any of the family + */ +extern enum cmd_return_code uty_command(struct vty *vty) ; +extern enum cmd_return_code uty_auth (struct vty *vty, const char *buf, + enum cli_do cli_do) ; +extern enum cmd_return_code vty_cmd_exit(struct vty* vty) ; +extern enum cmd_return_code vty_cmd_end(struct vty* vty) ; +extern enum cmd_return_code uty_cmd_close(struct vty *vty, const char* reason) ; +extern enum cmd_return_code uty_stop_input(struct vty *vty) ; +extern enum cmd_return_code uty_end_config (struct vty *vty) ; +extern enum cmd_return_code uty_down_level (struct vty *vty) ; + +extern bool vty_config_lock (struct vty *, enum node_type node); +extern void vty_config_unlock (struct vty *, enum node_type node); +extern void uty_config_unlock (struct vty *vty, enum node_type node) ; + +/*============================================================================== + * Functions in vty_cli -- used outside the immediate vty family + */ +extern void vty_queued_result(struct vty* vty, enum cmd_return_code ret); +extern void uty_set_host_name(const char* name) ; + +/*============================================================================== + * Functions in vty_io -- used outside the immediate vty family + */ +extern void vty_open_config_write(struct vty* vty, int fd) ; +extern int vty_close_config_write(struct vty*) ; + +extern void vty_log_fixed (const char *buf, size_t len); + +extern void uty_log (struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va); + +/*============================================================================== + * Functions in command.c + */ +extern void cmd_post_command(struct vty* vty, int ret) ; + +#endif /* _ZEBRA_UTY_H */ diff --git a/lib/vector.c b/lib/vector.c index 7c148628..e03febdc 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -1,6 +1,9 @@ -/* Generic vector interface routine +/* Generic vector interface routine -- functions * Copyright (C) 1997 Kunihiro Ishiguro * + * 24-Nov-2009 -- extended to add a number of new operations on vectors. + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it @@ -16,7 +19,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -24,166 +27,1262 @@ #include "vector.h" #include "memory.h" -/* Initialize vector : allocate memory and return vector. */ +/* Vectors are implemented as a structure which points to an array of pointers + * to vector items. That array -- the body of the vector -- can change size, + * and therefore may move around in memory. + * + * The vector structure may be statically allocated, embedded in another + * structure, or allocated dynamically. In any case the vector operations + * require the address of the vector structure -- see typedef for vector. + * + * Vector items are accessed by index, fetching and storing pointers to + * those items. Can also push and pop items. + * + * A vector has a known (logical) end position. Everything beyond the end is + * defined to be NULL. When a vector is initialised it is set empty. + * + * At any given moment the vector body has a limit on the number of items it + * can accommodate (the physical end). + * + * A vector will grow to accommodate what is put into it. Adding items beyond + * the (logical) end moves it. Adding items beyond the (physical) limit causes + * the body to be extended to suit, and to leave some spare space for future + * expansion. + * + * While the vector is small (see VECTOR_LIMIT_DOUBLE_MAX) the body will grow by + * doubling in size. When it is larger, it grows to be multiples of + * VECTOR_LIMIT_DOUBLE_MAX. + * + * Deleting items reduces the (logical) end position, but does NOT release + * memory -- the (physical) limit is not changed. + * + * To release memory: vector_chop will release everything beyond the current + * end; vector_decant will create a new body of exactly the current size, + * releasing the old body; vector_discard will release everything beyond a + * given position. + * + * NB: you can set a vector item to be NULL. If you set a vector item beyond + * the current end, NULL items are inserted in the vector. + * + * NB: when setting a vector item it is the caller's responsibility to + * deallocate any pre-existing value of the item. + * + * NB: when deleting items it is also the caller's responsibility to deallocate + * any values that require it. + * + * Implementation Notes + * + * Everything beyond the (logical) end is implicitly NULL. + * + * Actual memory between (logical) end and (physical) limit is UNDEFINED. So + * when advancing the end some care has to be taken to ensure that any new + * items in the vector are either set to something or cleared to NULL. + * + * It would have been possible to ensure that everything between end and limit + * is cleared to NULL, but that is more work -- in particular it creates work + * when it is not always required. + */ + +#define P_ITEMS_SIZE(n) SIZE(p_vector_item, n) +/*============================================================================== + * Initialisation, allocation, reset etc. + */ + +/* Initialise a brand new vector, setting it empty. + * + * Allocates vector structure if none given -- that is, if v == NULL. + * + * If size is given as zero, no body is allocated, otherwise body of exactly + * the required size is allocated. + * + * NB: discards any existing vector body -- so it is the caller's responsibility + * to release any existing body, and any items in that body. + */ vector -vector_init (unsigned int size) +vector_init_new(vector v, unsigned int size) { - vector v = XCALLOC (MTYPE_VECTOR, sizeof (struct _vector)); + if (v == NULL) + v = XCALLOC(MTYPE_VECTOR, sizeof(struct vector)) ; + else + memset(v, 0, sizeof(struct vector)) ; - /* allocate at least one slot */ - if (size == 0) - size = 1; + if (size != 0) + { + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(size)) ; + v->limit = size ; + } ; - v->alloced = size; - v->active = 0; - v->index = XCALLOC (MTYPE_VECTOR_INDEX, sizeof (void *) * size); - return v; -} + return v ; +} ; + +/* Initialize vector : allocate memory and return vector. + * allocates body with at least 1 entry. + */ +vector +vector_init (unsigned int size) +{ + return vector_init_new(NULL, size ? size : 1) ; /* at least 1 entry */ +} ; +/* Basic: free the vector body and the vector structure. */ void -vector_only_wrapper_free (vector v) +vector_free (vector v) { + XFREE (MTYPE_VECTOR_BODY, v->p_items); XFREE (MTYPE_VECTOR, v); } +/* Re-Initialize vector (or create new one), setting it empty. + * + * Allocates vector structure if none given -- that is, if v == NULL. + * + * If size is given as zero, no body is allocated, but any existing body is + * retained. (vector_reset() will discard body.) + * + * Otherwise ensures existing body is at least the required size, or a body + * of exactly the required size is allocated. + * + * NB: when re-initialising an existing vector it is the caller's + * responsibility to release any vector item values *before* doing this. + * */ +vector +vector_re_init(vector v, unsigned int size) +{ + if ((v == NULL) || (v->p_items == NULL)) + return vector_init_new(v, size) ; + + v->end = 0 ; + + if (v->limit < size) + { + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(size)) ; + v->limit = size ; + } ; + + return v ; +} ; + +/* Free the vector body, and free the vector structure or reset it. + * + * Return NULL if releases vector, otherwise the address of the vector. + * + * NB: it is the caller's responsibility to release any vector item values + * *before* doing this. + */ +vector +vector_reset(vector v, int free_structure) +{ + if (v == NULL) + return NULL ; /* allow for already freed vector */ + + if (v->p_items != NULL) + XFREE(MTYPE_VECTOR_BODY, v->p_items) ; + + if (free_structure) + { + XFREE(MTYPE_VECTOR, v) ; + return NULL ; + } + else + return vector_init_new(v, 0) ; +} ; + +/* Set vector length to be (at least) the given fixed length. + * + * There must be a vector. + * + * Does nothing if the vector is already as long or longer than the given + * length. + * + * If the body is not big enough for the new length, allocates or extends to + * exactly the new length. Otherwise, leaves body as it is. + * + * Appends NULLs as required to extend to the required length. + * + * Note that the existing contents of the vector are preserved in all cases. + */ +extern void +vector_set_new_min_length(vector v, unsigned int len) +{ + assert (v != NULL) ; + + if (len > v->limit) + { + if (v->p_items == NULL) + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ; + else + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(len)); + v->limit = len ; + } ; + + if (v->end < len) + vector_extend(v, len) ; +} ; + +/* Pop item from vector, stepping past any NULLs. + * + * If vector is empty, free the body and, if required, the vector structure. + * + * Useful for emptying out and discarding a vector: + * + * while ((p_v = vector_ream_out(v, 1))) + * ... do what's required to release the item p_v + * + * Returns NULL if vector was empty and has now been freed as required. + */ +p_vector_item +vector_ream(vector v, int free_structure) +{ + p_vector_item p_v ; + + if (v == NULL) + return NULL ; + + while (v->end != 0) + { + p_v = v->p_items[--v->end] ; + if (p_v != NULL) + return p_v ; /* return non-NULL item */ + } ; + + /* vector is empty: free the body, and (if required) the vector structure. */ + vector_reset(v, free_structure) ; + + return NULL ; /* signals end */ +} ; + +/*============================================================================== + * Unset item, condensing and trimming vector. + */ + +/* Trim any NULL entries at the current (logical) end of the vector. + * + * Returns the (new) end of the vector. + */ +extern vector_index +vector_trim(vector v) +{ + vector_index i = v->end ; + while ((i > 0) && (v->p_items[i - 1] == NULL)) + --i ; + v->end = i ; + return i ; +} ; + +/* Removes any NULL entries from the given vector. + * + * Returns the (new) end of the vector. + */ +extern vector_index vector_condense(vector v) +{ + vector_index i = 0 ; + vector_index j ; + + /* Find first NULL, if any */ + while ((i < v->end) && (v->p_items[i] != NULL)) + ++i ; + + /* Quit if no NULLs (or vector is empty) */ + if (i == v->end) + return i ; + + /* Shuffle any remaining non-NULL down */ + for (j = i + 1 ; j < v->end ; ++j) + if (v->p_items[j] != NULL) + v->p_items[i++] = v->p_items[j] ; + + v->end = i ; + + return i ; +} ; + +/* Unset item at given index (ie set it NULL). + * + * Return the old value of the item. + * + * If the item at the current (logical) end of the vector is NULL, move the + * end backwards until finds a non-NULL item, or the vector becomes empty. + */ +extern p_vector_item +vector_unset_item(vector v, vector_index i) +{ + p_vector_item was ; + + if (i < v->end) + { + was = v->p_items[i] ; + v->p_items[i] = NULL ; + } + else if (v->end == 0) + return NULL ; /* avoid test for last entry NULL if is empty */ + else + was = NULL ; + + if (v->p_items[v->end - 1] == NULL) + vector_trim(v) ; + + return was ; +} ; + +/*============================================================================== + * Inserting and deleting items. + */ + +/* Insert item in vector, before item at given position + * Move items and extend vector as required. + */ void -vector_only_index_free (void *index) +vector_insert_item(vector v, vector_index i, void* p_v) { - XFREE (MTYPE_VECTOR_INDEX, index); -} + if ((i == v->end) && (i < v->limit)) + ++v->end ; + else + vector_insert(v, i, 1) ; + v->p_items[i] = (p_vector_item)p_v ; +} ; + +/* Insert item in vector at given position with rider: + * + * rider < 0 -- insert before the item at the given position + * rider == 0 -- insert at the given position -- REPLACING any existing value + * rider > 0 -- insert after the item at the given position + * + * NB: when an item is replaced, it is the caller's responsibility to release + * any memory used by the item, if required. + * + * Move items and extend vector as required. + */ void -vector_free (vector v) +vector_insert_item_here(vector v, vector_index i, int rider, + p_vector_item p_v) { - XFREE (MTYPE_VECTOR_INDEX, v->index); - XFREE (MTYPE_VECTOR, v); -} + if (rider == 0) + return vector_set_item(v, i, p_v) ; + + if (rider > 0) + ++i ; /* insert before next item */ + vector_insert_item(v, i, p_v) ; +} ; + +/* Delete item from vector. + * + * Move items as required. Reduces number of items in the vector (unless + * the item in question is beyond the end of the vector !) + * + * NB: it is the caller's responsibility to release memory used by any + * current value of the item, if required. + * + * NB: does NOT change the size of the vector body. + */ +p_vector_item +vector_delete_item(vector v, vector_index i) +{ + p_vector_item p_e ; + if (i < v->end) + { + p_e = v->p_items[i] ; /* pick up the current value */ + if (i != (v->end - 1)) + vector_delete(v, i, 1) ; + else + v->end = i ; + return p_e ; + } + else + return NULL ; +} ; + +/*============================================================================== + * Moving items within vector. + */ + +/* Move item in vector from source position to destination position. + * Moves intervening items up or down as required. + * Extends vector to include the destination, if required. + * A source item beyond the end of the vector is implicitly NULL. + */ +void +vector_move_item(vector v, vector_index i_dst, vector_index i_src) +{ + p_vector_item* pp_s ; + p_vector_item* pp_d ; + p_vector_item p_e ; + + vector_index old_end = v->end ; + + /* Worry about whether both source and destination exist. */ + if (i_dst >= old_end) + { + vector_insert(v, i_dst, 1) ; /* ensure destination exists */ + if (i_src >= old_end) + return ; /* both were beyond the end */ + } + else if (i_src >= old_end) + { + i_src = old_end ; /* clamp to just beyond last */ + vector_insert(v, i_src, 1) ; /* create empty entry */ + } ; + + if (i_dst == i_src) /* avoid work and edge case */ + return ; + + /* Both src and dst are within the vector and src != dst */ + pp_s = &v->p_items[i_src] ; /* address of src entry */ + pp_d = &v->p_items[i_dst] ; /* address of dst entry */ + p_e = *pp_s ; /* pick up item to move */ + if (i_src < i_dst) + memmove(pp_s, pp_s+1, P_ITEMS_SIZE(i_dst - i_src)) ; + else + memmove(pp_d+1, pp_d, P_ITEMS_SIZE(i_src - i_dst)) ; + *pp_d = p_e ; /* put down the item to move */ +} ; + +/* Move item in vector to given position with rider: + * + * rider < 0 -- move to before the item at the given position + * rider == 0 -- move to replace item at the given position + * rider > 0 -- move to after the item at the given position + * + * NB: it is the caller's responsibility to release the any existing value + * that will be replaced. + * + * NB: replacing an item by itself (rider == 0, i_dst == i_src) deletes it ! + * + * Move items and extend vector as required. + */ +void +vector_move_item_here(vector v, vector_index i_dst, int rider, + vector_index i_src) +{ + if (rider != 0) + { + if (i_src < i_dst) + { + if (rider < 0) + --i_dst ; /* moving up places src after dst */ + } + else if (i_src > i_dst) + { + if (rider > 0) + ++i_dst ; /* moving down places src before dst */ + } ; + + vector_move_item(v, i_dst, i_src) ; + } + else + { + /* to replace: copy and then delete. */ + vector_set_item(v, i_dst, vector_get_item(v, i_src)) ; + vector_delete_item(v, i_src) ; + } ; +} ; + +/* Reverse vector: reverse the order of items in the vector. + */ +void +vector_reverse(vector v) +{ + if (v != NULL) + vector_part_reverse(v, 0, v->end) ; +} ; + +/* Reverse portion of vector. + */ +void +vector_part_reverse(vector v, vector_index i, unsigned int n) +{ + vector_index j ; + + if (v == NULL) + return ; + + if ((i + n) > v->limit) + vector_extend(v, i + n) ; /* ensure portion exists */ + + if (n <= 1) + return ; + j = i + n - 1 ; /* j > i, because n > 1 */ + do + { + p_vector_item p_i = v->p_items[i] ; + v->p_items[i++] = v->p_items[j] ; + v->p_items[j--] = p_i ; + } while (j > i) ; +} ; + +/*============================================================================== + * Copying, moving and appending entire vectors. + */ + +static void vector_new_limit(vector v, vector_index new_end) ; + +/* Shallow vector copy -- copies pointers to item values, not the values. */ +/* Creates a new vector. */ +/* NB: copies whole body, including stuff beyond (logical) end ! */ +/* TODO: is this behaviour required ? */ vector vector_copy (vector v) { - unsigned int size; - vector new = XCALLOC (MTYPE_VECTOR, sizeof (struct _vector)); + vector new = vector_init_new(NULL, v->limit) ; - new->active = v->active; - new->alloced = v->alloced; + new->end = v->end; - size = sizeof (void *) * (v->alloced); - new->index = XCALLOC (MTYPE_VECTOR_INDEX, size); - memcpy (new->index, v->index, size); + if (v->limit > 0) + memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->limit)) ; return new; } -/* Check assigned index, and if it runs short double index pointer */ -void -vector_ensure (vector v, unsigned int num) -{ - if (v->alloced > num) - return; - - v->index = XREALLOC (MTYPE_VECTOR_INDEX, - v->index, sizeof (void *) * (v->alloced * 2)); - memset (&v->index[v->alloced], 0, sizeof (void *) * v->alloced); - v->alloced *= 2; - - if (v->alloced <= num) - vector_ensure (v, num); -} +/* Shallow vector copy -- copies pointers to item values, not the values. + * Creates a new vector or re-initialises an existing one. + * + * NB: creates new vector with same limit as existing one, but copies only + * the known items (ie up to end, not up to limit). + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to release any existing items if that is required. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to ensure the vector structure is currently valid. + * + * NB: do NOT try copying a vector to itself !! + */ +vector +vector_copy_here(vector dst, vector src) +{ + assert((src != NULL) && (src != dst)) ; -/* This function only returns next empty slot index. It dose not mean - the slot's index memory is assigned, please call vector_ensure() - after calling this function. */ -int -vector_empty_slot (vector v) + dst = vector_re_init(dst, src->limit) ; + + dst->end = src->end; + + if (src->end > 0) + memcpy(dst->p_items, src->p_items, P_ITEMS_SIZE(src->end)) ; + + return dst ; +} ; + +/* Vector move -- moves body of vector. + * Creates a new vector or re-initialises an existing one. + * Leaves the source vector empty -- does not release the structure. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to release any existing items if that is required. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to ensure the vector structure is currently valid. + * + * NB: do NOT try moving a vector to itself !! + */ +vector +vector_move_here(vector dst, vector src) { - unsigned int i; + assert((src != NULL) && (src != dst)) ; - if (v->active == 0) - return 0; + if (dst != NULL) + dst = vector_reset(dst, 0) ; /* Reset to deallocate any existing body */ + else + dst = vector_init_new(dst, 0) ; /* Create new structure sans body. */ - for (i = 0; i < v->active; i++) - if (v->index[i] == 0) - return i; + *dst = *src ; /* Copy the vector structure */ - return i; + vector_init_new(src, 0) ; /* Set empty, forgetting body */ + + return dst ; } +/* Shallow vector copy append -- copies pointers to item values, not the values. + * Appends copied pointers to the destination vector. + * Creates a new destination vector if required. + * + * NB: Can append to self. + */ +vector +vector_copy_append(vector dst, vector src) +{ + vector_index new_end ; + + assert(src != NULL) ; + + if (dst != NULL) + { + new_end = dst->end + src->end ; + if (new_end > dst->limit) + vector_new_limit(dst, new_end) ; + } + else + { + new_end = src->end ; + vector_init_new(dst, new_end) ; + } ; + + if (src->end) + memcpy(&dst->p_items[dst->end], src->p_items, P_ITEMS_SIZE(src->end)) ; + + dst->end = new_end ; /* Done last, allows for append to self ! */ + return dst ; +} ; + +/* Vector move append -- moves pointers to item values. + * Appends moved pointers to the destination vector. + * Creates a new destination vector if required (dst == NULL). + * Leaves the source vector empty -- does not release the structure. + * + * NB: do NOT try moving a vector to itself !! + */ +vector +vector_move_append(vector dst, vector src) +{ + assert((src != NULL) && (src != dst)) ; + + if ((dst == NULL) || (dst->end == 0)) + return vector_move_here(dst, src) ; /* Easy way to do it if dst empty */ + + vector_copy_append(dst, src) ; /* Extend dst and copy src */ + vector_init_new(src, 0) ; /* Set empty, forgetting body */ + + return dst ; +} ; + +/*============================================================================== + * Portmanteau splice/extract/replace function. + * + * All take a portion of 'src' vector and: + * + * splice: + * + * a) replace a portion of the 'dst' vector by a portion of the 'src' vector + * copying the replaced portion of the 'dst' vector to the 'to' vector + * b) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- true + * to -- vector, or NULL => allocate new vector + * dst -- vector + * i_dst -- start of portion to replace + * n_dst -- length of portion to replace. 0 => insertion. + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: the (possibly new) 'to' vector + * + * NB: 'to', 'dst' and 'src' must be distinct vectors. + * + * extract: + * + * a) copy a portion of the 'src' vector to the 'to' vector + * c) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- true + * to -- vector, or NULL => allocate new vector + * dst -- NULL + * i_dst -- ignored + * n_dst -- ignored + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: the (possibly new) 'to' vector + * + * NB: 'to' and 'src' must be distinct vectors. + * + * replace: + * + * a) replace a portion of the 'dst' vector by a portion of the 'src' vector + * b) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- false + * to -- ignored + * dst -- vector + * i_dst -- start of portion to replace + * n_dst -- length of portion to replace. 0 => insertion. + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: original 'to' argument + * + * NB: 'dst' and 'src' must be distinct vectors. + * + * All copies are shallow -- pointers to item values are copied, not the values. + * + * NB: any existing contents of the 'to' vector are discarded. + * + * NB: it is the caller's responsibility to release memory allocated to any + * items which are discarded in these operations. + * + * NB: for splice and replace, the resulting destination vector will be at + * least i_dst + n_src long. (Even if is copying actual or implied NULLs + * from the source.) + * + * NB: where new vectors are created, they will be of exactly the required size. + * + * NB: where an existing vector is reused, it is the caller's responsibility + * to ensure the vector structure is currently valid (by vector_init_new() + * or by ensuring it is zeroized). + */ +vector +vector_sak(int to_copy, vector to, + vector dst, vector_index i_dst, unsigned int n_dst, + vector src, vector_index i_src, unsigned int n_src, int src_move) +{ + int dst_replace ; /* true => replace portion of 'dst' */ + + vector_index new_dst_end = 0 ; /* new end of dst */ + + unsigned int n_dst_nulls ; /* number of implicit NULLs to add */ + unsigned int n_dst_move ; /* number of items to move up or down */ + unsigned int n_src_real ; /* number of items to really copy */ + unsigned int n_src_nulls ; /* number of implicit NULLs to "copy" */ + + assert((to == NULL) || (dst == NULL) || (to != dst)) ; + assert((src == NULL) || (dst == NULL) || (src != dst)) ; + + /* Worry about how much we really have in the source vector. */ + + n_src_real = n_src ; /* assume all real */ + n_src_nulls = 0 ; /* so no NULLs to "copy" */ + + if (n_src != 0) + { + if ((src == NULL) || (i_src >= src->end)) + n_src_real = 0 ; + else if ((i_src + n_src) > src->end) + n_src_real = src->end - i_src ; + n_src_nulls = n_src - n_src_real ; + } ; + + /* If no 'dst' vector, then this is an extract. */ + + n_dst_move = 0 ; /* assume nothing to move */ + n_dst_nulls = 0 ; /* assume no NULLs to add */ + + if (dst == NULL) + /* For extract: set up dst, i_dst and n_dst so that can copy to the */ + /* 'to' vector as if from 'dst'. */ + { + dst_replace = 0 ; /* no replacement operation */ + dst = src ; /* copy from here */ + i_dst = i_src ; + n_dst = n_src_real ; + } + else + /* Reduce n_dst to the number of actual items to be replaced. */ + /* */ + /* Calculate the new end of 'dst'. */ + { + dst_replace = 1 ; /* have replacement to do */ + if (i_dst >= dst->end) + /* If i_dst is beyond the end of 'dst', then there is nothing */ + /* to replace (so set n_dst == 0). Will be adding n_src items */ + /* at i_dst -- so new end must be i_dst + n_src. */ + { + n_dst_nulls = i_dst - dst->end ; /* fill from end to i_dst */ + n_dst = 0 ; /* nothing to replace */ + new_dst_end = i_dst + n_src ; /* all beyond current end */ + } + else + /* If i_dst + n_dst is beyond the end of 'dst', reduce n_dst to */ + /* number of items up to the end. */ + /* Will remove n_dst items and insert n_src, so end will move */ + /* by n_src - n_dst. */ + { + if ((i_dst + n_dst) > dst->end) + n_dst = dst->end - i_dst ; /* what we actually replace */ + else if (n_dst != n_src) + n_dst_move = dst->end - (i_dst + n_dst) ; + /* what we move up or down */ + + new_dst_end = dst->end + n_src - n_dst ; + /* end depends on amount added */ + /* & amount actually replaced */ + } ; + } ; + + /* Copy portion of 'dst' (or of 'src') to 'to', if required. */ + /* */ + /* Have arranged: n_dst -- number of items to copy, all existent */ + /* dst -- vector to copy from -- if n_dst > 0 */ + /* i_dst -- first item to copy -- if n_dst > 0 */ + + if (to_copy) + { + to = vector_re_init(to, n_dst) ; /* reinitialise or create */ + to->end = n_dst ; + if (n_dst > 0) + memcpy(to->p_items, &dst->p_items[i_dst], P_ITEMS_SIZE(n_dst)) ; + } ; + + /* Replace portion of 'dst' by portion of 'src', if required. */ + /* */ + /* Have arranged: */ + /* */ + /* new_dst_end -- end of dst once dust settles */ + /* n_dst_nulls -- number of NULLs to insert at dst->end to fill up */ + /* to i_dst (when i_dst is beyond old end.) */ + /* n_dst_move -- number of items in dst to move up or down to */ + /* leave n_src item hole at i_dst to fill in. */ + /* n_src_real -- number of real src items at i_src to copy to dst */ + /* at i_dst. */ + /* n_src_nulls -- number of nulls to add to fill to i_dst + n_src. */ + + if (dst_replace) + { + if (new_dst_end > dst->limit) /* extend body if required */ + vector_new_limit(dst, new_dst_end) ; + + if (n_dst_nulls > 0) + memset(&dst->p_items[dst->end], 0, P_ITEMS_SIZE(n_dst_nulls)) ; + if (n_dst_move > 0) + memmove(&dst->p_items[i_dst + n_dst], &dst->p_items[i_dst + n_src], + P_ITEMS_SIZE(n_dst_move)) ; + if (n_src_real > 0) + memcpy(&dst->p_items[i_dst], &src->p_items[i_src], + P_ITEMS_SIZE(n_src_real)) ; + if (n_src_nulls > 0) + memset(&dst->p_items[i_dst + n_src_real], 0, + P_ITEMS_SIZE(n_src_nulls)) ; + dst->end = new_dst_end ; + } ; + + /* Delete portion of 'src', if required (and have 'src' !) */ + + if (src_move && (n_src_real != 0)) + vector_delete(src, i_src, n_src_real) ; + + /* Done -- return 'to' vector as promised. */ + + return to ; +} ; + +/*============================================================================== + * Legacy Vector Operations + */ + /* Set value to the smallest empty slot. */ int vector_set (vector v, void *val) { - unsigned int i; + vector_index i; - i = vector_empty_slot (v); - vector_ensure (v, i); + i = 0 ; + while (1) + { + if (i == v->end) + { + i = vector_extend_by_1(v) ; + break ; + } + + if (v->p_items[i] == NULL) + break ; - v->index[i] = val; + ++i ; + } ; - if (v->active <= i) - v->active = i + 1; + v->p_items[i] = val; return i; } /* Set value to specified index slot. */ int -vector_set_index (vector v, unsigned int i, void *val) +vector_set_index (vector v, vector_index i, void *val) { vector_ensure (v, i); - - v->index[i] = val; - - if (v->active <= i) - v->active = i + 1; - + v->p_items[i] = val; return i; } /* Look up vector. */ -void * -vector_lookup (vector v, unsigned int i) +p_vector_item +vector_lookup (vector v, vector_index i) { - if (i >= v->active) + if (i >= v->end) return NULL; - return v->index[i]; + return v->p_items[i]; } /* Lookup vector, ensure it. */ -void * -vector_lookup_ensure (vector v, unsigned int i) +p_vector_item +vector_lookup_ensure (vector v, vector_index i) { vector_ensure (v, i); - return v->index[i]; + return v->p_items[i]; } -/* Unset value at specified index slot. */ +/* Count the number of not empty slots. */ +vector_index +vector_count (vector v) +{ + vector_index i; + unsigned count = 0; + + for (i = 0; i < v->end; i++) + if (v->p_items[i] != NULL) + count++; + + return count; +} + +/*============================================================================== + * Sorting and Searching vector. + */ + +/* Sort the given vector. + * + * NB: the comparison function receives a pointer to the pointer to the + * vector item's value. + * + * NB: if there are NULL items in the vector, the comparison function MUST + * be ready for them. + */ +void +vector_sort(vector v, vector_sort_cmp* cmp) +{ + if (v->end <= 1) + return ; /* Stop dead if 0 or 1 items */ + + typedef int qsort_cmp(const void*, const void*) ; + + qsort(v->p_items, v->end, sizeof(p_vector_item), (qsort_cmp*)cmp) ; +} ; + +/* Perform binary search on the given vector. + * + * The vector MUST be sorted in the order implied by the comparison function + * given. The vector need not contain unique values, but this search makes + * no effort to select any particular instance of a sequence of equals. + * + * Returns: + * + * result == 0: found an equal value. + * index returned is of first entry found which is equal to + * the value sought. There may be other equal values, before + * and/or after this one in the vector. + * + * result == +1: value not found and vector not empty. + * index returned is of the largest entry whose value is less + * than the value sought. + * (The value sought belongs after this point.) + * + * result == -1: value is less than everything in the vector, or the + * vector is empty. + * index returned is 0 + * + * NB: The comparison function takes arguments which are: + * + * const void** pointer to pointer to value being searched for. + * const void** pointer to pointer to vector item to compare with + * + * The value being searched for need not be in the same form as a vector + * item. However, if it is then the same comparison function can be used + * to sort and search. + * + * NB: if there are NULL items in the vector, the comparison function MUST + * be ready for them. + */ +vector_index +vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, + int* result) +{ + vector_index il, iv, ih ; + int c ; + + if (v->end <= 1) + { + *result = (v->end == 0) ? -1 : + cmp(&p_val, (const void* const*)&v->p_items[0]) ; + return 0 ; /* Stop dead if 0 or 1 items */ + } ; + + /* We have at least two items. */ + il = 0 ; + ih = v->end - 1 ; + + /* Pick off the edge cases: >= last and <= first. */ + if ((c = cmp(&p_val, (const void* const*)&v->p_items[ih])) >= 0) + { + *result = c ; /* 0 => found. +1 => val > last */ + return ih ; /* return high index. */ + } ; + if ((c = cmp(&p_val, (const void* const*)&v->p_items[il])) <= 0) + { + *result = c ; /* 0 => found. -1 => val < first */ + return il ; /* return low index. */ + } + + /* Now binary chop. We know that item[il] < val < item[ih] */ + /* We also know that il < ih */ + while (1) + { + iv = (il + ih) / 2 ; + if (iv == il) /* true if (ih == il+1) */ + { + *result = +1 ; + return il ; /* return il: item[il] < val < item[il+1] */ + } ; + /* We now know that il < iv < ih */ + c = cmp(&p_val, (const void* const*)&v->p_items[iv]) ; + if (c == 0) + { + *result = 0 ; + return iv ; /* found !! */ + } + if (c < 0) + ih = iv ; /* step down iv > il, so new ih > il */ + else + il = iv ; /* step up iv < ih, so new il < ih */ + } ; +} ; + +/*============================================================================== + * Mechanics for adding/deleting items and managing the vector (logical) end + * and (physical) limit. + */ + +/* Extract the LS bit of unsigned integer 'x'. */ +#define lsbit(x) ((x) & ((~(x)) + 1)) +/* Round 'x' up to a multiple of 'm' */ +#define multiple(x, m) ((((x) + (m) - 1) / (m)) * (m)) + +/* Set new limit to suit new end for given vector. + * + * The new limit will be at least: VECTOR_LIMIT_MIN. + * + * While the vector is relatively small, the limit is doubled until there + * is at least 1/8 of the new vector free. + * + * Beyond VECTOR_LIMIT_DOUBLE_MAX, however, the limit is set to the + * smallest multiple of VECTOR_LIMIT_DOUBLE_MAX which gives at least + * VECTOR_LIMIT_SLACK_MIN free entries beyond the new end. + * + * This is an attempt to balance the cost of repeated reallocations of + * memory against the cost of possible wasted space at the end of the + * vector. + * + * NB: the new_limit depends entirely on the new end position. (Current + * end position is ignored.) + * + * NB: the new limit may be less than the current limit, in which case the + * vector body is reduced in size. + * + * Except for any size set when the vector is initialised, the vector body + * size will be a power of 2 or a multiple of VECTOR_LIMIT_DOUBLE_MAX. + * (Vectors are regular in their habits, which may help the memory allocator). + * + * TODO: what to do if calculation of new_limit overflows, or calculation + * of P_ITEMS_SIZE will ? + */ +static void +vector_new_limit(vector v, vector_index new_end) +{ + vector_index old_limit = v->limit ; + vector_index new_limit ; + + if (new_end > ((VECTOR_LIMIT_DOUBLE_MAX * 7) / 8)) + { + new_limit = multiple(new_end + VECTOR_LIMIT_SLACK_MIN, + VECTOR_LIMIT_DOUBLE_MAX) ; + } + else + { + /* Want the new_limit to be a power of 2. */ + /* If the old_limit was a power of 2, start from there. */ + /* Otherwise start from a power of 2 less than new_end: either the */ + /* minimum value or a value mid way to VECTOR_LIMIT_DOUBLE_MAX. */ + if ( (old_limit != 0) && (old_limit == lsbit(old_limit)) + && (new_end >= old_limit) ) + new_limit = old_limit ; + else + new_limit = (new_end < VECTOR_LIMIT_MID) ? VECTOR_LIMIT_MIN + : VECTOR_LIMIT_MID ; + while (new_end > ((new_limit * 7) / 8)) + new_limit *= 2 ; + } ; + + v->p_items = + XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ; + + v->limit = new_limit ; +} ; + +/* Extend vector and set new (logical) end. + * + * Extends body if required. + * Ensures everything between old and new end is set NULL. + * + * NB: expects new end > old end, but copes with new end <= old end. + */ +void +vector_extend(vector v, vector_index new_end) +{ + vector_index old_end = v->end ; + + if (new_end > v->limit) + vector_new_limit(v, new_end) ; + v->end = new_end ; + + if (new_end > old_end) + memset(&v->p_items[old_end], 0, P_ITEMS_SIZE(new_end - old_end)) ; +} ; + +/* Insert entries into vector: insert 'n' NULL entries at location 'i'. + * + * Updates end (and limit) to be at least 'i' + 'n'. + * (So if 'i' < end then end becomes end + 'n', else end becomes 'i' + 'n'.) + */ +void +vector_insert(vector v, vector_index i, unsigned int n) +{ + vector_index old_end, new_end ; + unsigned int n_above ; + + /* If i < old end, then we are inserting n NULLs, and need + * to shuffle at least one item up. + * else we are setting new end to i + n and need to NULL + * fill from old end to the new end. + */ + old_end = v->end ; + if (i < old_end) + { + if (n == 0) + return ; /* give up now if not inserting anything */ + n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */ + new_end = old_end + n ; + } + else + { + n_above = 0 ; /* nothing to shuffle up. */ + new_end = i + n ; + i = old_end ; /* where to zeroize from */ + n = new_end - old_end ; /* how much to zeroize */ + } ; + + /* Now we extend the body if we need to. */ + if (new_end > v->limit) + vector_new_limit(v, new_end) ; + v->end = new_end ; + + if (n_above > 0) + memmove(&v->p_items[i + n], &v->p_items[i], P_ITEMS_SIZE(n_above)) ; + + memset(&v->p_items[i], 0, P_ITEMS_SIZE(n)) ; +} ; + +/* Delete items from vector: delete 'n' items at location 'i'. + * + * Does nothing if 'i' is beyond current end of vector or if 'n' == 0. + * + * Deletes from 'i' to end if less than 'n' items to the end. + * + * NB: does NOT change the size of the body. + * + * NB: it is the caller's responsibility to have released any memory allocated + * for the items that are being deleted. +*/ void -vector_unset (vector v, unsigned int i) +vector_delete(vector v, vector_index i, unsigned int n) { - if (i >= v->alloced) - return; + vector_index old_end, new_end ; + + old_end = v->end ; - v->index[i] = NULL; + if ((i >= old_end) || (n == 0)) + return ; - if (i + 1 == v->active) + /* If i + n < old_end, we have 1 or more items to keep and move down */ + if ((i + n) < old_end) { - v->active--; - while (i && v->index[--i] == NULL && v->active--) - ; /* Is this ugly ? */ + memmove(&v->p_items[i], &v->p_items[i + n], + P_ITEMS_SIZE(old_end - (i + n))) ; + new_end = old_end - n ; } -} + else + { + new_end = i ; /* We are keeping nothing above 'i' */ + /* NB: new_end < old_end */ + } ; -/* Count the number of not emplty slot. */ -unsigned int -vector_count (vector v) + v->end = new_end ; /* account for stuff dropped */ +} ; + +/* Discard entries from vector: discard entries from location 'i' onwards. + * + * Releases memory from 'i' onwards. + * Releases the body altogether if this sets the vector empty ('i' == 0). + * Sets new end of vector iff 'i' < current end. + * + * Does nothing if 'i' is beyond current limit (physical end) of vector. + * + * NB: it is the caller's responsibility to have released any memory allocated + * for the items that are being discarded. +*/ +void +vector_discard(vector v, vector_index i) { - unsigned int i; - unsigned count = 0; + if (i >= v->limit) + return ; - for (i = 0; i < v->active; i++) - if (v->index[i] != NULL) - count++; + if (i == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else + { + v->limit = i ; + if (i < v->end) + v->end = i ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(i)) ; + } ; +} ; - return count; -} +/* Chop vector at the current (logical) end. + * + * Releases the body altogether if the vector is currently empty. +*/ +void +vector_chop(vector v) +{ + vector_index new_limit = v->end ; + + if (new_limit == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else if (new_limit != v->limit) + { + v->limit = new_limit ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ; + } ; +} ; + +/* Decant vector into a new body allocated to current logical size, and + * release the old body. + * + * Releases the body altogether if the vector is currently empty. +*/ +void +vector_decant(vector v) +{ + p_vector_item* p_old_body ; + vector_index new_limit = v->end ; + + if (new_limit == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else + { + p_old_body = v->p_items ; + + vector_init_new(v, new_limit) ; /* initialise with new body */ + + memcpy(v->p_items, p_old_body, P_ITEMS_SIZE(new_limit)) ; + /* copy the old body across */ + v->end = new_limit ; + + XFREE(MTYPE_VECTOR_BODY, p_old_body) ; + } ; +} ; diff --git a/lib/vector.h b/lib/vector.h index 6b27fd96..e69e628a 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -1,7 +1,9 @@ -/* - * Generic vector interface header. +/* Generic vector interface header. * Copyright (C) 1997, 98 Kunihiro Ishiguro * + * 24-Nov-2009 -- extended to add a number of new operations on vectors. + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it @@ -17,47 +19,337 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_VECTOR_H #define _ZEBRA_VECTOR_H -/* struct for vector */ -struct _vector +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*------------------------------------------------------------------------------ + * types and struct for vector + * + * NB: an entirely zero structure represents an entirely empty vector. + * + * TODO: could force vector_index to be 32 bits ? + */ +typedef void* p_vector_item ; +typedef unsigned int vector_index ; + +typedef struct vector* vector ; /* pointer to vector structure */ +typedef struct vector vector_t ; /* embedded vector structure */ +struct vector { - unsigned int active; /* number of active slots */ - unsigned int alloced; /* number of allocated slot */ - void **index; /* index to data */ + p_vector_item* p_items ; /* pointer to array of vector item pointers */ + vector_index end ; /* number of "active" item entries */ + vector_index limit ; /* number of allocated item entries */ }; -typedef struct _vector *vector; -#define VECTOR_MIN_SIZE 1 +/* Under very controlled circumstances, may access the vector body */ +typedef p_vector_item const* vector_body_t ; + +/* Values that control the allocation of the vector body. */ +/* NB: these must all be powers of 2. */ + +/* The body, when allocated, will have at least this many entries. */ +#define VECTOR_LIMIT_MIN 8 +/* When the body grows, it doubles in size, until it is this big. */ +/* After that it grows in units of this much. */ +#define VECTOR_LIMIT_DOUBLE_MAX 2048 +/* "Midway" between VECTOR_LIMIT_MIN and VECTOR_LIMIT_DOUBLE_MAX. */ +#define VECTOR_LIMIT_MID 128 +/* When growing in units of VECTOR_LIMIT_DOUBLE_MAX, this is the */ +/* minimum slack space to leave after the logical end of the vector. */ +#define VECTOR_LIMIT_SLACK_MIN ((VECTOR_LIMIT_DOUBLE_MAX) / 8) + +/* (Sometimes) useful macros. */ -/* (Sometimes) usefull macros. This macro convert index expression to - array expression. */ -/* Reference slot at given index, caller must ensure slot is active */ -#define vector_slot(V,I) ((V)->index[(I)]) -/* Number of active slots. - * Note that this differs from vector_count() as it the count returned - * will include any empty slots +/* Reference item at given index. */ +/* NB: does not guarantee the item is "active" (that is: within the */ +/* (logical) vector) -- but see vector_ensure. */ +/* Returns address of vector item. */ +/* See: VECTOR_ITEMS() for walking items in a vector. */ +/* See: vector_get_item(), which is preferable. */ +#define vector_slot(V,I) ((V)->p_items[(I)]) + +/* Number of "active" item entries -- (logical) end of the vector. */ +/* Note that this differs from vector_count() as this count will */ +/* include any NULL items. */ +#define vector_active(V) ((V)->end) + +/* To walk all items in a vector: + * + * vector_index i ; + * xxxxx* p_v ; + * + * for (VECTOR_ITEMS(v, p_v, i)) + * { + * ... i is index of current item + * ... p_v is address of current item value -- may be NULL + * } ; + * + * ... i is number of items in vector (including any NULLs) + */ +#define VECTOR_ITEMS(v, p_v, i)\ + (i) = 0 ;\ + (i) < (v)->end ? (((p_v) = (void*)(v)->p_items[i]), 1) \ + : (((p_v) = NULL), 0) ;\ + ++(i) + +/*============================================================================== + * Prototypes. */ -#define vector_active(V) ((V)->active) -/* Prototypes. */ extern vector vector_init (unsigned int size); -extern void vector_ensure (vector v, unsigned int num); -extern int vector_empty_slot (vector v); +Inline void vector_ensure(vector v, vector_index i) ; extern int vector_set (vector v, void *val); -extern int vector_set_index (vector v, unsigned int i, void *val); -extern void vector_unset (vector v, unsigned int i); -extern unsigned int vector_count (vector v); -extern void vector_only_wrapper_free (vector v); -extern void vector_only_index_free (void *index); +extern int vector_set_index (vector v, vector_index i, void *val); +#define vector_unset(v, i) (void)vector_unset_item(v, i) +extern vector_index vector_count (vector v); extern void vector_free (vector v); extern vector vector_copy (vector v); -extern void *vector_lookup (vector, unsigned int); -extern void *vector_lookup_ensure (vector, unsigned int); +extern void *vector_lookup (vector, vector_index); +extern void *vector_lookup_ensure (vector, vector_index); + +extern vector vector_init_new(vector v, unsigned int size) ; +extern vector vector_re_init(vector v, unsigned int size) ; +extern vector vector_reset(vector v, int free_structure) ; +extern p_vector_item vector_ream(vector v, int free_structure) ; + +/* Reset vector and free the vector structure. */ +#define vector_reset_free(v) vector_reset(v, 1) +/* Reset vector but free the heap structure. */ +#define vector_reset_keep(v) vector_reset(v, 0) +/* Ream out vector and free the vector structure. */ +#define vector_ream_free(v) vector_ream(v, 1) +/* Ream out vector but keep the vector structure. */ +#define vector_ream_keep(v) vector_ream(v, 0) + +Inline void vector_set_min_length(vector v, unsigned int len) ; +extern void vector_set_new_min_length(vector v, unsigned int len) ; + +Inline void vector_set_length(vector v, unsigned int len) ; +#define vector_set_end(v, l) vector_set_length(v, l) + +Inline vector_index vector_length(vector v) ; +#define vector_end(v) vector_length(v) +Inline int vector_is_empty(vector v) ; + +Inline vector_body_t vector_body(vector v) ; + +Inline p_vector_item vector_get_item(vector v, vector_index i) ; +Inline p_vector_item vector_get_first_item(vector v) ; +Inline p_vector_item vector_get_last_item(vector v) ; +Inline void vector_set_item(vector v, vector_index i, p_vector_item p_v) ; +Inline void vector_assign_item(vector v, vector_index dst, vector_index src) ; +extern p_vector_item vector_unset_item(vector v, vector_index i) ; +extern vector_index vector_trim(vector v) ; +extern vector_index vector_condense(vector v) ; + +extern void vector_insert_item(vector v, vector_index i, p_vector_item p_v) ; +extern void vector_insert_item_here(vector v, vector_index i, int rider, + p_vector_item p_v) ; +extern void vector_move_item(vector v, vector_index dst, vector_index src) ; +extern void vector_move_item_here(vector v, vector_index dst, int rider, + vector_index src) ; +extern p_vector_item vector_delete_item(vector v, vector_index i) ; +extern void vector_reverse(vector v) ; +extern void vector_part_reverse(vector v, vector_index i, unsigned int n) ; + +Inline void vector_push_item(vector v, p_vector_item p_v) ; +Inline p_vector_item vector_pop_item(vector v) ; + +extern void vector_insert(vector v, vector_index i, unsigned int n) ; +extern void vector_delete(vector v, vector_index i, unsigned int n) ; + +typedef int vector_bsearch_cmp(const void* const* pp_val, + const void* const* item) ; +vector_index vector_bsearch(vector v, vector_bsearch_cmp* cmp, + const void* p_val, int* result) ; +typedef int vector_sort_cmp(const void* const* a, const void* const* b) ; +void vector_sort(vector v, vector_sort_cmp* cmp) ; + +extern vector vector_copy_here(vector dst, vector src) ; +extern vector vector_move_here(vector dst, vector src) ; +extern vector vector_copy_append(vector dst, vector src) ; +extern vector vector_move_append(vector dst, vector src) ; + +#define vector_copy_extract(to, src, i_src, n_src) \ + vector_sak(1, to, NULL, 0, 0, src, i_src, n_src, 0) +#define vector_move_extract(to, src, i_src, n_src) \ + vector_sak(1, to, NULL, 0, 0, src, i_src, n_src, 1) +#define vector_copy_splice(to, dst, i_dst, n_dst, src, i_src, n_src) \ + vector_sak(1, to, dst, i_dst, n_dst, src, i_src, n_src, 0) +#define vector_move_splice(to, dst, i_dst, n_dst, src, i_src, n_src) \ + vector_sak(1, to, dst, i_dst, n_dst, src, i_src, n_src, 1) +#define vector_copy_replace(dst, i_dst, n_dst, src, i_src, n_src) \ + vector_sak(0, NULL, dst, i_dst, n_dst, src, i_src, n_src, 0) +#define vector_move_replace(dst, i_dst, n_dst, src, i_src, n_src) \ + vector_sak(0, NULL, dst, i_dst, n_dst, src, i_src, n_src, 1) + +extern vector vector_sak(int to_copy, vector to, + vector dst, vector_index i_dst, unsigned int n_dst, + vector src, vector_index i_src, unsigned int n_src, int src_move) ; + +extern void vector_discard(vector v, vector_index i) ; +extern void vector_chop(vector v) ; +extern void vector_decant(vector v) ; + +Inline vector_index vector_extend_by_1(vector v) ; +extern void vector_extend(vector v, vector_index new_end) ; + +/*============================================================================== + * The inline functions: + */ + +/* Extend vector by one item at the end, which is about to be set. */ +/* Returns index of new least item in the vector. */ +/* NB: if left unset, the item may be UNDEFINED. */ +Inline vector_index +vector_extend_by_1(vector v) +{ + vector_index i = v->end ; + + if (i < v->limit) + return v->end++ ; /* simple if we have room */ + + vector_extend(v, i + 1) ; /* the hard way */ + return i ; +} ; + +/* Ensure given index is "active". */ +/* Adjusts logical and physical end of the vector as required, filling */ +/* with NULLs upto any new logical end. */ +Inline void +vector_ensure(vector v, vector_index i) +{ + if (i < v->end) /* trivial if within vector */ + return ; + if ((i == v->end) && (i < v->limit)) /* simple if end and have room */ + v->p_items[v->end++] = NULL ; /* set NULL for complete safety */ + else + vector_extend(v, i + 1) ; /* do it the hard way */ +} ; + +/* Want vector to be at least the given length. */ +/* */ +/* Adjusts logical and physical end of the vector as required, filling */ +/* with NULLs upto any new logical end -- does not allocate any more */ +/* than is exactly necessary. */ +Inline void +vector_set_min_length(vector v, unsigned int len) +{ + if (len > v->end) /* will not reduce the length */ + vector_set_new_min_length(v, len) ; +} ; + +/* Want vector to be the given length. */ +/* */ +/* If this is less than the current length, items are discarded. It */ +/* is the caller's responsibility to have freed anything that needs it. */ +/* */ +/* Adjusts logical and physical end of the vector as required, filling */ +/* with NULLs upto any new logical end -- does not allocate any more */ +/* than is exactly necessary. */ +Inline void +vector_set_length(vector v, unsigned int len) +{ + if (len > v->end) + vector_set_new_min_length(v, len) ; /* Extend if new length greater */ + else + v->end = len ; /* chop */ +} ; + +/* Return index of end of vector (index of last item + 1) */ +Inline vector_index +vector_length(vector v) +{ + return v->end ; +} ; + +/* Returns whether vector is empty or not. */ +Inline int +vector_is_empty(vector v) +{ + return (v->end == 0) ; +} ; + +/* Returns highly restricted pointer to vector body */ +//Inline const void* const* +//vector_body(vector v) +//{ +// return (const void* const*)v->p_items ; +//} ; + +Inline vector_body_t +vector_body(vector v) +{ + return (vector_body_t)v->p_items ; +} ; + +/* Access functions -- Inline for obvious reasons. */ + +/* Get pointer to item. Returns NULL if accessing beyond end. */ +Inline p_vector_item +vector_get_item(vector v, vector_index i) +{ + return (i < v->end) ? v->p_items[i] : NULL ; +} ; + +/* Get pointer to first item. Returns NULL if vector empty. */ +Inline p_vector_item +vector_get_first_item(vector v) +{ + return (v->end != 0) ? v->p_items[0] : NULL ; +} ; + +/* Get pointer to last item. Returns NULL if vector empty. */ +Inline p_vector_item +vector_get_last_item(vector v) +{ + return (v->end != 0) ? v->p_items[v->end - 1] : NULL ; +} ; + +/* Set item value in vector. Extend vector if required. */ +/* NB: it is the caller's responsibility to release memory used by any */ +/* current value of the item, if required. */ +Inline void +vector_set_item(vector v, vector_index i, void* p_v) +{ + vector_ensure(v, i) ; + v->p_items[i] = (p_vector_item)p_v ; +} ; + +/* Set dst item to be a copy of the src item. Extend vector if required. + * + * NB: it is the caller's responsibility to look after the memory being + * used by the current dst item or the new (duplicated) src item. + */ +Inline void +vector_assign_item(vector v, vector_index dst, vector_index src) +{ + vector_set_item(v, dst, vector_get_item(v, src)) ; +} ; + +/* Push value onto vector, extending as required. */ +Inline void +vector_push_item(vector v, void* p_v) +{ + vector_index i = vector_extend_by_1(v) ; + v->p_items[i] = (p_vector_item)p_v ; +} ; + +/* Pop value from vector. Returns NULL if vector is empty. */ +/* NB: does NOT change the size of the vector body. */ +Inline p_vector_item +vector_pop_item(vector v) +{ + return (v->end > 0) ? v->p_items[--v->end] : NULL ; +} ; #endif /* _ZEBRA_VECTOR_H */ diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c new file mode 100644 index 00000000..685f33ec --- /dev/null +++ b/lib/vio_fifo.c @@ -0,0 +1,1165 @@ +/* VTY I/O FIFO + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stddef.h> +#include <string.h> + +#include "vio_fifo.h" +#include "network.h" +#include "list_util.h" + +#include "memory.h" +#include "zassert.h" + +/*============================================================================== + * VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer. + * + * The FIFO is arranged as lumps of some given size. Lumps are allocated + * as and when necessary, and released once emptied. + * + * The last lump is never released. So, it may be that only one lump is + * ever needed. + * + * When releasing lumps, keeps one lump "spare", to be reused as necessary. + * This is used in ... TODO <<<< And is released... + * + *------------------------------------------------------------------------------ + * Implementation notes: + * + * The FIFO is initialised with all pointers NULL -- so with no lumps at all. + * + * Once a lump has been allocated there is always one lump in the FIFO. + * + * The following are expected to be true: + * + * * put_ptr == get_ptr => FIFO empty + * + * * put_ptr == tail->end -- at all times (NULL when no lumps) + * + * put_ptr >= tail->data ) otherwise something is broken + * put_ptr <= tail->end ) + * + * * get_ptr == head->end -- when there is more than one lump + * get_ptr <= put_ptr -- when there is only one lump + * + * get_ptr >= head->data ) otherwise something is broken + * get_ptr <= head->end ) + * + * * put_ptr == put_end => tail lump is full + * put_ptr < put_end => space exists in the tail lump + * put_ptr > put_end => broken + * + * * get_ptr == get_end => head lump is empty + * BUT if there is only one lump, make sure that + * get_end == put_ptr. + * get_ptr < get_end => data exists in the head lump + * get_ptr > get_end => broken + * + * Note that: + * + * * when the get_ptr reaches the put_ptr the pointers are reset to the + * start of the one and only lump. + * + * Everywhere that the get_ptr is moved, must check for meeting the + * put_ptr and reset pointers. At the same time, when reaches the end of + * a lump, gets rid of it. + * + * * when advancing the put_ptr does not check for advancing the get_end. + * + * The one exception to this, is that when the put_ptr advances to a new + * block, if there was one lump, sets the get_end to the end of that block. + * + * Everywhere that the get_end is used, must check for there being one + * lump and the possibility that put_ptr has changed. + */ + +/*============================================================================== + * Initialisation, allocation and freeing of FIFO and lumps thereof. + */ + +/* Return default size, or given size rounded up to 16 byte boundary */ +static size_t +vio_fifo_size(size_t size) +{ + if (size == 0) + return 4096 ; + else + return ((size + 16 - 1) / 16) * 16 ; +} ; + +/*============================================================================== + * Initialise VTY I/O FIFO -- allocating if required. + */ +extern vio_fifo +vio_fifo_init_new(vio_fifo vf, size_t size) +{ + if (vf == NULL) + vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; + else + memset(vf, 0, sizeof(vio_fifo_t)) ; + + /* Zeroising the the vio_fifo_t has set: + * + * lump -- base pair, both pointers NULL => list is empty + * + * put_ptr -- NULL ) no lump to put anything into + * put_end -- NULL ) put_ptr == put_end => no room in current lump + * + * get_ptr -- NULL ) no lump to get anything from + * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump + * + * rdr_lump -- NULL ) no rdr_lump + * rdr_ptr -- NULL + * + * spare -- NULL no spare lump + * + * ALSO put_ptr == get_ptr => FIFO is empty ! + */ + + vf->size = vio_fifo_size(size) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return vf ; +} + +/*------------------------------------------------------------------------------ + * Free contents of given FIFO, and free FIFO structure as well, if required. + * + * Does nothing if given a NULL pointer -- must already have been freed ! + * + * If does not free the FIFO structure, resets it all empty. + * + * Frees *all* FIFO lumps. + * + * See also: vio_fifo_reset_keep(vio_fifo) + * vio_fifo_reset_free(vio_fifo) + */ +extern vio_fifo +vio_fifo_reset(vio_fifo vf, int free_structure) +{ + vio_fifo_lump lump ; + + if (vf == NULL) + return NULL ; + + while (ddl_pop(&lump, vf->base, list) != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + + if (vf->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; + + if (free_structure) + XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + else + vio_fifo_init_new(vf, vf->size) ; + + return vf ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO is empty, with one lump -- reset all pointers. + */ +inline static void +vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +{ + if (vf->rdr_lump != NULL) + { + assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; + vf->rdr_ptr = lump->data ; + } ; + + /* Note that sets the lump->end to the true lump->end */ + vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; + vf->put_end = lump->end = lump->data + lump->size ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + */ +inline static void +vio_fifo_ptr_unset(vio_fifo vf) +{ + assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; + + vf->one = false ; + + vf->put_ptr = NULL ; + vf->put_end = NULL ; + vf->get_ptr = NULL ; + vf->get_end = NULL ; + + vf->rdr_lump = NULL ; + vf->rdr_ptr = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Clear out contents of FIFO -- will continue to use the FIFO. + * + * Keeps one FIFO lump. (Frees everything else, including any spare.) + */ +extern void +vio_fifo_clear(vio_fifo vf) +{ + vio_fifo_lump tail ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + assert(vf != NULL) ; + + tail = ddl_tail(vf->base) ; + + if (tail != NULL) + { + while (ddl_head(vf->base) != tail) + { + vio_fifo_lump lump ; + ddl_pop(&lump, vf->base, list) ; + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + } ; + + vf->rdr_lump = NULL ; /* clear rdr */ + vf->rdr_ptr = NULL ; + + vf->one = true ; + vio_fifo_ptr_reset(vf, tail) ; + } + else + vio_fifo_ptr_unset(vf) ; + + if (vf->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * See how much room there is in the FIFO. + * + * If no lumps have been allocated, returns the size of the lump that would + * allocate. + * + * Otherwise, returns the amount of space available *without* allocating any + * further lumps. + * + * Returns: room available as described + */ +extern size_t +vio_fifo_room(vio_fifo vf) +{ + if (vf->put_ptr != NULL) + return vf->put_end - vf->put_ptr ; + else + return vf->size ; +} ; + +/*------------------------------------------------------------------------------ + * Allocate another lump for putting into. + * + * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. + * + * Set the put_ptr/put_end pointers to point at the new lump. + * + * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * + * If have just filled the first lump on the list, update the get_end pointer + * to reflect the fact that the out lump is now full. + */ +extern void +vio_fifo_lump_new(vio_fifo vf, size_t size) +{ + vio_fifo_lump lump ; + int first_alloc ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + + if (vf->one) + vf->get_end = vf->put_ptr ; /* update get_end */ + + lump = ddl_tail(vf->base) ; + + first_alloc = (lump == NULL) ; /* extra initialisation needed */ + + if (first_alloc) + assert(vf->put_ptr == NULL) ; /* must all be NULL together */ + else + assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ + + size = vio_fifo_size(size) ; + + if ((vf->spare != NULL) && (vf->spare->size >= size)) + { + lump = vf->spare ; + vf->spare = NULL ; + } + else + { + lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, + offsetof(vio_fifo_lump_t, data[size])) ; + lump->size = size ; + } ; + + lump->end = lump->data + lump->size ; + + ddl_append(vf->base, lump, list) ; + + vf->one = first_alloc ; + + vf->put_ptr = lump->data ; + vf->put_end = lump->end ; + + if (first_alloc) + { + vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ + vf->get_end = vf->put_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Release lump, head or tail (or both) and update pointers. + * + * Note that this does release the lump if it is the only lump. + * + * Do nothing if nothing is yet allocated. + * + * If releasing the only lump in the FIFO, resets all pointers to NULL. + * + * If releasing the head lump: + * + * * the lump MUST be finished with -- so vf->get_ptr must be at the end + * + * If releasing the only lump, the FIFO MUST be empty. + * + * * if the lump is the current vf->rdr_lump, the reader must be at the + * end too -- ie it must be the same as the vf->get_ptr ! + * + * If releasing the tail lump: + * + * * the lump MUST be empty + * + * If releasing the only lump, the FIFO MUST be empty. + * + * * if the lump is the current vf->rdr_lump, the reader must be at the + * end too -- ie it must be the same as the vf->get_ptr ! + */ +static void +vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) +{ + vio_fifo_lump head ; + vio_fifo_lump tail ; + vio_fifo_lump free ; + bool release_head ; + bool release_tail ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + /* Prepare and check whether removing head or tail (or both) */ + head = ddl_head(vf->base) ; + tail = ddl_tail(vf->base) ; + + release_head = (lump == head) ; + release_tail = (lump == tail) ; + + assert(release_head || release_tail) ; + + /* Unless nothing ever allocated -- release the lump. */ + free = lump ; /* expect to free the lump */ + if (lump != NULL) + { + vio_fifo_lump keep ; + + /* Consistency checks */ + if (release_head) + { + if (release_tail) + assert(vf->get_ptr == vf->put_ptr) ; + else + assert(vf->get_ptr == lump->end) ; + + if (vf->rdr_lump == lump) + assert(vf->rdr_ptr == vf->get_ptr) ; + } + else if (release_tail) + { + assert(vf->put_ptr == lump->data) ; + + if (vf->rdr_lump == lump) + assert(vf->rdr_ptr == vf->put_ptr) ; + } ; + + /* Remove lump from FIFO and decide whether to keep as spare, or + * which of spare and this to free. + */ + ddl_del(vf->base, lump, list) ; + + keep = vf->spare ; /* expect to keep current spare */ + + if ((keep == NULL) || (keep->size < lump->size)) + { + keep = lump ; + free = vf->spare ; + } ; + + vf->spare = keep ; + + head = ddl_head(vf->base) ; /* changed if released head */ + tail = ddl_tail(vf->base) ; /* changed if released tail */ + } ; + + /* Now update pointers... depending on what was released and what have + * left. + */ + if (head == NULL) + { + /* Deal with FIFO that now has no lumps or had none to start with */ + if (lump != NULL) + assert(vf->one) ; + + vio_fifo_ptr_unset(vf) ; + } + else + { + /* Have at least one lump left -- so must have had at least two ! */ + assert(!vf->one) ; + + vf->one = (head == tail) ; /* update */ + + if (release_head) + { + /* Released the head. + * + * Update the vf->get_ptr and the vf->get_end. + */ + vf->get_ptr = head->data ; + if (vf->one) + vf->get_end = vf->put_ptr ; + else + vf->get_end = head->end ; + + /* Update vf->rdr_ptr and vf->rdr_lump. */ + if (vf->rdr_lump == lump) + { + vf->rdr_lump = head ; + vf->rdr_ptr = head->data ; + } ; + } + else + { + /* Released the tail. + * Update the vf->put_ptr and vf->put_end + */ + vf->put_ptr = vf->put_end = tail->end ; + + /* Update vf->rdr_ptr and vf->rdr_lump. */ + if (vf->rdr_lump == lump) + { + vf->rdr_lump = tail ; + vf->rdr_ptr = tail->end ; + } ; + } ; + } ; + + /* Finally, free any lump that is actually to be freed */ + + if (free != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Re-allocate lump for putting into. + * + * Call when vf->put_ptr == start of last lump, and that lump is not big + * enough ! + * + * There must be at least one lump. + * + * Updates put_ptr/put_end pointers to point at the new lump. + * + * Updates get_ptr/get_end pointers if required. + * + * Updates rdr_ptr if required. + */ +static void +vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +{ + bool rdr_set ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + /* FIFO may not be completely empty. + * This must be the last lump. + * The last lump must be empty. + */ + assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; + + /* Remove the last, *empty* lump, and update all pointers to suit. */ + rdr_set = (vf->rdr_lump == lump) ; + + vio_fifo_lump_release(vf, lump) ; + + /* Now allocate a new lump with the required size */ + vio_fifo_lump_new(vf, size) ; + + /* Restore the rdr_ptr, if required */ + if (rdr_set) + { + vio_fifo_lump tail ; + + tail = ddl_tail(vf->base) ; + + vf->rdr_lump = tail ; + vf->rdr_ptr = tail->data ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*============================================================================== + * Put data to the FIFO. + */ + +/*------------------------------------------------------------------------------ + * Store 'n' bytes -- allocate new lump if current is exhausted. + */ +extern void +vio_fifo_put(vio_fifo vf, const char* src, size_t n) +{ + size_t take ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + while (n > 0) + { + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + + take = (vf->put_end - vf->put_ptr) ; + if (take > n) + take = n ; + + memcpy(vf->put_ptr, src, take) ; + vf->put_ptr += take ; + + src += take ; + n -= take ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to fifo -- cf printf() + * + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) + */ +extern int +vio_fifo_printf(vio_fifo vf, const char* format, ...) +{ + va_list args; + int len ; + + va_start (args, format); + len = vio_fifo_vprintf(vf, format, args); + va_end (args); + + return len; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to fifo -- cf vprintf() + * + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) + */ +extern int +vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) +{ + va_list ac ; + int len ; + int have ; + size_t size ; + vio_fifo_lump lump ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + size = vf->size ; /* standard allocation size */ + while (1) + { + /* Find what space is left in the tail lump, and allocate a new, + * empty lump if required. + */ + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf, size) ; /* traps put_ptr > put_end */ + + have = vf->put_end - vf->put_ptr ; + assert(have > 0) ; + + /* Note that vsnprintf() returns the length of what it would like to + * have produced, if it had the space. That length does not include + * the trailing '\0'. + */ + va_copy(ac, args) ; + len = vsnprintf(vf->put_ptr, have, format, ac) ; + va_end(ac) ; + + if (len < have) + { + if (len < 0) + break ; /* quit if failed */ + + vf->put_ptr += len ; + break ; /* done */ + } ; + + /* Not able to complete the operation in the current buffer. + * + * If the required space (len + 1) is greater than the standard + * allocation, then need to increase the allocation for the next lump. + * + * If the current lump is empty, need to renew it with a fresh lump of + * the now known required size. + * + * If the current lump is not empty, need to cut the end off and then + * allocate a fresh lump (of the standard or now known required size). + */ + if (len >= (int)size) + size = len + 1 ; /* need a non-standard size */ + + lump = ddl_tail(vf->base) ; + + if (vf->put_ptr == lump->data) + /* Need to replace the last, empty, lump with another empty lump, but + * big enough. + */ + vio_fifo_lump_renew(vf, lump, size) ; + else + /* Need to cut this lump short, and allocate new lump at top of loop. + */ + lump->end = vf->put_end = vf->put_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return len ; +} ; + +/*============================================================================== + * Get data from the FIFO. + */ + +static bool vio_fifo_get_next_lump(vio_fifo vf) ; + +/*------------------------------------------------------------------------------ + * Get ready to read something out of the FIFO. + * + * Makes sure vf->get_end is up to date (if required) and if the FIFO is not + * empty, makes sure vf->get_ptr points at the next byte to be read. + * + * Returns: true <=> there is something in the FIFO. + */ +static inline bool +vio_fifo_get_ready(vio_fifo vf) +{ + assert(vf->rdr_lump == NULL) ; + + if (vf->one) + vf->get_end = vf->put_ptr ; /* make sure have everything */ + + if (vf->get_ptr >= vf->get_end) + if (!vio_fifo_get_next_lump(vf)) + return 0 ; /* quit now if nothing there */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Get upto 'n' bytes. + * + * Returns: number of bytes got -- may be zero. + */ +extern size_t +vio_fifo_get(vio_fifo vf, void* dst, size_t n) +{ + size_t have ; + void* dst_in ; + + if (!vio_fifo_get_ready(vf)) + return 0 ; /* quit now if nothing there */ + + dst_in = dst ; + while (n > 0) + { + have = vf->get_end - vf->get_ptr ; + + if (have > n) + have = n ; + + memcpy(dst, vf->get_ptr, have) ; + vf->get_ptr += have ; + dst = (char*)dst + have ; + + if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ + if (!vio_fifo_get_next_lump(vf)) + break ; /* quit if nothing more to come */ + + n -= have ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return (char*)dst - (char*)dst_in ; +} ; + +/*------------------------------------------------------------------------------ + * Get byte -- the long winded way. + * + * See the inline vio_fifo_get_byte(). + * + * The version is used when the get_ptr is at or just before the end of the + * current lump. Looks after all the necessary pointer updates associated with + * hitting end of lump, or hitting end of FIFO. + * + * Returns: 0x00..0xFF -- byte value (as an int) + * -1 => FIFO is empty. + */ + +extern int +vio_fifo_get_next_byte(vio_fifo vf) +{ + unsigned char u ; + + if (!vio_fifo_get_ready(vf)) + return -1 ; /* quit now if nothing there */ + + u = *vf->get_ptr++ ; + + /* As soon as reach the end want either to discard empty lump, or reset + * the pointers. + */ + if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ + vio_fifo_get_next_lump(vf) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return u ; +} ; + +/*------------------------------------------------------------------------------ + * Get pointer to a lump of bytes. + * + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_have = 0 + * + * If the FIFO is not empty, will return pointer to at least one byte. + * + * Returns number of bytes to the end of the current lump. There may be + * further lumps beyond the current one. + */ +extern void* +vio_fifo_get_lump(vio_fifo vf, size_t* p_have) +{ + if (!vio_fifo_get_ready(vf)) + { + *p_have = 0 ; + return NULL ; + } ; + + *p_have = (vf->get_end - vf->get_ptr) ; + return vf->get_ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Advance FIFO to position reached. + * + * Having done vio_fifo_get_lump(), can take any number of bytes (up to the + * number that "have"), then call this function to advance the pointers. + * + * The "here" argument must the the address returned by vio_fifo_get_lump() + * plus the number of bytes taken. + */ +extern void +vio_fifo_got_upto(vio_fifo vf, void* here) +{ + vf->get_ptr = here ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + if (vf->get_ptr >= vf->get_end) + vio_fifo_get_next_lump(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Write contents of FIFO -- assuming non-blocking file + * + * Will write all of FIFO, or upto but excluding the last lump. + * + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see errno + * + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". + */ +extern int +vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +{ + char* src ; + size_t have ; + int done ; + + while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + { + if (!all && vf->one) + break ; /* don't write last lump */ + + done = write_nb(fd, src, have) ; + + if (done < 0) + return -1 ; /* failed */ + + vio_fifo_got_upto(vf, src + done) ; + + if (done < (int)have) + return 1 ; /* blocked */ + } ; + + return 0 ; /* all gone */ +} ; + +/*------------------------------------------------------------------------------ + * Get the current rdr_end value. + * + * Unlike get_end, do not have a field for this, but find it each time. + */ +inline static char* +vio_fifo_rdr_end(vio_fifo vf) +{ + if (vf->rdr_lump == ddl_tail(vf->base)) + return vf->put_ptr ; + else + return vf->rdr_lump->end ; +} ; + +/*------------------------------------------------------------------------------ + * Get the current rdr position -- sets it up if not currently set. + * + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_have = 0 + * + * If the FIFO is not empty, will return pointer to at least one byte. + * + * Returns number of bytes to the end of the current lump. There may be + * further lumps beyond the current one. + * + * NB: unless returns FIFO is empty, it is a mistake to now do any "get" + * operation other than vio_fifo_step_rdr(), until do vio_fifo_sync_rdr() + * or vio_fifo_drop_rdr. + */ +extern void* +vio_fifo_get_rdr(vio_fifo vf, size_t* p_have) +{ + if (!vio_fifo_get_ready(vf)) + { + *p_have = 0 ; + return NULL ; + } ; + + if (vf->rdr_lump == NULL) /* set up new rdr if required */ + { + vf->rdr_lump = ddl_head(vf->base) ; + vf->rdr_ptr = vf->get_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; + return vf->rdr_ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Step the rdr forward by the given number of bytes. + * + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_have = 0 + * + * If the FIFO is not empty, will return pointer to at least one byte. + * + * Returns number of bytes to the end of the current lump. There may be + * further lumps beyond the current one. + * + * NB: this does not change the get pointers, so all the data being stepped + * over is preserved in the FIFO, until vio_fifo_sync_rdr(). + * + * NB: the step may NOT exceed the last reported "have". + */ +extern void* +vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +{ + char* rdr_end ; + + assert(vf->rdr_lump != NULL) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + rdr_end = vio_fifo_rdr_end(vf) ; + vf->rdr_ptr += step ; + + if (vf->rdr_ptr >= rdr_end) + { + assert(vf->rdr_ptr == rdr_end) ; + + if (vf->rdr_lump != ddl_tail(vf->base)) + { + vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; + vf->rdr_ptr = vf->rdr_lump->data ; + + rdr_end = vio_fifo_rdr_end(vf) ; + } ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *p_have = (rdr_end - vf->rdr_ptr) ; + return (*p_have > 0) ? vf->rdr_ptr : NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Move FIFO get position to the rdr position, if any. + * + * This clears the rdr position, and removes all data between the current and + * new get positions from the FIFO. + */ +extern void +vio_fifo_sync_rdr(vio_fifo vf) +{ + vio_fifo_lump head ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + if (vf->rdr_lump == NULL) + return ; + + while ((head = ddl_head(vf->base)) != vf->rdr_lump) + { + vf->get_ptr = vf->get_end ; /* jump to end of lump */ + vio_fifo_lump_release(vf, head) ; + } ; + + vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + + vf->rdr_lump = NULL ; /* clear the rdr */ + vf->rdr_ptr = NULL ; + + if (vf->one) + { + if (vf->put_ptr == vf->get_ptr) /* reset pointers if FIFO empty */ + vio_fifo_ptr_reset(vf, head) ; + else + vf->get_end = vf->put_ptr ; + } + else + vf->get_end = head->end ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Drop the rdr position (if any). + * + * This clears the rdr position leaving the get position and FIFO unchanged. + */ +extern void +vio_fifo_drop_rdr(vio_fifo vf) +{ + VIO_FIFO_DEBUG_VERIFY(vf) ; + + vf->rdr_lump = NULL ; + vf->rdr_ptr = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Move on to next lump to get stuff from. + * + * Advance pointers etc. so that have at least one byte available, unless + * the FIFO is entirely empty. + * + * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that + * these are equal ! + * + * NB: when there is only one block, it may be that get_end is out of date, + * and should be advanced to the current put_ptr position. + * + * That is done here, but may be worth updating get_end before testing + * against get_ptr. + * + * Returns: true <=> at least one byte in FIFO. + * + * NB: if finds that the FIFO is empty, resets the pointers to the start + * of the last lump -- does not release the last lump. + */ +static bool +vio_fifo_get_next_lump(vio_fifo vf) +{ + vio_fifo_lump head ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + assert(vf->get_ptr == vf->get_end) ; + + head = ddl_head(vf->base) ; /* current lump for get */ + + /* Deal with the simple case of one lump, first. + * + * To save work when putting data into the FIFO (particularly when putting + * a byte at a time) does not keep the vf->get_end up to date (when there is + * only one lump). + * + * If the FIFO is empty, reset pointers and return empty. + */ + if (vf->one) + { + assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; + + if (vf->get_ptr < vf->put_ptr) + { + /* Had an out of date vf->get_end */ + vf->get_end = vf->put_ptr ; + + return true ; /* FIFO not empty */ + } ; + + assert(vf->get_ptr == vf->put_ptr) ; + + /* FIFO is empty -- reset pointers and exit */ + vio_fifo_ptr_reset(vf, head) ; + + return false ; /* FIFO empty */ + } ; + + /* Release the head and update pointers + * + * Deals with possibility that nothing has yet been allocated + */ + vio_fifo_lump_release(vf, head) ; + + return (vf->get_ptr < vf->get_end) ; +} ; + +/*============================================================================== + * For debug purposes -- verify the state of the given FIFO + */ +Private void +vio_fifo_verify(vio_fifo vf) +{ + vio_fifo_lump head ; + vio_fifo_lump lump ; + vio_fifo_lump tail ; + + head = ddl_head(vf->base) ; + tail = ddl_tail(vf->base) ; + + /* If nothing allocated, should all be NULL & !vf->one */ + /* If something allocated, tail must not be NULL */ + if (head == NULL) + { + if ( (tail != NULL) + || (vf->put_ptr != NULL) + || (vf->put_end != NULL) + || (vf->get_ptr != NULL) + || (vf->rdr_lump != NULL) + || (vf->rdr_ptr != NULL) + || (vf->one) ) + zabort("nothing allocated, but not all NULL") ; + return ; + } + else + { + if (tail == NULL) + zabort("head pointer not NULL, but tail pointer is") ; + } ; + + /* Check that all the pointers are within respective lumps + * + * Know that put_end is always tail->end, but get_end need not be. + */ + if ( (tail->data > vf->put_ptr) + || (vf->put_ptr > vf->put_end) + || (vf->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; + + if ( (head->data > vf->get_ptr) + || (vf->get_ptr > vf->get_end) + || (vf->get_end > head->end) ) + zabort("get pointers outside the head lump") ; + + /* If head == tail, should be vf->one, etc. */ + if (head == tail) + { + if (!vf->one) + zabort("have one lump, but !vf->one") ; + + if (vf->get_end > vf->put_ptr) + zabort("get_end is greater than put_ptr when vf->one") ; + } + else + { + if (vf->one) + zabort("have two or more lumps, but vf->one is true") ; + + if (vf->get_end != head->end) + zabort("get_end is not head->end when !vf->one") ; + } ; + + /* If have an rdr_lump -- make sure everything else is valid */ + if (vf->rdr_lump != NULL) + { + lump = head ; + while (lump != vf->rdr_lump) + { + if (lump == tail) + zabort("rdr_lump is not part of FIFO") ; + lump = ddl_next(lump, list) ; + } ; + + if ( (lump->data > vf->rdr_ptr) + || (vf->rdr_ptr > lump->end) ) + zabort("rdr_ptr outside its lump") ; + + if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) + zabort("rdr_ptr is less than get_ptr in first lump") ; + + if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) + zabort("rdr_ptr is greater than put_ptr in last lump") ; + } + else + { + if (vf->rdr_ptr != NULL) + zabort("rdr_ptr not NULL when rdr_lump is") ; + } +} ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h new file mode 100644 index 00000000..52f3455e --- /dev/null +++ b/lib/vio_fifo.h @@ -0,0 +1,205 @@ +/* VTY I/O FIFO -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_VIO_FIFO_H +#define _ZEBRA_VIO_FIFO_H + +#include "zebra.h" +#include <stdint.h> +#include <stdbool.h> + +#include "list_util.h" +#include "zassert.h" + +#ifndef Inline /* in case of compiler issues */ +#define Inline static inline +#endif + +#ifndef Private /* extern, but for "friends" only */ +#define Private extern +#endif + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +/*============================================================================== + * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. + */ + +#ifdef NDEBUG +# define VIO_FIFO_DEBUG 0 /* NDEBUG override */ +#else +# ifndef VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG 1 /* Set to 1 to turn on debug checks */ +# endif +#endif + +/*============================================================================== + * Data Structures + */ +typedef struct vio_fifo vio_fifo_t ; +typedef struct vio_fifo* vio_fifo ; + +typedef struct vio_fifo_lump vio_fifo_lump_t ; +typedef struct vio_fifo_lump* vio_fifo_lump ; + +struct vio_fifo +{ + struct dl_base_pair(vio_fifo_lump) base ; + + bool one ; + + char* put_ptr ; + char* put_end ; + + char* get_ptr ; + char* get_end ; + + vio_fifo_lump rdr_lump ; + char* rdr_ptr ; + + size_t size ; + + vio_fifo_lump spare ; +} ; + +struct vio_fifo_lump +{ + struct dl_list_pair(vio_fifo_lump) list ; + + char* end ; /* end of this particular lump */ + size_t size ; /* size of lump when allocated */ + char data[] ; +} ; + +/*============================================================================== + * Functions + */ + +extern vio_fifo vio_fifo_init_new(vio_fifo vf, size_t size) ; +extern vio_fifo vio_fifo_reset(vio_fifo vf, int free_structure) ; + +#define vio_fifo_reset_keep(vf) vio_fifo_reset(vf, 0) +#define vio_fifo_reset_free(vf) vio_fifo_reset(vf, 1) + +extern void vio_fifo_clear(vio_fifo vf) ; +Inline bool vio_fifo_empty(vio_fifo vf) ; +extern size_t vio_fifo_room(vio_fifo vf) ; + +extern void vio_fifo_put(vio_fifo vf, const char* src, size_t n) ; +Inline void vio_fifo_put_byte(vio_fifo vf, char b) ; + +extern int vio_fifo_printf(vio_fifo vf, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern int vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) ; + +extern size_t vio_fifo_get(vio_fifo vf, void* dst, size_t n) ; +Inline int vio_fifo_get_byte(vio_fifo vf) ; +extern void* vio_fifo_get_lump(vio_fifo vf, size_t* have) ; +extern void vio_fifo_got_upto(vio_fifo vf, void* here) ; + +Inline bool vio_fifo_full_lump(vio_fifo vf) ; +extern int vio_fifo_write_nb(vio_fifo vf, int fd, bool all) ; + +extern void* vio_fifo_get_rdr(vio_fifo vf, size_t* have) ; +extern void* vio_fifo_step_rdr(vio_fifo vf, size_t* have, size_t step) ; +extern void vio_fifo_sync_rdr(vio_fifo vf) ; +extern void vio_fifo_drop_rdr(vio_fifo vf) ; + +Private void vio_fifo_lump_new(vio_fifo vf, size_t size) ; +Private int vio_fifo_get_next_byte(vio_fifo vf) ; + +/*============================================================================== + * Debug -- verification function + */ + +Private void vio_fifo_verify(vio_fifo vf) ; + +#if VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf) +#else +# define VIO_FIFO_DEBUG_VERIFY(vf) +#endif + +/*============================================================================== + * Inline Functions + */ + +/*------------------------------------------------------------------------------ + * Returns true <=> FIFO is empty + */ +Inline bool +vio_fifo_empty(vio_fifo vf) +{ + return (vf->get_ptr == vf->put_ptr) ; +} + +/*------------------------------------------------------------------------------ + * Put one byte to the FIFO + */ +Inline void +vio_fifo_put_byte(vio_fifo vf, char b) +{ + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *vf->put_ptr++ = b ; +} ; + +/*------------------------------------------------------------------------------ + * Get one byte from the FIFO. + * + * Returns: 0x00..0xFF -- byte value (as an int) + * -1 => FIFO is empty. + */ +Inline int +vio_fifo_get_byte(vio_fifo vf) +{ + if (vf->get_end <= (vf->get_ptr + 1)) + return vio_fifo_get_next_byte(vf) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return (unsigned char)*vf->get_ptr++ ; +} ; + +/*------------------------------------------------------------------------------ + * See if have at least one full lump. + * + * This may be used with vio_fifo_write_nb(..., false) to use FIFO as a sort of + * double buffer. + * + * Returns: true <=> there is at least one full lump in the FIFO + * (excluding the last lump if it happens to be full) + */ +Inline bool +vio_fifo_full_lump(vio_fifo vf) +{ + return (!vf->one && (vf->put_ptr != NULL)) ; +} ; + +#endif /* _ZEBRA_VIO_FIFO_H */ diff --git a/lib/vio_lines.c b/lib/vio_lines.c new file mode 100644 index 00000000..c2e9c43c --- /dev/null +++ b/lib/vio_lines.c @@ -0,0 +1,380 @@ +/* Line Control for VTY Terminal output + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stdint.h> + +#include "memory.h" +#include "zassert.h" + +#include "vio_lines.h" +#include "qiovec.h" + +/*============================================================================== + * Line control handles the output of simple text to a telnet connection, + * folding and counting lines (for "--more--" purposes) if required. + * + * LIMITATIONS: + * + * 1) does not handle '\r' except as part of '\r''\n' pairs. + * + * Telnet requires that bare '\r' be sent as '\r''\0'. That is not + * implemented. + * + * The handling of '\r' which is not part of '\r''\n' is UNDEFINED. + * (In particular, the '\r' may be sent as is, or not sent at all.) + * + * 2) does not worry about '\t' or '\b' or any other control character. + * + * Apart from '\r' and '\n' all characters are deemed to be printing + * characters -- and to have width == 1. + * + * 3) has no idea about escape sequences or telnet commands. + * + * In particular: when looking for '\n' (and '\r') has no way of telling + * if those are part of an escape sequence. + * + * 4) DOES NOT handle 0xFF character value. + * + * For Telnet this should be escaped. It isn't. + * + * Current use of VTY command output will not be troubled by these limitations. + * To do more would cost code and cpu unnecessarily. + * + * WHAT IT DOES DO: + * + * 1) maps bare '\n' to '\r''\n'. + * + * Swallows '\r' immediately before '\n' if present. + * + * 2) if required, breaks output into screen width chunks, and counts + * down the height of a "screen full". + * + */ + +/*============================================================================== + * Initialise, allocate, reset etc. + */ + +/*------------------------------------------------------------------------------ + * Initialise new vio_line_control -- allocate if required. + * + * This is for initialising a new structure. Any current contents are lost. + * + * A width of <= 0 => very large width indeed. + * A height of <= 0 => indefinite height + * + * Pause is unset. vio_lc_append will collect an indefinite number of lines. + * + * Column and line position set to zero. + * + * Returns: address of vio_line_control + */ +extern vio_line_control +vio_lc_init_new(vio_line_control lc, int width, int height) +{ + if (lc == NULL) + lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ; + else + memset(lc, 0, sizeof(struct vio_line_control)) ; + + /* Zeroising has set: + * + * pause = 0 -- no limit on the number of lines to append + * paused = 0 -- not paused + * + * col = 0 -- at column 0 + * lines = 0 -- no lines collected, yet + * + * iov = all 0 -- empty + * writing = 0 -- not writing + */ + + lc->width = width >= 0 ? width : 0 ; + lc->height = height >= 0 ? height : 0 ; + + return lc ; +} ; + +/*------------------------------------------------------------------------------ + * Reset vio_line_control (if any) -- release body and (if required) the + * structure. + * + * Returns: address of vio_line_control (if any) -- NULL if structure released + */ +extern vio_line_control +vio_lc_reset(vio_line_control lc, bool free_structure) +{ + if (lc != NULL) + { + if (free_structure) + XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */ + else + vio_lc_init_new(lc, lc->width, lc->height) ; + /* re-initialise */ + } ; + + return lc ; +} ; + +/*------------------------------------------------------------------------------ + * Clear given vio_line_control. + * + * Sets: pause = 0 + * paused = 0 + * col = 0 + * writing = 0 + * + * NB: it is the callers responsibility to release anything buffered because + * it was earlier appended. + */ +extern void +vio_lc_clear(vio_line_control lc) +{ + qiovec_clear(&lc->qiov) ; + + lc->pause = 0 ; + lc->paused = 0 ; + lc->col = 0 ; + lc->writing = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Sets width and height for line control + * + * A width of <= 0 => very large width indeed. + * A height of <= 0 => indefinite height + * + * Pause is adjusted if it is not zero, and may become zero and set paused. + */ +extern void +vio_lc_set_window(vio_line_control lc, int width, int height) +{ + unsigned old_height ; + + old_height = lc->height ; + + lc->width = width >= 0 ? width : 0 ; + lc->height = height >= 0 ? height : 0 ; + + if (lc->pause != 0) + { + if (lc->height > old_height) + lc->pause += lc->height - old_height ; + else + { + if (lc->pause >= (old_height - lc->height)) + lc->pause = 0 ; + else + lc->pause -= old_height - lc->height ; + } ; + lc->paused = (lc->pause == 0) ; + } ; +} ; + +/*============================================================================== + * Appending and writing + */ + +/*------------------------------------------------------------------------------ + * Sets pause to the current height and clear paused. + */ +extern void +vio_lc_set_pause(vio_line_control lc) +{ + lc->pause = lc->height ; + lc->paused = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Put newline (if required) and account for it + */ +static inline void +vio_lc_newline(vio_line_control lc, bool required) +{ + if (required) + qiovec_push(&lc->qiov, "\r\n", 2) ; + + lc->col = 0 ; + lc->line += 1 ; + if (lc->pause != 0) + { + lc->pause -= 1 ; + lc->paused = (lc->pause == 0) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Append a lump of output to the given line control's buffers. + * + * Breaks the output into lines which are no longer than the lc->width. + * + * Maps '\n' to '\r''\n'. + * + * Discards '\r' if found before '\n', and possibly at other times. + * + * If lc->width == 0, use a very large width indeed. + * + * If lc->pause == 0, append an indefinite number of lines + * + * NB: the buffer presented MUST be retained until the contents of the + * line control's buffers have been written. + * + * Returns: number of bytes able to append + */ +extern size_t +vio_lc_append(vio_line_control lc, const void* buf, size_t len) +{ + const char* p ; + const char* end ; + + unsigned width ; + unsigned pause ; + + /* Prepare local width and pause */ + if (lc->width > 0) + width = lc->width ; + else + width = UINT_MAX ; + + if (lc->pause > 0) + pause = 0 ; + else + pause = 1 ; + + lc->paused = 0 ; + + /* Append: stop when run out of data or run out of lines */ + end = (const char*)buf + len ; + p = buf ; + + while ((p < end) && (lc->pause != pause)) + { + const char* e ; + bool nl ; + int nlx ; + + nlx = 0 ; /* no line ending chars yet */ + + /* scan for '\n'. */ + e = memchr(p, '\n', (end - p)) ; + nl = (e != NULL) ; + if (nl) + ++nlx ; /* account for the '\n' */ + else + e = end ; /* use all there is */ + + /* peel off trailing '\r'. + * + * NB: if have not got a '\n', then this may discard a bare + * '\r' -- but bare '\r' are undefined in any case. + */ + if ((e > p) && (*(e - 1) == '\r')) + { + --e ; /* strip the '\r' */ + ++nlx ; /* but account for it */ + } + + /* have p..e characters and possibly nl to add to the output. + * + * Note that if enters the while, (e - p) > 0. So there is at least one + * character to add. This avoids generating a spurious line ending if + * the width has been reduced, and the next thing output is a line end. + */ + while ((p < e) && (lc->pause != pause)) + { + const char* t ; + unsigned col ; + + col = lc->col + (e - p) ; /* NB: e > p */ + + if (col > width) + { + /* can use only part of what there is */ + if (width > lc->col) + t = p + (width - lc->col) ; + /* take to edge of screen */ + else + t = p ; + assert(t < e) ; /* if not need to deal with nl */ + } + else + { + /* can use all of what there is */ + if (nlx == 2) /* if have crlf, use it */ + { + e += nlx ; /* use the crlf that's there */ + nlx = 0 ; /* used it */ + } ; + + t = e ; /* take it all */ + } ; + + assert(t >= p) ; + if (t != p) + qiovec_push(&lc->qiov, p, (t - p)) ; + + /* advance. If not taken all the line, need a crlf */ + p = t ; + + if (p < e) + vio_lc_newline(lc, 1) ; + } ; + + /* If taken all of line, deal with any outstanding nl and nlx */ + if (p == e) + { + if (nl) + vio_lc_newline(lc, (nlx != 0)) ; + + p += nlx ; /* step past '\r' or '\n' */ + } ; + } ; + + /* Exhausted the available data or the line count */ + assert(p <= end) ; + + return (p - (const char*)buf) ; /* what have taken */ +} ; + +/*------------------------------------------------------------------------------ + * Write away any collected output -- assuming NON-BLOCKING. + * + * Does nothing if the line control is empty. + * + * Loops internally if gets EINTR. + * + * Returns: > 0 => one or more bytes left to output + * 0 => all done -- zero bytes left to output + * -1 => failed -- see errno + * + * Sets lc->writing if write does not complete + */ +extern int +vio_lc_write_nb(int fd, vio_line_control lc) +{ + int ret ; + + ret = qiovec_write_nb(fd, &lc->qiov) ; + + lc->writing = (ret > 0) ; + + return ret ; +} ; diff --git a/lib/vio_lines.h b/lib/vio_lines.h new file mode 100644 index 00000000..ffef94ec --- /dev/null +++ b/lib/vio_lines.h @@ -0,0 +1,91 @@ +/* Line Control for VTY Terminal output -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your + * option) any later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_VIO_LINES_H +#define _ZEBRA_VIO_LINES_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#include "qiovec.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * + */ + +/*------------------------------------------------------------------------------ + * Line control -- collecting lines of a given width for output. + * + * NB: a completely zero structure is a valid, clear vio_line_control. + */ + +typedef struct vio_line_control* vio_line_control ; +typedef struct vio_line_control vio_line_control_t ; +struct vio_line_control +{ + unsigned width ; /* console width -- 0 => HUGE */ + unsigned height ; /* console height -- 0 => indefinite */ + + unsigned pause ; /* number of lines to next pause + 0 => indefinite */ + bool paused ; /* true <=> last append stopped on pause */ + + unsigned col ; /* current column position */ + unsigned line ; /* line number of last complete line */ + + struct qiovec qiov ; /* iovec control */ + bool writing ; /* write started, but not completed */ +} ; + +/*============================================================================== + * Functions + */ +extern vio_line_control vio_lc_init_new(vio_line_control lc, int width, + int height) ; +extern vio_line_control vio_lc_reset(vio_line_control lc, bool free_structure) ; + +#define vio_lc_reset_keep(lc) vio_lc_reset(lc, 0) +#define vio_lc_reset_free(lc) vio_lc_reset(lc, 1) + +Inline bool vio_lc_empty(vio_line_control lc) ; +extern void vio_lc_clear(vio_line_control lc) ; +extern void vio_lc_set_window(vio_line_control lc, int width, int height) ; + +extern void vio_lc_set_pause(vio_line_control lc) ; +extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) ; +extern int vio_lc_write_nb(int fd, vio_line_control lc) ; + +/*------------------------------------------------------------------------------ + * Is given line control empty ? + */ +Inline bool +vio_lc_empty(vio_line_control lc) +{ + return qiovec_empty(&lc->qiov) ; +} ; + +#endif /* _ZEBRA_VIO_LINES_H */ @@ -1,7 +1,8 @@ -/* - * Virtual terminal [aka TeletYpe] interface routine. +/* VTY top level * Copyright (C) 1997, 98 Kunihiro Ishiguro * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it @@ -17,1083 +18,872 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ -#include <zebra.h> +#include "zebra.h" +#include <stdbool.h> +#include "lib/version.h" -#include "linklist.h" -#include "thread.h" -#include "buffer.h" -#include <lib/version.h> -#include "command.h" -#include "sockunion.h" -#include "memory.h" -#include "str.h" -#include "log.h" -#include "prefix.h" -#include "filter.h" +#include "vty_io.h" #include "vty.h" -#include "privs.h" -#include "network.h" - -#include <arpa/telnet.h> - -/* Vty events */ -enum event -{ - VTY_SERV, - VTY_READ, - VTY_WRITE, - VTY_TIMEOUT_RESET, -#ifdef VTYSH - VTYSH_SERV, - VTYSH_READ, - VTYSH_WRITE -#endif /* VTYSH */ -}; +#include "uty.h" +#include "vty_cli.h" -static void vty_event (enum event, int, struct vty *); +#include "list_util.h" -/* Extern host structure from command.c */ -extern struct host host; - -/* Vector which store each vty structure. */ -static vector vtyvec; +#include "command.h" +#include "command_queue.h" +#include "command_execute.h" +#include "memory.h" +#include "log.h" +#include "mqueue.h" -/* Vty timeout value. */ -static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; +/*============================================================================== + * Variables etc. (see uty.h) + */ -/* Vty access-class command */ -static char *vty_accesslist_name = NULL; +/* The mutex and related debug counters */ +qpt_mutex_t vty_mutex ; -/* Vty access-calss for IPv6. */ -static char *vty_ipv6_accesslist_name = NULL; +#if VTY_DEBUG -/* VTY server thread. */ -vector Vvty_serv_thread; +int vty_lock_count = 0 ; +int vty_assert_fail = 0 ; -/* Current directory. */ -char *vty_cwd = NULL; +#endif -/* Configure lock. */ -static int vty_config; +/* For thread handling -- initialised in vty_init */ +struct thread_master* vty_master = NULL ; -/* Login password check. */ -static int no_password_check = 0; +/* In the qpthreads world, have nexus for the CLI and one for the Routeing + * Engine. Some commands are processed directly in the CLI, most have to + * be sent to the Routeing Engine. + */ +qpn_nexus vty_cli_nexus = NULL ; +qpn_nexus vty_cmd_nexus = NULL ; -/* Restrict unauthenticated logins? */ -static const u_char restricted_mode_default = 0; -static u_char restricted_mode = 0; +/* List of all known vio */ +vty_io vio_list_base = NULL ; -/* Integrated configuration file path */ -char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; +/* List of all vty which are in monitor state. */ +vty_io vio_monitors_base = NULL ; - -/* VTY standard output function. */ -int -vty_out (struct vty *vty, const char *format, ...) -{ - va_list args; - int len = 0; - int size = 1024; - char buf[1024]; - char *p = NULL; +/* List of all vty which are on death watch */ +vty_io vio_death_watch = NULL ; - if (vty_shell (vty)) - { - va_start (args, format); - vprintf (format, args); - va_end (args); - } - else - { - /* Try to write to initial buffer. */ - va_start (args, format); - len = vsnprintf (buf, sizeof buf, format, args); - va_end (args); +/* Vty timeout value -- see "exec timeout" command */ +unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; - /* Initial buffer is not enough. */ - if (len < 0 || len >= size) - { - while (1) - { - if (len > -1) - size = len + 1; - else - size = size * 2; +/* Vty access-class command */ +char *vty_accesslist_name = NULL; - p = XREALLOC (MTYPE_VTY_OUT_BUF, p, size); - if (! p) - return -1; +/* Vty access-class for IPv6. */ +char *vty_ipv6_accesslist_name = NULL; - va_start (args, format); - len = vsnprintf (p, size, format, args); - va_end (args); +/* Current directory -- initialised in vty_init() */ +static char *vty_cwd = NULL; - if (len > -1 && len < size) - break; - } - } +/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */ +bool vty_config = 0 ; - /* When initial buffer is enough to store all output. */ - if (! p) - p = buf; +/* Login password check override. */ +bool no_password_check = 0; - /* Pointer p must point out buffer. */ - buffer_put (vty->obuf, (u_char *) p, len); +/* Restrict unauthenticated logins? */ +const bool restricted_mode_default = 0 ; + bool restricted_mode = 0 ; - /* If p is not different with buf, it is allocated buffer. */ - if (p != buf) - XFREE (MTYPE_VTY_OUT_BUF, p); - } +/* Watch-dog timer. */ +union vty_watch_dog vty_watch_dog = { NULL } ; - return len; -} +/*------------------------------------------------------------------------------ + * VTYSH stuff + */ -static int -vty_log_out (struct vty *vty, const char *level, const char *proto_str, - const char *format, struct timestamp_control *ctl, va_list va) -{ - int ret; - int len; - char buf[1024]; +/* Integrated configuration file path -- for VTYSH */ +char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; - if (!ctl->already_rendered) - { - ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); - ctl->already_rendered = 1; - } - if (ctl->len+1 >= sizeof(buf)) - return -1; - memcpy(buf, ctl->buf, len = ctl->len); - buf[len++] = ' '; - buf[len] = '\0'; - - if (level) - ret = snprintf(buf+len, sizeof(buf)-len, "%s: %s: ", level, proto_str); - else - ret = snprintf(buf+len, sizeof(buf)-len, "%s: ", proto_str); - if ((ret < 0) || ((size_t)(len += ret) >= sizeof(buf))) - return -1; +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void uty_reset (bool final, const char* why) ; +static void uty_init_commands (void) ; +static void vty_save_cwd (void) ; - if (((ret = vsnprintf(buf+len, sizeof(buf)-len, format, va)) < 0) || - ((size_t)((len += ret)+2) > sizeof(buf))) - return -1; +/*------------------------------------------------------------------------------ + * Tracking the initialisation state. + */ +enum vty_init_state +{ + vty_init_pending = 0, /* first and lowest numbered state */ + vty_init_1st_stage, + vty_init_2nd_stage, + vty_init_started, + vty_init_reset, + vty_init_terminated /* final and highest numbered state */ +}; - buf[len++] = '\r'; - buf[len++] = '\n'; +static enum vty_init_state vty_init_state ; - if (write(vty->fd, buf, len) < 0) - { - if (ERRNO_IO_RETRY(errno)) - /* Kernel buffer is full, probably too much debugging output, so just - drop the data and ignore. */ - return -1; - /* Fatal I/O error. */ - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: write failed to vty client fd %d, closing: %s", - __func__, vty->fd, safe_strerror(errno)); - buffer_reset(vty->obuf); - /* cannot call vty_close, because a parent routine may still try - to access the vty struct */ - vty->status = VTY_CLOSE; - shutdown(vty->fd, SHUT_RDWR); - return -1; - } - return 0; -} +/*============================================================================== + * Public Interface + */ -/* Output current time to the vty. */ -void -vty_time_print (struct vty *vty, int cr) +/*------------------------------------------------------------------------------ + * Initialise vty handling (threads and pthreads) + * + * Install vty's own commands like `who' command. + * + * This runs before any pthreads or nexus stuff starts -- so is, implicitly, + * in the CLI thread. + * + * NB: may be called once and once only. + */ +extern void +vty_init (struct thread_master *master_thread) { - char buf [25]; - - if (quagga_timestamp(0, buf, sizeof(buf)) == 0) - { - zlog (NULL, LOG_INFO, "quagga_timestamp error"); - return; - } - if (cr) - vty_out (vty, "%s\n", buf); - else - vty_out (vty, "%s ", buf); + VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ + VTY_ASSERT_CLI_THREAD() ; /* True if !qpthreads_enabled */ - return; -} + assert(vty_init_state == vty_init_pending) ; -/* Say hello to vty interface. */ -void -vty_hello (struct vty *vty) -{ - if (host.motdfile) - { - FILE *f; - char buf[4096]; + vty_master = master_thread; /* Local pointer to the master thread */ - f = fopen (host.motdfile, "r"); - if (f) - { - while (fgets (buf, sizeof (buf), f)) - { - char *s; - /* work backwards to ignore trailling isspace() */ - for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); - s--); - *s = '\0'; - vty_out (vty, "%s%s", buf, VTY_NEWLINE); - } - fclose (f); - } - else - vty_out (vty, "MOTD file not found%s", VTY_NEWLINE); - } - else if (host.motd) - vty_out (vty, "%s", host.motd); -} + vty_save_cwd (); /* need cwd for config reading */ -/* Put out prompt and wait input from user. */ -static void -vty_prompt (struct vty *vty) -{ - struct utsname names; - const char*hostname; + vio_list_base = NULL ; /* no VTYs yet */ + vio_monitors_base = NULL ; + vio_death_watch = NULL ; - if (vty->type == VTY_TERM) - { - hostname = host.name; - if (!hostname) - { - uname (&names); - hostname = names.nodename; - } - vty_out (vty, cmd_prompt (vty->node), hostname); - } -} + vty_cli_nexus = NULL ; /* not running qnexus-wise */ + vty_cmd_nexus = NULL ; -/* Send WILL TELOPT_ECHO to remote server. */ -static void -vty_will_echo (struct vty *vty) -{ - unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' }; - vty_out (vty, "%s", cmd); -} + vty_watch_dog.anon = NULL ; /* no watch dog */ -/* Make suppress Go-Ahead telnet option. */ -static void -vty_will_suppress_go_ahead (struct vty *vty) -{ - unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' }; - vty_out (vty, "%s", cmd); -} + uty_init_commands() ; /* install nodes */ -/* Make don't use linemode over telnet. */ -static void -vty_dont_linemode (struct vty *vty) -{ - unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' }; - vty_out (vty, "%s", cmd); -} + vty_init_state = vty_init_1st_stage ; -/* Use window size. */ -static void -vty_do_window_size (struct vty *vty) -{ - unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' }; - vty_out (vty, "%s", cmd); + VTY_UNLOCK() ; } -#if 0 /* Currently not used. */ -/* Make don't use lflow vty interface. */ -static void -vty_dont_lflow_ahead (struct vty *vty) +/*------------------------------------------------------------------------------ + * Further initialisation for qpthreads. + * + * This is done during "second stage" initialisation, when all nexuses have + * been set up and the qpthread_enabled state established. + * + * This is before any threads have been started, so is, implicitly, in the + * CLI thread. + * + * Need to know where the CLI nexus and the Routeing Engine nexus are. + * + * Initialise mutex. + * + * Cannot lock or assert in CLI thread while initialising those things ! + * + * NB: may be called once and once only. + */ +extern void +vty_init_r (qpn_nexus cli, qpn_nexus cmd) { - unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW, '\0' }; - vty_out (vty, "%s", cmd); -} -#endif /* 0 */ + assert(vty_init_state == vty_init_1st_stage) ; -/* Allocate new vty struct. */ -struct vty * -vty_new () -{ - struct vty *new = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + vty_cli_nexus = cli ; + vty_cmd_nexus = cmd ; - new->obuf = buffer_new(0); /* Use default buffer size. */ - new->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); - new->max = VTY_BUFSIZ; + qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); - return new; -} + vty_init_state = vty_init_2nd_stage ; +} ; -/* Authentication of vty */ -static void -vty_auth (struct vty *vty, char *buf) +/*------------------------------------------------------------------------------ + * Initialisation for vtysh application. + * + * TODO: work out what this needs to do ! (If anything.) + */ +extern void +vty_init_vtysh (void) { - char *passwd = NULL; - enum node_type next_node = 0; - int fail; - char *crypt (const char *, const char *); + VTY_LOCK() ; - switch (vty->node) - { - case AUTH_NODE: - if (host.encrypt) - passwd = host.password_encrypt; - else - passwd = host.password; - if (host.advanced) - next_node = host.enable ? VIEW_NODE : ENABLE_NODE; - else - next_node = VIEW_NODE; - break; - case AUTH_ENABLE_NODE: - if (host.encrypt) - passwd = host.enable_encrypt; - else - passwd = host.enable; - next_node = ENABLE_NODE; - break; - } + VTY_UNLOCK() ; +} ; - if (passwd) - { - if (host.encrypt) - fail = strcmp (crypt(buf, passwd), passwd); - else - fail = strcmp (buf, passwd); - } - else - fail = 1; - - if (! fail) - { - vty->fail = 0; - vty->node = next_node; /* Success ! */ - } - else - { - vty->fail++; - if (vty->fail >= 3) - { - if (vty->node == AUTH_NODE) - { - vty_out (vty, "%% Bad passwords, too many failures!%s", VTY_NEWLINE); - vty->status = VTY_CLOSE; - } - else - { - /* AUTH_ENABLE_NODE */ - vty->fail = 0; - vty_out (vty, "%% Bad enable passwords, too many failures!%s", VTY_NEWLINE); - vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; - } - } - } -} - -/* Command execution over the vty interface. */ -static int -vty_command (struct vty *vty, char *buf) +/*------------------------------------------------------------------------------ + * Start the VTY going. + * + * This starts the listeners for VTY_TERM and VTY_SHELL_SERV. + * + * Also starts the watch dog. + * + * This is run during early morning start, after any daemonisation, but before + * any threads are started -- so is, implicitly, in the CLI thread. + * + * NB: may be called once and once only. + * + * NB: MUST be in the CLI thread (if any). + */ +extern void +vty_start(const char *addr, unsigned short port, const char *path) { - int ret; - vector vline; - const char *protocolname; - - /* Split readline string up into the vector */ - vline = cmd_make_strvec (buf); - - if (vline == NULL) - return CMD_SUCCESS; + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; -#ifdef CONSUMED_TIME_CHECK - { - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; + assert( (vty_init_state == vty_init_1st_stage) + || (vty_init_state == vty_init_2nd_stage) ) ; - GETRUSAGE(&before); -#endif /* CONSUMED_TIME_CHECK */ + uty_watch_dog_start() ; - ret = cmd_execute_command (vline, vty, NULL, 0); + uty_open_listeners(addr, port, path) ; - /* Get the name of the protocol if any */ - if (zlog_default) - protocolname = zlog_proto_names[zlog_default->protocol]; - else - protocolname = zlog_proto_names[ZLOG_NONE]; - -#ifdef CONSUMED_TIME_CHECK - GETRUSAGE(&after); - if ((realtime = thread_consumed_time(&after, &before, &cputime)) > - CONSUMED_TIME_CHECK) - /* Warn about CPU hog that must be fixed. */ - zlog_warn("SLOW COMMAND: command took %lums (cpu time %lums): %s", - realtime/1000, cputime/1000, buf); - } -#endif /* CONSUMED_TIME_CHECK */ - - if (ret != CMD_SUCCESS) - switch (ret) - { - case CMD_WARNING: - if (vty->type == VTY_FILE) - vty_out (vty, "Warning...%s", VTY_NEWLINE); - break; - case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - break; - case CMD_ERR_NO_MATCH: - vty_out (vty, "%% [%s] Unknown command: %s%s", protocolname, buf, VTY_NEWLINE); - break; - case CMD_ERR_INCOMPLETE: - vty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); - break; - } - cmd_free_strvec (vline); - - return ret; -} - -static const char telnet_backward_char = 0x08; -static const char telnet_space_char = ' '; + vty_init_state = vty_init_started ; + VTY_UNLOCK() ; +} ; -/* Basic function to write buffer to vty. */ -static void -vty_write (struct vty *vty, const char *buf, size_t nbytes) +/*------------------------------------------------------------------------------ + * Reset all VTY status for reasons unknown -- probably SIGHUP + */ +extern void +vty_reset() { - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - return; - - /* Should we do buffering here ? And make vty_flush (vty) ? */ - buffer_put (vty->obuf, buf, nbytes); + vty_reset_because("Reset") ; } -/* Ensure length of input buffer. Is buffer is short, double it. */ -static void -vty_ensure (struct vty *vty, int length) -{ - if (vty->max <= length) - { - vty->max *= 2; - vty->buf = XREALLOC (MTYPE_VTY, vty->buf, vty->max); - } -} - -/* Basic function to insert character into vty. */ -static void -vty_self_insert (struct vty *vty, char c) +/*------------------------------------------------------------------------------ + * Reset all VTY status + * + * This is done in response to SIGHUP -- and runs in the CLI thread. + * + * Half closes all VTY, leaving the death watch to tidy up once all output + * and any command in progress have completed. + * + * Closes all listening sockets. + * + * TODO: revoke ? + * + * NB: old code discarded all output and hard closed all the VTY... + */ +extern void +vty_reset_because(const char* why) { - int i; - int length; + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; - vty_ensure (vty, vty->length + 1); - length = vty->length - vty->cp; - memmove (&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); - vty->buf[vty->cp] = c; + assert(vty_init_state == vty_init_started) ; - vty_write (vty, &vty->buf[vty->cp], length + 1); - for (i = 0; i < length; i++) - vty_write (vty, &telnet_backward_char, 1); + uty_reset(0, why) ; /* not final ! */ - vty->cp++; - vty->length++; + vty_init_state = vty_init_reset ; + VTY_UNLOCK() ; } -/* Self insert character 'c' in overwrite mode. */ -static void -vty_self_insert_overwrite (struct vty *vty, char c) -{ - vty_ensure (vty, vty->length + 1); - vty->buf[vty->cp++] = c; +/*------------------------------------------------------------------------------ + * Restart the VTY, following a vty_reset(). + * + * This starts the listeners for VTY_TERM and VTY_SHELL_SERV, again. + * + * NB: may be called once, and once only, *after* a vty_reset(). + * + * NB: need not be in the CLI thread (if any). + */ +struct vty_restart_args +{ + char* addr ; + unsigned short port ; + char* path ; +} ; +MQB_ARGS_SIZE_OK(vty_restart_args) ; + +static void uty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; +static void uty_restart(const char *addr, unsigned short port, + const char *path) ; +extern void +vty_restart(const char *addr, unsigned short port, const char *path) +{ + VTY_LOCK() ; + + /* If not running qnexus-wise, call uty_restart directly. + * + * Otherwise, construct and dispatch message to do a uty_restart. + */ + if (!vty_cli_nexus) + uty_restart(addr, port, path) ; + else + { + mqueue_block mqb ; + struct vty_restart_args* args ; - if (vty->cp > vty->length) - vty->length++; + mqb = mqb_init_new(NULL, uty_restart_action, vty_cli_nexus) ; + args = mqb_get_args(mqb) ; - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - return; + if (addr != NULL) + args->addr = XSTRDUP(MTYPE_TMP, addr) ; + else + args->addr = NULL ; - vty_write (vty, &c, 1); -} + args->port = port ; -/* Insert a word into vty interface with overwrite mode. */ -static void -vty_insert_word_overwrite (struct vty *vty, char *str) -{ - int len = strlen (str); - vty_write (vty, str, len); - strcpy (&vty->buf[vty->cp], str); - vty->cp += len; - vty->length = vty->cp; -} + if (path != NULL) + args->path = XSTRDUP(MTYPE_TMP, path) ; + else + args->path = NULL ; -/* Forward character. */ -static void -vty_forward_char (struct vty *vty) -{ - if (vty->cp < vty->length) - { - vty_write (vty, &vty->buf[vty->cp], 1); - vty->cp++; - } -} + mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; + } ; + + VTY_UNLOCK() ; +} ; -/* Backward character. */ +/* Deal with the uty_restart message */ static void -vty_backward_char (struct vty *vty) +uty_restart_action(mqueue_block mqb, mqb_flag_t flag) { - if (vty->cp > 0) + struct vty_restart_args* args ; + args = mqb_get_args(mqb) ; + + if (flag == mqb_action) { - vty->cp--; - vty_write (vty, &telnet_backward_char, 1); - } -} + VTY_LOCK() ; -/* Move to the beginning of the line. */ -static void -vty_beginning_of_line (struct vty *vty) -{ - while (vty->cp) - vty_backward_char (vty); -} + uty_restart(args->addr, args->port, args->path) ; -/* Move to the end of the line. */ -static void -vty_end_of_line (struct vty *vty) -{ - while (vty->cp < vty->length) - vty_forward_char (vty); -} + VTY_UNLOCK() ; + } ; -static void vty_kill_line_from_beginning (struct vty *); -static void vty_redraw_line (struct vty *); + if (args->addr != NULL) + XFREE(MTYPE_TMP, args->addr) ; + if (args->path != NULL) + XFREE(MTYPE_TMP, args->path) ; +} ; -/* Print command line history. This function is called from - vty_next_line and vty_previous_line. */ +/* Do the actual restart */ static void -vty_history_print (struct vty *vty) +uty_restart(const char *addr, unsigned short port, const char *path) { - int length; + VTY_ASSERT_LOCKED() ; + assert(vty_init_state == vty_init_reset) ; - vty_kill_line_from_beginning (vty); + uty_open_listeners(addr, port, path) ; - /* Get previous line from history buffer */ - length = strlen (vty->hist[vty->hp]); - memcpy (vty->buf, vty->hist[vty->hp], length); - vty->cp = vty->length = length; - - /* Redraw current line */ - vty_redraw_line (vty); -} + vty_init_state = vty_init_started ; +} ; -/* Show next command line history. */ -static void -vty_next_line (struct vty *vty) +/*------------------------------------------------------------------------------ + * System shut-down + * + * In the pthreads world, all threads other than the main (CLI) thread have + * been joined -- so this is, implicitly, in the CLI thread. + * + * Close all known vty and release all memory -- discard all pending output. + * + * NB: this may be done in any initialisation state. + * + * Note that all the locking stuff does nothing if not qpthreads_enabled, so + * these may be done in any state of initialisation. (It is assumed that the + * switch into qpthreads_enabled is an atomic action... so all second stage + * initialisation completes together.) + */ +extern void +vty_terminate (void) { - int try_index; + if ( (vty_init_state == vty_init_pending) + || (vty_init_state == vty_init_terminated) ) + return ; /* nothing to do ! */ - if (vty->hp == vty->hindex) - return; + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; - /* Try is there history exist or not. */ - try_index = vty->hp; - if (try_index == (VTY_MAXHIST - 1)) - try_index = 0; - else - try_index++; + assert( (vty_init_state > vty_init_pending) + && (vty_init_state < vty_init_terminated) ) ; - /* If there is not history return. */ - if (vty->hist[try_index] == NULL) - return; - else - vty->hp = try_index; - - vty_history_print (vty); -} - -/* Show previous command line history. */ -static void -vty_previous_line (struct vty *vty) -{ - int try_index; + uty_reset(1, "Shut down") ; /* final reset */ - try_index = vty->hp; - if (try_index == 0) - try_index = VTY_MAXHIST - 1; - else - try_index--; + VTY_UNLOCK() ; - if (vty->hist[try_index] == NULL) - return; - else - vty->hp = try_index; + qpt_mutex_destroy(&vty_mutex, 0); - vty_history_print (vty); + vty_init_state = vty_init_terminated ; } -/* This function redraw all of the command line character. */ +/*------------------------------------------------------------------------------ + * Reset -- final or for SIGHUP + * + * Closes listeners. + * + * Closes (final) or half closes (SIGHUP) all VTY, and revokes any outstanding + * commands. + * + * Resets the vty timeout and access lists. + * + * When reach final reset it should not be possible for there to be any + * commands still in progress. If there are, they are simply left on the + * death-watch list... there is no pressing need to do anything more radical, + * and the presence of anything on the death watch is grounds for some debug + * activity ! + */ static void -vty_redraw_line (struct vty *vty) +uty_reset (bool curtains, const char* why) { - vty_write (vty, vty->buf, vty->length); - vty->cp = vty->length; -} + vty_io vio ; + vty_io next ; -/* Forward word. */ -static void -vty_forward_word (struct vty *vty) -{ - while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') - vty_forward_char (vty); - - while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') - vty_forward_char (vty); -} + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; -/* Backward word without skipping training space. */ -static void -vty_backward_pure_word (struct vty *vty) -{ - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_backward_char (vty); -} + uty_close_listeners() ; -/* Backward word. */ -static void -vty_backward_word (struct vty *vty) -{ - while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') - vty_backward_char (vty); + next = sdl_head(vio_list_base) ; + while (next != NULL) + { + vio = next ; + next = sdl_next(vio, vio_list) ; - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_backward_char (vty); -} + if (vio->type == VTY_TERM) + cq_revoke(vio->vty) ; -/* When '^D' is typed at the beginning of the line we move to the down - level. */ -static void -vty_down_level (struct vty *vty) -{ - vty_out (vty, "%s", VTY_NEWLINE); - (*config_exit_cmd.func)(NULL, vty, 0, NULL); - vty_prompt (vty); - vty->cp = 0; -} + if (why != NULL) + vio->close_reason = why ; -/* When '^Z' is received from vty, move down to the enable mode. */ -static void -vty_end_config (struct vty *vty) -{ - vty_out (vty, "%s", VTY_NEWLINE); + if (curtains) + uty_close(vio) ; + else + uty_half_close(vio, why) ; + } ; - switch (vty->node) + vty_timeout_val = VTY_TIMEOUT_DEFAULT; + + if (vty_accesslist_name) { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - vty_config_unlock (vty); - vty->node = ENABLE_NODE; - break; - default: - /* Unknown node, we have to ignore it. */ - break; + XFREE(MTYPE_VTY, vty_accesslist_name); + vty_accesslist_name = NULL; } - vty_prompt (vty); - vty->cp = 0; -} - -/* Delete a charcter at the current point. */ -static void -vty_delete_char (struct vty *vty) -{ - int i; - int size; - - if (vty->length == 0) + if (vty_ipv6_accesslist_name) { - vty_down_level (vty); - return; + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + vty_ipv6_accesslist_name = NULL; } - if (vty->cp == vty->length) - return; /* completion need here? */ + if (curtains && vty_cwd) + XFREE (MTYPE_TMP, vty_cwd); + + if (curtains) + uty_watch_dog_stop() ; /* and final death watch run */ +} ; - size = vty->length - vty->cp; +/*============================================================================== + * Opening and closing VTY. + * + * VTY without a socket may be opened and closed at will. + * + * TODO: sort out the relationship between the non-socket VTY and vty_reset() + */ - vty->length--; - memmove (&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); - vty->buf[vty->length] = '\0'; - - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - return; +/*------------------------------------------------------------------------------ + * Create a new VTY of the given type + * + * The type may NOT be: VTY_TERM or VTY_SHELL_SERV + */ +extern struct vty * +vty_open(enum vty_type type) +{ + struct vty* vty ; - vty_write (vty, &vty->buf[vty->cp], size - 1); - vty_write (vty, &telnet_space_char, 1); + VTY_LOCK() ; + vty = uty_new(type, -1) ; /* fails for VTY_TERM or VTY_SHELL_SERV */ + VTY_UNLOCK() ; - for (i = 0; i < size; i++) - vty_write (vty, &telnet_backward_char, 1); -} + return vty ; +} ; -/* Delete a character before the point. */ -static void -vty_delete_backward_char (struct vty *vty) +/*------------------------------------------------------------------------------ + * Close the given VTY + */ +extern void +vty_close (struct vty *vty) { - if (vty->cp == 0) - return; - - vty_backward_char (vty); - vty_delete_char (vty); + VTY_LOCK() ; + uty_close(vty->vio) ; + VTY_UNLOCK() ; } -/* Kill rest of line from current point. */ -static void -vty_kill_line (struct vty *vty) +/*============================================================================== + * General VTY output. + * + * This is mostly used during command execution, to output the results of the + * command. + * + * All these end up in uty_vout -- see vty_io. + */ + +/*------------------------------------------------------------------------------ + * VTY output -- cf fprintf ! + */ +extern int +vty_out (struct vty *vty, const char *format, ...) { - int i; - int size; + int result; - size = vty->length - vty->cp; - - if (size == 0) - return; + VTY_LOCK() ; + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + VTY_UNLOCK() ; + return result; +} - for (i = 0; i < size; i++) - vty_write (vty, &telnet_space_char, 1); - for (i = 0; i < size; i++) - vty_write (vty, &telnet_backward_char, 1); +/*------------------------------------------------------------------------------ + * VTY output -- output a given numnber of spaces + */ - memset (&vty->buf[vty->cp], 0, size); - vty->length = vty->cp; -} +/* 1 2 3 4 */ +/* 1234567890123456789012345678901234567890 */ +const char vty_spaces_string[] = " " ; +CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ; -/* Kill line from the beginning. */ -static void -vty_kill_line_from_beginning (struct vty *vty) +extern void +vty_out_indent(struct vty *vty, int indent) { - vty_beginning_of_line (vty); - vty_kill_line (vty); -} + while (indent > 0) + { + vty_out(vty, VTY_SPACES(indent)) ; + indent -= VTY_MAX_SPACES ; + } +} ; -/* Delete a word before the point. */ -static void -vty_forward_kill_word (struct vty *vty) +/*------------------------------------------------------------------------------ + * VTY output -- output the current time in standard form, to the second. + */ +extern void +vty_time_print (struct vty *vty, int cr) { - while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') - vty_delete_char (vty); - while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') - vty_delete_char (vty); -} + char buf [timestamp_buffer_len]; -/* Delete a word before the point. */ -static void -vty_backward_kill_word (struct vty *vty) -{ - while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') - vty_delete_backward_char (vty); - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_delete_backward_char (vty); + quagga_timestamp(0, buf, sizeof(buf)) ; + + if (cr) + vty_out (vty, "%s%s", buf, VTY_NEWLINE); + else + vty_out (vty, "%s ", buf); + + return; } -/* Transpose chars before or at the point. */ -static void -vty_transpose_chars (struct vty *vty) +/*------------------------------------------------------------------------------ + * Say hello to vty interface. + */ +void +vty_hello (struct vty *vty) { - char c1, c2; - - /* If length is short or point is near by the beginning of line then - return. */ - if (vty->length < 2 || vty->cp < 1) - return; + VTY_LOCK() ; - /* In case of point is located at the end of the line. */ - if (vty->cp == vty->length) +#ifdef QDEBUG + uty_out (vty, "%s%s", debug_banner, VTY_NEWLINE); +#endif + if (host.motdfile) { - c1 = vty->buf[vty->cp - 1]; - c2 = vty->buf[vty->cp - 2]; + FILE *f; + char buf[4096]; - vty_backward_char (vty); - vty_backward_char (vty); - vty_self_insert_overwrite (vty, c1); - vty_self_insert_overwrite (vty, c2); + f = fopen (host.motdfile, "r"); + if (f) + { + while (fgets (buf, sizeof (buf), f)) + { + char *s; + /* work backwards to ignore trailing isspace() */ + for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); + s--); + *s = '\0'; + uty_out (vty, "%s%s", buf, VTY_NEWLINE); + } + fclose (f); + } + else + uty_out (vty, "MOTD file %s not found%s", host.motdfile, VTY_NEWLINE); } - else - { - c1 = vty->buf[vty->cp]; - c2 = vty->buf[vty->cp - 1]; + else if (host.motd) + uty_out (vty, "%s", host.motd); - vty_backward_char (vty); - vty_self_insert_overwrite (vty, c1); - vty_self_insert_overwrite (vty, c2); - } + VTY_UNLOCK() ; } -/* Do completion at vty interface. */ -static void -vty_complete_command (struct vty *vty) +/*------------------------------------------------------------------------------ + * Clear the contents of the command output FIFO etc. + */ +extern void +vty_out_clear(struct vty* vty) { - int i; - int ret; - char **matched = NULL; - vector vline; + VTY_LOCK() ; + uty_out_clear(vty->vio) ; + VTY_UNLOCK() ; +} ; - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - return; +/*============================================================================== + * Command Execution + */ + +/*------------------------------------------------------------------------------ + * Execute command -- adding to history is not empty or just comment + * + * This is for VTY_TERM type VTY. + * + * Outputs diagnostics if fails to parse. + * + * Returns: command return code + */ +extern enum cmd_return_code +uty_command(struct vty *vty) +{ + enum cmd_return_code ret; - vline = cmd_make_strvec (vty->buf); - if (vline == NULL) - return; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - /* In case of 'help \t'. */ - if (isspace ((int) vty->buf[vty->length - 1])) - vector_set (vline, '\0'); + assert(vty->vio->type == VTY_TERM) ; - matched = cmd_complete_command (vline, vty, &ret); - - cmd_free_strvec (vline); + /* Parse the command and add to history (if not empty) */ + ret = cmd_parse_command(vty, + cmd_parse_completion + cmd_parse_do + cmd_parse_tree) ; + if (ret != CMD_EMPTY) + uty_cli_hist_add (vty->vio, vty->buf) ; - vty_out (vty, "%s", VTY_NEWLINE); - switch (ret) + /* If parsed and not empty, dispatch */ + if (ret == CMD_SUCCESS) { +#ifdef CONSUMED_TIME_CHECK + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; + + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ + + ret = cmd_dispatch(vty, cmd_may_queue) ; + +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + if ((realtime = thread_consumed_time(&after, &before, &cputime)) > + CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, + "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, vty->buf) ; +#endif /* CONSUMED_TIME_CHECK */ + } ; + + /* Deal with the return code */ + switch (ret) + { case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - vty_prompt (vty); - vty_redraw_line (vty); + uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); break; + case CMD_ERR_NO_MATCH: - /* vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); */ - vty_prompt (vty); - vty_redraw_line (vty); + uty_out (vty, "%% Unknown command.%s", VTY_NEWLINE) ; break; - case CMD_COMPLETE_FULL_MATCH: - vty_prompt (vty); - vty_redraw_line (vty); - vty_backward_pure_word (vty); - vty_insert_word_overwrite (vty, matched[0]); - vty_self_insert (vty, ' '); - XFREE (MTYPE_TMP, matched[0]); - break; - case CMD_COMPLETE_MATCH: - vty_prompt (vty); - vty_redraw_line (vty); - vty_backward_pure_word (vty); - vty_insert_word_overwrite (vty, matched[0]); - XFREE (MTYPE_TMP, matched[0]); - vector_only_index_free (matched); - return; - break; - case CMD_COMPLETE_LIST_MATCH: - for (i = 0; matched[i] != NULL; i++) - { - if (i != 0 && ((i % 6) == 0)) - vty_out (vty, "%s", VTY_NEWLINE); - vty_out (vty, "%-10s ", matched[i]); - XFREE (MTYPE_TMP, matched[i]); - } - vty_out (vty, "%s", VTY_NEWLINE); - vty_prompt (vty); - vty_redraw_line (vty); + case CMD_ERR_INCOMPLETE: + uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); break; - case CMD_ERR_NOTHING_TODO: - vty_prompt (vty); - vty_redraw_line (vty); - break; - default: - break; - } - if (matched) - vector_only_index_free (matched); -} -static void -vty_describe_fold (struct vty *vty, int cmd_width, - unsigned int desc_width, struct desc *desc) -{ - char *buf; - const char *cmd, *p; - int pos; + default: + break ; + } ; - cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; + return ret ; +} ; - if (desc_width <= 0) - { - vty_out (vty, " %-*s %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE); - return; - } +/*------------------------------------------------------------------------------ + * Authentication of vty + * + * During AUTH_NODE and AUTH_ENABLE_NODE, when a command line is dispatched by + * any means this function is called. + * + * Note that if the AUTH_NODE password fails too many times, the terminal is + * closed. + * + * Returns: command return code + */ +extern enum cmd_return_code +uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) +{ + char *passwd = NULL; + enum node_type next_node = 0; + int fail; + char *crypt (const char *, const char *); + enum cmd_return_code ret ; - buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + vty_io vio = vty->vio ; - for (p = desc->str; strlen (p) > desc_width; p += pos + 1) - { - for (pos = desc_width; pos > 0; pos--) - if (*(p + pos) == ' ') - break; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - if (pos == 0) - break; + /* What to do ? + * + * In fact, all the exotic command terminators simply discard any input + * and return. + */ + switch (cli_do) + { + case cli_do_nothing: + case cli_do_ctrl_c: + case cli_do_ctrl_z: + return CMD_SUCCESS ; - strncpy (buf, p, pos); - buf[pos] = '\0'; - vty_out (vty, " %-*s %s%s", cmd_width, cmd, buf, VTY_NEWLINE); + case cli_do_command: + break ; - cmd = ""; - } + case cli_do_ctrl_d: + case cli_do_eof: + return uty_cmd_close(vty, "End") ; - vty_out (vty, " %-*s %s%s", cmd_width, cmd, p, VTY_NEWLINE); + default: + zabort("unknown or invalid cli_do") ; + } ; - XFREE (MTYPE_TMP, buf); -} + /* Ordinary command dispatch -- see if password is OK. */ + switch (vty->node) + { + case AUTH_NODE: + if (host.encrypt) + passwd = host.password_encrypt; + else + passwd = host.password; + if (host.advanced) + next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + else + next_node = VIEW_NODE; + break; -/* Describe matched command function. */ -static void -vty_describe_command (struct vty *vty) -{ - int ret; - vector vline; - vector describe; - unsigned int i, width, desc_width; - struct desc *desc, *desc_cr = NULL; + case AUTH_ENABLE_NODE: + if (host.encrypt) + passwd = host.enable_encrypt; + else + passwd = host.enable; + next_node = ENABLE_NODE; + break; - vline = cmd_make_strvec (vty->buf); + default: + zabort("unknown node type") ; + } - /* In case of '> ?'. */ - if (vline == NULL) + if (passwd) { - vline = vector_init (1); - vector_set (vline, '\0'); + if (host.encrypt) + fail = strcmp (crypt(buf, passwd), passwd); + else + fail = strcmp (buf, passwd); } - else - if (isspace ((int) vty->buf[vty->length - 1])) - vector_set (vline, '\0'); - - describe = cmd_describe_command (vline, vty, &ret); + else + fail = 1; - vty_out (vty, "%s", VTY_NEWLINE); + ret = CMD_SUCCESS ; - /* Ambiguous error. */ - switch (ret) + if (! fail) { - case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - goto out; - break; - case CMD_ERR_NO_MATCH: - vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); - goto out; - break; - } - - /* Get width of command string. */ - width = 0; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - unsigned int len; + vio->fail = 0; + vty->node = next_node; /* Success ! */ + } + else + { + vio->fail++; + if (vio->fail >= 3) + { + if (vty->node == AUTH_NODE) + { + ret = uty_cmd_close(vty, "Bad passwords, too many failures!%s") ; + } + else + { + /* AUTH_ENABLE_NODE */ + vio->fail = 0; + uty_out (vty, "%% Bad enable passwords, too many failures!%s", + VTY_NEWLINE); + vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; - if (desc->cmd[0] == '\0') - continue; + ret = CMD_WARNING ; + } + } + } - len = strlen (desc->cmd); - if (desc->cmd[0] == '.') - len--; + return ret ; +} ; - if (width < len) - width = len; - } +/*------------------------------------------------------------------------------ + * Command line "exit" command -- aka "quit" + * + * Falls back one NODE level. + * + * Returns: command return code + */ +extern enum cmd_return_code +vty_cmd_exit(struct vty* vty) +{ + enum cmd_return_code ret ; - /* Get width of description string. */ - desc_width = vty->width - (width + 6); + VTY_LOCK() ; - /* Print out description. */ - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - if (desc->cmd[0] == '\0') - continue; - - if (strcmp (desc->cmd, command_cr) == 0) - { - desc_cr = desc; - continue; - } - - if (!desc->str) - vty_out (vty, " %-s%s", - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - VTY_NEWLINE); - else if (desc_width >= strlen (desc->str)) - vty_out (vty, " %-*s %s%s", width, - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str, VTY_NEWLINE); - else - vty_describe_fold (vty, width, desc_width, desc); - -#if 0 - vty_out (vty, " %-*s %s%s", width - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str ? desc->str : "", VTY_NEWLINE); -#endif /* 0 */ - } - - if ((desc = desc_cr)) + ret = CMD_SUCCESS ; + switch (vty->node) { - if (!desc->str) - vty_out (vty, " %-s%s", - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - VTY_NEWLINE); - else if (desc_width >= strlen (desc->str)) - vty_out (vty, " %-*s %s%s", width, - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str, VTY_NEWLINE); + case VIEW_NODE: + case ENABLE_NODE: + case RESTRICTED_NODE: + if (vty_shell (vty)) + exit (0); else - vty_describe_fold (vty, width, desc_width, desc); + ret = uty_cmd_close(vty, "Exit") ; + break; + case CONFIG_NODE: + uty_config_unlock (vty, ENABLE_NODE); + break; + case INTERFACE_NODE: + case ZEBRA_NODE: + case BGP_NODE: + case RIP_NODE: + case RIPNG_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case MASC_NODE: + case RMAP_NODE: + case VTY_NODE: + vty->node = CONFIG_NODE ; + break; + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: + vty->node = BGP_NODE ; + break; + case KEYCHAIN_KEY_NODE: + vty->node = KEYCHAIN_NODE ; + break; + default: + break; } -out: - cmd_free_strvec (vline); - if (describe) - vector_free (describe); - - vty_prompt (vty); - vty_redraw_line (vty); + VTY_UNLOCK() ; + return ret ; } -static void -vty_clear_buf (struct vty *vty) -{ - memset (vty->buf, 0, vty->max); -} - -/* ^C stop current input and do not add command line to the history. */ -static void -vty_stop_input (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command line "end" command + * + * Falls back to ENABLE_NODE. + * + * Returns: command return code + */ +extern enum cmd_return_code +vty_cmd_end(struct vty* vty) { - vty->cp = vty->length = 0; - vty_clear_buf (vty); - vty_out (vty, "%s", VTY_NEWLINE); + VTY_LOCK() ; switch (vty->node) { @@ -1108,6 +898,11 @@ vty_stop_input (struct vty *vty) case RIP_NODE: case RIPNG_NODE: case BGP_NODE: + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: case RMAP_NODE: case OSPF_NODE: case OSPF6_NODE: @@ -1116,1465 +911,557 @@ vty_stop_input (struct vty *vty) case KEYCHAIN_KEY_NODE: case MASC_NODE: case VTY_NODE: - vty_config_unlock (vty); - vty->node = ENABLE_NODE; + uty_config_unlock (vty, ENABLE_NODE); break; default: - /* Unknown node, we have to ignore it. */ break; } - vty_prompt (vty); - - /* Set history pointer to the latest one. */ - vty->hp = vty->hindex; -} - -/* Add current command line to the history buffer. */ -static void -vty_hist_add (struct vty *vty) -{ - int index; - - if (vty->length == 0) - return; - - index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; - - /* Ignore the same string as previous one. */ - if (vty->hist[index]) - if (strcmp (vty->buf, vty->hist[index]) == 0) - { - vty->hp = vty->hindex; - return; - } - /* Insert history entry. */ - if (vty->hist[vty->hindex]) - XFREE (MTYPE_VTY_HIST, vty->hist[vty->hindex]); - vty->hist[vty->hindex] = XSTRDUP (MTYPE_VTY_HIST, vty->buf); + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; - /* History index rotation. */ - vty->hindex++; - if (vty->hindex == VTY_MAXHIST) - vty->hindex = 0; - - vty->hp = vty->hindex; -} - -/* #define TELNET_OPTION_DEBUG */ - -/* Get telnet window size. */ -static int -vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) +/*------------------------------------------------------------------------------ + * Result of command is to close the input. + * + * Posts the reason for the close. + * + * Returns: CMD_CLOSE + */ +extern enum cmd_return_code +uty_cmd_close(struct vty *vty, const char* reason) { -#ifdef TELNET_OPTION_DEBUG - int i; + vty->vio->close_reason = reason ; + return CMD_CLOSE ; +} ; - for (i = 0; i < nbytes; i++) - { - switch (buf[i]) - { - case IAC: - vty_out (vty, "IAC "); - break; - case WILL: - vty_out (vty, "WILL "); - break; - case WONT: - vty_out (vty, "WONT "); - break; - case DO: - vty_out (vty, "DO "); - break; - case DONT: - vty_out (vty, "DONT "); - break; - case SB: - vty_out (vty, "SB "); - break; - case SE: - vty_out (vty, "SE "); - break; - case TELOPT_ECHO: - vty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE); - break; - case TELOPT_SGA: - vty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE); - break; - case TELOPT_NAWS: - vty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE); - break; - default: - vty_out (vty, "%x ", buf[i]); - break; - } - } - vty_out (vty, "%s", VTY_NEWLINE); - -#endif /* TELNET_OPTION_DEBUG */ - - switch (buf[0]) - { - case SB: - vty->sb_len = 0; - vty->iac_sb_in_progress = 1; - return 0; - break; - case SE: - { - if (!vty->iac_sb_in_progress) - return 0; - - if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) - { - vty->iac_sb_in_progress = 0; - return 0; - } - switch (vty->sb_buf[0]) - { - case TELOPT_NAWS: - if (vty->sb_len != TELNET_NAWS_SB_LEN) - zlog_warn("RFC 1073 violation detected: telnet NAWS option " - "should send %d characters, but we received %lu", - TELNET_NAWS_SB_LEN, (u_long)vty->sb_len); - else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) - zlog_err("Bug detected: sizeof(vty->sb_buf) %lu < %d, " - "too small to handle the telnet NAWS option", - (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); - else - { - vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]); - vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]); -#ifdef TELNET_OPTION_DEBUG - vty_out(vty, "TELNET NAWS window size negotiation completed: " - "width %d, height %d%s", - vty->width, vty->height, VTY_NEWLINE); -#endif - } - break; - } - vty->iac_sb_in_progress = 0; - return 0; - break; - } - default: - break; - } - return 1; -} - -/* Execute current command line. */ -static int -vty_execute (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command line ^C action. + * + * Ignores contents of command line (including not adding to history). + * + * Fall back to ENABLE_NODE if in any one of a number of nodes. + * + * Resets the history pointer. + * + * Returns: command return code + */ +extern enum cmd_return_code +uty_stop_input(struct vty *vty) { - int ret; + vty_io vio = vty->vio ; - ret = CMD_SUCCESS; + VTY_ASSERT_LOCKED() ; switch (vty->node) { - case AUTH_NODE: - case AUTH_ENABLE_NODE: - vty_auth (vty, vty->buf); + case CONFIG_NODE: + case INTERFACE_NODE: + case ZEBRA_NODE: + case RIP_NODE: + case RIPNG_NODE: + case BGP_NODE: + case RMAP_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case KEYCHAIN_KEY_NODE: + case MASC_NODE: + case VTY_NODE: + uty_config_unlock (vty, ENABLE_NODE) ; break; default: - ret = vty_command (vty, vty->buf); - if (vty->type == VTY_TERM) - vty_hist_add (vty); + /* Unknown node, we have to ignore it. */ break; } - /* Clear command line buffer. */ - vty->cp = vty->length = 0; - vty_clear_buf (vty); - - if (vty->status != VTY_CLOSE ) - vty_prompt (vty); - - return ret; -} + /* Set history pointer to the latest one. */ + vio->hp = vio->hindex; -#define CONTROL(X) ((X) - '@') -#define VTY_NORMAL 0 -#define VTY_PRE_ESCAPE 1 -#define VTY_ESCAPE 2 + return CMD_SUCCESS ; +} ; -/* Escape character command map. */ -static void -vty_escape_map (unsigned char c, struct vty *vty) +/*------------------------------------------------------------------------------ + * Command ^Z action. + * + * Ignores contents of command line (including not adding to history). + * + * Fall back to ENABLE_NODE if in any one of a number of nodes. + * + * Returns: command return code + */ +extern enum cmd_return_code +uty_end_config (struct vty *vty) { - switch (c) + VTY_ASSERT_LOCKED() ; + + switch (vty->node) { - case ('A'): - vty_previous_line (vty); - break; - case ('B'): - vty_next_line (vty); - break; - case ('C'): - vty_forward_char (vty); + case VIEW_NODE: + case ENABLE_NODE: + case RESTRICTED_NODE: + /* Nothing to do. */ break; - case ('D'): - vty_backward_char (vty); + case CONFIG_NODE: + case INTERFACE_NODE: + case ZEBRA_NODE: + case RIP_NODE: + case RIPNG_NODE: + case BGP_NODE: + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: + case RMAP_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case KEYCHAIN_KEY_NODE: + case MASC_NODE: + case VTY_NODE: + uty_config_unlock (vty, ENABLE_NODE) ; break; default: + /* Unknown node, we have to ignore it. */ break; } - /* Go back to normal mode. */ - vty->escape = VTY_NORMAL; + return CMD_SUCCESS ; } -/* Quit print out to the buffer. */ -static void -vty_buffer_reset (struct vty *vty) -{ - buffer_reset (vty->obuf); - vty_prompt (vty); - vty_redraw_line (vty); -} - -/* Read data via vty socket. */ -static int -vty_read (struct thread *thread) +/*------------------------------------------------------------------------------ + * Command ^D action -- when nothing else on command line. + * + * Same as "exit" command. + * + * Returns: command return code + */ +extern enum cmd_return_code +uty_down_level (struct vty *vty) { - int i; - int nbytes; - unsigned char buf[VTY_READ_BUFSIZ]; - - int vty_sock = THREAD_FD (thread); - struct vty *vty = THREAD_ARG (thread); - vty->t_read = NULL; - - /* Read raw data from socket */ - if ((nbytes = read (vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) - { - if (nbytes < 0) - { - if (ERRNO_IO_RETRY(errno)) - { - vty_event (VTY_READ, vty_sock, vty); - return 0; - } - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: read error on vty client fd %d, closing: %s", - __func__, vty->fd, safe_strerror(errno)); - } - buffer_reset(vty->obuf); - vty->status = VTY_CLOSE; - } - - for (i = 0; i < nbytes; i++) - { - if (buf[i] == IAC) - { - if (!vty->iac) - { - vty->iac = 1; - continue; - } - else - { - vty->iac = 0; - } - } - - if (vty->iac_sb_in_progress && !vty->iac) - { - if (vty->sb_len < sizeof(vty->sb_buf)) - vty->sb_buf[vty->sb_len] = buf[i]; - vty->sb_len++; - continue; - } + return vty_cmd_exit(vty) ; +} ; - if (vty->iac) - { - /* In case of telnet command */ - int ret = 0; - ret = vty_telnet_option (vty, buf + i, nbytes - i); - vty->iac = 0; - i += ret; - continue; - } - - - if (vty->status == VTY_MORE) - { - switch (buf[i]) - { - case CONTROL('C'): - case 'q': - case 'Q': - vty_buffer_reset (vty); - break; -#if 0 /* More line does not work for "show ip bgp". */ - case '\n': - case '\r': - vty->status = VTY_MORELINE; - break; -#endif - default: - break; - } - continue; - } - - /* Escape character. */ - if (vty->escape == VTY_ESCAPE) - { - vty_escape_map (buf[i], vty); - continue; - } - - /* Pre-escape status. */ - if (vty->escape == VTY_PRE_ESCAPE) - { - switch (buf[i]) - { - case '[': - vty->escape = VTY_ESCAPE; - break; - case 'b': - vty_backward_word (vty); - vty->escape = VTY_NORMAL; - break; - case 'f': - vty_forward_word (vty); - vty->escape = VTY_NORMAL; - break; - case 'd': - vty_forward_kill_word (vty); - vty->escape = VTY_NORMAL; - break; - case CONTROL('H'): - case 0x7f: - vty_backward_kill_word (vty); - vty->escape = VTY_NORMAL; - break; - default: - vty->escape = VTY_NORMAL; - break; - } - continue; - } +/*============================================================================== + * Reading of configuration file + * + * The reading of the configuration file occurs at two times: + * + * 1. early in the morning, before daemonisation, and before any threads + * or nexuses have been set up. + * + * In the qpthreads world, this means that it is running in the main (CLI) + * and only thread and nexus. + * + * 2. at SIGHUP time. + * + * In the qpthreads world, this is running in whatever thread is executing + * commands. + * + * Sets up a VTY_CONFIG_READ in which to execute commands. This has no CLI + * and no socket. All output is buffered in the cmd_obuf. All commands are + * run directly in the thread -- no commands are queued. + */ - switch (buf[i]) - { - case CONTROL('A'): - vty_beginning_of_line (vty); - break; - case CONTROL('B'): - vty_backward_char (vty); - break; - case CONTROL('C'): - vty_stop_input (vty); - break; - case CONTROL('D'): - vty_delete_char (vty); - break; - case CONTROL('E'): - vty_end_of_line (vty); - break; - case CONTROL('F'): - vty_forward_char (vty); - break; - case CONTROL('H'): - case 0x7f: - vty_delete_backward_char (vty); - break; - case CONTROL('K'): - vty_kill_line (vty); - break; - case CONTROL('N'): - vty_next_line (vty); - break; - case CONTROL('P'): - vty_previous_line (vty); - break; - case CONTROL('T'): - vty_transpose_chars (vty); - break; - case CONTROL('U'): - vty_kill_line_from_beginning (vty); - break; - case CONTROL('W'): - vty_backward_kill_word (vty); - break; - case CONTROL('Z'): - vty_end_config (vty); - break; - case '\n': - case '\r': - vty_out (vty, "%s", VTY_NEWLINE); - vty_execute (vty); - break; - case '\t': - vty_complete_command (vty); - break; - case '?': - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - vty_self_insert (vty, buf[i]); - else - vty_describe_command (vty); - break; - case '\033': - if (i + 1 < nbytes && buf[i + 1] == '[') - { - vty->escape = VTY_ESCAPE; - i++; - } - else - vty->escape = VTY_PRE_ESCAPE; - break; - default: - if (buf[i] > 31 && buf[i] < 127) - vty_self_insert (vty, buf[i]); - break; - } - } +static FILE * vty_use_backup_config (char *fullpath) ; +static void vty_read_file (FILE *confp, struct cmd_element* first_cmd, + bool ignore_warnings) ; - /* Check status. */ - if (vty->status == VTY_CLOSE) - vty_close (vty); - else - { - vty_event (VTY_WRITE, vty_sock, vty); - vty_event (VTY_READ, vty_sock, vty); - } - return 0; +/*------------------------------------------------------------------------------ + * Read the given configuration file. + */ +extern void +vty_read_config (char *config_file, + char *config_default) +{ + vty_read_config_first_cmd_special(config_file, config_default, NULL, 1); } -/* Flush buffer to the vty. */ -static int -vty_flush (struct thread *thread) +/*------------------------------------------------------------------------------ + * Read the given configuration file. + * + * The config_file (-f argument) is used if specified. + * + * If config_file is NULL, use the config_default. + * + * If using the config_default, if VTYSH_ENABLED, look for "vtysh" in the name. + * If find "vtysh" and find the "integrate_default" file, then do nothing + * now -- expect vtysh to connect in due course and provide the configuration. + * + * The config_file or config_default may be relative file names. + * + * May have a function to call after the first actual command is processed. + * This mechanism supports the setting of qpthreads-ness by configuration file + * command. + */ +extern void +vty_read_config_first_cmd_special(char *config_file, + char *config_default, + struct cmd_element* first_cmd, + bool ignore_warnings) { - int erase; - buffer_status_t flushrc; - int vty_sock = THREAD_FD (thread); - struct vty *vty = THREAD_ARG (thread); - - vty->t_write = NULL; + char cwd[MAXPATHLEN]; + FILE *confp = NULL; + char *fullpath; + char *tmp = NULL; - /* Tempolary disable read thread. */ - if ((vty->lines == 0) && vty->t_read) + /* Deal with VTYSH_ENABLED magic */ + if (VTYSH_ENABLED && (config_file == NULL)) { - thread_cancel (vty->t_read); - vty->t_read = NULL; - } + int ret; + struct stat conf_stat; - /* Function execution continue. */ - erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); + /* !!!!PLEASE LEAVE!!!! + * This is NEEDED for use with vtysh -b, or else you can get + * a real configuration food fight with a lot garbage in the + * merged configuration file it creates coming from the per + * daemon configuration files. This also allows the daemons + * to start if there default configuration file is not + * present or ignore them, as needed when using vtysh -b to + * configure the daemons at boot - MAG + */ - /* N.B. if width is 0, that means we don't know the window size. */ - if ((vty->lines == 0) || (vty->width == 0)) - flushrc = buffer_flush_available(vty->obuf, vty->fd); - else if (vty->status == VTY_MORELINE) - flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, - 1, erase, 0); - else - flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, - vty->lines >= 0 ? vty->lines : - vty->height, - erase, 0); - switch (flushrc) - { - case BUFFER_ERROR: - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("buffer_flush failed on vty client fd %d, closing", - vty->fd); - buffer_reset(vty->obuf); - vty_close(vty); - return 0; - case BUFFER_EMPTY: - if (vty->status == VTY_CLOSE) - vty_close (vty); - else - { - vty->status = VTY_NORMAL; - if (vty->lines == 0) - vty_event (VTY_READ, vty_sock, vty); - } - break; - case BUFFER_PENDING: - /* There is more data waiting to be written. */ - vty->status = VTY_MORE; - if (vty->lines == 0) - vty_event (VTY_WRITE, vty_sock, vty); - break; - } + /* Stat for vtysh Zebra.conf, if found startup and wait for + * boot configuration + */ - return 0; -} + if ( strstr(config_default, "vtysh") == NULL) + { + ret = stat (integrate_default, &conf_stat); + if (ret >= 0) + return; + } + } ; -/* Create new vty structure. */ -static struct vty * -vty_create (int vty_sock, union sockunion *su) -{ - struct vty *vty; + /* Use default if necessary, and deal with constructing full path */ + if (config_file == NULL) + config_file = config_default ; - /* Allocate new vty structure and set up default values. */ - vty = vty_new (); - vty->fd = vty_sock; - vty->type = VTY_TERM; - vty->address = sockunion_su2str (su); - if (no_password_check) + if (! IS_DIRECTORY_SEP (config_file[0])) { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; - else - vty->node = VIEW_NODE; + getcwd (cwd, sizeof(cwd)) ; + tmp = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (config_file) + 2) ; + sprintf (tmp, "%s/%s", cwd, config_file); + fullpath = tmp; } else - vty->node = AUTH_NODE; - vty->fail = 0; - vty->cp = 0; - vty_clear_buf (vty); - vty->length = 0; - memset (vty->hist, 0, sizeof (vty->hist)); - vty->hp = 0; - vty->hindex = 0; - vector_set_index (vtyvec, vty_sock, vty); - vty->status = VTY_NORMAL; - vty->v_timeout = vty_timeout_val; - if (host.lines >= 0) - vty->lines = host.lines; - else - vty->lines = -1; - vty->iac = 0; - vty->iac_sb_in_progress = 0; - vty->sb_len = 0; - - if (! no_password_check) { - /* Vty is not available if password isn't set. */ - if (host.password == NULL && host.password_encrypt == NULL) - { - vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); - vty->status = VTY_CLOSE; - vty_close (vty); - return NULL; - } - } - - /* Say hello to the world. */ - vty_hello (vty); - if (! no_password_check) - vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); - - /* Setting up terminal. */ - vty_will_echo (vty); - vty_will_suppress_go_ahead (vty); - - vty_dont_linemode (vty); - vty_do_window_size (vty); - /* vty_dont_lflow_ahead (vty); */ - - vty_prompt (vty); - - /* Add read/write thread. */ - vty_event (VTY_WRITE, vty_sock, vty); - vty_event (VTY_READ, vty_sock, vty); - - return vty; -} - -/* Accept connection from the network. */ -static int -vty_accept (struct thread *thread) -{ - int vty_sock; - struct vty *vty; - union sockunion su; - int ret; - unsigned int on; - int accept_sock; - struct prefix *p = NULL; - struct access_list *acl = NULL; - char *bufp; - - accept_sock = THREAD_FD (thread); + tmp = NULL ; + fullpath = config_file; + } ; - /* We continue hearing vty socket. */ - vty_event (VTY_SERV, accept_sock, NULL); + /* try to open the configuration file */ + confp = fopen (fullpath, "r"); - memset (&su, 0, sizeof (union sockunion)); - - /* We can handle IPv4 or IPv6 socket. */ - vty_sock = sockunion_accept (accept_sock, &su); - if (vty_sock < 0) + if (confp == NULL) { - zlog_warn ("can't accept vty socket : %s", safe_strerror (errno)); - return -1; - } - set_nonblocking(vty_sock); + fprintf (stderr, "%s: failed to open configuration file %s: %s\n", + __func__, fullpath, errtostr(errno, 0).str); - p = sockunion2hostprefix (&su); - - /* VTY's accesslist apply. */ - if (p->family == AF_INET && vty_accesslist_name) - { - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - { - char *buf; - zlog (NULL, LOG_INFO, "Vty connection refused from %s", - (buf = sockunion_su2str (&su))); - free (buf); - close (vty_sock); - - /* continue accepting connections */ - vty_event (VTY_SERV, accept_sock, NULL); - - prefix_free (p); - - return 0; - } - } + confp = vty_use_backup_config (fullpath); + if (confp) + fprintf (stderr, "WARNING: using backup configuration file!\n"); + else + { + fprintf (stderr, "can't open backup configuration file [%s%s]\n", + fullpath, CONF_BACKUP_EXT); + exit(1); + } + } ; -#ifdef HAVE_IPV6 - /* VTY's ipv6 accesslist apply. */ - if (p->family == AF_INET6 && vty_ipv6_accesslist_name) - { - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - { - char *buf; - zlog (NULL, LOG_INFO, "Vty connection refused from %s", - (buf = sockunion_su2str (&su))); - free (buf); - close (vty_sock); - - /* continue accepting connections */ - vty_event (VTY_SERV, accept_sock, NULL); - - prefix_free (p); - - return 0; - } - } -#endif /* HAVE_IPV6 */ - - prefix_free (p); +#ifdef QDEBUG + fprintf(stderr, "Reading config file: %s\n", fullpath); +#endif - on = 1; - ret = setsockopt (vty_sock, IPPROTO_TCP, TCP_NODELAY, - (char *) &on, sizeof (on)); - if (ret < 0) - zlog (NULL, LOG_INFO, "can't set sockopt to vty_sock : %s", - safe_strerror (errno)); + vty_read_file (confp, first_cmd, ignore_warnings); + fclose (confp); - zlog (NULL, LOG_INFO, "Vty connection from %s", - (bufp = sockunion_su2str (&su))); - if (bufp) - XFREE (MTYPE_TMP, bufp); + host_config_set (fullpath); - vty = vty_create (vty_sock, &su); +#ifdef QDEBUG + fprintf(stderr, "Finished reading config file\n"); +#endif - return 0; + if (tmp) + XFREE (MTYPE_TMP, tmp); } -#if defined(HAVE_IPV6) && !defined(NRL) -static void -vty_serv_sock_addrinfo (const char *hostname, unsigned short port) +/*------------------------------------------------------------------------------ + * Try to use a backup configuration file. + * + * Having failed to open the file "<fullpath>", if there is a file called + * "<fullpath>.sav" that can be opened for reading, then: + * + * - make a copy of that file + * - call it "<fullpath>" + * - return an open FILE + * + * Returns: NULL => no "<fullpath>.sav", or faild doing any of the above + * otherwise, returns FILE* for open file. + */ +static FILE * +vty_use_backup_config (char *fullpath) { - int ret; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - int sock; - char port_str[BUFSIZ]; + char *tmp_path ; + struct stat buf; + int ret, tmp, sav; + int c; + char buffer[4096] ; - memset (&req, 0, sizeof (struct addrinfo)); - req.ai_flags = AI_PASSIVE; - req.ai_family = AF_UNSPEC; - req.ai_socktype = SOCK_STREAM; - sprintf (port_str, "%d", port); - port_str[sizeof (port_str) - 1] = '\0'; + enum { xl = 32 } ; + tmp_path = malloc(strlen(fullpath) + xl) ; - ret = getaddrinfo (hostname, port_str, &req, &ainfo); + /* construct the name "<fullname>.sav", and try to open it. */ + confirm(xl > sizeof(CONF_BACKUP_EXT)) ; + sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ; - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (ret)); - exit (1); - } - - ainfo_save = ainfo; + sav = -1 ; + if (stat (tmp_path, &buf) != -1) + sav = open (tmp_path, O_RDONLY); - do + if (sav < 0) { - if (ainfo->ai_family != AF_INET -#ifdef HAVE_IPV6 - && ainfo->ai_family != AF_INET6 -#endif /* HAVE_IPV6 */ - ) - continue; - - sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); - if (sock < 0) - continue; - - sockopt_reuseaddr (sock); - sockopt_reuseport (sock); - - ret = bind (sock, ainfo->ai_addr, ainfo->ai_addrlen); - if (ret < 0) - { - close (sock); /* Avoid sd leak. */ - continue; - } - - ret = listen (sock, 3); - if (ret < 0) - { - close (sock); /* Avoid sd leak. */ - continue; - } - - vty_event (VTY_SERV, sock, NULL); - } - while ((ainfo = ainfo->ai_next) != NULL); - - freeaddrinfo (ainfo_save); -} -#endif /* HAVE_IPV6 && ! NRL */ + free (tmp_path); + return NULL; + } ; -/* Make vty server socket. */ -static void -vty_serv_sock_family (const char* addr, unsigned short port, int family) -{ - int ret; - union sockunion su; - int accept_sock; - void* naddr=NULL; + /* construct a temporary file and copy "<fullpath.sav>" to it. */ + confirm(xl > sizeof(".XXXXXX")) + sprintf (tmp_path, "%s%s", fullpath, ".XXXXXX") ; - memset (&su, 0, sizeof (union sockunion)); - su.sa.sa_family = family; - if(addr) - switch(family) - { - case AF_INET: - naddr=&su.sin.sin_addr; -#ifdef HAVE_IPV6 - case AF_INET6: - naddr=&su.sin6.sin6_addr; -#endif - } - - if(naddr) - switch(inet_pton(family,addr,naddr)) + /* Open file to configuration write. */ + tmp = mkstemp (tmp_path); + if (tmp < 0) { - case -1: - zlog_err("bad address %s",addr); - naddr=NULL; - break; - case 0: - zlog_err("error translating address %s: %s",addr,safe_strerror(errno)); - naddr=NULL; + free (tmp_path); + close(sav); + return NULL; } - /* Make new socket. */ - accept_sock = sockunion_stream_socket (&su); - if (accept_sock < 0) - return; + while((c = read (sav, buffer, sizeof(buffer))) > 0) + write (tmp, buffer, c); - /* This is server, so reuse address. */ - sockopt_reuseaddr (accept_sock); - sockopt_reuseport (accept_sock); + close (sav); + close (tmp); - /* Bind socket to universal address and given port. */ - ret = sockunion_bind (accept_sock, &su, port, naddr); - if (ret < 0) + /* Make sure that have the required file status */ + if (chmod(tmp_path, CONFIGFILE_MASK) != 0) { - zlog_warn("can't bind socket"); - close (accept_sock); /* Avoid sd leak. */ - return; + unlink (tmp_path); + free (tmp_path); + return NULL; } - /* Listen socket under queue 3. */ - ret = listen (accept_sock, 3); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't listen socket"); - close (accept_sock); /* Avoid sd leak. */ - return; - } + /* Make <fullpath> be a name for the new file just created. */ + ret = link (tmp_path, fullpath) ; - /* Add vty server event. */ - vty_event (VTY_SERV, accept_sock, NULL); -} + /* Discard the temporary, now */ + unlink (tmp_path) ; + free (tmp_path) ; -#ifdef VTYSH -/* For sockaddr_un. */ -#include <sys/un.h> + /* If link was successful, try to open -- otherwise, failed. */ + return (ret == 0) ? fopen (fullpath, "r") : NULL ; +} ; -/* VTY shell UNIX domain socket. */ +/*------------------------------------------------------------------------------ + * Read the given configuration file. + * + * May have a function to call after the first actual command is processed. + * This mechanism supports the setting of qpthreads-ness by configuration file + * command. + * + * In the qpthreads world: + * + * * when the configuration is first read, this runs in the CLI thread + * (the main and only thread). + * + * * when the configuration is reread, this runs in the command processor + * thread. + * + * All consoles are shut down, so there can be no interference from that + * quarter. + * + * so all commands are executed directly. + */ static void -vty_serv_un (const char *path) -{ - int ret; - int sock, len; - struct sockaddr_un serv; - mode_t old_mask; - struct zprivs_ids_t ids; - - /* First of all, unlink existing socket */ - unlink (path); - - /* Set umask */ - old_mask = umask (0007); - - /* Make UNIX domain socket. */ - sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - { - zlog_err("Cannot create unix stream socket: %s", safe_strerror(errno)); - return; - } - - /* Make server socket. */ - memset (&serv, 0, sizeof (struct sockaddr_un)); - serv.sun_family = AF_UNIX; - strncpy (serv.sun_path, path, strlen (path)); -#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN - len = serv.sun_len = SUN_LEN(&serv); -#else - len = sizeof (serv.sun_family) + strlen (serv.sun_path); -#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ - - ret = bind (sock, (struct sockaddr *) &serv, len); - if (ret < 0) - { - zlog_err("Cannot bind path %s: %s", path, safe_strerror(errno)); - close (sock); /* Avoid sd leak. */ - return; - } - - ret = listen (sock, 5); - if (ret < 0) - { - zlog_err("listen(fd %d) failed: %s", sock, safe_strerror(errno)); - close (sock); /* Avoid sd leak. */ - return; - } - - umask (old_mask); - - zprivs_get_ids(&ids); - - if (ids.gid_vty > 0) - { - /* set group of socket */ - if ( chown (path, -1, ids.gid_vty) ) - { - zlog_err ("vty_serv_un: could chown socket, %s", - safe_strerror (errno) ); - } - } - - vty_event (VTYSH_SERV, sock, NULL); -} - -/* #define VTYSH_DEBUG 1 */ - -static int -vtysh_accept (struct thread *thread) +vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings) { - int accept_sock; - int sock; - int client_len; - struct sockaddr_un client; - struct vty *vty; - - accept_sock = THREAD_FD (thread); - - vty_event (VTYSH_SERV, accept_sock, NULL); - - memset (&client, 0, sizeof (struct sockaddr_un)); - client_len = sizeof (struct sockaddr_un); - - sock = accept (accept_sock, (struct sockaddr *) &client, - (socklen_t *) &client_len); - - if (sock < 0) - { - zlog_warn ("can't accept vty socket : %s", safe_strerror (errno)); - return -1; - } - - if (set_nonblocking(sock) < 0) - { - zlog_warn ("vtysh_accept: could not set vty socket %d to non-blocking," - " %s, closing", sock, safe_strerror (errno)); - close (sock); - return -1; - } - -#ifdef VTYSH_DEBUG - printf ("VTY shell accept\n"); -#endif /* VTYSH_DEBUG */ - - vty = vty_new (); - vty->fd = sock; - vty->type = VTY_SHELL_SERV; - vty->node = VIEW_NODE; + enum cmd_return_code ret ; + struct vty *vty ; + qtime_t taking ; - vty_event (VTYSH_READ, sock, vty); - - return 0; -} - -static int -vtysh_flush(struct vty *vty) -{ - switch (buffer_flush_available(vty->obuf, vty->fd)) - { - case BUFFER_PENDING: - vty_event(VTYSH_WRITE, vty->fd, vty); - break; - case BUFFER_ERROR: - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: write error to fd %d, closing", __func__, vty->fd); - buffer_reset(vty->obuf); - vty_close(vty); - return -1; - break; - case BUFFER_EMPTY: - break; - } - return 0; -} - -static int -vtysh_read (struct thread *thread) -{ - int ret; - int sock; - int nbytes; - struct vty *vty; - unsigned char buf[VTY_READ_BUFSIZ]; - unsigned char *p; - u_char header[4] = {0, 0, 0, 0}; - - sock = THREAD_FD (thread); - vty = THREAD_ARG (thread); - vty->t_read = NULL; - - if ((nbytes = read (sock, buf, VTY_READ_BUFSIZ)) <= 0) - { - if (nbytes < 0) - { - if (ERRNO_IO_RETRY(errno)) - { - vty_event (VTYSH_READ, sock, vty); - return 0; - } - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: read failed on vtysh client fd %d, closing: %s", - __func__, sock, safe_strerror(errno)); - } - buffer_reset(vty->obuf); - vty_close (vty); -#ifdef VTYSH_DEBUG - printf ("close vtysh\n"); -#endif /* VTYSH_DEBUG */ - return 0; - } - -#ifdef VTYSH_DEBUG - printf ("line: %.*s\n", nbytes, buf); -#endif /* VTYSH_DEBUG */ + /* Set up configuration file reader VTY -- which buffers all output */ + vty = vty_open(VTY_CONFIG_READ); + vty->node = CONFIG_NODE; - for (p = buf; p < buf+nbytes; p++) - { - vty_ensure(vty, vty->length+1); - vty->buf[vty->length++] = *p; - if (*p == '\0') - { - /* Pass this line to parser. */ - ret = vty_execute (vty); - /* Note that vty_execute clears the command buffer and resets - vty->length to 0. */ - - /* Return result. */ -#ifdef VTYSH_DEBUG - printf ("result: %d\n", ret); - printf ("vtysh node: %d\n", vty->node); -#endif /* VTYSH_DEBUG */ - - header[3] = ret; - buffer_put(vty->obuf, header, 4); - - if (!vty->t_write && (vtysh_flush(vty) < 0)) - /* Try to flush results; exit if a write error occurs. */ - return 0; - } - } + /* Make sure we have a suitable buffer, and set vty->buf to point at + * it -- same like other command execution. + */ + qs_need(&vty->vio->clx, VTY_BUFSIZ) ; + vty->buf = qs_chars(&vty->vio->clx) ; - vty_event (VTYSH_READ, sock, vty); + taking = qt_get_monotonic() ; - return 0; -} + /* Execute configuration file */ + ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx, + ignore_warnings) ; -static int -vtysh_write (struct thread *thread) -{ - struct vty *vty = THREAD_ARG (thread); + taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ; - vty->t_write = NULL; - vtysh_flush(vty); - return 0; -} + zlog_info("Finished reading configuration in %d.%dsecs%s", + (int)(taking / 1000), (int)(taking % 1000), + (ret == CMD_SUCCESS) ? "." : " -- FAILED") ; -#endif /* VTYSH */ + VTY_LOCK() ; -/* Determine address family to bind. */ -void -vty_serv_sock (const char *addr, unsigned short port, const char *path) -{ - /* If port is set to 0, do not listen on TCP/IP at all! */ - if (port) + if (ret != CMD_SUCCESS) { + fprintf (stderr, "%% while processing line %u of the configuration:\n" + "%s", vty->lineno, vty->buf) ; -#ifdef HAVE_IPV6 -#ifdef NRL - vty_serv_sock_family (addr, port, AF_INET); - vty_serv_sock_family (addr, port, AF_INET6); -#else /* ! NRL */ - vty_serv_sock_addrinfo (addr, port); -#endif /* NRL*/ -#else /* ! HAVE_IPV6 */ - vty_serv_sock_family (addr,port, AF_INET); -#endif /* HAVE_IPV6 */ - } - -#ifdef VTYSH - vty_serv_un (path); -#endif /* VTYSH */ -} - -/* Close vty interface. Warning: call this only from functions that - will be careful not to access the vty afterwards (since it has - now been freed). This is safest from top-level functions (called - directly by the thread dispatcher). */ -void -vty_close (struct vty *vty) -{ - int i; - - /* Cancel threads.*/ - if (vty->t_read) - thread_cancel (vty->t_read); - if (vty->t_write) - thread_cancel (vty->t_write); - if (vty->t_timeout) - thread_cancel (vty->t_timeout); - - /* Flush buffer. */ - buffer_flush_all (vty->obuf, vty->fd); - - /* Free input buffer. */ - buffer_free (vty->obuf); - - /* Free command history. */ - for (i = 0; i < VTY_MAXHIST; i++) - if (vty->hist[i]) - XFREE (MTYPE_VTY_HIST, vty->hist[i]); - - /* Unset vector. */ - vector_unset (vtyvec, vty->fd); - - /* Close socket. */ - if (vty->fd > 0) - close (vty->fd); - - if (vty->address) - XFREE (MTYPE_TMP, vty->address); - if (vty->buf) - XFREE (MTYPE_VTY, vty->buf); - - /* Check configure. */ - vty_config_unlock (vty); - - /* OK free vty. */ - XFREE (MTYPE_VTY, vty); -} - -/* When time out occur output message then close connection. */ -static int -vty_timeout (struct thread *thread) -{ - struct vty *vty; + switch (ret) + { + case CMD_WARNING: + fprintf (stderr, "%% Warning...\n"); + break; - vty = THREAD_ARG (thread); - vty->t_timeout = NULL; - vty->v_timeout = 0; + case CMD_ERROR: + fprintf (stderr, "%% Error...\n"); + break; - /* Clear buffer*/ - buffer_reset (vty->obuf); - vty_out (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + case CMD_ERR_AMBIGUOUS: + fprintf (stderr, "%% Ambiguous command.\n"); + break; - /* Close connection. */ - vty->status = VTY_CLOSE; - vty_close (vty); + case CMD_ERR_NO_MATCH: + fprintf (stderr, "%% There is no such command.\n"); + break; - return 0; -} + case CMD_ERR_INCOMPLETE: + fprintf (stderr, "%% Incomplete command.\n"); + break; -/* Read up configuration file from file_name. */ -static void -vty_read_file (FILE *confp) -{ - int ret; - struct vty *vty; + default: + fprintf(stderr, "%% (unknown cause %d)\n", ret) ; + break ; + } ; - vty = vty_new (); - vty->fd = 0; /* stdout */ - vty->type = VTY_TERM; - vty->node = CONFIG_NODE; - - /* Execute configuration file */ - ret = config_from_file (vty, confp); + uty_out_fflush(vty->vio, stderr) ; /* flush command output buffer */ - if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) ) - { - switch (ret) - { - case CMD_ERR_AMBIGUOUS: - fprintf (stderr, "Ambiguous command.\n"); - break; - case CMD_ERR_NO_MATCH: - fprintf (stderr, "There is no such command.\n"); - break; - } - fprintf (stderr, "Error occured during reading below line.\n%s\n", - vty->buf); - vty_close (vty); exit (1); - } + } ; - vty_close (vty); -} + uty_close(vty->vio) ; + VTY_UNLOCK() ; +} ; -static FILE * -vty_use_backup_config (char *fullpath) -{ - char *fullpath_sav, *fullpath_tmp; - FILE *ret = NULL; - struct stat buf; - int tmp, sav; - int c; - char buffer[512]; - - fullpath_sav = malloc (strlen (fullpath) + strlen (CONF_BACKUP_EXT) + 1); - strcpy (fullpath_sav, fullpath); - strcat (fullpath_sav, CONF_BACKUP_EXT); - if (stat (fullpath_sav, &buf) == -1) - { - free (fullpath_sav); - return NULL; - } - - fullpath_tmp = malloc (strlen (fullpath) + 8); - sprintf (fullpath_tmp, "%s.XXXXXX", fullpath); - - /* Open file to configuration write. */ - tmp = mkstemp (fullpath_tmp); - if (tmp < 0) - { - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - sav = open (fullpath_sav, O_RDONLY); - if (sav < 0) - { - unlink (fullpath_tmp); - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - while((c = read (sav, buffer, 512)) > 0) - write (tmp, buffer, c); - - close (sav); - close (tmp); - - if (chmod(fullpath_tmp, CONFIGFILE_MASK) != 0) - { - unlink (fullpath_tmp); - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - if (link (fullpath_tmp, fullpath) == 0) - ret = fopen (fullpath, "r"); - - unlink (fullpath_tmp); - - free (fullpath_sav); - free (fullpath_tmp); - return ret; -} +/*============================================================================== + * Configuration node/state handling + * + * At most one VTY may hold the configuration symbol of power at any time. + */ -/* Read up configuration file from file_name. */ -void -vty_read_config (char *config_file, - char *config_default_dir) +/*------------------------------------------------------------------------------ + * Attempt to gain the configuration symbol of power + * + * If succeeds, set the given node. + * + * Returns: true <=> now own the symbol of power. + */ +extern bool +vty_config_lock (struct vty *vty, enum node_type node) { - char cwd[MAXPATHLEN]; - FILE *confp = NULL; - char *fullpath; - char *tmp = NULL; + bool result; - /* If -f flag specified. */ - if (config_file != NULL) - { - if (! IS_DIRECTORY_SEP (config_file[0])) - { - getcwd (cwd, MAXPATHLEN); - tmp = XMALLOC (MTYPE_TMP, - strlen (cwd) + strlen (config_file) + 2); - sprintf (tmp, "%s/%s", cwd, config_file); - fullpath = tmp; - } - else - fullpath = config_file; + VTY_LOCK() ; - confp = fopen (fullpath, "r"); - - if (confp == NULL) - { - fprintf (stderr, "%s: failed to open configuration file %s: %s\n", - __func__, fullpath, safe_strerror (errno)); - - confp = vty_use_backup_config (fullpath); - if (confp) - fprintf (stderr, "WARNING: using backup configuration file!\n"); - else - { - fprintf (stderr, "can't open configuration file [%s]\n", - config_file); - exit(1); - } - } - } - else + if (vty_config == 0) { -#ifdef VTYSH - int ret; - struct stat conf_stat; + vty->vio->config = 1 ; + vty_config = 1 ; + } ; - /* !!!!PLEASE LEAVE!!!! - * This is NEEDED for use with vtysh -b, or else you can get - * a real configuration food fight with a lot garbage in the - * merged configuration file it creates coming from the per - * daemon configuration files. This also allows the daemons - * to start if there default configuration file is not - * present or ignore them, as needed when using vtysh -b to - * configure the daemons at boot - MAG - */ - - /* Stat for vtysh Zebra.conf, if found startup and wait for - * boot configuration - */ - - if ( strstr(config_default_dir, "vtysh") == NULL) - { - ret = stat (integrate_default, &conf_stat); - if (ret >= 0) - return; - } -#endif /* VTYSH */ - - confp = fopen (config_default_dir, "r"); - if (confp == NULL) - { - fprintf (stderr, "%s: failed to open configuration file %s: %s\n", - __func__, config_default_dir, safe_strerror (errno)); - - confp = vty_use_backup_config (config_default_dir); - if (confp) - { - fprintf (stderr, "WARNING: using backup configuration file!\n"); - fullpath = config_default_dir; - } - else - { - fprintf (stderr, "can't open configuration file [%s]\n", - config_default_dir); - exit (1); - } - } - else - fullpath = config_default_dir; - } - - vty_read_file (confp); - - fclose (confp); - - host_config_set (fullpath); - - if (tmp) - XFREE (MTYPE_TMP, fullpath); -} - -/* Small utility function which output log to the VTY. */ -void -vty_log (const char *level, const char *proto_str, - const char *format, struct timestamp_control *ctl, va_list va) -{ - unsigned int i; - struct vty *vty; - - if (!vtyvec) - return; - - for (i = 0; i < vector_active (vtyvec); i++) - if ((vty = vector_slot (vtyvec, i)) != NULL) - if (vty->monitor) - { - va_list ac; - va_copy(ac, va); - vty_log_out (vty, level, proto_str, format, ctl, ac); - va_end(ac); - } -} + result = vty->vio->config; -/* Async-signal-safe version of vty_log for fixed strings. */ -void -vty_log_fixed (const char *buf, size_t len) -{ - unsigned int i; - struct iovec iov[2]; + if (result) + vty->node = node ; - /* vty may not have been initialised */ - if (!vtyvec) - return; - - iov[0].iov_base = (void *)buf; - iov[0].iov_len = len; - iov[1].iov_base = (void *)"\r\n"; - iov[1].iov_len = 2; + VTY_UNLOCK() ; - for (i = 0; i < vector_active (vtyvec); i++) - { - struct vty *vty; - if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor) - /* N.B. We don't care about the return code, since process is - most likely just about to die anyway. */ - writev(vty->fd, iov, 2); - } + return result; } -int -vty_config_lock (struct vty *vty) +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +vty_config_unlock (struct vty *vty, enum node_type node) { - if (vty_config == 0) - { - vty->config = 1; - vty_config = 1; - } - return vty->config; + VTY_LOCK() ; + uty_config_unlock(vty, node); + VTY_UNLOCK() ; } -int -vty_config_unlock (struct vty *vty) +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +uty_config_unlock (struct vty *vty, enum node_type node) { - if (vty_config == 1 && vty->config == 1) + VTY_ASSERT_LOCKED() ; + if ((vty_config == 1) && (vty->vio->config == 1)) { - vty->config = 0; - vty_config = 0; + vty->vio->config = 0; + vty_config = 0; } - return vty->config; -} - -/* Master of the threads. */ -static struct thread_master *master; -static void -vty_event (enum event event, int sock, struct vty *vty) -{ - struct thread *vty_serv_thread; - - switch (event) - { - case VTY_SERV: - vty_serv_thread = thread_add_read (master, vty_accept, vty, sock); - vector_set_index (Vvty_serv_thread, sock, vty_serv_thread); - break; -#ifdef VTYSH - case VTYSH_SERV: - thread_add_read (master, vtysh_accept, vty, sock); - break; - case VTYSH_READ: - vty->t_read = thread_add_read (master, vtysh_read, vty, sock); - break; - case VTYSH_WRITE: - vty->t_write = thread_add_write (master, vtysh_write, vty, sock); - break; -#endif /* VTYSH */ - case VTY_READ: - vty->t_read = thread_add_read (master, vty_read, vty, sock); + assert(node <= MAX_NON_CONFIG_NODE) ; + vty->node = node ; +} ; - /* Time out treatment. */ - if (vty->v_timeout) - { - if (vty->t_timeout) - thread_cancel (vty->t_timeout); - vty->t_timeout = - thread_add_timer (master, vty_timeout, vty, vty->v_timeout); - } - break; - case VTY_WRITE: - if (! vty->t_write) - vty->t_write = thread_add_write (master, vty_flush, vty, sock); - break; - case VTY_TIMEOUT_RESET: - if (vty->t_timeout) - { - thread_cancel (vty->t_timeout); - vty->t_timeout = NULL; - } - if (vty->v_timeout) - { - vty->t_timeout = - thread_add_timer (master, vty_timeout, vty, vty->v_timeout); - } - break; - } -} - -DEFUN (config_who, +/*============================================================================== + * Commands + * + */ +DEFUN_CALL (config_who, config_who_cmd, "who", "Display who is on vty\n") { - unsigned int i; - struct vty *v; + unsigned int i = 0; + vty_io vio ; - for (i = 0; i < vector_active (vtyvec); i++) - if ((v = vector_slot (vtyvec, i)) != NULL) - vty_out (vty, "%svty[%d] connected from %s.%s", - v->config ? "*" : " ", - i, v->address, VTY_NEWLINE); + VTY_LOCK() ; + + vio = vio_list_base ; + while (vio != NULL) /* TODO: show only VTY_TERM ??? */ + { + uty_out (vty, "%svty[%d] connected from %s.%s", + vio->config ? "*" : " ", + i, uty_get_name(vio), VTY_NEWLINE); + vio = sdl_next(vio, vio_list) ; + } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } /* Move to vty configuration mode. */ -DEFUN (line_vty, +DEFUN_CALL (line_vty, line_vty_cmd, "line vty", "Configure a terminal line\n" "Virtual terminal\n") { + VTY_LOCK() ; vty->node = VTY_NODE; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2584,6 +1471,8 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { unsigned long timeout = 0; + VTY_LOCK() ; + /* min_str and sec_str are already checked by parser. So it must be all digit string. */ if (min_str) @@ -2595,14 +1484,15 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) timeout += strtol (sec_str, NULL, 10); vty_timeout_val = timeout; - vty->v_timeout = timeout; - vty_event (VTY_TIMEOUT_RESET, 0, vty); + if (vty_term(vty) || vty_shell_serv(vty)) + uty_sock_set_timer(&vty->vio->sock, timeout) ; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (exec_timeout_min, +DEFUN_CALL (exec_timeout_min, exec_timeout_min_cmd, "exec-timeout <0-35791>", "Set timeout value\n" @@ -2611,7 +1501,7 @@ DEFUN (exec_timeout_min, return exec_timeout (vty, argv[0], NULL); } -DEFUN (exec_timeout_sec, +DEFUN_CALL (exec_timeout_sec, exec_timeout_sec_cmd, "exec-timeout <0-35791> <0-2147483>", "Set the EXEC timeout\n" @@ -2621,7 +1511,7 @@ DEFUN (exec_timeout_sec, return exec_timeout (vty, argv[0], argv[1]); } -DEFUN (no_exec_timeout, +DEFUN_CALL (no_exec_timeout, no_exec_timeout_cmd, "no exec-timeout", NO_STR @@ -2631,61 +1521,71 @@ DEFUN (no_exec_timeout, } /* Set vty access class. */ -DEFUN (vty_access_class, +DEFUN_CALL (vty_access_class, vty_access_class_cmd, "access-class WORD", "Filter connections based on an IP access list\n" "IP access list\n") { + VTY_LOCK() ; + if (vty_accesslist_name) XFREE(MTYPE_VTY, vty_accesslist_name); vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + VTY_UNLOCK() ; return CMD_SUCCESS; } /* Clear vty access class. */ -DEFUN (no_vty_access_class, +DEFUN_CALL (no_vty_access_class, no_vty_access_class_cmd, "no access-class [WORD]", NO_STR "Filter connections based on an IP access list\n" "IP access list\n") { + int result = CMD_SUCCESS; + + VTY_LOCK() ; if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0]))) { - vty_out (vty, "Access-class is not currently applied to vty%s", + uty_out (vty, "Access-class is not currently applied to vty%s", VTY_NEWLINE); - return CMD_WARNING; + result = CMD_WARNING; + } + else + { + XFREE(MTYPE_VTY, vty_accesslist_name); + vty_accesslist_name = NULL; } - XFREE(MTYPE_VTY, vty_accesslist_name); - - vty_accesslist_name = NULL; - - return CMD_SUCCESS; + VTY_UNLOCK() ; + return result; } #ifdef HAVE_IPV6 /* Set vty access class. */ -DEFUN (vty_ipv6_access_class, +DEFUN_CALL (vty_ipv6_access_class, vty_ipv6_access_class_cmd, "ipv6 access-class WORD", IPV6_STR "Filter connections based on an IP access list\n" "IPv6 access list\n") { + VTY_LOCK() ; if (vty_ipv6_accesslist_name) XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + VTY_UNLOCK() ; return CMD_SUCCESS; } /* Clear vty access class. */ -DEFUN (no_vty_ipv6_access_class, +DEFUN_CALL (no_vty_ipv6_access_class, no_vty_ipv6_access_class_cmd, "no ipv6 access-class [WORD]", NO_STR @@ -2693,112 +1593,135 @@ DEFUN (no_vty_ipv6_access_class, "Filter connections based on an IP access list\n" "IPv6 access list\n") { + int result = CMD_SUCCESS; + + VTY_LOCK() ; + if (! vty_ipv6_accesslist_name || (argc && strcmp(vty_ipv6_accesslist_name, argv[0]))) { - vty_out (vty, "IPv6 access-class is not currently applied to vty%s", + uty_out (vty, "IPv6 access-class is not currently applied to vty%s", VTY_NEWLINE); - return CMD_WARNING; + result = CMD_WARNING; } + else + { + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - - vty_ipv6_accesslist_name = NULL; + vty_ipv6_accesslist_name = NULL; + } + VTY_UNLOCK() ; return CMD_SUCCESS; } #endif /* HAVE_IPV6 */ /* vty login. */ -DEFUN (vty_login, +DEFUN_CALL (vty_login, vty_login_cmd, "login", "Enable password checking\n") { + VTY_LOCK() ; no_password_check = 0; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (no_vty_login, +DEFUN_CALL (no_vty_login, no_vty_login_cmd, "no login", NO_STR "Enable password checking\n") { + VTY_LOCK() ; no_password_check = 1; + VTY_UNLOCK() ; return CMD_SUCCESS; } /* initial mode. */ -DEFUN (vty_restricted_mode, +DEFUN_CALL (vty_restricted_mode, vty_restricted_mode_cmd, "anonymous restricted", "Restrict view commands available in anonymous, unauthenticated vty\n") { + VTY_LOCK() ; restricted_mode = 1; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (vty_no_restricted_mode, +DEFUN_CALL (vty_no_restricted_mode, vty_no_restricted_mode_cmd, "no anonymous restricted", NO_STR "Enable password checking\n") { + VTY_LOCK() ; restricted_mode = 0; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (service_advanced_vty, +DEFUN_CALL (service_advanced_vty, service_advanced_vty_cmd, "service advanced-vty", "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { + VTY_LOCK() ; host.advanced = 1; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (no_service_advanced_vty, +DEFUN_CALL (no_service_advanced_vty, no_service_advanced_vty_cmd, "no service advanced-vty", NO_STR "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { + VTY_LOCK() ; host.advanced = 0; + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (terminal_monitor, +DEFUN_CALL (terminal_monitor, terminal_monitor_cmd, "terminal monitor", "Set terminal line parameters\n" "Copy debug output to the current terminal line\n") { - vty->monitor = 1; + VTY_LOCK() ; + uty_set_monitor(vty->vio, true); + VTY_UNLOCK() ; return CMD_SUCCESS; } -DEFUN (terminal_no_monitor, +DEFUN_CALL (terminal_no_monitor, terminal_no_monitor_cmd, "terminal no monitor", "Set terminal line parameters\n" NO_STR "Copy debug output to the current terminal line\n") { - vty->monitor = 0; + VTY_LOCK() ; + uty_set_monitor(vty->vio, false); + VTY_UNLOCK() ; return CMD_SUCCESS; } -ALIAS (terminal_no_monitor, +ALIAS_CALL (terminal_no_monitor, no_terminal_monitor_cmd, "no terminal monitor", NO_STR "Set terminal line parameters\n" "Copy debug output to the current terminal line\n") -DEFUN (show_history, +DEFUN_CALL (show_history, show_history_cmd, "show history", SHOW_STR @@ -2806,24 +1729,34 @@ DEFUN (show_history, { int index; - for (index = vty->hindex + 1; index != vty->hindex;) + VTY_LOCK() ; + + for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) { + qstring line ; + if (index == VTY_MAXHIST) { index = 0; continue; } - if (vty->hist[index] != NULL) - vty_out (vty, " %s%s", vty->hist[index], VTY_NEWLINE); + line = vector_get_item(&vty->vio->hist, index) ; + if (line != NULL) + uty_out (vty, " %s%s", line->char_body, VTY_NEWLINE); index++; } + VTY_UNLOCK() ; return CMD_SUCCESS; } -/* Display current configuration. */ +/*============================================================================== + * Output the current configuration + * + * Returns: CMD_SUCCESS + */ static int vty_config_write (struct vty *vty) { @@ -2839,14 +1772,14 @@ vty_config_write (struct vty *vty) /* exec-timeout */ if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) - vty_out (vty, " exec-timeout %ld %ld%s", + vty_out (vty, " exec-timeout %ld %ld%s", vty_timeout_val / 60, vty_timeout_val % 60, VTY_NEWLINE); /* login */ if (no_password_check) vty_out (vty, " no login%s", VTY_NEWLINE); - + if (restricted_mode != restricted_mode_default) { if (restricted_mode_default) @@ -2854,58 +1787,22 @@ vty_config_write (struct vty *vty) else vty_out (vty, " anonymous restricted%s", VTY_NEWLINE); } - + vty_out (vty, "!%s", VTY_NEWLINE); return CMD_SUCCESS; } -struct cmd_node vty_node = -{ - VTY_NODE, - "%s(config-line)# ", - 1, -}; - -/* Reset all VTY status. */ -void -vty_reset () -{ - unsigned int i; - struct vty *vty; - struct thread *vty_serv_thread; - - for (i = 0; i < vector_active (vtyvec); i++) - if ((vty = vector_slot (vtyvec, i)) != NULL) - { - buffer_reset (vty->obuf); - vty->status = VTY_CLOSE; - vty_close (vty); - } - - for (i = 0; i < vector_active (Vvty_serv_thread); i++) - if ((vty_serv_thread = vector_slot (Vvty_serv_thread, i)) != NULL) - { - thread_cancel (vty_serv_thread); - vector_slot (Vvty_serv_thread, i) = NULL; - close (i); - } - - vty_timeout_val = VTY_TIMEOUT_DEFAULT; - - if (vty_accesslist_name) - { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } - - if (vty_ipv6_accesslist_name) - { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = NULL; - } -} +/*============================================================================== + * The cwd at start-up. + */ +/*------------------------------------------------------------------------------ + * Save cwd + * + * This is done early in the morning so that any future operations on files + * can use the original cwd if required. + */ static void vty_save_cwd (void) { @@ -2920,51 +1817,212 @@ vty_save_cwd (void) getcwd (cwd, MAXPATHLEN); } - vty_cwd = XMALLOC (MTYPE_TMP, strlen (cwd) + 1); - strcpy (vty_cwd, cwd); -} + vty_cwd = XSTRDUP(MTYPE_TMP, cwd) ; +} ; +/*------------------------------------------------------------------------------ + * Get cwd as at start-up. Never changed -- so no locking required. + */ char * vty_get_cwd () { return vty_cwd; } -int +/*============================================================================== + * Access functions for VTY values, where locking is or might be required. + */ + +bool vty_shell (struct vty *vty) { - return vty->type == VTY_SHELL ? 1 : 0; + bool result; + VTY_LOCK() ; + result = (vty->vio->type == VTY_SHELL) ; + VTY_UNLOCK() ; + return result; +} + +bool +vty_term(struct vty *vty) +{ + bool result; + VTY_LOCK() ; + result = (vty->vio->type == VTY_TERM); + VTY_UNLOCK() ; + return result; } -int +bool vty_shell_serv (struct vty *vty) { - return vty->type == VTY_SHELL_SERV ? 1 : 0; + bool result; + VTY_LOCK() ; + result = (vty->vio->type == VTY_SHELL_SERV); + VTY_UNLOCK() ; + return result; +} + +enum node_type +vty_get_node(struct vty *vty) +{ + int result; + VTY_LOCK() ; + result = vty->node; + VTY_UNLOCK() ; + return result; } void -vty_init_vtysh () +vty_set_node(struct vty *vty, enum node_type node) { - vtyvec = vector_init (VECTOR_MIN_SIZE); + VTY_LOCK() ; + vty->node = node; + VTY_UNLOCK() ; } -/* Install vty's own commands like `who' command. */ void -vty_init (struct thread_master *master_thread) +vty_set_lines(struct vty *vty, int lines) { - /* For further configuration read, preserve current directory. */ - vty_save_cwd (); + VTY_LOCK() ; + vty->vio->lines = lines; + vty->vio->lines_set = 1 ; + uty_set_height(vty->vio) ; + VTY_UNLOCK() ; +} - vtyvec = vector_init (VECTOR_MIN_SIZE); +/*============================================================================== + * + */ +const char* wordlist[] = + { + "Lorem", + "ipsum", + "dolor", + "magna", + "vita", + "brevis", + "Aliquot", + "in", + "tempura", + "mores", + "ad", + "Astronomica", + "per", + "impedimenta", + "quod", + "et", + "sed", + "semper", + "ut", + "Elisium", + "est", + }; + + +DEFUN (delay_secs, + delay_secs_cmd, + "delay <0-600> secs <0-10000> lines", + "Delay for a number of seconds and spit out a number of lines\n" + "Delay time\n" + "Delay time units\n" + "How much to output\n" + "Output units\n") +{ + unsigned long delay ; + unsigned long lines ; + + unsigned long unit ; + + delay = strtol(argv[0], NULL, 10) ; + lines = strtol(argv[1], NULL, 10) ; + + vty_out(vty, "delay %d secs %d lines\n", (int)delay, (int)lines) ; + + unit = (lines * 100) / delay ; + + while (delay--) + { + char buf[200] ; + char* e ; + int n ; + int w = sizeof(wordlist) / sizeof(char*) ; + + sleep(1) ; + + n = ((rand() % (unit + 1)) + (unit / 2)) / 100 ; + + if ((n > (int)lines) || (delay == 0)) + n = lines ; + + lines -= n ; + + while (n--) + { + char* q ; + const char* p ; + int a ; + + q = buf ; + e = buf + (rand() % 120) + 30 ; + + if ((rand() % 6) == 0) + e = buf ; + + a = (rand() % 4) == 1 ; + while (q < e) + { + int s ; + s = 0 ; + if (a == 1) + s = (rand() % 5) + 1 ; + else if (a > 1) + s = 1 ; + + while (s--) + *q++ = ' ' ; + + p = wordlist[rand() % w] ; + while (*p != '\0') + *q++ = *p++ ; + + a = (rand() % 4) + 1 ; + } ; + + *q++ = '\n' ; + *q++ = '\0' ; - master = master_thread; + vty_out(vty, buf) ; + } ; + } ; - /* Initilize server thread vector. */ - Vvty_serv_thread = vector_init (VECTOR_MIN_SIZE); + return CMD_SUCCESS; +} + +/*============================================================================== + * The VTY command nodes + */ + +struct cmd_node vty_node = +{ + VTY_NODE, + "%s(config-line)# ", + 1, +}; + +/*------------------------------------------------------------------------------ + * Install vty's own commands like `who' command. + */ +static void +uty_init_commands (void) +{ + VTY_ASSERT_LOCKED() ; - /* Install bgp top node. */ install_node (&vty_node, vty_config_write); + install_element (VIEW_NODE, &delay_secs_cmd); + install_element (ENABLE_NODE, &delay_secs_cmd); + install_element (RESTRICTED_NODE, &config_who_cmd); install_element (RESTRICTED_NODE, &show_history_cmd); install_element (VIEW_NODE, &config_who_cmd); @@ -2993,18 +2051,4 @@ vty_init (struct thread_master *master_thread) install_element (VTY_NODE, &vty_ipv6_access_class_cmd); install_element (VTY_NODE, &no_vty_ipv6_access_class_cmd); #endif /* HAVE_IPV6 */ -} - -void -vty_terminate (void) -{ - if (vty_cwd) - XFREE (MTYPE_TMP, vty_cwd); - - if (vtyvec && Vvty_serv_thread) - { - vty_reset (); - vector_free (vtyvec); - vector_free (Vvty_serv_thread); - } -} +} ; @@ -1,130 +1,171 @@ -/* Virtual terminal [aka TeletYpe] interface routine - Copyright (C) 1997 Kunihiro Ishiguro - -This file is part of GNU Zebra. - -GNU Zebra 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, or (at your option) any -later version. - -GNU Zebra 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. - -You should have received a copy of the GNU General Public License -along with GNU Zebra; see the file COPYING. If not, write to the Free -Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -02111-1307, USA. */ +/* VTY top level + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ #ifndef _ZEBRA_VTY_H #define _ZEBRA_VTY_H +#include <stdbool.h> + #include "thread.h" #include "log.h" +#include "qpthreads.h" +#include "qpselect.h" +#include "qtimers.h" +#include "qpnexus.h" +#include "list_util.h" +#include "vector.h" +#include "qstring.h" +#include "node_type.h" + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif -#define VTY_BUFSIZ 512 -#define VTY_MAXHIST 20 - -/* VTY struct. */ -struct vty -{ - /* File descripter of this vty. */ - int fd; - - /* Is this vty connect to file or not */ - enum {VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV} type; - - /* Node status of this vty */ - int node; - - /* What address is this vty comming from. */ - char *address; - - /* Failure count */ - int fail; - - /* Output buffer. */ - struct buffer *obuf; - - /* Command input buffer */ - char *buf; - - /* Command cursor point */ - int cp; - - /* Command length */ - int length; +/*============================================================================== + * The VTYSH uses a unix socket to talk to the daemon. + * + * The ability to respond to a connection from VTYSH appears to be a *compile* + * time option. In the interests of keeping the code up to date, the VTYSH + * option is turned into a testable constant. + */ +#ifdef VTYSH +# define VTYSH_DEFINED 1 +#else +# define VTYSH_DEFINED 0 +#endif - /* Command max length. */ - int max; +enum { VTYSH_ENABLED = VTYSH_DEFINED } ; - /* Histry of command */ - char *hist[VTY_MAXHIST]; +#undef VTYSH_DEFINED - /* History lookup current point */ - int hp; +/*============================================================================== + * VTY Types + */ +enum vty_type +{ + VTY_NONE = 0, /* no type at all */ - /* History insert end point */ - int hindex; + VTY_TERM, /* a telnet terminal -- input and output */ + VTY_SHELL_SERV, /* a vty_shell slave -- input and output */ - /* For current referencing point of interface, route-map, - access-list etc... */ - void *index; + VTY_CONFIG_READ, /* reading config file -- output is to buffer + -- no input */ - /* For multiple level index treatment such as key chain and key. */ - void *index_sub; + VTY_CONFIG_WRITE, /* writing config file -- output is to file + -- no input */ - /* For escape character. */ - unsigned char escape; + VTY_STDOUT, /* general output -- output is to stdout + -- no input */ - /* Current vty status. */ - enum {VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE} status; + VTY_STDERR, /* general output -- output is to stderr + -- no input */ - /* 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; + VTY_SHELL, /* vty in vtysh -- output is to stdout */ +} ; - /* 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): - <NAWS char> <16-bit width> <16-bit height> */ -#define TELNET_NAWS_SB_LEN 5 - 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; +/*============================================================================== + * VTY struct. + */ - /* Window width/height. */ - int width; - int height; +typedef struct vty_io* vty_io ; /* private to vty.c */ - /* Configure lines. */ - int lines; +struct cmd_parsed ; /* in case vty.h expanded before command.h */ - /* Terminal monitor. */ - int monitor; +struct vty +{ + /*---------------------------------------------------------------------- + * The following are the context in which commands are executed. + */ + + /* Node status of this vty + * + * NB: when using qpthreads should lock VTY to access this -- so use + * vty_get_node() and vty_set_node(). + */ + enum node_type node ; + + /* For current referencing point of interface, route-map, access-list + * etc... + * + * NB: this value is private to the command execution, which is assumed + * to all be in the one thread... so no lock required. + */ + void *index ; + + /* For multiple level index treatment such as key chain and key. + * + * NB: this value is private to the command execution, which is assumed + * to all be in the one thread... so no lock required. + */ + void *index_sub ; + + /* String which is newline... read only -- no locking */ + const char* newline ; + + /*---------------------------------------------------------------------------- + * The current command line. + * + * These are set when a command is parsed and dispatched. + * + * They are not touched until the command completes -- so may be read while + * the command is being parsed and executed. + */ + const char* buf ; + struct cmd_parsed* parsed ; + unsigned lineno ; + + /*---------------------------------------------------------------------- + * The following are used inside vty.c only. + */ + + /* Pointer to related vty_io structure */ + vty_io vio ; +}; - /* In configure mode. */ - int config; +/*------------------------------------------------------------------------------ + * Can now include this + */ - /* Read and write thread. */ - struct thread *t_read; - struct thread *t_write; +#include "command.h" - /* Timeout seconds and thread. */ - unsigned long v_timeout; - struct thread *t_timeout; -}; +/*------------------------------------------------------------------------------ + * + */ +#define VTY_BUFSIZ 512 +#define VTY_MAXHIST 51 /* Integrated configuration file. */ #define INTEGRATE_DEFAULT_CONFIG "Quagga.conf" /* Small macro to determine newline is newline only or linefeed needed. */ -#define VTY_NEWLINE ((vty->type == VTY_TERM) ? "\r\n" : "\n") +#define VTY_NEWLINE (((vty) != NULL) ? (vty)->newline : "\n") + +/* For indenting, mostly. */ +extern const char vty_spaces_string[] ; +enum { VTY_MAX_SPACES = 40 } ; +#define VTY_SPACES(n) (vty_spaces_string + ((n) < VTY_MAX_SPACES \ + ? VTY_MAX_SPACES - (n) : 0)) /* Default time out value */ #define VTY_TIMEOUT_DEFAULT 600 @@ -197,31 +238,47 @@ do { } \ } while (0) -/* Exported variables */ +/*------------------------------------------------------------------------------ + * Exported variables + */ extern char integrate_default[]; -/* Prototypes. */ +/*------------------------------------------------------------------------------ + * Prototypes. + */ extern void vty_init (struct thread_master *); +extern void vty_init_r (qpn_nexus, qpn_nexus); +extern void vty_start(const char *addr, unsigned short port, const char *path) ; +#define vty_serv_sock(addr, port, path) vty_start(addr, port, path) +extern void vty_restart(const char *addr, unsigned short port, + const char *path) ; +extern struct vty* vty_open(enum vty_type type) ; +extern void vty_close(struct vty *); + extern void vty_init_vtysh (void); extern void vty_terminate (void); extern void vty_reset (void); -extern struct vty *vty_new (void); +extern void vty_reset_because(const char* why) ; + extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); -extern void vty_read_config (char *, char *); +extern void vty_out_indent(struct vty *vty, int indent) ; +extern void vty_out_clear(struct vty *vty) ; + +extern void vty_read_config (char *config_file, char *config_default); +extern void vty_read_config_first_cmd_special( + char *config_file, char *config_default, + struct cmd_element* first_cmd, bool ignore_warnings) ; + extern void vty_time_print (struct vty *, int); -extern void vty_serv_sock (const char *, unsigned short, const char *); -extern void vty_close (struct vty *); + extern char *vty_get_cwd (void); -extern void vty_log (const char *level, const char *proto, - const char *fmt, struct timestamp_control *, va_list); -extern int vty_config_lock (struct vty *); -extern int vty_config_unlock (struct vty *); -extern int vty_shell (struct vty *); -extern int vty_shell_serv (struct vty *); -extern void vty_hello (struct vty *); -/* Send a fixed-size message to all vty terminal monitors; this should be - an async-signal-safe function. */ -extern void vty_log_fixed (const char *buf, size_t len); +extern bool vty_shell (struct vty *); +extern bool vty_term (struct vty *); +extern bool vty_shell_serv (struct vty *); +extern void vty_hello (struct vty *); +extern enum node_type vty_get_node(struct vty *); +extern void vty_set_node(struct vty *, enum node_type); +extern void vty_set_lines(struct vty *, int); #endif /* _ZEBRA_VTY_H */ diff --git a/lib/vty_cli.c b/lib/vty_cli.c new file mode 100644 index 00000000..bdaf1128 --- /dev/null +++ b/lib/vty_cli.c @@ -0,0 +1,2705 @@ +/* VTY Command Line Handler + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include "keystroke.h" +#include "vty.h" +#include "uty.h" +#include "vty_cli.h" +#include "vty_io.h" +#include "vio_lines.h" + +#include "command.h" +#include "command_execute.h" +#include "command_queue.h" + +#include "memory.h" + +/*============================================================================== + * Host name handling + * + * The host name is used in the command line prompt. The name used is either + * the name set by "hostname" command, or the current machine host name. + * + * Static variables -- under the VTY_LOCK ! + */ + +static char* vty_host_name = NULL ; +int vty_host_name_set = 0 ; + +static void uty_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Update vty_host_name as per "hostname" or "no hostname" command + */ +extern void +uty_set_host_name(const char* name) +{ + VTY_ASSERT_LOCKED() ; + + vty_host_name_set = (name != NULL) ; + + if (vty_host_name_set) + uty_new_host_name(name) ; + else + uty_check_host_name() ; +} ; + +/*------------------------------------------------------------------------------ + * If vty_host_name is set, free it. + */ +extern void +uty_free_host_name(void) +{ + if (vty_host_name != NULL) + XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ +} ; + +/*------------------------------------------------------------------------------ + * If the host name is not set by command, see if the actual host name has + * changed, and if so change it. + * + * This is done periodically in case the actual host name changes ! + */ +extern void +uty_check_host_name(void) +{ + struct utsname names ; + + VTY_ASSERT_LOCKED() ; + + if (vty_host_name_set) + return ; /* nothing to do if set by command */ + + uname (&names) ; + + if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) + uty_new_host_name(names.nodename) ; +} ; + +/*------------------------------------------------------------------------------ + * Set new vty_host_name and run along list of VTYs to mark the change. + */ +static void +uty_new_host_name(const char* name) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + uty_free_host_name() ; + vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + + vio = vio_list_base ; + while (vio != NULL) + { + vio->cli_prompt_set = 0 ; + vio = sdl_next(vio, vio_list) ; + } ; +} ; + +/*============================================================================== + * General mechanism for command execution. + * + * Command execution is driven by select/pselect -- which means that the + * processing of commands is multiplexed with all other activity. In the + * following: + * + * -- read_ready and write_ready are events signalled by select/pselect + * + * -- setting read or write on, means enabling the file for select/pselect to + * consider it for read_ready or write_ready, respectively. + * + * State of the CLI: + * + * cli_blocked -- the CLI is unable to process any further keystrokes. + * + * cmd_in_progress -- a command has been dispatched and has not yet + * completed (may have been queued). + * + * cmd_out_enabled -- the command FIFO is may be emptied. + * + * This is set when a command completes, and cleared when + * everything is written away. + * + * cli_more_wait -- is in "--more--" wait state + * + * The following are the valid combinations: + * + * blkd : cip : o_en : m_wt : + * -----:------:------:------:-------------------------------------------- + * 0 : 0 : 0 : 0 : collecting a new command + * 0 : 1 : 0 : 0 : command dispatched + * 1 : 1 : 0 : 0 : waiting for (queued) command to complete + * 1 : 0 : 1 : 0 : waiting for command output to complete + * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away + * 0 : 0 : 0 : 1 : waiting for "--more--" response + * 1 : 1 : 1 : 0 : waiting for command to complete, after the + * CLI has been closed + * + * There are two output FIFOs: + * + * 1. for the CLI itself -- uty_cli_out and friends + * + * 2. for output generated by commands -- vty_out and friends. + * + * The CLI FIFO is emptied whenever possible, in preference to the command + * FIFO. The command FIFO is emptied when cmd_out_enabled. While + * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a + * given command is collected together before being sent to the file. + * + * Note that only sets read on when the keystroke stream is empty and has not + * yet hit eof. The CLI process is driven mostly by write_ready -- which + * invokes read_ready. + * + * Note also that after each command dispatch the CLI processor exits, to be + * re-entered again on write_ready/read_ready -- so does one command line at + * a time, yielding the processor after each one. + * + * Note that select/pselect treat a socket which is at "EOF", or has seen an + * error, or has been half closed, etc. as readable and writable. This means + * that the CLI will continue to move forward even after the socket is no + * longer delivering any data. + * + *------------------------------------------------------------------------------ + * The "--more--" handling. + * + * This is largely buried in the output handling. + * + * While cmd_in_progress is true cmd_out_enabled will be false. When the + * command completes: + * + * * cmd_in_progress is cleared + * + * * cmd_out_enabled is set + * + * * cli_blocked will be set + * + * * the line_control structure is reset + * + * * the output process is kicked off by setting write on + * + * The output process used the line_control structure to manage the output, and + * occasionally enter the trivial "--more--" CLI. This is invisible to the + * main CLI. (See the cli_more_wait flag and its handling.) + * + * When all the output has completed the CLI will be kicked, which will see + * that the output buffer is now empty, and it can proceed. + * + * It is expected that the output will end with a newline -- so that when the + * CLI is kicked, the cursor will be at the start of an empty line. + * + * This mechanism means that the command output FIFO only ever contains the + * output from the last command executed. + * + * If the user decides to abandon output at the "--more--" prompt, then the + * contents of the command output FIFO are discarded. + * + *------------------------------------------------------------------------------ + * The qpthreads/qpnexus extension. + * + * When running in qnexus mode, many commands are not executed directly in the + * CLI, but are queued for execution by the main "routeing" nexus. + * + * The parsing of commands is context sensitive. The context depends may + * change during the execution of a command. So... it is not possible to + * dispatch a command until the previous one has completed. + * + * In qnexus mode, when a command is queued, the CLI does not go cli_blocked, + * even if some output has already been generated. This allows a further + * command to be entered while the previous one is executed. However, if the + * command is dispatched before the previous one completes, then the cli will + * block. + * + * While the previous command is executing, the current command line has a + * minimal prompt -- to show that the context is not known. When the previous + * command completes, the command line is redrawn in the new context. + * + *------------------------------------------------------------------------------ + * Command line drawn state. + * + * When the cli_drawn flag is set, the current console line contains the + * current prompt and the user input to date. The cursor is positioned where + * the user last placed it. + * + * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all + * output and prompt, and leaves the console at the start of an empty line + * where the command line used to be. + * + * On entry to the CLI, it will draw the command line again if it has been + * wiped. + * + * This mechanism is used to support the partial command line that may be + * entered while a queued command executes. + * + * It is also used for the command help/completion system. + * + * It is also used to support the "monitor" output. + */ + +/*============================================================================== + * The CLI + */ + +#define CONTROL(X) ((X) - '@') + +static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; +static enum vty_readiness uty_cli_standard(vty_io vio) ; +static enum vty_readiness uty_cli_more_wait(vty_io vio) ; +static void uty_cli_draw(vty_io vio) ; +static void uty_cli_draw_this(vty_io vio, enum node_type node) ; +static void uty_cli_wipe(vty_io vio, int len) ; + +static void uty_will_echo (vty_io vio) ; +static void uty_will_suppress_go_ahead (vty_io vio) ; +static void uty_dont_linemode (vty_io vio) ; +static void uty_do_window_size (vty_io vio) ; +static void uty_dont_lflow_ahead (vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Initialise CLI. + * + * It is assumed that the following have been initialised, empty or zero: + * + * cli_prompt_for_node + * cl + * clx + * cli_vbuf + * cli_obuf + * + * cli_drawn + * cli_dirty + * + * cli_prompt_set + * + * cli_blocked + * cmd_in_progress + * cmd_out_enabled + * cli_wait_more + * + * cli_more_enabled + * + * Sets the CLI such that there is apparently a command in progress, so that + * further initialisation (in particular hello messages and the like) is + * treated as a "start up command". + * + * Sends a suitable set of Telnet commands to start the process. + */ +extern void +uty_cli_init(vty_io vio) +{ + assert(vio->type == VTY_TERM) ; + + vio->cmd_in_progress = 1 ; + vio->cli_blocked = 1 ; + + vio->cli_do = cli_do_nothing ; + + /* Setting up terminal. */ + uty_will_echo (vio); + uty_will_suppress_go_ahead (vio); + uty_dont_linemode (vio); + uty_do_window_size (vio); + if (0) + uty_dont_lflow_ahead (vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Start the CLI. + * + * All start-up operations are complete -- so the "command" is now complete. + * + * Returns: write_ready -- so the first event is a write event, to flush + * any output to date. + */ +extern enum vty_readiness +uty_cli_start(vty_io vio) +{ + uty_cli_cmd_complete(vio, CMD_SUCCESS) ; + return write_ready ; +} ; + +/*------------------------------------------------------------------------------ + * Close the CLI + * + * Note that if any command is revoked, then will clear cmd_in_progress and + * set cmd_out_enabled -- so any output can now clear. + */ +extern void +uty_cli_close(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; + + cq_revoke(vio->vty) ; + + vio->cli_blocked = 1 ; /* don't attempt any more */ + vio->cmd_out_enabled = 1 ; /* allow output to clear */ +} ; + +/*------------------------------------------------------------------------------ + * CLI for VTY_TERM + * + * Do nothing at all if half closed. + * + * Otherwise do: standard CLI + * or: "--more--" CLI + * + * NB: on return, requires that an attempt is made to write away anything that + * may be ready for that. + */ +extern enum vty_readiness +uty_cli(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert((vio->type == VTY_TERM) || (vio->real_type == VTY_TERM)) ; + + if (vio->half_closed) + return not_ready ; /* Nothing more if half closed */ + + /* Standard or "--more--" CLI ? */ + if (vio->cli_more_wait) + return uty_cli_more_wait(vio) ; + else + return uty_cli_standard(vio) ; +} ; + +/*============================================================================== + * The Standard CLI + */ + +static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ; +static void uty_cli_response(vty_io vio, enum cli_do cli_do) ; +static bool uty_cli_dispatch(vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Standard CLI for VTY_TERM -- if not blocked, runs until: + * + * * runs out of keystrokes + * * executes a command + * + * Note that this executes at most one command each time it is called. This + * is to allow for a modicum of sharing of the system. For real keyboard input + * this will make no difference at all ! + * + * Returns: not_ready blocked and was blocked when entered + * write_ready if there is anything in the keystroke stream + * read_ready otherwise + */ +static enum vty_readiness +uty_cli_standard(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + /* cli_blocked is set when is waiting for a command, or its output to + * complete -- unless either of those has happened, is still blocked. + * + * NB: in both these cases, assumes that other forces are at work to + * keep things moving. + */ + if (vio->cli_blocked) + { + assert(vio->cmd_in_progress || vio->cmd_out_enabled) ; + + if (vio->cmd_in_progress) + { + assert(!vio->cmd_out_enabled) ; + return not_ready ; + } ; + + if (!vio_fifo_empty(&vio->cmd_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + vio->cmd_out_enabled = 0 ; + } ; + + /* If there is nothing pending, then can run the CLI until there is + * something to do, or runs out of input. + * + * If there is something to do, that is because a previous command has + * now completed, which may have wiped the pending command or changed + * the required prompt. + */ + if (vio->cli_do == cli_do_nothing) + vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + else + uty_cli_draw_this(vio, vio->vty->node) ; + + /* If have something to do, do it. */ + if (vio->cli_do != cli_do_nothing) + { + /* Reflect immediate response */ + uty_cli_response(vio, vio->cli_do) ; + + /* If command not already in progress, dispatch this one, which may + * set the CLI blocked. + * + * Otherwise is now blocked until queued command completes. + */ + if (!vio->cmd_in_progress) + vio->cli_blocked = uty_cli_dispatch(vio) ; + else + vio->cli_blocked = 1 ; + } ; + + /* Use write_ready as a proxy for read_ready on the keystroke stream. + * + * Also, if the command line is not drawn, then return write_ready, so + * that + * + * Note that if has just gone cli_blocked, still returns ready. This is + * defensive: at worst will generate one unnecessary read_ready/write_ready + * event. + */ + if (keystroke_stream_empty(vio->key_stream)) + return read_ready ; + else + return write_ready ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch the current vio->cli_do -- queueing it if necessary. + * + * Requires that are NOT blocked and NO command is queued. + * + * Expects to be on new blank line, and when returns will be on new, blank + * line. + * + * Returns: true <=> command completed and output is pending + * false => command has been queued and is now in progress + * + * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. + * + * Can set vio->cl_do = and vio->cl to be a follow-on command. + */ +static bool +uty_cli_dispatch(vty_io vio) +{ + qstring_t tmp ; + enum cli_do cli_do ; + enum cmd_return_code ret ; + + struct vty* vty = vio->vty ; + + VTY_ASSERT_LOCKED() ; + assert(!vio->cli_blocked && !vio->cmd_in_progress) ; + + /* Set vio->clx to the command about to execute. + * + * Clear vio->cl and vio->cl_do. + */ + vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ + vio->cmd_out_enabled = 0 ; /* => collect all output */ + + tmp = vio->clx ; /* swap clx and cl */ + vio->clx = vio->cl ; + vio->cl = tmp ; + + qs_term(&vio->clx) ; /* ensure string is terminated */ + vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ + cli_do = vio->cli_do ; /* current operation */ + + vio->cli_do = cli_do_nothing ; /* clear */ + qs_clear(&vio->cl) ; /* set cl empty (with '\0') */ + + /* Reset the command output FIFO and line_control */ + assert(vio_fifo_empty(&vio->cmd_obuf)) ; + uty_out_clear(vio) ; /* clears FIFO and line control */ + + /* Dispatch command */ + if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) + { + /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ + ret = uty_auth(vty, vty->buf, cli_do) ; + } + else + { + /* All other nodes... */ + switch (cli_do) + { + case cli_do_nothing: + ret = CMD_SUCCESS ; + break ; + + case cli_do_command: + ret = uty_command(vty) ; + break ; + + case cli_do_ctrl_c: + ret = uty_stop_input(vty) ; + break ; + + case cli_do_ctrl_d: + ret = uty_down_level(vty) ; + break ; + + case cli_do_ctrl_z: + ret = uty_command(vty) ; + if (ret == CMD_QUEUED) + vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ + else + ret = uty_end_config(vty) ; /* do the ^Z now */ + break ; + + case cli_do_eof: + ret = uty_cmd_close(vio->vty, "End") ; + break ; + + default: + zabort("unknown cli_do_xxx value") ; + } ; + } ; + + if (ret == CMD_QUEUED) + { + uty_cli_draw(vio) ; /* draw the prompt */ + return false ; /* command not complete */ + } + else + { + uty_cli_cmd_complete(vio, ret) ; + return true ; /* command complete */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * Queued command has completed. + * + * Note that sets write on whether there is anything in the output buffer + * or not... write_ready will kick read_ready. + */ +extern void +vty_queued_result(struct vty *vty, enum cmd_return_code ret) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + if (!vio->closed) + { + uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ + + /* Do the command completion actions that were deferred because the + * command was queued. + * + * Return of CMD_QUEUED => command was revoked before being executed. + * However interesting that might be... frankly don't care. + */ + uty_cli_cmd_complete(vio, ret) ; + + /* Kick the socket -- to write away any outstanding output, and + * re-enter the CLI when that's done. + */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + } + else + { + /* If the VTY is closed, the only reason it still exists is because + * there was cmd_in_progress. + */ + vio->cmd_in_progress = 0 ; + + uty_close(vio) ; /* Final close */ + } ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Command has completed, so: + * + * * clear cmd_in_progress + * * set cmd_out_enabled -- so any output can now proceed + * * set cli_blocked -- waiting for output to complete + * * and prepare the line control for output + * + * If the return is CMD_CLOSE, then also now does the required half close. + * + * Note that apart from CMD_CLOSE, don't really care what the return was. Any + * diagnostics or other action must be dealt with elsewhere (as part of the + * command execution. + * + * Note that everything proceeds as if there is some output. So after every + * command goes through at least one write_ready event. + * + * This ensures some multiplexing at the command level. + * + * It also means that the decision about whether there is anything to output + * is left to the output code. + */ +static void +uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; + + if (ret == CMD_CLOSE) + uty_half_close(vio, NULL) ; + + vio->cmd_in_progress = 0 ; /* command complete */ + vio->cmd_out_enabled = 1 ; /* enable the output */ + vio->cli_blocked = 1 ; /* now blocked waiting for output */ + + vio->vty->buf = NULL ; /* finished with command line */ + + uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ +} ; + +/*============================================================================== + * The "--more--" CLI + * + * While command output is being cleared from its FIFO, the CLI is cli_blocked. + * + * When the output side signals that "--more--" is required, it sets the + * cli_more_wait flag and clears the cmd_out_enabled flag. + * + * The first stage of handling "--more--" is to suck the input dry, so that + * (as far as is reasonably possible) does not steal a keystroke as the + * "--more--" response which was typed before the prompt was issued. + * + * The cli_blocked flag indicates that the CLI is in this first stage. + */ + +/*------------------------------------------------------------------------------ + * Change the CLI to the "--more--" CLI. + * + * Outputs the new prompt line. + */ +extern void +uty_cli_enter_more_wait(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; + + uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is + wiped before change the CLI state */ + + vio->cmd_out_enabled = 0 ; /* stop output pro tem */ + vio->cli_more_wait = 1 ; /* new state */ + + uty_cli_draw(vio) ; /* draw the "--more--" */ +} ; + +/*------------------------------------------------------------------------------ + * Exit the "--more--" CLI. + * + * Wipes the "--more--" prompt. + * + * This is used when the user responds to the prompt. + * + * It is also used when the vty is "half-closed". In this case, it is (just) + * possible that the '--more--' prompt is yet to be completely written away, + * so: + * + * * assert that is either: !vio->cli_blocked (most of the time it will) + * or: !vio_fifo_empty(&vio->cli_obuf) + * + * * note that can wipe the prompt even though it hasn't been fully + * written away yet. (The effect is to append the wipe action to the + * cli_obuf !) + */ +extern void +uty_cli_exit_more_wait(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf)) + && !vio->cmd_out_enabled && vio->cli_more_wait) ; + + uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--') + before changing the CLI state */ + + vio->cli_blocked = 1 ; /* back to blocked waiting for output */ + vio->cli_more_wait = 0 ; /* exit more_wait */ + vio->cmd_out_enabled = 1 ; /* re-enable output */ +} ; + +/*------------------------------------------------------------------------------ + * Handle the "--more--" state. + * + * Deals with the first stage if cli_blocked. + * + * Tries to steal a keystroke, and when succeeds wipes the "--more--" + * prompt and exits cli_more_wait -- and may cancel all outstanding output. + * + * EOF on input causes immediate exit from cli_more_state. + * + * Returns: read_ready -- waiting to steal a keystroke + * now_ready -- just left cli_more_wait + * not_ready -- otherwise + */ +static enum vty_readiness +uty_cli_more_wait(vty_io vio) +{ + struct keystroke steal ; + + VTY_ASSERT_LOCKED() ; + + /* Deal with the first stage of "--more--" */ + if (vio->cli_blocked) + { + int get ; + + /* If the CLI buffer is not yet empty, then is waiting for the + * initial prompt to clear, so nothing to be done here. + */ + if (!vio_fifo_empty(&vio->cli_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + + /* empty the input buffer into the keystroke stream */ + do + { + get = uty_read(vio, NULL) ; + } while (get > 0) ; + } ; + + /* Go through the "--more--" process, unless no longer write_open (!) */ + if (vio->sock.write_open) + { + /* The read fetches a reasonable lump from the I/O -- so if there + * is a complete keystroke available, expect to get it. + * + * If no complete keystroke available to steal, returns ks_null. + * + * If has hit EOF (or error etc), returns knull_eof. + */ + uty_read(vio, &steal) ; + + /* If nothing stolen, make sure prompt is drawn and wait for more + * input. + */ + if ((steal.type == ks_null) && (steal.value != knull_eof)) + { + if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */ + return write_ready ; + else + return read_ready ; + } ; + + /* Stolen a keystroke -- a (very) few terminate all output */ + if (steal.type == ks_char) + { + switch (steal.value) + { + case CONTROL('C'): + case 'q': + case 'Q': + uty_out_clear(vio) ; + break; + + default: /* everything else, thrown away */ + break ; + } ; + } ; + } ; + + /* End of "--more--" process + * + * Wipe out the prompt and update state. + * + * Return write_ready to tidy up the screen and, unless cleared, write + * some more. + */ + uty_cli_exit_more_wait(vio) ; + + return now_ready ; +} ; + +/*============================================================================== + * CLI VTY output + * + * This is buffered separately from the general (command) VTY output above. + * + * Has a dedicated buffer in the struct vty, which is flushed regularly during + * command processing. + * + * It is expected that can flush straight to the file, since this is running at + * CLI speed. However, if the CLI is being driven by something other than a + * keyboard, or "monitor" output has filled the buffers, then may need to + * have intermediate buffering. + * + * No actual I/O takes place here-- all "output" is to vio->cli_obuf + * and/or vio->cli_ex_obuf + * + * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. + * This is used while passwords are entered and to allow command line changes + * to be made while the line is not visible. + */ + +enum { cli_rep_count = 32 } ; + +typedef const char cli_rep_char[cli_rep_count] ; + +static const char telnet_backspaces[] = + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + } ; +CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; + +static const char telnet_spaces[] = + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' + } ; +CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; + +static const char* telnet_newline = "\r\n" ; + +static void uty_cli_write(vty_io vio, const char *this, int len) ; +static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf fprintf() + */ +static void +uty_cli_out(vty_io vio, const char *format, ...) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->sock.write_open) + { + va_list args ; + + va_start (args, format); + vio_fifo_vprintf(&vio->cli_obuf, format, args) ; + va_end(args); + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo user input + * + * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open + */ +static void +uty_cli_echo(vty_io vio, const char *this, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->sock.write_open) + return ; + + uty_cli_write(vio, this, len) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo 'n' characters using a cli_rep_char string + * + * Do nothing if echo suppressed (eg in AUTH_NODE) + */ +static void +uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->sock.write_open) + return ; + + uty_cli_write_n(vio, chars, n) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf write() + */ +inline static void +uty_cli_write(vty_io vio, const char *this, int len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->sock.write_open) + vio_fifo_put(&vio->cli_obuf, this, len) ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write 'n' characters using a cli_rep_char string + */ +static void +uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) +{ + int len ; + + len = sizeof(cli_rep_char) ; + while (n > 0) + { + if (n < len) + len = n ; + uty_cli_write(vio, chars, len) ; + n -= len ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write given string + * + * Returns length of string. + */ +static int +uty_cli_write_s(vty_io vio, const char *str) +{ + int len ; + + len = strlen(str) ; + if (len != 0) + uty_cli_write(vio, str, len) ; + + return len ; +} ; + +/*============================================================================== + * Standard Messages + */ + +/*------------------------------------------------------------------------------ + * Send newline to the console. + * + * Clears the cli_drawn and the cli_dirty flags. + */ +static void +uty_cli_out_newline(vty_io vio) +{ + uty_cli_write(vio, telnet_newline, 2) ; + vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Wipe 'n' characters. + * + * If 'n' < 0, wipes characters backwards and moves cursor back. + * 'n' > 0, wipes characters forwards, leaving cursor where it is + */ +static void +uty_cli_out_wipe_n(vty_io vio, int n) +{ + if (n < 0) + { + n = abs(n) ; + uty_cli_write_n(vio, telnet_backspaces, n); + } ; + + if (n > 0) + { + uty_cli_write_n(vio, telnet_spaces, n) ; + uty_cli_write_n(vio, telnet_backspaces, n) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send response to the given cli_do + * + * If no command is in progress, then will send newline to signal that the + * command is about to be dispatched. + * + * If command is in progress, then leaves cursor on '^' to signal that is now + * waiting for previous command to complete. + */ +static const char* cli_response [2][cli_do_count] = +{ + { /* when not waiting for previous command to complete */ + [cli_do_command] = "", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" + }, + { /* when waiting for a previous command to complete */ + [cli_do_command] = "^", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" + } +} ; + +static void +uty_cli_response(vty_io vio, enum cli_do cli_do) +{ + const char* str ; + int len ; + + if ((cli_do == cli_do_nothing) || (vio->half_closed)) + return ; + + str = (cli_do < cli_do_count) + ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; + assert(str != NULL) ; + + len = uty_cli_write_s(vio, str) ; + + if (vio->cmd_in_progress) + { + vio->cli_extra_len = len ; + uty_cli_write_n(vio, telnet_backspaces, len) ; + } + else + { + uty_cli_out_newline(vio) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send various messages with trailing newline. + */ + +static void +uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; + uty_cli_out_newline(vio) ; +} ; + +static void +uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; + uty_cli_out_newline(vio) ; +} ; + +/*============================================================================== + * Command line draw and wipe + */ + +/*------------------------------------------------------------------------------ + * Wipe the current console line -- if any. + */ +static void +uty_cli_wipe(vty_io vio, int len) +{ + int a ; + int b ; + + if (!vio->cli_drawn) + return ; /* quit if already wiped */ + + assert(vio->cl.cp <= vio->cl.len) ; + + /* Establish how much ahead and how much behind the cursor */ + a = vio->cli_extra_len ; + b = vio->cli_prompt_len ; + + if (!vio->cli_echo_suppress && !vio->cli_more_wait) + { + a += vio->cl.len - vio->cl.cp ; + b += vio->cl.cp ; + } ; + + /* Wipe anything ahead of the current position and ahead of new len */ + if ((a + b) > len) + uty_cli_out_wipe_n(vio, +a) ; + + /* Wipe anything behind current position, but ahead of new len */ + if (b > len) + { + uty_cli_out_wipe_n(vio, -(b - len)) ; + b = len ; /* moved the cursor back */ + } ; + + /* Back to the beginning of the line */ + uty_cli_write_n(vio, telnet_backspaces, b) ; + + /* Nothing there any more */ + vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * If not currently drawn, draw prompt etc according to the current state + * and node. + * + * See uty_cli_draw(). + */ +extern bool +uty_cli_draw_if_required(vty_io vio) +{ + if (vio->cli_drawn) + return false ; + + uty_cli_draw(vio) ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt etc for the current vty node. + * + * See uty_cli_draw_this() + */ +static void +uty_cli_draw(vty_io vio) +{ + uty_cli_draw_this(vio, vio->vty->node) ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt and entire command line, leaving current position where it + * should be. + * + * If command line is currently drawn, this wipes and redraws. + * + * Otherwise, assumes is positioned at start of an empty line. + * + * Draws prompt according to the given 'node', except: + * + * * if is half_closed, draw nothing -- wipes the current line + * + * * if is cli_more_wait, draw the "--more--" prompt + * + * * if is cmd_in_progress, draw the vestigial prompt. + * + * By the time the current command completes, the node may have changed, so + * the current prompt may be invalid. + * + * Sets: cli_drawn = true + * cli_dirty = false + * cli_prompt_len = length of prompt used + * cli_extra_len = 0 + * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) + */ +static void +uty_cli_draw_this(vty_io vio, enum node_type node) +{ + const char* prompt ; + size_t l_len ; + int p_len ; + + if (vio->cli_dirty) + uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */ + + /* Sort out what the prompt is. */ + if (vio->half_closed) + { + prompt = "" ; + p_len = 0 ; + l_len = 0 ; + } + else if (vio->cli_more_wait) + { + prompt = "--more--" ; + p_len = strlen(prompt) ; + l_len = 0 ; + } + else if (vio->cmd_in_progress) + { + /* If there is a queued command, the prompt is a minimal affair. */ + prompt = "~ " ; + p_len = strlen(prompt) ; + l_len = vio->cl.len ; + } + else + { + /* The prompt depends on the node, and is expected to include the + * host name. + * + * Caches the prompt so doesn't re-evaluate it every time. + * + * If the host name changes, the cli_prompt_set flag is cleared. + */ + if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) + { + const char* prompt ; + + if (vty_host_name == NULL) + uty_check_host_name() ; /* should never be required */ + + prompt = cmd_prompt(node) ; + if (prompt == NULL) + { + zlog_err("vty %s has node %d", uty_get_name(vio), node) ; + prompt = "%s ???: " ; + } ; + + qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + + vio->cli_prompt_node = node ; + vio->cli_prompt_set = 1 ; + } ; + + prompt = vio->cli_prompt_for_node.body ; + p_len = vio->cli_prompt_for_node.len ; + l_len = vio->cl.len ; + } ; + + /* Now, if line is currently drawn, time to wipe it */ + if (vio->cli_drawn) + uty_cli_wipe(vio, p_len + l_len) ; + + /* Set about writing the prompt and the line */ + vio->cli_drawn = 1 ; + vio->cli_extra_len = 0 ; + vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + vio->cli_prompt_len = p_len ; + + uty_cli_write(vio, prompt, p_len) ; + + if (l_len != 0) + { + uty_cli_write(vio, qs_chars(&vio->cl), l_len) ; + if (vio->cl.cp < l_len) + uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ; + } ; +} ; + +/*============================================================================== + * Monitor output. + * + * To prepare for monitor output, wipe as much as is necessary for the + * monitor line to appear correctly. + * + * After monitor output, may need to do two things: + * + * * if the output was incomplete, place the rump in the CLI buffer, + * so that: + * + * a. don't mess up the console with partial lines + * + * b. don't lose part of a message + * + * c. act as a brake on further monitor output -- cannot do any more + * until the last, part, line is dealt with. + * + * * restore the command line, unless it is empty ! + */ + + /*----------------------------------------------------------------------------- + * Prepare for new monitor output line. + * + * Wipe any existing command line. + */ +extern void +uty_cli_pre_monitor(vty_io vio, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_wipe(vio, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Recover from monitor line output. + * + * If monitor line failed to complete, append the rump to the CLI buffer. + * + * If have a non-empty command line, or is cli_more_wait, redraw the command + * line. + * + * Returns: 0 => rump was empty and no command line stuff written + * > 0 => rump not empty or some command line stuff written + */ +extern int +uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (len != 0) + uty_cli_write(vio, buf, len) ; + + if (vio->cli_more_wait || (vio->cl.len != 0)) + { + uty_cli_draw(vio) ; + ++len ; + } ; + + return len ; +} ; + +/*============================================================================== + * Command line processing loop + */ + +static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ; +static int uty_cli_insert (vty_io vio, const char* chars, int n) ; +static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; +static int uty_cli_word_overwrite (vty_io vio, char *str) ; +static int uty_cli_forwards(vty_io vio, int n) ; +static int uty_cli_backwards(vty_io vio, int n) ; +static int uty_cli_del_forwards(vty_io vio, int n) ; +static int uty_cli_del_backwards(vty_io vio, int n) ; +static int uty_cli_bol (vty_io vio) ; +static int uty_cli_eol (vty_io vio) ; +static int uty_cli_word_forwards_delta(vty_io vio) ; +static int uty_cli_word_forwards(vty_io vio) ; +static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ; +static int uty_cli_word_backwards_pure (vty_io vio) ; +static int uty_cli_word_backwards (vty_io vio) ; +static int uty_cli_del_word_forwards(vty_io vio) ; +static int uty_cli_del_word_backwards(vty_io vio) ; +static int uty_cli_del_to_eol (vty_io vio) ; +static int uty_cli_clear_line(vty_io vio) ; +static int uty_cli_transpose_chars(vty_io vio) ; +static void uty_cli_history_use(vty_io vio, int step) ; +static void uty_cli_next_line(vty_io vio) ; +static void uty_cli_previous_line (vty_io vio) ; +static void uty_cli_complete_command (vty_io vio, enum node_type node) ; +static void uty_cli_describe_command (vty_io vio, enum node_type node) ; + +/*------------------------------------------------------------------------------ + * Fetch next keystroke, reading from the file if required. + */ +static inline bool +uty_cli_get_keystroke(vty_io vio, keystroke stroke) +{ + if (keystroke_get(vio->key_stream, stroke)) + return 1 ; + + uty_read(vio, NULL) ; /* not stealing */ + + return keystroke_get(vio->key_stream, stroke) ; +} ; + +/*------------------------------------------------------------------------------ + * Process keystrokes until run out of input, or get something to cli_do. + * + * If required, draw the prompt and command line. + * + * Process keystrokes until run out of stuff to do, or have a "command line" + * that must now be executed. + * + * Processes the contents of the keystroke stream. If exhausts that, will set + * ready to read and return. (To give some "sharing".) + * + * Returns: cli_do_xxxx + * + * When returns the cl is '\0' terminated. + */ +static enum cli_do +uty_cli_process(vty_io vio, enum node_type node) +{ + struct keystroke stroke ; + uint8_t u ; + int auth ; + enum cli_do ret ; + + auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + /* Now process as much as possible of what there is */ + ret = cli_do_nothing ; + while (1) + { + if (!vio->cli_drawn) + uty_cli_draw_this(vio, node) ; + + if (!uty_cli_get_keystroke(vio, &stroke)) + { + ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; + break ; + } ; + + if (stroke.flags != 0) + { + /* TODO: deal with broken keystrokes */ + continue ; + } ; + + switch (stroke.type) + { + /* Straightforward character -----------------------------------*/ + /* Note: only interested in 8-bit characters ! */ + case ks_char: + u = (uint8_t)stroke.value ; + + switch (stroke.value) + { + case CONTROL('A'): + uty_cli_bol (vio); + break; + + case CONTROL('B'): + uty_cli_backwards(vio, 1); + break; + + case CONTROL('C'): + ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/ + break ; + + case CONTROL('D'): + if (vio->cl.len == 0) /* if at start of empty line */ + ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/ + else + uty_cli_del_forwards(vio, 1); + break; + + case CONTROL('E'): + uty_cli_eol (vio); + break; + + case CONTROL('F'): + uty_cli_forwards(vio, 1); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_backwards(vio, 1); + break; + + case CONTROL('K'): + uty_cli_del_to_eol (vio); + break; + + case CONTROL('N'): + uty_cli_next_line (vio); + break; + + case CONTROL('P'): + uty_cli_previous_line (vio); + break; + + case CONTROL('T'): + uty_cli_transpose_chars (vio); + break; + + case CONTROL('U'): + uty_cli_clear_line(vio); + break; + + case CONTROL('W'): + uty_cli_del_word_backwards (vio); + break; + + case CONTROL('Z'): + ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ + break; + + case '\n': + case '\r': + ret = cli_do_command ; /* Exit on CR or LF.............*/ + break ; + + case '\t': + if (auth) + break ; + else + uty_cli_complete_command (vio, node); + break; + + case '?': + if (auth) + uty_cli_insert (vio, (char*)&u, 1); + else + uty_cli_describe_command (vio, node); + break; + + default: + if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) + uty_cli_insert (vio, (char*)&u, 1) ; + break; + } + break ; + + /* ESC X -------------------------------------------------------------*/ + case ks_esc: + switch (stroke.value) + { + case 'b': + uty_cli_word_backwards (vio); + break; + + case 'f': + uty_cli_word_forwards (vio); + break; + + case 'd': + uty_cli_del_word_forwards (vio); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_word_backwards (vio); + break; + + default: + break; + } ; + break ; + + /* ESC [ X -----------------------------------------------------------*/ + case ks_csi: + if (stroke.len != 0) + break ; /* only recognise 3 byte sequences */ + + switch (stroke.value) + { + case ('A'): + uty_cli_previous_line (vio); + break; + + case ('B'): + uty_cli_next_line (vio); + break; + + case ('C'): + uty_cli_forwards(vio, 1); + break; + + case ('D'): + uty_cli_backwards(vio, 1); + break; + default: + break ; + } ; + break ; + + /* Telnet Command ----------------------------------------------------*/ + case ks_iac: + uty_telnet_command(vio, &stroke, false) ; + break ; + + /* Unknown -----------------------------------------------------------*/ + default: + zabort("unknown keystroke type") ; + } ; + + /* After each keystroke..... */ + + if (ret != cli_do_nothing) + { + uty_cli_eol (vio) ; /* go to the end of the line */ + break ; /* stop processing */ + } ; + } ; + + /* Tidy up and return where got to. */ + + qs_term(&vio->cl) ; /* add '\0' */ + + return ret ; +} ; + +/*============================================================================== + * Command line operations + */ + +/*------------------------------------------------------------------------------ + * Insert 'n' characters at current position in the command line + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_insert (vty_io vio, const char* chars, int n) +{ + int after ; + + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + + if (n <= 0) + return n ; /* avoid trouble */ + + after = qs_insert(&vio->cl, chars, n) ; + + uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + + if (after != 0) + uty_cli_echo_n(vio, telnet_backspaces, after) ; + + vio->cl.cp += n ; + + return n ; +} ; + +/*------------------------------------------------------------------------------ + * Overstrike 'n' characters at current position in the command line + * + * Move current position forwards. + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_overwrite (vty_io vio, char* chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + + if (n > 0) + { + qs_replace(&vio->cl, chars, n) ; + uty_cli_echo(vio, chars, n) ; + + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Insert a word into vty interface with overwrite mode. + * + * NB: Assumes result will then be the end of the line. + * + * Returns number of characters inserted -- ie length of string + */ +static int +uty_cli_word_overwrite (vty_io vio, char *str) +{ + int n ; + VTY_ASSERT_LOCKED() ; + + n = uty_cli_overwrite(vio, str, strlen(str)) ; + + vio->cl.len = vio->cl.cp ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Forward 'n' characters -- stop at end of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_forwards(vty_io vio, int n) +{ + int have ; + VTY_ASSERT_LOCKED() ; + + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Backwards 'n' characters -- stop at start of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_backwards(vty_io vio, int n) +{ + VTY_ASSERT_LOCKED() ; + + if ((int)vio->cl.cp < n) + n = vio->cl.cp ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cli_echo_n(vio, telnet_backspaces, n) ; + vio->cl.cp -= n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters -- forwards -- stop at end of line. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_forwards(vty_io vio, int n) +{ + int after ; + int have ; + + VTY_ASSERT_LOCKED() ; + + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; /* cannot delete more than have */ + + assert(n >= 0) ; + + if (n <= 0) + return 0 ; + + after = qs_delete(&vio->cl, n) ; + + if (after > 0) + uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + + uty_cli_echo_n(vio, telnet_spaces, n) ; + uty_cli_echo_n(vio, telnet_backspaces, after + n) ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters before the point. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_backwards(vty_io vio, int n) +{ + return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; +} + +/*------------------------------------------------------------------------------ + * Move to the beginning of the line. + * + * Returns number of characters moved over. + */ +static int +uty_cli_bol (vty_io vio) +{ + return uty_cli_backwards(vio, vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Move to the end of the line. + * + * Returns number of characters moved over + */ +static int +uty_cli_eol (vty_io vio) +{ + return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word delta -- distance to start of next word. + * + * Return number of characters to step over to reach next word. + * + * Steps over non-space characters and then any spaces. + */ +static int +uty_cli_word_forwards_delta(vty_io vio) +{ + char* cp ; + char* tp ; + char* ep ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + ep = qs_ep_char(&vio->cl) ; + + tp = cp ; + + while ((tp < ep) && (*tp != ' ')) + ++tp ; + + while ((tp < ep) && (*tp == ' ')) + ++tp ; + + return tp - cp ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word -- move to start of next word. + * + * Moves past any non-spaces, then past any spaces. + */ +static int +uty_cli_word_forwards(vty_io vio) +{ + return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word delta -- distance to start of next word, back. + * + * Return number of characters to step over to reach next word. + * + * If "eat_spaces", starts by stepping over spaces. + * Steps back until next (backwards) character is space, or hits start of line. + */ +static int +uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) +{ + char* cp ; + char* tp ; + char* sp ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + sp = qs_chars(&vio->cl) ; + + tp = cp ; + + if (eat_spaces) + while ((tp > sp) && (*(tp - 1) == ' ')) + --tp ; + + while ((tp > sp) && (*(tp - 1) != ' ')) + --tp ; + + return cp - tp ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word, but not trailing spaces. + * + * Move back until next (backwards) character is space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards_pure (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word -- move to start of previous word. + * + * Moves past any spaces, then move back until next (backwards) character is + * space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Delete to end of word -- forwards. + * + * Deletes any leading spaces, then deletes upto next space or end of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_forwards(vty_io vio) +{ + return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} + +/*------------------------------------------------------------------------------ + * Delete to start of word -- backwards. + * + * Deletes any trailing spaces, then deletes upto next space or start of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_backwards(vty_io vio) +{ + return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill rest of line from current point. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_to_eol (vty_io vio) +{ + return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill line from the beginning. + * + * Returns number of characters deleted. + */ +static int +uty_cli_clear_line(vty_io vio) +{ + uty_cli_bol(vio) ; + return uty_cli_del_to_eol(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Transpose chars before or at the point. + * + * Return number of characters affected. + */ +static int +uty_cli_transpose_chars(vty_io vio) +{ + char chars[2] ; + char* cp ; + + VTY_ASSERT_LOCKED() ; + + /* Give up if < 2 characters or at start of line. */ + if ((vio->cl.len < 2) || (vio->cl.cp < 1)) + return 0 ; + + /* Move back to first of characters to exchange */ + if (vio->cl.cp == vio->cl.len) + uty_cli_backwards(vio, 2) ; + else + uty_cli_backwards(vio, 1) ; + + /* Pick up in the new order */ + cp = qs_cp_char(&vio->cl) ; + chars[1] = *cp++ ; + chars[0] = *cp ; + + /* And overwrite */ + return uty_cli_overwrite(vio, chars, 2) ; +} ; + +/*============================================================================== + * Command line history handling + */ + +/*------------------------------------------------------------------------------ + * Add given command line to the history buffer. + * + * This is inserting the vty->buf line into the history. + */ +extern void +uty_cli_hist_add (vty_io vio, const char* cmd_line) +{ + qstring prev_line ; + qstring_t line ; + int prev_index ; + + VTY_ASSERT_LOCKED() ; + + /* Construct a dummy qstring for the given command line */ + qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ + + /* make sure have a suitable history vector */ + vector_set_min_length(&vio->hist, VTY_MAXHIST) ; + + /* find the previous command line in the history */ + prev_index = vio->hindex - 1 ; + if (prev_index < 0) + prev_index = VTY_MAXHIST - 1 ; + + prev_line = vector_get_item(&vio->hist, prev_index) ; + + /* If the previous line is NULL, that means the history is empty. + * + * If the previous line is essentially the same as the current line, + * replace it with the current line -- so that the latest whitespace + * version is saved. + * + * Either way, replace the the previous line entry by moving hindex + * back ! + */ + if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) + vio->hindex = prev_index ; + else + prev_line = vector_get_item(&vio->hist, vio->hindex) ; + + /* Now replace the hindex entry */ + vector_set_item(&vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + + /* Advance to the near future and reset the history pointer */ + vio->hindex++; + if (vio->hindex == VTY_MAXHIST) + vio->hindex = 0; + + vio->hp = vio->hindex; +} ; + +/*------------------------------------------------------------------------------ + * Replace command line by current history. + * + * This function is called from vty_next_line and vty_previous_line. + * + * Step +1 is towards the present + * -1 is into the past + */ +static void +uty_cli_history_use(vty_io vio, int step) +{ + int index ; + unsigned old_len ; + unsigned after ; + unsigned back ; + qstring hist ; + + VTY_ASSERT_LOCKED() ; + + assert((step == +1) || (step == -1)) ; + + index = vio->hp ; + + /* Special case of being at the insertion point */ + if (index == vio->hindex) + { + if (step > 0) + return ; /* already in the present */ + + /* before stepping back from the present, take a copy of the + * current command line -- so can get back to it. + */ + hist = vector_get_item(&vio->hist, vio->hindex) ; + vector_set_item(&vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + } ; + + /* Advance or retreat */ + index += step ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + else if (index >= VTY_MAXHIST) + index = 0 ; + + hist = vector_get_item(&vio->hist, index) ; + + /* If moving backwards in time, may not move back to the insertion + * point (that would be wrapping round to the present) and may not + * move back to a NULL entry (that would be going back before '.'). + */ + if (step < 0) + if ((hist == NULL) || (index == vio->hindex)) + return ; + + /* Now, if arrived at the insertion point, this is returning to the + * present, which is fine. + */ + vio->hp = index; + + /* Move back to the start of the current line */ + uty_cli_bol(vio) ; + + /* Get previous line from history buffer and echo that */ + old_len = vio->cl.len ; + qs_copy(&vio->cl, hist) ; + + /* Sort out wiping out any excess and setting the cursor position */ + if (old_len > vio->cl.len) + after = old_len - vio->cl.len ; + else + after = 0 ; + + back = after ; + if (vio->cl.len > vio->cl.cp) + back += (vio->cl.len - vio->cl.cp) ; + + if (vio->cl.len > 0) + uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; + + if (after > 0) + uty_cli_echo_n(vio, telnet_spaces, after) ; + + if (back > 0) + uty_cli_echo_n(vio, telnet_backspaces, back) ; + + return ; +} ; + +/*------------------------------------------------------------------------------ + * Use next history line, if any. + */ +static void +uty_cli_next_line(vty_io vio) +{ + uty_cli_history_use(vio, +1) ; +} + +/*------------------------------------------------------------------------------ + * Use previous history line, if any. + */ +static void +uty_cli_previous_line (vty_io vio) +{ + uty_cli_history_use(vio, -1) ; +} + +/*============================================================================== + * Command Completion and Command Description + * + */ +static void uty_cli_describe_show(vty_io vio, vector describe) ; +static void uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) ; +static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) ; + +static vector uty_cli_cmd_prepare(vty_io vio, int help) ; + +/*------------------------------------------------------------------------------ + * Command completion + */ +static void +uty_cli_complete_command (vty_io vio, enum node_type node) +{ + unsigned i ; + int ret ; + int len ; + int n ; + vector matched ; + vector vline ; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + matched = cmd_complete_command (vline, node, &ret); + cmd_free_strvec (vline); + + /* Show the result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + case CMD_COMPLETE_FULL_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + uty_cli_insert(vio, " ", 1); + break ; + + case CMD_COMPLETE_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + break ; + + case CMD_COMPLETE_LIST_MATCH: + len = 6 ; + for (i = 0; i < vector_end(matched); i++) + { + int sl = strlen((char*)vector_get_item(matched, i)) ; + if (len < sl) + len = sl ; + } ; + + n = vio->width ; + if (n == 0) + n = 60 ; + n = n / (len + 2) ; + if (n == 0) + n = 1 ; + + for (i = 0; i < vector_end(matched); i++) + { + if ((i % n) == 0) + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i)); + } + uty_cli_out_newline(vio) ; + + break; + + case CMD_COMPLETE_ALREADY: + default: + break; + } ; + + cmd_free_strvec(matched); +} ; + +/*------------------------------------------------------------------------------ + * Command Description + */ +static void +uty_cli_describe_command (vty_io vio, enum node_type node) +{ + int ret; + vector vline; + vector describe; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + describe = cmd_describe_command (vline, node, &ret); + cmd_free_strvec (vline); + + uty_cli_out_newline(vio); /* clears cli_drawn */ + + /* Deal with result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + default: + uty_cli_describe_show(vio, describe) ; + break ; + } ; + + if (describe != NULL) + vector_free (describe); +} + +/*------------------------------------------------------------------------------ + * Show the command description. + * + * Generates lines of the form: + * + * word description text + * + * Where the word field is adjusted to suit the longest word, and the + * description text is wrapped if required (if the width of the console is + * known) so that get: + * + * word description .................................. + * .............text + * + * If one of the options is '<cr>', that is always shown last. + */ +static void +uty_cli_describe_show(vty_io vio, vector describe) +{ + unsigned int i, cmd_width, desc_width; + struct desc *desc, *desc_cr ; + + /* Get width of the longest "word" */ + cmd_width = 0; + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + unsigned int len; + + if (desc->cmd[0] == '\0') + continue; + + len = strlen (desc->cmd); + if (desc->cmd[0] == '.') + len--; + + if (cmd_width < len) + cmd_width = len; + } + + /* Set width of description string. */ + desc_width = vio->width - (cmd_width + 6); + + /* Print out description. */ + desc_cr = NULL ; /* put <cr> last if it appears */ + + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + if (desc->cmd[0] == '\0') + continue; + + if (strcmp (desc->cmd, command_cr) == 0) + { + desc_cr = desc; + continue; + } + + uty_cli_describe_fold (vio, cmd_width, desc_width, desc); + } + + if (desc_cr != NULL) + uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); +} ; + +/*------------------------------------------------------------------------------ + * Show one word and the description, folding the description as required. + */ +static void +uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) +{ + char *buf; + const char *cmd, *p; + int pos; + + VTY_ASSERT_LOCKED() ; + + cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; + p = desc->str ; + + /* If have a sensible description width */ + if (desc_width > 20) + { + buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + + while (strlen (p) > desc_width) + { + /* move back to first space */ + for (pos = desc_width; pos > 0; pos--) + if (*(p + pos) == ' ') + break; + + /* if did not find a space, break at width */ + if (pos == 0) + pos = desc_width ; + + strncpy (buf, p, pos); + buf[pos] = '\0'; + uty_cli_describe_line(vio, cmd_width, cmd, buf) ; + + cmd = ""; /* for 2nd and subsequent lines */ + + p += pos ; /* step past what just wrote */ + while (*p == ' ') + ++p ; /* skip spaces */ + } ; + + XFREE (MTYPE_TMP, buf); + } ; + + uty_cli_describe_line(vio, cmd_width, cmd, p) ; +} ; + +/*------------------------------------------------------------------------------ + * Show one description line. + */ +static void +uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) +{ + if (str != NULL) + uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ; + else + uty_cli_out (vio, " %-s", cmd) ; + uty_cli_out_newline(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Prepare "vline" token array for command handler. + * + * For "help" (command completion/description), if the command line is empty, + * or ends in ' ', adds an empty token to the end of the token array. + */ +static vector +uty_cli_cmd_prepare(vty_io vio, int help) +{ + vector vline ; + + vline = cmd_make_strvec(qs_term(&vio->cl)) ; + + /* Note that if there is a vector of tokens, then there is at least one + * token, so can guarantee that vio->cl.len >= 1 ! + */ + if (help) + if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1))) + vline = cmd_add_to_strvec(vline, "") ; + + return vline ; +} ; + +/*============================================================================== + * VTY telnet stuff + */ + +#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ + +static const char* telnet_commands[256] = +{ + [tn_IAC ] = "IAC", + [tn_DONT ] = "DONT", + [tn_DO ] = "DO", + [tn_WONT ] = "WONT", + [tn_WILL ] = "WILL", + [tn_SB ] = "SB", + [tn_GA ] = "GA", + [tn_EL ] = "EL", + [tn_EC ] = "EC", + [tn_AYT ] = "AYT", + [tn_AO ] = "AO", + [tn_IP ] = "IP", + [tn_BREAK] = "BREAK", + [tn_DM ] = "DM", + [tn_NOP ] = "NOP", + [tn_SE ] = "SE", + [tn_EOR ] = "EOR", + [tn_ABORT] = "ABORT", + [tn_SUSP ] = "SUSP", + [tn_EOF ] = "EOF", +} ; + +static const char* telnet_options[256] = +{ + [to_BINARY] = "BINARY", /* 8-bit data path */ + [to_ECHO] = "ECHO", /* echo */ + [to_RCP] = "RCP", /* prepare to reconnect */ + [to_SGA] = "SGA", /* suppress go ahead */ + [to_NAMS] = "NAMS", /* approximate message size */ + [to_STATUS] = "STATUS", /* give status */ + [to_TM] = "TM", /* timing mark */ + [to_RCTE] = "RCTE", /* remote controlled tx and echo */ + [to_NAOL] = "NAOL", /* neg. about output line width */ + [to_NAOP] = "NAOP", /* neg. about output page size */ + [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ + [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ + [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ + [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ + [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ + [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ + [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ + [to_XASCII] = "XASCII", /* extended ascii character set */ + [to_LOGOUT] = "LOGOUT", /* force logout */ + [to_BM] = "BM", /* byte macro */ + [to_DET] = "DET", /* data entry terminal */ + [to_SUPDUP] = "SUPDUP", /* supdup protocol */ + [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ + [to_SNDLOC] = "SNDLOC", /* send location */ + [to_TTYPE] = "TTYPE", /* terminal type */ + [to_EOR] = "EOR", /* end or record */ + [to_TUID] = "TUID", /* TACACS user identification */ + [to_OUTMRK] = "OUTMRK", /* output marking */ + [to_TTYLOC] = "TTYLOC", /* terminal location number */ + [to_3270REGIME] = "3270REGIME", /* 3270 regime */ + [to_X3PAD] = "X3PAD", /* X.3 PAD */ + [to_NAWS] = "NAWS", /* window size */ + [to_TSPEED] = "TSPEED", /* terminal speed */ + [to_LFLOW] = "LFLOW", /* remote flow control */ + [to_LINEMODE] = "LINEMODE", /* Linemode option */ + [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ + [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ + [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ + [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ + [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ + [to_EXOPL] = "EXOPL", /* extended-options-list */ +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as decimal. + */ +static void +uty_cli_out_dec(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "%d ", (int)u) ; +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as hex. + */ +static void +uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "0x%02x ", (unsigned)u) ; +} ; + +/*------------------------------------------------------------------------------ + * Send telnet: "WILL TELOPT_ECHO" + */ +static void +uty_will_echo (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "suppress Go-Ahead" + */ +static void +uty_will_suppress_go_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use linemode" + */ +static void +uty_dont_linemode (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "Use window size" + */ +static void +uty_do_window_size (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use lflow" -- not currently used + */ +static void +uty_dont_lflow_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * The keystroke iac callback function. + * + * This deals with IAC sequences that should be dealt with as soon as they + * are read -- not stored in the keystroke stream for later processing. + */ +extern bool +uty_cli_iac_callback(keystroke_iac_callback_args) +{ + return uty_telnet_command((vty_io)context, stroke, true) ; +} ; + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * May be called during keystroke iac callback, or when processing CLI + * keystrokes. + * + * In particular: get telnet window size. + * + * Returns: true <=> dealt with, for: + * + * * telnet window size. + */ +static bool +uty_telnet_command(vty_io vio, keystroke stroke, bool callback) +{ + uint8_t* p ; + uint8_t o ; + int left ; + bool dealt_with ; + + /* Echo to the other end if required */ + if (TELNET_OPTION_DEBUG) + { + uty_cli_wipe(vio, 0) ; + + p = stroke->buf ; + left = stroke->len ; + + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_commands[*p], *p) ; + ++p ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_options[*p], *p) ; + ++p ; + + if (left > 0) + { + while(left-- > 0) + uty_cli_out_hex(vio, NULL, *p++) ; + + if (stroke->flags & kf_truncated) + uty_cli_out(vio, "... ") ; + + if (!(stroke->flags & kf_broken)) + { + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; + } + } ; + + if (stroke->flags & kf_broken) + uty_cli_out (vio, "BROKEN") ; + + uty_cli_out (vio, "\r\n") ; + } ; + + /* Process the telnet command */ + dealt_with = false ; + + if (stroke->flags != 0) + return dealt_with ; /* go no further if broken */ + + p = stroke->buf ; + left = stroke->len ; + + passert(left >= 1) ; /* must be if not broken ! */ + passert(stroke->value == *p) ; /* or something is wrong */ + + ++p ; /* step past X of IAC X */ + --left ; + + /* Decode the one command that is interesting -- "NAWS" */ + switch (stroke->value) + { + case tn_SB: + passert(left > 0) ; /* or parser failed */ + + o = *p++ ; /* the option byte */ + --left ; + switch(o) + { + case to_NAWS: + if (left != 4) + { + uzlog(NULL, LOG_WARNING, + "RFC 1073 violation detected: telnet NAWS option " + "should send %d characters, but we received %d", + (3 + 4 + 2), (3 + left + 2)) ; + } + else + { + vio->width = *p++ << 8 ; + vio->width += *p++ ; + vio->height = *p++ << 8 ; + vio->height += *p ; + + if (TELNET_OPTION_DEBUG) + uty_cli_out(vio, "TELNET NAWS window size received: " + "width %d, height %d%s", + vio->width, vio->height, telnet_newline) ; + uty_set_height(vio) ; + + dealt_with = true ; + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; + + return dealt_with ; +} ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h new file mode 100644 index 00000000..f532616a --- /dev/null +++ b/lib/vty_cli.h @@ -0,0 +1,49 @@ +/* VTY Command Line Handler + * Copyright (C) 1997 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_CLI_H +#define _ZEBRA_VTY_CLI_H + +#include "vty_io.h" +#include "keystroke.h" + +extern void uty_cli_init(vty_io vio) ; +extern enum vty_readiness uty_cli_start(vty_io vio) ; +extern void uty_cli_close(vty_io vio) ; + +extern enum vty_readiness uty_cli(vty_io vio) ; +extern keystroke_callback uty_cli_iac_callback ; + +extern void uty_cli_hist_add (vty_io vio, const char* cmd_line) ; +extern void uty_cli_enter_more_wait(vty_io vio) ; +extern void uty_cli_exit_more_wait(vty_io vio) ; + +extern void uty_free_host_name(void) ; +extern void uty_check_host_name(void) ; + +extern bool uty_cli_draw_if_required(vty_io vio) ; + +extern void uty_cli_pre_monitor(vty_io vio, size_t len) ; +extern int uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) ; + +#endif /* _ZEBRA_VTY_CLI_H */ diff --git a/lib/vty_io.c b/lib/vty_io.c new file mode 100644 index 00000000..74e32c75 --- /dev/null +++ b/lib/vty_io.c @@ -0,0 +1,2742 @@ +/* VTY IO Functions + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty.h" +#include "vty_io.h" +#include "vty_cli.h" +#include "qstring.h" +#include "keystroke.h" + +#include "memory.h" + +#include "prefix.h" +#include "filter.h" +#include "privs.h" +#include "sockunion.h" +#include "sockopt.h" +#include "network.h" + +#include <arpa/telnet.h> +#include <sys/un.h> /* for VTYSH */ +#include <sys/socket.h> + +#define VTYSH_DEBUG 0 + +/*============================================================================== + * VTY Command Output -- base functions + * + * During command processing the output sent here is held until the command + * completes. + */ + +static int uty_config_write(vty_io vio, bool all) ; + +/*------------------------------------------------------------------------------ + * VTY output function -- cf fprintf + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + */ +extern int +uty_out (struct vty *vty, const char *format, ...) +{ + int result; + VTY_ASSERT_LOCKED() ; + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + return result; +} + +/*------------------------------------------------------------------------------ + * VTY output function -- cf vfprintf + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + * + * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: + * + * * MAY NOT do any command output if !cmd_enabled + * + * * first, the life of a vty is not guaranteed unless cmd_in_progress, + * so should not attempt to use a vty anywhere other than command + * execution. + * + * * second, cmd_out_enabled is false most of the time, and is only + * set true when a command completes, and it is time to write away + * the results. + * + * * all output is placed in the vio->cmd_obuf. When the command completes, + * the contents of the cmd_obuf will be written away -- subject to line + * control. + * + * * output is discarded if the vty is no longer write_open + */ +extern int +uty_vout(struct vty *vty, const char *format, va_list args) +{ + vty_io vio ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + vio = vty->vio ; + + switch (vio->type) + { + case VTY_STDOUT: + case VTY_SHELL: + ret = vprintf (format, args) ; + break ; + + case VTY_STDERR: + ret = vfprintf (stderr, format, args) ; + break ; + + case VTY_CONFIG_WRITE: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) + ret = uty_config_write(vio, false) ; + break ; + + case VTY_TERM: + case VTY_SHELL_SERV: + assert(vio->cmd_in_progress) ; + + if (!vio->sock.write_open) + return 0 ; /* discard output if not open ! */ + + /* fall through.... */ + + case VTY_CONFIG_READ: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + break ; + + default: + zabort("impossible VTY type") ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Clear the contents of the command output FIFO etc. + * + * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc + * flags -- competent parties must deal with those + */ +extern void +uty_out_clear(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + vio_fifo_clear(&vio->cmd_obuf) ; + + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Flush the contents of the command output FIFO to the given file. + * + * Takes no notice of any errors ! + */ +extern void +uty_out_fflush(vty_io vio, FILE* file) +{ + char* src ; + size_t have ; + + VTY_ASSERT_LOCKED() ; + + fflush(file) ; + + while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + { + fwrite(src, 1, have, file) ; + vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + } ; + + fflush(file) ; +} ; + +/*============================================================================== + * The watch dog. + * + * The watch dog starts up every now and checks: + * + * * for changes to the host name, which should be reflected in the + * prompt for any terminals. + * + * * the death watch list + */ + +enum { vty_watch_dog_interval = 5 } ; + +static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ; +static int vty_watch_dog_thread(struct thread *thread) ; + +static void uty_watch_dog_bark(void) ; +static bool uty_death_watch_scan(void) ; + +/*------------------------------------------------------------------------------ + * Start watch dog -- the first time a VTY is created. + */ +extern void +uty_watch_dog_start() +{ + if (vty_cli_nexus) + vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, NULL) ; + + uty_watch_dog_bark() ; /* start up by barking the first time */ +} + +/*------------------------------------------------------------------------------ + * Stop watch dog timer -- at close down. + * + * Final run along the death-watch + * + */ +extern void +uty_watch_dog_stop(void) +{ + if (vty_watch_dog.anon != NULL) + { + if (vty_cli_nexus) + qtimer_free(vty_watch_dog.qnexus) ; + else + thread_cancel(vty_watch_dog.thread) ; + } ; + + uty_death_watch_scan() ; /* scan the death-watch list */ +} + +/*------------------------------------------------------------------------------ + * qnexus watch dog action + */ +static void +vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) +{ + VTY_LOCK() ; + + uty_watch_dog_bark() ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * thread watch dog action + */ +static int +vty_watch_dog_thread(struct thread *thread) +{ + VTY_LOCK() ; + + vty_watch_dog.thread = NULL ; + uty_watch_dog_bark() ; + + VTY_UNLOCK() ; + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Watch dog action + */ +static void +uty_watch_dog_bark(void) +{ + uty_check_host_name() ; /* check for host name change */ + + uty_death_watch_scan() ; /* scan the death-watch list */ + + /* Set timer to go off again later */ + if (vty_cli_nexus) + qtimer_set(vty_watch_dog.qnexus, + qt_add_monotonic(QTIME(vty_watch_dog_interval)), + vty_watch_dog_qnexus) ; + else + { + if (vty_watch_dog.thread != NULL) + thread_cancel (vty_watch_dog.thread); + vty_watch_dog.thread = thread_add_timer (vty_master, + vty_watch_dog_thread, NULL, vty_watch_dog_interval) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Scan the death watch list. + * + * A vty may finally be freed if it is closed and there is no command in + * progress. + */ +static bool +uty_death_watch_scan(void) +{ + vty_io vio ; + vty_io next ; + + next = vio_death_watch ; + while (next != NULL) + { + vio = next ; + next = sdl_next(vio, vio_list) ; + + if (vio->closed && !vio->cmd_in_progress) + { + uty_close(vio) ; /* closes again to ensure that all buffers + are released. */ + + sdl_del(vio_death_watch, vio, vio_list) ; + + XFREE(MTYPE_VTY, vio->vty) ; + XFREE(MTYPE_VTY, vio) ; + } ; + } ; + + return (vio_death_watch == NULL) ; +} ; + +/*============================================================================== + * Prototypes. + */ +static void uty_sock_init_new(vio_sock sock, int fd, void* info) ; +static void uty_sock_half_close(vio_sock sock) ; +static void uty_sock_close(vio_sock sock) ; + +static void vty_read_qnexus (qps_file qf, void* file_info) ; +static void vty_write_qnexus (qps_file qf, void* file_info) ; +static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ; + +static int vty_read_thread (struct thread *thread) ; +static int vty_write_thread (struct thread *thread) ; +static int vty_timer_thread (struct thread *thread) ; + +static void vtysh_read_qnexus (qps_file qf, void* file_info) ; +static int vtysh_read_thread (struct thread *thread) ; + +static enum vty_readiness uty_write(vty_io vio) ; + +/*============================================================================== + * Creation and destruction of VTY objects + */ + +/*------------------------------------------------------------------------------ + * Allocate new vty struct + * + * Allocates and initialises basic vty and vty_io structures, setting the + * given type. + * + * Note that where is not setting up a vty_sock, this *may* be called from + * any thread. + * + * NB: may not create a VTY_CONFIG_WRITE type vty directly + * + * see: vty_open_config_write() and vty_close_config_write() + * + * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. + * (So MUST be in the CLI thread to set those up !) + * + * the sock_fd is ignored for everything else. + * + * Returns: new vty + */ +extern struct vty * +uty_new(enum vty_type type, int sock_fd) +{ + struct vty *vty ; + struct vty_io* vio ; + + VTY_ASSERT_LOCKED() ; + + /* If this is a VTY_TERM or a VTY_SHELL, place */ + switch (type) + { + case VTY_TERM: /* Require fd -- Telnet session */ + case VTY_SHELL_SERV: /* Require fd -- Unix socket */ + assert(sock_fd >= 0) ; + break ; + + case VTY_CONFIG_WRITE: + zabort("may not make a new VTY_CONFIG_WRITE VTY") ; + break ; + + case VTY_CONFIG_READ: + case VTY_STDOUT: + case VTY_STDERR: + case VTY_SHELL: + sock_fd = -1 ; /* No fd -- output to stdout/stderr */ + break ; + + default: + zabort("unknown VTY type") ; + } ; + + /* Basic allocation */ + vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; + + vty->vio = vio ; + vio->vty = vty ; + + /* Zeroising the vty_io structure has set: + * + * name = NULL -- no name, yet + * + * vio_list both pointers NULL + * mon_list both pointers NULL + * + * half_closed = 0 -- NOT half closed (important !) + * closed = 0 -- NOT closed (important !) + * close_reason = NULL -- no reason, yet + * + * real_type = 0 -- not material + * file_fd = 0 -- not material + * file_error = 0 -- not material + * + * key_stream = NULL -- no key stream (always empty, at EOF) + * + * cli_drawn = 0 -- not drawn + * cli_dirty = 0 -- not dirty + * cli_prompt_len = 0 ) + * cli_extra_len = 0 ) not material + * cli_echo_suppress = 0 ) + * + * cli_prompt_node = 0 -- not material + * cli_prompt_set = 0 -- so prompt needs to be constructed + * + * cli_blocked = 0 -- not blocked + * cmd_in_progress = 0 -- no command in progress + * cmd_out_enabled = 0 -- command output is disabled + * cli_wait_more = 0 -- not waiting for response to "--more--" + * + * cli_more_enabled = 0 -- not enabled for "--more--" + * + * cmd_out_done = 0 -- not material + * + * cli_do = 0 == cli_do_nothing + * + * cmd_lc = NULL -- no line control + * + * fail = 0 -- no login failures yet + * + * hist = empty vector + * hp = 0 -- at the beginning + * hindex = 0 -- the beginning + * + * width = 0 -- unknown console width + * height = 0 -- unknown console height + * + * lines = 0 -- no limit + * lines_set = 0 -- no explicit setting + * + * monitor = 0 -- not a monitor + * monitor_busy = 0 -- not a busy monitor + * + * config = 0 -- not holder of "config" mode + */ + confirm(cli_do_nothing == 0) ; + confirm(AUTH_NODE == 0) ; /* default node type */ + + vio->type = type ; + + /* Zeroising the vty structure has set: + * + * node = 0 TODO: something better for node value ???? + * buf = NULL -- no command line, yet + * parsed = NULL -- no parsed command, yet + * lineno = 0 -- nothing read, yet + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet + */ + if (type == VTY_TERM) + vty->newline = "\n" ; /* line control looks after "\r\n" */ + else + vty->newline = "\n" ; + + /* Initialise the vio_sock, */ + uty_sock_init_new(&vio->sock, sock_fd, vio) ; + + /* Make sure all buffers etc. are initialised clean and empty. + * + * Note that no buffers are actually allocated at this stage. + */ + qs_init_new(&vio->cli_prompt_for_node, 0) ; + + qs_init_new(&vio->cl, 0) ; + qs_init_new(&vio->clx, 0) ; + + vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ + + vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; + + /* Place on list of known vio/vty */ + sdl_push(vio_list_base, vio, vio_list) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_TERM -- ie attached to a telnet session. + * + * Returns: new vty + */ +static struct vty * +uty_new_term(int sock_fd, union sockunion *su) +{ + struct vty *vty ; + vty_io vio ; + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (VTY_TERM, sock_fd) ; + vio = vty->vio ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + + /* Set the socket action functions */ + if (vty_cli_nexus) + { + vio->sock.action.read.qnexus = vty_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = vty_timer_qnexus ; + } + else + { + vio->sock.action.read.thread = vty_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = vty_timer_thread ; + } ; + + /* The text form of the address identifies the VTY */ + vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); + + /* Set the initial node */ + if (no_password_check) + { + if (restricted_mode) + vty->node = RESTRICTED_NODE; + else if (host.advanced) + vty->node = ENABLE_NODE; + else + vty->node = VIEW_NODE; + } + else + vty->node = AUTH_NODE; + + /* Pick up current timeout setting */ + vio->sock.v_timeout = vty_timeout_val; + + /* Use global 'lines' setting, as default. May be -1 => unset */ + vio->lines = host.lines ; + + /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ + vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; + uty_set_height(vio) ; /* set initial state */ + + /* Initialise the CLI, ready for start-up messages etc. */ + uty_cli_init(vio) ; + + /* Reject connection if password isn't set, and not "no password" */ + if ((host.password == NULL) && (host.password_encrypt == NULL) + && ! no_password_check) + { + uty_half_close (vio, "Vty password is not set."); + vty = NULL; + } + else + { + /* Say hello to the world. */ + vty_hello (vty); + + if (! no_password_check) + uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, + VTY_NEWLINE, VTY_NEWLINE); + } ; + + /* Now start the CLI and set a suitable state of readiness */ + ready = uty_cli_start(vio) ; + uty_sock_set_readiness(&vio->sock, ready) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. + * + * Returns: new vty + */ +static struct vty * +uty_new_shell_serv(int sock_fd) +{ + struct vty *vty ; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (VTY_SHELL_SERV, sock_fd) ; + vio = vty->vio ; + + /* Set the action functions */ + if (vty_cli_nexus) + { + vio->sock.action.read.qnexus = vtysh_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = NULL ; + } + else + { + vio->sock.action.read.thread = vtysh_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = NULL ; + } ; + + vty->node = VIEW_NODE; + + /* Kick start the CLI etc. */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Set/Clear "monitor" state: + * + * set: if VTY_TERM and not already "monitor" (and write_open !) + * clear: if is "monitor" + */ +extern void +uty_set_monitor(vty_io vio, bool on) +{ + VTY_ASSERT_LOCKED() ; + + if (on && !vio->monitor) + { + if ((vio->type == VTY_TERM) && vio->sock.write_open) + { + vio->monitor = 1 ; + sdl_push(vio_monitors_base, vio, mon_list) ; + } ; + } + else if (!on && vio->monitor) + { + vio->monitor = 0 ; + sdl_del(vio_monitors_base, vio, mon_list) ; + } +} ; + +/*------------------------------------------------------------------------------ + * Return "name" of VTY + * + * For VTY_TERM this is the IP address of the far end of the telnet connection. + */ +extern const char* +uty_get_name(vty_io vio) +{ + return (vio->name != NULL) ? vio->name : "?" ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY for reading. + * + * For VTY_TERM (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input, setting it to "EOF" + * * turns off any monitor status ! + * * drop down to RESTRICTED_NODE + * + * For VTY_SHELL_SERV (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input + * * drop down to RESTRICTED_NODE + * + * In all cases: + * + * * place on death watch + * * set the vty half_closed + * * sets the reason for closing (if any given) + * + * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all + * the buffers, the VTY is closed. + * + * May already have set the vio->close_reason, or can set it now. (Passing a + * NULL reason has no effect on any existing posted reason.) + */ +extern void +uty_half_close (vty_io vio, const char* reason) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return ; + + if (reason != NULL) + vio->close_reason = reason ; + + /* Do the file side of things + * + * Note that half closing the file sets a new timeout, sets read off + * and write on. + */ + uty_sock_half_close(&vio->sock) ; + uty_set_monitor(vio, 0) ; + + /* Discard everything in the keystroke stream and force it to EOF */ + if (vio->key_stream != NULL) + keystroke_stream_set_eof(vio->key_stream) ; + + /* Turn off "--more--" so that all output clears without interruption. + * + * If is sitting on a "--more--" prompt, then exit the wait_more CLI. + */ + vio->cli_more_enabled = 0 ; + + if (vio->cli_more_wait) + uty_cli_exit_more_wait(vio) ; + + /* If a command is not in progress, enable output, which will clear + * the output buffer if there is anything there, plus any close reason, + * and then close. + * + * If command is in progress, then this process will start when it + * completes. + */ + if (!vio->cmd_in_progress) + vio->cmd_out_enabled = 1 ; + + /* Make sure no longer holding the config symbol of power */ + uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + + /* Log closing of VTY_TERM */ + if (vio->type == VTY_TERM) + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; + + /* Move to the death watch list */ + sdl_del(vio_list_base, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; + + vio->half_closed = 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY. + * + * Shuts down everything and discards all buffers etc. etc. + * + * If cmd_in_progress, cannot complete the process -- but sets the closed + * flag. + * + * Can call vty_close() any number of times. + * + * The vty structure is placed on death watch, which will finally free the + * structure once no longer cmd_in_progress. + */ +extern void +uty_close (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + /* Empty all the output buffers */ + vio_fifo_reset_keep(&vio->cli_obuf) ; + vio_fifo_reset_keep(&vio->cmd_obuf) ; + vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; + + /* If not already closed, close. */ + if (!vio->closed) + { + uty_half_close(vio, NULL) ; /* place on death watch -- if not + already done */ + if (vio->type == VTY_TERM) + uty_cli_close(vio) ; /* tell the CLI to stop */ + + vio->closed = 1 ; /* now closed (stop uty_write() + from recursing) */ + + if (vio->sock.write_open) + uty_write(vio) ; /* last gasp attempt */ + + uty_sock_close(&vio->sock) ; + + } ; + + /* Nothing more should happen, so can now release almost everything, + * the exceptions being the things that are related to a cmd_in_progress. + * + * All writing to buffers is suppressed, and as the sock has been closed, + * there will be no more read_ready or write_ready events. + */ + if (vio->name != NULL) + XFREE(MTYPE_VTY_NAME, vio->name) ; + + vio->key_stream = keystroke_stream_free(vio->key_stream) ; + + qs_free_body(&vio->cli_prompt_for_node) ; + qs_free_body(&vio->cl) ; + + { + qstring line ; + while ((line = vector_ream_keep(&vio->hist)) != NULL) + qs_reset_free(line) ; + } ; + + /* The final stage cannot be completed if cmd_in_progress. + * + * The clx is pointed at by vty->buf -- containing the current command. + * + * Once everything is released, can take the vty off death watch, and + * release the vio and the vty. + */ + if (!vio->cmd_in_progress) + { + qs_free_body(&vio->clx) ; + vio->vty->buf = NULL ; + } ; +} ; + +/*============================================================================== + * For writing configuration file by command, temporarily redirect output to + * an actual file. + */ + +/*------------------------------------------------------------------------------ + * Set the given fd as the VTY_FILE output. + */ +extern void +vty_open_config_write(struct vty* vty, int fd) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; + + vio->real_type = vio->type ; + + vio->type = VTY_CONFIG_WRITE ; + vio->file_fd = fd ; + vio->file_error = 0 ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Write away configuration file stuff -- all or just the full lump(s). + * + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see vio->file_error + */ +static int +uty_config_write(vty_io vio, bool all) +{ + int ret ; + + VTY_ASSERT_LOCKED() ; + + if (vio->file_error == 0) + { + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; + + if (ret < 0) + vio->file_error = errno ; + } + else + ret = -1 ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Write away any pending stuff, and return the VTY to normal. + */ +extern int +vty_close_config_write(struct vty* vty) +{ + vty_io vio ; + int err ; + + VTY_LOCK() ; + + vio = vty->vio ; + + assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; + + uty_config_write(vio, true) ; /* write all that is left */ + + err = vio->file_error ; + + vio->type = vio->real_type ; + vio->file_fd = -1 ; + vio->file_error = 0 ; + + VTY_UNLOCK() ; + + return err ; +} ; + +/*============================================================================== + * vio_sock level operations + */ + +/*------------------------------------------------------------------------------ + * Initialise a new vio_sock structure. + * + * Requires that: the vio_sock structure is not currently in use. + * + * if fd >= 0 then: sock is open and ready read and write + * otherwise: sock is not open + * + * there are no errors, yet. + * + * Sets timeout to no timeout at all -- timeout is optional. + * + * NB: MUST be in the CLI thread if the fd is >= 0 ! + */ +static void +uty_sock_init_new(vio_sock sock, int fd, void* info) +{ + VTY_ASSERT_LOCKED() ; + + if (fd >= 0) + VTY_ASSERT_CLI_THREAD() ; + + memset(sock, 0, sizeof(struct vio_sock)) ; + + /* Zeroising the structure has set: + * + * action = all the actions set NULL + * + * error_seen = 0 -- no error, yet + * + * qf = NULL -- no qfile, yet + * t_read = NULL ) no threads, yet + * t_write = NULL ) + * + * v_timeout = 0 -- no timeout set + * timer_runing = 0 -- not running, yet + * t_timer = NULL -- no timer thread, yet + * qtr = NULL -- no qtimer, yet + */ + sock->fd = fd ; + sock->info = info ; + + sock->read_open = (fd >= 0) ; + sock->write_open = (fd >= 0) ; + + if ((fd >= 0) && vty_cli_nexus) + { + sock->qf = qps_file_init_new(NULL, NULL); + qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); + } ; +} ; + +/*------------------------------------------------------------------------------ + * Restart the timer. + * + * If a timeout time is set, then start or restart the timer with that value. + * + * If no timeout time is set, and the timer is running, unset it. + */ +static void +uty_sock_restart_timer(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->v_timeout != 0) + { + assert(sock->action.timer.anon != NULL) ; + + if (vty_cli_nexus) + { + if (sock->qtr == NULL) /* allocate qtr if required */ + sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, sock->info) ; + qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), + sock->action.timer.qnexus) ; + } + else + { + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer); + sock->t_timer = thread_add_timer (vty_master, + sock->action.timer.thread, sock->info, sock->v_timeout) ; + } ; + + sock->timer_running = 1 ; + } + else if (sock->timer_running) + { + if (vty_cli_nexus) + { + if (sock->qtr != NULL) + qtimer_unset(sock->qtr) ; + } + else + { + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer) ; + } ; + + sock->timer_running = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set read on/off + * + * Returns: the on/off state set + */ +static bool +uty_sock_set_read(vio_sock sock, bool on) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->fd < 0) + return 0 ; + + if (on) + { + assert(sock->action.read.anon != NULL) ; + + if (vty_cli_nexus) + qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; + else + { + if (sock->t_read != NULL) + thread_cancel(sock->t_read) ; + + sock->t_read = thread_add_read(vty_master, + sock->action.read.thread, sock->info, sock->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + qps_disable_modes(sock->qf, qps_read_mbit) ; + else + { + if (sock->t_read != NULL) + thread_cancel (sock->t_read) ; + } ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set write on/off + * + * Returns: the on/off state set + */ +static bool +uty_sock_set_write(vio_sock sock, bool on) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->fd < 0) + return 0 ; + + if (on) + { + assert(sock->action.write.anon != NULL) ; + + if (vty_cli_nexus) + qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; + else + { + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; + + sock->t_write = thread_add_write(vty_master, + sock->action.write.thread, sock->info, sock->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + qps_disable_modes(sock->qf, qps_write_mbit) ; + else + { + if (sock->t_write != NULL) + thread_cancel (sock->t_write) ; + } ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set read/write readiness -- for VTY_TERM + * + * Note that for VTY_TERM, set only one of read or write, and sets write for + * preference. + */ +extern void +uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + uty_sock_set_read(sock, (ready == read_ready)) ; + uty_sock_set_write(sock, (ready >= write_ready)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a new timer value. + */ +extern void +uty_sock_set_timer(vio_sock sock, unsigned long timeout) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + sock->v_timeout = timeout ; + if (sock->timer_running) + uty_sock_restart_timer(sock) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vty sock for reading. + * + * Sets timer to timeout for clearing any pending output. + * + * NB: if there is a socket, MUST be in the CLI thread + */ +static void +uty_sock_half_close(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + + sock->read_open = 0 ; /* make sure */ + + if (sock->fd < 0) + return ; /* nothing more if no socket */ + + VTY_ASSERT_CLI_THREAD() ; + + shutdown(sock->fd, SHUT_RD) ; /* actual half close */ + + uty_sock_set_read(sock, off) ; + uty_sock_set_write(sock, on) ; + sock->v_timeout = 30 ; /* for output to clear */ + uty_sock_restart_timer(sock) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vio_sock, completely -- shut down any timer. + * + * Structure is cleared of everything except the last error ! + * + * NB: if there is a socket, MUST be in the CLI thread + */ +static void +uty_sock_close(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + + sock->read_open = 0 ; /* make sure */ + sock->write_open = 0 ; + + if (sock->fd < 0) + { + assert( (sock->qf == NULL) + && (sock->qtr == NULL) + && (sock->t_read == NULL) + && (sock->t_write == NULL) + && (sock->t_timer == NULL) ) ; + return ; /* no more to be done here */ + } ; + + VTY_ASSERT_CLI_THREAD() ; + close(sock->fd) ; + + if (vty_cli_nexus) + { + assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; + qps_remove_file(sock->qf) ; + qps_file_free(sock->qf) ; + sock->qf = NULL ; + } ; + + sock->fd = -1 ; + + if (sock->t_read != NULL) + thread_cancel(sock->t_read) ; + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; + + sock->t_read = NULL ; + sock->t_write = NULL ; + + sock->info = NULL ; + sock->action.read.anon = NULL ; + sock->action.write.anon = NULL ; + sock->action.timer.anon = NULL ; + + if (sock->qtr != NULL) + qtimer_free(sock->qtr) ; + if (sock->t_timer != NULL) + thread_cancel(sock->t_timer) ; + + sock->v_timeout = 0 ; + sock->qtr = NULL ; + sock->t_timer = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Dealing with an I/O error on VTY socket + * + * If this is the first error for this VTY, produce suitable log message. + * + * If is a "monitor", turn that off, *before* issuing log message. + */ +static int +uty_sock_error(vty_io vio, const char* what) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* can no longer be a monitor ! *before* any logging ! */ + uty_set_monitor(vio, 0) ; + + /* if this is the first error, log it */ + if (vio->sock.error_seen == 0) + { + const char* type ; + switch (vio->type) + { + case VTY_TERM: + type = "VTY Terminal" ; + break ; + case VTY_SHELL_SERV: + type = "VTY Shell Server" ; + break ; + default: + zabort("unknown VTY type for uty_sock_error()") ; + } ; + + vio->sock.error_seen = errno ; + uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ; + } ; + + return -1 ; +} ; + +/*============================================================================== + * Readiness and the VTY_TERM type VTY. + * + * For VTY_TERM the driving force is write ready. This is used to prompt the + * VTY_TERM when there is outstanding output (obviously), but also if there + * is buffered input in the keystroke stream. + * + * The VTY_TERM uses read ready only when it doesn't set write ready. Does + * not set both at once. + * + * So there is only one, common, uty_ready function, which: + * + * 1. attempts to clear any output it can. + * + * The state of the output affects the CLI, so must always do this before + * before invoking the CLI. + * + * If this write enters the "--more--" state, then will have tried to + * write away the prompt. + * + * 2. invokes the CLI + * + * Which will do either the standard CLI stuff or the special "--more--" + * stuff. + * + * 3. attempts to write any output there now is. + * + * If the CLI generated new output, as much as possible is written away + * now. + * + * If this write enters the "--more--" state, then it returns now_ready, + * if the prompt was written away, which loops back to the CLI. + * + * Note that this is arranging: + * + * a. to write away the "--more--" prompt as soon as the tranche of output to + * which it refers, completes + * + * b. to enter the cli_more_wait CLI for the first time immediately after the + * "--more--" prompt is written away. + * + * The loop limits itself to one trache of command output each time. + * + * Resets the timer because something happened. + */ +static void +uty_ready(vty_io vio) +{ + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + + vio->cmd_out_done = 0 ; /* not done any command output yet */ + + uty_write(vio) ; /* try to clear outstanding stuff */ + do + { + ready = uty_cli(vio) ; /* do any CLI work... */ + ready |= uty_write(vio) ; /* ...and any output that generates */ + } while (ready >= now_ready) ; + + uty_sock_set_readiness(&vio->sock, ready) ; + uty_sock_restart_timer(&vio->sock) ; +} ; + +/*============================================================================== + * Reading from VTY_TERM. + * + * The select/pselect call-back ends up in uty_read_ready(). + * + * Note that uty_write_ready() also calls uty_read_ready, in order to kick the + * current CLI. + */ + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read -> kicking CLI + */ +static void +vty_read_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + uty_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: ready to read -> kicking CLI + */ +static int +vty_read_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_read = NULL ; /* implicitly */ + uty_ready(vio); + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the keystroke stream + * + * Steal keystroke if required -- see keystroke_input() + * + * Returns: 0 => nothing available + * > 0 => read at least one byte + * -1 => EOF (or not open, or failed) + */ +extern int +uty_read (vty_io vio, keystroke steal) +{ + unsigned char buf[500] ; + int get ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open */ + + get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; + if (get >= 0) + keystroke_input(vio->key_stream, buf, get, steal) ; + else if (get < 0) + { + if (get == -1) + uty_sock_error(vio, "read") ; + + vio->sock.read_open = 0 ; + keystroke_input(vio->key_stream, NULL, 0, steal) ; + + get = -1 ; + } ; + + return get ; +} ; + +/*============================================================================== + * The write sock action for VTY_TERM type VTY + * + * There are two sets of buffering: + * + * cli -- command line -- which reflects the status of the command line + * + * cmd -- command output -- which is written to the file only while + * cmd_out_enabled. + * + * The cli output takes precedence. + * + * Output of command stuff is subject to line_control, and may go through the + * "--more--" mechanism. + */ + +static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; +static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to write -> try to empty buffers + */ +static void +vty_write_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info ; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + uty_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- thread: ready to write -> try to empty buffers + */ +static int +vty_write_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_write = NULL; /* implicitly */ + uty_ready(vio) ; + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Write as much as possible of what there is. + * + * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, + * empty. + * + * Note that if !write_open, or becomes !write_open, then the FIFOs are empty + * and all output instantly successful. + * + * Sets write on if prevented from writing everything available for output + * by write() threatening to block. + * + * Returns: write_ready if should now set write on + * now_ready if should loop back and try again + * not_ready otherwise + */ +static enum vty_readiness +uty_write(vty_io vio) +{ + int ret ; + + VTY_ASSERT_LOCKED() ; + + ret = -1 ; + while (vio->sock.write_open) + { + /* Any outstanding line control output takes precedence */ + if (vio->cmd_lc != NULL) + { + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + if (ret != 0) + break ; + } + + /* Next: empty out the cli output */ + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret != 0) + break ; + + /* Finished now if not allowed to progress the command stuff */ + if (!vio->cmd_out_enabled) + return not_ready ; /* done all can do */ + + /* Last: if there is something in the command buffer, do that */ + if (!vio_fifo_empty(&vio->cmd_obuf)) + { + if (vio->cmd_out_done) + break ; /* ...but not if done once */ + + vio->cmd_out_done = 1 ; /* done this once */ + + assert(!vio->cli_more_wait) ; + + if (vio->cmd_lc != NULL) + ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + else + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; + + /* If moved into "--more--" state@ + * + * * the "--more--" prompt is ready to be written, so do that now + * + * * if that completes, then want to run the CLI *now* to perform the + * first stage of the "--more--" process. + */ + if (vio->cli_more_wait) + { + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret == 0) + return now_ready ; + } ; + + if (ret != 0) + break ; + } + + /* Exciting stuff: there is nothing left to output... + * + * ... watch out for half closed state. + */ + if (vio->half_closed) + { + if (vio->close_reason != NULL) + { + vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ + + struct vty* vty = vio->vty ; + if (vio->cli_drawn || vio->cli_dirty) + vty_out(vty, VTY_NEWLINE) ; + vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; + + vio->cmd_in_progress = 0 ; + + vio->close_reason = NULL ; /* MUST discard now... */ + continue ; /* ... and write away */ + } ; + + if (!vio->closed) /* avoid recursion */ + uty_close(vio) ; + + return not_ready ; /* it's all over */ + } ; + + /* For VTY_TERM: if the command line is not drawn, now is a good + * time to do that. + */ + if (vio->type == VTY_TERM) + if (uty_cli_draw_if_required(vio)) + continue ; /* do that now. */ + + /* There really is nothing left to output */ + return not_ready ; + } ; + + /* Arrives here if there is more to do, or failed (or was !write_open) */ + + if (ret >= 0) + return write_ready ; + + /* If is write_open, then report the error + * + * If still read_open, let the reader pick up and report the error, when it + * has finished anything it has buffered. + */ + if (vio->sock.write_open) + { + if (!vio->sock.read_open) + uty_sock_error(vio, "write") ; + + vio->sock.write_open = 0 ; /* crash close write */ + } ; + + /* For whatever reason, is no longer write_open -- clear all buffers. + */ + vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ + uty_out_clear(vio) ; /* throw away cmd stuff */ + + vio->close_reason = NULL ; /* too late for this */ + + return not_ready ; /* NB: NOT blocked by I/O */ +} ; + +/*------------------------------------------------------------------------------ + * Write as much as possible -- for "monitor" output. + * + * Outputs only: + * + * a. outstanding line control stuff. + * + * b. contents of CLI buffer + * + * And: + * + * a. does not report any errors. + * + * b. does not change anything except the state of the buffers. + * + * In particular, for the qpthreaded world, does not attempt to change + * the state of the qfile or any other "thread private" structures. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed (or !write_open) + */ +static int +uty_write_monitor(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (!vio->sock.write_open) + return -1 ; + + if (vio->cmd_lc != NULL) + { + int ret ; + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + + if (ret != 0) + return ret ; + } ; + + return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; +} ; + +/*------------------------------------------------------------------------------ + * Write the given FIFO to output -- subject to possible line control. + * + * Note that even if no "--more--" is set, will have set some height, so + * that does not attempt to empty the FIFO completely all in one go. + * + * If the line control becomes "paused", it is time to enter "--more--" state + * -- unless the FIFO is empty (or "--more--" is not enabled). + * + * NB: expects that the sock is write_open + * + * Returns: > 0 => blocked or completed one tranche + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +{ + int ret ; + char* src ; + size_t have ; + + /* Collect another line_control height's worth of output. + * + * Expect the line control to be empty at this point, but it does not have + * to be. + */ + vio_lc_set_pause(lc) ; /* clears lc->paused */ + + src = vio_fifo_get_rdr(vf, &have) ; + + while ((src != NULL) && (!lc->paused)) + { + size_t take ; + take = vio_lc_append(lc, src, have) ; + src = vio_fifo_step_rdr(vf, &have, take) ; + } ; + + vio->cli_dirty = (lc->col != 0) ; + + /* Write the contents of the line control */ + ret = uty_write_lc(vio, vf, lc) ; + + if (ret < 0) + return ret ; /* give up now if failed. */ + + if ((ret == 0) && vio_fifo_empty(vf)) + return 0 ; /* FIFO and line control empty */ + + /* If should now do "--more--", now is the time to prepare for that. + * + * Entering more state issues a new prompt in the CLI buffer, which can + * be written once line control write completes. + * + * The "--more--" cli will not do anything until the CLI buffer has + * cleared. + */ + if (lc->paused && vio->cli_more_enabled) + uty_cli_enter_more_wait(vio) ; + + return 1 ; /* FIFO or line control, not empty */ +} ; + +/*------------------------------------------------------------------------------ + * Write contents of line control (if any). + * + * NB: expects that the sock is write_open + * + * NB: does nothing other than write() and buffer management. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +{ + int ret ; + + ret = vio_lc_write_nb(vio->sock.fd, lc) ; + + if (ret <= 0) + vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Start command output -- clears down the line control. + * + * Requires that that current line is empty -- restarts the line control + * on the basis that is at column 0. + */ +extern void +uty_cmd_output_start(vty_io vio) +{ + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the effective height for line control (if any) + * + * If using line_control, may enable the "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. + */ +extern void +uty_set_height(vty_io vio) +{ + bool on ; + + on = 0 ; /* default state */ + + if ((vio->cmd_lc != NULL) && !vio->half_closed) + { + int height ; + + height = 0 ; /* default state */ + + if ((vio->width) != 0) + { + /* If window size is known, use lines or given height */ + if (vio->lines >= 0) + height = vio->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = vio->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (vio->lines_set) + height = vio->lines ; + } ; + + if (height > 0) + on = 1 ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(vio->cmd_lc, vio->width, height) ; + } ; + + vio->cli_more_enabled = on ; +} ; + +/*============================================================================== + * Timer for VTY_TERM (and VTY_SHELL_SERV). + */ + +/*------------------------------------------------------------------------------ + * Timer has expired. + * + * If half_closed, then this is curtains -- have waited long enough ! + * + * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + */ +static void +uty_timer_expired (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return uty_close(vio) ; /* curtains */ + + uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ + } ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: deal with timer timeout. + */ +static void +vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) +{ + vty_io vio = timer_info ; + + VTY_LOCK() ; + + uty_timer_expired(vio); + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- thread: deal with timer timeout. + */ +static int +vty_timer_thread (struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + vio->sock.t_timer = NULL ; /* implicitly */ + + uty_timer_expired(vio) ; + + VTY_UNLOCK() ; + return 0; +} + +/*============================================================================== + * VTY Listener(s) + * + * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. + */ + +typedef struct vty_listener* vty_listener ; + +struct vty_listener +{ + vty_listener next ; /* ssl type list */ + + enum vty_type type ; + + struct vio_sock sock ; +}; + +/* List of listeners so can tidy up. */ +static vty_listener vty_listeners_list = NULL ; + +/* Prototypes for listener stuff */ +static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ; +static int uty_serv_sock(const char* addr, unsigned short port) ; +static int uty_serv_sock_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) ; +static int uty_serv_vtysh(const char *path) ; +static int vty_accept_thread(struct thread *thread) ; +static void vty_accept_qnexus(qps_file qf, void* listener) ; +static int uty_accept(vty_listener listener, int listen_sock) ; +static int uty_accept_term(vty_listener listener) ; +static int uty_accept_shell_serv (vty_listener listener) ; + +static void uty_serv_start_listener(int fd, enum vty_type type) ; + +/*------------------------------------------------------------------------------ + * If possible, will use getaddrinfo() to find all the things to listen on. + */ + +#if defined(HAVE_IPV6) && !defined(NRL) +# define VTY_USE_ADDRINFO 1 +#else +# define VTY_USE_ADDRINFO 0 +#endif + +/*------------------------------------------------------------------------------ + * Open VTY listener(s) + * + * addr -- address ) to listen for VTY_TERM connections + * port -- port ) + * path -- path for VTYSH connections -- if VTYSH_ENABLED + */ +extern void +uty_open_listeners(const char *addr, unsigned short port, const char *path) +{ + VTY_ASSERT_LOCKED() ; + + /* If port is set to 0, do not listen on TCP/IP at all! */ + if (port) + { + int n ; + + if (VTY_USE_ADDRINFO) + n = uty_serv_sock_addrinfo(addr, port); + else + n = uty_serv_sock(addr, port); + + if (n == 0) + uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; + } + + /* If want to listen for vtysh, set up listener now */ + if (VTYSH_ENABLED && (path != NULL)) + uty_serv_vtysh(path) ; +} ; + +/*------------------------------------------------------------------------------ + * Close VTY listener + * + * addr -- address ) to listen for VTY_TERM connections + * port -- port ) + * path -- path for VTYSH connections -- if VTYSH_ENABLED + */ +extern void +uty_close_listeners(void) +{ + vty_listener listener ; + + VTY_ASSERT_LOCKED() ; + + while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) + { + uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ + XFREE(MTYPE_VTY, listener) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERM -- using getaddrinfo(). + * + * Returns: number of listeners successfully opened. + */ +static int +uty_serv_sock_addrinfo (const char *hostname, unsigned short port) +{ +#if VTY_USE_ADDRINFO + +# ifndef HAVE_IPV6 +# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? +# endif + + int ret; + int n ; + struct addrinfo req; + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + char port_str[16]; + + VTY_ASSERT_LOCKED() ; + + /* Want to listen, TCP-wise, on all available address families, on the + * given port. + */ + memset (&req, 0, sizeof (struct addrinfo)); + req.ai_flags = AI_PASSIVE; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + snprintf(port_str, sizeof(port_str), "%d", port); + + ret = getaddrinfo (hostname, port_str, &req, &ainfo); + + if (ret != 0) + { + fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); + exit (1); + } + + /* Open up sockets on all AF_INET and AF_INET6 addresses */ + ainfo_save = ainfo; + + n = 0 ; + do + { + if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) + continue; + + assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; + + ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol, ainfo->ai_addr, port) ; + if (ret >= 0) + ++n ; + } + while ((ainfo = ainfo->ai_next) != NULL); + + freeaddrinfo (ainfo_save); + + return n ; + +#else + zabort("uty_serv_sock_addrinfo not implemented") ; +#endif /* VTY_USE_ADDRINFO */ +} + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! + * + * Returns: number of listeners successfully opened. + */ +static int +uty_serv_sock(const char* addr, unsigned short port) +{ + int ret; + int n ; + union sockunion su_addr ; + struct sockaddr* sa ; + + VTY_ASSERT_LOCKED() ; + + n = 0 ; /* nothing opened yet */ + + /* If have an address, see what kind and whether valid */ + sa = NULL ; + + if (addr != NULL) + { + ret = str2sockunion (addr, &su_addr) ; + if (ret == 0) + sa = &su_addr.sa ; + else + uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); + } ; + + /* Try for AF_INET */ + ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ + +#if HAVE_IPV6 + /* Try for AF_INET6 */ + ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ +#endif + + /* If not used the address... something wrong */ + if (sa != NULL) + zlog_err("could not use address %s, to listen for VTY", addr); + + /* Done */ + return n ; +} + +/*------------------------------------------------------------------------------ + * Open a VTY_TERM listener socket. + * + * The sockaddr 'sa' may be NULL or of a different address family, in which + * case "any" address is used. + * + * If the sockaddr 'sa' is used, only the address portion is used. + * + * Returns: < 0 => failed + * == 0 => OK -- did not use the sockaddr 'sa'. + * > 1 => OK -- and did use the sockaddr 'sa' + */ +static int +uty_serv_sock_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) +{ + union sockunion su[1] ; + int sock_fd ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + /* Is there an address and is it for this family ? */ + if ((sa != NULL) || (sa->sa_family == family)) + /* Set up sockunion containing required family and address */ + sockunion_new_sockaddr(su, sa) ; + else + { + /* no address or wrong family -- set up empty sockunion of + * required family */ + sockunion_init_new(su, family) ; + sa = NULL ; + } ; + + /* Open the socket and set its properties */ + sock_fd = sockunion_socket(su, type, protocol) ; + if (sock_fd < 0) + return -1 ; + + ret = setsockopt_reuseaddr (sock_fd); + + if (ret >= 0) + ret = setsockopt_reuseport (sock_fd); + + if (ret >= 0) + ret = set_nonblocking(sock_fd); + +#ifdef HAVE_IPV6 + /* Want only IPv6 on AF_INET6 socket (not mapped addresses) + * + * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the + * attempt to bind to :: after binding to 0.0.0.0. + */ + if ((ret >= 0) && (family == AF_INET6)) + ret = setsockopt_ipv6_v6only(sock_fd) ; +#endif + + if (ret >= 0) + ret = sockunion_bind (sock_fd, su, port, (sa == NULL)) ; + + if (ret >= 0) + ret = sockunion_listen (sock_fd, 3); + + if (ret < 0) + { + close (sock_fd); + return -1 ; + } + + /* Socket is open -- set VTY Term listener going */ + uty_serv_start_listener(sock_fd, VTY_TERM) ; + + /* Return OK and signal whether used address or not */ + return (sa != NULL) ? 1 : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Open a VTY_SHEL_SERV listener socket (UNIX domain). + * + * Returns: < 0 => failed + * >= 0 => OK + */ +static int +uty_serv_vtysh(const char *path) +{ + int ret; + int sock, sa_len, path_len ; + struct sockaddr_un sa_un ; + mode_t old_mask; + struct zprivs_ids_t ids; + + VTY_ASSERT_LOCKED() ; + + /* worry about the path length */ + path_len = strlen(path) + 1 ; + if (path_len >= (int)sizeof(sa_un.sun_path)) + { + uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); + return -1 ; + } ; + + /* First of all, unlink existing socket */ + unlink (path); + + /* Make UNIX domain socket. */ + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + { + uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", + errtoa(errno, 0).str) ; + return -1 ; + } + + /* Bind to the required path */ + memset (&sa_un, 0, sizeof(sa_un)); + sa_un.sun_family = AF_UNIX; + strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); + + sa_len = SUN_LEN(&sa_un) ; + +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + sa_un.sun_len = sa_len ; +#endif + + old_mask = umask (0007); + + ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; + if (ret < 0) + uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); + + if (ret >= 0) + ret = set_nonblocking(sock); + + if (ret >= 0) + { + ret = listen (sock, 5); + if (ret < 0) + uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, + errtoa(errno, 0).str) ; + } ; + + zprivs_get_ids(&ids); + + if (ids.gid_vty > 0) + { + /* set group of socket */ + if ( chown (path, -1, ids.gid_vty) ) + uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + errtoa(errno, 0).str) ; + } + + umask (old_mask); + + /* Give up now if failed along the way */ + if (ret < 0) + { + close (sock) ; + return -1 ; + } ; + + /* Socket is open -- set VTY Term listener going */ + uty_serv_start_listener(sock, VTY_SHELL_SERV) ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Socket is open -- set a VTY listener going + * + * Note that the vyt_listener structure is passed to the accept action function. + */ +static void +uty_serv_start_listener(int fd, enum vty_type type) +{ + vty_listener listener ; + + listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); + + ssl_push(vty_listeners_list, listener, next) ; + uty_sock_init_new(&listener->sock, fd, listener) ; + + listener->type = type ; + + if (vty_cli_nexus) + listener->sock.action.read.qnexus = vty_accept_qnexus ; + else + listener->sock.action.read.thread = vty_accept_thread ; + + uty_sock_set_read(&listener->sock, on) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action for the thread world -- create and dispatch VTY + */ +static int +vty_accept_thread(struct thread *thread) +{ + vty_listener listener = THREAD_ARG(thread) ; + int result ; + + VTY_LOCK() ; + + result = uty_accept(listener, THREAD_FD(thread)); + + uty_sock_set_read(&listener->sock, on) ; + + VTY_UNLOCK() ; + return result ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action for the qnexus world -- create and dispatch VTY + */ +static void +vty_accept_qnexus(qps_file qf, void* listener) +{ + VTY_LOCK() ; + + uty_accept(listener, qf->fd); + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV + */ +static int +uty_accept(vty_listener listener, int listen_sock) +{ + VTY_ASSERT_LOCKED() ; + + assert(listener->sock.fd == listen_sock) ; + + switch (listener->type) + { + case VTY_TERM: + return uty_accept_term(listener) ; + + case VTY_SHELL_SERV: + return uty_accept_shell_serv(listener) ; + + default: + zabort("unknown vty type") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_TERM + */ +static int +uty_accept_term(vty_listener listener) +{ + int sock_fd; + union sockunion su; + int ret; + unsigned int on; + struct prefix *p ; + + VTY_ASSERT_LOCKED() ; + + /* We can handle IPv4 or IPv6 socket. */ + sockunion_init_new(&su, AF_UNSPEC) ; + + sock_fd = sockunion_accept (listener->sock.fd, &su); + + if (sock_fd < 0) + { + if (sock_fd == -1) + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", + errtoa(errno, 0).str) ; + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return -1 ; + } ; + + /* New socket is open... worry about access lists */ + p = sockunion2hostprefix (&su); + ret = 0 ; /* so far, so good */ + + if ((p->family == AF_INET) && vty_accesslist_name) + { + /* VTY's accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } + +#ifdef HAVE_IPV6 + if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) + { + /* VTY's ipv6 accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } +#endif /* HAVE_IPV6 */ + + prefix_free (p); + + if (ret != 0) + { + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; + close (sock_fd); + return 0; + } ; + + /* Final options (optional) */ + on = 1 ; + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, + (void*)&on, sizeof (on)); + if (ret < 0) + uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", + sock_fd, errtoa(errno, 0).str) ; + + /* All set -- create the VTY_TERM */ + uty_new_term(sock_fd, &su); + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, + sock_fd) ; + + return 0; +} + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_SHELL_SERV + */ +static int +uty_accept_shell_serv (vty_listener listener) +{ + int sock_fd ; + int ret ; + int client_len ; + struct sockaddr_un client ; + + VTY_ASSERT_LOCKED() ; + + client_len = sizeof(client); + memset (&client, 0, client_len); + + sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, + (socklen_t *) &client_len) ; + + if (sock_fd < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + errtoa(errno, 0).str) ; + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return -1 ; + } ; + + /* All set -- create the VTY_SHELL_SERV */ + if (VTYSH_DEBUG) + printf ("VTY shell accept\n"); + + uty_new_shell_serv(sock_fd) ; + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); + return 0; +} + +/*============================================================================== + * Reading from the VTY_SHELL_SERV type sock. + * + * The select/pselect call-back ends up in utysh_read_ready(). + */ + +/*------------------------------------------------------------------------------ + * Ready to read -> kicking the "SHELL_SERV CLI" + * + * End up here when there is something ready to be read. + * + * Will also end up here if an error has occurred, the other end has closed, + * this end has half closed, etc. This fact is used to kick the CLI even when + * there is no data to be read. + * + * Note that nothing is actually read here -- reading is done in the CLI itself, + * if required. + * + * The CLI decides whether to re-enable read, or enable write, or both. + */ +static void +utysh_read_ready(vty_io vio) +{ + uty_sock_set_read(&vio->sock, off) ; + + /* TODO: need minimal "CLI" for VTY_SHELL_SERV + * NB: when output from command is flushed out, must append the + * following four bytes: '\0' '\0' '\0' <ret> + * Where <ret> is the command return code. + */ +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" + */ +static void +vtysh_read_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + utysh_read_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" + */ +static int +vtysh_read_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_read = NULL ; /* implicitly */ + utysh_read_ready(vio); + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the command line buffer + * + * Lines coming in are terminated by '\0'. + * + * Assumes that the incoming command line is empty or otherwise incomplete. + * + * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping + * when get '\0' or empties the "buf". + * + * When empties "buf", reads a lump from the sock. + * + * Returns: 0 => command line is incomplete + * 1 => have a complete command line + * -1 => EOF (or not open, or failed) + */ +extern int +utysh_read (vty_io vio, qstring cl, qstring buf) +{ + int get ; + char* cp ; + char* ep ; + size_t have ; + + while (1) + { + /* process what there is in the buffer */ + if (buf->len > buf->cp) + { + cp = qs_cp_char(buf) ; + ep = qs_ep_char(buf) ; + have = ep - cp ; + + ep = memchr(cp, '\0', have) ; + if (ep != NULL) + have = ep - cp ; /* have upto, but excluding '\0' */ + + if (have > 0) /* take what have */ + { + qs_insert(cl, cp, have) ; + cl->cp += have ; + buf->cp += have ; + } ; + + if (ep != NULL) /* if found '\0' */ + { + qs_term(cl) ; /* '\0' terminate */ + ++buf->cp ; /* step past it */ + return 1 ; /* have a complete line <<<<<<<<<<<<< */ + } + } ; + + /* buffer is empty -- try and get some more stuff */ + assert(buf->len == buf->cp) ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ + + qs_need(buf, 500) ; /* need a reasonable lump */ + qs_clear(buf) ; /* set cp = len = 0 */ + + get = read_nb(vio->sock.fd, buf->body, buf->size) ; + if (get > 0) + buf->len = get ; + else if (get == 0) + return 0 ; /* have an incomplete line <<<<<<<<<<<< */ + else + { + if (get == -1) + uty_sock_error(vio, "read") ; + + vio->sock.read_open = 0 ; + + return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ + } ; + } ; +} ; + +/*============================================================================== + * Output to vty which are set to "monitor". + * + * This is VERY TRICKY. + * + * If not running qpthreaded, then the objective is to get the message away + * immediately -- do not wish it to be delayed in any way by the thread + * system. + * + * So proceed as follows: + * + * a. wipe command line -- which adds output to the CLI buffer + * + * b. write the CLI buffer to the sock and any outstanding line control. + * + * c. write the monitor output. + * + * If that does not complete, put the tail end to the CLI buffer. + * + * d. restore any command line -- which adds output to the CLI buffer + * + * e. write the CLI buffer to the sock + * + * If that all succeeds, nothing has changed as far as the VTY stuff is + * concerned -- except that possibly some CLI output was sent before it got + * round to it. + * + * Note that step (b) will deal with any output hanging around from an + * earlier step (e). If cannot complete that, then does not add fuel to the + * fire -- but the message will be discarded. + * + * If that fails, or does not complete, then can set write on, to signal that + * there is some output in the CLI buffer that needs to be sent, or some + * error to be dealt with. + * + * The output should be tidy. + * + * To cut down the clutter, step (d) is performed only if the command line + * is not empty (or if in cli_more_wait). Once a the user has started to enter + * a command, the prompt and the command will remain visible. + * + * When logging an I/O error for a vty that happens to be a monitor, the + * monitor-ness has already been turned off. The monitor output code does not + * attempt to log any errors, sets write on so that the error will be picked + * up that way. + * + * However, in the event of an assertion failure, it is possible that an + * assertion will fail inside the monitor output. The monitor_busy flag + * prevents disaster. It is also left set if I/O fails in monitor output, so + * will not try to use the monitor again. + * + * Note that an assertion which is false for all vty monitors will recurse + * through all the monitors, setting each one busy, in turn ! + * + + + * TODO: sort out write on in the qpthreads world ?? + * + * The problem is that the qpselect structure is designed to be accessed ONLY + * within the thread to which it belongs. This makes it impossible for the + * monitor output to set/clear read/write on the vty sock... so some way + * around this is required. + */ + +/*------------------------------------------------------------------------------ + * Output logging information to all vty which are set to "monitor". + */ +extern void +uty_log(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + vio = sdl_head(vio_monitors_base) ; + + if (vio == NULL) + return ; /* go no further if no "monitor" vtys */ + + /* Prepare line for output. */ + uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ + + /* write to all known "monitor" vty + * + */ + while (vio != NULL) + { + if (!vio->monitor_busy) + { + int ret ; + + vio->monitor_busy = 1 ; /* close the door */ + + uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ + + ret = uty_write_monitor(vio) ; + if (ret == 0) + { + ret = write_nb(vio->sock.fd, ll->line, ll->len) ; + + if (ret >= 0) + { + ret = uty_cli_post_monitor(vio, ll->line + ret, + ll->len - ret) ; + if (ret > 0) + ret = uty_write_monitor(vio) ; + } ; + } ; + + if (ret != 0) + /* need to prod */ ; + + if (ret >= 0) + vio->monitor_busy = 0 ; + } ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Async-signal-safe version of vty_log for fixed strings. + * + * This is last gasp operation. + */ +void +vty_log_fixed (const char *buf, size_t len) +{ + vty_io vio ; + + /* Write to all known "monitor" vty + * + * Forget all the niceties -- about to die in any case. + */ + vio = sdl_head(vio_monitors_base) ; + while (vio != NULL) + { + write(vio->sock.fd, buf, len) ; + write(vio->sock.fd, "\r\n", 2) ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; diff --git a/lib/vty_io.h b/lib/vty_io.h new file mode 100644 index 00000000..19689853 --- /dev/null +++ b/lib/vty_io.h @@ -0,0 +1,310 @@ +/* VTY IO Structure and Functions -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra 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, or (at your option) any + * later version. + * + * GNU Zebra 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. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_H +#define _ZEBRA_VTY_IO_H + +#include <stdbool.h> +#include <errno.h> + +#include "uty.h" +#include "vty.h" +#include "vio_fifo.h" +#include "vio_lines.h" +#include "keystroke.h" +#include "thread.h" +#include "command.h" +#include "qstring.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by: + * + * vty.c -- the main vty handler + * vty_cli.c -- which handles the command line stuff + * vty_io.c -- .... + * + * The "struct vty" is used extensively across the Quagga daemons, where it + * has two functions relating to command handling as: + * + * 1) a "file handle" for output produced by commands + * + * 2) the holder of some context -- notably the current command "node" -- for + * command execution to use + * + * The bulk of "struct vty" is, therefore, private to vty.c and is factored + * out into the "struct vty_io". + * + * To reduce the size of vty.c, some groups of functions are separated into: + * + * vty_cli.c -- which looks after the keystroke by keystroke handling + * of the command line. + * + */ + +/*------------------------------------------------------------------------------ + * VTY sock structure + * + * Used for VTY_TERM and VTY_SHELL_SERV VTY types, which are attached to TCP + * and UNIX sockets, respectively. + * + * Also used for the associated listeners. + */ + +typedef int thread_action(struct thread *) ; + +union sock_action +{ + qps_action* qnexus ; + thread_action* thread ; + void* anon ; +} ; + +union timer_action +{ + qtimer_action* qnexus ; + thread_action* thread ; + void* anon ; +} ; + +struct vio_sock_actions +{ + union sock_action read ; + union sock_action write ; + union timer_action timer ; +}; + +typedef struct vio_sock* vio_sock ; +struct vio_sock +{ + int fd ; + + void* info ; /* for action routines */ + + struct vio_sock_actions action ; + + bool read_open ; /* read returns 0 if not open */ + bool write_open ; /* write completes instantly if not open */ + int error_seen ; /* non-zero => failed */ + + qps_file qf ; /* when running qnexus */ + + struct thread *t_read; /* when running threads */ + struct thread *t_write; + + unsigned long v_timeout; /* time-out in seconds -- 0 => none */ + bool timer_running ; /* true when timer is running */ + + qtimer qtr; /* when running qnexus */ + struct thread *t_timer; /* when running threads */ + +} ; + +enum +{ + on = true, + off = false +} ; + +enum vty_readiness /* bit significant */ +{ + not_ready = 0, + read_ready = 1, + write_ready = 2, /* takes precedence */ + now_ready = 4 +} ; + +/*------------------------------------------------------------------------------ + * The vty_io structure + */ + +struct vty_io { + struct vty* vty ; /* the related vty */ + char *name ; /* for VTY_TERM is IP address) */ + + /* List of all vty_io objects */ + struct dl_list_pair(vty_io) vio_list ; + + /* List of all vty_io that are in monitor state */ + struct dl_list_pair(vty_io) mon_list ; + + /* VTY type and sock stuff */ + enum vty_type type; + + struct vio_sock sock ; /* for VTY_TERM and VTY_SHELL_SERV */ + + bool half_closed ; /* => on death watch list */ + bool closed ; /* => all I/O terminated + will also be half_closed */ + + const char* close_reason ; /* message to be sent, once all other + output has completed, giving reason + for closing the VTY. */ + + /* When writing configuration file */ + enum vty_type real_type ; + + int file_fd ; + int file_error ; + + /*--------------------------------------------------------------------*/ + /* Command line and related state */ + + keystroke_stream key_stream ; + + /* cli_drawn <=> the current prompt and user input occupy the current + * line on the screen. + * + * cli_dirty <=> the last command output did not end with a newline. + * + * If cli_drawn is true, the following are valid: + * + * cli_prompt_len -- the length of the prompt part. + * (will be the "--more--" prompt in cli_more_wait) + * + * cli_extra_len -- the length of any ^X at the cursor position + * (for when blocked waiting for queued command) + * + * cli_echo_suppress -- the user part of the command line is suppressed + * + * NB: cli_echo_suppress is only used for password entry. + */ + bool cli_drawn ; + bool cli_dirty ; + + int cli_prompt_len ; + int cli_extra_len ; + + bool cli_echo_suppress ; + + /* "cache" for prompt -- when node or host name changes, prompt does */ + enum node_type cli_prompt_node ; + bool cli_prompt_set ; + qstring_t cli_prompt_for_node ; + + /* State of the CLI + * + * cli_blocked -- blocked from processing keystrokes + * cmd_in_progress -- command dispatched (may be queued) + * cmd_out_enabled -- contents of the command FIFO may be written away + * cli_more_wait -- is in "--more--" wait state + */ + bool cli_blocked ; + bool cmd_in_progress ; + bool cmd_out_enabled ; + bool cli_more_wait ; + + /* This is used to control command output, so that each write_ready event + * generates at most one tranche of output. + */ + bool cmd_out_done ; + + /* This is set only if the "--more--" handling is enabled */ + bool cli_more_enabled ; + + /* Command Line(s) + * + * cli_do -- when current command being prepared is completed (by + * CR/LF or otherwise) this says what there now is to be done. + * + * cl -- current command line being prepared. + * + * clx -- current command line being executed. + * + * NB: during command execution vty->buf is set to point at the '\0' + * terminated current command line being executed. + */ + enum cli_do cli_do ; + + qstring_t cl ; + qstring_t clx ; + + /* CLI output buffering */ + vio_fifo_t cli_obuf ; + + /* Command output buffering */ + vio_fifo_t cmd_obuf ; + + vio_line_control cmd_lc ; + + /* Failure count for login attempts */ + int fail; + + /* History of commands */ + vector_t hist ; + int hp ; /* History lookup current point */ + int hindex; /* History insert end point */ + + /* Window width/height as reported by Telnet. 0 => unknown */ + int width; + int height; + + /* Configure lines. */ + int lines; + bool lines_set ; /* true <=> explicitly set */ + + /* Terminal monitor. */ + bool monitor ; + bool monitor_busy ; + + /* In configure mode. */ + bool config; +} ; + +/*============================================================================== + * Functions + */ + +extern struct vty* uty_new (enum vty_type type, int sock_fd) ; + +extern void uty_open_listeners(const char *addr, unsigned short port, + const char *path) ; +extern void uty_close_listeners(void) ; + +extern void uty_watch_dog_start(void) ; +extern void uty_watch_dog_stop(void) ; + +extern void uty_half_close (vty_io vio, const char* reason) ; +extern void uty_close (vty_io vio) ; + +extern int uty_out (struct vty *vty, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern int uty_vout(struct vty *vty, const char *format, va_list args) ; +extern void uty_out_clear(vty_io vio) ; +extern void uty_out_fflush(vty_io vio, FILE* file) ; + +extern void uty_set_height(vty_io vio) ; +extern void uty_cmd_output_start(vty_io vio) ; + +extern void uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) ; +extern void uty_sock_set_timer(vio_sock sock, unsigned long timeout) ; + +extern int uty_read (vty_io vio, keystroke steal) ; +extern int utysh_read (vty_io vio, qstring cl, qstring buf) ; + + +extern const char* uty_get_name(vty_io vio) ; + +extern void uty_set_monitor(vty_io vio, bool on) ; + +#endif /* _ZEBRA_VTY_IO_H */ diff --git a/lib/workqueue.c b/lib/workqueue.c index 52b5f41c..56959db0 100644 --- a/lib/workqueue.c +++ b/lib/workqueue.c @@ -1,4 +1,4 @@ -/* +/* * Quagga Work Queue Support. * * Copyright (C) 2005 Sun Microsystems, Inc. @@ -18,38 +18,33 @@ * You should have received a copy of the GNU General Public License * along with Quagga; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <lib/zebra.h> #include "thread.h" #include "memory.h" #include "workqueue.h" -#include "linklist.h" #include "command.h" #include "log.h" +#include "linklist.h" /* master list of work_queues */ static struct list work_queues; -#define WORK_QUEUE_MIN_GRANULARITY 1 - -static struct work_queue_item * -work_queue_item_new (struct work_queue *wq) -{ - struct work_queue_item *item; - assert (wq); - - item = XCALLOC (MTYPE_WORK_QUEUE_ITEM, - sizeof (struct work_queue_item)); - - return item; -} +enum { + WQ_MIN_GRANULARITY = 1, + WQ_HYSTERESIS_FACTOR = 4, +} ; static void -work_queue_item_free (struct work_queue_item *item) +work_queue_item_free (struct work_queue *wq, struct work_queue_item *item) { - XFREE (MTYPE_WORK_QUEUE_ITEM, item); + /* call private data deletion callback if needed */ + if (wq->spec.del_item_data != NULL) + wq->spec.del_item_data (wq, item) ; + + XFREE (MTYPE_WORK_QUEUE_ITEM, item) ; return; } @@ -58,46 +53,40 @@ struct work_queue * work_queue_new (struct thread_master *m, const char *queue_name) { struct work_queue *new; - + new = XCALLOC (MTYPE_WORK_QUEUE, sizeof (struct work_queue)); if (new == NULL) return new; - - new->name = XSTRDUP (MTYPE_WORK_QUEUE_NAME, queue_name); + + new->name = XSTRDUP (MTYPE_WORK_QUEUE_NAME, queue_name); new->master = m; SET_FLAG (new->flags, WQ_UNPLUGGED); - - if ( (new->items = list_new ()) == NULL) - { - XFREE (MTYPE_WORK_QUEUE_NAME, new->name); - XFREE (MTYPE_WORK_QUEUE, new); - - return NULL; - } - - new->items->del = (void (*)(void *)) work_queue_item_free; - + listnode_add (&work_queues, new); - - new->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; + + new->cycles.granularity = WQ_MIN_GRANULARITY; /* Default values, can be overriden by caller */ new->spec.hold = WORK_QUEUE_DEFAULT_HOLD; - + return new; } void work_queue_free (struct work_queue *wq) { + work_queue_item item ; + if (wq->thread != NULL) thread_cancel(wq->thread); - - /* list_delete frees items via callback */ - list_delete (wq->items); - listnode_delete (&work_queues, wq); - + + while ((item = wq->head) != NULL) + { + wq->head = item->next ; + work_queue_item_free(wq, item) ; + } ; + XFREE (MTYPE_WORK_QUEUE_NAME, wq->name); XFREE (MTYPE_WORK_QUEUE, wq); return; @@ -109,59 +98,151 @@ work_queue_schedule (struct work_queue *wq, unsigned int delay) /* if appropriate, schedule work queue thread */ if ( CHECK_FLAG (wq->flags, WQ_UNPLUGGED) && (wq->thread == NULL) - && (listcount (wq->items) > 0) ) + && (wq->head != NULL) ) { - wq->thread = thread_add_background (wq->master, work_queue_run, + wq->thread = thread_add_background (wq->master, work_queue_run, wq, delay); return 1; } else return 0; } - -void -work_queue_add (struct work_queue *wq, void *data) + +/*------------------------------------------------------------------------------ + * Create new work queue item and place on the end of the given work queue. + * + * Schedules the work queue if there were no items (unless already scheduled + * or plugged). + * + * Returns the address of the args area in the new item. + */ +extern void* +work_queue_item_add (struct work_queue *wq) { - struct work_queue_item *item; - + work_queue_item item ; + assert (wq); - if (!(item = work_queue_item_new (wq))) + item = XCALLOC (MTYPE_WORK_QUEUE_ITEM, sizeof (struct work_queue_item)); + + if (item == NULL) { zlog_err ("%s: unable to get new queue item", __func__); - return; + return NULL ; + } + + item->next = NULL ; + if (wq->head == NULL) + { + assert(wq->list_count == 0) ; + wq->head = item ; + item->prev = NULL ; } - - item->data = data; - listnode_add (wq->items, item); - + else + { + assert((wq->tail != NULL) && (wq->list_count > 0)) ; + wq->tail->next = item ; + item->prev = wq->tail ; + } ; + wq->tail = item ; + + ++wq->list_count ; work_queue_schedule (wq, wq->spec.hold); - - return; + + return work_queue_item_args(item) ; } static void -work_queue_item_remove (struct work_queue *wq, struct listnode *ln) +work_queue_item_remove (struct work_queue *wq, work_queue_item item) { - struct work_queue_item *item = listgetdata (ln); + assert ((wq != NULL) && (item != NULL)) ; + + if (wq->head == item) + { + /* Removing the first item */ + assert(item->prev == NULL) ; + + wq->head = item->next ; - assert (item && item->data); + if (wq->tail == item) + { + /* Removing the only item */ + assert((item->next == NULL) && (wq->list_count == 1)) ; + wq->tail = NULL ; + } + else + { + /* First, but not the only item */ + assert((item->next != NULL) && (wq->list_count > 1)) ; + wq->head->prev = NULL ; + } ; + } + else if (wq->tail == item) + { + /* Removing last, but not only item */ + assert(item->next == NULL) ; + assert((item->prev != NULL) && (wq->list_count > 1)) ; + + wq->tail = item->prev ; + wq->tail->next = NULL ; + } + else + { + /* Removing from somewhere in middle */ + assert(item->next != NULL) ; + assert((item->prev != NULL) && (wq->list_count > 2)) ; + + item->prev->next = item->next ; + item->next->prev = item->prev ; + } ; - /* call private data deletion callback if needed */ - if (wq->spec.del_item_data) - wq->spec.del_item_data (wq, item->data); + --wq->list_count ; + work_queue_item_free (wq, item); - list_delete_node (wq->items, ln); - work_queue_item_free (item); - return; } -static void -work_queue_item_requeue (struct work_queue *wq, struct listnode *ln) +static work_queue_item +work_queue_item_requeue (struct work_queue *wq, work_queue_item item) { - LISTNODE_DETACH (wq->items, ln); - LISTNODE_ATTACH (wq->items, ln); /* attach to end of list */ + work_queue_item next = item->next ; + work_queue_item last = wq->tail ; + + assert(last != NULL) ; + + if (last == item) + { + /* Requeuing last item -- easy ! */ + assert(next == NULL) ; + return item ; + } ; + + assert(next != NULL) ; + + if (wq->head == item) + { + /* Requeuing first, but not only item */ + assert(item->prev == NULL) ; + + wq->head = next ; + next->prev = NULL ; + } + else + { + /* Requeuing something in middle */ + assert(item->prev != NULL) ; + + item->prev->next = item->next ; + item->next->prev = item->prev ; + } ; + + item->next = NULL ; + item->prev = last ; + + last->next = item ; + wq->tail = item ; + + return next ; } DEFUN(show_work_queues, @@ -172,8 +253,8 @@ DEFUN(show_work_queues, { struct listnode *node; struct work_queue *wq; - - vty_out (vty, + + vty_out (vty, "%c %8s %5s %8s %21s%s", ' ', "List","(ms) ","Q. Runs","Cycle Counts ", VTY_NEWLINE); @@ -183,24 +264,24 @@ DEFUN(show_work_queues, "Items", "Hold", "Total", - "Best","Gran.","Avg.", - "Name", + "Best","Gran.","Avg.", + "Name", VTY_NEWLINE); - + for (ALL_LIST_ELEMENTS_RO ((&work_queues), node, wq)) { vty_out (vty,"%c %8d %5d %8ld %7d %6d %6u %s%s", (CHECK_FLAG (wq->flags, WQ_UNPLUGGED) ? ' ' : 'P'), - listcount (wq->items), + wq->list_count, wq->spec.hold, wq->runs, wq->cycles.best, wq->cycles.granularity, - (wq->runs) ? + (wq->runs) ? (unsigned int) (wq->cycles.total / wq->runs) : 0, wq->name, VTY_NEWLINE); } - + return CMD_SUCCESS; } @@ -212,9 +293,9 @@ work_queue_plug (struct work_queue *wq) { if (wq->thread) thread_cancel (wq->thread); - + wq->thread = NULL; - + UNSET_FLAG (wq->flags, WQ_UNPLUGGED); } @@ -232,22 +313,21 @@ work_queue_unplug (struct work_queue *wq) /* timer thread to process a work queue * will reschedule itself if required, - * otherwise work_queue_item_add + * otherwise work_queue_item_add */ int work_queue_run (struct thread *thread) { struct work_queue *wq; - struct work_queue_item *item; + work_queue_item next, item ; wq_item_status ret; unsigned int cycles = 0; - struct listnode *node, *nnode; char yielded = 0; wq = THREAD_ARG (thread); wq->thread = NULL; - assert (wq && wq->items); + assert (wq != NULL) ; /* calculate cycle granularity: * list iteration == 1 cycle @@ -258,38 +338,40 @@ work_queue_run (struct thread *thread) * * Best: starts low, can only increase * - * Granularity: starts at WORK_QUEUE_MIN_GRANULARITY, can be decreased - * if we run to end of time slot, can increase otherwise + * Granularity: starts at WQ_MIN_GRANULARITY, can be decreased + * if we run to end of time slot, can increase otherwise * by a small factor. * * We could use just the average and save some work, however we want to be * able to adjust quickly to CPU pressure. Average wont shift much if * daemon has been running a long time. */ - if (wq->cycles.granularity == 0) - wq->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; + if (wq->cycles.granularity == 0) + wq->cycles.granularity = WQ_MIN_GRANULARITY; - for (ALL_LIST_ELEMENTS (wq->items, node, nnode, item)) + next = wq->head ; + while (next != NULL) { - assert (item && item->data); - + item = next ; + next = item->next ; /* default next item */ + /* dont run items which are past their allowed retries */ if (item->ran > wq->spec.max_retries) { /* run error handler, if any */ - if (wq->spec.errorfunc) - wq->spec.errorfunc (wq, item->data); - work_queue_item_remove (wq, node); + if (wq->spec.errorfunc != NULL) + wq->spec.errorfunc (wq, item); + work_queue_item_remove (wq, item); continue; } /* run and take care of items that want to be retried immediately */ do { - ret = wq->spec.workfunc (wq, item->data); + ret = wq->spec.workfunc (wq, item); item->ran++; } - while ((ret == WQ_RETRY_NOW) + while ((ret == WQ_RETRY_NOW) && (item->ran < wq->spec.max_retries)); switch (ret) @@ -308,21 +390,21 @@ work_queue_run (struct thread *thread) case WQ_REQUEUE: { item->ran--; - work_queue_item_requeue (wq, node); + next = work_queue_item_requeue (wq, item); break; } case WQ_RETRY_NOW: /* a RETRY_NOW that gets here has exceeded max_tries, same as ERROR */ case WQ_ERROR: { - if (wq->spec.errorfunc) + if (wq->spec.errorfunc != NULL) wq->spec.errorfunc (wq, item); } /* fall through here is deliberate */ case WQ_SUCCESS: default: { - work_queue_item_remove (wq, node); + work_queue_item_remove (wq, item); break; } } @@ -331,7 +413,7 @@ work_queue_run (struct thread *thread) cycles++; /* test if we should yield */ - if ( !(cycles % wq->cycles.granularity) + if ( !(cycles % wq->cycles.granularity) && thread_should_yield (thread)) { yielded = 1; @@ -341,20 +423,18 @@ work_queue_run (struct thread *thread) stats: -#define WQ_HYSTERESIS_FACTOR 4 - /* we yielded, check whether granularity should be reduced */ if (yielded && (cycles < wq->cycles.granularity)) { - wq->cycles.granularity = ((cycles > 0) ? cycles - : WORK_QUEUE_MIN_GRANULARITY); + wq->cycles.granularity = ((cycles > 0) ? cycles + : WQ_MIN_GRANULARITY); } /* otherwise, should granularity increase? */ else if (cycles >= (wq->cycles.granularity)) { if (cycles > wq->cycles.best) wq->cycles.best = cycles; - + /* along with yielded check, provides hysteresis for granularity */ if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR * WQ_HYSTERESIS_FACTOR)) @@ -362,8 +442,7 @@ stats: else if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR)) wq->cycles.granularity += WQ_HYSTERESIS_FACTOR; } -#undef WQ_HYSTERIS_FACTOR - + wq->runs++; wq->cycles.total += cycles; @@ -371,12 +450,12 @@ stats: printf ("%s: cycles %d, new: best %d, worst %d\n", __func__, cycles, wq->cycles.best, wq->cycles.granularity); #endif - + /* Is the queue done yet? If it is, call the completion callback. */ - if (listcount (wq->items) > 0) + if (wq->head != NULL) work_queue_schedule (wq, 0); else if (wq->spec.completion_func) wq->spec.completion_func (wq); - + return 0; } diff --git a/lib/workqueue.h b/lib/workqueue.h index f59499a0..369cd871 100644 --- a/lib/workqueue.h +++ b/lib/workqueue.h @@ -1,4 +1,4 @@ -/* +/* * Quagga Work Queues. * * Copyright (C) 2005 Sun Microsystems, Inc. @@ -18,14 +18,18 @@ * You should have received a copy of the GNU General Public License * along with Quagga; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _QUAGGA_WORK_QUEUE_H #define _QUAGGA_WORK_QUEUE_H +#ifndef Inline +#define Inline static inline +#endif + /* Hold time for the initial schedule of a queue run, in millisec */ -#define WORK_QUEUE_DEFAULT_HOLD 50 +enum { WORK_QUEUE_DEFAULT_HOLD = 50 } ; /* action value, for use by item processor and item error handlers */ typedef enum @@ -40,15 +44,47 @@ typedef enum * the particular item.. */ } wq_item_status; +enum { wq_args_size_max = 24 } ; /* maximum size of union wq_args */ + +union wq_args +{ + void* data ; + char bytes[wq_args_size_max] ; /* empty space `*/ +} ; + +#define WQ_ARGS_SIZE_OK(s) CONFIRM(sizeof(struct s) <= wq_args_size_max) + /* A single work queue item, unsurprisingly */ +typedef struct work_queue_item* work_queue_item ; struct work_queue_item { - void *data; /* opaque data */ + union wq_args args ; /* cast as required */ + + struct work_queue_item* next ; /* the queue itself */ + struct work_queue_item* prev ; + unsigned short ran; /* # of times item has been run */ -}; +} ; + +/* work_queue_item structures are malloced. That guarantees maximum alignment. + * To guarantee maximum alignment for "struct args", it must be first item ! + * + * (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining + * about first argument of offsetof().) + */ +typedef struct work_queue_item work_queue_item_t ; +CONFIRM(offsetof(work_queue_item_t, args) == 0) ; + /* so guaranteed max alignment */ #define WQ_UNPLUGGED (1 << 0) /* available for draining */ +typedef struct work_queue* work_queue ; + +typedef wq_item_status wq_workfunc(work_queue, work_queue_item); +typedef void wq_errorfunc(work_queue, work_queue_item); +typedef void wq_del_item_data(work_queue, work_queue_item); +typedef void wq_completion_func(work_queue); + struct work_queue { /* Everything but the specification struct is private @@ -57,52 +93,55 @@ struct work_queue struct thread_master *master; /* thread master */ struct thread *thread; /* thread, if one is active */ char *name; /* work queue name */ - + /* Specification for this work queue. * Public, must be set before use by caller. May be modified at will. */ struct { - /* optional opaque user data, global to the queue. */ + /* optional opaque user data, global to the queue. */ void *data; - + /* work function to process items with: * First argument is the workqueue queue. * Second argument is the item data */ - wq_item_status (*workfunc) (struct work_queue *, void *); - - /* error handling function, optional */ - void (*errorfunc) (struct work_queue *, struct work_queue_item *); - - /* callback to delete user specific item data */ - void (*del_item_data) (struct work_queue *, void *); - - /* completion callback, called when queue is emptied, optional */ - void (*completion_func) (struct work_queue *); - + wq_workfunc* workfunc ; + + /* error handling function -- optional */ + wq_errorfunc* errorfunc ; + + /* callback to delete user specific item data -- optional */ + wq_del_item_data* del_item_data ; + + /* completion callback, called when queue is emptied -- optional */ + wq_completion_func* completion_func ; + /* max number of retries to make for item that errors */ - unsigned int max_retries; + unsigned int max_retries; unsigned int hold; /* hold time for first run, in ms */ } spec; - + /* remaining fields should be opaque to users */ - struct list *items; /* queue item list */ - unsigned long runs; /* runs count */ - + work_queue_item head ; /* queue item list */ + work_queue_item tail ; + unsigned list_count ; + + unsigned long runs; /* runs count */ + struct { unsigned int best; unsigned int granularity; unsigned long total; } cycles; /* cycle counts */ - + /* private state */ u_int16_t flags; /* user set flag */ }; /* User API */ -/* create a new work queue, of given name. +/* create a new work queue, of given name. * user must fill in the spec of the returned work queue before adding * anything to it */ @@ -112,7 +151,10 @@ extern struct work_queue *work_queue_new (struct thread_master *, extern void work_queue_free (struct work_queue *); /* Add the supplied data as an item onto the workqueue */ -extern void work_queue_add (struct work_queue *, void *); +Inline void work_queue_add (struct work_queue *, void *); + +extern void* work_queue_item_add(struct work_queue* wq) ; +Inline void* work_queue_item_args(work_queue_item item) ; /* plug the queue, ie prevent it from being drained / processed */ extern void work_queue_plug (struct work_queue *wq); @@ -122,4 +164,22 @@ extern void work_queue_unplug (struct work_queue *wq); /* Helpers, exported for thread.c and command.c */ extern int work_queue_run (struct thread *); extern struct cmd_element show_work_queues_cmd; + +/*============================================================================== + * The Inline functions + */ + +Inline void work_queue_add (struct work_queue* wq, void* data) +{ + union wq_args* args = work_queue_item_add(wq) ; + args->data = data ; +} + +/* Return pointer to the args area in the given work queue item */ +Inline void* +work_queue_item_args(work_queue_item item) +{ + return &item->args ; +} ; + #endif /* _QUAGGA_WORK_QUEUE_H */ diff --git a/lib/zassert.h b/lib/zassert.h index 79126760..8ca2203f 100644 --- a/lib/zassert.h +++ b/lib/zassert.h @@ -5,9 +5,23 @@ #ifndef _QUAGGA_ASSERT_H #define _QUAGGA_ASSERT_H +#include "confirm.h" + extern void _zlog_assert_failed (const char *assertion, const char *file, unsigned int line, const char *function) __attribute__ ((noreturn)); +extern void _zlog_abort_mess (const char *mess, const char *file, + unsigned int line, const char *function) + __attribute__ ((noreturn)); + +extern void _zlog_abort_errno (const char *mess, const char *file, + unsigned int line, const char *function) + __attribute__ ((noreturn)); + +extern void _zlog_abort_err (const char *mess, int err, const char *file, + unsigned int line, const char *function) + __attribute__ ((noreturn)); + #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define __ASSERT_FUNCTION __func__ @@ -21,7 +35,29 @@ extern void _zlog_assert_failed (const char *assertion, const char *file, (_zlog_assert_failed(#EX, __FILE__, __LINE__, \ __ASSERT_FUNCTION), 0))) +/* Implicitly *permanent* assert() -- irrespective of NDEBUG */ #undef assert #define assert(EX) zassert(EX) +/* Explicitly permanent assert() */ +#define passert(EX) zassert(EX) + +/* NDEBUG time assert() */ +#ifndef NDEBUG +#define dassert(EX) zassert(EX) +#else +#define dassert(EX) +#endif + +/* Abort with message */ +#define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION) + +/* Abort with message and errno and strerror() thereof */ +#define zabort_errno(MS) _zlog_abort_errno(MS, __FILE__, __LINE__, \ + __ASSERT_FUNCTION) + +/* Abort with message and given error and strerror() thereof */ +#define zabort_err(MS, ERR) _zlog_abort_err(MS, ERR, __FILE__, __LINE__, \ + __ASSERT_FUNCTION) + #endif /* _QUAGGA_ASSERT_H */ diff --git a/lib/zclient.c b/lib/zclient.c index 52a3627d..6803aa4a 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -32,18 +32,25 @@ #include "zclient.h" #include "memory.h" #include "table.h" - -/* Zebra client events. */ -enum event {ZCLIENT_SCHEDULE, ZCLIENT_READ, ZCLIENT_CONNECT}; -/* Prototype for event manager. */ -static void zclient_event (enum event, struct zclient *); +/* Zebra client events. */ +enum event {ZLOOKUP_SCHEDULE, ZCLIENT_SCHEDULE, ZCLIENT_READ, ZCLIENT_CONNECT}; extern struct thread_master *master; /* This file local debug flag. */ int zclient_debug = 0; - + +/* Nexus to use, if any */ +static qpn_nexus zclient_nexus = NULL; + +/* prototypes */ +static int zclient_read (struct zclient *zclient); +static void zclient_event (enum event, struct zclient *); +static void zclient_event_r (enum event event, struct zclient *zclient); +static void zclient_event_t (enum event event, struct zclient *zclient); +static void zclient_connect_r (qtimer qtr, void* timer_info, qtime_t when); + /* Allocate zclient structure. */ struct zclient * zclient_new () @@ -55,6 +62,13 @@ zclient_new () zclient->obuf = stream_new (ZEBRA_MAX_PACKET_SIZ); zclient->wb = buffer_new(0); + if (zclient_nexus) + { + zclient->qf = qps_file_init_new(zclient->qf, NULL); + zclient->qtr = qtimer_init_new(zclient->qtr, zclient_nexus->pile, + zclient_connect_r, zclient); + } + return zclient; } @@ -73,16 +87,36 @@ zclient_free (struct zclient *zclient) if (zclient->wb) buffer_free(zclient->wb); + /* qfile and qtimer */ + if (zclient->qf) + { + qps_remove_file(zclient->qf); + qps_file_free(zclient->qf); + zclient->qf = NULL; + } + if (zclient->qtr) + { + qtimer_free(zclient->qtr); + zclient->qtr = NULL; + } + XFREE (MTYPE_ZCLIENT, zclient); } +/* Initialize to use a nexus (qpselect etc). */ +void +zclient_init_r (qpn_nexus n) +{ + zclient_nexus = n; +} + /* Initialize zebra client. Argument redist_default is unwanted redistribute route type. */ void zclient_init (struct zclient *zclient, int redist_default) { int i; - + /* Enable zebra client connection by default. */ zclient->enable = 1; @@ -108,6 +142,13 @@ zclient_init (struct zclient *zclient, int redist_default) zclient_event (ZCLIENT_SCHEDULE, zclient); } +/* Schedule lookup connection */ +void +zlookup_schedule(struct zclient *zclient) +{ + zclient_event (ZLOOKUP_SCHEDULE, zclient); +} + /* Stop zebra client services. */ void zclient_stop (struct zclient *zclient) @@ -120,6 +161,12 @@ zclient_stop (struct zclient *zclient) THREAD_OFF(zclient->t_connect); THREAD_OFF(zclient->t_write); + if (zclient->qf) + qps_remove_file(zclient->qf); + + if (zclient->qtr) + qtimer_unset(zclient->qtr); + /* Reset streams. */ stream_reset(zclient->ibuf); stream_reset(zclient->obuf); @@ -155,8 +202,8 @@ zclient_socket(void) sock = socket (AF_INET, SOCK_STREAM, 0); if (sock < 0) return -1; - - /* Make server socket. */ + + /* Make server socket. */ memset (&serv, 0, sizeof (struct sockaddr_in)); serv.sin_family = AF_INET; serv.sin_port = htons (ZEBRA_PORT); @@ -188,8 +235,8 @@ zclient_socket_un (const char *path) sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return -1; - - /* Make server socket. */ + + /* Make server socket. */ memset (&addr, 0, sizeof (struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy (addr.sun_path, path, strlen (path)); @@ -217,8 +264,37 @@ zclient_failed(struct zclient *zclient) return -1; } +/* Write as much data as possible. + * nexus version */ +static void +zclient_flush_data_r(qps_file qf, void* file_info) +{ + struct zclient *zclient = file_info; + + qps_disable_modes(qf, qps_write_mbit); + + if (zclient->sock < 0) + return; + + switch (buffer_flush_available(zclient->wb, zclient->sock)) + { + case BUFFER_ERROR: + zlog_warn("%s: buffer_flush_available failed on zclient fd %d, closing", + __func__, zclient->sock); + zclient_failed(zclient); + break; + case BUFFER_PENDING: + qps_enable_mode(qf, qps_write_mnum, zclient_flush_data_r) ; + break; + case BUFFER_EMPTY: + break; + } +} + +/* Write as much data as possible. + * thread version */ static int -zclient_flush_data(struct thread *thread) +zclient_flush_data_t(struct thread *thread) { struct zclient *zclient = THREAD_ARG(thread); @@ -233,7 +309,7 @@ zclient_flush_data(struct thread *thread) return zclient_failed(zclient); break; case BUFFER_PENDING: - zclient->t_write = thread_add_write(master, zclient_flush_data, + zclient->t_write = thread_add_write(master, zclient_flush_data_t, zclient, zclient->sock); break; case BUFFER_EMPTY: @@ -256,11 +332,17 @@ zclient_send_message(struct zclient *zclient) return zclient_failed(zclient); break; case BUFFER_EMPTY: - THREAD_OFF(zclient->t_write); + if (zclient_nexus) + qps_disable_modes(zclient->qf, qps_write_mbit); + else + THREAD_OFF(zclient->t_write); break; case BUFFER_PENDING: - THREAD_WRITE_ON(master, zclient->t_write, - zclient_flush_data, zclient, zclient->sock); + if (zclient_nexus) + qps_enable_mode(zclient->qf, qps_write_mnum, zclient_flush_data_r) ; + else + THREAD_WRITE_ON(master, zclient->t_write, + zclient_flush_data_t, zclient, zclient->sock); break; } return 0; @@ -288,7 +370,7 @@ zebra_message_send (struct zclient *zclient, int command) /* Send very simple command only Zebra message. */ zclient_create_header (s, command); - + return zclient_send_message(zclient); } @@ -313,6 +395,10 @@ zclient_start (struct zclient *zclient) if (zclient->t_connect) return 0; + /* Check timer */ + if (zclient->qtr && zclient->qtr->active) + return 0; + /* Make socket. */ #ifdef HAVE_TCP_ZEBRA zclient->sock = zclient_socket (); @@ -335,7 +421,10 @@ zclient_start (struct zclient *zclient) zclient->fail = 0; if (zclient_debug) zlog_debug ("zclient connect success with socket [%d]", zclient->sock); - + + if (zclient_nexus) + qps_add_file(zclient_nexus->selection, zclient->qf, zclient->sock, zclient); + /* Create read thread. */ zclient_event (ZCLIENT_READ, zclient); @@ -358,9 +447,24 @@ zclient_start (struct zclient *zclient) } /* This function is a wrapper function for calling zclient_start from + qtimer. */ +static void +zclient_connect_r (qtimer qtr, void* timer_info, qtime_t when) +{ + struct zclient *zclient = timer_info; + + qtimer_unset(qtr); + + if (zclient_debug) + zlog_debug ("zclient_connect is called"); + + zclient_start (zclient); +} + +/* This function is a wrapper function for calling zclient_start from timer or event thread. */ static int -zclient_connect (struct thread *t) +zclient_connect_t (struct thread *t) { struct zclient *zclient; @@ -372,14 +476,58 @@ zclient_connect (struct thread *t) return zclient_start (zclient); } - - /* + +/* Connect to zebra for nexthop lookup. + * thread version */ +static int +zlookup_connect_t (struct thread *t) +{ + struct zclient *zlookup; + + zlookup = THREAD_ARG (t); + zlookup->t_connect = NULL; + + if (zlookup->sock != -1) + return 0; + +#ifdef HAVE_TCP_ZEBRA + zlookup->sock = zclient_socket (); +#else + zlookup->sock = zclient_socket_un (ZEBRA_SERV_PATH); +#endif /* HAVE_TCP_ZEBRA */ + if (zlookup->sock < 0) + return -1; + + return 0; +} + +/* Connect to zebra for nexthop lookup. + * nexus version */ +static void +zlookup_connect_r (qtimer qtr, void* timer_info, qtime_t when) +{ + struct zclient *zlookup = timer_info; + + qtimer_unset(qtr); + + if (zlookup->sock != -1) + return; + +#ifdef HAVE_TCP_ZEBRA + zlookup->sock = zclient_socket (); +#else + zlookup->sock = zclient_socket_un (ZEBRA_SERV_PATH); +#endif /* HAVE_TCP_ZEBRA */ +} + + + /* * "xdr_encode"-like interface that allows daemon (client) to send * a message to zebra server for a route that needs to be * added/deleted to the kernel. Info about the route is specified * by the caller in a struct zapi_ipv4. zapi_ipv4_read() then writes * the info down the zclient socket using the stream_* functions. - * + * * The corresponding read ("xdr_decode") function on the server * side is zread_ipv4_add()/zread_ipv4_delete(). * @@ -391,11 +539,11 @@ zclient_connect (struct thread *t) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Destination IPv4 Prefix for route | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Nexthop count | + * | Nexthop count | * +-+-+-+-+-+-+-+-+ * - * - * A number of IPv4 nexthop(s) or nexthop interface index(es) are then + * + * A number of IPv4 nexthop(s) or nexthop interface index(es) are then * described, as per the Nexthop count. Each nexthop described as: * * +-+-+-+-+-+-+-+-+ @@ -405,18 +553,18 @@ zclient_connect (struct thread *t) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Alternatively, if the flags field has ZEBRA_FLAG_BLACKHOLE or - * ZEBRA_FLAG_REJECT is set then Nexthop count is set to 1, then _no_ + * ZEBRA_FLAG_REJECT is set then Nexthop count is set to 1, then _no_ * nexthop information is provided, and the message describes a prefix * to blackhole or reject route. * * If ZAPI_MESSAGE_DISTANCE is set, the distance value is written as a 1 * byte value. - * + * * If ZAPI_MESSAGE_METRIC is set, the metric value is written as an 8 * byte value. * * XXX: No attention paid to alignment. - */ + */ int zapi_ipv4_route (u_char cmd, struct zclient *zclient, struct prefix_ipv4 *p, struct zapi_ipv4 *api) @@ -428,9 +576,9 @@ zapi_ipv4_route (u_char cmd, struct zclient *zclient, struct prefix_ipv4 *p, /* Reset stream. */ s = zclient->obuf; stream_reset (s); - + zclient_create_header (s, cmd); - + /* Put type and nexthop. */ stream_putc (s, api->type); stream_putc (s, api->flags); @@ -496,7 +644,7 @@ zapi_ipv6_route (u_char cmd, struct zclient *zclient, struct prefix_ipv6 *p, stream_putc (s, api->type); stream_putc (s, api->flags); stream_putc (s, api->message); - + /* Put prefix information. */ psize = PSIZE (p->prefixlen); stream_putc (s, p->prefixlen); @@ -531,10 +679,10 @@ zapi_ipv6_route (u_char cmd, struct zclient *zclient, struct prefix_ipv6 *p, } #endif /* HAVE_IPV6 */ -/* +/* * send a ZEBRA_REDISTRIBUTE_ADD or ZEBRA_REDISTRIBUTE_DELETE * for the route type (ZEBRA_ROUTE_KERNEL etc.). The zebra server will - * then set/unset redist[type] in the client handle (a struct zserv) for the + * then set/unset redist[type] in the client handle (a struct zserv) for the * sending client */ int @@ -544,12 +692,12 @@ zebra_redistribute_send (int command, struct zclient *zclient, int type) s = zclient->obuf; stream_reset(s); - + zclient_create_header (s, command); stream_putc (s, type); - + stream_putw_at (s, 0, stream_get_endp (s)); - + return zclient_send_message(zclient); } @@ -568,7 +716,7 @@ zebra_router_id_update_read (struct stream *s, struct prefix *rid) } /* Interface addition from zebra daemon. */ -/* +/* * The format of the message sent with type ZEBRA_INTERFACE_ADD or * ZEBRA_INTERFACE_DELETE from zebra to the client is: * 0 1 2 3 @@ -628,11 +776,11 @@ zebra_interface_add_read (struct stream *s) if (ifp->hw_addr_len) stream_get (ifp->hw_addr, s, ifp->hw_addr_len); #endif /* HAVE_STRUCT_SOCKADDR_DL */ - + return ifp; } -/* +/* * Read interface up/down msg (ZEBRA_INTERFACE_UP/ZEBRA_INTERFACE_DOWN) * from zebra server. The format of this message is the same as * that sent for ZEBRA_INTERFACE_ADD/ZEBRA_INTERFACE_DELETE (see @@ -670,7 +818,7 @@ zebra_interface_state_read (struct stream *s) return ifp; } -/* +/* * format of message for address additon is: * 0 * 0 1 2 3 4 5 6 7 @@ -770,7 +918,7 @@ zebra_interface_address_read (int type, struct stream *s) stream_get (&d.u.prefix, s, plen); d.family = family; - if (type == ZEBRA_INTERFACE_ADDRESS_ADD) + if (type == ZEBRA_INTERFACE_ADDRESS_ADD) { /* N.B. NULL destination pointers are encoded as all zeroes */ ifc = connected_add_by_prefix(ifp, &p,(memconstant(&d.u.prefix,0,plen) ? @@ -791,20 +939,32 @@ zebra_interface_address_read (int type, struct stream *s) return ifc; } - +/* nexus: Zebra client message read function. */ +static void +zclient_read_r (qps_file qf, void* file_info) +{ + struct zclient *zclient = file_info; + qps_disable_modes(qf, qps_read_mbit); + zclient_read(zclient); +} + +/* thread: Zebra client message read function. */ +static int +zclient_read_t (struct thread *thread) +{ + struct zclient *zclient = THREAD_ARG (thread); + zclient->t_read = NULL; + return zclient_read(zclient); +} + /* Zebra client message read function. */ static int -zclient_read (struct thread *thread) +zclient_read (struct zclient *zclient) { int ret; size_t already; uint16_t length, command; uint8_t marker, version; - struct zclient *zclient; - - /* Get socket to zebra. */ - zclient = THREAD_ARG (thread); - zclient->t_read = NULL; /* Read zebra header (if we don't have it already). */ if ((already = stream_get_endp(zclient->ibuf)) < ZEBRA_HEADER_SIZE) @@ -835,15 +995,15 @@ zclient_read (struct thread *thread) marker = stream_getc (zclient->ibuf); version = stream_getc (zclient->ibuf); command = stream_getw (zclient->ibuf); - + if (marker != ZEBRA_HEADER_MARKER || version != ZSERV_VERSION) { zlog_err("%s: socket %d version mismatch, marker %d, version %d", __func__, zclient->sock, marker, version); return zclient_failed(zclient); } - - if (length < ZEBRA_HEADER_SIZE) + + if (length < ZEBRA_HEADER_SIZE) { zlog_err("%s: socket %d message length %u is less than %d ", __func__, zclient->sock, length, ZEBRA_HEADER_SIZE); @@ -952,7 +1112,7 @@ void zclient_redistribute (int command, struct zclient *zclient, int type) { - if (command == ZEBRA_REDISTRIBUTE_ADD) + if (command == ZEBRA_REDISTRIBUTE_ADD) { if (zclient->redist[type]) return; @@ -980,7 +1140,7 @@ zclient_redistribute_default (int command, struct zclient *zclient) return; zclient->default_information = 1; } - else + else { if (!zclient->default_information) return; @@ -991,30 +1151,78 @@ zclient_redistribute_default (int command, struct zclient *zclient) zebra_message_send (zclient, command); } +/* Arm event. */ static void zclient_event (enum event event, struct zclient *zclient) { + if (zclient_nexus) + zclient_event_r(event, zclient); + else + zclient_event_t(event, zclient); +} + +/* Arm event. + * nexus version */ +static void +zclient_event_r (enum event event, struct zclient *zclient) +{ switch (event) { + case ZLOOKUP_SCHEDULE: + if (!zclient->qtr->active) + qtimer_set(zclient->qtr, qt_get_monotonic(), zlookup_connect_r) ; + break; case ZCLIENT_SCHEDULE: - if (! zclient->t_connect) - zclient->t_connect = - thread_add_event (master, zclient_connect, zclient, 0); + if (!zclient->qtr->active) + qtimer_set(zclient->qtr, qt_get_monotonic(), zclient_connect_r) ; break; case ZCLIENT_CONNECT: if (zclient->fail >= 10) return; if (zclient_debug) - zlog_debug ("zclient connect schedule interval is %d", + zlog_debug ("zclient connect schedule interval is %d", zclient->fail < 3 ? 10 : 60); + if (!zclient->qtr->active) + qtimer_set(zclient->qtr, + qt_add_monotonic(QTIME(zclient->fail < 3 ? 10 : 60)), zclient_connect_r) ; + break; + case ZCLIENT_READ: + qps_enable_mode(zclient->qf, qps_read_mnum, zclient_read_r) ; + break; + } +} + +/* Arm event. + * thread version */ +static void +zclient_event_t (enum event event, struct zclient *zclient) +{ + switch (event) + { + case ZLOOKUP_SCHEDULE: + if (! zclient->t_connect) + zclient->t_connect = + thread_add_event (master, zlookup_connect_t, zclient, 0); + break; + case ZCLIENT_SCHEDULE: + if (! zclient->t_connect) + zclient->t_connect = + thread_add_event (master, zclient_connect_t, zclient, 0); + break; + case ZCLIENT_CONNECT: + if (zclient->fail >= 10) + return; + if (zclient_debug) + zlog_debug ("zclient connect schedule interval is %d", + zclient->fail < 3 ? 10 : 60); if (! zclient->t_connect) - zclient->t_connect = - thread_add_timer (master, zclient_connect, zclient, - zclient->fail < 3 ? 10 : 60); + zclient->t_connect = + thread_add_timer (master, zclient_connect_t, zclient, + zclient->fail < 3 ? 10 : 60); break; case ZCLIENT_READ: - zclient->t_read = - thread_add_read (master, zclient_read, zclient, zclient->sock); + zclient->t_read = + thread_add_read (master, zclient_read_t, zclient, zclient->sock); break; } } diff --git a/lib/zclient.h b/lib/zclient.h index 21786ab8..17f4e317 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -7,12 +7,12 @@ * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. - * + * * GNU Zebra 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. - * + * * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, @@ -24,6 +24,8 @@ /* For struct interface and struct connected. */ #include "if.h" +#include "qpnexus.h" +#include "prefix.h" /* For input/output buffer to zebra. */ #define ZEBRA_MAX_PACKET_SIZ 4096 @@ -60,6 +62,10 @@ struct zclient /* Thread to write buffered data to zebra. */ struct thread *t_write; + /* If using nexus, qfile and qtimer */ + qps_file qf; + qtimer qtr; + /* Redistribute information. */ u_char redist_default; u_char redist[ZEBRA_ROUTE_MAX]; @@ -121,11 +127,13 @@ struct zapi_ipv4 /* Prototypes of zebra client service functions. */ extern struct zclient *zclient_new (void); +extern void zclient_init_r (qpn_nexus); extern void zclient_init (struct zclient *, int); extern int zclient_start (struct zclient *); extern void zclient_stop (struct zclient *); extern void zclient_reset (struct zclient *); extern void zclient_free (struct zclient *); +extern void zlookup_schedule(struct zclient *); /* Get TCP socket connection to zebra daemon at loopback address. */ extern int zclient_socket (void); @@ -154,7 +162,7 @@ extern struct interface *zebra_interface_state_read (struct stream *s); extern struct connected *zebra_interface_address_read (int, struct stream *); extern void zebra_interface_if_set_value (struct stream *, struct interface *); extern void zebra_router_id_update_read (struct stream *s, struct prefix *rid); -extern int zapi_ipv4_route (u_char, struct zclient *, struct prefix_ipv4 *, +extern int zapi_ipv4_route (u_char, struct zclient *, struct prefix_ipv4 *, struct zapi_ipv4 *); #ifdef HAVE_IPV6 @@ -179,7 +187,7 @@ struct zapi_ipv6 u_int32_t metric; }; -extern int zapi_ipv6_route (u_char cmd, struct zclient *zclient, +extern int zapi_ipv6_route (u_char cmd, struct zclient *zclient, struct prefix_ipv6 *p, struct zapi_ipv6 *api); #endif /* HAVE_IPV6 */ diff --git a/lib/zebra.h b/lib/zebra.h index 2dc84514..799cfc3d 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -115,7 +115,7 @@ typedef int socklen_t; #ifdef __va_copy #define va_copy(DST,SRC) __va_copy(DST,SRC) #else -/* Now we are desperate; this should work on many typical platforms. +/* Now we are desperate; this should work on many typical platforms. But this is slightly dangerous, because the standard does not require va_copy to be a macro. */ #define va_copy(DST,SRC) memcpy(&(DST), &(SRC), sizeof(va_list)) @@ -260,7 +260,7 @@ typedef int socklen_t; #endif /* BSDI_NRL */ /* Local includes: */ -#if !(defined(__GNUC__) || defined(VTYSH_EXTRACT_PL)) +#if !(defined(__GNUC__) || defined(VTYSH_EXTRACT_PL)) #define __attribute__(x) #endif /* !__GNUC__ || VTYSH_EXTRACT_PL */ @@ -285,7 +285,7 @@ typedef int socklen_t; -/* +/* * RFC 3542 defines several macros for using struct cmsghdr. * Here, we define those that are not present */ @@ -330,7 +330,7 @@ struct in_pktinfo }; #endif -/* +/* * OSPF Fragmentation / fragmented writes * * ospfd can support writing fragmented packets, for cases where @@ -350,13 +350,13 @@ struct in_pktinfo #define WANT_OSPF_WRITE_FRAGMENT #endif -/* +/* * IP_HDRINCL / struct ip byte order * * Linux: network byte order * *BSD: network, except for length and offset. (cf Stevens) * SunOS: nominally as per BSD. but bug: network order on LE. - * OpenBSD: network byte order, apart from older versions which are as per + * OpenBSD: network byte order, apart from older versions which are as per * *BSD */ #if defined(__NetBSD__) || defined(__FreeBSD__) \ @@ -385,7 +385,7 @@ struct in_pktinfo /* MAX / MIN are not commonly defined, but useful */ #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) -#endif +#endif #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif @@ -451,7 +451,7 @@ struct in_pktinfo extern const char *zebra_route_string(unsigned int route_type); /* Map a route type to a char. For example, ZEBRA_ROUTE_RIPNG -> 'R'. */ extern char zebra_route_char(unsigned int route_type); -/* Map a zserv command type to the same string, +/* Map a zserv command type to the same string, * e.g. ZEBRA_INTERFACE_ADD -> "ZEBRA_INTERFACE_ADD" */ /* Map a protocol name to its number. e.g. ZEBRA_ROUTE_BGP->9*/ extern int proto_name2num(const char *s); @@ -496,17 +496,8 @@ extern const char *zserv_command_string (unsigned int command); #define INADDR_LOOPBACK 0x7f000001 /* Internet address 127.0.0.1. */ #endif -/* Address family numbers from RFC1700. */ -#define AFI_IP 1 -#define AFI_IP6 2 -#define AFI_MAX 3 - -/* Subsequent Address Family Identifier. */ -#define SAFI_UNICAST 1 -#define SAFI_MULTICAST 2 -#define SAFI_UNICAST_MULTICAST 3 -#define SAFI_MPLS_VPN 4 -#define SAFI_MAX 5 +/* AFI/SAFI types and numbers. */ +#include "qafi_safi.h" /* Filter direction. */ #define FILTER_IN 0 @@ -530,36 +521,10 @@ extern const char *zserv_command_string (unsigned int command); #define SET_FLAG(V,F) (V) |= (F) #define UNSET_FLAG(V,F) (V) &= ~(F) -/* AFI and SAFI type. */ -typedef u_int16_t afi_t; -typedef u_int8_t safi_t; - /* Zebra types. Used in Zserv message header. */ typedef u_int16_t zebra_size_t; typedef u_int16_t zebra_command_t; -/* FIFO -- first in first out structure and macros. */ -struct fifo -{ - struct fifo *next; - struct fifo *prev; -}; - -#define FIFO_INIT(F) \ - do { \ - struct fifo *Xfifo = (struct fifo *)(F); \ - Xfifo->next = Xfifo->prev = Xfifo; \ - } while (0) - -#define FIFO_ADD(F,N) \ - do { \ - struct fifo *Xfifo = (struct fifo *)(F); \ - struct fifo *Xnode = (struct fifo *)(N); \ - Xnode->next = Xfifo; \ - Xnode->prev = Xfifo->prev; \ - Xfifo->prev = Xfifo->prev->next = Xnode; \ - } while (0) - #define FIFO_DEL(N) \ do { \ struct fifo *Xnode = (struct fifo *)(N); \ @@ -567,14 +532,4 @@ struct fifo Xnode->next->prev = Xnode->prev; \ } while (0) -#define FIFO_HEAD(F) \ - ((((struct fifo *)(F))->next == (struct fifo *)(F)) \ - ? NULL : (F)->next) - -#define FIFO_EMPTY(F) \ - (((struct fifo *)(F))->next == (struct fifo *)(F)) - -#define FIFO_TOP(F) \ - (FIFO_EMPTY(F) ? NULL : ((struct fifo *)(F))->next) - #endif /* _ZEBRA_H */ |