diff options
-rw-r--r-- | bgpd/bgp_main.c | 8 | ||||
-rwxr-xr-x | configure.ac | 1 | ||||
-rw-r--r-- | lib/Makefile.am | 6 | ||||
-rw-r--r-- | lib/command.c | 112 | ||||
-rw-r--r-- | lib/filter.c | 2 | ||||
-rw-r--r-- | lib/heap.c | 517 | ||||
-rw-r--r-- | lib/heap.h | 160 | ||||
-rw-r--r-- | lib/log.c | 658 | ||||
-rw-r--r-- | lib/log.h | 30 | ||||
-rw-r--r-- | lib/memory.h | 6 | ||||
-rw-r--r-- | lib/memtypes.c | 18 | ||||
-rw-r--r-- | lib/mqueue.c | 615 | ||||
-rw-r--r-- | lib/mqueue.h | 282 | ||||
-rw-r--r-- | lib/plist.c | 2 | ||||
-rw-r--r-- | lib/privs.c | 203 | ||||
-rw-r--r-- | lib/privs.h | 4 | ||||
-rw-r--r-- | lib/qpnexus.c | 120 | ||||
-rw-r--r-- | lib/qpnexus.h | 86 | ||||
-rw-r--r-- | lib/qpselect.c | 1321 | ||||
-rw-r--r-- | lib/qpselect.h | 208 | ||||
-rw-r--r-- | lib/qpthreads.c | 713 | ||||
-rw-r--r-- | lib/qpthreads.h | 417 | ||||
-rw-r--r-- | lib/qtime.c | 194 | ||||
-rw-r--r-- | lib/qtime.h | 307 | ||||
-rw-r--r-- | lib/qtimers.c | 336 | ||||
-rw-r--r-- | lib/qtimers.h | 144 | ||||
-rw-r--r-- | lib/routemap.c | 2 | ||||
-rw-r--r-- | lib/vector.c | 62 | ||||
-rw-r--r-- | lib/vector.h | 3 | ||||
-rw-r--r-- | lib/vty.c | 866 | ||||
-rw-r--r-- | lib/vty.h | 19 | ||||
-rw-r--r-- | lib/zassert.h | 50 | ||||
-rw-r--r-- | tests/test-privs.c | 28 | ||||
-rw-r--r-- | vtysh/vtysh.c | 2 | ||||
-rw-r--r-- | vtysh/vtysh_config.c | 3 |
35 files changed, 7069 insertions, 436 deletions
diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 9d14683c..7ab403bc 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -404,9 +404,10 @@ main (int argc, char **argv) /* Initializations. */ srand (time (NULL)); signal_init (master, Q_SIGC(bgp_signals), bgp_signals); - zprivs_init (&bgpd_privs); + zprivs_init_r (&bgpd_privs); cmd_init (1); - vty_init (master); + vty_init_r(); +/* vty_init (master); */ memory_init (); /* BGP related initialization. */ @@ -442,6 +443,9 @@ main (int argc, char **argv) (bm->address ? bm->address : "<all>"), bm->port); + /* create CLI thread */ + vty_exec_r(); + /* Start finite state machine, here we go! */ while (thread_fetch (master, &thread)) thread_call (&thread); diff --git a/configure.ac b/configure.ac index 9c47b5b4..17ab4387 100755 --- a/configure.ac +++ b/configure.ac @@ -128,6 +128,7 @@ if test "x${cflags_specified}" = "x" ; then CFLAGS="${CFLAGS} -Wbad-function-cast -Wwrite-strings" CFLAGS="${CFLAGS} -Wmissing-prototypes -Wmissing-declarations" CFLAGS="${CFLAGS} -Wchar-subscripts -Wcast-qual" + CFLAGS="${CFLAGS} -pthread" # TODO: conditionally addd -Wpacked if handled AC_MSG_RESULT([gcc default]) ;; diff --git a/lib/Makefile.am b/lib/Makefile.am index f655ac39..3184a9b6 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,7 +12,8 @@ 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 symtab.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 BUILT_SOURCES = memtypes.h route_types.h @@ -27,7 +28,8 @@ 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 symtab.h + workqueue.h route_types.h symtab.h heap.h \ + qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt diff --git a/lib/command.c b/lib/command.c index 60880f49..80b113e8 100644 --- a/lib/command.c +++ b/lib/command.c @@ -548,57 +548,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); @@ -2569,8 +2569,7 @@ DEFUN (config_write_file, } /* Make vty for configuration file. */ - file_vty = vty_new (); - file_vty->fd = fd; + file_vty = vty_new (fd); file_vty->type = VTY_FILE; /* Config file header print. */ @@ -3060,49 +3059,51 @@ DEFUN (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; } @@ -3113,7 +3114,7 @@ DEFUN (config_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; } @@ -3150,7 +3151,7 @@ DEFUN (config_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; } @@ -3238,7 +3239,7 @@ DEFUN (config_log_file, "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, @@ -3289,7 +3290,7 @@ DEFUN (config_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; } @@ -3321,8 +3322,8 @@ 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; } @@ -3358,7 +3359,7 @@ 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; } @@ -3370,7 +3371,7 @@ 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; } @@ -3387,10 +3388,7 @@ DEFUN_DEPRECATED (config_log_trap, 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; } @@ -3402,7 +3400,7 @@ 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; } @@ -3412,7 +3410,7 @@ DEFUN (config_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; } @@ -3423,7 +3421,7 @@ DEFUN (no_config_log_record_priority, "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; } @@ -3435,6 +3433,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); @@ -3442,7 +3442,9 @@ 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; } @@ -3454,7 +3456,7 @@ 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; } diff --git a/lib/filter.c b/lib/filter.c index af8d587f..57109854 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) { diff --git a/lib/heap.c b/lib/heap.c new file mode 100644 index 00000000..35a1b51d --- /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 */ @@ -34,6 +34,25 @@ #ifdef HAVE_UCONTEXT_H #include <ucontext.h> #endif +#include "qpthreads.h" + +#ifdef NDEBUG +#define LOCK qpt_mutex_lock(vty_mutex); +#define UNLOCK qpt_mutex_unlock(vty_mutex); +#else +#define LOCK qpt_mutex_lock(vty_mutex);++vty_lock_count; +#define UNLOCK --vty_lock_count;qpt_mutex_unlock(vty_mutex); +#define ASSERTLOCKED if(vty_lock_count==0 && !vty_lock_asserted){vty_lock_asserted=1;assert(0);} +#endif + +/* 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. */ @@ -66,14 +85,23 @@ const char *zlog_priority[] = "debugging", NULL, }; - - /* For time string format. */ size_t quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { + size_t result; + LOCK + result = uquagga_timestamp(timestamp_precision, buf, buflen); + UNLOCK + return result; +} + +/* unprotected version for when mutex already held */ +size_t +uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) +{ static struct { time_t last; size_t len; @@ -81,6 +109,8 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) } cache; struct timeval clock; + size_t result = 0; + /* would it be sufficient to use global 'recent_time' here? I fear not... */ gettimeofday(&clock, NULL); @@ -122,14 +152,19 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) } while (--prec > 0); *p = '.'; - return cache.len+1+timestamp_precision; + result = cache.len+1+timestamp_precision; } - buf[cache.len] = '\0'; - return cache.len; + else + { + buf[cache.len] = '\0'; + result = cache.len; + } + } else { + if (buflen > 0) + buf[0] = '\0'; } - if (buflen > 0) - buf[0] = '\0'; - return 0; + + return result; } /* Utility routine for current time printing. */ @@ -138,20 +173,30 @@ time_print(FILE *fp, struct timestamp_control *ctl) { if (!ctl->already_rendered) { - ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); + ctl->len = uquagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); ctl->already_rendered = 1; } fprintf(fp, "%s ", ctl->buf); } - /* va_list version of zlog. */ static void vzlog (struct zlog *zl, int priority, const char *format, va_list args) { + LOCK + uvzlog(zl, priority, format, args); + UNLOCK +} + +/* va_list version of zlog. Unprotected assumes mutex already held*/ +static void +uvzlog (struct zlog *zl, int priority, const char *format, va_list args) +{ struct timestamp_control tsctl; tsctl.already_rendered = 0; + ASSERTLOCKED + /* If zlog is not specified, use default one. */ if (zl == NULL) zl = zlog_default; @@ -165,55 +210,58 @@ vzlog (struct zlog *zl, int priority, const char *format, va_list args) vfprintf (stderr, format, args); fprintf (stderr, "\n"); fflush (stderr); - - /* In this case we return at here. */ - return; } - 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); - } + tsctl.precision = zl->timestamp_precision; - /* 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); - } + /* Syslog output */ + if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) + { + va_list ac; + va_copy(ac, args); + vsyslog (priority|zlog_default->facility, format, ac); + va_end(ac); + } - /* stdout output. */ - if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) - { - 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); - } + /* 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); + } - /* 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); + /* stdout output. */ + if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) + { + 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); + } + + /* Terminal monitor. */ + if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) + { + const char *priority_name = (zl->record_priority ? zlog_priority[priority] : NULL); + const char *proto_name = zlog_proto_names[zl->protocol]; + vty_log (priority_name, proto_name, format, &tsctl, args); + } + } } static char * @@ -518,8 +566,16 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) void zlog_backtrace(int priority) { + LOCK + zlog_backtrace(priority); + 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 +584,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,16 +672,69 @@ 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]; + char err_mess[buff_size]; + strerror_r(err, err_mess, buff_size); + snprintf(buff, buff_size, + "%s, in file %s, line %u, function %s, error %d \"%s\"", + mess, file, line, (function ? function : "?"), + err, err_mess); + zlog_abort(buff); +} + + +static void +zlog_abort (const char *mess) { +#ifndef NDEBUG + /* don't work about being unlocked */ + vty_lock_asserted = 1; +#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); + + uzlog(NULL, LOG_CRIT, "%s", mess); + uzlog_backtrace(LOG_CRIT); abort(); } @@ -648,22 +768,33 @@ openzlog (const char *progname, zlog_proto_t protocol, void closezlog (struct zlog *zl) { + LOCK + closelog(); if (zl->fp != NULL) fclose (zl->fp); XFREE (MTYPE_ZLOG, zl); + + UNLOCK } /* Called from command.c. */ void zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level) { + LOCK + if (zl == NULL) zl = zlog_default; - zl->maxlvl[dest] = log_level; + if (zl != NULL) + { + zl->maxlvl[dest] = log_level; + } + + UNLOCK } int @@ -671,46 +802,68 @@ zlog_set_file (struct zlog *zl, const char *filename, int log_level) { FILE *fp; mode_t oldumask; + int result = 1; + + 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; + UNLOCK + return result; } /* Reset opend file. */ int zlog_reset_file (struct zlog *zl) { + int result; + LOCK + result = uzlog_reset_file(zl); + 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; } @@ -720,39 +873,318 @@ int zlog_rotate (struct zlog *zl) { int level; + int result = 1; + + 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, safe_strerror(save_errno)); + free(fname); + result = -1; + } + else + { + logfile_fd = fileno(zl->fp); + zl->maxlvl[ZLOG_DEST_FILE] = level; + } + } + } + UNLOCK + return result; +} + +int +zlog_get_default_lvl (struct zlog *zl) +{ + int result = LOG_DEBUG; + + LOCK - if (zl->filename) + if (zl == NULL) + zl = zlog_default; + + 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; + UNLOCK + return result; +} + +void +zlog_set_default_lvl (struct zlog *zl, int level) +{ + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->default_lvl = level; } - return 1; + UNLOCK } - + +/* Set logging level and default for all destinations */ +void +zlog_set_default_lvl_dest (struct zlog *zl, int level) +{ + int i; + + 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; + } + + UNLOCK +} + +int +zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest) +{ + int result = ZLOG_DISABLED; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->maxlvl[dest]; + } + + UNLOCK + return result; +} + +int +zlog_get_facility (struct zlog *zl) +{ + int result = LOG_DAEMON; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->facility; + } + + UNLOCK + return result; +} + +void +zlog_set_facility (struct zlog *zl, int facility) +{ + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->facility = facility; + } + + UNLOCK +} + +int +zlog_get_record_priority (struct zlog *zl) +{ + int result = 0; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->record_priority; + } + + UNLOCK + return result; +} + +void +zlog_set_record_priority (struct zlog *zl, int record_priority) +{ + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->record_priority = record_priority; + } + UNLOCK +} + +int +zlog_get_timestamp_precision (struct zlog *zl) +{ + int result = 0; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->timestamp_precision; + } + UNLOCK + return result; +} + +void +zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision) +{ + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + zl->timestamp_precision = timestamp_precision; + } + + 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; + LOCK + result = uzlog_get_proto_name(zl); + 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; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL && zl->filename != NULL) + { + result = strdup(zl->filename); + } + + UNLOCK + return result; +} + +const char * +zlog_get_ident (struct zlog *zl) +{ + const char * result = NULL; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = zl->ident; + } + + UNLOCK + return result; +} + +/* logging to a file? */ +int +zlog_is_file (struct zlog *zl) +{ + int result = 0; + + LOCK + + if (zl == NULL) + zl = zlog_default; + + if (zl != NULL) + { + result = (zl->fp != NULL); + } + + UNLOCK; + return result; +} + /* Message lookup function. */ const char * lookup (const struct message *mes, int key) @@ -928,3 +1360,7 @@ proto_name2num(const char *s) return -1; } #undef RTSIZE + +#undef LOCK +#undef UNLOCK +#undef ASSERTLOCKED @@ -93,7 +93,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 +117,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,6 +154,23 @@ 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)") @@ -185,7 +209,9 @@ extern void zlog_backtrace_sigsafe(int priority, void *program_counter); *buf will be set to '\0', and 0 will be returned. */ extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, char *buf, size_t buflen); - +/* unprotected version for when mutex already held */ +extern size_t uquagga_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 */ diff --git a/lib/memory.h b/lib/memory.h index 037efef2..fd9f1b97 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 { @@ -33,10 +35,10 @@ struct mlist { 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) \ diff --git a/lib/memtypes.c b/lib/memtypes.c index 197fb88c..8146f21f 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -16,18 +16,30 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, { MTYPE_STRVEC, "String vector" }, - { MTYPE_VECTOR, "Vector structure" }, - { MTYPE_VECTOR_BODY, "Vector body" }, + { 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_BLOCKS, "Mqueue message blocks" }, + { 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_VTY, "VTY" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, { MTYPE_VTY_HIST, "VTY history" }, diff --git a/lib/mqueue.c b/lib/mqueue.c new file mode 100644 index 00000000..7b93e4b0 --- /dev/null +++ b/lib/mqueue.c @@ -0,0 +1,615 @@ +/* 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 hold 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. + * + * Messages take the form of a small block of information which contains: + * + * * flags -- used by the message handler + * * context -- identifies the context of the message (see revoke) + * + * * action -- void action(mqueue_block) message dispatch + * * arg0 -- *void/uintptr_t/intptr_t ) standard arguments + * * arg1 -- *void/uintptr_t/intptr_t ) + * + * There are set/get functions for action/arg0/arg1 -- users should not poke + * around inside the structure. + * + * To send a message, first allocate a message block (see mqb_init_new), + * then fill in the arguments and enqueue it. + * + * + */ + +/*============================================================================== + * Initialisation etc. for Message Queues. + * + * TODO: how to shut down a message queue... for reset/exit ? + */ + +/* 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 enabled, it is TOO LATE to enable + * qpthreads. + */ + +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 */ + /* waiters set zero already */ + + mq->type = type ; + switch (type) + { + case mqt_cond_unicast: + case mqt_cond_broadcast: + if (qpthreads_enabled) + 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 ; +} ; + +/* 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. + */ +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 in lots of 256. Uses first message_block + * in each lot to keep track of the lots. + * + * mqueue_initialise MUST be called before the first message block is allocated. + */ + +static pthread_mutex_t mqb_mutex ; + +#define MQB_LOT_SIZE 256 + +static mqueue_block mqb_lot_list = NULL ; +static mqueue_block mqb_free_list = NULL ; + +static mqueue_block mqueue_block_new_lot(void) ; + +/* Initialise message block (allocate if required) and set action and context. + */ +mqueue_block +mqb_init_new(mqueue_block mqb, mqueue_action action, mqb_context_t context) +{ + if (mqb == NULL) + { + qpt_mutex_lock(&mqb_mutex) ; + + mqb = mqb_free_list ; + if (mqb == NULL) + mqb = mqueue_block_new_lot() ; + + mqb_free_list = mqb->next ; + + qpt_mutex_unlock(&mqb_mutex) ; + } ; + + memset(mqb, 0, sizeof(struct mqueue_block)) ; + + mqb->action = action ; + mqb->context = context ; + + return mqb ; +} ; + +/* Free message block when done with it. + */ +void +mqb_free(mqueue_block mqb) +{ + qpt_mutex_lock(&mqb_mutex) ; + + mqb->next = mqb_free_list ; + mqb_free_list = mqb ; + + qpt_mutex_unlock(&mqb_mutex) ; +} ; + +/* Make a new lot of empty message_block structures. + * + * NB: caller MUST hold the mqb_mutex. + */ +static mqueue_block +mqueue_block_new_lot(void) +{ + mqueue_block first, last, this ; + + mqueue_block new = XCALLOC(MTYPE_MQUEUE_BLOCKS, + SIZE(struct mqueue_block, MQB_LOT_SIZE)) ; + first = &new[1] ; + last = &new[MQB_LOT_SIZE - 1] ; + + new->next = mqb_lot_list ; /* add to list of lots */ + mqb_lot_list = new ; + + /* String all the new message_blocks together. */ + this = last ; + while (this > first) + { + mqueue_block prev = this-- ; + this->next = prev ; + } ; + assert(this == first) ; + + last->next = mqb_free_list ; /* point last at old free list */ + mqb_free_list = first ; /* new blocks at head of free list */ + + return mqb_free_list ; +} ; + +/*============================================================================== + * 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 != 0, will enqueue after any previously enqueued priority + * messages. + * + * 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. + * + * NB: this works perfectly well if !qpthreads enabled. Of course, there can + * never be any waiters... so no kicking is ever done. + */ + +void +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) +{ + qpt_mutex_lock(&mq->mutex) ; + + if (mq->head == NULL) + { + mqb->next = NULL ; + mq->head = mqb ; + mq->tail_priority = priority ? mqb : NULL ; + mq->tail = mqb ; + } + else if (priority) + { + mqueue_block after = mq->tail_priority ; + if (after == NULL) + { + mqb->next = mq->head ; + mq->head = mqb ; + } + else + { + mqb->next = after->next ; + after->next = mqb ; + } + mq->tail_priority = mqb ; + } + else + { + dassert(mq->tail != NULL) ; + mqb->next = NULL ; + mq->tail->next = mqb ; + mq->tail = mqb ; + } ; + + 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.) + */ + +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 ; + + qpt_mutex_lock(&mq->mutex) ; + + while (1) + { + mqb = mq->head ; + if (mqb != NULL) + break ; /* Easy if queue not empty */ + + 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) ; + + last = mq->kick.signal.tail ; + if (last == NULL) + { + mq->kick.signal.head = mtsig ; + mtsig->prev = (void*)mq ; + } + else + { + 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 */ + + mq->head = mqb->next ; + if (mqb == mq->tail_priority) + mq->tail_priority = NULL ; + +done: + qpt_mutex_unlock(&mq->mutex) ; + + return mqb ; +} ; + +/* 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.) + */ +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 ; +} ; + +/*============================================================================== + * 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. + */ +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 ; +} ; + +/* 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)) ) ; +} ; + +/*============================================================================== + * Initialise Message Queue handling + * + * Must be called before any qpt_threads are started. + * + * Freezes qpthreads_enabled. + * + * TODO: how do we shut down message queue handling ? + */ +void +mqueue_initialise(void) +{ + if (qpthreads_enabled_freeze) + qpt_mutex_init_new(&mqb_mutex, qpt_mutex_quagga) ; +} ; diff --git a/lib/mqueue.h b/lib/mqueue.h new file mode 100644 index 00000000..6fb519d4 --- /dev/null +++ b/lib/mqueue.h @@ -0,0 +1,282 @@ +/* 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 "qpthreads.h" +#include "qtime.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + */ + +typedef struct mqueue_block* mqueue_block ; + +typedef uint32_t mqb_flags_t ; +typedef uint32_t mqb_context_t ; + +typedef void* mqb_ptr_t ; +typedef intptr_t mqb_int_t ; +typedef uintptr_t mqb_uint_t ; + +typedef union +{ + mqb_ptr_t p ; + mqb_int_t i ; + mqb_uint_t u ; +} mqb_arg_t ; + +typedef void mqueue_action(mqueue_block mqb) ; + +struct mqueue_block +{ + mqueue_block next ; /* single linked list */ + + mqueue_action* action ; /* for message dispatch */ + + mqb_flags_t flags ; /* for message handler */ + + mqb_context_t context ; /* for message revoke */ + + mqb_arg_t arg0 ; /* may be pointer or integer */ + mqb_arg_t arg1 ; /* may be pointer or integer */ +} ; + +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) */ + + enum mqueue_queue_type type ; + + unsigned waiters ; + + union { + struct mqueue_queue_cond cond ; + struct mqueue_queue_signal signal ; + } kick ; +} ; + +/*============================================================================== + * Functions + */ + +void +mqueue_initialise(void) ; + +mqueue_queue +mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ; + +void +mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; + +mqueue_thread_signal +mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, + int signum) ; +mqueue_block +mqb_init_new(mqueue_block mqb, mqueue_action action, mqb_context_t context) ; + +#define mqb_new(action, context) mqb_init_new(NULL, action, context) + +void +mqb_free(mqueue_block mqb) ; + +void +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) ; + +mqueue_block +mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; + +int +mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; + +/*============================================================================== + * 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_context(mqueue_block mqb, mqb_context_t context) ; + +Inline void mqb_set_arg0_p(mqueue_block mqb, mqb_ptr_t p) ; +Inline void mqb_set_arg0_i(mqueue_block mqb, mqb_int_t i) ; +Inline void mqb_set_arg0_u(mqueue_block mqb, mqb_uint_t u) ; +Inline void mqb_set_arg1_p(mqueue_block mqb, mqb_ptr_t p) ; +Inline void mqb_set_arg1_i(mqueue_block mqb, mqb_int_t i) ; +Inline void mqb_set_arg1_u(mqueue_block mqb, mqb_uint_t u) ; + +Inline void mqb_dispatch(mqueue_block mqb) ; +Inline mqb_context_t mqb_qet_context(mqueue_block mqb) ; + +Inline mqb_ptr_t mqb_get_arg0_p(mqueue_block mqb) ; +Inline mqb_int_t mqb_get_arg0_i(mqueue_block mqb) ; +Inline mqb_uint_t mqb_get_arg0_u(mqueue_block mqb) ; +Inline mqb_ptr_t mqb_get_arg1_p(mqueue_block mqb) ; +Inline mqb_int_t mqb_get_arg1_i(mqueue_block mqb) ; +Inline mqb_uint_t mqb_get_arg1_u(mqueue_block mqb) ; + +/*============================================================================== + * The Inline functions. + */ + +/* Set operations. */ + +Inline void +mqb_set_action(mqueue_block mqb, mqueue_action action) +{ + mqb->action = action ; +} ; + +Inline void +mqb_set_context(mqueue_block mqb, mqb_context_t context) +{ + mqb->context = context ; +} ; + +Inline void +mqb_set_arg0_p(mqueue_block mqb, mqb_ptr_t p) +{ + mqb->arg0.p = p ; +} ; + +Inline void +mqb_set_arg0_i(mqueue_block mqb, mqb_int_t i) +{ + mqb->arg0.i = i ; +} ; + +Inline void +mqb_set_arg0_u(mqueue_block mqb, mqb_uint_t u) +{ + mqb->arg0.u = u ; +} ; + +Inline void +mqb_set_arg1_p(mqueue_block mqb, mqb_ptr_t p) +{ + mqb->arg1.p = p ; +} ; + +Inline void +mqb_set_arg1_i(mqueue_block mqb, mqb_int_t i) +{ + mqb->arg1.i = i ; +} ; + +Inline void +mqb_set_arg1_u(mqueue_block mqb, mqb_uint_t u) +{ + mqb->arg1.u = u ; +} ; + +/* Get operations */ + +Inline void +mqb_dispatch(mqueue_block mqb) +{ + mqb->action(mqb) ; +} ; + +Inline mqb_context_t +mqb_qet_context(mqueue_block mqb) +{ + return mqb->context ; +} ; + +Inline mqb_ptr_t +mqb_get_arg0_p(mqueue_block mqb) +{ + return mqb->arg0.p ; +} ; + +Inline mqb_int_t +mqb_get_arg0_i(mqueue_block mqb) +{ + return mqb->arg0.i ; +} ; + +Inline mqb_uint_t +mqb_get_arg0_u(mqueue_block mqb) +{ + return mqb->arg0.u ; +} ; + +Inline mqb_ptr_t +mqb_get_arg1_p(mqueue_block mqb) +{ + return mqb->arg1.p ; +} ; + +Inline mqb_int_t +mqb_get_arg1_i(mqueue_block mqb) +{ + return mqb->arg1.i ; +} ; + +Inline mqb_uint_t +mqb_get_arg1_u(mqueue_block mqb) +{ + return mqb->arg1.u ; +} ; + +#endif /* _ZEBRA_MQUEUE_H */ diff --git a/lib/plist.c b/lib/plist.c index 10d5e31c..b01c48f6 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -1378,7 +1378,7 @@ vty_show_prefix_entry (struct vty *vty, struct prefix_list *plist, { /* 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) { diff --git a/lib/privs.c b/lib/privs.c index 69606f57..68757340 100644 --- a/lib/privs.c +++ b/lib/privs.c @@ -25,6 +25,10 @@ #include "log.h" #include "privs.h" #include "memory.h" +#include "qpthreads.h" + +/* Needs to be qpthread safe */ +static qpt_mutex_t* mx = NULL; #ifdef HAVE_CAPABILITIES /* sort out some generic internal types for: @@ -61,6 +65,7 @@ typedef priv_set_t *pstorage_t; * 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 @@ -187,25 +192,42 @@ int zprivs_change_caps (zebra_privs_ops_t op) { cap_flag_value_t cflag; - + int result = 0; + int change = 0; + + qpt_mutex_lock(mx); + /* 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); - + { + qpt_mutex_unlock(mx); + 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, + 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); + + qpt_mutex_unlock(mx); + return result; } zebra_privs_current_t @@ -213,11 +235,17 @@ zprivs_state_caps (void) { int i; cap_flag_value_t val; + zebra_privs_current_t result = ZPRIVS_LOWERED; + + qpt_mutex_lock(mx); /* 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); + { + qpt_mutex_unlock(mx); + exit (1); + } for (i=0; i < zprivs_state.syscaps_p->num; i++) { @@ -226,12 +254,18 @@ zprivs_state_caps (void) { zlog_warn ("zprivs_state_caps: could not cap_get_flag, %s", safe_strerror (errno) ); - return ZPRIVS_UNKNOWN; + result = ZPRIVS_UNKNOWN; + break; } if (val == CAP_SET) - return ZPRIVS_RAISED; + { + result = ZPRIVS_RAISED; + break; + } } - return ZPRIVS_LOWERED; + + qpt_mutex_unlock(mx); + return result; } static void @@ -376,12 +410,16 @@ zcaps2sys (zebra_capabilities_t *zcaps, int num) int zprivs_change_caps (zebra_privs_ops_t op) { + int result = 0; + qpt_mutex_lock(mx); + /* 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__); + qpt_mutex_unlock(mx); exit (1); } @@ -389,49 +427,66 @@ zprivs_change_caps (zebra_privs_ops_t op) * 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; + result = -1; - if (setppriv (PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps) != 0) - return -1; + qpt_mutex_unlock(mx); - return 0; + return result; } /* Retrieve current privilege state, is it RAISED or LOWERED? */ zebra_privs_current_t zprivs_state_caps (void) { - zebra_privs_current_t result; + zebra_privs_current_t result = ZPRIVS_UNKNOWN; pset_t *effective; + qpt_mutex_lock(mx); + 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; } 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__, + safe_strerror (errno)); + } 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); - + qpt_mutex_unlock(mx); return result; } @@ -560,19 +615,42 @@ zprivs_caps_terminate (void) int zprivs_change_uid (zebra_privs_ops_t op) { + int result = 0; + + qpt_mutex_lock(mx); 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; + } + + qpt_mutex_unlock(mx); + return result; } zebra_privs_current_t zprivs_state_uid (void) { - return ( (zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED); + zebra_privs_current_t result; + + qpt_mutex_lock(mx); + result = ( (zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED); + qpt_mutex_unlock(mx); + return result; } int @@ -584,7 +662,25 @@ zprivs_change_null (zebra_privs_ops_t op) zebra_privs_current_t zprivs_state_null (void) { - return zprivs_null_state; + int result; + + qpt_mutex_lock(mx); + result = zprivs_null_state; + qpt_mutex_unlock(mx); + return result; +} + +void +zprivs_init_r(struct zebra_privs_t *zprivs) +{ + mx = qpt_mutex_init(mx, qpt_mutex_quagga); + zprivs_init(zprivs); +} + +void +zprivs_destroy_r(void) +{ + mx = qpt_mutex_destroy(mx, 1); } void @@ -599,12 +695,15 @@ zprivs_init(struct zebra_privs_t *zprivs) exit (1); } + qpt_mutex_lock(mx); + /* NULL privs */ if (! (zprivs->user || zprivs->group || zprivs->cap_num_p || zprivs->cap_num_i) ) { zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; + qpt_mutex_unlock(mx); return; } @@ -619,6 +718,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); + qpt_mutex_unlock(mx); exit (1); } } @@ -635,6 +735,7 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not setgroups, %s\n", safe_strerror (errno) ); + qpt_mutex_unlock(mx); exit (1); } } @@ -642,6 +743,7 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not lookup vty group %s\n", zprivs->vty_group); + qpt_mutex_unlock(mx); exit (1); } } @@ -656,6 +758,7 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not lookup group %s\n", zprivs->group); + qpt_mutex_unlock(mx); exit (1); } /* change group now, forever. uid we do later */ @@ -663,6 +766,7 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "zprivs_init: could not setregid, %s\n", safe_strerror (errno) ); + qpt_mutex_unlock(mx); exit (1); } } @@ -671,7 +775,7 @@ zprivs_init(struct zebra_privs_t *zprivs) 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. @@ -683,6 +787,7 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init (uid): could not setreuid, %s\n", safe_strerror (errno)); + qpt_mutex_unlock(mx); exit (1); } } @@ -690,6 +795,8 @@ zprivs_init(struct zebra_privs_t *zprivs) zprivs->change = zprivs_change_uid; zprivs->current_state = zprivs_state_uid; #endif /* HAVE_CAPABILITIES */ + + qpt_mutex_unlock(mx); } void @@ -701,6 +808,8 @@ zprivs_terminate (struct zebra_privs_t *zprivs) exit (0); } + qpt_mutex_lock(mx); + #ifdef HAVE_CAPABILITIES zprivs_caps_terminate(); #else /* !HAVE_CAPABILITIES */ @@ -710,6 +819,7 @@ zprivs_terminate (struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_terminate: could not setreuid, %s", safe_strerror (errno) ); + qpt_mutex_unlock(mx); exit (1); } } @@ -718,20 +828,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; + + qpt_mutex_unlock(mx); return; } void zprivs_get_ids(struct zprivs_ids_t *ids) { + qpt_mutex_lock(mx); - 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); + qpt_mutex_unlock(mx); return; } diff --git a/lib/privs.h b/lib/privs.h index 46d614e0..18d35d8d 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 (struct zebra_privs_t *zprivs); extern void zprivs_init (struct zebra_privs_t *zprivs); +extern void zprivs_destroy_r (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/qpnexus.c b/lib/qpnexus.c new file mode 100644 index 00000000..01985d29 --- /dev/null +++ b/lib/qpnexus.c @@ -0,0 +1,120 @@ +/* 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 "qpnexus.h" +#include "memory.h" + +/* prototypes */ +static void* qpn_start(void* arg); + +/*============================================================================== + * Quagga Nexus Interface -- qpt_xxxx + * + + */ + +/* Initialise a nexus -- allocating it if required. + * + * Returns the qtn_nexus. + */ +qpn_nexus +qpn_init_new(qpn_nexus qpn) +{ + 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); + + /* TODO mqueue initialisation */ + + return qpn; +} + +void +qpn_free(qpn_nexus qpn) +{ + /* timers and the pile */ + qtimer qtr; + while ((qtr = qtimer_pile_ream(qpn->pile, 1))) + { + qtimer_free(qtr); + } + + /* TODO: free qtn->selection */ + + /* TODO: free qtn->queue */ + + XFREE(MTYPE_QPN_NEXUS, qpn) ; +} + +/* Create and execute the qpthread */ +void +qpn_exec(qpn_nexus qpn) +{ + qpn->thread_id = qpt_thread_create(qpn_start, qpn, NULL) ; +} + +static void* +qpn_start(void* arg) +{ + qpn_nexus qpn = arg; + int actions; + + while (!qpn->terminate) + { + qtime_mono_t now = qt_get_monotonic(); + + /* process timers */ + while (qtimer_pile_dispatch_next(qpn->pile, now)) + { + } + + /* block for some input, output or timeout */ + actions = qps_pselect( qpn->selection, + qtimer_pile_top_time(qpn->pile, now + QTIME(MAX_PSELECT_TIMOUT)) ); + + /* process I/O actions */ + while (actions) + { + actions = qps_dispatch_next(qpn->selection) ; + } + + /* TODO process message queue */ + } + + qpn_free(qpn); + + return NULL; +} + + + + diff --git a/lib/qpnexus.h b/lib/qpnexus.h new file mode 100644 index 00000000..ce546edd --- /dev/null +++ b/lib/qpnexus.h @@ -0,0 +1,86 @@ +/* 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_TIMOUT 10 + +/*============================================================================== + * Data Structures. + */ + +typedef struct qpn_nexus* qpn_nexus ; + +struct qpn_nexus +{ + /* set true to terminate the thread (eventually) */ + int terminate; + + /* thread ID */ + qpt_thread_t thread_id; + + /* pselect handler */ + qps_selection selection; + + /* timer pile */ + qtimer_pile pile; + + /* message queue */ + mqueue_queue queue; + +}; + +/*============================================================================== + * Functions + */ + +extern qpn_nexus qpn_init_new(qpn_nexus qtn); +extern void qpn_exec(qpn_nexus qtn); +void qpn_free(qpn_nexus qpn); + +#endif /* _ZEBRA_QPNEXUS_H */ diff --git a/lib/qpselect.c b/lib/qpselect.c new file mode 100644 index 00000000..cf1df985 --- /dev/null +++ b/lib/qpselect.c @@ -0,0 +1,1321 @@ +/* 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" + +/*============================================================================== + * 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. + */ + +static int qps_super_set_map_made = 0 ; + +static void qps_make_super_set_map(void) ; + +/*============================================================================== + * qps_selection handling + */ + +/* Forward references */ +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_next_fd_pending(fd_super_set* pending, int fd, int fd_last) ; +static void qps_selection_validate(qps_selection qps) ; + +/* See qps_make_super_set_map() and qps_pselect() below. */ +static short fd_byte_count[FD_SETSIZE] ; /* number of bytes for fds 0..fd */ + +/* Initialise a selection -- allocating it if required. + * + * Returns the qps_selection. + */ +qps_selection +qps_selection_init_new(qps_selection qps) +{ + if (!qps_super_set_map_made) + qps_make_super_set_map() ; /* map the fd_super_set */ + + if (qps == NULL) + qps = XCALLOC(MTYPE_QPS_SELECTION, sizeof(struct qps_selection)) ; + else + memset(qps, 0, sizeof(struct qps_selection)) ; + + /* Zeroising initialises: + * + * fd_count -- no fd's yet + * fd_direct -- not direct lookup + * + * files -- empty vector + * + * fd_last -- unset + * 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 else to do -- see also qps_selection_re_init(), below. + */ + + return qps ; +} ; + +/* Re-initialise a selection. + */ +static void +qps_selection_re_init(qps_selection qps) +{ + memset(qps, 0, sizeof(struct qps_selection)) ; +} ; + +/* 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_ream_keep(&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 timeout + * *time*. + * + * The time-out time is an "absolute" time, as measured by qt_get_monotonic(). + * + * A timeout time <= the current qt_get_monotonic() is treated as a zero + * timeout period, and will return immediately from the pselect. + * + * 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_mono_t timeout) +{ + struct timespec ts ; + qps_mnum_t mnum ; + fd_set* p_fds[qps_mnum_count] ; + int n ; + + /* TODO: put this under a debug skip */ + 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 = fd_byte_count[qps->fd_last] ; /* copy up to last sig. byte */ + + qps->pend_mnum = qps_mnum_count ; + for (mnum = 0 ; mnum < qps_mnum_count ; ++mnum) + if ((qps->tried_count[mnum] = qps->enabled_count[mnum]) != 0) + { + 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 ; + } + 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 ; + + /* Convert timeout time to interval for pselect() */ + timeout -= qt_get_monotonic() ; + if (timeout < 0) + timeout = 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, timeout), + (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 */ + } ; + + /* Flush the results vectors -- not apparently done if n <= 0) */ + qps_super_set_zero(qps->results, qps_mnum_count) ; + + qps->pend_count = 0 ; /* nothing pending */ + + /* 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). + */ +int +qps_dispatch_next(qps_selection qps) +{ + int fd ; + qps_file qf ; + qps_mnum_t mnum ; + + /* TODO: put this under a debug skip */ + qps_selection_validate(qps) ; + + if (qps->pend_count == 0) + return 0 ; /* quit immediately of nothing to do. */ + + fd = qps->pend_fd ; + mnum = qps->pend_mnum ; + + dassert( (mnum >= 0) && (mnum < qps_mnum_count) + && (qps->tried_count[mnum] != 0) + && (qps->pend_count > 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 + */ + + if (template != NULL) + memcpy(qf->actions, template->actions, sizeof(qf->actions)) ; + + return qf ; +} ; + +/* Free dynamically allocated qps_file structure. + * + * It is the caller's responsibility to have removed it from any selection it + * may have been in. + */ +void +qps_file_free(qps_file qf) +{ + assert(qf->selection == NULL) ; /* Mustn't be a selection member ! */ + + XFREE(MTYPE_QPS_FILE, qf) ; +} ; + +/* 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 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 < 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( ((qps->fd_count != 0) && (fd_last >= 0)) || + ((qps->fd_count == 0) && (fd_last < 0)) ) ; + + qps->fd_last = (fd_last >= 0) ? fd_last : 0 ; + + /* 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. */ + +#define QPS_TESTING 0 /* true => testing */ + +#if !QPS_TESTING + +/* Not testing, so map to the standard FD_SET etc. functions. */ +# define qFD_SET FD_SET +# define qFD_CLR FD_CLR +# define qFD_ISSET FD_ISSET +# define qFD_ZERO FD_ZERO + +#else + +/* Set up the testing */ + +# define QPS_TEST_WORD 4 /* Wordsize */ +# define QPS_TEST_BE 1 /* true => big-endian */ +# define QPS_TEST_B_ORD 07 /* 07 => bits 0..7, 70 => bits 7..0 */ + +# define QPS_TEST_WORD_BITS (QPS_TEST_WORD * 8) +# if QPS_TEST_BE +# define QPS_BYTE(fd) ( ((fd / QPS_TEST_WORD_BITS) * QPS_TEST_WORD) \ + + (QPS_TEST_WORD - 1) - ((fd % QPS_TEST_WORD_BITS) / 8) ) +#else +# define QPS_BYTE(fd) ( fd / 8 ) +#endif + +# if QPS_TEST_B_ORD == 07 +# define QPS_BIT(fd) (0x01 << (fd & 0x7)) +# else +# define QPS_BIT(fd) (0x80 >> (fd & 0x7)) +# endif + + static void + qFD_SET(int fd, fd_set* set) + { + *((uint8_t*)set + QPS_BYTE(fd)) |= QPS_BIT(fd) ; + } ; + + static void + qFD_CLR(int fd, fd_set* set) + { + *((uint8_t*)set + QPS_BYTE(fd)) &= ~QPS_BIT(fd) ; + } ; + + static int + qFD_ISSET(int fd, fd_set* set) + { + return (*((uint8_t*)set + QPS_BYTE(fd)) & QPS_BIT(fd)) != 0 ; + } ; + + static void + qFD_ZERO(fd_set* set) + { + memset(set, 0, sizeof(fd_set)) ; + } ; + +#endif + +/* 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 ; + + 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 < FD_SETSIZE ; ++fd) + if (qFD_ISSET(fd, &test.fdset)) + zabort("Zeroised fd_super_set is not empty") ; + + /* (2) check that zeroising the fd_set doesn't change things */ + qFD_ZERO(&test.fdset) ; + for (iw = 0 ; iw < 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 < FD_SETSIZE ; ++fd) + { + fd_word_t w ; + + qFD_SET(fd, &test.fdset) ; + + w = 0 ; + for (iw = 0 ; iw < 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") ; + + qFD_CLR(fd, &test.fdset) ; + + for (iw = 0 ; iw < 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 < 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 < 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 < 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 ; + } ; + + 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 < 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_TESTING + + /* Checking that the maps have been correctly deduced */ + + for (fd = 0 ; fd < FD_SETSIZE ; ++fd) + { + uint8_t b ; + short c ; + + iw = fd / QPS_TEST_WORD_BITS ; + if (QPS_TEST_BE) + ib = ( ((fd / QPS_TEST_WORD_BITS) * QPS_TEST_WORD) + + (QPS_TEST_WORD - 1) - ((fd % QPS_TEST_WORD_BITS) / 8) ) ; + else + ib = ( fd / 8 ) ; + + if (QPS_TEST_B_ORD == 07) + b = 0x01 << (fd % 8) ; + else + b = 0x80 >> (fd % 8) ; + + if (QPS_TEST_BE) + c = (iw + 1) * QPS_TEST_WORD ; + 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_TEST_B_ORD == 07) + { + 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") ; + } ; + + zabort("OK fd mapping") ; +#endif + + /* Phew -- we're all set now */ + qps_super_set_map_made = 1 ; +} ; + +/* 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)) ; +} ; + +#if 0 /* Mask unused function */ +/* Copy 'n' contiguous fd_super_sets + */ +static void +qps_super_set_copy(fd_super_set* p_dst, fd_super_set* p_src, int n) +{ + memcpy(p_dst, p_src, SIZE(fd_super_set, n)) ; +} ; +#endif + +/* Compare 'n' contiguous fd_super_sets + */ +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 ( ((n == 0) && (qps->fd_last !=0)) || (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 < 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..1e67d174 --- /dev/null +++ b/lib/qpselect.h @@ -0,0 +1,208 @@ +/* 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 ; + +/* 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 we are looking after */ + 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 + */ + +qps_selection +qps_selection_init_new(qps_selection qps) ; + +void +qps_add_file(qps_selection qps, qps_file qf, int fd, void* file_info) ; + +void +qps_remove_file(qps_file qf) ; + +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) + +void +qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ; + +int +qps_pselect(qps_selection qps, qtime_mono_t timeout) ; + +int +qps_dispatch_next(qps_selection qps) ; + +/*============================================================================== + * qps_file structure handling + */ + +qps_file +qps_file_init_new(qps_file qf, qps_file template) ; + +void +qps_file_free(qps_file qf) ; + +void +qps_enable_mode(qps_file qf, qps_mnum_t mnum, qps_action* action) ; + +void +qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) ; + +void +qps_disable_modes(qps_file qf, qps_mbit_t mbits) ; + +#endif /* _ZEBRA_QPSELECT_H */ diff --git a/lib/qpthreads.c b/lib/qpthreads.c new file mode 100644 index 00000000..e7a8da2f --- /dev/null +++ b/lib/qpthreads.c @@ -0,0 +1,713 @@ +/* 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 "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. + * + * 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. + * + * Before the first thread is created and before any mutexes or condition + * variables are initialised, the qpthreads_enabled MUST be set. And it MUST + * not be changed again ! + * + * 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 ; + +int qpthreads_enabled_flag = 0 ; + +/* Function to set qpthreads_enabled, one way or the other. + * + * NB: can repeatedly set to the same state, but not change state once set. + */ +void +qpt_set_qpthreads_enabled(int how) +{ + + switch (qpthreads_enabled_state) + { + case qpt_state_unset: + break ; + case qpt_state_set_frozen: + if (how != 0) + zabort("Too late to enable qpthreads") ; + 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 ; +} ; + +/* 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) ; + + 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 ; +} ; + +/*============================================================================== + * 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) + 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) + 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..49a7c1e3 --- /dev/null +++ b/lib/qpthreads.h @@ -0,0 +1,417 @@ +/* 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 "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. + * + * 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 int)qpthreads_enabled_flag) +#define qpthreads_enabled_freeze qpt_freeze_qpthreads_enabled() + +/*============================================================================== + * 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) ; + +/*============================================================================== + * qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION ! + */ +private int qpthreads_enabled_flag ; /* DO NOT WRITE TO THIS PLEASE */ + +private void +qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */ + +private int +qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */ + +/*============================================================================== + * Thread self knowledge -- returns 'NULL' if !qpthreads_enabled + */ + +Inline qpt_thread_t qpt_thread_self(void) +{ + return qpthreads_enabled ? pthread_self() : (qpt_thread_t)NULL; +} ; + +/*============================================================================== + * 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/qtime.c b/lib/qtime.c new file mode 100644 index 00000000..3ec34a72 --- /dev/null +++ b/lib/qtime.c @@ -0,0 +1,194 @@ +/* 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) ; +} ; diff --git a/lib/qtime.h b/lib/qtime.h new file mode 100644 index 00000000..35e1a51b --- /dev/null +++ b/lib/qtime.h @@ -0,0 +1,307 @@ +/* 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 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) ; + +/* 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; +} ; + +/* 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..d4aff6ec --- /dev/null +++ b/lib/qtimers.c @@ -0,0 +1,336 @@ +/* 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" + +/*============================================================================== + * 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. + * + * 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 the qtimer structure may NOT be + * freed. + */ + +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 + * + * unset_pending -- NULL -- nothing pending + */ + + /* Eclipse flags offsetof(struct qtimer, backlink) as a syntax error :-( */ + 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. + */ +qtime_mono_t +qtimer_pile_top_time(qtimer_pile qtp, qtime_mono_t max_time) +{ + qtimer qtr = heap_top_item(&qtp->timers) ; + + if ((qtr == NULL) || (qtr->time >= max_time)) + return max_time ; + else + return qtr->time ; +} ; + +/* 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). + */ +int +qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) +{ + qtimer qtr ; + + qtr = heap_top_item(&qtp->timers) ; + if ((qtr != NULL) && (qtr->time <= upto)) + { + qtr->state = qtr_state_unset_pending ; + + qtr->action(qtr, qtr->timer_info, upto) ; + + if (qtr->state == qtr_state_unset_pending) + qtimer_unset(qtr) ; + + return 1 ; + } + else + return 0 ; +} ; + +/* 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->state = qtr_state_inactive ; /* 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 + * + * state -- not active + * + * time -- unset + * action -- NULL -- no action set (yet) + * timer_info -- NULL -- no timer info set (yet) + */ + + confirm(qtr_state_inactive == 0) ; + + qtr->pile = qtp ; + qtr->action = action ; + qtr->timer_info = timer_info ; + + return qtr ; +} ; + +/* Free given timer. + * + * Unsets it first if it is active. + * + * The timer MAY NOT be currently the subject of qtimer_pile_dispatch_next(). + */ +void +qtimer_free(qtimer qtr) +{ + assert(qtr->state != qtr_state_unset_pending) ; + + if (qtr->state != qtr_state_inactive) + qtimer_unset(qtr) ; + + XFREE(MTYPE_QTIMER, qtr) ; +} ; + +/* Set pile in which given timer belongs. + * + * Unsets the timer if active in another pile. + * (Does nothing if active in the "new" pile.) + */ +void +qtimer_set_pile(qtimer qtr, qtimer_pile qtp) +{ + if (qtr_is_active(qtr) && (qtr->pile != qtp)) + qtimer_unset(qtr) ; + qtr->pile = qtp ; +} + +/* Set action for given timer. + */ +void +qtimer_set_action(qtimer qtr, qtimer_action* action) +{ + qtr->action = action ; +} ; + +/* Set timer_info for given timer. + */ +void +qtimer_set_info(qtimer qtr, void* timer_info) +{ + qtr->timer_info = timer_info ; +} ; + +/* 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. + */ +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 ; + dassert(qtp != NULL) ; + + qtr->time = when ; + + if (qtr_is_active(qtr)) + heap_update_item(&qtp->timers, qtr) ; /* update in heap */ + else + heap_push_item(&qtp->timers, qtr) ; /* add to heap */ + + qtr->state = qtr_state_active ; /* overrides any unset pending */ + + if (action != NULL) + qtr->action = action ; + else + dassert(qtr->action != NULL) ; +} ; + +/* Unset given timer + * + * If the timer is active, removes from pile and sets inactive. + */ +void +qtimer_unset(qtimer qtr) +{ + if (qtr_is_active(qtr)) + { + qtimer_pile qtp = qtr->pile ; + dassert(qtp != NULL) ; + + heap_delete_item(&qtp->timers, qtr) ; + + qtr->state = qtr_state_inactive ; /* overrides any unset pending */ + } ; +} ; diff --git a/lib/qtimers.h b/lib/qtimers.h new file mode 100644 index 00000000..3a774b38 --- /dev/null +++ b/lib/qtimers.h @@ -0,0 +1,144 @@ +/* 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 "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) ; + +enum qtimer_state { + qtr_state_inactive = 0, + qtr_state_active = 1, /* timer is active in its pile */ + qtr_state_unset_pending = 3 /* timer is active, but unset is pending */ +} ; + +#define qtr_is_active(qtr) ((qtr)->state != qtr_state_inactive) + +typedef enum qtimer_state qtimer_state_t ; + +struct qtimer +{ + qtimer_pile pile ; + heap_backlink_t backlink ; + + qtimer_state_t state ; + + qtime_mono_t time ; + qtimer_action* action ; + void* timer_info ; +} ; + +struct qtimer_pile +{ + struct heap timers ; +} ; + +/*============================================================================== + * Functions + */ + +qtimer_pile +qtimer_pile_init_new(qtimer_pile qtp) ; + +int +qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ; + +qtime_mono_t +qtimer_pile_top_time(qtimer_pile qtp, qtime_mono_t max_time) ; + +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) + +qtimer +qtimer_init_new(qtimer qtr, qtimer_pile qtp, + qtimer_action* action, void* timer_info) ; +void +qtimer_set_pile(qtimer qtr, qtimer_pile qtp) ; + +void +qtimer_set_action(qtimer qtr, qtimer_action* action) ; + +void +qtimer_set_info(qtimer qtr, void* timer_info) ; + +void +qtimer_free(qtimer qtr) ; + +void +qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) ; + +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 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 ; +} ; + +#endif /* _ZEBRA_QTIMERS_H */ diff --git a/lib/routemap.c b/lib/routemap.c index 4f4e6d62..2dfa5a46 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -207,7 +207,7 @@ 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) diff --git a/lib/vector.c b/lib/vector.c index 8268c830..ad2af289 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -234,6 +234,38 @@ vector_ream(vector v, int free_structure) } ; /*============================================================================== + * Unset item. + */ + +/* 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 vetor 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 + was = NULL ; + + /* Now move end back past any NULLs */ + i = v->end ; + while ((i > 0) && (v->p_items[i-1] == NULL)) + --i ; + v->end = i ; + + return was ; +} ; + +/*============================================================================== * Inserting and deleting items. */ @@ -822,36 +854,6 @@ vector_lookup_ensure (vector v, vector_index i) return v->p_items[i]; } -/* Unset value at specified index slot. */ -void -vector_unset (vector v, vector_index i) -{ - vector_index j ; - if (i >= v->end) - return; /* Everything beyond or at end is implicitly NULL */ - - v->p_items[i] = NULL; - - /* See if everything ahead of 'i' is also NULL. */ - j = i ; - while (++j < v->end) - if (v->p_items[j] != NULL) - return ; /* Finished if anything ahead 'i' is not NULL */ - - /* Everything from 'i' onwards is NULL. - * Step backwards across any NULLs and then set the new end. - */ -#if 0 - v->end--; - while (i && v->p_items[--i] == NULL && v->end--) - ; /* Is this ugly ? */ -#endif - while ((i != 0) && (v->p_items[i - 1] == NULL)) - --i ; - - v->end = i ; -} - /* Count the number of not empty slots. */ vector_index vector_count (vector v) diff --git a/lib/vector.h b/lib/vector.h index 3a7e7ca5..2897cb51 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -107,7 +107,7 @@ Inline void vector_ensure(vector v, vector_index i) ; extern int vector_empty_slot (vector v); extern int vector_set (vector v, void *val); extern int vector_set_index (vector v, vector_index i, void *val); -extern void vector_unset (vector v, vector_index i); +#define vector_unset(v, i) (void)vector_unset_item(v, i) extern vector_index vector_count (vector v); extern void vector_only_wrapper_free (vector v); extern void vector_only_index_free (void *index); @@ -138,6 +138,7 @@ 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) ; +extern p_vector_item vector_unset_item(vector v, vector_index i) ; 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, @@ -38,6 +38,36 @@ #include "network.h" #include <arpa/telnet.h> +#include "qpthreads.h" +#include "qpnexus.h" + +/* Needs to be qpthread safe */ +qpt_mutex_t* vty_mutex = NULL; +#ifdef NDEBUG +#define LOCK qpt_mutex_lock(vty_mutex); +#define UNLOCK qpt_mutex_unlock(vty_mutex); +#else +int vty_lock_count = 0; +int vty_lock_asserted = 0; +#define LOCK qpt_mutex_lock(vty_mutex);++vty_lock_count; +#define UNLOCK --vty_lock_count;qpt_mutex_unlock(vty_mutex); +#define ASSERTLOCKED if(vty_lock_count==0 && !vty_lock_asserted){vty_lock_asserted=1;assert(0);} +#endif + +/* + * To make vty qpthread safe we use a single mutex. In general external + * routines have explicit locks, static routines assume that they are being + * called with the mutex already locked. There are a few exceptions, e.g. + * callbacks where static routines are being invoked from outside the module. + * + * As we are not using recursive mutexes so there are a few cases where + * both external and static versions of a routine exist. The former for use + * outside, the latter for use inside the module (and lock). In these cases + * the internal static versions starts uty_. + * + * 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. + */ /* Vty events */ enum event @@ -53,7 +83,24 @@ enum event #endif /* VTYSH */ }; +/* Prototypes */ +static int uty_out (struct vty *vty, const char *format, ...); +static int uty_vout(struct vty *vty, const char *format, va_list args); static void vty_event (enum event, int, struct vty *); +static void uty_hello (struct vty *vty); +static void uty_close (struct vty *vty); +static int uty_config_unlock (struct vty *vty); +static int uty_shell (struct vty *vty); +static int uty_read (struct vty *vty, int vty_sock); +static int uty_flush (struct vty *vty, int vty_sock); +static void vty_event_t (enum event event, int sock, struct vty *vty); +static void vty_event_r (enum event event, int sock, struct vty *vty); +static int uty_accept (int accept_sock); +static int uty_timeout (struct vty *vty); +static void vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when); +static void vty_read_r (qps_file qf, void* file_info); +static void vty_flush_r (qps_file qf, void* file_info); +void uty_reset (void); /* Extern host structure from command.c */ extern struct host host; @@ -71,10 +118,10 @@ static char *vty_accesslist_name = NULL; static char *vty_ipv6_accesslist_name = NULL; /* VTY server thread. */ -vector Vvty_serv_thread; +static vector Vvty_serv_thread; /* Current directory. */ -char *vty_cwd = NULL; +static char *vty_cwd = NULL; /* Configure lock. */ static int vty_config; @@ -89,29 +136,60 @@ static u_char restricted_mode = 0; /* Integrated configuration file path */ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; +/* Master of the threads. */ +static struct thread_master *master = NULL; +static qpn_nexus master_nexus = NULL; /* VTY standard output function. vty == NULL or VTY_SHELL => stdout */ int vty_out (struct vty *vty, const char *format, ...) { + int result; + + LOCK + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + UNLOCK + return result; +} + +/* internal VTY standard output function. vty == NULL or VTY_SHELL => stdout */ +static int +uty_out (struct vty *vty, const char *format, ...) +{ + int result; + ASSERTLOCKED va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + return result; +} + +/* internal VTY standard output function. vty == NULL or VTY_SHELL => stdout */ +static int +uty_vout(struct vty *vty, const char *format, va_list args) +{ int len = 0; int size = 1024; char buf[1024]; char *p = NULL; + va_list ac; - if (vty_shell (vty)) + ASSERTLOCKED + + if (uty_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); + va_copy(ac, args); + len = vsnprintf (buf, sizeof buf, format, ac); + va_end(ac); /* Initial buffer is not enough. */ if (len < 0 || len >= size) @@ -125,11 +203,11 @@ vty_out (struct vty *vty, const char *format, ...) p = XREALLOC (MTYPE_VTY_OUT_BUF, p, size); if (! p) - return -1; + return -1; - va_start (args, format); - len = vsnprintf (p, size, format, args); - va_end (args); + va_copy(ac, args); + len = vsnprintf (p, size, format, ac); + va_end(ac); if (len > -1 && len < size) break; @@ -180,9 +258,11 @@ vty_log_out (struct vty *vty, const char *level, const char *proto_str, int len; char buf[1024]; + ASSERTLOCKED + if (!ctl->already_rendered) { - ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); + ctl->len = uquagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); ctl->already_rendered = 1; } if (ctl->len+1 >= sizeof(buf)) @@ -213,7 +293,7 @@ vty_log_out (struct vty *vty, const char *level, const char *proto_str, 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", + uzlog(NULL, LOG_WARNING, "%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 @@ -248,6 +328,15 @@ vty_time_print (struct vty *vty, int cr) void vty_hello (struct vty *vty) { + LOCK + uty_hello(vty); + UNLOCK +} + +static void +uty_hello (struct vty *vty) +{ + ASSERTLOCKED if (host.motdfile) { FILE *f; @@ -263,15 +352,15 @@ vty_hello (struct vty *vty) for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); s--); *s = '\0'; - vty_out (vty, "%s%s", buf, VTY_NEWLINE); + uty_out (vty, "%s%s", buf, VTY_NEWLINE); } fclose (f); } else - vty_out (vty, "MOTD file not found%s", VTY_NEWLINE); + uty_out (vty, "MOTD file not found%s", VTY_NEWLINE); } else if (host.motd) - vty_out (vty, host.motd); + uty_out (vty, "%s", host.motd); } /* Put out prompt and wait input from user. */ @@ -281,6 +370,8 @@ vty_prompt (struct vty *vty) struct utsname names; const char*hostname; + ASSERTLOCKED + if (vty->type == VTY_TERM) { hostname = host.name; @@ -289,7 +380,7 @@ vty_prompt (struct vty *vty) uname (&names); hostname = names.nodename; } - vty_out (vty, cmd_prompt (vty->node), hostname); + uty_out (vty, cmd_prompt (vty->node), hostname); } } @@ -298,7 +389,8 @@ static void vty_will_echo (struct vty *vty) { unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' }; - vty_out (vty, "%s", cmd); + ASSERTLOCKED + uty_out (vty, "%s", cmd); } /* Make suppress Go-Ahead telnet option. */ @@ -306,7 +398,8 @@ static void vty_will_suppress_go_ahead (struct vty *vty) { unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' }; - vty_out (vty, "%s", cmd); + ASSERTLOCKED + uty_out (vty, "%s", cmd); } /* Make don't use linemode over telnet. */ @@ -314,7 +407,8 @@ static void vty_dont_linemode (struct vty *vty) { unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' }; - vty_out (vty, "%s", cmd); + ASSERTLOCKED + uty_out (vty, "%s", cmd); } /* Use window size. */ @@ -322,7 +416,8 @@ static void vty_do_window_size (struct vty *vty) { unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' }; - vty_out (vty, "%s", cmd); + ASSERTLOCKED + uty_out (vty, "%s", cmd); } #if 0 /* Currently not used. */ @@ -331,21 +426,30 @@ static void vty_dont_lflow_ahead (struct vty *vty) { unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW, '\0' }; - vty_out (vty, "%s", cmd); + ASSERTLOCKED + uty_out (vty, "%s", cmd); } #endif /* 0 */ /* Allocate new vty struct. */ struct vty * -vty_new () +vty_new (int fd) { - struct vty *new = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + struct vty *vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); - new->obuf = buffer_new(0); /* Use default buffer size. */ - new->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); - new->max = VTY_BUFSIZ; + vty->obuf = buffer_new(0); /* Use default buffer size. */ + vty->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); + vty->max = VTY_BUFSIZ; + vty->fd = fd; - return new; + if (master_nexus) + { + vty->qf = qps_file_init_new(vty->qf, NULL); + qps_add_file(master_nexus->selection, vty->qf, vty->fd, vty); + vty->qtr = qtimer_init_new(vty->qtr, master_nexus->pile, vty_timeout_r, vty); + } + + return vty; } /* Authentication of vty */ @@ -357,6 +461,8 @@ vty_auth (struct vty *vty, char *buf) int fail; char *crypt (const char *, const char *); + ASSERTLOCKED + switch (vty->node) { case AUTH_NODE: @@ -400,14 +506,14 @@ vty_auth (struct vty *vty, char *buf) { if (vty->node == AUTH_NODE) { - vty_out (vty, "%% Bad passwords, too many failures!%s", VTY_NEWLINE); + uty_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); + uty_out (vty, "%% Bad enable passwords, too many failures!%s", VTY_NEWLINE); vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; } } @@ -422,6 +528,8 @@ vty_command (struct vty *vty, char *buf) vector vline; const char *protocolname; + ASSERTLOCKED + /* Split readline string up into the vector */ vline = cmd_make_strvec (buf); @@ -437,20 +545,19 @@ vty_command (struct vty *vty, char *buf) GETRUSAGE(&before); #endif /* CONSUMED_TIME_CHECK */ + UNLOCK ret = cmd_execute_command (vline, vty, NULL, 0); + LOCK /* 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]; + protocolname = uzlog_get_proto_name(NULL); #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", + uzlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", realtime/1000, cputime/1000, buf); } #endif /* CONSUMED_TIME_CHECK */ @@ -460,16 +567,16 @@ vty_command (struct vty *vty, char *buf) { case CMD_WARNING: if (vty->type == VTY_FILE) - vty_out (vty, "Warning...%s", VTY_NEWLINE); + uty_out (vty, "Warning...%s", VTY_NEWLINE); break; case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + uty_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); + uty_out (vty, "%% [%s] Unknown command: %s%s", protocolname, buf, VTY_NEWLINE); break; case CMD_ERR_INCOMPLETE: - vty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); + uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); break; } cmd_free_strvec (vline); @@ -484,6 +591,7 @@ static const char telnet_space_char = ' '; static void vty_write (struct vty *vty, const char *buf, size_t nbytes) { + ASSERTLOCKED if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) return; @@ -495,6 +603,7 @@ vty_write (struct vty *vty, const char *buf, size_t nbytes) static void vty_ensure (struct vty *vty, int length) { + ASSERTLOCKED if (vty->max <= length) { vty->max *= 2; @@ -509,6 +618,8 @@ vty_self_insert (struct vty *vty, char c) int i; int length; + ASSERTLOCKED + vty_ensure (vty, vty->length + 1); length = vty->length - vty->cp; memmove (&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); @@ -526,6 +637,7 @@ vty_self_insert (struct vty *vty, char c) static void vty_self_insert_overwrite (struct vty *vty, char c) { + ASSERTLOCKED vty_ensure (vty, vty->length + 1); vty->buf[vty->cp++] = c; @@ -542,7 +654,11 @@ vty_self_insert_overwrite (struct vty *vty, char c) static void vty_insert_word_overwrite (struct vty *vty, char *str) { + int len = strlen (str); + + ASSERTLOCKED + vty_write (vty, str, len); strcpy (&vty->buf[vty->cp], str); vty->cp += len; @@ -553,6 +669,7 @@ vty_insert_word_overwrite (struct vty *vty, char *str) static void vty_forward_char (struct vty *vty) { + ASSERTLOCKED if (vty->cp < vty->length) { vty_write (vty, &vty->buf[vty->cp], 1); @@ -564,6 +681,7 @@ vty_forward_char (struct vty *vty) static void vty_backward_char (struct vty *vty) { + ASSERTLOCKED if (vty->cp > 0) { vty->cp--; @@ -575,6 +693,7 @@ vty_backward_char (struct vty *vty) static void vty_beginning_of_line (struct vty *vty) { + ASSERTLOCKED while (vty->cp) vty_backward_char (vty); } @@ -583,6 +702,7 @@ vty_beginning_of_line (struct vty *vty) static void vty_end_of_line (struct vty *vty) { + ASSERTLOCKED while (vty->cp < vty->length) vty_forward_char (vty); } @@ -597,6 +717,8 @@ vty_history_print (struct vty *vty) { int length; + ASSERTLOCKED + vty_kill_line_from_beginning (vty); /* Get previous line from history buffer */ @@ -614,6 +736,8 @@ vty_next_line (struct vty *vty) { int try_index; + ASSERTLOCKED + if (vty->hp == vty->hindex) return; @@ -639,6 +763,8 @@ vty_previous_line (struct vty *vty) { int try_index; + ASSERTLOCKED + try_index = vty->hp; if (try_index == 0) try_index = VTY_MAXHIST - 1; @@ -657,6 +783,7 @@ vty_previous_line (struct vty *vty) static void vty_redraw_line (struct vty *vty) { + ASSERTLOCKED vty_write (vty, vty->buf, vty->length); vty->cp = vty->length; } @@ -665,6 +792,7 @@ vty_redraw_line (struct vty *vty) static void vty_forward_word (struct vty *vty) { + ASSERTLOCKED while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') vty_forward_char (vty); @@ -676,6 +804,7 @@ vty_forward_word (struct vty *vty) static void vty_backward_pure_word (struct vty *vty) { + ASSERTLOCKED while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') vty_backward_char (vty); } @@ -684,6 +813,7 @@ vty_backward_pure_word (struct vty *vty) static void vty_backward_word (struct vty *vty) { + ASSERTLOCKED while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') vty_backward_char (vty); @@ -696,7 +826,8 @@ vty_backward_word (struct vty *vty) static void vty_down_level (struct vty *vty) { - vty_out (vty, "%s", VTY_NEWLINE); + ASSERTLOCKED + uty_out (vty, "%s", VTY_NEWLINE); (*config_exit_cmd.func)(NULL, vty, 0, NULL); vty_prompt (vty); vty->cp = 0; @@ -706,7 +837,8 @@ vty_down_level (struct vty *vty) static void vty_end_config (struct vty *vty) { - vty_out (vty, "%s", VTY_NEWLINE); + ASSERTLOCKED + uty_out (vty, "%s", VTY_NEWLINE); switch (vty->node) { @@ -734,7 +866,7 @@ vty_end_config (struct vty *vty) case KEYCHAIN_KEY_NODE: case MASC_NODE: case VTY_NODE: - vty_config_unlock (vty); + uty_config_unlock (vty); vty->node = ENABLE_NODE; break; default: @@ -753,6 +885,8 @@ vty_delete_char (struct vty *vty) int i; int size; + ASSERTLOCKED + if (vty->length == 0) { vty_down_level (vty); @@ -782,6 +916,7 @@ vty_delete_char (struct vty *vty) static void vty_delete_backward_char (struct vty *vty) { + ASSERTLOCKED if (vty->cp == 0) return; @@ -796,6 +931,8 @@ vty_kill_line (struct vty *vty) int i; int size; + ASSERTLOCKED + size = vty->length - vty->cp; if (size == 0) @@ -814,6 +951,7 @@ vty_kill_line (struct vty *vty) static void vty_kill_line_from_beginning (struct vty *vty) { + ASSERTLOCKED vty_beginning_of_line (vty); vty_kill_line (vty); } @@ -822,6 +960,7 @@ vty_kill_line_from_beginning (struct vty *vty) static void vty_forward_kill_word (struct vty *vty) { + ASSERTLOCKED while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') vty_delete_char (vty); while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') @@ -832,6 +971,7 @@ vty_forward_kill_word (struct vty *vty) static void vty_backward_kill_word (struct vty *vty) { + ASSERTLOCKED while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') vty_delete_backward_char (vty); while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') @@ -844,6 +984,8 @@ vty_transpose_chars (struct vty *vty) { char c1, c2; + ASSERTLOCKED + /* If length is short or point is near by the beginning of line then return. */ if (vty->length < 2 || vty->cp < 1) @@ -875,11 +1017,14 @@ vty_transpose_chars (struct vty *vty) static void vty_complete_command (struct vty *vty) { + int i; int ret; char **matched = NULL; vector vline; + ASSERTLOCKED + if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) return; @@ -895,11 +1040,11 @@ vty_complete_command (struct vty *vty) cmd_free_strvec (vline); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); switch (ret) { case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); vty_prompt (vty); vty_redraw_line (vty); break; @@ -929,11 +1074,11 @@ vty_complete_command (struct vty *vty) 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]); + uty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%-10s ", matched[i]); XFREE (MTYPE_TMP, matched[i]); } - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); vty_prompt (vty); vty_redraw_line (vty); @@ -957,11 +1102,13 @@ vty_describe_fold (struct vty *vty, int cmd_width, const char *cmd, *p; int pos; + ASSERTLOCKED + cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; if (desc_width <= 0) { - vty_out (vty, " %-*s %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE); + uty_out (vty, " %-*s %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE); return; } @@ -978,12 +1125,12 @@ vty_describe_fold (struct vty *vty, int cmd_width, strncpy (buf, p, pos); buf[pos] = '\0'; - vty_out (vty, " %-*s %s%s", cmd_width, cmd, buf, VTY_NEWLINE); + uty_out (vty, " %-*s %s%s", cmd_width, cmd, buf, VTY_NEWLINE); cmd = ""; } - vty_out (vty, " %-*s %s%s", cmd_width, cmd, p, VTY_NEWLINE); + uty_out (vty, " %-*s %s%s", cmd_width, cmd, p, VTY_NEWLINE); XFREE (MTYPE_TMP, buf); } @@ -992,12 +1139,14 @@ vty_describe_fold (struct vty *vty, int cmd_width, static void vty_describe_command (struct vty *vty) { - int ret; + int ret; vector vline; vector describe; unsigned int i, width, desc_width; struct desc *desc, *desc_cr = NULL; + ASSERTLOCKED + vline = cmd_make_strvec (vty->buf); /* In case of '> ?'. */ @@ -1012,17 +1161,17 @@ vty_describe_command (struct vty *vty) describe = cmd_describe_command (vline, vty, &ret); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); /* Ambiguous error. */ switch (ret) { case CMD_ERR_AMBIGUOUS: - vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + uty_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); + uty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); goto out; break; } @@ -1062,18 +1211,18 @@ vty_describe_command (struct vty *vty) } if (!desc->str) - vty_out (vty, " %-s%s", + uty_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, + uty_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 + uty_out (vty, " %-*s %s%s", width desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str ? desc->str : "", VTY_NEWLINE); #endif /* 0 */ @@ -1082,11 +1231,11 @@ vty_describe_command (struct vty *vty) if ((desc = desc_cr)) { if (!desc->str) - vty_out (vty, " %-s%s", + uty_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, + uty_out (vty, " %-*s %s%s", width, desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str, VTY_NEWLINE); else @@ -1105,6 +1254,7 @@ out: static void vty_clear_buf (struct vty *vty) { + ASSERTLOCKED memset (vty->buf, 0, vty->max); } @@ -1112,9 +1262,10 @@ vty_clear_buf (struct vty *vty) static void vty_stop_input (struct vty *vty) { + ASSERTLOCKED vty->cp = vty->length = 0; vty_clear_buf (vty); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); switch (vty->node) { @@ -1137,7 +1288,7 @@ vty_stop_input (struct vty *vty) case KEYCHAIN_KEY_NODE: case MASC_NODE: case VTY_NODE: - vty_config_unlock (vty); + uty_config_unlock (vty); vty->node = ENABLE_NODE; break; default: @@ -1156,6 +1307,8 @@ vty_hist_add (struct vty *vty) { int index; + ASSERTLOCKED + if (vty->length == 0) return; @@ -1191,46 +1344,48 @@ vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) #ifdef TELNET_OPTION_DEBUG int i; + ASSERTLOCKED + for (i = 0; i < nbytes; i++) { switch (buf[i]) { case IAC: - vty_out (vty, "IAC "); + uty_out (vty, "IAC "); break; case WILL: - vty_out (vty, "WILL "); + uty_out (vty, "WILL "); break; case WONT: - vty_out (vty, "WONT "); + uty_out (vty, "WONT "); break; case DO: - vty_out (vty, "DO "); + uty_out (vty, "DO "); break; case DONT: - vty_out (vty, "DONT "); + uty_out (vty, "DONT "); break; case SB: - vty_out (vty, "SB "); + uty_out (vty, "SB "); break; case SE: - vty_out (vty, "SE "); + uty_out (vty, "SE "); break; case TELOPT_ECHO: - vty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE); + uty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE); break; case TELOPT_SGA: - vty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE); + uty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE); break; case TELOPT_NAWS: - vty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE); + uty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE); break; default: - vty_out (vty, "%x ", buf[i]); + uty_out (vty, "%x ", buf[i]); break; } } - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); #endif /* TELNET_OPTION_DEBUG */ @@ -1255,11 +1410,11 @@ vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) { case TELOPT_NAWS: if (vty->sb_len != TELNET_NAWS_SB_LEN) - zlog_warn("RFC 1073 violation detected: telnet NAWS option " + uzlog(NULL, LOG_WARNING, "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, " + uzlog(NULL, LOG_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 @@ -1267,7 +1422,7 @@ vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) 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: " + uty_out(vty, "TELNET NAWS window size negotiation completed: " "width %d, height %d%s", vty->width, vty->height, VTY_NEWLINE); #endif @@ -1324,6 +1479,7 @@ vty_execute (struct vty *vty) static void vty_escape_map (unsigned char c, struct vty *vty) { + ASSERTLOCKED switch (c) { case ('A'): @@ -1350,22 +1506,51 @@ vty_escape_map (unsigned char c, struct vty *vty) static void vty_buffer_reset (struct vty *vty) { + ASSERTLOCKED buffer_reset (vty->obuf); vty_prompt (vty); vty_redraw_line (vty); } -/* Read data via vty socket. */ +/* Callback: qpthreads., Read data via vty socket. */ +static void +vty_read_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + /* is this necessary? */ + qps_disable_modes(qf, qps_read_mbit); + uty_read(vty, vty_sock); + + UNLOCK +} + +/* Callback: threads. Read data via vty socket. */ static int vty_read (struct thread *thread) { - int i; - int nbytes; - unsigned char buf[VTY_READ_BUFSIZ]; - int vty_sock = THREAD_FD (thread); struct vty *vty = THREAD_ARG (thread); + int result ; + + LOCK + vty->t_read = NULL; + result = uty_read(vty, vty_sock); + + UNLOCK + return result; +} + +static int +uty_read (struct vty *vty, int vty_sock) +{ + int i; + int nbytes; + unsigned char buf[VTY_READ_BUFSIZ]; /* Read raw data from socket */ if ((nbytes = read (vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) @@ -1378,7 +1563,7 @@ vty_read (struct thread *thread) return 0; } vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: read error on vty client fd %d, closing: %s", + uzlog(NULL, LOG_WARNING, "%s: read error on vty client fd %d, closing: %s", __func__, vty->fd, safe_strerror(errno)); } buffer_reset(vty->obuf); @@ -1526,7 +1711,7 @@ vty_read (struct thread *thread) break; case '\n': case '\r': - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vty, "%s", VTY_NEWLINE); vty_execute (vty); break; case '\t': @@ -1556,32 +1741,66 @@ vty_read (struct thread *thread) /* Check status. */ if (vty->status == VTY_CLOSE) - vty_close (vty); + uty_close (vty); else { vty_event (VTY_WRITE, vty_sock, vty); vty_event (VTY_READ, vty_sock, vty); } + return 0; } -/* Flush buffer to the vty. */ +/* Callback: qpthreads. Flush buffer to the vty. */ +static void +vty_flush_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + qps_disable_modes(qf, qps_write_mbit); + + /* Temporary disable read thread. */ + if ((vty->lines == 0)) + { + qps_disable_modes(qf, qps_read_mbit); + } + + uty_flush(vty, vty_sock); + + UNLOCK +} + +/* Callback: threads. Flush buffer to the vty. */ static int vty_flush (struct thread *thread) { - int erase; - buffer_status_t flushrc; int vty_sock = THREAD_FD (thread); struct vty *vty = THREAD_ARG (thread); + int result; + LOCK vty->t_write = NULL; - /* Tempolary disable read thread. */ + /* Temporary disable read thread. */ if ((vty->lines == 0) && vty->t_read) { thread_cancel (vty->t_read); vty->t_read = NULL; } + result = uty_flush(vty, vty_sock); + + UNLOCK + return result; +} + +static int +uty_flush (struct vty *vty, int vty_sock) +{ + int erase; + buffer_status_t flushrc; /* Function execution continue. */ erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); @@ -1601,14 +1820,14 @@ vty_flush (struct thread *thread) { case BUFFER_ERROR: vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("buffer_flush failed on vty client fd %d, closing", + uzlog(NULL, LOG_WARNING, "buffer_flush failed on vty client fd %d, closing", vty->fd); buffer_reset(vty->obuf); - vty_close(vty); - return 0; + uty_close(vty); + break; case BUFFER_EMPTY: if (vty->status == VTY_CLOSE) - vty_close (vty); + uty_close (vty); else { vty->status = VTY_NORMAL; @@ -1633,9 +1852,10 @@ vty_create (int vty_sock, union sockunion *su) { struct vty *vty; + ASSERTLOCKED + /* Allocate new vty structure and set up default values. */ - vty = vty_new (); - vty->fd = vty_sock; + vty = vty_new (vty_sock); vty->type = VTY_TERM; vty->address = sockunion_su2str (su); if (no_password_check) @@ -1672,17 +1892,17 @@ vty_create (int vty_sock, union sockunion *su) /* 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); + uty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); vty->status = VTY_CLOSE; - vty_close (vty); + uty_close (vty); return NULL; } } /* Say hello to the world. */ - vty_hello (vty); + uty_hello (vty); if (! no_password_check) - vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); /* Setting up terminal. */ vty_will_echo (vty); @@ -1701,22 +1921,45 @@ vty_create (int vty_sock, union sockunion *su) return vty; } -/* Accept connection from the network. */ +/* Callback: qpthreads. Accept connection from the network. */ +static void +vty_accept_r (qps_file qf, void* file_info) +{ + LOCK + + int accept_sock = qf->fd; + uty_accept(accept_sock); + + UNLOCK +} + +/* Callback: threads. Accept connection from the network. */ static int vty_accept (struct thread *thread) { + int result; + + LOCK + + int accept_sock = THREAD_FD (thread); + result = uty_accept(accept_sock); + + UNLOCK + return result; +} + +static int +uty_accept (int accept_sock) +{ 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); - /* We continue hearing vty socket. */ vty_event (VTY_SERV, accept_sock, NULL); @@ -1726,7 +1969,7 @@ vty_accept (struct thread *thread) vty_sock = sockunion_accept (accept_sock, &su); if (vty_sock < 0) { - zlog_warn ("can't accept vty socket : %s", safe_strerror (errno)); + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", safe_strerror (errno)); return -1; } set_nonblocking(vty_sock); @@ -1740,7 +1983,7 @@ vty_accept (struct thread *thread) (access_list_apply (acl, p) == FILTER_DENY)) { char *buf; - zlog (NULL, LOG_INFO, "Vty connection refused from %s", + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", (buf = sockunion_su2str (&su))); free (buf); close (vty_sock); @@ -1749,7 +1992,6 @@ vty_accept (struct thread *thread) vty_event (VTY_SERV, accept_sock, NULL); prefix_free (p); - return 0; } } @@ -1762,7 +2004,7 @@ vty_accept (struct thread *thread) (access_list_apply (acl, p) == FILTER_DENY)) { char *buf; - zlog (NULL, LOG_INFO, "Vty connection refused from %s", + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", (buf = sockunion_su2str (&su))); free (buf); close (vty_sock); @@ -1771,7 +2013,6 @@ vty_accept (struct thread *thread) vty_event (VTY_SERV, accept_sock, NULL); prefix_free (p); - return 0; } } @@ -1783,10 +2024,10 @@ vty_accept (struct thread *thread) 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", + uzlog (NULL, LOG_INFO, "can't set sockopt to vty_sock : %s", safe_strerror (errno)); - zlog (NULL, LOG_INFO, "Vty connection from %s", + uzlog (NULL, LOG_INFO, "Vty connection from %s", (bufp = sockunion_su2str (&su))); if (bufp) XFREE (MTYPE_TMP, bufp); @@ -1807,6 +2048,8 @@ vty_serv_sock_addrinfo (const char *hostname, unsigned short port) int sock; char port_str[BUFSIZ]; + ASSERTLOCKED + memset (&req, 0, sizeof (struct addrinfo)); req.ai_flags = AI_PASSIVE; req.ai_family = AF_UNSPEC; @@ -1862,6 +2105,7 @@ vty_serv_sock_addrinfo (const char *hostname, unsigned short port) } #endif /* HAVE_IPV6 && ! NRL */ +#if defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) /* Make vty server socket. */ static void vty_serv_sock_family (const char* addr, unsigned short port, int family) @@ -1871,6 +2115,8 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family) int accept_sock; void* naddr=NULL; + ASSERTLOCKED + memset (&su, 0, sizeof (union sockunion)); su.sa.sa_family = family; if(addr) @@ -1888,11 +2134,11 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family) switch(inet_pton(family,addr,naddr)) { case -1: - zlog_err("bad address %s",addr); + uzlog(NULL, LOG_ERR, "bad address %s",addr); naddr=NULL; break; case 0: - zlog_err("error translating address %s: %s",addr,safe_strerror(errno)); + uzlog(NULL, LOG_ERR, "error translating address %s: %s",addr,safe_strerror(errno)); naddr=NULL; } @@ -1909,7 +2155,7 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family) ret = sockunion_bind (accept_sock, &su, port, naddr); if (ret < 0) { - zlog_warn("can't bind socket"); + uzlog(NULL, LOG_WARNING, "can't bind socket"); close (accept_sock); /* Avoid sd leak. */ return; } @@ -1926,6 +2172,7 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family) /* Add vty server event. */ vty_event (VTY_SERV, accept_sock, NULL); } +#endif /* defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) */ #ifdef VTYSH /* For sockaddr_un. */ @@ -1941,6 +2188,8 @@ vty_serv_un (const char *path) mode_t old_mask; struct zprivs_ids_t ids; + ASSERTLOCKED + /* First of all, unlink existing socket */ unlink (path); @@ -1951,7 +2200,7 @@ vty_serv_un (const char *path) sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { - zlog_err("Cannot create unix stream socket: %s", safe_strerror(errno)); + uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", safe_strerror(errno)); return; } @@ -1968,7 +2217,7 @@ vty_serv_un (const char *path) ret = bind (sock, (struct sockaddr *) &serv, len); if (ret < 0) { - zlog_err("Cannot bind path %s: %s", path, safe_strerror(errno)); + ulog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, safe_strerror(errno)); close (sock); /* Avoid sd leak. */ return; } @@ -1976,7 +2225,7 @@ vty_serv_un (const char *path) ret = listen (sock, 5); if (ret < 0) { - zlog_err("listen(fd %d) failed: %s", sock, safe_strerror(errno)); + uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, safe_strerror(errno)); close (sock); /* Avoid sd leak. */ return; } @@ -1990,7 +2239,7 @@ vty_serv_un (const char *path) /* set group of socket */ if ( chown (path, -1, ids.gid_vty) ) { - zlog_err ("vty_serv_un: could chown socket, %s", + uzlog (NULL, LOG_ERR, "vty_serv_un: could chown socket, %s", safe_strerror (errno) ); } } @@ -2000,16 +2249,36 @@ vty_serv_un (const char *path) /* #define VTYSH_DEBUG 1 */ +/* Callback: qpthreads. Accept connection */ +void int +vtysh_accept_r (qps_file qf, void* file_info) +{ + int accept_sock = qf->fd; + LOCK + utysh_accept (accept_sock); + UNLOCK +} + +/* Callback: threads. Accept connection */ static int vtysh_accept (struct thread *thread) { - int accept_sock; + int accept_sock = THREAD_FD (thread); + LOCK + result = utysh_accept (accept_sock); + UNLOCK + return result; +} + +static int +utysh_accept (int accept_sock) +{ int sock; int client_len; struct sockaddr_un client; struct vty *vty; - accept_sock = THREAD_FD (thread); + ASSERTLOCKED vty_event (VTYSH_SERV, accept_sock, NULL); @@ -2021,13 +2290,13 @@ vtysh_accept (struct thread *thread) if (sock < 0) { - zlog_warn ("can't accept vty socket : %s", safe_strerror (errno)); + uzlog (NULL, LOG_WARNING, "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," + uzlog (NULL, LOG_WARNING, "vtysh_accept: could not set vty socket %d to non-blocking," " %s, closing", sock, safe_strerror (errno)); close (sock); return -1; @@ -2050,6 +2319,8 @@ vtysh_accept (struct thread *thread) static int vtysh_flush(struct vty *vty) { + ASSERTLOCKED + switch (buffer_flush_available(vty->obuf, vty->fd)) { case BUFFER_PENDING: @@ -2057,9 +2328,9 @@ vtysh_flush(struct vty *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); + uzlog(NULL, LOG_WARNING, "%s: write error to fd %d, closing", __func__, vty->fd); buffer_reset(vty->obuf); - vty_close(vty); + uty_close(vty); return -1; break; case BUFFER_EMPTY: @@ -2068,21 +2339,48 @@ vtysh_flush(struct vty *vty) return 0; } +/* Callback: qpthreads., Read data via vty socket. */ +static void +vtysh_read_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + /* is this necessary? */ + qps_disable_modes(qf, qps_read_mbit); + utysh_read(vty, vty_soc); + + UNLOCK +} + +/* Callback: threads. Read data via vty socket. */ static int vtysh_read (struct thread *thread) { + int vty_sock = THREAD_FD (thread); + struct vty *vty = THREAD_ARG (thread); + int result; + + LOCK + + vty->t_read = NULL; + result = uty_read(vty, vty_soc); + + UNLOCK + return result; +} + +static int +utysh_read (struct vty *vty, int sock) +{ 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) @@ -2093,11 +2391,11 @@ vtysh_read (struct thread *thread) return 0; } vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - zlog_warn("%s: read failed on vtysh client fd %d, closing: %s", + uzlog(NULL, LOG_WARNING, "%s: read failed on vtysh client fd %d, closing: %s", __func__, sock, safe_strerror(errno)); } buffer_reset(vty->obuf); - vty_close (vty); + uty_close (vty); #ifdef VTYSH_DEBUG printf ("close vtysh\n"); #endif /* VTYSH_DEBUG */ @@ -2139,13 +2437,32 @@ vtysh_read (struct thread *thread) return 0; } +/* Callback: qpthraeds. Write */ +static void +vtysh_write_r (qps_file qf, void* file_info) +{ + struct vty *vty = (struct vty *)file_info; + + LOCK + + qps_disable_modes(qf, qps_write_mbit); + vtysh_flush(vty); + + UNLOCK +} + +//* Callback: thraeds. Write */ static int vtysh_write (struct thread *thread) { struct vty *vty = THREAD_ARG (thread); + LOCK + vty->t_write = NULL; vtysh_flush(vty); + + UNLOCK return 0; } @@ -2155,6 +2472,8 @@ vtysh_write (struct thread *thread) void vty_serv_sock (const char *addr, unsigned short port, const char *path) { + LOCK + /* If port is set to 0, do not listen on TCP/IP at all! */ if (port) { @@ -2174,6 +2493,8 @@ vty_serv_sock (const char *addr, unsigned short port, const char *path) #ifdef VTYSH vty_serv_un (path); #endif /* VTYSH */ + + UNLOCK } /* Close vty interface. Warning: call this only from functions that @@ -2183,8 +2504,18 @@ vty_serv_sock (const char *addr, unsigned short port, const char *path) void vty_close (struct vty *vty) { + LOCK + uty_close(vty); + UNLOCK +} + +static void +uty_close (struct vty *vty) +{ int i; + ASSERTLOCKED + /* Cancel threads.*/ if (vty->t_read) thread_cancel (vty->t_read); @@ -2192,6 +2523,17 @@ vty_close (struct vty *vty) thread_cancel (vty->t_write); if (vty->t_timeout) thread_cancel (vty->t_timeout); + if (vty->qf) + { + qps_remove_file(vty->qf); + qps_file_free(vty->qf); + vty->qf = NULL; + } + if (vty->qtr) + { + qtimer_free(vty->qtr); + vty->qtr = NULL; + } /* Flush buffer. */ buffer_flush_all (vty->obuf, vty->fd); @@ -2217,29 +2559,48 @@ vty_close (struct vty *vty) XFREE (MTYPE_VTY, vty->buf); /* Check configure. */ - vty_config_unlock (vty); + uty_config_unlock (vty); /* OK free vty. */ XFREE (MTYPE_VTY, vty); } -/* When time out occur output message then close connection. */ +/* Callback: qpthreads. When time out occur output message then close connection. */ +static void +vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when) +{ + struct vty *vty = (struct vty *)timer_info; + LOCK + qtimer_unset(qtr); + uty_timeout(vty); + UNLOCK +} + +/* Callback: threads. When time out occur output message then close connection. */ static int vty_timeout (struct thread *thread) { - struct vty *vty; - - vty = THREAD_ARG (thread); + int result; + struct vty *vty = THREAD_ARG (thread); + LOCK vty->t_timeout = NULL; + result = uty_timeout(vty); + UNLOCK + return result; +} + +static int +uty_timeout (struct vty *vty) +{ vty->v_timeout = 0; /* Clear buffer*/ buffer_reset (vty->obuf); - vty_out (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + uty_out (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); /* Close connection. */ vty->status = VTY_CLOSE; - vty_close (vty); + uty_close (vty); return 0; } @@ -2251,14 +2612,15 @@ vty_read_file (FILE *confp) int ret; struct vty *vty; - vty = vty_new (); - vty->fd = 0; /* stdout */ + vty = vty_new (0); /* stdout */ vty->type = VTY_TERM; vty->node = CONFIG_NODE; /* Execute configuration file */ ret = config_from_file (vty, confp); + LOCK + if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) ) { switch (ret) @@ -2272,11 +2634,12 @@ vty_read_file (FILE *confp) } fprintf (stderr, "Error occured during reading below line.\n%s\n", vty->buf); - vty_close (vty); + uty_close (vty); exit (1); } - vty_close (vty); + uty_close (vty); + UNLOCK } static FILE * @@ -2409,7 +2772,7 @@ vty_read_config (char *config_file, { ret = stat (integrate_default, &conf_stat); if (ret >= 0) - return; + return; } #endif /* VTYSH */ @@ -2454,18 +2817,19 @@ vty_log (const char *level, const char *proto_str, unsigned int i; struct vty *vty; + ASSERTLOCKED + 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); - } + for (i = 0; i < vector_active (vtyvec); i++) + if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor) + { + va_list ac; + va_copy(ac, va); + vty_log_out (vty, level, proto_str, format, ctl, ac); + va_end(ac); + } } /* Async-signal-safe version of vty_log for fixed strings. */ @@ -2497,17 +2861,32 @@ vty_log_fixed (const char *buf, size_t len) int vty_config_lock (struct vty *vty) { + int result; + LOCK if (vty_config == 0) { vty->config = 1; vty_config = 1; } - return vty->config; + result = vty->config; + UNLOCK + return result; } int vty_config_unlock (struct vty *vty) { + int result; + LOCK + result = uty_config_unlock(vty); + UNLOCK + return result; +} + +static int +uty_config_unlock (struct vty *vty) +{ + ASSERTLOCKED if (vty_config == 1 && vty->config == 1) { vty->config = 0; @@ -2516,14 +2895,23 @@ vty_config_unlock (struct vty *vty) return vty->config; } -/* Master of the threads. */ -static struct thread_master *master; - static void vty_event (enum event event, int sock, struct vty *vty) { + if (master_nexus) + vty_event_r(event, sock, vty); + else + vty_event_t(event, sock, vty); +} + +/* thread event setter */ +static void +vty_event_t (enum event event, int sock, struct vty *vty) + { struct thread *vty_serv_thread; + ASSERTLOCKED + switch (event) { case VTY_SERV: @@ -2556,7 +2944,7 @@ vty_event (enum event event, int sock, struct vty *vty) case VTY_WRITE: if (! vty->t_write) vty->t_write = thread_add_write (master, vty_flush, vty, sock); - break; + break; case VTY_TIMEOUT_RESET: if (vty->t_timeout) { @@ -2572,6 +2960,70 @@ vty_event (enum event event, int sock, struct vty *vty) } } +/* qpthreads event setter */ +static void +vty_event_r (enum event event, int sock, struct vty *vty) + { + + qps_file accept_file = NULL; + + ASSERTLOCKED + + switch (event) + { + case VTY_SERV: + accept_file = vector_get_item(Vvty_serv_thread, sock); + if (accept_file == NULL) + { + accept_file = qps_file_init_new(accept_file, NULL); + qps_add_file(master_nexus->selection, accept_file, sock, NULL); + vector_set_index(Vvty_serv_thread, sock, accept_file); + } + qps_enable_mode(accept_file, qps_read_mnum, vty_accept_r) ; + break; +#ifdef VTYSH + case VTYSH_SERV: + accept_file = vector_get_item(Vvty_serv_thread, sock); + if (accept_file == NULL) + { + accept_file = qps_file_init_new(accept_file, NULL); + qps_add_file(master, accept_file, sock, NULL); + vector_set_index(Vvty_serv_thread, sock, accept_file); + } + qps_enable_mode(accept_file, qps_read_mnum, vtysh_accept_r) ; + break; + case VTYSH_READ: + qps_enable_mode(vty->file, qps_read_mnum, vtysh_read_r) ; + break; + case VTYSH_WRITE: + qps_enable_mode(vty->file, qps_write_mnum, vtysh_write_r) ; + break; +#endif /* VTYSH */ + case VTY_READ: + qps_enable_mode(vty->qf, qps_read_mnum, vty_read_r) ; + + /* Time out treatment. */ + if (vty->v_timeout) + { + qtimer_set(vty->qtr, qt_add_monotonic(QTIME(vty->v_timeout)), NULL) ; + } + break; + case VTY_WRITE: + qps_enable_mode(vty->qf, qps_write_mnum, vty_flush_r) ; + break; + case VTY_TIMEOUT_RESET: + if (vty->v_timeout) + { + qtimer_set(vty->qtr, qt_add_monotonic(QTIME(vty->v_timeout)), NULL) ; + } + else + { + qtimer_unset(vty->qtr); + } + break; + } +} + DEFUN (config_who, config_who_cmd, "who", @@ -2605,6 +3057,8 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { unsigned long timeout = 0; + LOCK + /* min_str and sec_str are already checked by parser. So it must be all digit string. */ if (min_str) @@ -2619,7 +3073,7 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) vty->v_timeout = timeout; vty_event (VTY_TIMEOUT_RESET, 0, vty); - + UNLOCK return CMD_SUCCESS; } @@ -2892,25 +3346,49 @@ struct cmd_node vty_node = void vty_reset () { + LOCK + uty_reset(); + UNLOCK +} + +void +uty_reset () +{ unsigned int i; struct vty *vty; struct thread *vty_serv_thread; + qps_file qf; 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); + uty_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); - } + if (master_nexus) + { + for (i = 0; i < vector_active (Vvty_serv_thread); i++) + if ((qf = vector_slot (Vvty_serv_thread, i)) != NULL) + { + qps_remove_file(qf); + qps_file_free(qf); + vector_slot (Vvty_serv_thread, i) = NULL; + close (i); + } + } + else + { + assert(master); + 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; @@ -2954,25 +3432,60 @@ vty_get_cwd () int vty_shell (struct vty *vty) { + LOCK + int result; + result = uty_shell (vty); + UNLOCK + return result; +} + +static int +uty_shell (struct vty *vty) +{ return ((vty == NULL) || (vty->type == VTY_SHELL)) ? 1 : 0; } int vty_shell_serv (struct vty *vty) { - return vty->type == VTY_SHELL_SERV ? 1 : 0; + LOCK + int result; + result = ((vty->type == VTY_SHELL_SERV) ? 1 : 0); + UNLOCK + return result; } void vty_init_vtysh () { + LOCK vtyvec = vector_init (0); + UNLOCK } -/* Install vty's own commands like `who' command. */ +/* qpthreads: Install vty's own commands like `who' command. */ +void +vty_init_r (void) +{ + master_nexus = qpn_init_new(master_nexus); + vty_mutex = qpt_mutex_init(vty_mutex, qpt_mutex_quagga); + vty_init(NULL); +} + +/* create and execute our thread */ +void +vty_exec_r(void) +{ + if (master_nexus) + qpn_exec(master_nexus); +} + +/* threads: Install vty's own commands like `who' command. */ void vty_init (struct thread_master *master_thread) { + LOCK + /* For further configuration read, preserve current directory. */ vty_save_cwd (); @@ -3014,18 +3527,33 @@ 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 */ + + UNLOCK } void vty_terminate (void) { + LOCK + + if (master_nexus) + master_nexus->terminate = 1; + if (vty_cwd) XFREE (MTYPE_TMP, vty_cwd); if (vtyvec && Vvty_serv_thread) { - vty_reset (); + uty_reset (); vector_free (vtyvec); vector_free (Vvty_serv_thread); } + UNLOCK + + if (vty_mutex) + vty_mutex = qpt_mutex_destroy(vty_mutex, 1); } + +#undef LOCK +#undef UNLOCK +#undef ASSERTLOCKED @@ -23,6 +23,9 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "thread.h" #include "log.h" +#include "qpthreads.h" +#include "qpselect.h" +#include "qtimers.h" #define VTY_BUFSIZ 512 #define VTY_MAXHIST 20 @@ -30,7 +33,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA /* VTY struct. */ struct vty { - /* File descripter of this vty. */ + /* File descriptor of this vty. */ int fd; /* Is this vty connect to file or not */ @@ -39,7 +42,7 @@ struct vty /* Node status of this vty */ int node; - /* What address is this vty comming from. */ + /* What address is this vty coming from. */ char *address; /* Failure count */ @@ -112,11 +115,14 @@ struct vty int config; /* Read and write thread. */ + + qps_file qf; struct thread *t_read; struct thread *t_write; /* Timeout seconds and thread. */ unsigned long v_timeout; + qtimer qtr; struct thread *t_timeout; }; @@ -205,13 +211,20 @@ do { /* Exported variables */ extern char integrate_default[]; +extern qpt_mutex_t* vty_mutex; +#ifndef NDEBUG +extern int vty_lock_count; +extern int vty_lock_asserted; +#endif /* Prototypes. */ +extern void vty_init_r (void); +extern void vty_exec_r(void); extern void vty_init (struct thread_master *); extern void vty_init_vtysh (void); extern void vty_terminate (void); extern void vty_reset (void); -extern struct vty *vty_new (void); +extern struct vty *vty_new (int); extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); extern int vty_puts(struct vty* vty, const char* str) ; extern int vty_out_newline(struct vty *vty) ; diff --git a/lib/zassert.h b/lib/zassert.h index 79126760..424688b9 100644 --- a/lib/zassert.h +++ b/lib/zassert.h @@ -8,6 +8,18 @@ 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 +33,45 @@ 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 + +/* TODO: implement _zlog_abort() to give required messages */ + +/* Abort with message */ +#define zabort(MS) _zlog_assert_failed(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) + +/*============================================================================== + * 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 + #endif /* _QUAGGA_ASSERT_H */ diff --git a/tests/test-privs.c b/tests/test-privs.c index a888ea0f..568fe79a 100644 --- a/tests/test-privs.c +++ b/tests/test-privs.c @@ -125,26 +125,42 @@ main (int argc, char **argv) #define PRIV_STATE() \ ((test_privs.current_state() == ZPRIVS_RAISED) ? "Raised" : "Lowered") - printf ("%s\n", PRIV_STATE()); + printf ("Initial state: %s\n", PRIV_STATE()); + + test_privs.change(ZPRIVS_RAISE); + printf ("Change raise: state: %s\n", PRIV_STATE()); + test_privs.change(ZPRIVS_RAISE); + printf ("Change raise: state: %s\n", PRIV_STATE()); - printf ("%s\n", PRIV_STATE()); test_privs.change(ZPRIVS_LOWER); + printf ("Change lower: state: %s\n", PRIV_STATE()); + + test_privs.change(ZPRIVS_LOWER); + printf ("Change lower: state: %s\n", PRIV_STATE()); - printf ("%s\n", PRIV_STATE()); + printf ("Get ids %s\n", PRIV_STATE()); zprivs_get_ids (&ids); /* terminate privileges */ zprivs_terminate(&test_privs); /* but these should continue to work... */ - printf ("%s\n", PRIV_STATE()); + printf ("Terminated state: %s\n", PRIV_STATE()); + + test_privs.change(ZPRIVS_RAISE); + printf ("Change raise: state: %s\n", PRIV_STATE()); + test_privs.change(ZPRIVS_RAISE); + printf ("Change raise: state: %s\n", PRIV_STATE()); - printf ("%s\n", PRIV_STATE()); test_privs.change(ZPRIVS_LOWER); + printf ("Change lower: state: %s\n", PRIV_STATE()); + + test_privs.change(ZPRIVS_LOWER); + printf ("Change lower: state: %s\n", PRIV_STATE()); - printf ("%s\n", PRIV_STATE()); + printf ("Get ids %s\n", PRIV_STATE()); zprivs_get_ids (&ids); printf ("terminating\n"); diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 3f189adb..935c4933 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -2227,7 +2227,7 @@ void vtysh_init_vty (void) { /* Make vty structure. */ - vty = vty_new (); + vty = vty_new (0); vty->type = VTY_SHELL; vty->node = VIEW_NODE; diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index fb8a1269..f7071fc2 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -353,8 +353,7 @@ vtysh_read_file (FILE *confp) int ret; struct vty *vty; - vty = vty_new (); - vty->fd = 0; /* stdout */ + vty = vty_new (0); /* stdout */ vty->type = VTY_TERM; vty->node = CONFIG_NODE; |