diff options
author | Chris Hall <chris.hall@highwayman.com> | 2012-06-08 15:01:11 +0100 |
---|---|---|
committer | Chris Hall <chris.hall@highwayman.com> | 2012-06-08 15:01:11 +0100 |
commit | 58b4a3411f12d3ef34ef52ab197729887074f321 (patch) | |
tree | e456eec427d977e2e723039b83d614d13edcf983 | |
parent | 12d01b8b9c68f5dc72f2accdb29a760feb31420a (diff) | |
download | quagga-ex25b.tar.bz2 quagga-ex25b.tar.xz |
Fix bugs in vty error handlingex25b
Advance version to 0.99.20ex25b.
Bug fixes:
* when closing a vty, if close() returned an error, the vtys would
fall into a loop trying to recover.
This was found on FreeBSD, which will return ECONNRESET on close(),
which is not standard POSIX behaviour.
* when closing a vty in response to EPIPE or ECONNRESET, managed to
leave the vty mutex locked.
This stopped the Routing Engine *dead* on the next CLI command
that required the Routing Engine. SIGTERM/SIGINT would not
stop bgpd -- a SIGKILL would be required.
Other changes:
* toned down the error reporting of EPIPE in the vty -- so no
longer looks like an error...
...closing a vty by "exit" command is logged as "closed"
...closing a vty in response to the client closing their end of
the connection is logged as "terminated: terminal closed",
and no longer logs an EPIPE error.
* changed error reporting on close() and shutdown() for vty, so
that nothing is logged if an i/o error has already logged...
...so that redundant error messages are suppressed.
* applied slightly finer jittering to bgpd timers.
* work in progress on new vtysh.
-rw-r--r-- | bgpd/bgp_fsm.c | 37 | ||||
-rwxr-xr-x | configure.ac | 2 | ||||
-rw-r--r-- | lib/list_util.h | 140 | ||||
-rw-r--r-- | lib/qrand.c | 9 | ||||
-rw-r--r-- | lib/qrand.h | 4 | ||||
-rw-r--r-- | lib/vector.c | 2 | ||||
-rw-r--r-- | lib/vty_command.c | 61 | ||||
-rw-r--r-- | lib/vty_io.c | 60 | ||||
-rw-r--r-- | lib/vty_io.h | 4 | ||||
-rw-r--r-- | lib/vty_io_basic.c | 54 | ||||
-rw-r--r-- | lib/vty_io_basic.h | 3 | ||||
-rw-r--r-- | lib/vty_io_term.c | 8 | ||||
-rw-r--r-- | lib/vty_io_vtysh.c | 9 | ||||
-rw-r--r-- | tests/test-list_util.c | 1230 | ||||
-rw-r--r-- | vtysh/vtysh_config.c | 650 |
15 files changed, 1608 insertions, 665 deletions
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index 9b63e9d2..03266369 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -36,6 +36,7 @@ #include "lib/qtimers.h" #include "lib/sockunion.h" +#include "lib/qrand.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_network.h" @@ -1539,7 +1540,7 @@ bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) */ static void -bgp_hold_timer_set(bgp_connection connection, unsigned secs) ; +bgp_hold_timer_set(bgp_connection connection, uint secs) ; /*------------------------------------------------------------------------------ * Null action -- do nothing at all. @@ -2282,27 +2283,43 @@ static qtimer_action bgp_keepalive_timer_action ; */ enum { - no_jitter = 0, - with_jitter = 1, + no_jitter = false, + with_jitter = true, } ; +static qrand_seq_t jseq = QRAND_SEQ_INIT(314159265) ; + /*------------------------------------------------------------------------------ * Start or reset given qtimer with given interval, in seconds. * * If the interval is zero, unset the timer. */ static void -bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs, - int jitter, qtimer_action* action) +bgp_timer_set(bgp_connection connection, qtimer timer, uint secs, + bool jitter, qtimer_action* action) { if (secs == 0) qtimer_unset(timer) ; else { - secs *= 40 ; /* a bit of resolution for jitter */ - if (jitter != no_jitter) - secs -= ((rand() % ((int)secs + 1)) / 4) ; - qtimer_set_interval(timer, QTIME(secs) / 40, action) ; + qtime_t interval ; + + if (jitter) + { + /* Jitter as recommended in RFC 4271, Section 10 + * + * We expect secs to be relatively small, so we can multiply it by + * some number 750..1000 without overflow ! + */ + qassert(secs <= (UINT_MAX / 1000)) ; + + secs *= 750 + qrand(jseq, 250 + 1) ; + interval = QTIME(secs) / 1000 ; + } + else + interval = QTIME(secs) ; + + qtimer_set_interval(timer, interval, action) ; } ; } ; @@ -2313,7 +2330,7 @@ bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs, * Setting 0 will unset the HoldTimer. */ static void -bgp_hold_timer_set(bgp_connection connection, unsigned secs) +bgp_hold_timer_set(bgp_connection connection, uint secs) { bgp_timer_set(connection, connection->hold_timer, secs, no_jitter, bgp_hold_timer_action) ; diff --git a/configure.ac b/configure.ac index 778458b0..2406d758 100755 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ ## AC_PREREQ(2.53) -AC_INIT(Quagga, 0.99.20ex24b, [http://bugzilla.quagga.net]) +AC_INIT(Quagga, 0.99.20ex25b, [http://bugzilla.quagga.net]) AC_CONFIG_SRCDIR(lib/zebra.h) AC_CONFIG_MACRO_DIR([m4]) diff --git a/lib/list_util.h b/lib/list_util.h index 19183760..23677cd5 100644 --- a/lib/list_util.h +++ b/lib/list_util.h @@ -512,14 +512,18 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) * * ddl_in_after(after, base, item, list) -- insert after * - * Treat as void function. The after & item may *not* be NULL. + * Treat as void function. The item may *not* be NULL. + * + * If after == NULL, insert item at the end of the current list. * * Undefined if item is already on any list (including this one), or if * after is not on the list. * * ddl_in_before(before, base, item, list) -- insert before * - * Treat as void function. The before & item may *not* be NULL. + * Treat as void function. The item may *not* be NULL. + * + * If before == NULL, insert item at the start of the current list. * * Undefined if item is already on any list (including this one), or if * before is not on the list. @@ -570,6 +574,24 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) * * Treat as function returning void*. Returns NULL if the item is NULL. * + * ddl_slice(base, sub, list) -- remove sublist from given list + * + * Treat as void function. Does nothing if the sublist is empty. + * + * ddl_splice_after(after, base, sub, list) + * -- insert sublist after given item + * + * Treat as void function. Does nothing if the sublist is empty. + * + * If after == NULL, insert sublist at the end of the current list. + * + * ddl_splice_before(before, base, sub, list) + * -- insert sublist before given item + * + * Treat as void function. Does nothing if the sublist is empty. + * + * If before == NULL, insert sublist at the start of the current list. + * * Note that ddl_del() and ddl_pop() do NOT affect the item->list.next * or item->list.prev pointers. * @@ -577,7 +599,7 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) * * "base" to be an r-value of type: struct base_pair(struct item*)* * - * That is... a variable or field which is a pointer to + * That is... a variable or field which is a pointer pair. * * "item" to be an l-value of type struct item* * @@ -586,7 +608,9 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) * "list" to be the name of a field in struct item * of type: struct list_pair(struct item*) * + * "sub" to be an r-value of type: struct base_pair(struct item*)* * + * That is... a variable or field which is a pointer pointer pair. * *------------------------------------------------------------------------------ * For example: @@ -672,23 +696,33 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) } while (0) #define ddl_in_after(after, base, item, list) \ - do { (item)->list.next = (after)->list.next ; \ - (item)->list.prev = (after) ; \ - if ((after)->list.next != NULL) \ - (after)->list.next->list.prev = (item) ; \ + do { if (after != NULL) \ + { \ + (item)->list.next = (after)->list.next ; \ + (item)->list.prev = (after) ; \ + if ((after)->list.next != NULL) \ + (after)->list.next->list.prev = (item) ; \ + else \ + (base).tail = (item) ; \ + (after)->list.next = (item) ; \ + } \ else \ - (base).tail = (item) ; \ - (after)->list.next = (item) ; \ + ddl_append(base, item, list) ; \ } while (0) #define ddl_in_before(before, base, item, list) \ - do { (item)->list.next = (before) ; \ - (item)->list.prev = (before)->list.prev ; \ - if ((before)->list.prev != NULL) \ - (before)->list.prev->list.next = (item) ; \ + do { if (before != NULL) \ + { \ + (item)->list.next = (before) ; \ + (item)->list.prev = (before)->list.prev ; \ + if ((before)->list.prev != NULL) \ + (before)->list.prev->list.next = (item) ; \ + else \ + (base).head = (item) ; \ + (before)->list.prev = (item) ; \ + } \ else \ - (base).head = (item) ; \ - (before)->list.prev = (item) ; \ + ddl_push(base, item, list) ; \ } while (0) #define ddl_del(base, item, list) \ @@ -751,6 +785,82 @@ Private bool ssl_del_func(void** p_prev, void* item, size_t link_offset) #define ddl_prev(item, list) \ ((item) != NULL ? (item)->list.prev : NULL) +#define ddl_slice(base, sub, list) \ + do { if ((sub).head != NULL) \ + { \ + if ((sub).head->list.prev != NULL) \ + (sub).head->list.prev->list.next = (sub).tail->list.next ; \ + else \ + { \ + qassert((sub).head == (base).head) ; \ + (base).head = (sub).tail->list.next ; \ + } ; \ + \ + if ((sub).tail->list.next != NULL) \ + (sub).tail->list.next->list.prev = (sub).head->list.prev ; \ + else \ + { \ + qassert((sub).tail == (base).tail) ; \ + (base).tail = (sub).head->list.prev ; \ + } ; \ + \ + (sub).head->list.prev = NULL ; \ + (sub).tail->list.next = NULL ; \ + } \ + } while (0) + +#define ddl_splice_after(after, base, sub, list) \ + do { if ((sub).head != NULL) \ + { \ + if (after != NULL) \ + { \ + (sub).head->list.prev = (after) ; \ + (sub).tail->list.next = (after)->list.next ; \ + if ((after)->list.next != NULL) \ + (after)->list.next->list.prev = (sub).tail ; \ + else \ + (base).tail = (sub).tail ; \ + (after)->list.next = (sub).head ; \ + } \ + else \ + { \ + (sub).head->list.prev = (base).tail ; \ + (sub).tail->list.next = NULL ; \ + if ((base).tail != NULL) \ + (base).tail->list.next = (sub).head ; \ + else \ + (base).head = (sub).head ; \ + (base).tail = (sub).tail ; \ + } ; \ + } \ + } while (0) + +#define ddl_splice_before(before, base, sub, list) \ + do { if ((sub).head != NULL) \ + { \ + if (before != NULL) \ + { \ + (sub).tail->list.next = (before) ; \ + (sub).head->list.prev = (before)->list.prev ; \ + if ((before)->list.prev != NULL) \ + (before)->list.prev->list.next = (sub).head ; \ + else \ + (base).head = (sub).head ; \ + (before)->list.prev = (sub).tail ; \ + } \ + else \ + { \ + (sub).tail->list.next = (base).head ; \ + (sub).head->list.prev = NULL ; \ + if ((base).head != NULL) \ + (base).head->list.prev = (sub).tail ; \ + else \ + (base).tail = (sub).tail ; \ + (base).head = (sub).head ; \ + } ; \ + } \ + } while (0) + /*============================================================================== * Double Base, Single Link * diff --git a/lib/qrand.c b/lib/qrand.c index 402bb432..a395b92c 100644 --- a/lib/qrand.c +++ b/lib/qrand.c @@ -32,17 +32,16 @@ * * If range == 1, returns 0 every time ! */ -extern int -qrand(qrand_seq seq, int range) +extern uint +qrand(qrand_seq seq, uint range) { uint64_t r ; - r = seq->last ^ 3141592653 ; - r = ((r * 2650845021) + 5) & 0xFFFFFFFF ; /* see Knuth */ + r = ((seq->last * 2650845021) + 5) & 0xFFFFFFFF ; /* see Knuth */ seq->last = r ; if (range == 0) return r >> 1 ; else - return (r % range) ; + return (r * range) >> 32 ; } ; diff --git a/lib/qrand.h b/lib/qrand.h index e9a18436..cb81aceb 100644 --- a/lib/qrand.h +++ b/lib/qrand.h @@ -35,7 +35,7 @@ struct qrand_seq { - uint last ; + uint32_t last ; } ; typedef struct qrand_seq* qrand_seq ; @@ -47,6 +47,6 @@ typedef struct qrand_seq qrand_seq_t[1] ; * Functions */ -extern int qrand(qrand_seq seq, int range) ; +extern uint qrand(qrand_seq seq, uint range) ; #endif /* _ZEBRA_QRAND_H */ diff --git a/lib/vector.c b/lib/vector.c index 09fb1b54..09457aac 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -903,7 +903,7 @@ vector_sak(int to_copy, vector to, if (n_dst_nulls > 0) memset(&dst->p_items[dst->end], 0, P_ITEMS_SIZE(n_dst_nulls)) ; if (n_dst_move > 0) - memmove(&dst->p_items[i_dst + n_dst], &dst->p_items[i_dst + n_src], + memmove(&dst->p_items[i_dst + n_src], &dst->p_items[i_dst + n_dst], P_ITEMS_SIZE(n_dst_move)) ; if (n_src_real > 0) memcpy(&dst->p_items[i_dst], &src->p_items[i_src], diff --git a/lib/vty_command.c b/lib/vty_command.c index d55220a6..02897a5a 100644 --- a/lib/vty_command.c +++ b/lib/vty_command.c @@ -131,7 +131,7 @@ * Prototypes */ static bool uty_cmd_loop_prepare(vty_io vio) ; -static cmd_ret_t uty_cmd_hiatus(vty_io vio, cmd_ret_t ret) ; +static cmd_ret_t uty_cmd_hiatus(vty_io vio) ; static cmd_ret_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ; static void uty_cmd_out_cancel(vty_io vio) ; static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ; @@ -1295,9 +1295,6 @@ vty_cmd_hiatus(vty vty, cmd_ret_t ret) qassert((vio->cl_state == vcl_cq_running) || (vio->cl_state == vcl_running)) ; - if (vio->state & vst_final) - return CMD_STOP ; /* it is all over */ - /* Handle the return code, either: * * * nothing further required -- any exception will have updated the @@ -1306,26 +1303,31 @@ vty_cmd_hiatus(vty vty, cmd_ret_t ret) * * this is a command or parsing error, for which a vx_quash exception * must be raised, and suitable error message generated. */ - switch (ret) + if (vio->state & vst_final) + ret = CMD_STOP ; /* it is all over */ + else { - case CMD_SUCCESS: - case CMD_HIATUS: - case CMD_WAITING: - case CMD_STOP: - case CMD_CANCEL: - case CMD_IO_ERROR: - break ; + switch (ret) + { + case CMD_SUCCESS: + case CMD_HIATUS: + case CMD_WAITING: + case CMD_STOP: + case CMD_CANCEL: + case CMD_IO_ERROR: + break ; - case CMD_WARNING: - case CMD_ERROR: - case CMD_ERR_PARSING: - case CMD_ERR_NO_MATCH: - case CMD_ERR_AMBIGUOUS: - case CMD_ERR_INCOMPLETE: - default: /* assume some sort of error */ - uty_vio_exception(vio, vx_quash) ; - uty_cmd_failed(vio, ret) ; - break ; + case CMD_WARNING: + case CMD_ERROR: + case CMD_ERR_PARSING: + case CMD_ERR_NO_MATCH: + case CMD_ERR_AMBIGUOUS: + case CMD_ERR_INCOMPLETE: + default: /* assume some sort of error */ + uty_vio_exception(vio, vx_quash) ; + uty_cmd_failed(vio, ret) ; + break ; + } ; } ; /* The meat of the hiatus -- loop back as required. @@ -1342,11 +1344,12 @@ vty_cmd_hiatus(vty vty, cmd_ret_t ret) * position is a little complicated. In particular, changes on the output * side may affect whether is waiting in the CLI. */ - while (1) + do { vio->signal = CMD_SUCCESS ; /* we are here ! */ - ret = uty_cmd_hiatus(vio, ret) ; + if (ret != CMD_STOP) + ret = uty_cmd_hiatus(vio) ; switch (ret) { @@ -1384,10 +1387,8 @@ vty_cmd_hiatus(vty vty, cmd_ret_t ret) default: zabort("invalid return code from uty_cmd_hiatus") ; } ; - - if (vio->signal == CMD_SUCCESS) - break ; - } ; + } + while (vio->signal != CMD_SUCCESS); if (ret == CMD_WAITING) { @@ -1418,8 +1419,10 @@ vty_cmd_hiatus(vty vty, cmd_ret_t ret) * pselect() system has moved things forward to). */ static cmd_ret_t -uty_cmd_hiatus(vty_io vio, cmd_ret_t ret) +uty_cmd_hiatus(vty_io vio) { + cmd_ret_t ret ; + /* (1) Do we need to close one or more vin and/or vout, or are we waiting for * one to close ? * diff --git a/lib/vty_io.c b/lib/vty_io.c index 930ef7dd..ebcdb0a9 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -353,7 +353,7 @@ uty_close_reason_set(vty_io vio, const char* why, bool replace) if ((why == NULL) || (*why == '\0')) vio->close_reason = qs_free(vio->close_reason) ; else - vio->close_reason = qs_printf(vio->close_reason, "Terminated: %s", why) ; + vio->close_reason = qs_printf(vio->close_reason, "terminated: %s", why) ; } ; /*------------------------------------------------------------------------------ @@ -370,7 +370,7 @@ uty_suspend_reason_set(vty_io vio, const char* why) if ((why == NULL) || (*why == '\0')) why = "*reason unknown (BUG)*" ; - vio->suspend_reason = qs_printf(vio->suspend_reason, "Suspended: %s", why) ; + vio->suspend_reason = qs_printf(vio->suspend_reason, "suspended: %s", why) ; } ; /*------------------------------------------------------------------------------ @@ -912,7 +912,7 @@ uty_vout_base_close(vty_io vio) { qstring wrapped ; - wrapped = qs_printf(NULL, "---\n%s\n", qs_string(vio->close_reason)) ; + wrapped = qs_printf(NULL, "%%---\n%% %s\n", qs_string(vio->close_reason)); switch(vf->vout_type) { @@ -1276,7 +1276,7 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, * and uty_cmd_loop_prepare() * push_complete = false -- see uty_vout_push() * - * io_error = false -- so far, so good + * had_error = false -- so far, so good * * blocking = vio->blocking || io_type is blocking * @@ -2161,7 +2161,7 @@ uty_vf_not_open_error(vio_vf vf, vio_err_type_t err_type) * * * if the error is in the vin_base or the vout_base, but the vout_base is * still working, then raise vx_stop_io_error, which will cancel down to - * to vin_base/vout_base and then close vin_base and hence the vty. + * vin_base/vout_base and then close vin_base and hence the vty. * * * if the error is an I/O error, and the error is in the vout_base, then * stop everything, final -- raise vx_sto_final exception ! @@ -2218,6 +2218,7 @@ uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err) vio_exception_t vx ; vty_io vio ; bool was_final ; + bool epipe ; VTY_ASSERT_LOCKED() ; @@ -2225,6 +2226,8 @@ uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err) /* Decide how to handle the error, post the exception and signal. */ + epipe = false ; + if ((vf != vio->vin_base) && (vf != vio->vout_base)) vx = vx_io_error ; else @@ -2234,36 +2237,49 @@ uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err) if (vf == vio->vout_base) { if ((err_type & verr_io) || ((err_type & verr_mask) == verr_vout)) - vx = vx_stop_final ; + { + vx = vx_stop_final ; + epipe = (err_type & verr_io) && (err == EPIPE) ; + } ; } ; } ; - was_final = (vio->state & vst_final) != 0 ; + was_final = (vio->state & vst_final) != 0 ; /* before this exception */ uty_vio_exception(vio, vx) ; /* Log error and create error message as required. * - * Note that we log the first error on the vf and all I/O errors. + * Note that we log the first error on the vf and all I/O errors (except + * for EPIPE on the vout_base -- which we leave for the close reason + * logging). * * We only output an error message for the first error on the vf, and then * only if was not vst_final before the error. */ - if (!vf->io_error || (err_type & verr_io)) + if ((!vf->had_error || (err_type & verr_io)) && !epipe) zlog_warn("%s", uty_error_message(vf, err_type, err, true /* log */).str) ; - if (!vf->io_error && !was_final) + if (!vf->had_error && !was_final) { verr_mess_t err_mess ; err_mess = uty_error_message(vf, err_type, err, false /* not log */) ; if (vx == vx_stop_final) - uty_close_reason_set(vio, err_mess.str, true /* replace */) ; + uty_close_reason_set(vio, err_mess.str, !epipe) ; else vio_fifo_printf(uty_cmd_get_ebuf(vio), "%% %s\n", err_mess.str) ; } ; + vf->had_error = true ; + + /* If this is an I/O error, tell the vfd not to generate any further I/O + * error messages on close etc. + */ + if (err_type & verr_io) + vio_vfd_set_failed(vf->vfd) ; + /* Signal to the command loop and return CMD_IO_ERROR. * * One or both will be collected in the command loop "hiatus" and dealt @@ -2291,9 +2307,12 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) const char* sort ; bool vout ; int fd ; + bool epipe ; VTY_ASSERT_LOCKED() ; + epipe = (err_type & verr_io) && (err == EPIPE) ; + vout = false ; fd = -1 ; what = "*unknown verr_xxx (bug)*" ; @@ -2330,6 +2349,7 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) default: qassert(false) ; + epipe = false ; } ; name = vf->name ; @@ -2387,6 +2407,7 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) qassert(false) ; where = "*unknown VOUT_XXX (bug)*" ; + epipe = false ; break ; } ; } @@ -2434,6 +2455,7 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) qassert(false) ; where = "*unknown VIN_XXX (bug)*" ; + epipe = false ; break ; } ; } ; @@ -2447,7 +2469,10 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) else sort = "time-out" ; - qfs_printf(qfs, "%s %s %s", where, what, sort) ; + if (!epipe) + qfs_printf(qfs, "%s %s %s", where, what, sort) ; + else + qfs_printf(qfs, "%s", where) ; if (name != NULL) qfs_printf(qfs, " '%s'", name) ; @@ -2455,9 +2480,14 @@ uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log) if (fd >= 0) qfs_printf(qfs, " (fd=%d)", fd) ; - if (((err_type & verr_io) != 0) && (err != 0)) - qfs_printf(qfs, ": %s", errtoa(err, 0).str) ; - else if ((err_type & verr_vtysh) != 0) + if (err_type & verr_io) + { + if (!epipe) + qfs_printf(qfs, ": %s", errtoa(err, 0).str) ; + else + qfs_printf(qfs, " connection closed") ; + } + else if (err_type & verr_vtysh) { switch (err) { diff --git a/lib/vty_io.h b/lib/vty_io.h index 3594d025..13139af3 100644 --- a/lib/vty_io.h +++ b/lib/vty_io.h @@ -375,7 +375,7 @@ struct vio_vf /* General I/O * * Once an error has been posted -- see uty_vf_error() -- it is curtains - * for the vio_vf. The io_error flag is to prevent multiple errors being + * for the vio_vf. The had_error flag is to prevent multiple errors being * posted for the same vio_vf, because after the first error any subsequent * errors are most likely a consequence of the first. * @@ -397,7 +397,7 @@ struct vio_vf * line, or to return CMD_WAITING, which will then wait for some I/O to * complete and signal the command loop to proceed. */ - bool io_error ; /* an I/O or timeout posted */ + bool had_error ; /* an error has been posted */ bool blocking ; /* all I/O is blocking, pseudo or real */ diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c index 8fa579d6..fac64f66 100644 --- a/lib/vty_io_basic.c +++ b/lib/vty_io_basic.c @@ -434,6 +434,7 @@ vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) * * type -- X -- see below * io_type -- X -- see below + * failed -- false * * action_info -- NULL -- set below if ! "blocking" vio_vfd * @@ -520,6 +521,17 @@ vio_vfd_set_action_info(vio_vfd vfd, void* action_info) vfd->action_info = action_info ; } ; +/*------------------------------------------------------------------------------ + * If there is a vfd, set the "failed" flag, which suppresses any further + * error logging - to avoid clutter. + */ +extern void +vio_vfd_set_failed(vio_vfd vfd) +{ + if (vfd != NULL) + vfd->failed = true ; +} ; + #if 0 /*------------------------------------------------------------------------------ * If there is a read action set for the give vio_vfd (if any), then kick it. @@ -577,10 +589,25 @@ vio_vfd_read_close(vio_vfd vfd) { if (vfd->type == vfd_socket) { - int rc = shutdown(vfd->fd, SHUT_RD) ; - if (rc < 0) - zlog_err("%s: shutdown() failed, fd=%d: %s", __func__, + int rc, try ; + + /* POSIX doesn't list EINTR as a failure for shutdown() + * but we handle it the same way as for close() in any case. + * + * We are completely paranoid and arrange not to get trapped + * here indefinitely. + */ + try = 5 ; + do + rc = shutdown(vfd->fd, SHUT_RD) ; + while ((rc < 0) && (errno == EINTR) && (try-- > 0)) ; + + if ((rc < 0) && !vfd->failed) + { + zlog_err("%s: shutdown() failed, fd=%d: %s", __func__, vfd->fd, errtoa(errno, 0).str) ; + vfd->failed = true ; + } ; } ; vio_vfd_set_read(vfd, off, 0) ; vfd->io_type ^= vfd_io_read ; /* now write only ! */ @@ -668,18 +695,23 @@ vio_vfd_close(vio_vfd vfd) */ if ((vfd->fd >= 0) && ((vfd->io_type & vfd_io_no_close) == 0)) { - while (1) - { - int rc = close(vfd->fd) ; - - if (rc == 0) - break ; + int rc, try ; - if (errno == EINTR) - continue ; + /* POSIX lists EINTR as a failure for close(). + * + * We are completely paranoid and arrange not to get trapped + * here indefinitely. + */ + try = 5 ; + do + rc = close(vfd->fd) ; + while ((rc < 0) && (errno == EINTR) && (try-- > 0)) ; + if ((rc < 0) && !vfd->failed) + { zlog_err("%s: close() failed, fd=%d: %s", __func__, vfd->fd, errtoa(errno, 0).str) ; + vfd->failed = true ; } ; } ; diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h index 5c122873..3f731905 100644 --- a/lib/vty_io_basic.h +++ b/lib/vty_io_basic.h @@ -124,6 +124,8 @@ struct vio_vfd vfd_type_t type ; /* used for half-close */ vfd_io_type_t io_type ; /* read, write, read/write */ + bool failed ; /* avoid repeated error messages */ + /* The rest of the vfd is to do with managing read/write ready and * read/write timeouts for *non* blocking vfd. * @@ -219,6 +221,7 @@ extern vio_vfd vio_vfd_new(int fd, vfd_type_t type, extern void vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action) ; extern void vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action) ; extern void vio_vfd_set_action_info(vio_vfd vfd, void* action_info) ; +extern void vio_vfd_set_failed(vio_vfd vfd) ; extern vio_vfd vio_vfd_read_close(vio_vfd vfd) ; extern vio_vfd vio_vfd_close(vio_vfd vfd) ; extern on_off_b vio_vfd_set_read(vio_vfd vfd, on_off_b how, diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c index 7ef7e0df..fc0fd8f0 100644 --- a/lib/vty_io_term.c +++ b/lib/vty_io_term.c @@ -431,17 +431,15 @@ uty_term_write_close(vio_vf vf) */ if (vio->state & vst_final) { - const char* reason, * sep ; + const char* reason ; vf->cli = uty_cli_free(vf->cli) ; reason = qs_string(vf->vio->close_reason) ; - sep = ": " ; if ((reason == NULL) || (*reason == '\0')) - reason = sep = "" ; + reason = "closed" ; - zlog_info("VTY connection (fd %d) closed%s%s", - vio_vfd_fd(vf->vfd), sep, reason) ; + zlog_info("VTY connection (fd %d) %s", vio_vfd_fd(vf->vfd), reason) ; } else if (vf_active(vf->vout_state)) { diff --git a/lib/vty_io_vtysh.c b/lib/vty_io_vtysh.c index f506eaa3..321ee6aa 100644 --- a/lib/vty_io_vtysh.c +++ b/lib/vty_io_vtysh.c @@ -258,17 +258,16 @@ uty_sh_serv_write_close(vio_vf vf) if (vio->state & vst_final) { - const char* reason, * sep ; + const char* reason ; XFREE(MTYPE_VTY_CLI, vf->vtysh) ; reason = qs_string(vf->vio->close_reason) ; - sep = ": " ; if ((reason == NULL) || (*reason == '\0')) - reason = sep = "" ; + reason = "closed" ; - zlog_info("vtysh connection '%s' (fd %d) closed%s%s", vf->name, - vio_vfd_fd(vf->vfd), sep, reason) ; + zlog_info("vtysh connection '%s' (fd %d) %s", vf->name, + vio_vfd_fd(vf->vfd), reason) ; } else if (vf_active(vf->vout_state)) { diff --git a/tests/test-list_util.c b/tests/test-list_util.c index 7078762c..1107586e 100644 --- a/tests/test-list_util.c +++ b/tests/test-list_util.c @@ -4,6 +4,7 @@ #include "command.h" #include "list_util.h" #include <string.h> +#include "vector.h" /* List util torture tests * @@ -16,6 +17,9 @@ static void test_ssl(void); static void test_sdl(void); static void test_ddl(void); +static uint assert_limit ; + + #define test_assert(true, message) \ do { if (!(true)) test_assert_fail(#true, message, __func__, __LINE__) ; \ } while (0) @@ -26,6 +30,11 @@ test_assert_fail(const char* truth, const char* message, const char* func, { printf("*** %s %d: (%s) not true: %s\n", func, line, truth, message) ; + if (assert_limit > 0) + { + --assert_limit ; + assert(assert_limit > 0) ; + } ; } ; /*============================================================================== @@ -47,6 +56,8 @@ main(int argc, char **argv) srand(22) ; /* Consistent testing required */ + assert_limit = 25 ; + test_ssl() ; test_sdl() ; test_ddl() ; @@ -63,6 +74,22 @@ static unsigned majic(void* a, void* b) return z ; } ; +/* aux list (vector) functions + */ +static vector aux_init(void) ; +static void aux_reset(vector aux) ; +static int aux_length(vector aux) ; +static void* aux_item(vector aux, int index) ; +static void* aux_next_item(vector aux, int index) ; +static void* aux_prev_item(vector aux, int index) ; +static void aux_insert(vector aux, int index, void* item) ; +static void aux_delete(vector aux, void* item) ; +static void aux_push_head(vector aux, void* item) ; +static void* aux_pop_head(vector aux) ; +static void aux_push_tail(vector aux, void* item) ; +static void* aux_pop_tail(vector aux) ; +static int aux_find(vector aux, void* item) ; + /*============================================================================== * Single Base, Single Link * @@ -1162,157 +1189,201 @@ test_sdl(void) * ddl_next(item, next) -- step to next item, if any * ddl_prev(item, next) -- step to prev item, if any * + * ddl_slice(base, sub, list) -- remove sublist from given list + * ddl_splice_after(after, base, sub, list) + * -- insert sublist after given item + * ddl_splice_before(before, base, sub, list) + * -- insert sublist before given item + * * Cases to cover: */ /* Testing runs two lists through struct ddt_item objects. */ -enum list { a_list, b_list, list_count } ; +enum list + { + a_list, + b_list, /* a_list..b_list is all main lists */ + + a_sub_list, + b_sub_list, /* a_sub_list..b_sub_list is all sub lists */ + + list_count /* 0..list_count -1 is all lists */ + } ; +typedef enum list list_t ; + +enum list_bit + { + a_list_bit = 1 << a_list, + b_list_bit = 1 << b_list, + + a_sub_list_bit = 1 << a_sub_list, + b_sub_list_bit = 1 << b_sub_list, + + list_bits = 1 << list_count, + } ; +typedef enum list_bit list_bit_t ; typedef struct ddt_item* ddt_item ; struct ddt_list_pair dl_list_pair(ddt_item) ; /* Example struct constructor */ struct ddt_base_pair dl_base_pair(ddt_item) ; -typedef struct dl_base_pair(ddt_item) ddt_base_pair_t ; - /* Example typedef constructor */ -typedef struct dl_base_pair(ddt_item)* p_ddt_base_pair ; +typedef struct dl_base_pair(ddt_item) ddt_base_pair_xt ; /* Example typedef constructor */ +typedef struct ddt_list_pair ddt_list_pair_t ; typedef struct ddt_list_pair* ddt_list_pair ; -typedef struct ddt_base_pair* ddt_base_pair ; - -typedef struct ddt_rank* ddt_rank ; -struct ddt_rank /* information for each list */ -{ - struct ddt_list_pair list ; /* the thing we are testing */ - - int lister ; - int list_found ; - unsigned majic ; - - int ordinal ; -}; +typedef struct ddt_base_pair ddt_base_pair_t ; +typedef struct ddt_base_pair* ddt_base_pair ; struct ddt_item /* the test items */ { - struct ddt_rank a ; + ddt_list_pair_t a ; char a_rubbish[21] ; - struct ddt_rank b ; + uint seen ; + + ddt_list_pair_t b ; char b_rubbish[19] ; } ; -/* The test list base pairs, and pointers to the actual bases, for use in +/* Pointers to the actual bases, for use in * the verification code. */ -static ddt_base_pair ddt_bases[list_count] ; - -/* For some tests want to construct lists and know the first, last and - * somewhere between items. - */ +struct ddt_test_list +{ + const char* name ; -enum where { first, middle, last, where_count } ; + ddt_base_pair base ; -struct ddt_test_list_items -{ - struct - { - ddt_item where[where_count] ; - } list[list_count] ; + vector aux ; } ; -/*------------------------------------------------------------------------------ - * The test list items -- keep track here also for use in verification. - */ -enum { ddt_max_items = 1000 } ; +typedef struct ddt_test_list* ddt_test_list ; +typedef struct ddt_test_list ddt_test_list_t ; -static unsigned ddt_item_count = 0 ; -static unsigned ddt_item_alloc = 0 ; -static ddt_item ddt_items[ddt_max_items] ; +static ddt_test_list_t ddt_lists[list_count] ; -static inline ddt_item -ddt_new_item(void) + +static inline ddt_list_pair +ddt_list(ddt_item item, list_t lt) { - ddt_item item ; + switch (lt) + { + case a_list: + case a_sub_list: + return &(item->a) ; - assert(ddt_item_count <= ddt_item_alloc) ; + case b_list: + case b_sub_list: + return &(item->b) ; - if (ddt_item_count == ddt_item_alloc) - { - assert(ddt_item_alloc < ddt_max_items) ; - ddt_items[ddt_item_alloc++] = malloc(sizeof(struct ddt_item)) ; + default: + assert(false) ; } ; +} ; - item = ddt_items[ddt_item_count++] ; +static inline ddt_base_pair +ddt_base(list_t lt) +{ + switch (lt) + { + case a_list: + case a_sub_list: + case b_list: + case b_sub_list: + return ddt_lists[lt].base ; + + default: + assert(false) ; + } ; +} ; - memset(item, ddt_item_count & 0xFF, sizeof(struct ddt_item)) ; +static inline vector +ddt_aux(list_t lt) +{ + switch (lt) + { + case a_list: + case a_sub_list: + case b_list: + case b_sub_list: + return ddt_lists[lt].aux ; + + default: + assert(false) ; + } ; +} ; - item->a.lister = 0 ; - item->b.lister = 0 ; +static inline uint +ddt_list_bit(list_t lt) +{ + switch (lt) + { + case a_list: + case a_sub_list: + return 1 ; - item->a.ordinal = 0 ; - item->b.ordinal = 0 ; + case b_list: + case b_sub_list: + return 2 ; - return item ; + default: + assert(false) ; + } ; } ; /*------------------------------------------------------------------------------ - * Given an item and a list ordinal, return pointer to "rank" for item. + * Initialise a test list. */ -static inline ddt_rank -ddt_get_rank(ddt_item item, enum list l) +static void +ddt_test_list_init(ddt_test_list tl, ddt_base_pair base, const char* name) { - if (item == NULL) - return NULL ; + tl->name = name ; - if (l == a_list) - return &item->a ; - if (l == b_list) - return &item->b ; + tl->base = base ; - assert(0) ; + base->head = NULL ; + base->tail = NULL ; + + tl->aux = aux_init() ; } ; /*------------------------------------------------------------------------------ - * Keep track of what should be found on the lists, and majic marker to check - * that don't get lost and point into space. + * The test list items -- keep track here also for use in verification. */ -static inline unsigned -ddt_get_majic(ddt_item item, enum list l) -{ - return majic(item, ddt_bases[l]) ; -} ; - -static void -ddt_test_list_add(ddt_item item, enum list l) -{ - ddt_rank rank = ddt_get_rank(item, l) ; - - test_assert(rank->lister == 0, "already on list") ; +enum { ddt_max_items = 1000 } ; - rank->lister = 1 ; - rank->majic = ddt_get_majic(item, l) ; - rank->ordinal = 0 ; /* unknown ordinal */ -} ; +static unsigned ddt_item_count = 0 ; +static unsigned ddt_item_alloc = 0 ; +static unsigned ddt_item_byte = 0 ; +static ddt_item ddt_items[ddt_max_items] ; -static void -ddt_test_list_del(ddt_item item, enum list l) +static ddt_item +ddt_new_item(void) { - ddt_rank rank ; + ddt_item item ; + uint i, b ; - if (item == NULL) - return ; + assert(ddt_item_count <= ddt_item_alloc) ; - rank = ddt_get_rank(item, l) ; + if (ddt_item_count == ddt_item_alloc) + { + assert(ddt_item_alloc < ddt_max_items) ; + ddt_items[ddt_item_alloc++] = malloc(sizeof(struct ddt_item)) ; + } ; - test_assert(rank->lister == 1, "not on list") ; + item = ddt_items[ddt_item_count++] ; - rank->lister = 0 ; - rank->majic = 0 ; + b = ddt_item_byte + 0xA5 ; + for (i = 0 ; i < sizeof(struct ddt_item) ; ++i) + ((uchar*)item)[i] = (b += 0x55) ; + + return item ; } ; /*------------------------------------------------------------------------------ @@ -1339,24 +1410,20 @@ ddt_test_list_del(ddt_item item, enum list l) static void ddt_verify_lists(void) { - ddt_base_pair base ; - ddt_rank r ; - ddt_item this ; - ddt_item prev ; - int l ; - int i ; + uint i, l ; - /* Wash the found flags */ - for (l = 0 ; l < list_count ; ++l) - for (i = 0 ; i < (int)ddt_item_count ; ++i) - ddt_get_rank(ddt_items[i], l)->list_found = 0 ; + /* Wash the seen flags + */ + for (i = 0 ; i < ddt_item_count ; ++i) + ddt_items[i]->seen = 0 ; - /* Walk the lists */ + /* Walk the lists + */ for (l = 0 ; l < list_count ; ++l) { - int ordinal = 0 ; + ddt_base_pair base ; - base = ddt_bases[l] ; + base = ddt_lists[l].base ; if (base == NULL) continue ; @@ -1364,56 +1431,52 @@ ddt_verify_lists(void) test_assert(base->head == base->tail, "broken list bases") ; else { - r = ddt_get_rank(base->head, l) ; - test_assert(r->list.prev == NULL, "broken list first item->prev") ; - r = ddt_get_rank(base->tail, l) ; - test_assert(r->list.next == NULL, "broken list last item->next") ; + vector aux ; + ddt_list_pair list ; + ddt_item this ; + ddt_item prev ; + uint s ; + + list = ddt_list(base->head, l) ; + test_assert(list->prev == NULL, "broken list first item->prev") ; + list = ddt_list(base->tail, l) ; + test_assert(list->next == NULL, "broken list last item->next") ; this = base->head ; prev = NULL ; + s = ddt_list_bit(l) ; /* seen flag */ + + aux = ddt_lists[l].aux ; + i = 0 ; while (this != NULL) { - r = ddt_get_rank(this, l) ; + test_assert(i < vector_length(aux), "list longer than aux list") ; + test_assert(aux_item(aux, i) == this, + "list and aux list out of step") ; - test_assert(r->list.prev == prev, "broken item->prev") ; + test_assert((this->seen & s) == 0, "list item seen already") ; + this->seen |= s ; - test_assert(r->lister, "should not be on this list") ; + list = ddt_list(this, l) ; - test_assert(!r->list_found, "circular list") ; - r->list_found = 1 ; + test_assert(list->prev == prev, "broken item->prev") ; - if (r->ordinal != 0) - { - test_assert(r->ordinal > ordinal, "list out of order") ; - ordinal = r->ordinal ; - } - - test_assert(r->majic == ddt_get_majic(this, l), - "wrong sort of majic") ; prev = this ; - this = r->list.next ; + this = list->next ; + ++i ; } ; + test_assert(i == vector_length(aux), "list shorter than aux list") ; test_assert(base->tail == prev, "broken tail pointer") ; } ; } ; - - /* Verify that did not miss anything should have found */ - /* Wash the found flags */ - for (l = 0 ; l < list_count ; ++l) - for (i = 0 ; i < (int)ddt_item_count ; ++i) - { - r = ddt_get_rank(ddt_items[i], l) ; - - if (r->lister) - test_assert(r->list_found, "should have found this on list") ; - } ; } ; /*------------------------------------------------------------------------------ * Reset the test list handling * + * Sets all ddt_lists empty. */ static void ddt_reset_lists(void) @@ -1421,126 +1484,130 @@ ddt_reset_lists(void) int l ; for (l = 0 ; l < list_count ; ++l) - ddl_init(*(ddt_bases[l])) ; + { + ddl_init(*(ddt_lists[l].base)) ; + aux_reset(ddt_lists[l].aux) ; + } ; ddt_item_count = 0 ; } ; /*------------------------------------------------------------------------------ - * Make lists with 'n' items each. - * - * If 'n' 0..3, makes lists with exactly that many items. + * Set all lists empty, make main lists with 'n' and sub lists with 'm' items + * each. * - * Otherwise, list length has +/- 25% jitter. + * Lists will have a random number of items in common. */ static void -ddt_test_make_lists(struct ddt_test_list_items* test, int n) +ddt_test_make_lists(int n, int m) { - ddt_base_pair base ; ddt_item item ; - ddt_rank rank ; - enum list l ; - int t ; - int m ; + vector aux ; - int req[list_count] ; - int mid[list_count] ; + uint l, t ; - /* Capture the requirements */ + ddt_reset_lists() ; + + uint req[list_count] ; + + /* Capture the requirements + */ t = 0 ; - m = 1 ; for (l = 0 ; l < list_count ; ++l) { - m += m ; - int j = (n + 1) / 4 ; + uint ln ; - if (n <= 3) - req[l] = n ; - else - req[l] = n - j + (rand() % (j + j + 1)) ; + switch (l) + { + case a_list: + case b_list: + ln = n ; + break ; - mid[l] = req[l] / 2 ; + case a_sub_list: + case b_sub_list: + ln = m ; + break ; - t += req[l] ; + default: + assert(false) ; + } ; - test->list[l].where[first] = NULL ; - test->list[l].where[middle] = NULL ; - test->list[l].where[last] = NULL ; + t += ln ; + req[l] = ln ; } ; - ddt_reset_lists() ; - /* Have t = total number of items still required - * m = 2^n -- where there are 'n' lists + * + * We ensure that the a_list and the a_sub_list are distinct, and that + * the b_list and the b_sub_list are also distinct. */ while (t != 0) { - int b ; - int r ; - r = (rand() % (m - 1)) + 1 ; /* bit pattern, at least one set */ + uint r ; - item = ddt_new_item() ; + r = rand() % list_bits ; - b = 1 ; - for (l = 0 ; l < list_count ; ++l) + if ((r & a_list_bit) || (m == 0)) + r &= ~a_sub_list_bit ; + if ((r & b_list_bit) || (m == 0)) + r &= ~b_sub_list_bit ; + + item = NULL ; + + l = 0 ; + while (r != 0) { - if ((req[l] != 0) && ((r & b) != 0)) + if ((r & 1) && (req[l] != 0)) { --req[l] ; --t ; - ddt_test_list_add(item, l) ; - - if (test->list[l].where[first] == NULL) - test->list[l].where[first] = item ; - - if (mid[l]-- == 0) - test->list[l].where[middle] = item ; + if (item == NULL) + item = ddt_new_item() ; - test->list[l].where[last] = item ; - - base = ddt_bases[l] ; - rank = ddt_get_rank(item, l) ; - - if (base->head == NULL) - { - base->head = item ; - base->tail = item ; - rank->list.next = NULL ; - rank->list.prev = NULL ; - } - else if (rand() & 1) - { - rank->list.next = base->head ; - rank->list.prev = NULL ; - ddt_get_rank(base->head, l)->list.prev = item ; - base->head = item ; - } - else - { - rank->list.next = NULL ; - rank->list.prev = base->tail ; - ddt_get_rank(base->tail, l)->list.next = item ; - base->tail = item ; - } + aux = ddt_lists[l].aux ; + aux_insert(aux, rand() % (vector_length(aux) + 1), item) ; } ; - b <<= 1 ; + + r >>= 1 ; + l += 1 ; } } ; - /* Number the items */ + /* Construct the lists + */ for (l = 0 ; l < list_count ; ++l) { - int o = 0 ; + ddt_base_pair base ; + ddt_list_pair list ; + uint i ; + + base = ddt_lists[l].base ; + aux = ddt_lists[l].aux ; - base = ddt_bases[l] ; + base->head = NULL ; /* Both NULL if list empty */ + base->tail = NULL ; - item = base->head ; - while (item != NULL) + list = NULL ; + for (i = 0 ; i < vector_length(aux) ; ++i) { - rank = ddt_get_rank(item, l) ; - rank->ordinal = ++o ; /* first is 1 */ - item = rank->list.next ; + ddt_list_pair prev ; + + prev = list ; + + item = aux_item(aux, i) ; + list = ddt_list(item, l) ; + + if (prev == NULL) + base->head = item ; + else + prev->next = item ; + + list->next = NULL ; + list->prev = base->tail ; + + base->tail = item ; } ; } ; @@ -1562,6 +1629,12 @@ ddt_test_make_lists(struct ddt_test_list_items* test, int n) * ddl_tail(base) -- return tail of list * ddl_next(item, next) -- step to next item, if any * ddl_prev(item, next) -- step to prev item, if any + * + * ddl_slice(base, sub, list) -- remove sublist from given list + * ddl_splice_after(after, base, sub, list) + * -- insert sublist after given item + * ddl_splice_before(before, base, sub, list) + * -- insert sublist before given item */ static struct ddt_parent @@ -1578,19 +1651,23 @@ test_ddl(void) { struct ddt_base_pair a_base ; struct ddt_parent* b ; + ddt_base_pair_t a_sub ; + ddt_base_pair_t b_sub ; - struct ddt_test_list_items test ; int n ; - int o ; int base_n = 23 ; int rand_n = 17 ; printf("=== Testing ddl -- Double Base, Double Link -- stuff\n") ; - /* Initialise the test support */ - ddt_bases[a_list] = &a_base ; - ddt_bases[b_list] = &ddt_parent.base ; + /* Initialise the test support + */ + ddt_test_list_init(&ddt_lists[a_list], &a_base, "a-list") ; + ddt_test_list_init(&ddt_lists[b_list], &ddt_parent.base, "b-list") ; + + ddt_test_list_init(&ddt_lists[a_sub_list], &a_sub, "a-sub-list") ; + ddt_test_list_init(&ddt_lists[b_sub_list], &b_sub, "b-sub-list") ; ddt_item_count = 0 ; ddt_item_alloc = 0 ; @@ -1604,7 +1681,6 @@ test_ddl(void) ddt_verify_lists() ; /* start as mean to go on */ - /* ddl_push(base, item, list) -- insert at head of list * * Cases: (a) empty list @@ -1615,7 +1691,7 @@ test_ddl(void) ddt_reset_lists() ; n = base_n + (rand() % rand_n) ; - while (n) + while (n > 0) { ddt_item item ; int r ; @@ -1627,19 +1703,17 @@ test_ddl(void) if (r & 1) { - ddl_push(a_base, item, a.list) ; + ddl_push(a_base, item, a) ; test_assert(a_base.head == item, "ddl_push broken") ; - ddt_test_list_add(item, a_list) ; - item->a.ordinal = n ; + aux_push_head(ddt_lists[a_list].aux, item) ; } ; ddt_verify_lists() ; if (r & 2) { - ddl_push(b->base, item, b.list) ; + ddl_push(b->base, item, b) ; test_assert(b->base.head == item, "ddl_push broken") ; - ddt_test_list_add(item, b_list) ; - item->b.ordinal = n ; + aux_push_head(ddt_lists[b_list].aux, item) ; } ; ddt_verify_lists() ; @@ -1657,33 +1731,29 @@ test_ddl(void) ddt_reset_lists() ; n = base_n + (rand() % rand_n) ; - o = 0 ; - while (n) + while (n > 0) { ddt_item item ; int r ; printf(".") ; - ++o ; item = ddt_new_item() ; r = (rand() % 3) + 1 ; if (r & 1) { - ddl_append(a_base, item, a.list) ; + ddl_append(a_base, item, a) ; test_assert(a_base.tail == item, "ddl_append broken") ; - ddt_test_list_add(item, a_list) ; - item->a.ordinal = o ; + aux_push_tail(ddt_lists[a_list].aux, item) ; } ; ddt_verify_lists() ; if (r & 2) { - ddl_append(b->base, item, b.list) ; + ddl_append(b->base, item, b) ; test_assert(b->base.tail == item, "ddl_append broken") ; - ddt_test_list_add(item, b_list) ; - item->b.ordinal = o ; + aux_push_tail(ddt_lists[b_list].aux, item) ; } ; ddt_verify_lists() ; @@ -1693,37 +1763,60 @@ test_ddl(void) /* ddl_in_after(after, base, item, list) -- insert after * - * Cases: (a) after first and only (so is also last) - * (b) after first when more than one - * (c) after last when more than one - * (d) after something between + * NB: after may NOT be NULL. + * + * Cases: (a) after NULL when list is empty + * (b) after NULL when list has 1 or more entries + * (c) after first and only (so is also last) + * (d) after first when more than one + * (e) after last when more than one + * (f) after something between */ printf(" ddl_in_after test") ; n = base_n + (rand() % rand_n) ; - while (n) + while (n >= 0) { - ddt_item item ; - ddt_item after ; + int w ; printf(".") ; - ddt_test_make_lists(&test, n) ; - item = ddt_new_item() ; - after = test.list[a_list].where[n % where_count] ; - ddl_in_after(after, a_base, item, a.list) ; - test_assert(after->a.list.next == item, "ddl_in_after broken") ; - test_assert(item->a.list.prev == after, "ddl_in_after broken") ; - ddt_test_list_add(item, a_list) ; - ddt_verify_lists() ; + for (w = 0 ; w <= n ; ++w) + { + ddt_item item ; + ddt_item after ; + vector aux ; - item = ddt_new_item() ; - after = test.list[b_list].where[n % where_count] ; - ddl_in_after(after, b->base, item, b.list) ; - test_assert(after->b.list.next == item, "ddl_in_after broken") ; - test_assert(item->b.list.prev == after, "ddl_in_after broken") ; - ddt_test_list_add(item, b_list) ; - ddt_verify_lists() ; + ddt_test_make_lists(n, n) ; + + item = ddt_new_item() ; + + aux = ddt_aux(a_list) ; + after = aux_item(aux, w) ; + + ddl_in_after(after, a_base, item, a) ; + + if (after != NULL) + aux_insert(aux, w+1, item) ; + else + aux_push_tail(aux, item) ; + + ddt_verify_lists() ; + + item = ddt_new_item() ; + + aux = ddt_aux(b_list) ; + after = aux_item(aux, w) ; + + ddl_in_after(after, b->base, item, b) ; + + if (after != NULL) + aux_insert(aux, w+1, item) ; + else + aux_push_tail(aux, item) ; + + ddt_verify_lists() ; + } ; --n ; } ; @@ -1731,38 +1824,58 @@ test_ddl(void) /* ddl_in_before(before, base, item, list) -- insert before * - * Cases: (a) before first and only (so is also last) - * (b) before first when more than one - * (c) before last when more than one - * (d) before something between + * Cases: (a) before NULL when list empty + * (b) before NULL when list has 1 or more entries + * (c) before first and only (so is also last) + * (d) before first when more than one + * (e) before last when more than one + * (f) before something between */ printf(" ddl_in_before test") ; n = base_n + (rand() % rand_n) ; - while (n) + while (n >= 0) { - ddt_item item ; - ddt_item before ; + int w ; printf(".") ; - ddt_test_make_lists(&test, n) ; + for (w = 0 ; w <= n ; ++w) + { + ddt_item item ; + ddt_item before ; + vector aux ; - item = ddt_new_item() ; - before = test.list[a_list].where[n % where_count] ; - ddl_in_before(before, a_base, item, a.list) ; - test_assert(before->a.list.prev == item, "ddl_in_before broken") ; - test_assert(item->a.list.next == before, "ddl_in_before broken") ; - ddt_test_list_add(item, a_list) ; - ddt_verify_lists() ; + ddt_test_make_lists(n, n) ; - item = ddt_new_item() ; - before = test.list[b_list].where[n % where_count] ; - ddl_in_before(before, b->base, item, b.list) ; - test_assert(before->b.list.prev == item, "ddl_in_before broken") ; - test_assert(item->b.list.next == before, "ddl_in_before broken") ; - ddt_test_list_add(item, b_list) ; - ddt_verify_lists() ; + item = ddt_new_item() ; + + aux = ddt_aux(a_list) ; + before = aux_item(aux, w) ; + + ddl_in_before(before, a_base, item, a) ; + + if (before != NULL) + aux_insert(aux, w, item) ; + else + aux_push_head(aux, item) ; + + ddt_verify_lists() ; + + item = ddt_new_item() ; + + aux = ddt_aux(b_list) ; + before = aux_item(aux, w) ; + + ddl_in_before(before, b->base, item, b) ; + + if (before != NULL) + aux_insert(aux, w, item) ; + else + aux_push_head(aux, item) ; + + ddt_verify_lists() ; + } ; --n ; } ; @@ -1782,46 +1895,37 @@ test_ddl(void) ddt_item item ; ddt_item temp ; ddt_item peek ; - int ordinal ; printf(".") ; - ddt_test_make_lists(&test, n) ; + ddt_test_make_lists(n, n) ; - ordinal = 0 ; while (1) { peek = a_base.head ; - temp = ddl_pop(&item, a_base, a.list) ; + temp = ddl_pop(&item, a_base, a) ; test_assert(temp == item, "ddl_pop broken") ; test_assert(peek == item, "ddl_pop broken") ; - ddt_test_list_del(item, a_list) ; + aux_pop_head(ddt_aux(a_list)) ; ddt_verify_lists() ; if (item == NULL) break ; - - ++ordinal ; - test_assert(item->a.ordinal == ordinal, "ddl_pop not in order") ; } ; - ordinal = 0 ; while (1) { peek = b->base.head ; - temp = ddl_pop(&item, b->base, b.list) ; + temp = ddl_pop(&item, b->base, b) ; test_assert(temp == item, "ddl_pop broken") ; test_assert(peek == item, "ddl_pop broken") ; - ddt_test_list_del(item, b_list) ; + aux_pop_head(ddt_aux(b_list)) ; ddt_verify_lists() ; if (item == NULL) break ; - - ++ordinal ; - test_assert(item->b.ordinal == ordinal, "ddl_pop not in order") ; } ; --n ; @@ -1834,7 +1938,6 @@ test_ddl(void) * (b) list with one item * (c) empty list */ - printf(" ddl_crop test") ; n = base_n + (rand() % rand_n) ; @@ -1843,58 +1946,37 @@ test_ddl(void) ddt_item item ; ddt_item temp ; ddt_item peek ; - int ordinal ; printf(".") ; - ddt_test_make_lists(&test, n) ; + ddt_test_make_lists(n, n) ; - ordinal = 0 ; while (1) { peek = a_base.tail ; - temp = ddl_crop(&item, a_base, a.list) ; + temp = ddl_crop(&item, a_base, a) ; test_assert(temp == item, "ddl_crop broken") ; test_assert(peek == item, "ddl_crop broken") ; - ddt_test_list_del(item, a_list) ; + aux_pop_tail(ddt_aux(a_list)) ; ddt_verify_lists() ; if (item == NULL) break ; - - if (ordinal == 0) - ordinal = item->a.ordinal ; - else - { - test_assert(ordinal > 0, "ordinal out of whack") ; - --ordinal ; - test_assert(item->a.ordinal == ordinal, "ddl_crop not in order") ; - } ; } ; - ordinal = 0 ; while (1) { peek = b->base.tail ; - temp = ddl_crop(&item, b->base, b.list) ; + temp = ddl_crop(&item, b->base, b) ; test_assert(temp == item, "ddl_crop broken") ; test_assert(peek == item, "ddl_crop broken") ; - ddt_test_list_del(item, b_list) ; + aux_pop_tail(ddt_aux(b_list)) ; ddt_verify_lists() ; if (item == NULL) break ; - - if (ordinal == 0) - ordinal = item->b.ordinal ; - else - { - test_assert(ordinal > 0, "ordinal out of whack") ; - --ordinal ; - test_assert(item->b.ordinal == ordinal, "ddl_crop not in order") ; - } ; } ; --n ; @@ -1906,28 +1988,38 @@ test_ddl(void) * Cases: (a) first and only (so is also last) * (b) first when more than one * (c) last when more than one - * (d) something between + * (d) everything between */ printf(" ddl_del test") ; n = base_n + (rand() % rand_n) ; - while (n) + while (n > 0) { - ddt_item item ; + int w ; printf(".") ; - ddt_test_make_lists(&test, n) ; + for (w = 0 ; w < n ; ++w) + { + ddt_item item ; + vector aux ; - item = test.list[a_list].where[n % where_count] ; - ddl_del(a_base, item, a.list) ; - ddt_test_list_del(item, a_list) ; - ddt_verify_lists() ; + ddt_test_make_lists(n, n) ; - item = test.list[b_list].where[n % where_count] ; - ddl_del(b->base, item, b.list) ; - ddt_test_list_del(item, b_list) ; - ddt_verify_lists() ; + aux = ddt_aux(a_list) ; + item = aux_item(aux, w) ; + + ddl_del(a_base, item, a) ; + aux_delete(aux, item) ; + ddt_verify_lists() ; + + aux = ddt_aux(b_list) ; + item = aux_item(aux, w) ; + + ddl_del(b->base, item, b) ; + aux_delete(aux, item) ; + ddt_verify_lists() ; + } ; --n ; } ; @@ -1949,18 +2041,20 @@ test_ddl(void) printf(".") ; - ddt_test_make_lists(&test, n) ; + ddt_test_make_lists(n, n) ; while (1) { item = a_base.head ; - peek = (item != NULL) ? item->a.list.next : NULL ; + peek = (item != NULL) ? item->a.next : NULL ; - ddl_del_head(a_base, a.list) ; + ddl_del_head(a_base, a) ; test_assert(a_base.head == peek, "ddl_del_head broken") ; - ddt_test_list_del(item, a_list) ; + if (item != NULL) + aux_delete(ddt_aux(a_list), item) ; + ddt_verify_lists() ; if (item == NULL) @@ -1970,13 +2064,15 @@ test_ddl(void) while (1) { item = b->base.head ; - peek = (item != NULL) ? item->b.list.next : NULL ; + peek = (item != NULL) ? item->b.next : NULL ; - ddl_del_head(b->base, b.list) ; + ddl_del_head(b->base, b) ; test_assert(b->base.head == peek, "ddl_del_head broken") ; - ddt_test_list_del(item, b_list) ; + if (item != NULL) + aux_delete(ddt_aux(b_list), item) ; + ddt_verify_lists() ; if (item == NULL) @@ -2003,18 +2099,20 @@ test_ddl(void) printf(".") ; - ddt_test_make_lists(&test, n) ; + ddt_test_make_lists(n, n) ; while (1) { item = a_base.tail ; - peek = (item != NULL) ? item->a.list.prev : NULL ; + peek = (item != NULL) ? item->a.prev : NULL ; - ddl_del_tail(a_base, a.list) ; + ddl_del_tail(a_base, a) ; test_assert(a_base.tail == peek, "ddl_del_tail broken") ; - ddt_test_list_del(item, a_list) ; + if (item != NULL) + aux_delete(ddt_aux(a_list), item) ; + ddt_verify_lists() ; if (item == NULL) @@ -2024,13 +2122,15 @@ test_ddl(void) while (1) { item = b->base.tail ; - peek = (item != NULL) ? item->b.list.prev : NULL ; + peek = (item != NULL) ? item->b.prev : NULL ; - ddl_del_tail(b->base, b.list) ; + ddl_del_tail(b->base, b) ; test_assert(b->base.tail == peek, "ddl_del_tail broken") ; - ddt_test_list_del(item, b_list) ; + if (item != NULL) + aux_delete(ddt_aux(b_list), item) ; + ddt_verify_lists() ; if (item == NULL) @@ -2055,7 +2155,7 @@ test_ddl(void) { printf(".") ; - ddt_test_make_lists(&test, n) ; + ddt_test_make_lists(n, n) ; test_assert(ddl_head(a_base) == a_base.head, "ddl_head broken") ; test_assert(ddl_tail(a_base) == a_base.tail, "ddl_head broken") ; @@ -2066,7 +2166,6 @@ test_ddl(void) } ; printf("\n") ; - /* ddl_next(item, next) -- step to next item, if any * ddl_prev(item, next) -- step to prev item, if any * @@ -2078,39 +2177,444 @@ test_ddl(void) printf(" ddl_next and ddl_prev test") ; n = base_n + (rand() % rand_n) ; - while (n) + while (n > 0) { - ddt_item item ; - ddt_item where ; + int w ; printf(".") ; - ddt_test_make_lists(&test, n) ; + for (w = 0 ; w < n ; ++w) + { + ddt_item item ; + vector aux ; + int i ; + + ddt_test_make_lists(n, n) ; + + aux = ddt_aux(a_list) ; + item = aux_item(aux, w) ; + + i = aux_find(aux, item) ; + test_assert(i == w, "ddl_next/_prev list and aux list out of step") ; - where = test.list[a_list].where[n % where_count] ; - item = ddl_next(where, a.list) ; - test_assert(item == where->a.list.next, "ddl_next broken") ; + test_assert(ddl_next(item, a) == aux_next_item(aux, i), + "ddl_next broken") ; + test_assert(ddl_prev(item, a) == aux_prev_item(aux, i), + "ddl_prev broken") ; - where = test.list[b_list].where[n % where_count] ; - item = ddl_next(where, b.list) ; - test_assert(item == where->b.list.next, "ddl_next broken") ; + aux = ddt_aux(b_list) ; + item = aux_item(aux, w) ; - where = test.list[a_list].where[n % where_count] ; - item = ddl_prev(where, a.list) ; - test_assert(item == where->a.list.prev, "ddl_prev broken") ; + i = aux_find(aux, item) ; + test_assert(i == w, "ddl_next/_prev list and aux list out of step") ; - where = test.list[b_list].where[n % where_count] ; - item = ddl_prev(where, b.list) ; - test_assert(item == where->b.list.prev, "ddl_prev broken") ; + test_assert(ddl_next(item, b) == aux_next_item(aux, i), + "ddl_next broken") ; + test_assert(ddl_prev(item, b) == aux_prev_item(aux, i), + "ddl_prev broken") ; + } ; --n ; } ; printf("\n") ; + /* ddl_slice(base, sub, list) -- remove sublist from given list + * + * Cases: (a) sub is empty + * (b) sub is the entire list -- list with 1 or more entries. + * (c) sub includes head of list, but 1 or more entries remain + * (d) sub includes tail of list, but 1 or more entries remain + * (e) sub is part of list, and 1 or more entries remain at either + * end. + */ + printf(" ddl_slice") ; -} + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + int s, e ; + + printf(".") ; + + for (s = 0 ; s <= n ; ++s) + { + for (e = s ; e <= n ; ++e) + { + vector aux ; + + ddt_test_make_lists(n, 0) ; + + if (s < e) + { + vector aux_sub ; + + aux = ddt_aux(a_list) ; + + a_sub.head = aux_item(aux, s) ; + a_sub.tail = aux_item(aux, e - 1) ; + + aux_sub = ddt_aux(a_sub_list) ; + assert(vector_length(aux_sub) == 0) ; + + vector_move_extract(aux_sub, aux, s, e - s) ; + } ; + + ddl_slice(a_base, a_sub, a) ; + ddt_verify_lists() ; + + if (s < e) + { + vector aux_sub ; + + aux = ddt_aux(b_list) ; + + b_sub.head = aux_item(aux, s) ; + b_sub.tail = aux_item(aux, e - 1) ; + + aux_sub = ddt_aux(b_sub_list) ; + assert(vector_length(aux_sub) == 0) ; + + vector_move_extract(aux_sub, aux, s, e - s) ; + } ; + + ddl_slice(b->base, b_sub, b) ; + ddt_verify_lists() ; + } ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_splice_after(after, base, sub, list) + * -- insert sublist after given item + * Cases: (a) sub is empty and list is empty + * (b) sub has 1 or more entries and list is empty (after = NULL). + * (c) sub includes head of list, but 1 or more entries remain + * (d) sub includes tail of list, but 1 or more entries remain + * (e) sub is part of list, and 1 or more entries remain at either + * end. + */ + printf(" ddl_splice_after") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + printf(".") ; + + int m ; + + for (m = 0 ; m <= 5 ; ++m) + { + int w ; + + for (w = 0 ; w <= n ; ++w) + { + ddt_item after ; + vector aux, s_aux ; + + ddt_test_make_lists(n, m) ; + + aux = ddt_aux(a_list) ; + after = aux_item(aux, w) ; + + ddl_splice_after(after, a_base, a_sub, a) ; + + s_aux = ddt_aux(a_sub_list) ; + + if (after != NULL) + vector_move_replace(aux, w+1, 0, s_aux, 0, m) ; + else + vector_move_replace(aux, w, 0, s_aux, 0, m) ; + + ddl_init(a_sub) ; + + ddt_verify_lists() ; + + aux = ddt_aux(b_list) ; + after = aux_item(aux, w) ; + + ddl_splice_after(after, b->base, b_sub, b) ; + + s_aux = ddt_aux(b_sub_list) ; + + if (after != NULL) + vector_move_replace(aux, w+1, 0, s_aux, 0, m) ; + else + vector_move_replace(aux, w, 0, s_aux, 0, m) ; + + ddl_init(b_sub) ; + + ddt_verify_lists() ; + } ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_splice_before(before, base, sub, list) + * -- insert sublist before given item + * + * Cases: (a) sub is empty and list is empty + * (b) sub has 1 or more entries and list is empty (before = NULL). + * (c) sub includes head of list, but 1 or more entries remain + * (d) sub includes tail of list, but 1 or more entries remain + * (e) sub is part of list, and 1 or more entries remain at either + * end. + */ + printf(" ddl_splice_before") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + printf(".") ; + + int m ; + + for (m = 0 ; m <= 5 ; ++m) + { + int w ; + + for (w = 0 ; w <= n ; ++w) + { + ddt_item before ; + vector aux, s_aux ; + + ddt_test_make_lists(n, m) ; + + aux = ddt_aux(a_list) ; + before = aux_item(aux, w) ; + + ddl_splice_before(before, a_base, a_sub, a) ; + + s_aux = ddt_aux(a_sub_list) ; + + if (before != NULL) + vector_move_replace(aux, w, 0, s_aux, 0, m) ; + else + vector_move_replace(aux, 0, 0, s_aux, 0, m) ; + + ddl_init(a_sub) ; + + ddt_verify_lists() ; + + aux = ddt_aux(b_list) ; + before = aux_item(aux, w) ; + + ddl_splice_before(before, b->base, b_sub, b) ; + + s_aux = ddt_aux(b_sub_list) ; + + if (before != NULL) + vector_move_replace(aux, w, 0, s_aux, 0, m) ; + else + vector_move_replace(aux, 0, 0, s_aux, 0, m) ; + + ddl_init(b_sub) ; + + ddt_verify_lists() ; + } ; + } ; + + --n ; + } ; + printf("\n") ; + +} ; /* * TODO * */ + +/*============================================================================== + * Auxilliary list handling -- so that can test one list structure against + * another. + * + * Implements list as vector of pointers. + */ + +/*------------------------------------------------------------------------------ + * Create new, empty aux list (vector) + */ +static vector +aux_init(void) +{ + return vector_init_new(NULL, 50) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty the aux vector + */ +static void +aux_reset(vector aux) +{ + vector_set_length(aux, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Get number of items in the given aux list + */ +static int +aux_length(vector aux) +{ + return vector_length(aux) ; +} ; + +/*------------------------------------------------------------------------------ + * Get item at the given index -- -ve counts from length + * + * Index must be -length..length + */ +static void* +aux_item(vector aux, int index) +{ + int length ; + + length = vector_length(aux) ; + + if (index < 0) + index += length ; + + if ((index >= 0) && (index < length)) + return vector_get_item(aux, index) ; + else + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Get item after item at the given index -- -ve counts from length + * + * Index must be -length..length + */ +static void* +aux_next_item(vector aux, int index) +{ + int length ; + + length = vector_length(aux) ; + + if (index < 0) + index += length ; + + if ((index >= 0) && (index < length)) + return vector_get_item(aux, index + 1) ; + else + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Get item before the item at the given index -- -ve counts from length + * + * Offset must be -length..length + */ +static void* +aux_prev_item(vector aux, int index) +{ + int length ; + + length = vector_length(aux) ; + + if (index < 0) + index += length ; + + if ((index > 0) && (index <= length)) + return vector_get_item(aux, index - 1) ; + else + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Insert item at given offset in the aux list. + * + * Item(s) at and beyond the given offset (if any) move along the list. + * + * 0 => at start + * -1 => at end + * + * Offset must be -1..length + */ +static void +aux_insert(vector aux, int index, void* item) +{ + if (index >= 0) + assert((uint)index <= vector_length(aux)) ; + else + index = vector_length(aux) ; + + vector_insert_item(aux, index, item) ; +} ; + +/*------------------------------------------------------------------------------ + * Find given item in the aux list and remove it. + * + * Shortens the list by 1 ! + */ +static void +aux_delete(vector aux, void* item) +{ + int index ; + + index = aux_find(aux, item) ; + + if (index < 0) + test_assert(index >= 0, "aux_delete: item not found !") ; + else + vector_delete_item(aux, index) ; +} ; + +/*------------------------------------------------------------------------------ + * Push item onto front of list. + */ +static void +aux_push_head(vector aux, void* item) +{ + vector_unshift_item(aux, item) ; /* opposite sense to vector ! */ +} ; + +/*------------------------------------------------------------------------------ + * Pop item from front of list -- returns NULL if list is empty. + */ +static void* +aux_pop_head(vector aux) +{ + return vector_shift_item(aux) ; /* opposite sense to vector ! */ +} ; + +/*------------------------------------------------------------------------------ + * Append item to end of list. + */ +static void +aux_push_tail(vector aux, void* item) +{ + vector_push_item(aux, item) ; /* opposite sense to vector ! */ +} ; + +/*------------------------------------------------------------------------------ + * Remove item from end of list -- returns NULL if list is empty. + */ +static void* +aux_pop_tail(vector aux) +{ + return vector_pop_item(aux) ; /* opposite sense to vector ! */ +} ; + +/*------------------------------------------------------------------------------ + * Find index of item -- returns -1 if not found. + */ +static int +aux_find(vector aux, void* item) +{ + int i, l ; + + l = aux_length(aux) ; + + for (i = 0 ; i < l ; ++i) + if (aux_item(aux, i) == item) + return i ; + + return -1 ; +} ; + + diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index e7afc49d..c6be8ea3 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -391,6 +391,7 @@ typedef config_item_list_t* config_item_list ; typedef enum /* types of config_item */ { it_dummy = 0, + it_node, /* Node is a list of line/group */ it_line, it_group, @@ -500,6 +501,7 @@ struct config_collection config_fragment fragment_list ; qstring_t temp ; /* used when creating fragments and names */ + qstring_t comment ; /* used when creating comment names */ } ; /*------------------------------------------------------------------------------ @@ -524,10 +526,11 @@ static void vtysh_config_parse_group_end(config_collection collection) ; static void vtysh_config_node_find(config_collection collection) ; static bool vtysh_config_item_insert(config_collection collection, config_item_type_t it) ; -static bool vtysh_config_try_merge(config_collection collection, - config_item parent, config_name name) ; -static config_name vtysh_config_get_name(config_collection collection, - config_item parent, config_item_type_t type) ; +static config_item vtysh_config_try_merge(config_collection collection, + config_item_list list) ; +static config_name vtysh_config_get_name(config_collection collection) ; +static config_name vtysh_config_get_comment_name(config_collection collection, + config_item comment) ; static symbol_cmp_func vtysh_config_match_cmp ; static symbol_free_func vtysh_config_match_free ; @@ -573,6 +576,7 @@ vtysh_config_collection_new(void) * lump_list -- NULLs -- empty * fragment_list -- NULL -- empty * temp -- empty qstring (embedded) + * comment -- empty qstring (embedded) */ confirm(NULL_NODE == 0) ; confirm(ELSTRING_INIT_ALL_ZEROS) ; @@ -936,7 +940,7 @@ vtysh_config_item_insert(config_collection collection, config_item_type_t it) { config_item parent ; config_name name ; - bool merged ; + config_item merged ; bool co_opted ; assert((it == it_line) || (it == it_group) || (it == it_dummy)) ; @@ -997,8 +1001,8 @@ vtysh_config_item_insert(config_collection collection, config_item_type_t it) ddl_append(parent->list, collection->last_item, siblings) ; } ; - /* We now have a new item, attached to the parent. For that item we have - * the following set: + /* We now have a new collection->last_item, attached to its parent. For that + * item we have the following set: * * parent * ordinal @@ -1009,10 +1013,10 @@ vtysh_config_item_insert(config_collection collection, config_item_type_t it) * * And everything else is clear. * - * Now need to establish, and register, the name of the item. + * Now need to establish, and register, the name of the item -- based on the + * parsed stuff still sitting in the collection. */ - collection->last_item->name = - name = vtysh_config_get_name(collection, parent, it) ; + name = vtysh_config_get_name(collection) ; /* If the name is unique, then cannot merge. * @@ -1022,14 +1026,16 @@ vtysh_config_item_insert(config_collection collection, config_item_type_t it) * If does not merge, we add the new item to the owners of the name. */ if (name->list == NULL) - merged = false ; + merged = NULL ; else - merged = vtysh_config_try_merge(collection, parent, name) ; + merged = vtysh_config_try_merge(collection, &parent->list) ; - if (!merged) - ssl_append(name->list, collection->last_item, also) ; + if (merged == NULL) + ssl_push(name->list, collection->last_item, also) ; + else + collection->last_item = merged ; - return merged ; + return merged != NULL ; } ; /*------------------------------------------------------------------------------ @@ -1205,11 +1211,14 @@ vtysh_config_node_find(config_collection collection) * separator "following" it. This means that multiple blank lines at the start * of a block of comments will be squashed together, and squashed with blank * '!' and/or '#' comment lines. + * + * NB: does not name the comment item. That is done when the it_dummy item + * is merged with a real item. */ static void vtysh_config_parse_comment(config_collection collection) { - config_item new_item, parent ; + config_item new_item, comment_parent ; /* If the last item was it_comment, then need to create new item * and attach it to the comment part of the parent @@ -1217,29 +1226,112 @@ vtysh_config_parse_comment(config_collection collection) * If the last item was not comment, then need to start a (pro tem) it_dummy * item, to which the comment can be attached. */ - parent = collection->last_item ; + comment_parent = collection->last_item ; - if (parent != NULL) + if ((comment_parent != NULL) && (comment_parent->type == it_comment)) { - if (parent->type == it_comment) - parent = parent->parent ; + comment_parent = comment_parent->parent ; } else { - vtysh_config_item_insert(collection, it_dummy) ; + if (comment_parent != NULL) + qassert(comment_parent->type != it_dummy) ; - parent = collection->last_item ; + vtysh_config_item_insert(collection, it_dummy) ; + comment_parent = collection->last_item ; } ; - new_item = vtysh_config_item_new(collection, parent, it_comment) ; + new_item = vtysh_config_item_new(collection, comment_parent, it_comment) ; *new_item->line = *collection->line ; - ddl_append(parent->pre_comments, new_item, siblings) ; + ddl_append(comment_parent->pre_comments, new_item, siblings) ; collection->last_item = new_item ; } ; /*------------------------------------------------------------------------------ + * Merge one set of pre-comment lines with an existing one. + * + * This is used where items are being merged. + * + * If the target pre-comment lines are unnamed, names them. Then, name each + * source pre-comment line and merge it in. + */ +static void +vtysh_config_merge_comment(config_collection collection, + config_item target, config_item source) +{ + config_item comment ; + + /* Get the trivial cases out of the way. + */ + comment = ddl_head(source->pre_comments) ; + if (comment == NULL) + return ; + + qassert(comment->type == it_comment) ; + + comment = ddl_head(target->pre_comments) ; + if (comment == NULL) + { + /* Move source pre-comments to target, and update parent of all of + * those. + */ + target->pre_comments = source->pre_comments ; + ddl_init( source->pre_comments) ; + + comment = ddl_head(target->pre_comments) ; + while (comment != NULL) + { + qassert(comment->parent == source) ; + qassert(comment->type == it_comment) ; + + comment->parent = target ; + + comment = ddl_next(comment, siblings) ; + } ; + + return ; + } ; + + qassert(comment->type == it_comment) ; + + /* Name the target pre-comments, if required. + */ + if (comment->name == NULL) + { + qassert(comment->parent == target) ; + + comment->name = vtysh_config_get_comment_name(collection, comment) ; + comment = ddl_next(comment, siblings) ; + } ; + + /* Now merge the source comments in + */ + while (ddl_pop(&comment, source->pre_comments, siblings) != NULL) + { + config_name name ; + config_item merged ; + + qassert(comment->parent == source) ; + qassert(comment->type == it_comment) ; + qassert(ddl_head(comment->pre_comments) == NULL) ; + + comment->parent = target ; + name = vtysh_config_get_comment_name(collection, comment) ; + ddl_append(target->pre_comments, comment, siblings) ; + + if (name->list != NULL) + merged = vtysh_config_try_merge(collection, &target->pre_comments) ; + else + merged = NULL ; + + if (merged == NULL) + ssl_push(comment->name->list, comment, also) ; + } ; +} ; + +/*------------------------------------------------------------------------------ * We have a separator line. * * If we have a last_item, then we add a separator to it. @@ -1380,87 +1472,182 @@ vtysh_config_parse_group_end(config_collection collection) * Try to merge new item with an earlier one of the same name. * * The current last_item has been appended to the given parent, and has the - * given name. At least one other item has the same name. + * given name. At least one other item has the same name, so we have one or + * more "targets" to attempt to merge with. * * No value has yet been set for the current last_item, but it may have * pre_comments. * - * If we can move a "bubble" of items up the parent's list, such that the - * current last_item overlaps (one of) the items with the same name, then - * we do that and discard the duplicate node -- this is a "merge". Must also - * merge any pre-comment(s). + * We can move items up the parent's list, provided we are moving past items + * which have no daemons in common with the items we are moving. + * + * We have two cases: + * + * 1) where the current last_item and one or more items above it have + * at least one daemon in common. + * + * In this case we have a "bubble" above the current last_item which we + * will need to move past the "target", if the current last_item is to + * be merged with the "target". This means that: + * + * a) all items between the current last_item and the target must + * have nothing in common with the bubble and nothing in + * common with the current last_item. + * + * For otherwise neither the bubble nor the current last_item can + * slide up past these items. * - * Each item with the same name is known as a "target". + * b) the target must have nothing in common with the bubble. * - * We can collect a "bubble" of items to move up, by scanning up from the - * current last_item, while each item has at least the daemons in the bubble - * so far (and adding an item adds all its daemons to the bubble). + * For otherwise the "bubble" cannot slide up past the target. * - * The bubble can move up to the target, provided that no item between the - * bubble and the target has any items in common with the bubble. + * 2) where the current last_item has no daemons in common with its + * immediate predecessor. + * + * In this case we have an empty bubble. + * + * If we can move the (possibly empty) bubble and the current last_item up the + * list such that the current last_item overlaps (one of) the items with the + * same name, then we do that and discard the duplicate node -- this is a + * "merge". Must also merge any pre-comment(s). + * + * If there is a choice, will attempt to merge as far up the parent's list as + * possible. + + + * * Note that since we do this as we add items to the collection, the bubble is * always below the target. * */ -static bool -vtysh_config_try_merge(config_collection collection, config_item parent, - config_name name) +static config_item +vtysh_config_try_merge(config_collection collection, config_item_list list) { - daemon_set_t bubble_daemons ; - config_item item, bubble, seek, target ; + daemon_set_t bubble_daemons, total_daemons ; + config_item item, bubble, this, target ; /* Set bubble to contain the current last_item */ - bubble = item = collection->last_item ; - bubble_daemons = bubble->daemons ; + item = ddl_tail(*list) ; + total_daemons = item->daemons ; + + bubble = item ; /* bubble == item <=> empty bubble */ + bubble_daemons = 0 ; - target = NULL ; + target = NULL ; + + qassert(item->name->list != NULL) ; /* Scan upwards adding to "bubble", looking out for a name match. * - * We can add an item to the bubble iff it has all the bubble's daemons, at - * least. + * We can add an item to the bubble iff it has one or more daemons in common + * with the bubble or the item, and is NOT a target. */ while (1) { - bool addable ; - - seek = ddl_prev(bubble, siblings) ; + this = ddl_prev(bubble, siblings) ; - if (seek == NULL) - return false ; /* failed to find target */ + if (this == NULL) + return NULL ; /* failed to find target */ - /* If we cannot add the current seek item to the bubble, we are - * done with this phase. + /* Do not add completely disjoint item to the bubble. */ - if ((seek->daemons & bubble_daemons) != bubble_daemons) + if ((this->daemons & total_daemons) == 0) break ; - /* If we have a name match, then we are done. + /* Do not add a target item to the bubble. * - * If the bubble contains more than just the current last item, - * then we cannot merge, because we cannot slide the rest of the - * bubble past the target. + * If we find a target item, then we are either going to merge with it, + * or move past it. * - * If there is more than one target, then this one could not be merged - * with the nearest one above, which implies that something above this - * target prevented it and/or its bubble from sliding up. + * If there is more than one target item, then this one could not be + * merged with its predecessor, so something above prevented it from + * being moved. Adding this to the bubble would, therefore, be a waste + * of time. */ - if (seek->name == name) + if (this->name == item->name) + break ; + + /* Add item to bubble. + */ + bubble = this ; + bubble_daemons |= bubble->daemons ; + + total_daemons |= bubble_daemons ; + } ; + + /* Have constructed a (possibly empty) bubble, and we have "this" item above + * it. + * + * If we cannot move the bubble past this item, then we are done. If we have + * a target then note that. If we cannot move the bubble + the current + * last_item past this item, we are done. + */ + while ((this->daemons & bubble_daemons) == 0) + { + /* If we have found a target, then make a note, and stop immediately + * if there are no further targets. + */ + if (this->name == item->name) { - if (bubble != item) - return false ; + /* Good news -- found target ! + */ + if (target != NULL) + qassert(this == target->also) ; + target = this ; + + qassert(target->ordinal < item->ordinal) ; + + /* If there is only one possible target, then we stop. + */ + if (target->also == NULL) + break ; } ; - /* Add item to bubble. + /* If we cannot slide the bubble + current item past here, then we + * stop immediately. + */ + if ((this->daemons & total_daemons) != 0) + break ; + + /* Step up the list, stopping if run out. */ - bubble_daemons |= seek->daemons ; - bubble = seek ; + this = ddl_prev(this, siblings) ; + + if (this == NULL) + break ; + } ; + + /* If we have found a target to merge with: + * + * - merging any pre-comments with the target pre-comments. + * + * - merge the daemons. + * + * - merge the post-xxxx + * + * - discard the item being merged. + * + * - move the bubble above the target. + */ + if (target != NULL) + { + vtysh_config_merge_comment(collection, target, item) ; + + target->daemons |= item->daemons ; + + if (target->post_sep < item->post_sep) + target->post_sep = item->post_sep ; + + ddl_del(*list, item, siblings) ; + vtysh_config_item_free(item) ; + + } ; - return false ; + return target ; } ; /*============================================================================== @@ -1698,10 +1885,199 @@ vtysh_config_raw_free(config_collection collection) } ; qs_reset(collection->temp, keep_it) ; /* embedded */ + qs_reset(collection->comment, keep_it) ; /* embedded */ +} ; + +/*============================================================================== + * Name handling. + * + * The name of an item covers: + * + * * the ordinal of the parent item + * + * * the type of item + * + * * the item's "raw_name", which may be: + * + * - the tokens which form the item, separated by spaces + * + * - the entire line (for comments) less trailing whitespace + * + * While there is a single, global symbol table, the form of the name means + * that each name has local scope within the parent. + */ + +static config_name vtysh_config_make_name(config_collection collection, + config_item item, qstring raw_name) ; + +/*------------------------------------------------------------------------------ + * Get and set name for the new collection->last_item (just parsed and created) + * + * Returns: address of name object. + */ +static config_name +vtysh_config_get_name(config_collection collection) +{ + uint ti, tn ; + + ti = (collection->last_item->type == it_group) ? 2 : 0 ; + tn = collection->parsed->num_tokens ; + + return vtysh_config_make_name(collection, collection->last_item, + cmd_tokens_concat(collection->parsed, ti, tn - ti)) ; } ; +/*------------------------------------------------------------------------------ + * Get name for a comment item. + * + * Comment items only need names when two items carrying pre-comments are + * merged. + * + * For comments the original line is used, verbatim, for the name -- less any + * trailing whitespace. + * + * Returns: address of name object. + */ +static config_name +vtysh_config_get_comment_name(config_collection collection, config_item comment) +{ + qassert(comment->type == it_comment) ; + + qs_set_els(collection->comment, comment->line) ; + qs_trim(collection->comment, '\0') ; + + return vtysh_config_make_name(collection, comment, collection->comment) ; +} ; + +/*------------------------------------------------------------------------------ + * Make a name and either look it up in the collection's global name table, + * or add new entry to that. + * + * Requires: item->parent + * item->type + * the given "raw name" + * + * Returns: address of name object -- set into item->name + * + * All items with the same name share the same name object. So, can + * test for name equality by comparing name object addresses. + * + * The items to which a name applies are hung off the name->list entry, in + * LIFO order. A NULL name->list entry implies this is a new name. + */ +static config_name +vtysh_config_make_name(config_collection collection, config_item item, + qstring raw_name) +{ + config_name name ; + const char* tag ; + symbol sym ; + + qassert(item->name == NULL) ; /* can only have one */ + + switch (item->type) + { + case it_line: + tag = "" ; + break ; + + case it_group: + tag = "$" ; + break ; + + case it_comment: + tag = "!" ; + break ; + + default: + qassert(false) ; + tag = "?" ; + break ; + } ; + + qs_clear(collection->temp) ; + + qs_printf(collection->temp, "%u%s:%s", item->parent->ordinal, tag, + qs_string(raw_name)) ; + + sym = symbol_lookup(collection->match, qs_string(collection->temp), add) ; + + name = symbol_get_body(sym) ; + + if (name == NULL) + { + ulen len ; + + len = qs_len(collection->temp) + 1 ; /* including terminator */ + + name = vtysh_config_fragment_new(collection, + offsetof(config_name_t, string[len]), true /* align */) ; + + name->list = NULL ; + memcpy(name->string, qs_char(collection->temp), len) ; + + symbol_set_body(sym, name, true /* set */, free_it /* previous ! */) ; + } ; + + return item->name = name ; +} ; + +/*------------------------------------------------------------------------------ + * Compare name in the given config_name with the given name_string. + */ +static int +vtysh_config_match_cmp(const void* body, const void* name_string) +{ + const config_name_t* name = body ; + return strcmp(name->string, name_string) ; +} ; + +/*------------------------------------------------------------------------------ + * The symbol bodies are in fragments and are freed automatically. + */ +static void +vtysh_config_match_free(void* body) +{ +} ; + +/*============================================================================== + * The vtysh own configuration. + * + * There is not much of this. + * + * We don't write vtysh specific into file from vtysh. vtysh.conf should + * be edited by hand. + */ + +/*------------------------------------------------------------------------------ + * Show vtysh own configuration -- same like client daemon #vtysh-config-write + * + * This is for collecting the integrated configuration. + */ +static void +vtysh_config_own_config(vty vtysh) +{ + vty_out(vtysh, "#vtysh-config-daemon vtysh\n") ; + vty_out(vtysh, "#vtysh-config-node %s\n", cmd_node_name(CONFIG_NODE)) ; + + if (host.name_set) + vty_out(vtysh, "hostname %s\n", host.name) ; + + if (vtysh_integrated_vtysh_config) + vty_out(vtysh, "service integrated-vtysh-config\n") ; +} ; + +/*============================================================================== + * For diagnostic purposes -- "show" functions for data structures. + */ + extern void show_collected(config_collection collection) ; +static void show_collected_item(config_item item, qstring line) ; + +/*------------------------------------------------------------------------------ + * Output given collection to stderr + */ extern void show_collected(config_collection collection) { @@ -1726,8 +2102,9 @@ show_collected(config_collection collection) fprintf(stderr, "%d lumps\n", i) ; } ; -static void show_collected_item(config_item item, qstring line) ; - +/*------------------------------------------------------------------------------ + * Output given node to stderr + */ extern void show_collected_node(config_collection collection) { @@ -1768,6 +2145,9 @@ show_collected_node(config_collection collection) qs_free(line) ; } ; +/*------------------------------------------------------------------------------ + * Output given item to stderr + */ static void show_collected_item(config_item item, qstring line) { @@ -1820,135 +2200,3 @@ show_collected_item(config_item item, qstring line) } ; } ; -/*============================================================================== - * Name handling. - * - * When a new item is about to be inserted in the tree, need to get its name, - * so we can check if we have the same item already or later. - * - * If the config_name object has a NULL items entry, then this is the first - * occurrence of this name. - * - * The name covers: - * - * * the ordinal of the parent item - * - * * the type of item - * - * * the tokens which form the item, separated by spaces - * - * While there is a single, global symbol table, the form of the name means - * that each name has local scope within the parent. - * - * Returns: address of name object. - * - * All items with the same name share the same name object. So, can - * test for name equality by comparing name object addresses. - */ -static config_name -vtysh_config_get_name(config_collection collection, config_item parent, - config_item_type_t type) -{ - qstring tokens ; - config_name name ; - const char* tag ; - uint ti, tn ; - symbol sym ; - - ti = 0 ; - tn = collection->parsed->num_tokens ; - - switch (type) - { - case it_line: - tag = "" ; - break ; - - case it_group: - tag = "$" ; - ti = 2 ; - break ; - - case it_comment: - tag = "!" ; - break ; - - default: - qassert(false) ; - tag = "?" ; - break ; - } ; - - tokens = cmd_tokens_concat(collection->parsed, ti, tn - ti) ; - - qs_clear(collection->temp) ; - - qs_printf(collection->temp, "%u%s:%s", parent->ordinal, tag, qs_string(tokens)) ; - - sym = symbol_lookup(collection->match, qs_string(collection->temp), add) ; - - name = symbol_get_body(sym) ; - - if (name == NULL) - { - ulen len ; - - len = qs_len(collection->temp) + 1 ; /* including terminator */ - - name = vtysh_config_fragment_new(collection, - offsetof(config_name_t, string[len]), true /* align */) ; - - name->list = NULL ; - memcpy(name->string, qs_char(collection->temp), len) ; - - symbol_set_body(sym, name, true /* set */, free_it /* previous ! */) ; - } ; - - return name ; -} ; - -/*------------------------------------------------------------------------------ - * Compare name in the given config_name with the given name_string. - */ -static int -vtysh_config_match_cmp(const void* body, const void* name_string) -{ - const config_name_t* name = body ; - return strcmp(name->string, name_string) ; -} ; - -/*------------------------------------------------------------------------------ - * The symbol bodies are in fragments and are freed automatically. - */ -static void -vtysh_config_match_free(void* body) -{ -} ; - -/*============================================================================== - * The vtysh own configuration. - * - * There is not much of this. - * - * We don't write vtysh specific into file from vtysh. vtysh.conf should - * be edited by hand. - */ - -/*------------------------------------------------------------------------------ - * Show vtysh own configuration -- same like client daemon #vtysh-config-write - * - * This is for collecting the integrated configuration. - */ -static void -vtysh_config_own_config(vty vtysh) -{ - vty_out(vtysh, "#vtysh-config-daemon vtysh\n") ; - vty_out(vtysh, "#vtysh-config-node %s\n", cmd_node_name(CONFIG_NODE)) ; - - if (host.name_set) - vty_out(vtysh, "hostname %s\n", host.name) ; - - if (vtysh_integrated_vtysh_config) - vty_out(vtysh, "service integrated-vtysh-config\n") ; -} ; - |