diff options
author | Chris Hall <chris.hall@highwayman.com> | 2010-12-21 11:12:30 +0000 |
---|---|---|
committer | Chris Hall <chris.hall@highwayman.com> | 2010-12-21 11:12:30 +0000 |
commit | 121f2f888e02a28e7896f84dde019cb320f0b11d (patch) | |
tree | 99c3913759b80894b1cb83a508036223b9c98f5a | |
parent | d475a0f198f880595eb27e44008e5de3aad25d73 (diff) | |
download | quagga-121f2f888e02a28e7896f84dde019cb320f0b11d.tar.bz2 quagga-121f2f888e02a28e7896f84dde019cb320f0b11d.tar.xz |
Creation of pipework branch
93 files changed, 15968 insertions, 4968 deletions
@@ -30,7 +30,7 @@ #define _GMCH_BGP_H "19-Dec-2009" -#include <stdint.h> +#include "misc.h" #include "confirm.h" /*############################################################################## diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h index ca92238a..afa812f6 100644 --- a/bgpd/bgp_advertise.h +++ b/bgpd/bgp_advertise.h @@ -21,9 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _QUAGGA_BGP_ADVERTISE_H #define _QUAGGA_BGP_ADVERTISE_H -#ifndef Inline -#define Inline static inline -#endif +#include "lib/misc.h" /* BGP advertise FIFO. */ typedef struct bgp_advertise* bgp_advertise ; diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h index 895379aa..c02a84aa 100644 --- a/bgpd/bgp_aspath.h +++ b/bgpd/bgp_aspath.h @@ -21,10 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _QUAGGA_BGP_ASPATH_H #define _QUAGGA_BGP_ASPATH_H -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif +#include "lib/misc.h" /* AS path segment type. */ #define AS_SET 1 diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c index 1d847ee4..0df34bb0 100644 --- a/bgpd/bgp_clist.c +++ b/bgpd/bgp_clist.c @@ -127,7 +127,7 @@ community_list_lookup (struct community_list_handler *ch, if (!table) return NULL; - return symbol_get_value(symbol_seek(table, name)) ; + return symbol_get_value(symbol_lookup(table, name, no_add)) ; } static struct community_list * @@ -145,7 +145,7 @@ community_list_get (struct community_list_handler *ch, if (!table) return NULL; - sym = symbol_find(table, name) ; + sym = symbol_lookup(table, name, add) ; list = symbol_get_value(sym) ; if (!list) { @@ -768,10 +768,10 @@ community_list_terminate (struct community_list_handler *ch) { struct community_list *list ; - while ((list = symbol_table_ream_keep(&ch->community_list))) + while ((list = symbol_table_ream(&ch->community_list, keep_it))) community_list_delete(list) ; - while ((list = symbol_table_ream_keep(&ch->extcommunity_list))) + while ((list = symbol_table_ream(&ch->extcommunity_list, keep_it))) community_list_delete(list) ; XFREE (MTYPE_COMMUNITY_LIST_HANDLER, ch); diff --git a/bgpd/bgp_common.h b/bgpd/bgp_common.h index 0d2395ed..fb695125 100644 --- a/bgpd/bgp_common.h +++ b/bgpd/bgp_common.h @@ -22,18 +22,13 @@ #ifndef _QUAGGA_BGP_COMMON_H #define _QUAGGA_BGP_COMMON_H -#include <stdint.h> -#include <stdbool.h> +#include "misc.h" #include <sys/socket.h> #include "bgpd/bgp.h" #include "qafi_safi.h" #include "lib/zassert.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Here are a number of "incomplete" declarations, which allow a number of * bgpd structures to refer to each other. diff --git a/bgpd/bgp_connection.h b/bgpd/bgp_connection.h index 79270bd8..8c5e00d4 100644 --- a/bgpd/bgp_connection.h +++ b/bgpd/bgp_connection.h @@ -22,7 +22,7 @@ #ifndef _QUAGGA_BGP_CONNECTION_H #define _QUAGGA_BGP_CONNECTION_H -#include <stdbool.h> +#include "lib/misc.h" #include "lib/mqueue.h" #include "lib/qpthreads.h" @@ -40,10 +40,6 @@ #include "bgpd/bgp_notification.h" #include "bgpd/bgp_msg_read.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * The BGP Finite State Machine: states and events * diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h index ceec1b2f..7e3cceec 100644 --- a/bgpd/bgp_engine.h +++ b/bgpd/bgp_engine.h @@ -22,16 +22,14 @@ #ifndef _QUAGGA_BGP_ENGINE_H #define _QUAGGA_BGP_ENGINE_H +#include "lib/misc.h" + #include "bgpd/bgpd.h" #include "lib/mqueue.h" #include "lib/qpnexus.h" #include "lib/log.h" -#ifndef Inline -#define Inline static inline -#endif - enum { qdebug = #ifdef QDEBUG 1 diff --git a/bgpd/bgp_msg_write.h b/bgpd/bgp_msg_write.h index 77bfc1f2..8867e06f 100644 --- a/bgpd/bgp_msg_write.h +++ b/bgpd/bgp_msg_write.h @@ -24,8 +24,7 @@ #ifndef _QUAGGA_BGP_MSG_WRITE_H #define _QUAGGA_BGP_MSG_WRITE_H -#include <stdint.h> -#include <stdbool.h> +#include "misc.h" #include "bgpd/bgp_common.h" #include "bgpd/bgp_connection.h" diff --git a/bgpd/bgp_notification.h b/bgpd/bgp_notification.h index 20e96063..755f431e 100644 --- a/bgpd/bgp_notification.h +++ b/bgpd/bgp_notification.h @@ -24,13 +24,9 @@ #ifndef _QUAGGA_BGP_NOTIFY_H #define _QUAGGA_BGP_NOTIFY_H -#include <stddef.h> +#include "lib/misc.h" #include "bgpd/bgp_common.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * BGP NOTIFICATION message codes. */ diff --git a/bgpd/bgp_open_state.c b/bgpd/bgp_open_state.c index 9ca9617e..e1dbaeaf 100644 --- a/bgpd/bgp_open_state.c +++ b/bgpd/bgp_open_state.c @@ -48,7 +48,7 @@ bgp_open_state_init_new(bgp_open_state state) else memset(state, 0, sizeof(struct bgp_open_state)) ; - vector_init_new(&state->unknowns, 0) ; + vector_init_new(state->unknowns, 0) ; return state ; } @@ -66,10 +66,10 @@ bgp_open_state_free(bgp_open_state state) if (state != NULL) { - while ((unknown = vector_ream_keep(&state->unknowns)) != NULL) + while ((unknown = vector_ream(state->unknowns, keep_it)) != NULL) XFREE(MTYPE_TMP, unknown) ; - while ((afi_safi = vector_ream_keep(&state->afi_safi)) != NULL) + while ((afi_safi = vector_ream(state->afi_safi, keep_it)) != NULL) XFREE(MTYPE_TMP, afi_safi) ; XFREE(MTYPE_BGP_OPEN_STATE, state) ; @@ -222,7 +222,7 @@ bgp_open_state_unknown_add(bgp_open_state state, uint8_t code, if (length != 0) memcpy(unknown->value, value, length) ; - vector_push_item(&state->unknowns, unknown) ; + vector_push_item(state->unknowns, unknown) ; } ; /*------------------------------------------------------------------------------ @@ -231,7 +231,7 @@ bgp_open_state_unknown_add(bgp_open_state state, uint8_t code, extern int bgp_open_state_unknown_count(bgp_open_state state) { - return vector_end(&state->unknowns) ; + return vector_end(state->unknowns) ; } ; /*------------------------------------------------------------------------------ @@ -240,7 +240,7 @@ bgp_open_state_unknown_count(bgp_open_state state) extern bgp_cap_unknown bgp_open_state_unknown_cap(bgp_open_state state, unsigned index) { - return vector_get_item(&state->unknowns, index) ; + return vector_get_item(state->unknowns, index) ; } ; /*============================================================================== @@ -264,7 +264,7 @@ bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi, afi_safi->safi = safi ; afi_safi->cap_code = cap_code ; - vector_push_item(&state->afi_safi, afi_safi) ; + vector_push_item(state->afi_safi, afi_safi) ; return afi_safi ; } ; @@ -275,7 +275,7 @@ bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi, extern int bgp_open_state_afi_safi_count(bgp_open_state state) { - return vector_end(&state->afi_safi) ; + return vector_end(state->afi_safi) ; } ; /*------------------------------------------------------------------------------ @@ -284,7 +284,7 @@ bgp_open_state_afi_safi_count(bgp_open_state state) extern bgp_cap_afi_safi bgp_open_state_afi_safi_cap(bgp_open_state state, unsigned index) { - return vector_get_item(&state->afi_safi, index) ; + return vector_get_item(state->afi_safi, index) ; } ; /*============================================================================== diff --git a/bgpd/bgp_open_state.h b/bgpd/bgp_open_state.h index 8c30712b..e88deda0 100644 --- a/bgpd/bgp_open_state.h +++ b/bgpd/bgp_open_state.h @@ -22,16 +22,12 @@ #ifndef _QUAGGA_BGP_OPEN_STATE_H #define _QUAGGA_BGP_OPEN_STATE_H -#include <stdint.h> +#include "misc.h" #include "bgpd/bgp.h" #include "bgpd/bgp_common.h" #include "lib/vector.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * BGP Open State. * @@ -114,8 +110,8 @@ struct bgp_open_state bool has_restarted ; /* Restart State flag */ unsigned restart_time ; /* Restart Time in seconds */ - struct vector unknowns ; /* list of bgp_cap_unknown */ - struct vector afi_safi ; /* various afi/safi capabilities */ + vector_t unknowns ; /* list of bgp_cap_unknown */ + vector_t afi_safi ; /* various afi/safi capabilities */ } ; /*============================================================================== diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 62d76c60..9a5aaf30 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -776,7 +776,7 @@ bgp_route_refresh_send (struct peer *peer, afi_t afi, safi_t safi, bgp_orf_entry orfpe = NULL; struct prefix_list *plist = NULL; struct orf_prefix orfp; - vector_index i; + vector_index_t i; int orf_refresh = 0; enum prefix_list_type pe_type; @@ -1837,7 +1837,7 @@ bgp_route_refresh_recv(bgp_peer peer, bgp_route_refresh rr) { afi_t afi; safi_t safi; - vector_index i; + vector_index_t i, e; char name[BUFSIZ]; int ret; @@ -1852,11 +1852,11 @@ bgp_route_refresh_recv(bgp_peer peer, bgp_route_refresh rr) ret = snprintf (name, BUFSIZ, "%s.%d.%d", peer->host, afi, safi); assert(ret < BUFSIZ); - if (rr->entries.end > 0) + if ((e = bgp_orf_get_count(rr)) > 0) { - for (i = 0; i < rr->entries.end; ++i) + for (i = 0; i < e; ++i) { - bgp_orf_entry orfep = vector_slot(&rr->entries, i); + bgp_orf_entry orfep = vector_slot(rr->entries, i); /* ignore unknown */ if (orfep->unknown) diff --git a/bgpd/bgp_peer_index.c b/bgpd/bgp_peer_index.c index 518a22bc..10d53f62 100644 --- a/bgpd/bgp_peer_index.c +++ b/bgpd/bgp_peer_index.c @@ -59,7 +59,7 @@ */ static struct symbol_table bgp_peer_index ; /* lookup by 'name' */ -static struct vector bgp_peer_id_index ; /* lookup by peer-id */ +static vector_t bgp_peer_id_index ; /* lookup by peer-id */ static qpt_mutex bgp_peer_index_mutex = NULL ; @@ -112,7 +112,7 @@ bgp_peer_index_init(void* parent) sockunion_symbol_hash, /* "name" is an IP Address */ NULL) ; /* no value change call-back */ - vector_init_new(&bgp_peer_id_index, bgp_peer_id_unit) ; + vector_init_new(bgp_peer_id_index, bgp_peer_id_unit) ; /* Initialise table entirely empty */ bgp_peer_id_table = NULL ; @@ -149,11 +149,11 @@ bgp_peer_index_reset(void) bgp_peer_id_table_chunk chunk ; /* Ream out the peer id vector -- checking that all entries are empty */ - while ((entry = vector_ream_keep(&bgp_peer_id_index)) != NULL) + while ((entry = vector_ream(bgp_peer_id_index, keep_it)) != NULL) passert((entry->peer == NULL) && (entry->next_free != entry)) ; /* Discard body of symbol table -- must be empty ! */ - symbol_table_reset_keep(&bgp_peer_index) ; + symbol_table_reset(&bgp_peer_index, keep_it) ; /* Discard the empty chunks of entries */ while (bgp_peer_id_table != NULL) @@ -204,7 +204,7 @@ bgp_peer_index_register(bgp_peer peer, union sockunion* su) entry = bgp_peer_id_free_head ; bgp_peer_id_free_head = entry->next_free ; - assert(vector_get_item(&bgp_peer_id_index, entry->id) == entry) ; + assert(vector_get_item(bgp_peer_id_index, entry->id) == entry) ; /* Initialise the entry -- the id is already set */ entry->peer = peer ; @@ -213,7 +213,7 @@ bgp_peer_index_register(bgp_peer peer, union sockunion* su) peer->index_entry = entry; /* Insert the new entry into the symbol table. */ - entry = symbol_set_value(symbol_find(&bgp_peer_index, su), entry) ; + entry = symbol_set_value(symbol_lookup(&bgp_peer_index, su, add), entry) ; BGP_PEER_INDEX_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ @@ -241,7 +241,7 @@ bgp_peer_index_deregister(bgp_peer peer, union sockunion* su) BGP_PEER_INDEX_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ - sym = symbol_seek(&bgp_peer_index, su) ; + sym = symbol_lookup(&bgp_peer_index, su, no_add) ; passert(sym != NULL) ; entry = symbol_delete(sym) ; @@ -288,7 +288,7 @@ bgp_peer_index_seek_entry(union sockunion* su) /* Only the Routing Engine can add/delete entries -- so no lock required */ - entry = symbol_get_value(symbol_seek(&bgp_peer_index, su)) ; + entry = symbol_get_value(symbol_lookup(&bgp_peer_index, su, no_add)) ; if (entry != NULL) assert((entry->peer != NULL) && (entry->next_free = entry)) ; @@ -340,7 +340,7 @@ bgp_peer_index_seek_accept(union sockunion* su, bool* p_found) BGP_PEER_INDEX_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ - entry = symbol_get_value(symbol_seek(&bgp_peer_index, su)) ; + entry = symbol_get_value(symbol_lookup(&bgp_peer_index, su, no_add)) ; if (entry != NULL) { @@ -375,7 +375,7 @@ static void bgp_peer_id_table_free_entry(bgp_peer_index_entry entry) { assert((entry != NULL) && (entry->id < bgp_peer_id_count)) ; - assert(vector_get_item(&bgp_peer_id_index, entry->id) == entry) ; + assert(vector_get_item(bgp_peer_id_index, entry->id) == entry) ; if (bgp_peer_id_free_head == NULL) bgp_peer_id_free_head = entry ; @@ -424,7 +424,7 @@ bgp_peer_id_table_make_ids(void) while (id_new < bgp_peer_id_count) { - vector_set_item(&bgp_peer_id_index, id_new, entry) ; + vector_set_item(bgp_peer_id_index, id_new, entry) ; entry->id = id_new ; bgp_peer_id_table_free_entry(entry) ; diff --git a/bgpd/bgp_route_refresh.c b/bgpd/bgp_route_refresh.c index 5c2b6e5c..7a8d5df5 100644 --- a/bgpd/bgp_route_refresh.c +++ b/bgpd/bgp_route_refresh.c @@ -60,7 +60,7 @@ bgp_route_refresh_new(iAFI_t afi, iSAFI_t safi, unsigned count) rr->afi = afi ; rr->safi = safi ; - vector_init_new(&rr->entries, count) ; + vector_init_new(rr->entries, count) ; /* rest of bgp_route_refresh zeroised -- not relevant when vector empty */ @@ -74,7 +74,7 @@ extern void bgp_route_refresh_free(bgp_route_refresh rr) { bgp_orf_entry entry ; - while((entry = vector_ream_keep(&rr->entries)) != NULL) + while((entry = vector_ream(rr->entries, keep_it)) != NULL) XFREE(MTYPE_BGP_ORF_ENTRY, entry) ; XFREE(MTYPE_BGP_ROUTE_REFRESH, rr) ; @@ -130,7 +130,7 @@ bgp_orf_entry_new(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form, orfe->form = form ; orfe->unknown = (unknown_size != 0) ; - vector_push_item(&rr->entries, orfe) ; + vector_push_item(rr->entries, orfe) ; return orfe ; } ; diff --git a/bgpd/bgp_route_refresh.h b/bgpd/bgp_route_refresh.h index b44ca9e0..78324ff4 100644 --- a/bgpd/bgp_route_refresh.h +++ b/bgpd/bgp_route_refresh.h @@ -22,16 +22,12 @@ #ifndef _QUAGGA_BGP_ROUTE_REFRESH_H #define _QUAGGA_BGP_ROUTE_REFRESH_H -#include <stddef.h> +#include "lib/misc.h" #include "bgpd/bgp_common.h" #include "lib/prefix.h" #include "lib/plist.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Structures to hold ROUTE-REFRESH and ORF */ @@ -81,7 +77,7 @@ struct bgp_route_refresh iAFI_t afi ; /* NB: Internet AFI/SAFI */ iSAFI_t safi ; - struct vector entries ; /* empty => simple ROUTE-REFRESH */ + vector_t entries ; /* empty => simple ROUTE-REFRESH */ bool defer ; /* otherwise: immediate */ @@ -120,13 +116,13 @@ bgp_orf_add_unknown(bgp_route_refresh rr, uint8_t orf_type, bgp_size_t length, Inline unsigned bgp_orf_get_count(bgp_route_refresh rr) { - return vector_end(&rr->entries) ; + return vector_end(rr->entries) ; } ; Inline bgp_orf_entry bgp_orf_get_entry(bgp_route_refresh rr, unsigned index) { - return vector_get_item(&rr->entries, index) ; + return vector_get_item(rr->entries, index) ; } ; #endif /* _QUAGGA_BGP_ROUTE_REFRESH_H */ diff --git a/bgpd/bgp_session.h b/bgpd/bgp_session.h index ccc7a28e..e1a4c51b 100644 --- a/bgpd/bgp_session.h +++ b/bgpd/bgp_session.h @@ -22,8 +22,8 @@ #ifndef _QUAGGA_BGP_SESSION_H #define _QUAGGA_BGP_SESSION_H -#include <stdbool.h> #include <zebra.h> +#include "lib/misc.h" #include "bgpd/bgp_common.h" #include "bgpd/bgp_engine.h" @@ -37,10 +37,6 @@ #include "lib/sockunion.h" #include "lib/mqueue.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * BGP Session data structure. * diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 222e4485..b8af6140 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -10365,8 +10365,9 @@ DEFUN (show_ip_community_list, { struct symbol_table* table; vector extract ; - vector_index i ; + vector_index_t i ; struct symbol* sym ; + struct community_list *list; table = community_list_master_lookup (bgp_clist, COMMUNITY_LIST_MASTER); if (table == NULL) @@ -10375,7 +10376,11 @@ DEFUN (show_ip_community_list, extract = symbol_table_extract(table, NULL, NULL, 0, symbol_mixed_name_cmp) ; for (VECTOR_ITEMS(extract, sym, i)) - community_list_show (vty, symbol_get_value(sym)); + { + list = symbol_get_value(sym) ; + if (list != NULL) + community_list_show (vty, list) ; + } vector_free(extract) ; /* discard temporary vector */ @@ -10719,7 +10724,7 @@ DEFUN (show_ip_extcommunity_list, { struct symbol_table* table; vector extract ; - vector_index i ; + vector_index_t i ; struct symbol* sym ; table = community_list_master_lookup (bgp_clist, EXTCOMMUNITY_LIST_MASTER); @@ -10729,7 +10734,11 @@ DEFUN (show_ip_extcommunity_list, extract = symbol_table_extract(table, NULL, NULL, 0, symbol_mixed_name_cmp) ; for (VECTOR_ITEMS(extract, sym, i)) - extcommunity_list_show (vty, symbol_get_value(sym)); + { + list = symbol_get_value(sym) ; + if (list != NULL) + extcommunity_list_show (vty, list) ; + } vector_free(extract) ; /* discard temporary vector */ @@ -10785,7 +10794,7 @@ community_list_config_write_list(struct vty* vty, int what) struct community_list *list; struct community_entry *entry; vector extract ; - vector_index i ; + vector_index_t i ; struct symbol* sym ; int write = 0; @@ -10796,6 +10805,10 @@ community_list_config_write_list(struct vty* vty, int what) for (VECTOR_ITEMS(extract, sym, i)) { list = symbol_get_value(sym) ; + + if (list == NULL) + continue ; + for (entry = list->head; entry; entry = entry->next) { const char* list_type = "" ; diff --git a/lib/Makefile.am b/lib/Makefile.am index e88c5998..39b6b03e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -14,8 +14,9 @@ libzebra_la_SOURCES = \ 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 heap.c \ qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \ - command_queue.c qlib_init.c pthread_safe.c list_util.c \ - vty_io.c vty_cli.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ + command_parse.c command_queue.c qlib_init.c pthread_safe.c list_util.c \ + vty_io.c vty_io_file.c vty_io_shell.c vty_io_term.c vty_cli.c \ + vty_io_basic.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ qiovec.c qfstring.c errno_names.c BUILT_SOURCES = memtypes.h route_types.h @@ -33,9 +34,11 @@ pkginclude_HEADERS = \ privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \ workqueue.h route_types.h symtab.h heap.h \ qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \ - command_queue.h qlib_init.h qafi_safi.h \ - confirm.h miyagi.h pthread_safe.h list_util.h node_type.h uty.h \ - vty_io.h vty_cli.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ + command_parse.h command_queue.h qlib_init.h qafi_safi.h \ + confirm.h misc.h vargs.h miyagi.h pthread_safe.h list_util.h \ + tstring.h node_type.h uty.h \ + vty_io.h vty_io_file.h vty_io_shell.h vty_io_term.h vty_cli.h \ + vty_io_basic.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ qiovec.h qfstring.h errno_names.h \ route_types.h command_execute.h diff --git a/lib/command.c b/lib/command.c index a78ac05c..3c4f056a 100644 --- a/lib/command.c +++ b/lib/command.c @@ -36,6 +36,7 @@ Boston, MA 02111-1307, USA. */ #include "command_execute.h" #include "workqueue.h" #include "command_queue.h" +#include "command_parse.h" /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ @@ -47,10 +48,13 @@ char *command_cr = NULL; /* Host information structure. */ struct host host; +/* Store of qstrings, used for parsing. */ +token_vector_t spare_token_strings ; + /* Standard command node structures. */ static struct cmd_node auth_node = { - AUTH_NODE, + .node = AUTH_NODE, "Password: ", }; @@ -104,16 +108,16 @@ static const struct facility_map { size_t match; } syslog_facilities[] = { - { LOG_KERN, "kern", 1 }, - { LOG_USER, "user", 2 }, - { LOG_MAIL, "mail", 1 }, + { LOG_KERN, "kern", 1 }, + { LOG_USER, "user", 2 }, + { LOG_MAIL, "mail", 1 }, { LOG_DAEMON, "daemon", 1 }, - { LOG_AUTH, "auth", 1 }, + { LOG_AUTH, "auth", 1 }, { LOG_SYSLOG, "syslog", 1 }, - { LOG_LPR, "lpr", 2 }, - { LOG_NEWS, "news", 1 }, - { LOG_UUCP, "uucp", 2 }, - { LOG_CRON, "cron", 1 }, + { LOG_LPR, "lpr", 2 }, + { LOG_NEWS, "news", 1 }, + { LOG_UUCP, "uucp", 2 }, + { LOG_CRON, "cron", 1 }, #ifdef LOG_FTP { LOG_FTP, "ftp", 1 }, #endif @@ -128,6 +132,20 @@ static const struct facility_map { { 0, NULL, 0 }, }; +static struct cmd_element cmd_pipe = +{ + .string = "< or <|", /* Dummy */ + .func = cmd_pipe_func, + .doc = "Pipe input to command processor", + .daemon = 0, + .strvec = NULL, + .cmdsize = 0, + .config = NULL, + .subconfig = NULL, + .attr = CMD_ATTR_SIMPLE, +} ; + + static const char * facility_name(int facility) { @@ -284,144 +302,16 @@ sort_node () } ; /*------------------------------------------------------------------------------ - * Take string and break it into tokens. - * - * Discards leading and trailing white-space. - * - * Treats lines that start with '!' or '#' (after any leading white-space) - * as empty -- these are comment lines. - * - * Tokens are non-whitespace separated by one or more white-space. - * - * White-space is anything that isspace() thinks is a space. (Which in the - * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) + * Take string and break it into tokens -- see cmd_make_tokens(). * * Returns: NULL => empty line (after white-space trimming) or comment line. - * otherwise: is vector containing one or more tokens. - * - * .... - * - * - * Note: all the tokens in the vector have at least one character, and no - * entries are NULL. - * - * NB: it is the caller's responsibility to release the vector and its contents, - * see cmd_free_strvec(). - */ -static vector -cmd_make_vline(vector vline, qstring qs, const char *string) -{ - char *token, *tp ; - const char *cp, *sp, *ep, *op ; - - /* Reset any existing vline, and empty the qstring if given. */ - if (vline != NULL) - vector_set_length(vline, 0) ; - - qs_clear(qs) ; - - /* Strip leading and trailing white-space and deal with empty or effectively - * empty lines -- comment lines are treated as effectively empty. - */ - cp = string; - - if (string == NULL) - return NULL; - - while (isspace((int) *cp)) - cp++; - - ep = cp + strlen(cp) ; - - while ((ep > cp) && (isspace((int)*(ep - 1)))) - --ep ; - - if ((cp == ep) || (*cp == '!') || (*cp == '#')) - return NULL; - - /* Prepare return vector -- expect some reasonable number of tokens. */ - if (vline == NULL) - vline = vector_init(10) ; - - /* If writing the words to a qstring, copy the body of the original (less - * any leading/trailing whitespace) to the qstring and '\0' terminate. - */ - if (qs != NULL) - { - qs_set_n(qs, cp, ep - cp) ; - tp = (char*)qs->body ; /* start at the beginning */ - } - else - tp = NULL ; /* not used, but not undefined */ - - op = cp ; /* original start position */ - - /* Now have string cp..ep with no leading/trailing whitespace. - * - * If using a qstring, a copy of that exists at tp, complete with terminating - * '\0'. Writes '\0' terminators after each word found -- overwriting first - * separating white-space or the '\0' at the end. - * - * If not using a qstring, construct a new MTYPE_STRVEC for each word. - */ - while (cp < ep) - { - while (isspace((int) *cp)) - cp++ ; /* skip white-space */ - - sp = cp ; - while ((cp < ep) && !isspace((int) *cp)) - cp++ ; /* eat token characters */ - - if (qs == NULL) - { - /* creating array of MTYPE_STRVEC */ - size_t len ; - - len = cp - sp ; - token = XMALLOC (MTYPE_STRVEC, len + 1); - memcpy (token, sp, len); - *(token + len) = '\0'; - } - else - { - /* using qstring */ - token = tp + (sp - op) ; /* token in qstring */ - *(tp + (cp - op)) = '\0' ; /* terminate */ - } ; - - vector_push_item(vline, token); - } ; - - return vline ; -} - -/*------------------------------------------------------------------------------ - * Take string and break it into tokens. - * - * Discards leading and trailing white-space. - * - * Treats lines that start with '!' or '#' (after any leading white-space) - * as empty -- these are comment lines. - * - * Tokens are non-whitespace separated by one or more white-space. - * - * White-space is anything that isspace() thinks is a space. (Which in the - * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) - * - * Returns: NULL => empty line (after white-space trimming) or comment line. - * otherwise: is vector containing one or more tokens. - * - * Note: all the tokens in the vector have at least one character, and no - * entries are NULL. - * - * NB: it is the caller's responsibility to release the vector and its contents, - * see cmd_free_strvec(). + * otherwise: is vector containing one or more tokens in qstrings. */ extern vector cmd_make_strvec (const char *string) { - return cmd_make_vline(NULL, NULL, string) ; + return cmd_tokenise(NULL, string) ; +#error sort this one out } ; /*------------------------------------------------------------------------------ @@ -451,7 +341,7 @@ cmd_free_strvec (vector strvec) char *cp; /* Note that vector_ream_free() returns NULL if strvec == NULL */ - while((cp = vector_ream_free(strvec)) != NULL) + while((cp = vector_ream(strvec, free_it)) != NULL) XFREE (MTYPE_STRVEC, cp); } ; @@ -554,7 +444,7 @@ cmd_make_descvec (const char *string, const char *descstr) sp = cp; - while (! (isspace ((int) *cp) || *cp == '\r' || *cp == '\n' || *cp == ')' || *cp == '|') && *cp != '\0') + while (! (isspace ((int) *cp) || *cp == ')' || *cp == '|') && *cp != '\0') cp++; len = cp - sp; @@ -846,522 +736,6 @@ cmd_filter_by_symbol (char *command, char *symbol) #endif /*============================================================================== - * Match functions. - * - * Is the given string a, possibly incomplete, value of the required kind ? - */ - -/* Completion match types. */ -enum match_type -{ - no_match, /* nope */ - extend_match, - - ipv4_prefix_match, - ipv4_match, - ipv6_prefix_match, - ipv6_match, - range_match, - vararg_match, - - partly_match, /* OK as far as it went */ - exact_match /* Syntactically complete */ -}; - -/*------------------------------------------------------------------------------ - * Is this an IPv4 Address: - * - * 999.999.999.999 -- where no part may be > 255 - * - * TODO: cmd_ipv4_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_match() seems to accept leading zeros ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete - */ -static enum match_type -cmd_ipv4_match (const char *str) -{ - const char *sp; - int dots = 0, nums = 0; - char buf[4]; - - if (str == NULL) - return partly_match; - - for (;;) - { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0') - { - if (*str == '.') - { - if (dots >= 3) - return no_match; - - if (*(str + 1) == '.') - return no_match; - - if (*(str + 1) == '\0') - return partly_match; - - dots++; - break; - } - if (!isdigit ((int) *str)) - return no_match; - - str++; - } - - if (str - sp > 3) - return no_match; - - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; - - nums++; - - if (*str == '\0') - break; - - str++; - } - - if (nums < 4) - return partly_match; - - return exact_match; -} - -/*------------------------------------------------------------------------------ - * Is this an IPv4 Prefix: - * - * 999.999.999.999/99 -- where no part may be > 255, - * and prefix length may not be > 32 - * - * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete - * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. - */ -static enum match_type -cmd_ipv4_prefix_match (const char *str) -{ - const char *sp; - int dots = 0; - char buf[4]; - - if (str == NULL) - return partly_match; - - for (;;) - { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0' && *str != '/') - { - if (*str == '.') - { - if (dots == 3) - return no_match; - - if (*(str + 1) == '.' || *(str + 1) == '/') - return no_match; - - if (*(str + 1) == '\0') - return partly_match; - - dots++; - break; - } - - if (!isdigit ((int) *str)) - return no_match; - - str++; - } - - if (str - sp > 3) - return no_match; - - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; - - if (dots == 3) - { - if (*str == '/') - { - if (*(str + 1) == '\0') - return partly_match; - - str++; - break; - } - else if (*str == '\0') - return partly_match; - } - - if (*str == '\0') - return partly_match; - - str++; - } - - sp = str; - while (*str != '\0') - { - if (!isdigit ((int) *str)) - return no_match; - - str++; - } - - if (atoi (sp) > 32) - return no_match; - - return exact_match; -} - -/*------------------------------------------------------------------------------ - * Is this an IPv6 Address: - * - * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete - */ - -#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" -#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" -#define STATE_START 1 -#define STATE_COLON 2 -#define STATE_DOUBLE 3 -#define STATE_ADDR 4 -#define STATE_DOT 5 -#define STATE_SLASH 6 -#define STATE_MASK 7 - -#ifdef HAVE_IPV6 - -static enum match_type -cmd_ipv6_match (const char *str) -{ - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - const char *sp = NULL; - struct sockaddr_in6 sin6_dummy; - int ret; - - if (str == NULL) - return partly_match; - - if (strspn (str, IPV6_ADDR_STR) != strlen (str)) - return no_match; - - /* use inet_pton that has a better support, - * for example inet_pton can support the automatic addresses: - * ::1.2.3.4 - */ - ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); - - if (ret == 1) - return exact_match; - - while (*str != '\0') - { - switch (state) - { - case STATE_START: - if (*str == ':') - { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; - } - else - { - sp = str; - state = STATE_ADDR; - } - - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == ':') - state = STATE_DOUBLE; - else - { - sp = str + 1; - state = STATE_ADDR; - } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; - - if (*(str + 1) == ':') - return no_match; - else - { - if (*(str + 1) != '\0') - colons++; - sp = str + 1; - state = STATE_ADDR; - } - - double_colon++; - nums++; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '\0') - { - if (str - sp > 3) - return no_match; - - nums++; - state = STATE_COLON; - } - if (*(str + 1) == '.') - state = STATE_DOT; - break; - case STATE_DOT: - state = STATE_ADDR; - break; - default: - break; - } - - if (nums > 8) - return no_match; - - if (colons > 7) - return no_match; - - str++; - } - -#if 0 - if (nums < 11) - return partly_match; -#endif /* 0 */ - - return exact_match; -} - -/*------------------------------------------------------------------------------ - * Is this an IPv6 Prefix: - * - * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? - * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete - * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. - */ -static enum match_type -cmd_ipv6_prefix_match (const char *str) -{ - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - int mask; - const char *sp = NULL; - char *endptr = NULL; - - if (str == NULL) - return partly_match; - - if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) - return no_match; - - while (*str != '\0' && state != STATE_MASK) - { - switch (state) - { - case STATE_START: - if (*str == ':') - { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; - } - else - { - sp = str; - state = STATE_ADDR; - } - - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == '/') - return no_match; - else if (*(str + 1) == ':') - state = STATE_DOUBLE; - else - { - sp = str + 1; - state = STATE_ADDR; - } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; - - if (*(str + 1) == ':') - return no_match; - else - { - if (*(str + 1) != '\0' && *(str + 1) != '/') - colons++; - sp = str + 1; - - if (*(str + 1) == '/') - state = STATE_SLASH; - else - state = STATE_ADDR; - } - - double_colon++; - nums += 1; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '.' - || *(str + 1) == '\0' || *(str + 1) == '/') - { - if (str - sp > 3) - return no_match; - - for (; sp <= str; sp++) - if (*sp == '/') - return no_match; - - nums++; - - if (*(str + 1) == ':') - state = STATE_COLON; - else if (*(str + 1) == '.') - state = STATE_DOT; - else if (*(str + 1) == '/') - state = STATE_SLASH; - } - break; - case STATE_DOT: - state = STATE_ADDR; - break; - case STATE_SLASH: - if (*(str + 1) == '\0') - return partly_match; - - state = STATE_MASK; - break; - default: - break; - } - - if (nums > 11) - return no_match; - - if (colons > 7) - return no_match; - - str++; - } - - if (state < STATE_MASK) - return partly_match; - - mask = strtol (str, &endptr, 10); - if (*endptr != '\0') - return no_match; - - if (mask < 0 || mask > 128) - return no_match; - -/* I don't know why mask < 13 makes command match partly. - Forgive me to make this comments. I Want to set static default route - because of lack of function to originate default in ospf6d; sorry - yasu - if (mask < 13) - return partly_match; -*/ - - return exact_match; -} - -#endif /* HAVE_IPV6 */ - -/*------------------------------------------------------------------------------ - * Is this a decimal number in the allowed range: - * - * Returns: 1 => OK -- *including* empty string - * 0 => not a valid number, or not in required range - * (or invalid range !!) - */ - -#define DECIMAL_STRLEN_MAX 10 - -static int -cmd_range_match (const char *range, const char *str) -{ - char *p; - char buf[DECIMAL_STRLEN_MAX + 1]; - char *endptr = NULL; - unsigned long min, max, val; - - if (str == NULL) - return 1; - - val = strtoul (str, &endptr, 10); - if (*endptr != '\0') - return 0; - - range++; - p = strchr (range, '-'); - if (p == NULL) - return 0; - if (p - range > DECIMAL_STRLEN_MAX) - return 0; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - min = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return 0; - - range = p + 1; - p = strchr (range, '>'); - if (p == NULL) - return 0; - if (p - range > DECIMAL_STRLEN_MAX) - return 0; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - max = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return 0; - - if (val < min || val > max) - return 0; - - return 1; -} - -/*============================================================================== * Command "filtering". * * The command parsing process starts with a (shallow) copy of the cmd_vector @@ -1379,11 +753,13 @@ cmd_range_match (const char *range, const char *str) */ /*------------------------------------------------------------------------------ - * Make completion match and return match type flag. + * Make strict or completion match and return match type flag. * * Takes: command -- address of candidate token * cmd_v -- vector of commands that is being reduced/filtered * index -- index of token (position in line -- 0 == first) + * min_match -- any_match => allow partial matching + * exact_match => must match completely * * Returns: any of the enum match_type values: * @@ -1405,15 +781,18 @@ cmd_range_match (const char *range, const char *str) * furthest down this list. */ static enum match_type -cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) +cmd_filter(const char *command, vector cmd_v, unsigned int index, + match_type_t min_match) { unsigned int i; unsigned int k; - enum match_type match_type; + enum match_type best_match; + size_t c_len ; - match_type = no_match; + best_match = no_match ; + c_len = strlen(command) ; - /* If command and cmd_element string does not match, remove from vector */ + /* If command and cmd_element string do match, keep in vector */ k = 0 ; for (i = 0; i < vector_length (cmd_v); i++) { @@ -1422,11 +801,11 @@ cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) vector descvec; struct desc *desc; unsigned int j; - int matched ; + bool matched ; cmd_element = vector_get_item(cmd_v, i) ; - /* Skip past cmd_v entries that have already been set NULL */ + /* Skip past NULL cmd_v entries (just in case) */ if (cmd_element == NULL) continue ; @@ -1447,205 +826,78 @@ cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) if (CMD_VARARG (str)) { - if (match_type < vararg_match) - match_type = vararg_match; - matched++; + if (best_match < vararg_match) + best_match = vararg_match; + matched = true ; } else if (CMD_RANGE (str)) { if (cmd_range_match (str, command)) { - if (match_type < range_match) - match_type = range_match; - - matched++; + if (best_match < range_match) + best_match = range_match; + matched = true ; } } #ifdef HAVE_IPV6 else if (CMD_IPV6 (str)) { - if (cmd_ipv6_match (command)) + if (cmd_ipv6_match (command) >= min_match) { - if (match_type < ipv6_match) - match_type = ipv6_match; - - matched++; + if (best_match < ipv6_match) + best_match = ipv6_match; + matched = true ; } } else if (CMD_IPV6_PREFIX (str)) { - if (cmd_ipv6_prefix_match (command)) + if (cmd_ipv6_prefix_match (command) >= min_match) { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; - - matched++; + if (best_match < ipv6_prefix_match) + best_match = ipv6_prefix_match; + matched = true ; } } #endif /* HAVE_IPV6 */ else if (CMD_IPV4 (str)) { - if (cmd_ipv4_match (command)) + if (cmd_ipv4_match (command) >= min_match) { - if (match_type < ipv4_match) - match_type = ipv4_match; - - matched++; + if (best_match < ipv4_match) + best_match = ipv4_match; + matched = true ; } } else if (CMD_IPV4_PREFIX (str)) { - if (cmd_ipv4_prefix_match (command)) + if (cmd_ipv4_prefix_match (command) >= min_match) { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; + if (best_match < ipv4_prefix_match) + best_match = ipv4_prefix_match; + matched = true ; } } else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - /* Check is this point's argument optional ? */ { - if (match_type < extend_match) - match_type = extend_match; - matched++; + if (best_match < extend_match) + best_match = extend_match; + matched = true ; } - else if (strncmp (command, str, strlen (command)) == 0) + else { if (strcmp (command, str) == 0) - match_type = exact_match; - else { - if (match_type < partly_match) - match_type = partly_match; + best_match = exact_match ; + matched = true ; } - matched++; - } ; - } ; - - /* Keep cmd_v entry that has a match at this position */ - if (matched) - vector_set_item(cmd_v, k++, cmd_element) ; - } ; - - vector_set_length(cmd_v, k) ; /* discard what did not keep */ - - return match_type; -} ; - -/*------------------------------------------------------------------------------ - * Filter vector by command character with index. - * - * This appears to be identical to cmd_filter_by_completion(), except that - * when matching keywords, requires an exact match. - * - * TODO: see if can merge cmd_filter_by_completion() & cmd_filter_by_string() - */ -static enum match_type -cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) -{ - unsigned int i ; - unsigned int k ; - enum match_type match_type; - - match_type = no_match; - - /* If command and cmd_element string do match, keep in vector */ - k = 0 ; - for (i = 0; i < vector_length(cmd_v); i++) - { - unsigned int j; - int matched ; - const char *str; - struct cmd_element *cmd_element; - vector descvec; - struct desc *desc; - - cmd_element = vector_get_item(cmd_v, i) ; - - /* Skip past NULL cmd_v entries (just in case) */ - if (cmd_element == NULL) - continue ; - - /* Discard cmd_v entry that has no token at the current position */ - descvec = vector_get_item (cmd_element->strvec, index) ; - if (descvec == NULL) - continue ; - - /* See if have a match against any of the current possibilities */ - matched = 0 ; - for (j = 0; j < vector_length(descvec); j++) - { - desc = vector_get_item (descvec, j) ; - if (desc == NULL) - continue ; - - str = desc->cmd; - - if (CMD_VARARG (str)) - { - if (match_type < vararg_match) - match_type = vararg_match; - matched++; - } - else if (CMD_RANGE (str)) - { - if (cmd_range_match (str, command)) - { - if (match_type < range_match) - match_type = range_match; - matched++; - } - } -#ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command) == exact_match) - { - if (match_type < ipv6_match) - match_type = ipv6_match; - matched++; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command) == exact_match) - { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; - matched++; - } - } -#endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command) == exact_match) - { - if (match_type < ipv4_match) - match_type = ipv4_match; - matched++; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command) == exact_match) + else if (min_match <= partly_match) { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; - } - } - else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (match_type < extend_match) - match_type = extend_match; - matched++; - } - else - { - if (strcmp (command, str) == 0) - { - match_type = exact_match; - matched++; + if (strncmp (command, str, c_len) == 0) + { + if (best_match < partly_match) + best_match = partly_match ; + matched = true ; + } ; } ; } ; } ; @@ -1657,8 +909,8 @@ cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) vector_set_length(cmd_v, k) ; /* discard what did not keep */ - return match_type; -} + return best_match; +} ; /*------------------------------------------------------------------------------ * Check for ambiguous match @@ -1699,7 +951,8 @@ cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) * returns 1 in preference. */ static int -is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) +is_cmd_ambiguous (const char *command, vector cmd_v, int index, + enum match_type type) { unsigned int i; unsigned int k; @@ -1715,7 +968,7 @@ is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) const char *str_matched ; vector descvec; struct desc *desc; - int matched ; + bool matched ; enum match_type mt ; cmd_element = vector_get_item (cmd_v, i) ; @@ -1754,7 +1007,7 @@ is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) case exact_match: if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) && strcmp (command, str) == 0) - matched++; + matched = true ; break; case partly_match: @@ -1765,7 +1018,7 @@ is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) ret = 1; /* There is ambiguous match. */ else str_matched = str; - matched++; + matched = true ; } break; @@ -1776,47 +1029,43 @@ is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) ret = 1; else str_matched = str; - matched++; + matched = true ; } break; #ifdef HAVE_IPV6 case ipv6_match: if (CMD_IPV6 (str)) - matched++; + matched = true ; break; case ipv6_prefix_match: if ((mt = cmd_ipv6_prefix_match (command)) != no_match) { - if (mt == partly_match) - if (ret != 1) - ret = 2; /* There is incomplete match. */ - - matched++; + if ((mt == partly_match) && (ret != 1)) + ret = 2; /* There is incomplete match. */ + matched = true ; } break; #endif /* HAVE_IPV6 */ case ipv4_match: if (CMD_IPV4 (str)) - matched++; + matched = true ; break; case ipv4_prefix_match: if ((mt = cmd_ipv4_prefix_match (command)) != no_match) { - if (mt == partly_match) - if (ret != 1) - ret = 2; /* There is incomplete match. */ - - matched++; + if ((mt == partly_match) && (ret != 1)) + ret = 2; /* There is incomplete match. */ + matched = true ; } break; case extend_match: if (CMD_OPTION (str) || CMD_VARIABLE (str)) - matched++; + matched = true ; break; case no_match: @@ -1956,195 +1205,256 @@ desc_unique_string (vector v, const char *str) return 0; } +/*------------------------------------------------------------------------------ + * Special parsing for leading 'do', if current mode allows it. + * + * If finds a valid "do", sets current node and do_shortcut flag, and discards + * the "do" token. + * + * Returns: true <=> dealt with the "do" + * false => no do, or no do allowed. + */ static bool -cmd_try_do_shortcut (enum node_type node, char* first_word) { - return (node >= MIN_DO_SHORTCUT_NODE) - && (first_word != NULL) - && (strcmp( "do", first_word) == 0) ? 1 : 0 ; -} +cmd_try_do_shortcut(cmd_parsed parsed) +{ + const char* ts ; -/* '?' describe command support. */ -static vector -cmd_describe_command_real (vector vline, int node, int *status) + if (parsed->cnode < MIN_DO_SHORTCUT_NODE) + return false ; + + ts = cmd_token_string(cmd_token_get(&parsed->tokens, 0)) ; + if (strcmp("do", ts) != 0) + return false ; + + parsed->cnode = ENABLE_NODE ; + parsed->do_shortcut = true ; + cmd_token_discard(cmd_token_shift(&parsed->tokens)) ; + + return true ; +} ; + +/*============================================================================== + * '?' describe command support. + */ + +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct desc" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ +extern vector +cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) { - unsigned int i; - vector cmd_vector; -#define INIT_MATCHVEC_SIZE 10 - vector matchvec; - struct cmd_element *cmd_element; - unsigned int index; - int ret; - enum match_type match; - char *command; + vector ret ; + struct cmd_parsed parsed_s ; + cmd_parsed parsed ; + cmd_token_type_t tok_total ; - /* Set index. */ - if (vector_length (vline) == 0) - { - *status = CMD_ERR_NO_MATCH; - return NULL; - } - else - index = vector_length (vline) - 1; + /* Set up a parser object and tokenise the command line */ + parsed = cmd_parse_init_new(&parsed_s) ; + tok_total = cmd_tokenise(parsed, line, node) ; - /* Make copy vector of current node's command vector. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); - /* Prepare match vector */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - /* Filter commands. */ - /* Only words precedes current word will be checked in this loop. */ - for (i = 0; i < index; i++) - if ((command = vector_get_item (vline, i))) - { - match = cmd_filter_by_completion (command, cmd_vector, i); - if (match == vararg_match) - { - struct cmd_element *cmd_element; - vector descvec; - unsigned int j, k; - - for (j = 0; j < vector_length (cmd_vector); j++) - if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL - && (vector_length (cmd_element->strvec))) - { - descvec = vector_get_item (cmd_element->strvec, - vector_length (cmd_element->strvec) - 1); - for (k = 0; k < vector_length (descvec); k++) - { - struct desc *desc = vector_get_item (descvec, k); - vector_set (matchvec, desc); - } - } - - vector_set (matchvec, &desc_cr); - vector_free (cmd_vector); - - return matchvec; - } - if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1) - { - vector_free (cmd_vector); - vector_free (matchvec); - *status = CMD_ERR_AMBIGUOUS; - return NULL; - } - else if (ret == 2) - { - vector_free (cmd_vector); - vector_free (matchvec); - *status = CMD_ERR_NO_MATCH; - return NULL; - } - } - /* Prepare match vector */ - /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - /* Make sure that cmd_vector is filtered based on current word */ - command = vector_get_item (vline, index); - if (command) - match = cmd_filter_by_completion (command, cmd_vector, index); + /* Stop immediately if line is empty apart from comment */ + if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) + return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - /* Make description vector. */ - for (i = 0; i < vector_length (cmd_vector); i++) + /* Level 1 parsing + * + * Strip quotes and escapes from all the tokens. + */ + if (tok_total != cmd_tok_simple) { - vector strvec ; + ret = cmd_parse_phase_one(parsed) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; - cmd_element = vector_get_item (cmd_vector, i) ; - if (cmd_element == NULL) - continue ; + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if (type & cmd_parse_do) + cmd_try_do_shortcut(parsed) ; - /* Ignore cmd_element if no tokens at index position. - * - * Deal with special case of possible <cr> completion. - */ - strvec = cmd_element->strvec; - if (index >= vector_length (strvec)) + + + + return cmd_describe_command_real (tokens, node, status); + + + + static vector + cmd_describe_command_real (vector tokens, int node, int *status) + { + unsigned int i; + vector cmd_vector; + #define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_element *cmd_element; + unsigned int index; + int ret; + enum match_type match; + char *command; + + /* Set index. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + else + index = vector_length (tokens) - 1; + + /* Make copy vector of current node's command vector. */ + cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); + + /* Prepare match vector */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Filter commands. */ + /* Only words precedes current word will be checked in this loop. */ + for (i = 0; i < index; i++) + if ((command = vector_get_item (tokens, i))) { - if (command == NULL && index == vector_length (strvec)) + match = cmd_filter(command, cmd_vector, i, any_match) ; + + if (match == vararg_match) { - if (!desc_unique_string (matchvec, command_cr)) - vector_push_item(matchvec, &desc_cr); - } - continue ; - } ; + struct cmd_element *cmd_element; + vector descvec; + unsigned int j, k; + + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_element->strvec))) + { + descvec = vector_get_item (cmd_element->strvec, + vector_length (cmd_element->strvec) - 1); + for (k = 0; k < vector_length (descvec); k++) + { + struct desc *desc = vector_get_item (descvec, k); + vector_set (matchvec, desc); + } + } + + vector_set (matchvec, &desc_cr); + vector_free (cmd_vector); + + return matchvec; + } ; - /* Check if command is completed. */ - unsigned int j; - vector descvec = vector_get_item (strvec, index); - struct desc *desc; + ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; + if (ret != 0) + { + vector_free (cmd_vector); + vector_free (matchvec); + *status = (ret == 1) ? CMD_ERR_AMBIGUOUS + : CMD_ERR_NO_MATCH ; + return NULL ; + } ; + } - for (j = 0; j < vector_length (descvec); j++) - if ((desc = vector_get_item (descvec, j))) - { - const char *string; + /* Prepare match vector */ + /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - string = cmd_entry_function_desc (command, desc->cmd); - if (string) + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (tokens, index); + if (command) + match = cmd_filter(command, cmd_vector, index, any_match); + + /* Make description vector. */ + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector strvec ; + + cmd_element = vector_get_item (cmd_vector, i) ; + if (cmd_element == NULL) + continue ; + + /* Ignore cmd_element if no tokens at index position. + * + * Deal with special case of possible <cr> completion. + */ + strvec = cmd_element->strvec; + if (index >= vector_length (strvec)) + { + if (command == NULL && index == vector_length (strvec)) { - /* Uniqueness check */ - if (!desc_unique_string (matchvec, string)) - vector_push_item(matchvec, desc); + if (!desc_unique_string (matchvec, command_cr)) + vector_push_item(matchvec, &desc_cr); } + continue ; } ; - } ; - vector_free (cmd_vector); + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (strvec, index); + struct desc *desc; + + for (j = 0; j < vector_length (descvec); j++) + if ((desc = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, desc->cmd); + if (string) + { + /* Uniqueness check */ + if (!desc_unique_string (matchvec, string)) + vector_push_item(matchvec, desc); + } + } ; + } ; + + vector_free (cmd_vector); + + if (vector_length(matchvec) == 0) + { + vector_free (matchvec); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + *status = CMD_SUCCESS; + return matchvec; + } + + + + - if (vector_length(matchvec) == 0) - { - vector_free (matchvec); - *status = CMD_ERR_NO_MATCH; - return NULL; - } - *status = CMD_SUCCESS; - return matchvec; -} -/*------------------------------------------------------------------------------ - * Get description of current (partial) command - * - * Returns: NULL => no description available - * - * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS - * - * or: address of vector of "struct desc" values available. - * - * NB: when a vector is returned it is the caller's responsibility to - * vector_free() it. (The contents are all effectively const, so do not - * themselves need to be freed.) - */ -vector -cmd_describe_command (vector vline, int node, int *status) -{ - vector ret; - if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) - { - vector shifted_vline; - unsigned int index; - /* We can try it on enable node, cos' the vty is authenticated */ - shifted_vline = vector_init (vector_count(vline)); - /* use memcpy? */ - for (index = 1; index < vector_length (vline); index++) - { - vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); - } - ret = cmd_describe_command_real (shifted_vline, ENABLE_NODE, status); - vector_free(shifted_vline); - return ret; - } - return cmd_describe_command_real (vline, node, status); -} + + + + + + cmd_parse_reset(parsed, false) ; + + return +} ; /*------------------------------------------------------------------------------ * Check LCD of matched command. @@ -2192,7 +1502,7 @@ cmd_lcd (vector matchvec) * Command line completion support. */ static vector -cmd_complete_command_real (vector vline, int node, int *status) +cmd_complete_command_real (vector tokens, int node, int *status) { unsigned int i; unsigned int ivl ; @@ -2207,8 +1517,8 @@ cmd_complete_command_real (vector vline, int node, int *status) char *token; int n ; - /* Stop immediately if the vline is empty. */ - if (vector_length (vline) == 0) + /* Stop immediately if the tokens is empty. */ + if (vector_length (tokens) == 0) { *status = CMD_ERR_NO_MATCH; return NULL; @@ -2218,7 +1528,7 @@ cmd_complete_command_real (vector vline, int node, int *status) cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); /* First, filter upto, but excluding last token */ - last_ivl = vector_length (vline) - 1; + last_ivl = vector_length (tokens) - 1; for (ivl = 0; ivl < last_ivl; ivl++) { @@ -2226,7 +1536,7 @@ cmd_complete_command_real (vector vline, int node, int *status) int ret; /* TODO: does this test make any sense ? */ - if ((token = vector_get_item (vline, ivl)) == NULL) + if ((token = vector_get_item (tokens, ivl)) == NULL) continue ; /* First try completion match, return best kind of match */ @@ -2263,7 +1573,7 @@ cmd_complete_command_real (vector vline, int node, int *status) /* Now we got into completion */ index = last_ivl ; - token = vector_get_item(vline, last_ivl) ; /* is now the last token */ + token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ for (i = 0; i < vector_length (cmd_v); i++) { @@ -2349,31 +1659,32 @@ cmd_complete_command_real (vector vline, int node, int *status) * Can the current command be completed ? */ extern vector -cmd_complete_command (vector vline, int node, int *status) +cmd_complete_command (vector tokens, int node, int *status) { vector ret; - if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) + if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) { - vector shifted_vline; + vector shifted_tokens; unsigned int index; /* We can try it on enable node, cos' the vty is authenticated */ - shifted_vline = vector_init (vector_count(vline)); + shifted_tokens = vector_init (vector_count(tokens)); /* use memcpy? */ - for (index = 1; index < vector_length (vline); index++) + for (index = 1; index < vector_length (tokens); index++) { - vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); + vector_set_index (shifted_tokens, index-1, + vector_lookup(tokens, index)) ; } - ret = cmd_complete_command_real (shifted_vline, ENABLE_NODE, status); + ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); - vector_free(shifted_vline); + vector_free(shifted_tokens); return ret; } - return cmd_complete_command_real (vline, node, status); + return cmd_complete_command_real (tokens, node, status); } /*------------------------------------------------------------------------------ @@ -2400,58 +1711,12 @@ node_parent ( enum node_type node ) default: return CONFIG_NODE; - } -} - -/*------------------------------------------------------------------------------ - * Initialise a new struct cmd_parsed, allocating if required - */ -extern cmd_parsed -cmd_parse_init_new(cmd_parsed parsed) -{ - if (parsed == NULL) - parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; - else - memset(parsed, 0, sizeof(*parsed)) ; - - /* Zeroising the structure has set: - * - * cmd = NULL -- no command parsed, yet - * cnode -- no node set, yet - * - * do_shortcut -- false - * onode -- not material (do_shortcut is false) - * - * line = zeroised qstring -- empty - * words = zeroised qstring -- empty - * - * vline = zeroised vector -- empty - * - * so nothing else to do - */ - - return parsed ; + } ; } ; -/*------------------------------------------------------------------------------ - * Initialise a new struct cmd_parsed, allocating if required +/*============================================================================== + * Parsing of command lines */ -extern cmd_parsed -cmd_parse_reset(cmd_parsed parsed, bool free_structure) -{ - if (parsed != NULL) - { - qs_reset_keep(&parsed->words) ; - vector_reset_keep(&parsed->vline) ; - - if (free_structure) - XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ - else - cmd_parse_init_new(parsed) ; - } ; - - return parsed ; -} ; /*------------------------------------------------------------------------------ * Parse a command in the given "node", if possible, ready for execution. @@ -2465,8 +1730,9 @@ cmd_parse_reset(cmd_parsed parsed, bool free_structure) * current node. */ -static enum cmd_return_code -cmd_parse_this(struct cmd_parsed* parsed, bool strict) ; +static enum cmd_return_code cmd_parse_phase_one(cmd_parsed parsed) ; +static enum cmd_return_code cmd_parse_phase_two(struct cmd_parsed* parsed, + bool strict) ; /*------------------------------------------------------------------------------ * Parse a command in the given "node", or (if required) any of its ancestors. @@ -2502,41 +1768,61 @@ cmd_parse_this(struct cmd_parsed* parsed, bool strict) ; * See elsewhere for description of parsed structure. */ extern enum cmd_return_code -cmd_parse_command(struct vty* vty, enum cmd_parse_type type) +cmd_parse_command(struct vty* vty, cmd_parse_type_t type) { enum cmd_return_code ret ; enum cmd_return_code first_ret ; cmd_parsed parsed ; + cmd_token_type_t tok_total ; + bool varflag ; + unsigned int i, ivl ; /* Initialise the parsed structure -- assuming no 'do' */ if (vty->parsed == NULL) - vty->parsed = cmd_parse_init_new(NULL) ; - parsed = vty->parsed ; + parsed = vty->parsed = cmd_parse_init_new(NULL) ; + else + { + parsed = vty->parsed ; - parsed->onode = parsed->cnode = vty->node ; + parsed->cmd = NULL ; + parsed->do_shortcut = false ; - parsed->cmd = NULL ; - parsed->do_shortcut = 0 ; + if (parsed->pipes != cmd_pipe_none) + { + parsed->pipes = cmd_pipe_none ; + cmd_empty_parsed_tokens(parsed) ; + } ; + } ; - /* Parse the line into words -- set up parsed->words and parsed->vline */ - cmd_make_vline(&parsed->vline, &parsed->words, vty->buf) ; + /* Parse the line into tokens, set parsed->line, ->cnode & ->onode */ + tok_total = cmd_tokenise(parsed, vty->buf, vty->node) ; - if (vector_length(&parsed->vline) == 0) + /* Stop immediately if line is empty apart from comment */ + if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - /* If allowed to 'do', see if there. + /* Level 1 parsing * - * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + * Strip quotes and escapes from all the tokens. */ - if ((type & cmd_parse_do) && - cmd_try_do_shortcut(parsed->cnode, vector_get_item(&parsed->vline, 0))) + if (tok_total != cmd_tok_simple) { - parsed->cnode = ENABLE_NODE ; - parsed->do_shortcut = 1 ; + ret = cmd_parse_phase_one(parsed) ; + if (ret != CMD_SUCCESS) + return ret ; } ; - /* Try in the current node */ - ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if (type & cmd_parse_do) + cmd_try_do_shortcut(parsed) ; + + /* Level 2 parsing + * Try in the current node + */ + ret = cmd_parse_phase_two(parsed, type) ; if (ret != CMD_SUCCESS) { @@ -2556,72 +1842,132 @@ cmd_parse_command(struct vty* vty, enum cmd_parse_type type) } ; parsed->cnode = node_parent(parsed->cnode) ; - ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; + ret = cmd_parse_phase_two(parsed, type) ; } ; } ; - return vty->parsed->cmd->daemon ? CMD_SUCCESS_DAEMON - : CMD_SUCCESS ; + /* Parsed successfully -- construct the arg_vector */ + + varflag = false ; + ivl = vector_length(parsed->cmd->strvec) ; + + cmd_arg_vector_empty(parsed) ; + for (i = 0; i < ivl ; i++) + { + bool take = varflag ; + + if (!varflag) + { + vector descvec = vector_get_item (parsed->cmd->strvec, i); + + if (vector_length (descvec) == 1) + { + struct desc *desc = vector_get_item (descvec, 0); + + if (CMD_VARARG (desc->cmd)) + take = varflag = true ; + else + take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; + } + else + take = true ; + } + + if (take) + cmd_arg_vector_push(parsed, + cmd_token_value(cmd_token_get(&parsed->tokens, i))) ; + } ; + + /* Return appropriate form of success */ + return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1 of command parsing + * + * * At start of line look for: + * + * * '<' -- pipe-in command of some sort + * + * Sets the pipe type and the read_pipe_tokens -- all tokens up to + * '>', '|', '!' or '#'.. + * + * Scan for '>', '|', '!' or '#' + * + * * '>' or '|' -- pipe-out command of some sort + * + * Collect type of pipe, and then all tokens up to '!' or '#' + * + * + * + * * '!', '#', comment -- discards + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH ) + * CMD_ERR_AMBIGUOUS ) failed to parse + * CMD_ERR_INCOMPLETE ) + */ +static enum cmd_return_code +cmd_parse_phase_one(cmd_parsed parsed) +{ + + + + + + } ; /*------------------------------------------------------------------------------ - * Work function for cmd_parse_command + * Phase 2 of command parsing * * Takes a parsed structure, with the: * * cnode -- node to parse in - * vline -- the line broken into words + * tokens -- the line broken into words * do_shortcut -- true if first word is 'do' (to be ignored) * * and parses either strictly or with command completion. * - * If successful, reduces the vline structure down to the variable portions, - * ie to the argv[] for the command function. - * * Returns: CMD_SUCCESS -- parsed successfully * CMD_ERR_NO_MATCH ) * CMD_ERR_AMBIGUOUS ) failed to parse * CMD_ERR_INCOMPLETE ) */ static enum cmd_return_code -cmd_parse_this(cmd_parsed parsed, bool strict) +cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) { unsigned int i ; unsigned int ivl ; unsigned index ; - unsigned first ; - unsigned argc ; vector cmd_v; struct cmd_element *cmd_element; struct cmd_element *matched_element; unsigned int matched_count, incomplete_count; - enum match_type match = 0; - int varflag; - char *command; - - /* Need length of vline, discounting the first entry if required */ - first = parsed->do_shortcut ? 1 : 0 ; + enum match_type match ; + enum match_type filter_level ; + const char *command; - assert(vector_length(&parsed->vline) >= first) ; - ivl = vector_length(&parsed->vline) - first ; + /* Need number of tokens */ + ivl = cmd_token_count(&parsed->tokens) ; /* Make copy of command elements. */ cmd_v = vector_copy (cmd_node_vector (cmdvec, parsed->cnode)); /* Look for an unambiguous result */ + filter_level = (type & cmd_parse_strict) ? exact_match : any_match ; + match = no_match ; /* in case of emptiness */ for (index = 0 ; index < ivl; index++) { int ret ; - command = vector_get_item(&parsed->vline, index + first) ; - if (command == NULL) - continue ; + command = cmd_token_string(cmd_token_get(&parsed->tokens, index)) ; - match = strict ? cmd_filter_by_string(command, cmd_v, index) - : cmd_filter_by_completion(command, cmd_v, index) ; + match = cmd_filter(command, cmd_v, index, filter_level) ; if (match == vararg_match) - break; + break; ret = is_cmd_ambiguous (command, cmd_v, index, match); @@ -2630,7 +1976,7 @@ cmd_parse_this(cmd_parsed parsed, bool strict) assert((ret == 1) || (ret == 2)) ; vector_free (cmd_v); return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; - } + } } ; /* Check matched count. */ @@ -2645,17 +1991,17 @@ cmd_parse_this(cmd_parsed parsed, bool strict) continue ; if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; + { + matched_element = cmd_element; #if 0 - printf ("DEBUG: %s\n", cmd_element->string); + printf ("DEBUG: %s\n", cmd_element->string); #endif - matched_count++; - } + matched_count++; + } else - { - incomplete_count++; - } + { + incomplete_count++; + } } ; /* Finished with cmd_v. */ @@ -2670,37 +2016,6 @@ cmd_parse_this(cmd_parsed parsed, bool strict) return CMD_ERR_AMBIGUOUS ; } ; - /* Found command -- process the arguments ready for execution */ - varflag = 0 ; - argc = 0 ; - - for (index = 0; index < ivl ; index++) - { - int take = varflag ; - - if (!varflag) - { - vector descvec = vector_get_item (matched_element->strvec, index); - - if (vector_length (descvec) == 1) - { - struct desc *desc = vector_get_item (descvec, 0); - - if (CMD_VARARG (desc->cmd)) - take = varflag = 1 ; - else - take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; - } - else - take = 1 ; - } - - if (take) - vector_assign_item(&parsed->vline, argc++, index + first) ; - } ; - - vector_set_length(&parsed->vline, argc) ; /* set to new length */ - /* Everything checks out... ready to execute command */ parsed->cmd = matched_element ; @@ -2803,7 +2118,7 @@ cmd_execute_command(struct vty *vty, *cmd = vty->parsed->cmd ; /* for vtysh */ if (ret == CMD_SUCCESS) - ret = cmd_dispatch(vty, 0) ; + ret = cmd_dispatch(vty, cmd_may_queue) ; else if (ret == CMD_EMPTY) ret = CMD_SUCCESS ; @@ -2903,6 +2218,129 @@ config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd, return ret ; } ; +/*============================================================================== + */ + +static cmd_return_code_t cmd_fetch_command(struct vty* vty) ; + +/*------------------------------------------------------------------------------ + * Command Loop + * + * Read and dispatch commands until can no longer do so for whatever reason: + * + * - reached end of command stream -- CMD_CLOSE + * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc + * - waiting for input to arrive -- CMD_WAIT_INPUT + * - waiting for command to complete -- CMD_QUEUED + * - waiting for output to complete -- ?? + * + */ +extern cmd_return_code_t +cmd_command_loop(struct vty *vty, struct cmd_element* first_cmd, + qstring buf, bool ignore_warning) +{ + cmd_return_code_t ret ; + + vty->buf = buf->body ; + vty->lineno = 0 ; + + + /* + * + */ + + vty_out_clear(vty) ; + + while (1) { + + /* Fetch a command line */ + + ret = cmd_fetch_command(vty) ; + + if (ret != CMD_SUCCESS) + break ; + + ++vty->lineno ; + + /* Parse the command line we now have */ + + ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; + + if (ret == CMD_EMPTY) + continue ; /* skip empty/comment */ + + if (ret != CMD_SUCCESS) + break ; /* stop on *any* parsing issue */ + + /* special handling before of first command */ + if (first_cmd != NULL) + { + if (first_cmd != vty->parsed->cmd) + { + ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; + if (ret != CMD_SUCCESS) + break ; /* stop on *any* issue with "default" */ + } ; + first_cmd = NULL ; + } ; + + /* Reflect command line if required */ + + /* Standard command handling */ + + ret = cmd_dispatch(vty, cmd_no_queue) ; + + if (ret == CMD_QUEUED) + break ; + + /* Output Handling..... */ + + + /* Return code handling.... */ + + if (ret != CMD_SUCCESS) + { + if ((ret == CMD_WARNING) && !ignore_warning) + break ; + if (ret != CMD_CLOSE) + break ; + } ; + + vty_out_clear(vty) ; + } ; + + return ret ; +} ; + + +/*------------------------------------------------------------------------------ + * Fetch the next command. + * + * + * + * + */ +static cmd_return_code_t +cmd_fetch_command(struct vty* vty) +{ + + + + +} ; + + + + + + + + + + + +/*============================================================================*/ + /*----------------------------------------------------------------------------*/ /* Configration from terminal */ @@ -2927,7 +2365,7 @@ DEFUN_CALL (enable, { /* If enable password is NULL, change to ENABLE_NODE */ if ((host.enable == NULL && host.enable_encrypt == NULL) || - vty_shell_serv(vty)) + vty_shell_server(vty)) vty_set_node(vty, ENABLE_NODE); else vty_set_node(vty, AUTH_ENABLE_NODE); @@ -4061,17 +3499,20 @@ cmd_init (int terminal) desc_cr.cmd = command_cr; desc_cr.str = XSTRDUP(MTYPE_STRVEC, ""); - /* Allocate initial top vector of commands. */ - cmdvec = vector_init (0); + /* Allocate initial top vector of commands. */ + cmdvec = vector_init(0); + + /* Allocate vector of spare qstrings for tokens */ + cmd_spare_tokens_init() ; /* Default host value settings. */ - host.name = NULL; + host.name = NULL; host.password = NULL; - host.enable = NULL; - host.logfile = NULL; - host.config = NULL; - host.lines = -1; - host.motd = default_motd; + host.enable = NULL; + host.logfile = NULL; + host.config = NULL; + host.lines = -1; + host.motd = default_motd; host.motdfile = NULL; /* Install top nodes. */ @@ -4187,6 +3628,8 @@ cmd_terminate () struct desc *desc; vector cmd_node_v, cmd_element_v, desc_v; + cmd_spare_tokens_free() ; + if (cmdvec) { for (i = 0; i < vector_length (cmdvec); i++) diff --git a/lib/command.h b/lib/command.h index ba7c4bb2..fe4f0a56 100644 --- a/lib/command.h +++ b/lib/command.h @@ -23,16 +23,12 @@ #ifndef _ZEBRA_COMMAND_H #define _ZEBRA_COMMAND_H -#include <stdbool.h> +#include "misc.h" #include "node_type.h" #include "vector.h" #include "qstring.h" -#ifndef Inline -#define Inline static inline -#endif - struct vty ; /* in case command.h expanded first */ /* Host configuration variable */ @@ -89,6 +85,7 @@ struct cmd_node enum { + CMD_ATTR_SIMPLE = 0x00, /* bit significant */ CMD_ATTR_DEPRECATED = 0x01, CMD_ATTR_HIDDEN = 0x02, @@ -113,6 +110,7 @@ enum cmd_return_code CMD_EMPTY, CMD_SUCCESS_DAEMON, + CMD_WAIT_INPUT, CMD_CLOSE, CMD_QUEUED, @@ -126,6 +124,8 @@ enum cmd_return_code CMD_COMPLETE_ALREADY } ; +typedef enum cmd_return_code cmd_return_code_t ; + #define MSG_CMD_ERR_AMBIGUOUS "Ambiguous command" #define MSG_CMD_ERR_NO_MATCH "Unrecognised command" #define MSG_CMD_ERR_NO_MATCH_old "There is no matched command" @@ -148,58 +148,24 @@ typedef DEFUN_CMD_FUNCTION((cmd_function)) ; struct cmd_element { - const char *string; /* Command specification by string. */ - cmd_function* func ; - const char *doc; /* Documentation of this command. */ - int daemon; /* Daemon to which this command belong. */ - vector strvec; /* Pointing out each description vector. */ - unsigned int cmdsize; /* Command index count. */ - char *config; /* Configuration string */ - vector subconfig; /* Sub configuration string */ - u_char attr; /* Command attributes */ + const char* string ; /* Command specification by string. */ + cmd_function* func ; + const char* doc ; /* Documentation of this command. */ + int daemon ; /* Daemon to which this command belong. */ + vector strvec ; /* Vector of vectors of struct desc */ + unsigned int cmdsize ; /* Command index count. */ + char* config ; /* Configuration string */ + vector subconfig ; /* Sub configuration string */ + u_char attr ; /* Command attributes */ }; -/* Command description structure. */ +/* Command description structure. */ struct desc { - char *cmd; /* Command string. */ - char *str; /* Command's description. */ + char* cmd; /* Command string. */ + char* str; /* Command's description. */ }; -/* Command parsing options */ -enum cmd_parse_type /* bit significant */ -{ - cmd_parse_completion = 0x00, - cmd_parse_strict = 0x01, - - cmd_parse_do = 0x02, - cmd_parse_tree = 0x04, -} ; - -/* Parsed command */ -typedef struct cmd_parsed* cmd_parsed ; -struct cmd_parsed -{ - struct cmd_element *cmd ; /* NULL if empty command - or fails to parse */ - - enum node_type cnode ; /* node command is in */ - enum node_type onode ; /* node the parser started in */ - - bool do_shortcut ; /* true => is "do" command */ - - qstring_t words ; /* the words, '\0' separated */ - - vector_t vline ; /* pointers to the words */ -} ; - - -/* Command dispatch options */ -enum { - cmd_no_queue = true, - cmd_may_queue = false, -} ; - /*------------------------------------------------------------------------------ * Can now include these */ @@ -313,9 +279,9 @@ enum { #define CMD_VARARG(S) ((S[0]) == '.') #define CMD_RANGE(S) ((S[0] == '<')) -#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) -#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) -#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) +#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) +#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) +#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) #define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0)) /* Common descriptions. */ diff --git a/lib/command_execute.h b/lib/command_execute.h index a5a807c8..6a7e2e4b 100644 --- a/lib/command_execute.h +++ b/lib/command_execute.h @@ -24,11 +24,14 @@ #define _ZEBRA_COMMAND_EXECUTE_H #include "command.h" +#include "command_parse.h" +#include "node_type.h" extern vector cmd_make_strvec (const char *); extern vector cmd_add_to_strvec (vector v, const char* str) ; extern void cmd_free_strvec (vector); -extern vector cmd_describe_command (vector, int, int *status); +extern vector cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) ; extern vector cmd_complete_command (vector, int, int *status); extern const char *cmd_prompt (enum node_type); extern enum cmd_return_code @@ -50,9 +53,8 @@ Inline enum cmd_return_code cmd_dispatch_call(struct vty* vty) { cmd_parsed parsed = vty->parsed ; - return (*(parsed->cmd->func))(parsed->cmd, vty, - vector_length(&parsed->vline), - (const char * const*)vector_body(&parsed->vline)) ; + return (*(parsed->cmd->func))(parsed->cmd, vty, cmd_arg_vector_argc(parsed), + cmd_arg_vector_argv(parsed)) ; } ; #define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0) diff --git a/lib/command_parse.c b/lib/command_parse.c new file mode 100644 index 00000000..d8960b8d --- /dev/null +++ b/lib/command_parse.c @@ -0,0 +1,903 @@ +/* Quagga command line parsing -- header + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <zebra.h> + +#include "command_parse.h" +#include "memory.h" + +/*============================================================================== + * Token handling + */ + +/* Store of qstrings, used for parsing. */ +token_vector_t spare_tokens ; + +static char cmd_token_escape(char e) ; + +/*------------------------------------------------------------------------------ + * Initialise a brand new token vector -- empty. + */ +static inline void +cmd_token_vector_init(token_vector tokens) +{ + vector_init_new(tokens->body, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise empty spare tokens vector + */ +extern void +cmd_spare_tokens_init(void) +{ + cmd_token_vector_init(spare_tokens) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty out the spare_tokens vector and release all memory + */ +extern void +cmd_spare_tokens_free(void) +{ + token tok ; + + while ((tok = vector_ream(spare_tokens->body, keep_it)) != NULL) + qs_reset(tok->qs, keep_it) ; +} ; + +/*------------------------------------------------------------------------------ + * Take string and break it into tokens. + * + * Discards leading and trailing ' ' or '\t'. + * + * Expects string to have been preprocessed, if required, to ensure that any + * unwanted control characters have been removed. This code only recognises + * '\t'. + * + * Anything between '....' is ignored by the tokenizer. NB: this follows the + * shell convention, so '\' is also ignored and there is no way to include "'" + * in a single quoted string. + * + * Anything immediately preceded by '\' is ignored by the tokenizer. This + * includes blanks and quotes. + * + * Anything inside "...." is ignored by the tokenizer, including '\"' escapes. + * + * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'. + * + * Of the things which are not ignored by the tokenizer: + * + * * tokens are separated by whitespace -- one ' ' or '\t' characters + * The whitespace is discarded. + * + * * tokens are separated by "separators", which start with any of: + * + * '!', '#', '<', '>' and '|' + * + * which may be followed by one or more characters to form a separator + * token. + * + * - from '!' or '#' to end of line is a comment token. + * + * - '<' opt and '<|' opt are separators, where opt is any combination + * of '+', '*' and '-'. + * + * - '>', '>>' and '|' are separators. + * + * NB: the tokenization mimics the standard shell which makes the piping stuff + * straightforward. It's also well known. Apart from the "'" rule, it + * also seems fine ! + * + * NB: any control characters other than those spotted by isspace() are accepted + * as part of the current token ! + * + * The tokens returned contain all the original characters of the line, except + * for the removal of '\t' between tokens. + * + * Returns: the types of all tokens or'd together. + * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') + * or if the pointer was NULL. + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the callers responsibility to release the token objects in due + * course. + */ +extern cmd_token_type_t +cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) +{ + const char *cp, *ep ; + cmd_token_type_t total ; + + cmd_empty_token_vector(parsed->tokens) ; /* Empty the token vector */ + + parsed->line = line ; + parsed->onode = parsed->cnode = node ; + + total = cmd_tok_null ; /* nothing yet */ + + if (line == NULL) /* tolerate NULL */ + return total ; + + cp = line ; + ep = cp + strlen(cp) ; + + while (cp < ep) /* process to end */ + { + const char* sp ; + bool end ; + cmd_token_type_t type ; + + if ((*cp == ' ') || (*cp == '\t')) + { + /* skip white-space */ + do { ++cp ; } while ((*cp == ' ') || (*cp == '\t')) ; + + if (cp == ep) + { + if (total != cmd_tok_null) + total |= cmd_tok_trailing ; + break ; + } ; + } ; + + sp = cp ; + end = false ; + type = cmd_tok_simple ; + do + { + switch (*cp) + { + case '\t': /* whitespace at end of token */ + case ' ': + end = true ; + break ; + + case '\'': /* proceed to matching '\'' or end */ + type |= cmd_tok_sq ; + ++cp ; + while (cp < ep) + { + if (*cp++ == '\'') + break ; + } ; + break ; + + case '\\': /* step past escaped character, if any */ + type |= cmd_tok_esc ; + ++cp ; + if (cp < ep) + ++cp ; + break ; + + case '"': /* proceed to matching '"' or end... */ + type |= cmd_tok_dq ; + ++cp ; + while (cp < ep) /* NB: do not register '\\' separately */ + { + if (*cp++ == '"') + if (*(cp - 2) != '\\') /* ignore escaped '"' */ + break ; + } ; + break ; + + case '>': /* '>' or '>>' separators. */ + end = true ; + if (cp == sp) /* if at start of token */ + { + type = cmd_tok_pipe_out ; + ++cp ; + if ((cp < ep) && (*cp == '>')) + ++cp ; + } ; + break ; + + case '|': /* '|' separator. */ + end = true ; + if (cp == sp) + type = cmd_tok_pipe_out ; + ++cp ; + break ; + + case '<': /* '<' or '<|' separators. */ + end = true ; + if (cp == sp) + { + type = cmd_tok_pipe_in ; + ++cp ; + if ((cp < ep) && (*cp == '|')) + ++cp ; + if ( (cp < ep) && + ((*cp == '+') || (*cp == '-') || (*cp == '*')) ) + ++cp ; + } ; + break ; + + case '!': /* '!' and '#' separators. */ + case '#': + end = true ; + if (cp == sp) + { + type = cmd_tok_comment ; + cp = ep ; + } ; + break ; + + default: + ++cp ; + break ; + } ; + } while (!end && (cp < ep)) ; + + cmd_token_push(parsed->tokens, + cmd_token_new(type, sp, cp - sp, sp - line)) ; + } ; + + return total ; +} ; + +/*------------------------------------------------------------------------------ + * Process token to remove quotes and escapes (if any). + * + * Returns: true <=> OK + * false => invalid escape or incomplete quotes + * + * NB: if fails, returns token completed as far as possible. + */ +extern bool +cmd_token_do_complete(token t) +{ + char *s, *p, *q ; + char ch ; + bool ok = true ; + bool dq = false ; + + p = s = cmd_token_value(t) ; + q = p ; + while (*p != '\0') + { + switch (*p) + { + case '\'': + ++p ; /* skip leading '\'' */ + while (1) + { + if (*p == '\0') + { + ok = false ; /* broken '...' */ + break ; + } ; + + if (*p == '\'') + { + ++p ; /* skip trailing '\'' */ + break ; /* done '....' */ + } ; + + *q++ = *p++ ; + } ; + break ; + + case '"': + ++p ; /* skip '"' */ + dq = !dq ; + break ; + + case '\\': + ++p ; /* step past '\\' */ + ch = cmd_token_escape(*p) ; + if (ch == '\0') + { + ok = false ; + ch = *p ; + if (ch == '\0') + ch = '\\' ; /* \ at end is kept */ + else + *q++ = '\\' ; /* otherwise keep \x */ + } ; + *q++ = ch ; + break ; + + default: + *q++ = *p++ ; + } ; + } ; + + qs_term_here(t->qs, q) ; + + return ok && !dq ; +} + +/*------------------------------------------------------------------------------ + * Return escaped value of \e. + * + * Everything except '0'..'9', 'A'..'Z', 'a'..'z' can be escaped -- these are + * reserved for future actual escapes ! + * + * Returns '\0' if e == '\0' or if \e is an invalid escape. + */ +static char +cmd_token_escape(char e) +{ + if ((e < '0') || (e > 'z')) + return e ; + return isalpha(e) ? '\0' : e ; +} ; + +/*============================================================================== + * Parser object + */ + +/*------------------------------------------------------------------------------ + * Initialise a new cmd_parsed object, allocating if required + */ +extern cmd_parsed +cmd_parse_init_new(cmd_parsed parsed) +{ + if (parsed == NULL) + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; + else + memset(parsed, 0, sizeof(*parsed)) ; + + /* Zeroising the structure has set: + * + * cmd = NULL -- no command parsed, yet + * cnode -- no node set, yet + * + * do_shortcut -- false + * onode -- not material (do_shortcut is false) + * + * pipes = 0 -- cmd_pipe_none + */ + confirm(cmd_pipe_none == 0) ; + + cmd_token_vector_init(parsed->tokens) ; + cmd_token_vector_init(parsed->read_pipe_tokens) ; + cmd_token_vector_init(parsed->write_pipe_tokens) ; + cmd_arg_vector_init(parsed) ; + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ + * Empty out and (if required) free a cmd_parsed object + */ +extern cmd_parsed +cmd_parse_reset(cmd_parsed parsed, bool free_structure) +{ + if (parsed != NULL) + { + cmd_empty_parsed_tokens(parsed) ; /* give back tokens */ + cmd_arg_vector_free(parsed) ; /* give back vector body */ + + if (free_structure) + XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + else + cmd_parse_init_new(parsed) ; + } ; + + return parsed ; +} ; + +/*============================================================================== + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? + */ + + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address: + * + * 999.999.999.999 -- where no part may be > 255 + * + * TODO: cmd_ipv4_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ +extern match_type_t +cmd_ipv4_match (const char *str) +{ + const char *sp; + int dots = 0, nums = 0; + char buf[4]; + + if (str == NULL) + return partly_match; + + for (;;) + { + memset (buf, 0, sizeof (buf)); + sp = str; + while (*str != '\0') + { + if (*str == '.') + { + if (dots >= 3) + return no_match; + + if (*(str + 1) == '.') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy (buf, sp, str - sp); + if (atoi (buf) > 255) + return no_match; + + nums++; + + if (*str == '\0') + break; + + str++; + } + + if (nums < 4) + return partly_match; + + return exact_match; +} + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Prefix: + * + * 999.999.999.999/99 -- where no part may be > 255, + * and prefix length may not be > 32 + * + * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +extern match_type_t +cmd_ipv4_prefix_match (const char *str) +{ + const char *sp; + int dots = 0; + char buf[4]; + + if (str == NULL) + return partly_match; + + for (;;) + { + memset (buf, 0, sizeof (buf)); + sp = str; + while (*str != '\0' && *str != '/') + { + if (*str == '.') + { + if (dots == 3) + return no_match; + + if (*(str + 1) == '.' || *(str + 1) == '/') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy (buf, sp, str - sp); + if (atoi (buf) > 255) + return no_match; + + if (dots == 3) + { + if (*str == '/') + { + if (*(str + 1) == '\0') + return partly_match; + + str++; + break; + } + else if (*str == '\0') + return partly_match; + } + + if (*str == '\0') + return partly_match; + + str++; + } + + sp = str; + while (*str != '\0') + { + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (atoi (sp) > 32) + return no_match; + + return exact_match; +} + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address: + * + * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ + +#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" +#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" +#define STATE_START 1 +#define STATE_COLON 2 +#define STATE_DOUBLE 3 +#define STATE_ADDR 4 +#define STATE_DOT 5 +#define STATE_SLASH 6 +#define STATE_MASK 7 + +#ifdef HAVE_IPV6 + +extern match_type_t +cmd_ipv6_match (const char *str) +{ + int state = STATE_START; + int colons = 0, nums = 0, double_colon = 0; + const char *sp = NULL; + struct sockaddr_in6 sin6_dummy; + int ret; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV6_ADDR_STR) != strlen (str)) + return no_match; + + /* use inet_pton that has a better support, + * for example inet_pton can support the automatic addresses: + * ::1.2.3.4 + */ + ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); + + if (ret == 1) + return exact_match; + + while (*str != '\0') + { + switch (state) + { + case STATE_START: + if (*str == ':') + { + if (*(str + 1) != ':' && *(str + 1) != '\0') + return no_match; + colons--; + state = STATE_COLON; + } + else + { + sp = str; + state = STATE_ADDR; + } + + continue; + case STATE_COLON: + colons++; + if (*(str + 1) == ':') + state = STATE_DOUBLE; + else + { + sp = str + 1; + state = STATE_ADDR; + } + break; + case STATE_DOUBLE: + if (double_colon) + return no_match; + + if (*(str + 1) == ':') + return no_match; + else + { + if (*(str + 1) != '\0') + colons++; + sp = str + 1; + state = STATE_ADDR; + } + + double_colon++; + nums++; + break; + case STATE_ADDR: + if (*(str + 1) == ':' || *(str + 1) == '\0') + { + if (str - sp > 3) + return no_match; + + nums++; + state = STATE_COLON; + } + if (*(str + 1) == '.') + state = STATE_DOT; + break; + case STATE_DOT: + state = STATE_ADDR; + break; + default: + break; + } + + if (nums > 8) + return no_match; + + if (colons > 7) + return no_match; + + str++; + } + +#if 0 + if (nums < 11) + return partly_match; +#endif /* 0 */ + + return exact_match; +} + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Prefix: + * + * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? + * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +extern match_type_t +cmd_ipv6_prefix_match (const char *str) +{ + int state = STATE_START; + int colons = 0, nums = 0, double_colon = 0; + int mask; + const char *sp = NULL; + char *endptr = NULL; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) + return no_match; + + while (*str != '\0' && state != STATE_MASK) + { + switch (state) + { + case STATE_START: + if (*str == ':') + { + if (*(str + 1) != ':' && *(str + 1) != '\0') + return no_match; + colons--; + state = STATE_COLON; + } + else + { + sp = str; + state = STATE_ADDR; + } + + continue; + case STATE_COLON: + colons++; + if (*(str + 1) == '/') + return no_match; + else if (*(str + 1) == ':') + state = STATE_DOUBLE; + else + { + sp = str + 1; + state = STATE_ADDR; + } + break; + case STATE_DOUBLE: + if (double_colon) + return no_match; + + if (*(str + 1) == ':') + return no_match; + else + { + if (*(str + 1) != '\0' && *(str + 1) != '/') + colons++; + sp = str + 1; + + if (*(str + 1) == '/') + state = STATE_SLASH; + else + state = STATE_ADDR; + } + + double_colon++; + nums += 1; + break; + case STATE_ADDR: + if (*(str + 1) == ':' || *(str + 1) == '.' + || *(str + 1) == '\0' || *(str + 1) == '/') + { + if (str - sp > 3) + return no_match; + + for (; sp <= str; sp++) + if (*sp == '/') + return no_match; + + nums++; + + if (*(str + 1) == ':') + state = STATE_COLON; + else if (*(str + 1) == '.') + state = STATE_DOT; + else if (*(str + 1) == '/') + state = STATE_SLASH; + } + break; + case STATE_DOT: + state = STATE_ADDR; + break; + case STATE_SLASH: + if (*(str + 1) == '\0') + return partly_match; + + state = STATE_MASK; + break; + default: + break; + } + + if (nums > 11) + return no_match; + + if (colons > 7) + return no_match; + + str++; + } + + if (state < STATE_MASK) + return partly_match; + + mask = strtol (str, &endptr, 10); + if (*endptr != '\0') + return no_match; + + if (mask < 0 || mask > 128) + return no_match; + +/* I don't know why mask < 13 makes command match partly. + Forgive me to make this comments. I Want to set static default route + because of lack of function to originate default in ospf6d; sorry + yasu + if (mask < 13) + return partly_match; +*/ + + return exact_match; +} + +#endif /* HAVE_IPV6 */ + +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: true <=> OK -- *including* empty string + * false => not a valid number, or not in required range + * (or invalid range !!) + */ + +#define DECIMAL_STRLEN_MAX 10 + +extern bool +cmd_range_match (const char *range, const char *str) +{ + char *p; + char buf[DECIMAL_STRLEN_MAX + 1]; + char *endptr = NULL; + unsigned long min, max, val; + + if (str == NULL) + return true ; + + val = strtoul (str, &endptr, 10); + if (*endptr != '\0') + return false ; + + range++; + p = strchr (range, '-'); + if (p == NULL) + return false ; + if (p - range > DECIMAL_STRLEN_MAX) + return false ; + strncpy (buf, range, p - range); + buf[p - range] = '\0'; + min = strtoul (buf, &endptr, 10); + if (*endptr != '\0') + return false ; + + range = p + 1; + p = strchr (range, '>'); + if (p == NULL) + return false ; + if (p - range > DECIMAL_STRLEN_MAX) + return false ; + strncpy (buf, range, p - range); + buf[p - range] = '\0'; + max = strtoul (buf, &endptr, 10); + if (*endptr != '\0') + return false ; + + if (val < min || val > max) + return false ; + + return true ; +} + + diff --git a/lib/command_parse.h b/lib/command_parse.h new file mode 100644 index 00000000..ab91e7f4 --- /dev/null +++ b/lib/command_parse.h @@ -0,0 +1,478 @@ +/* Quagga command line parsing -- header + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_COMMAND_PARSE_H +#define _ZEBRA_COMMAND_PARSE_H + +#include <zebra.h> +#include "misc.h" + +#include "node_type.h" +#include "vector.h" +#include "qstring.h" + +/*============================================================================== + * Parsing of tokens + */ + +enum cmd_token_spec +{ + cmd_ts_simple = 0, + + cmd_ts_keyword, + cmd_ts_number, + cmd_ts_word, + cmd_ts_option, + + cmd_ts_ipv4_addr, + cmd_ts_ipv4_prefix, + + cmd_ts_ipv6_addr, + cmd_ts_ipv6_prefix, + + cmd_ts_vararg, + + + +} ; +typedef enum cmd_token_spec cmd_token_spec_t ; + +/*============================================================================== + * Completion match types. + * + * NB: the order of these is significant -- in particular as confirmed below. + */ +enum match_type +{ + no_match = 0, /* nope */ + any_match = 1, + + extend_match = any_match, + + ipv4_prefix_match, + ipv4_match, + ipv6_prefix_match, + ipv6_match, + range_match, + vararg_match, + + partly_match, /* OK as far as it went */ + exact_match, /* Syntactically complete -- greatest match */ + + match_type_count /* Number of match types */ +} ; +typedef enum match_type match_type_t ; + +CONFIRM(no_match == false) ; +CONFIRM(extend_match == (no_match + 1)) ; +CONFIRM(partly_match == (exact_match - 1)) ; +CONFIRM(exact_match == (match_type_count - 1)) ; + +/*============================================================================== + * + */ + +/* Command parsing options */ +enum cmd_parse_type /* bit significant */ +{ + cmd_parse_completion = 0, + cmd_parse_strict = BIT(0), + + cmd_parse_do = BIT(1), + cmd_parse_tree = BIT(2), +} ; +typedef enum cmd_parse_type cmd_parse_type_t ; + +/* Pipe types */ +enum cmd_pipe_type /* bit significant */ +{ + cmd_pipe_none = 0, + + cmd_pipe_in_file = BIT(0), + cmd_pipe_in_shell = BIT(1), + + cmd_pipe_reflect = BIT(4), + cmd_pipe_output = BIT(5), + cmd_pipe_more = BIT(6), + + cmd_pipe_out_file = BIT( 8), + cmd_pipe_out_file_append = BIT( 9), + cmd_pipe_out_shell = BIT(10), +} ; +typedef enum cmd_pipe_type cmd_pipe_type_t ; + +/*------------------------------------------------------------------------------ + * Token object -- a qstring and some other properties. + */ +enum cmd_token_type /* *bit* significant */ +{ + cmd_tok_null = 0, /* used for empty lines */ + + cmd_tok_simple = BIT( 0), + cmd_tok_trailing = BIT( 1), + + cmd_tok_sq = BIT( 8), + cmd_tok_dq = BIT( 9), /* '\\' within "..." are not + registered separately. */ + cmd_tok_esc = BIT(10), + + cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc), + + cmd_tok_pipe_in = BIT(12), + cmd_tok_pipe_out = BIT(13), + cmd_tok_comment = BIT(14), +} ; +typedef enum cmd_token_type cmd_token_type_t ; + +struct token +{ + cmd_token_type_t type ; + qstring_t qs ; + size_t tp ; +} ; +typedef struct token token_t[1] ; +typedef struct token* token ; + +/*------------------------------------------------------------------------------ + * Token vector -- a vector of token objects + */ +struct token_vector +{ + vector_t body ; +} ; + +typedef struct token_vector token_vector_t[1] ; +typedef struct token_vector* token_vector ; + +/*------------------------------------------------------------------------------ + * Argument vector -- a vector of const char* + */ +struct arg_vector +{ + vector_t body ; +} ; +typedef struct arg_vector arg_vector_t[1] ; +typedef struct arg_vector* arg_vector ; + +/*------------------------------------------------------------------------------ + * Parsed command line + * + * The current command line is only valid while command is being parsed and + * executed -- in between it is nonsense. The pointer can be overwritten at + * any time -- responsibility for the memory lies elsewhere. + * + * The args vector is a set of pointers to the strings in the relevant tokens. + * This vector is only valid while command is being parsed and executed -- in + * between it too is nonsense. The pointers can be overwritten or discarded at + * any time -- responsibility for the memory lies elsewhere. + * + * The token vectors contain the tokens for the current command being parsed + * and executed. After the command has completed these vectors can be + * emptied -- see cmd_empty_parsed_tokens() -- but the next command to be + * parsed will tidy up before proceeding. + */ +typedef struct cmd_parsed* cmd_parsed ; +struct cmd_parsed +{ + struct cmd_element *cmd ; /* NULL if empty command + or fails to parse */ + + const char* line ; /* the current line */ + + enum node_type cnode ; /* node command is in */ + enum node_type onode ; /* node the parser started in */ + + bool do_shortcut ; /* true => is "do" command */ + + token_vector_t tokens ; /* vector of token objects */ + arg_vector_t args ; /* vector of arguments */ + + cmd_pipe_type_t pipes ; /* if any */ + + token_vector_t read_pipe_tokens ; + token_vector_t write_pipe_tokens ; +} ; + +/* Command dispatch options */ +enum { + cmd_no_queue = true, + cmd_may_queue = false, +} ; + +/*------------------------------------------------------------------------------ + * Vector of spare token objects -- declared here to allow inlines, defined + * in command_parse.c + */ +extern token_vector_t spare_tokens ; + +/*============================================================================== + * Prototypes + */ +extern void cmd_spare_tokens_init(void) ; +extern void cmd_spare_tokens_free(void) ; + +extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; +extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; + +extern cmd_token_type_t cmd_tokenise(cmd_parsed parsed, const char *line, + node_type_t node) ; +Inline const char* cmd_token_string(token t) ; +Inline int cmd_token_count(token_vector tv) ; +Inline token cmd_token_get(token_vector tv, vector_index_t i) ; +Inline token cmd_token_pop(token_vector tv) ; +Inline void cmd_token_push(token_vector tv, token t) ; +Inline token cmd_token_shift(token_vector tv) ; +Inline void cmd_token_unshift(token_vector tv, token t) ; +Inline token cmd_token_make(void) ; +Inline token cmd_token_new(cmd_token_type_t type, const char* p, + size_t len, size_t tp) ; + +Inline void cmd_empty_token_vector(token_vector tv) ; +Inline void cmd_empty_parsed_tokens(cmd_parsed parsed) ; + +Inline bool cmd_token_complete(token t) ; +extern bool cmd_token_do_complete(token t) ; + +extern match_type_t cmd_ipv4_match (const char *str) ; +extern match_type_t cmd_ipv4_prefix_match (const char *str) ; +#if HAVE_IPV6 +extern match_type_t cmd_ipv6_match (const char *str) ; +extern match_type_t cmd_ipv6_prefix_match (const char *str) ; +#endif +extern bool cmd_range_match (const char *range, const char *str) ; + +/*============================================================================== + * Inline Functions + */ + +/*------------------------------------------------------------------------------ + * Get pointer to token value. + * + * Returns NULL if token NULL or no string. + */ +Inline char* +cmd_token_value(token t) +{ + return (t == NULL) ? NULL : qs_chars(t->qs) ; +} ; + +/*------------------------------------------------------------------------------ + * Get string value of given token. + * + * Returns an empty (not NULL) string if token NULL or no string. + */ +Inline const char* +cmd_token_string(token t) +{ + const char* s = cmd_token_value(t) ; + return (s == NULL) ? "" : s ; +} ; + +/*------------------------------------------------------------------------------ + * Get number of tokens in the given token vector + */ +Inline int +cmd_token_count(token_vector tv) +{ + return vector_length(tv->body) ; +} ; + +/*------------------------------------------------------------------------------ + * Get i'th token from given token vector -- zero origin + */ +Inline token +cmd_token_get(token_vector tv, vector_index_t i) +{ + return vector_get_item(tv->body, i) ; +} ; + +/*------------------------------------------------------------------------------ + * Pop token from end of given token vector -- if any. + */ +Inline token +cmd_token_pop(token_vector tv) +{ + return vector_pop_item(tv->body) ; +} ; + +/*------------------------------------------------------------------------------ + * Push token onto end of given token vector. + */ +Inline void +cmd_token_push(token_vector tv, token t) +{ + vector_push_item(tv->body, t) ; +} ; + +/*------------------------------------------------------------------------------ + * Shift first token off front of given token vector -- if any. + */ +Inline token +cmd_token_shift(token_vector tv) +{ + return vector_shift_item(tv->body) ; +} ; + +/*------------------------------------------------------------------------------ + * Unshift token onto front of given token vector. + */ +Inline void +cmd_token_unshift(token_vector tv, token t) +{ + vector_unshift_item(tv->body, t) ; +} ; + +/*------------------------------------------------------------------------------ + * Make a brand new token object + */ +Inline token +cmd_token_make(void) +{ + return XCALLOC(MTYPE_TOKEN, sizeof(struct token)) ; + + /* Zeroising the new structure sets: + * + * type = 0 -- cmd_tok_null + * qs = zeroised qstring -- empty string + */ + confirm(cmd_tok_null == 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Create new 'cmd_tok_simple' token from given characters + length + */ +Inline token +cmd_token_new(cmd_token_type_t type, const char* p, size_t len, size_t tp) +{ + token t ; + + t = cmd_token_pop(spare_tokens) ; + if (t == NULL) + t = cmd_token_make() ; + + t->type = type ; + qs_set_n(&t->qs, p, len) ; + t->tp = tp ; + + return t ; +} ; + +/*------------------------------------------------------------------------------ + * Discard given token -- give back to spare tokens list + */ +Inline void +cmd_token_discard(token t) +{ + if (t != NULL) + vector_push_item(spare_tokens->body, t) ; +} ; + +/*------------------------------------------------------------------------------ + * Release contents of token vector -- move to the spare_token_strings. + */ +Inline void +cmd_empty_token_vector(token_vector tv) +{ + if (cmd_token_count(tv) != 0) + vector_move_append(spare_tokens->body, tv->body) ; +} ; + +/*------------------------------------------------------------------------------ + * Release contents of all token vectors in given parsed object. + */ +Inline void +cmd_empty_parsed_tokens(cmd_parsed parsed) +{ + cmd_empty_token_vector(parsed->tokens) ; + cmd_empty_token_vector(parsed->read_pipe_tokens) ; + cmd_empty_token_vector(parsed->write_pipe_tokens) ; +} ; + +/*------------------------------------------------------------------------------ + * If token is incomplete (contains quotes or escapes) process those down. + * + * Returns: true <=> OK + * false => invalid escape + */ +Inline bool +cmd_token_complete(token t) +{ + return ((t->type & cmd_tok_incomplete) == 0) ? true + : cmd_token_do_complete(t) ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise arg_vector object in cmd_parsed. + */ +Inline void +cmd_arg_vector_init(cmd_parsed parsed) +{ + vector_init_new(parsed->args->body, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Free the body of the arg_vector object in cmd_parsed. + */ +Inline void +cmd_arg_vector_free(cmd_parsed parsed) +{ + vector_reset(parsed->args->body, keep_it) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty the body of the arg_vector object in cmd_parsed. + */ +Inline void +cmd_arg_vector_empty(cmd_parsed parsed) +{ + vector_set_length(parsed->args->body, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty the body of the arg_vector object in cmd_parsed. + */ +Inline void +cmd_arg_vector_push(cmd_parsed parsed, char* arg) +{ + vector_push_item(parsed->args->body, arg) ; +} ; + +/*------------------------------------------------------------------------------ + * Get the body of the argument vector. + */ +Inline const char * const* +cmd_arg_vector_argv(cmd_parsed parsed) +{ + return (const char* const*)vector_body(parsed->args->body) ; +} ; + +/*------------------------------------------------------------------------------ + * Get length of the body of the argument vector. + */ +Inline unsigned +cmd_arg_vector_argc(cmd_parsed parsed) +{ + return vector_length(parsed->args->body) ; +} ; + +#endif /* _ZEBRA_COMMAND_PARSE_H */ diff --git a/lib/elstring.h b/lib/elstring.h new file mode 100644 index 00000000..051e5962 --- /dev/null +++ b/lib/elstring.h @@ -0,0 +1,354 @@ +/* Length/String string handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_ELSTRING_H +#define _ZEBRA_ELSTRING_H + +#include "misc.h" +#include "zassert.h" +#include "memory.h" + +/*============================================================================== + * This is some very simple support for strings which are Length/Body + * objects. + * + * NB: this object knows NOTHING about memory allocation etc. The objective + * is the simplest possible encapsulation of strings which are NOT '\0' + * terminated. + * + * + * + */ +struct elstring +{ + union + { + void* v ; /* may be NULL iff len == 0 */ + const void* cv ; + } body ; + + ulen len ; + bool term ; /* true <=> body is '\0' terminated */ +} ; + +typedef struct elstring elstring_t[1] ; +typedef struct elstring* elstring ; + +/* Setting an elstring object to all zeros is enough to initialise it to + * an empty string. + */ +enum +{ + ELSTRING_INIT_ALL_ZEROS = true +} ; + +/*------------------------------------------------------------------------------ + * Various forms of body -- NB: + */ + +Inline void* +els_body_nn(elstring els) +{ + return els->body.v ; +} ; + +Inline void* +els_body(elstring els) +{ + return (els != NULL) ? els_body_nn(els) : NULL ; +} ; + +Inline ulen +els_len_nn(elstring els) +{ + return els->len ; +} ; + +Inline ulen +els_len(elstring els) +{ + return (els != NULL) ? els_len_nn(els) : 0 ; +} ; + +Inline bool +els_term_nn(elstring els) +{ + return els->term ; +} ; + +Inline bool +els_term(elstring els) +{ + return (els != NULL) ? els_term_nn(els) : false ; +} ; + +/*============================================================================== + * All so simple that everything is implemented as Inline + */ + +/*------------------------------------------------------------------------------ + * Initialise or create a new elstring + */ +Inline elstring +els_init_new(elstring els) +{ + if (els == NULL) + els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + else + memset(els, 0, sizeof(elstring_t)) ; + + confirm(ELSTRING_INIT_ALL_ZEROS) ; + + return els ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring value from ordinary string. + * + * NB: elstring MUST NOT be NULL. + * + * NB: treats str == NULL as a zero length string. + * + * NB: sets "term" unless str == NULL. + */ +Inline void +els_set_nn(elstring els, const void* str) +{ + els->body.cv = str ; + els->len = (str != NULL) ? strlen(str) : 0 ; + els->term = (str != NULL) ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring value from ordinary string. + * + * Creates elstring object if required (ie if els == NULL). + * + * See: els_set_nn. + */ +Inline elstring +els_set(elstring els, const void* str) +{ + if (els == NULL) + els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + + els_set_nn(els, str) ; + + return els ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring value from body + length. + * + * NB: sets term = false. + * + * NB: elstring MUST NOT be NULL. + * + * NB: treats str == NULL as a zero length string. + */ +Inline void +els_set_n_nn(elstring els, const void* body, ulen len) +{ + els->body.cv = body ; + els->len = len ; + els->term = false ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring value from body + length. + * + * Creates elstring object if required (ie if els == NULL). + * + * See els_set_n_nn. + */ +Inline elstring +els_set_n(elstring els, const void* body, ulen len) +{ + if (els == NULL) + els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + + els_set_n_nn(els, body, len) ; + + return els ; +} ; + +/*------------------------------------------------------------------------------ + * Clear contents of an elstring (if any) + * + * NB: it is the callers responsibility to free the contents of the elstring. + * if that is required, before freeing the elstring itself. + */ +Inline void +els_clear(elstring els) +{ + if (els != NULL) + { + els->body.v = NULL ; + els->len = 0 ; + els->term = false ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Release dynamically allocated elstring. + * + * Returns NULL. + * + * NB: it is the callers responsibility to free the contents of the elstring. + * if that is required, before freeing the elstring itself. + */ +Inline elstring +els_free(elstring els) +{ + if (els != NULL) + XFREE(MTYPE_TMP, els) ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring length. And set term false. + * + * NB: it is the caller's responsibility to set a valid length !! + * + * NB: elstring MUST NOT be NULL. + */ +Inline void +els_set_len_nn(elstring els, ulen len) +{ + els->len = len ; + els->term = false ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring body. And set term false. + * + * NB: it is the caller's responsibility to set a valid body !! + * + * NB: elstring MUST NOT be NULL. + */ +Inline void +els_set_body_nn(elstring els, const void* body) +{ + els->body.cv = body ; + els->term = false ; +} ; + +/*------------------------------------------------------------------------------ + * Set elstring terminated. + * + * NB: it is the caller's responsibility to set a valid body !! + * + * NB: elstring MUST NOT be NULL. + */ +Inline void +els_set_term_nn(elstring els, bool term) +{ + els->term = term ; +} ; + +/*------------------------------------------------------------------------------ + * Compare two elstrings -- returns the usual -ve, 0, +ve cmp result. + */ +Inline int +els_cmp(elstring a, elstring b) +{ + const uchar* ap ; + const uchar* bp ; + ulen al, bl ; + ulen n ; + + ap = els_body(a) ; + bp = els_body(b) ; + al = els_len(a) ; + bl = els_len(b) ; + + n = (al <= bl) ? al : bl ; + + while (n) + { + int d = *ap++ - *bp++ ; + if (d != 0) + return d ; + --n ; + } ; + + return al < bl ? -1 : (al == bl) ? 0 : +1 ; +} ; + +/*------------------------------------------------------------------------------ + * Are two elstrings equal ? -- returns true if strings equal. + */ +Inline bool +els_equal(elstring a, elstring b) +{ + const uchar* ap ; + const uchar* bp ; + ulen n ; + + n = els_len(b) ; + if (n != els_len(a)) + return false ; + + ap = els_body(a) ; + bp = els_body(b) ; + + while (n) + { + if (*ap++ != *bp++) + return false ; + --n ; + } ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Is 'b' a leading substring of 'a' ? -- returns true if it is. + * + * If 'b' is empty it is always a leading substring. + */ +Inline int +els_substring(elstring a, elstring b) +{ + const uchar* ap ; + const uchar* bp ; + ulen n ; + + n = els_len(b) ; + if (n > els_len(a)) + return false ; + + ap = els_body(a) ; + bp = els_body(b) ; + + while (n) + { + if (*ap++ != *bp++) + return false ; + --n ; + } ; + + return true ; +} ; + +#endif /* _ZEBRA_ELSTRING_H */ + @@ -79,7 +79,7 @@ */ static heap -heap_setup(heap h, int new_vector, vector_index size, heap_cmp* cmp, +heap_setup(heap h, int new_vector, vector_length_t size, heap_cmp* cmp, int with_backlink, unsigned int backlink_offset) ; /* Initialize heap -- allocating heap structure if required. @@ -166,12 +166,13 @@ heap_re_init(heap h, unsigned int size, heap_cmp* cmp, * *before* doing this. */ heap -heap_reset(heap h, int free_structure) +heap_reset(heap h, free_keep_b free_structure) { - vector_reset_keep(&h->v) ; /* vector structure is embedded in the heap */ + vector_reset(h->v, keep_it) ; /* vector structure is embedded in the heap */ + confirm(free_it == true) ; if (free_structure) - XFREE(MTYPE_VECTOR, h) ; /* sets h = NULL */ + XFREE(MTYPE_VECTOR, h) ; /* sets h = NULL */ return h ; } ; @@ -189,9 +190,9 @@ heap_setup(heap h, int new_vector, unsigned int size, heap_cmp* cmp, h->backlink_offset = backlink_offset ; if (new_vector) - vector_init_new(&h->v, size) ; + vector_init_new(h->v, size) ; else - vector_re_init(&h->v, size) ; + vector_re_init(h->v, size) ; return h ; } ; @@ -220,14 +221,14 @@ heap_setup(heap h, int new_vector, unsigned int size, heap_cmp* cmp, * NB: items are reamed out in no defined order. */ p_vector_item -heap_ream(heap h, int free_structure) +heap_ream(heap h, free_keep_b free_structure) { p_vector_item p_v ; if (h == NULL) return NULL ; - if ((p_v = vector_ream_keep(&h->v)) == NULL) + if ((p_v = vector_ream(h->v, keep_it)) == NULL) heap_reset(h, free_structure) ; return p_v ; @@ -247,11 +248,11 @@ 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)) + 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 */ + 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 */ @@ -296,13 +297,13 @@ void heap_delete_item(heap h, p_vector_item p_v) { p_vector_item p_x ; - vector_index i ; + vector_index_t 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 */ + p_x = vector_pop_item(h->v) ; /* extract last item, if any */ - if (i < h->v.end) /* if not deleting the last item... */ + 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 */ } ; @@ -322,25 +323,25 @@ heap_delete_item(heap h, p_vector_item p_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 ; + vector_index_t i = h->v->end ; + vector_index_t e ; + vector_length_t n = v->end ; p_vector_item p_v ; if (move_vector) - vector_move_append(&h->v, v) ; + vector_move_append(h->v, v) ; else - vector_copy_append(&h->v, v) ; + vector_copy_append(h->v, v) ; e = i ; /* old end of the heap. */ while (n--) { - p_v = h->v.p_items[i++] ; + 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 */ + h->v->end = e ; /* new end of heap */ } ; /* Pop given heap to vector -- creating vector if required (v == NULL). @@ -363,8 +364,8 @@ heap_push_vector(heap h, vector v, int move_vector) vector heap_pop_vector(vector v, heap h, int move_heap) { - vector_index n = h->v.end ; - vector_index i ; + vector_length_t n = h->v->end ; + vector_index_t i ; v = vector_re_init(v, n) ; /* guarantees >= 'n' items in vector */ v->end = n ; @@ -373,7 +374,7 @@ heap_pop_vector(vector v, heap h, int move_heap) v->p_items[i] = heap_pop_item(h) ; if (!move_heap) - vector_copy_here(&h->v, v) ; /* fully sorted is also heap ordered ! */ + vector_copy_here(h->v, v) ; /* fully sorted is also heap ordered ! */ return v ; } ; @@ -401,11 +402,11 @@ heap_pop_vector(vector v, heap h, int move_heap) * * Note that this sets the backlink on the given item. */ -private void -heap_bubble(heap h, vector_index i, p_vector_item p_v) +Private void +heap_bubble(heap h, vector_index_t 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)) + 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 @@ -421,11 +422,11 @@ heap_bubble(heap h, vector_index i, p_vector_item p_v) * 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) +Private void +heap_bubble_up(heap h, vector_index_t 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* ha = h->v->p_items ; /* underlying array */ + vector_index_t ip ; /* index of parent */ p_vector_item p_p ; /* pointer to parent item */ dassert(ha != NULL) ; @@ -453,16 +454,16 @@ heap_bubble_up(heap h, vector_index i, p_vector_item p_v) * * 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) +Private void +heap_bubble_down(heap h, vector_index_t 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 */ + vector_length_t e = h->v->end ; /* end of heap */ + vector_index_t ic ; /* index of child */ + vector_index_t 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 */ + p_vector_item* ha = h->v->p_items ; /* underlying array */ dassert(ha != NULL) ; while (1) @@ -497,21 +498,21 @@ heap_bubble_down(heap h, vector_index i, p_vector_item p_v) } ; /* Find index of given item in the given heap. */ -private vector_index +Private vector_index_t heap_find_item(heap h, p_vector_item p_v) { - vector_index i ; + vector_index_t 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) + 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)) ; + assert((i < h->v->end) && (h->v->p_items[i] == p_v)) ; return i ; } ; @@ -17,13 +17,9 @@ #ifndef _ZEBRA_HEAP_H #define _ZEBRA_HEAP_H +#include "misc.h" #include "vector.h" -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif - /*============================================================================== * Data structures etc. */ @@ -34,7 +30,7 @@ enum heap_state { Heap_Has_Backlink = 0x01, /* Set if backlink set */ } ; -typedef vector_index heap_backlink_t ; +typedef vector_index_t heap_backlink_t ; typedef struct heap* heap ; @@ -45,7 +41,7 @@ struct heap enum heap_state state ; unsigned int backlink_offset ; - struct vector v ; + vector_t v ; } ; /*============================================================================== @@ -66,17 +62,8 @@ extern heap heap_re_init(heap h, unsigned int size, heap_cmp* cmp, #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) +extern heap heap_reset(heap h, free_keep_b free_structure) ; +extern p_vector_item heap_ream(heap h, free_keep_b free_structure) ; Inline void heap_push_item(heap h, p_vector_item p_v) ; extern p_vector_item heap_pop_item(heap h) ; @@ -102,20 +89,16 @@ extern vector heap_pop_vector(vector v, heap h, int move_heap) ; * 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(heap h, vector_index_t 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_up(heap h, vector_index_t i, p_vector_item p_v) ; -private void -heap_bubble_down(heap h, vector_index i, p_vector_item p_v) ; +Private void +heap_bubble_down(heap h, vector_index_t i, p_vector_item p_v) ; -private vector_index +Private vector_index_t heap_find_item(heap h, p_vector_item p_v) ; /*============================================================================== @@ -128,7 +111,7 @@ 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) ; + heap_bubble_up(h, vector_extend_by_1(h->v), p_v) ; } ; /* Get copy of top heap item (does not pop). @@ -138,7 +121,7 @@ heap_push_item(heap h, p_vector_item p_v) Inline p_vector_item heap_top_item(heap h) { - return vector_get_first_item(&h->v) ; /* if any */ + return vector_get_first_item(h->v) ; /* if any */ } ; /* Update heap to reflect new value of top item. diff --git a/lib/keystroke.h b/lib/keystroke.h index 2b1d4d93..10494df0 100644 --- a/lib/keystroke.h +++ b/lib/keystroke.h @@ -22,19 +22,13 @@ #ifndef _ZEBRA_KEYSTROKE_H #define _ZEBRA_KEYSTROKE_H -#include <stddef.h> -#include <stdint.h> -#include <stdbool.h> +#include "misc.h" #include <arpa/telnet.h> #include "zassert.h" #include "vio_fifo.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Keystroke buffering */ diff --git a/lib/list_util.c b/lib/list_util.c index 720b8ca7..8ce01c03 100644 --- a/lib/list_util.c +++ b/lib/list_util.c @@ -19,8 +19,7 @@ * 02111-1307, USA. */ -#include <list_util.h> - +#include "list_util.h" /*============================================================================== * Single Base, Single Link diff --git a/lib/list_util.h b/lib/list_util.h index 876b7b11..bd796779 100644 --- a/lib/list_util.h +++ b/lib/list_util.h @@ -22,12 +22,7 @@ #ifndef _ZEBRA_LIST_UTIL_H #define _ZEBRA_LIST_UTIL_H -#include <stddef.h> - -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif +#include "misc.h" /*------------------------------------------------------------------------------ * Note that the following fell foul of "strict-aliasing": @@ -47,7 +42,7 @@ * } ; * * the assignment to *p_base is, apparently, unacceptable. This works - * perfectly well as am ordinary function. Using a GNUC extension it is + * perfectly well as an ordinary function. Using a GNUC extension it is * possible to avoid the function call... hence the ugly skips. */ #ifdef __GNUC__ @@ -26,6 +26,7 @@ #define _ZEBRA_LOG_H #include <syslog.h> +#include "vargs.h" #include "pthread_safe.h" /* Here is some guidance on logging levels to use: diff --git a/lib/memory.h b/lib/memory.h index 6c95d73a..6cc95a5b 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -39,6 +39,8 @@ extern struct mlist mlists[]; #include "lib/memtypes.h" +typedef enum MTYPE mtype_t ; + /* #define MEMORY_LOG */ #ifdef MEMORY_LOG #define XMALLOC(mtype, size) \ diff --git a/lib/memtypes.c b/lib/memtypes.c index 98b53209..e5a591d6 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -15,6 +15,7 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, + { MTYPE_STRING, "String (general)" }, { MTYPE_STRVEC, "String vector" }, { MTYPE_VECTOR, "Vector structure" }, { MTYPE_VECTOR_BODY, "Vector body" }, @@ -44,6 +45,7 @@ struct memory_list memory_list_lib[] = { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, { MTYPE_CMD_PARSED, "Parsed command" }, + { MTYPE_TOKEN, "Command token" }, { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, { MTYPE_VTY_HIST, "VTY history" }, @@ -53,7 +55,6 @@ struct memory_list memory_list_lib[] = { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, { MTYPE_VIO_LC, "VTY IO Line Control" }, { MTYPE_QSTRING, "qstring structure" }, - { MTYPE_QSTRING_BODY, "qstring body" }, { MTYPE_QIOVEC, "qiovec structure" }, { MTYPE_QIOVEC_VEC, "qiovec iovec vector" }, { MTYPE_IF, "Interface" }, diff --git a/lib/misc.h b/lib/misc.h new file mode 100644 index 00000000..9437b617 --- /dev/null +++ b/lib/misc.h @@ -0,0 +1,80 @@ +/* Miscellaneous basic definitions + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_MISC_H +#define _ZEBRA_MISC_H + +/* Stuff which we generally expect to have */ +#include <stddef.h> +#include <stdint.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "zassert.h" + +/* Bit number to bit mask */ +#define BIT(b) (1 << b) + +/* Just in case there are compiler issues */ +#define Inline static inline + +/* For things which have to be made extern -- typically because they are + * used by an inline function -- but are not for public consumption. + */ +#define Private extern + +/* Other names of true/false */ +enum on_off +{ + on = true, + off = false +} ; +typedef enum on_off on_off_b ; + +/* Whether to add or not on lookup. */ +enum add +{ + add = true, + no_add = false +} ; +typedef enum add add_b ; + +/* Used in object "reset" functions (destructors) */ +enum free_keep +{ + free_it = true, + keep_it = false +} ; +typedef enum free_keep free_keep_b ; + +/* We really want to be able to assume that an int is at least 32 bits */ +CONFIRM(UINT_MAX >= 0xFFFFFFFF) ; + +/* Some useful shorthand */ +typedef unsigned char byte ; +typedef unsigned char uchar ; +typedef unsigned int uint ; +typedef unsigned int usize ; +typedef unsigned int ulen ; + +#endif /* _ZEBRA_MISC_H */ diff --git a/lib/miyagi.h b/lib/miyagi.h index 569d2da9..639b962d 100644 --- a/lib/miyagi.h +++ b/lib/miyagi.h @@ -17,9 +17,7 @@ #ifndef _ZEBRA_MIYAGI_H #define _ZEBRA_MIYAGI_H -#ifndef Inline -#define Inline static inline -#endif +#include "misc.h" /*============================================================================== * Ghastly kludge to discard "const" from pointer diff --git a/lib/mqueue.h b/lib/mqueue.h index a28b6606..68dceb15 100644 --- a/lib/mqueue.h +++ b/lib/mqueue.h @@ -22,16 +22,11 @@ #ifndef _ZEBRA_MQUEUE_H #define _ZEBRA_MQUEUE_H -#include <stddef.h> -#include <stdbool.h> +#include "misc.h" #include "qpthreads.h" #include "qtime.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Message Queue Blocks -- mqb * diff --git a/lib/node_type.h b/lib/node_type.h index 7ec1107d..b1f26187 100644 --- a/lib/node_type.h +++ b/lib/node_type.h @@ -76,6 +76,7 @@ enum node_type FORWARDING_NODE, /* IP forwarding node. */ PROTOCOL_NODE, /* protocol filtering node */ VTY_NODE, /* Vty node. */ -}; +} ; +typedef enum node_type node_type_t ; #endif /* _ZEBRA_NODE_TYPE_H */ diff --git a/lib/plist.c b/lib/plist.c index b2163e2b..c761c5c5 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -268,7 +268,7 @@ static void prefix_master_reset(struct prefix_master * pm) { struct prefix_list* plist ; - while ((plist = symbol_table_ream_keep(&(pm->table)))) + while ((plist = symbol_table_ream(&(pm->table), keep_it))) prefix_list_delete(plist) ; pm->seqnum_flag = 1 ; /* Default is to generate sequence numbers. */ @@ -326,7 +326,8 @@ prefix_list_set_ref(prefix_list_ref* p_ref, afi_t afi, const char* name) if (pm == NULL) return NULL ; - return *p_ref = symbol_set_ref(*p_ref, symbol_find(&(pm->table), name)) ; + return *p_ref = symbol_set_ref(*p_ref, + symbol_lookup(&(pm->table), name, add)) ; } ; /* Copy reference to prefix_list. @@ -389,7 +390,7 @@ prefix_list_apply (struct prefix_list *plist, void *object) struct prefix *p ; int plen ; struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; in_addr_t ip ; #ifdef s6_addr32 @@ -575,7 +576,7 @@ prefix_list_delete (struct prefix_list* plist) static struct prefix_list * prefix_list_seek (struct prefix_master* pm, const char *name) { - return symbol_get_value(symbol_seek(&(pm->table), name)) ; + return symbol_get_value(symbol_lookup(&(pm->table), name, no_add)) ; } ; /* Lookup prefix_list by afi and name -- if afi is known, and name not NULL. @@ -603,7 +604,7 @@ prefix_list_get (struct prefix_master* pm, const char *name, afi_t afi) assert((pm != NULL) && (name != NULL)) ; - sym = symbol_find(&(pm->table), name) ; /* creates if required */ + sym = symbol_lookup(&(pm->table), name, add) ; /* creates if required */ plist = symbol_get_value(sym) ; return plist ? plist : prefix_list_new(pm, sym, afi) ; @@ -691,7 +692,7 @@ prefix_list_entry_ge_le_check(struct prefix_list_entry* pe, afi_t afi) * result > 0 -- not found. index returned is of the entry with the largest * sequence number smaller than the given one. */ -static vector_index +static vector_index_t prefix_list_entry_lookup_seq(struct prefix_list *plist, int seq, int* result) { return vector_bsearch(&plist->list, (vector_bsearch_cmp*)prefix_seq_cmp, @@ -725,7 +726,7 @@ prefix_list_entry_lookup_seq(struct prefix_list *plist, int seq, int* result) * * Note that the cache is never empty. */ -static vector_index +static vector_index_t prefix_list_entry_lookup_val(struct prefix_list *plist, struct prefix_list_entry* temp, vector* cache, @@ -753,7 +754,7 @@ prefix_list_entry_lookup_val(struct prefix_list *plist, else { struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; *cache = NULL ; /* Not found in cache. */ *result = 0 ; /* Assume found ! */ for (VECTOR_ITEMS(&plist->list, pe, i)) @@ -781,12 +782,12 @@ prefix_list_entry_lookup_val(struct prefix_list *plist, * -- result != 0 not found -- index is immaterial * * */ -static vector_index +static vector_index_t prefix_list_entry_lookup (struct prefix_list* plist, struct prefix_list_entry* pe_seek, int* result) { struct prefix_list_entry* pe_found ; - vector_index i ; + vector_index_t i ; if (pe_seek->flags & PREFIX_SEQ) { @@ -840,7 +841,7 @@ prefix_list_entry_insert(struct prefix_list *plist, { struct prefix_list_entry* pe ; vector cache ; - vector_index i, ic ; + vector_index_t i, ic ; int ret, retc ; u_int32_t mask ; int pl, sh ; @@ -901,7 +902,7 @@ prefix_list_entry_insert(struct prefix_list *plist, if (cache) { /* We need to know where the old value was. */ - vector_index io ; + vector_index_t io ; int reto ; io = vector_bsearch(cache, (vector_bsearch_cmp*)plist->cmp, pe, &reto) ; @@ -1005,7 +1006,7 @@ prefix_list_entry_delete (struct prefix_list *plist, struct prefix_list_entry *pe_seek) { struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; int ret ; i = prefix_list_entry_lookup (plist, pe_seek, &ret) ; @@ -1155,7 +1156,7 @@ static void __attribute__ ((unused)) prefix_list_print (struct prefix_list *plist) { struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; struct vty* vty = NULL ; if (plist == NULL) @@ -1461,7 +1462,7 @@ vty_show_prefix_entry (struct vty *vty, struct prefix_list *plist, if (dtype != summary_display) { struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; int with_seq = pm->seqnum_flag ; int with_stats = (dtype == detail_display) ||(dtype == sequential_display) ; @@ -1507,7 +1508,7 @@ vty_show_prefix_list (struct vty *vty, afi_t afi, const char *name, else { vector extract ; - vector_index i ; + vector_index_t i ; struct symbol* sym ; if (dtype == detail_display || dtype == summary_display) @@ -1542,7 +1543,7 @@ vty_show_prefix_list_prefix (struct vty *vty, afi_t afi, const char *name, { struct prefix_list *plist; struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; struct prefix p; int ret; int match; @@ -1598,7 +1599,7 @@ vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name, int ret; struct prefix p; struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; pm = prefix_master_get (afi); if (pm == NULL) @@ -1609,8 +1610,13 @@ vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name, struct symbol_walker walker ; symbol_walk_start(&pm->table, &walker) ; while ((plist = symbol_get_value(symbol_walk_next(&walker)))) - for (VECTOR_ITEMS(&plist->list, pe, i)) - pe->hitcnt = 0 ; + { + if (plist == NULL) + continue ; + + for (VECTOR_ITEMS(&plist->list, pe, i)) + pe->hitcnt = 0 ; + } ; } else { @@ -2815,7 +2821,7 @@ config_write_prefix_afi (afi_t afi, struct vty *vty) struct prefix_master *pm; int write = 0; vector extract ; - vector_index i, ipe ; + vector_index_t i, ipe ; struct symbol* sym ; pm = prefix_master_get (afi); @@ -2870,7 +2876,7 @@ prefix_bgp_orf_entry (struct stream *s, prefix_list_ref ref, u_char init_flag, u_char permit_flag, u_char deny_flag) { struct prefix_list_entry *pe; - vector_index i ; + vector_index_t i ; struct prefix_list *plist = prefix_list_ref_plist(ref) ; @@ -2895,7 +2901,7 @@ prefix_bgp_orf_entry (struct stream *s, prefix_list_ref ref, * return 0 - no such entry */ int -prefix_bgp_orf_get(struct prefix_list *plist, vector_index i, +prefix_bgp_orf_get(struct prefix_list *plist, vector_index_t i, struct orf_prefix *orfpe, enum prefix_list_type *pe_type) { struct prefix_list_entry *pe = NULL; @@ -2986,7 +2992,7 @@ prefix_bgp_show_prefix_list (struct vty *vty, afi_t afi, char *name) if (vty) { struct prefix_list_entry* pe ; - vector_index i ; + vector_index_t i ; vty_prefix_list_name_count_print(vty, plist, VTY_NEWLINE) ; diff --git a/lib/plist.h b/lib/plist.h index 4f383417..4abddcd0 100644 --- a/lib/plist.h +++ b/lib/plist.h @@ -62,7 +62,7 @@ extern const char* prefix_list_get_name(struct prefix_list* plist) ; extern struct stream * prefix_bgp_orf_entry (struct stream *, prefix_list_ref ref, u_char, u_char, u_char); -extern int prefix_bgp_orf_get(struct prefix_list *plist, vector_index i, +extern int prefix_bgp_orf_get(struct prefix_list *plist, vector_index_t i, struct orf_prefix *orfpe, enum prefix_list_type *pe_type); extern int prefix_bgp_orf_set (char *, afi_t, struct orf_prefix *, int, int); extern void prefix_bgp_orf_remove_all (char *); diff --git a/lib/prefix.c b/lib/prefix.c index f13050e5..8774475a 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -27,6 +27,7 @@ #include "sockunion.h" #include "memory.h" #include "log.h" +#include "tstring.h" /* Maskbit. */ static const u_char maskbit[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, @@ -272,18 +273,19 @@ prefix_ipv4_free (struct prefix_ipv4 *p) int str2prefix_ipv4 (const char *str, struct prefix_ipv4 *p) { - char* pnt ; - char* cp ; - int ret ; - unsigned plen ; + tstring_t(ipv4, 24) ; + char* pnt ; + const char* cp ; + int ret ; + unsigned plen ; pnt = strchr (str, '/'); if (pnt == NULL) { /* No / => simple address */ - plen = IPV4_MAX_BITLEN; - ret = inet_aton (str, &p->prefix); + plen = IPV4_MAX_BITLEN ; + cp = str ; } else { @@ -292,19 +294,18 @@ str2prefix_ipv4 (const char *str, struct prefix_ipv4 *p) if (plen > IPV4_MAX_PREFIXLEN) return 0; - cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1); - strncpy (cp, str, pnt - str); - *(cp + (pnt - str)) = '\0'; - ret = inet_aton (cp, &p->prefix); - XFREE (MTYPE_TMP, cp); - } + cp = tstring_set_n(ipv4, str, (pnt - str)) ; + } ; + ret = inet_aton (cp, &p->prefix); if (ret <= 0) /* should not return < 0, but it would not be valid ! */ return 0; p->family = AF_INET; p->prefixlen = plen; + tstring_free(ipv4) ; + return 1 ; } @@ -434,10 +435,11 @@ prefix_ipv6_free (struct prefix_ipv6 *p) int str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p) { - char* pnt ; - char* cp ; - int ret ; - unsigned plen ; + tstring_t(ipv6, 64) ; + char* pnt ; + const char* cp ; + int ret ; + unsigned plen ; pnt = strchr (str, '/'); @@ -445,7 +447,7 @@ str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p) { /* No / => simple address */ plen = IPV6_MAX_BITLEN; - ret = inet_pton (AF_INET6, str, &p->prefix); + cp = str ; } else { @@ -454,19 +456,18 @@ str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p) if (plen > IPV6_MAX_PREFIXLEN) return 0 ; - cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1); - strncpy (cp, str, pnt - str); - *(cp + (pnt - str)) = '\0'; - ret = inet_pton (AF_INET6, cp, &p->prefix); - XFREE (MTYPE_TMP, cp); - } + cp = tstring_set_n(ipv6, str, (pnt - str)) ; + } ; + ret = inet_pton (AF_INET6, cp, &p->prefix); if (ret <= 0) return 0 ; p->family = AF_INET6; p->prefixlen = plen; + tstring_free(ipv6) ; + return 1 ; } diff --git a/lib/prefix.h b/lib/prefix.h index 74f32e94..d8ed9a8a 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -23,12 +23,9 @@ #ifndef _ZEBRA_PREFIX_H #define _ZEBRA_PREFIX_H +#include "misc.h" #include "sockunion.h" -#ifndef Inline -#define Inline static inline -#endif - typedef const union sockunion* const_sockunion ; /* diff --git a/lib/qafi_safi.h b/lib/qafi_safi.h index 99c86055..c93b741e 100644 --- a/lib/qafi_safi.h +++ b/lib/qafi_safi.h @@ -23,7 +23,7 @@ #ifndef _QUAGGA_AFI_SAFI_H #define _QUAGGA_AFI_SAFI_H -#include <stdint.h> +#include "misc.h" #include "lib/zassert.h" /*============================================================================== diff --git a/lib/qfstring.c b/lib/qfstring.c index 583d729f..3db3fedf 100644 --- a/lib/qfstring.c +++ b/lib/qfstring.c @@ -19,10 +19,8 @@ * Boston, MA 02111-1307, USA. */ -#include <stdbool.h> -#include <stdint.h> - #include "qfstring.h" +#include "zassert.h" /*============================================================================== */ diff --git a/lib/qfstring.h b/lib/qfstring.h index 83caa13d..990b4ef4 100644 --- a/lib/qfstring.h +++ b/lib/qfstring.h @@ -22,21 +22,8 @@ #ifndef _ZEBRA_QFSTRING_H #define _ZEBRA_QFSTRING_H -#include "zebra.h" - -#include <stddef.h> -#include <stdint.h> - -#ifndef Inline -#define Inline static inline -#endif - -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ +#include "misc.h" +#include "vargs.h" /*============================================================================== * These "qfstrings" address the issues of dealing with *fixed* length @@ -68,21 +55,21 @@ enum pf_flags pf_none = 0, /* The following correspond to the "flags" */ - pf_commas = 1 << 0, /* "'" seen */ - pf_plus = 1 << 1, /* "+" seen */ - pf_space = 1 << 2, /* " " seen */ - pf_zeros = 1 << 3, /* "0" seen */ - pf_alt = 1 << 4, /* "#" seen */ + pf_commas = BIT( 0), /* "'" seen */ + pf_plus = BIT( 1), /* "+" seen */ + pf_space = BIT( 2), /* " " seen */ + pf_zeros = BIT( 3), /* "0" seen */ + pf_alt = BIT( 4), /* "#" seen */ - pf_precision = 1 << 7, /* '.' seen */ + pf_precision = BIT( 7), /* '.' seen */ /* The following signal how to render the value */ - pf_hex = 1 << 8, /* hex */ - pf_uc = 1 << 9, /* upper-case */ + pf_hex = BIT( 8), /* hex */ + pf_uc = BIT( 9), /* upper-case */ /* The following signal the type of value */ - pf_ptr = 1 << 14, /* is a pointer */ - pf_unsigned = 1 << 15, /* unsigned value */ + pf_ptr = BIT(14), /* is a pointer */ + pf_unsigned = BIT(15), /* unsigned value */ /* Common combination */ pf_hex_x = pf_unsigned | pf_hex, diff --git a/lib/qiovec.h b/lib/qiovec.h index ee03d2f2..f1498146 100644 --- a/lib/qiovec.h +++ b/lib/qiovec.h @@ -24,13 +24,7 @@ #include "zebra.h" -#include <stddef.h> -#include <stdint.h> -#include <stdbool.h> - -#ifndef Inline -#define Inline static inline -#endif +#include "misc.h" /*============================================================================== * Flexible size "struct iovec" diff --git a/lib/qpath.c b/lib/qpath.c new file mode 100644 index 00000000..f6157b89 --- /dev/null +++ b/lib/qpath.c @@ -0,0 +1,819 @@ +/* Some primitive path handling + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qpath.h" +#include "qstring.h" + +#include "zassert.h" + +/*============================================================================== + * Some primitive path handling, based on qstrings. + * + * + *============================================================================== + * Path Reduction + * + * As per POSIX, multiple '/' count as single '/', except for the very special + * case of exactly two '/' at the start of a path. (That case is referred to + * here as a "double root".) + * + * So this code reduces runs of '/' to single '/' -- except for the special + * case. + * + * This code also replaces "/./" by "/". + * + * + */ + +/*------------------------------------------------------------------------------ + * Initialise a brand new qpath -- allocate if required. + * + * If a path is given, set that path -- allocating body even if path is zero + * length. + * + * If no path is given, leaves qpath with no body. + * + * If path is given, the qpath is set and reduced (see above). + * + * Returns: address of qpath + * + * NB: assumes initialising a new structure. If not, then caller should + * use qpath_reset() or qs_clear(). + */ +extern qpath +qpath_init_new(qpath qp, const char* path) +{ + if (qp == NULL) + qp = XCALLOC(MTYPE_QPATH, sizeof(qpath_t)) ; + else + memset(qp, 0, sizeof(qpath_t)) ; + + /* Worry about fields other than the path */ + + qs_init_new(&qp->path, 0) ; + + if (path != NULL) + qpath_set(qp, path) ; + + return qp ; +} ; + +/*------------------------------------------------------------------------------ + * Reset contents of given qpath, freeing the structure if required. + * + * Discards all the contents of the qpath. + */ +extern qpath +qpath_reset(qpath qp, bool free_structure) +{ + if (qp == NULL) + return NULL ; + + qs_reset_keep(&qp->path, keep_it) ; + + if (free_structure) + XFREE(MTYPE_QPATH, qp) ; /* sets qp = NULL */ + else + /* Worry about fields other than the path */ + ; + + return qp ; +} ; + +/*------------------------------------------------------------------------------ + * Set given qpath to copy of the given string -- allocate if required. + * + * If setting an existing qpath, discards any existing contents -- so the qpath + * MUST have been initialised at some time (qpath_init_new). Keeps any body + * that has been allocated if possible. + * + * Reduces the path (see above). + * + * Sets the path len, but does not touch the path cp. + */ +extern qpath +qpath_set(qpath qp, const char* path) +{ + if (qp == NULL) + qp = qpath_init_new(NULL, path) ; + else + { + if (path != NULL) + qs_set(&qp->path, path) ; + else + qs_clear(&qp->path) ; + /* Worry about fields other than the path */ + } ; + + qpath_reduce(qp) ; + + return qp ; +} ; + +/*------------------------------------------------------------------------------ + * Reduce multiple '/' to single '/' (except for exactly "//" at start). + * + * Reduce "/./" to "/". + */ +static void +qpath_reduce(qpath qp, size_t off) +{ + qpath part ; + qstring qs ; + char* sp ; + char* p ; + char* q ; + + if (qp == NULL) + { + assert(off == 0) ; + return ; /* NULL qpath is empty */ + } ; + + qs = &qp->path ; + assert(off <= qs->len) ; /* Make sure 'off' is kosher */ + + sp = qs_chars(qs) ; /* NULL if qpath is completely empty */ + + if (sp == NULL) + return ; /* NULL path part is completely empty */ + + p = sp + off ; + + /* Deal with special case of "//" at start. + * + * If find "//x", where x is anything other than '/', step past the first + * '/'. Could step past both "//", but that stops it seeing "//./zzz" + */ + if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/')) + ++p ; + + /* Scan to see if there is anything that needs to be fixed. + * + * Looking for "//" and "/./". + */ + while (1) + { + if (*p++ == '\0') + return ; /* nothing to do if hit end of string */ + + if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) ) + { + if (*(p - 1) == '/') + break ; /* found "//" or "/./" */ + } + } ; + + /* Rats... there is something to be fixed. + * + * *p is second '/' of "//" or '.' of "/./". + */ + q = p ; + + while (*p != '\0') + { + /* Step past any number of '/' and any number of "./". */ + while (1) + { + while (*p == '/') + ++p ; + + if ((*p != '.') || (*p != '/')) + break ; + + p += 2 ; + } ; + + /* Scan, copying stuff, until get to '\0' or find "//" or "/./" */ + while (*p != '\0') + { + *q++ = *p++ ; /* copy non-'\0' */ + + if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) ) + { + if (*(p - 1) == '/') + break ; /* found "//" or "/./" */ + } ; + } ; + } ; + + /* Adjust the length and terminate */ + + qs->len = (q - sp) ; /* set the new length (shorter !) */ + *q = '\0' ; /* and terminate */ +} ; + +/*------------------------------------------------------------------------------ + * Make a copy of the given qpath. + * + * Creates a brand new qpath object, which is a full copy of the given one. + * + * The result is an empty qpath if the given one is NULL. + */ +extern qpath +qpath_copy(qpath qp_x) +{ + return qpath_init_new(NULL, qpath_path(qp_x)) ; +} ; + +/*------------------------------------------------------------------------------ + * Make a copy of a qpath to the given qpath. + * + * If required, creates new qpath object -- so qpath_copy_to(NULL, ...) is the + * same as qpath_copy(...). + * + * The result is an empty qpath if the given one is NULL. + */ +extern qpath +qpath_copy_to(qpath qp, qpath qp_x) +{ + return qpath_set(qp, qpath_path(qp_x)) ; +} ; + +/*============================================================================== + * Pop the last part of the given path. + * + * The cases are (where 'a' is anything except '/' and 'z' is anything): + * + * 1. "" -- empty path + * + * - path unchanged + * - return empty part + * + * 2. "aaa" -- the path is a single part + * + * - part removed from path, leaves "" + * - return the part + * + * Note that in (1) and (2) there is no '/', in the following there is at least + * one '/'. + * + * 3. "/" -- root only, or + * "//" double root only + * + * - path unchanged + * - return empty part + * + * 4. "/aaa" -- one remaining part before the root, + * "//aaa" or one remaining part after double route + * + * - part removed from path, leaves "/" or "//" + * - return the part + * + * 5. "zzz/" -- empty last part + * + * - "/" removed from end of path + * - return empty part + * + * 6. "zzz/aaa" -- non-empty last part + * + * - part and "/" removed from end of path + * - return part + * + * Note that other forms of multiple '/' have been reduced, already. + */ +extern qpath +qpath_pop(qpath qp) +{ + qpath part ; + qstring qs ; + char* sp ; + char* p ; + + /* Get pointers to start and end of path */ + if (qp != NULL) + { + qs = &qp->path ; + + sp = qs_chars(qs) ; /* NULL if qpath is completely empty */ + p = qs_ep_char(qs) ; /* points at trailing '\0' */ + } + else + qs = sp = p = NULL ; + + /* Deal with NULL-ness */ + if (sp == NULL) + { + assert(p == sp) ; /* ie qs->len == 0 if qs->body == NULL */ + + return qpath_init_new(NULL, NULL) ; + } ; + + /* Track back to last '/' */ + while ( (p > sp) && (*(p - 1) != '/') ) + --p ; + + /* Construct result which is from p forwards + * + * p == NULL if the given path is completely empty. + */ + part = qpath_init_new(NULL, p) ; + + /* If what remains is not empty, and not just "/" or "//", remove trailing '/' + * + * If p == sp, there was no '/', so path ends up empty. + * If p == sp + 1, there is one '/', and that is at the front of the path + * If p == sp + 2, there is either "//" or "a/" + * If p > sp + 2, there is "aa/" + */ + if (p >= (sp + 2)) /* if "//", "a/" or "aa/" etc. */ + { + /* Unless is special case of "//"... */ + if ( ! ((p == (sp + 2)) && (*sp == '/')) ) + --p ; /* ... discard trailing '/' */ + } ; + + qs->len = (p - sp) ; /* set the new length (shorter !) */ + *p = '\0' ; /* and terminate */ + + /* Return the part we hacked off */ + return part ; +} ; + +/*============================================================================== + * Shift off the first part of the given path. + * + * The cases are (where 'a' is anything except '/' and 'z' is anything): + * + * 1. "" -- empty path + * + * - path unchanged + * - return empty part + * + * 2. "aaa" -- the path is a single part + * + * - part removed from path, leaves "" + * - return the part + * + * Note that in (1) and (2) there is no '/', in the following there is at least + * one '/'. + * + * 3. "/" -- root only, or + * "//" double root + * + * - remove the "/" or "//" -- result is empty + * - return "/" or "//" part + * + * 4. "/azz" -- root followed by stuff, or + * "//zzz" double root followed by stuff. + * + * - remove the "/" or "//" + * - return "/" or "//" part + * + * 5. "aaa/" -- something followed by first '/' which is end of path + * + * - remove upto and including '/' -- result is empty + * - return upto but excluding '/' + * + * 6. "aaa/azz" -- something followed by first '/' followed by something else + * + * - remove upto and including '/' + * - return upto but excluding '/' + * + * Note that other forms of multiple '/' have been reduced, already. + */ +extern qpath +qpath_shift(qpath qp) +{ + qpath part ; + qstring qs ; + char* sp ; + char* p ; + char* q ; + + /* Get pointers to start and end of path */ + if (qp != NULL) + { + qs = &qp->path ; + sp = qs_chars(qs) ; /* NULL if qpath is completely empty */ + } + else + qs = sp = NULL ; + + /* Deal with NULL-ness */ + if (sp == NULL) + return qpath_init_new(NULL, NULL) ; + + p = sp ; + + /* Set p such that sp..p-1 is to be shifted off. + * And q such that q..end-1 is to be kept. + */ + if (*sp == '/') + { + ++p ; /* single root */ + if (*p == '/') + ++p ; /* double root */ + q = p ; + } + else + { + while ((*p != '/') && (*p != '/0')) + ++p ; /* step to '/' or end */ + + if (*p == '/') + q = p + 1 ; /* don't keep '/' */ + else + q = p ; /* keep '\0' ! */ + } ; + + /* Construct qpath for shifted off stuff */ + part = qpath_init_new(NULL, NULL) ; + + /* Hack off the shifted off stuff & '/' if required */ + + + /* Return the part we hacked off */ + return part ; +} ; + +/*------------------------------------------------------------------------------ + * Push one path onto the end of the given path. + * + * If the given path is NULL, creates a new, empty qpath to push onto. + * + * The given path is assumed to be the path to a "directory". An empty + * given path is treated as "the current directory". + * + * If the path to be pushed starts '/' or '~', then it is trimmed, removing + * leading characters upto and including '/' (stopping at '\0' if no '/' found). + * + * If path to be pushed onto is not empty, and does not end '/', then an '/' + * is appended before the path is pushed. + * + * Note that this means: + * + * -- pushing an empty path or one which is just "/", will leave the path + * ending "/" -- unless the given path is empty. + * + * -- cannot create a rooted path by pushing a path onto an empty path. + * + * -- pushing a "homed" path "~...." is assumed to be pushing onto the + * required "home". + * + * The resulting path is reduced (see above). + */ + +extern qpath +qpath_push(qpath qp, qpath qp_a) +{ + return qpath_push_str(qp, qpath_path(qp_a)) ; +} ; + +/*------------------------------------------------------------------------------ + * Push path string onto the end of the given path. + * + * See above for discussion of "push" operation. + */ +extern qpath +qpath_push_str(qpath qp, const char* path) +{ + qstring qs ; + char* ep ; + char* sp ; + size_t len ; + size_t off ; + + if (qp == NULL) + qp = qpath_init_new(NULL, NULL) ; + + qs = &qp->path ; + + /* Trim the path to be pushed: + * + * 1. discard from any leading '~' to the first '/' or to '\0'. + * + * 2. then discard any leading '/' + * + * 3. then establish length of result. + */ + if (path != NULL) + { + if (*path == '~') + do + { + ++path ; + } while ((*path != '/') && (*path != '\0')) ; + + while (*path == '/') + ++path ; /* Step past leading '/' */ + len = strlen(path) ; + } + else + len = 0 ; + + /* Worry about whether need to add a '/' to the path before pushing */ + sp = qs_chars(qs) ; + ep = qs_ep_char(qs) ; /* points at trailing '\0' */ + + if (sp == NULL) + assert(ep == sp) ; /* ie qs->len == 0 if qs->body == NULL */ + + off = qs->len ; /* where new stuff starts */ + + if (ep != sp) + { + if (*(ep - 1) == '/') + --off ; /* step back to the '/' */ + else + { + /* Destination is not empty and does not end '/', so append one. + * + * Note that we ensure there is space for the path which are + * about to push, so at most one allocation required. + */ + qs_need(qs, (ep - sp) + 1 + len) ; + qs_append_n(qs, "/", 1) ; + } ; + } ; + + /* Now push path */ + qs_append_n(qs, path, len) ; + + /* Reduce the new part of the result, and return + * + * Note that the 'off' points at the '/' which precedes the new stuff. + * So will spot "/./" where the new stuff starts "./". + */ + qpath_reduce(qp, off) ; + + return qp ; +} ; + +/*------------------------------------------------------------------------------ + * Join two paths to create a new path. + * + * Copies the destination path and then pushes the other path onto it. + */ +extern qpath +qpath_join(qpath qp, qpath qp_a) +{ + qpath qp_n ; + + qp_n = qpath_copy(qp) ; + return qpath_push(qp_n, qp_a) ; +} ; + +/*------------------------------------------------------------------------------ + * Join path string to the given path to create a new path. + * + * Copies the destination path and then pushes the path string onto it. + */ +extern qpath +qpath_join_str(qpath qp, const char* path) +{ + qpath qp_n ; + + qp_n = qpath_copy(qp) ; + return qpath_push_str(qp_n, path) ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start and end '/' ? + */ +extern bool +qpath_sex(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return qp_empty ; /* NULL qpath is empty */ + + sp = qs_chars(&qp->path) ; + + if ((sp == NULL) || (*sp == '\0')) + return qp_empty ; /* NULL body or just '\0' */ + + if (*sp == '~') + return qp_homed ; + + if (*sp == '/') + { + ++sp ; + if (*sp == '\0') + return qp_root ; + if (*sp != '/') + return qp_absolute ; + + ++sp ; + if (*sp == '\0') + return qp_double_root ; + else + return qp_double_absolute ; + } ; + + + + qp_empty, /* nothing at all */ + qp_relative, /* something, not starting '/' */ + qp_root, /* "/" all on its own */ + qp_absolute, /* something, starting with single '/' */ + qp_homed, /* something, starting with '~' */ + qp_double_root, /* "//" all on its own */ + qp_double_absolute /* something, starting with "//" */ + + + + return ((qp->path).len == 1) && (*sp == '/') ; +} ; + +/*------------------------------------------------------------------------------ + * Is there anything there ? + */ +extern bool +qpath_is_empty(qpath qp) +{ + if (qp == NULL) + return true ; /* NULL qpath is empty */ + + return (qp->path).len == 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Is it: not empty, not starting '/' (or '//') and not starting '~' + */ +extern bool +qpath_is_relative(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath is not relative */ + + sp = qs_chars(&qp->path) ; + + return (sp != NULL) && (*sp != '/') && (*sp != '.') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start and end '/' ? + */ +extern bool +qpath_is_root(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be root */ + + sp = qs_chars(&qp->path) ; + + return ((qp->path).len == 1) && (*sp == '/') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start '/' (and not '//') ? + * + * Note that just "/" (ie root) will return true => it is also absolute. + */ +extern bool +qpath_is_absolute(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be absolute */ + + sp = qs_chars(&qp->path) ; + + return (sp != NULL) && (*sp == '/') && (*(sp + 1) != '/') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start '~' + */ +extern bool +qpath_is_homed(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be homed */ + + sp = qs_chars( &qp->path) ; + + return (sp != NULL) && (*sp == '~') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start and end '/' ? + */ +extern bool +qpath_is_double_root(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be root */ + + sp = qs_chars(&qp->path) ; + + return ((qp->path).len == 2) && (*sp == '/') && (*(sp + 1) == '/') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path start '/' (and not '//') ? + * + * Note that just "/" (ie root) will return true => it is also absolute. + */ +extern bool +qpath_is_double_absolute(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be absolute */ + + sp = qs_chars(&qp->path) ; + + return (sp != NULL) && (*sp == '/') && (*(sp + 1) == '/') ; +} ; + +/*------------------------------------------------------------------------------ + * Does the given path *end* '/', or is it ~aaaa with no '/' at all. + * + * Note that root and double route return directory true. + */ +extern bool +qpath_is_directory(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be directory */ + + sp = qs_chars(&qp->path) ; + + if ((sp == NULL) || (*sp = '\0')) + return false ; /* Empty qpath cannot be directory */ + + ep = qs_ep_char(qs) ; + + if (*(ep - 1) == '/') + return true ; /* Ends '/' */ + + if (*sp == '~') + { + while (*(++sp) != '/') + { + if (*sp == '\0') /* Starts '~' and no '/' found */ + return true ; + } ; + } ; + + return false ; +} ; + +/*------------------------------------------------------------------------------ + * Is the given path an atom + * + * Note that root and double route are . + */ +extern bool +qpath_is_atom(qpath qp) +{ + char* sp ; + + if (qp == NULL) + return false ; /* NULL qpath cannot be directory */ + + sp = qs_chars(&qp->path) ; + + if ((sp == NULL) || (*sp = '\0')) + return false ; /* Empty qpath cannot be directory */ + + ep = qs_ep_char(qs) ; + + if (*(ep - 1) == '/') + return true ; /* Ends '/' */ + + if (*sp == '~') + { + while (*(++sp) != '/') + { + if (*sp == '\0') /* Starts '~' and no '/' found */ + return true ; + } ; + } ; + + return false ; +} ; + diff --git a/lib/qpath.h b/lib/qpath.h new file mode 100644 index 00000000..27c61dff --- /dev/null +++ b/lib/qpath.h @@ -0,0 +1,124 @@ +/* Some primitive path 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_QPATH_H +#define _ZEBRA_QPATH_H + +#include "zebra.h" +#include "misc.h" +#include "memory.h" + + + + + + + +/*============================================================================== + * For these purposes a path is "parts" separated by one more '/' characters. + * + * As per POSIX, a pair of leading "//" (not three or more leading '/') is + * very special. + * + * The following sexes of qpath are established after any "path reduction" + * has been done. Path reduction removes extraneous '/'. + */ + +typedef enum qpath_sex qpath_sex_t ; + +enum qpath_sex +{ + qp_empty, /* nothing at all */ + qp_relative, /* something, not starting '/' */ + qp_root, /* "/" all on its own */ + qp_absolute, /* something, starting with single '/' */ + qp_homed, /* something, starting with '~' */ + qp_double_root, /* "//" all on its own */ + qp_double_absolute /* something, starting with "//" */ +} ; + +typedef struct qpath qpath_t ; +typedef struct qpath* qpath ; + +/* The qpath structure is largely a qstring, but one in which there is always + * a body, even if it only contains "\0", and the len is kept up to date. + */ +struct qpath +{ + qstring_t path ; +} ; + +/*============================================================================== + * Functions + */ + +extern qpath +qpath_init_new(qpath qp, const char* path) ; + +extern qpath +qpath_reset(qpath qp, bool free_structure) ; + +Inline qpath +qpath_reset_free(qpath qp) { qpath_reset(qp, true) ; } ; + +Inline qpath +qpath_reset_keep(qpath qp) { qpath_reset(qp, false) ; } ; + +Inline const char* +qpath_path(qpath qp) ; + +extern qpath +qpath_trim(qpath qp) ; + +extern qpath +qpath_copy(qpath qp) ; + +extern qpath +qpath_pop(qpath qp) ; + +extern qpath +qpath_push(qpath qp, qpath qp_a) ; + +extern qpath +qpath_join(qpath qp, qpath qp_a) ; + +/*============================================================================== + * Inline stuff + */ + +/*------------------------------------------------------------------------------ + * Get *temporary* pointer to actual path contained in the given qpath. + * + * This is *temporary* to the extent that when the qpath is changed or freed, + * this pointer will be INVALID -- you have been warned. + * + * This is a *const* pointer. + * + * For a NULL qpath, or an empty qpath, returns pointer to an empty string + * ('\0' terminated ""). + */ +Inline const char* +qpath_path(qpath qp) +{ + return qs_string(qp != NULL ? &qp->path : qp) ; +} ; + +#endif /* _ZEBRA_QPATH_H */ diff --git a/lib/qpnexus.h b/lib/qpnexus.h index 0cf5a824..03b52876 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -22,7 +22,7 @@ #ifndef _ZEBRA_QPNEXUS_H #define _ZEBRA_QPNEXUS_H -#include <stdint.h> +#include "misc.h" #include <time.h> #include <pthread.h> #include <unistd.h> @@ -34,10 +34,6 @@ #include "mqueue.h" #include "qpselect.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Quagga Nexus Interface -- qpn_xxxx * diff --git a/lib/qpselect.c b/lib/qpselect.c index fd20d421..c15f112e 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -683,7 +683,7 @@ static qps_file qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert) { qps_file qf ; - vector_index i ; + vector_index_t i ; int ret ; dassert((fd >= 0) && (fd < (int)FD_SETSIZE)) ; @@ -772,9 +772,9 @@ qps_file_remove(qps_selection qps, qps_file qf) { qps_file qf_last ; int ret ; - vector_index i = vector_bsearch(&qps->files, - (vector_bsearch_cmp*)qps_fd_cmp, - &qf->fd, &ret) ; + vector_index_t 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 @@ -1279,9 +1279,9 @@ qps_selection_validate(qps_selection qps) int enabled_count[qps_mnum_count] ; fd_full_set enabled ; - qps_file qf ; - int fd, n, mnum, p_mnum ; - vector_index i ; + qps_file qf ; + int fd, n, mnum, p_mnum ; + vector_index_t i ; /* 1..4) Run down the selection vector and check. */ /* Collect new enabled_count and enabled bit vectors. */ diff --git a/lib/qpselect.h b/lib/qpselect.h index 8901ea36..48c86fe8 100644 --- a/lib/qpselect.h +++ b/lib/qpselect.h @@ -25,14 +25,11 @@ #include <sys/select.h> #include <errno.h> +#include "misc.h" #include "zassert.h" #include "qtime.h" #include "vector.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Quagga pselect -- qps_xxxx * diff --git a/lib/qpthreads.h b/lib/qpthreads.h index d73182ef..959194ad 100644 --- a/lib/qpthreads.h +++ b/lib/qpthreads.h @@ -22,24 +22,15 @@ #ifndef _ZEBRA_QPTHREADS_H #define _ZEBRA_QPTHREADS_H -#include <stdint.h> +#include "misc.h" #include <time.h> #include <pthread.h> #include <unistd.h> #include <errno.h> -#include <stdbool.h> #include "zassert.h" #include "qtime.h" -#ifndef Inline -#define Inline static inline -#endif - -#ifndef private -#define private extern -#endif - /*============================================================================== * Quagga Pthread Interface -- qpt_xxxx * @@ -133,13 +124,13 @@ qpt_thread_join(qpt_thread_t thread_id) ; /*============================================================================== * qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION ! */ -private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */ -private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */ +Private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */ +Private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */ -private int +Private int qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */ -private int +Private int qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */ /*============================================================================== diff --git a/lib/qstring.c b/lib/qstring.c index 5ca4d868..c7e5b81c 100644 --- a/lib/qstring.c +++ b/lib/qstring.c @@ -19,6 +19,9 @@ * Boston, MA 02111-1307, USA. */ +#include <stdio.h> +#include <ctype.h> + #include "qstring.h" #include "memory.h" @@ -28,131 +31,57 @@ */ /*------------------------------------------------------------------------------ - * Initialise qstring -- allocate if required. - * - * If non-zero len is given, a body is allocated (for at least len + 1). - * - * Returns: address of qstring - * - * NB: assumes initialising a new structure. If not, then caller should - * use qs_reset() or qs_clear(). + * Create a new, empty qs */ extern qstring -qs_init_new(qstring qs, size_t len) +qs_new(void) { - if (qs == NULL) - qs = qs_new() ; - else - memset(qs, 0, sizeof(qstring_t)) ; /* see qs_new() */ - - if (len != 0) - return qs_make_to_length(qs, len) ; - - return qs ; + /* zeroising sets a completely empty qstring -- see qs_init_new() */ + return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; } ; /*------------------------------------------------------------------------------ - * Allocate or reallocate so that string is big enough for the given length. + * Initialise qstring -- allocate if required. * - * Allocate qstring if required. Returns with a body with size > 0. + * If non-zero slen is given, a body is allocated (for at least slen + 1). + * If zero slen is given, no body is allocated. * - * Allocates to 16 byte boundaries with space for '\0' beyond given length. + * Sets qs->len = qs->cp = 0. '\0' terminates body if allocates one. * * Returns: address of qstring * - * NB: allocates new body if the size == 0. - * - * If the qstring is a "dummy", its contents are now copied to the new - * real qstring body -- up to a maximum of the new length. + * NB: assumes initialising a new structure. If not, then caller should + * use qs_reset() or qs_clear(). */ extern qstring -qs_make_to_length(qstring qs, size_t len) +qs_init_new(qstring qs, usize slen) { - size_t size = (len + 0x10) & ~(size_t)(0x10 - 1) ; - if (qs == NULL) qs = qs_new() ; + else + memset(qs, 0, sizeof(qstring_t)) ; - if (size > qs->size) - { - if (qs->size == 0) - { - void* old ; - old = qs->body ; - - qs->size = size ; - qs->body = XMALLOC(MTYPE_QSTRING_BODY, qs->size) ; - - if ((qs->len != 0) && (old != NULL)) - memcpy(qs->body, old, (qs->len <= len) ? qs->len : len) ; - } - else - { - qs->size *= 2 ; - if (qs->size < size) - qs->size = size ; - qs->body = XREALLOC(MTYPE_QSTRING_BODY, qs->body, qs->size) ; - } ; - }; - - return qs ; -} ; - -/*------------------------------------------------------------------------------ - * Add 'n' to the current string length, allocating or extending the body as - * required. - * - * Allocate qstring if required. Returns with a body with size > 0. - * - * Allocates to 16 byte boundaries with space for '\0' beyond new length. - * - * Returns: address of qstring - * - * also: sets char** p_ep to point at the *end* of the new len. - * - * NB: allocates new body if the size == 0. - * - * If the qstring is a "dummy", its contents are now copied to the new - * real qstring body -- up to a maximum of the new length. - */ -extern qstring -qs_add_len(qstring qs, size_t n, char** p_ep) -{ - size_t len ; - len = (qs != NULL) ? qs->len + n : n ; - - qs = qs_make_to_length(qs, len) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; - qs->len = len ; + /* Zeroising has set: + * + * body = NULL -- no body + * size = 0 -- no body + * + * len = 0 + * cp = 0 + * + * b_body = NULL -- no body buffer + * b_size = 0 -- no body buffer + */ - *p_ep = (char*)qs->body + len ; + if (slen != 0) + qs_make_to_size(qs, slen, false) ; return qs ; } ; /*------------------------------------------------------------------------------ - * Free body of qstring -- zeroise size, len and cp - * - * Does nothing if qstring is NULL - * - * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain - * the old body. - */ -extern void -qs_free_body(qstring qs) -{ - if (qs != NULL) - { - if (qs->size != 0) - XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ - - qs->size = 0 ; - qs->len = 0 ; - qs->cp = 0 ; - } ; -} ; - -/*------------------------------------------------------------------------------ * Reset qstring -- free body and, if required, free the structure. * * If not freeing the structure, zeroise size, len and cp -- qs_free_body() @@ -164,110 +93,97 @@ qs_free_body(qstring qs) * the old body. */ extern qstring -qs_reset(qstring qs, int free_structure) +qs_reset(qstring qs, free_keep_b free_structure) { if (qs != NULL) { - if (qs->size != 0) - XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + if (qs->b_body != NULL) + XFREE(MTYPE_STRING, qs->b_body) ; /* sets qs->b_body = NULL */ if (free_structure) - XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ + XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ else - { - qs->size = 0 ; - qs->len = 0 ; - qs->cp = 0 ; - } ; + memset(qs, 0, sizeof(qstring_t)) ; /* see qs_init_new */ } ; return qs ; } ; -/*============================================================================== - * printf() and vprintf() type functions - */ - /*------------------------------------------------------------------------------ - * Formatted print to qstring -- cf printf() + * Allocate or reallocate body so that is big enough for the given "slen". * - * Allocate qstring if required. + * If the qstring is currently an alias, copies all of the alias to a new + * body -- so always returns a non-alias qstring. * - * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) - */ -extern qstring -qs_printf(qstring qs, const char* format, ...) -{ - va_list args; - - va_start (args, format); - qs = qs_vprintf(qs, format, args); - va_end (args); - - return qs; -} ; - -/*------------------------------------------------------------------------------ - * Formatted print to qstring -- cf vprintf() + * Returns with a body with size > 0. Allocates to 16 byte boundaries with + * space for '\0' beyond given length. * - * Allocate qstring if required. + * Does NOT affect qs->len or qs->cp. Does NOT re-terminate. * - * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) + * NB: will allocate a new body even if the slen == 0. + * + * NB: always copies all of any aliased string (even if the slen == 0). + * + * NB: sets terminated false */ -extern qstring -qs_vprintf(qstring qs, const char *format, va_list args) +Private void +qs_make_to_size(qstring qs, usize slen, bool keep) { - va_list ac ; - int len ; - qstring qqs ; - - qqs = qs ; - if (qs == NULL) - qs = qs_new() ; + usize size ; + usize alen ; - while (1) + /* Worry about alias. If we keep it, we keep all of it. */ + if (keep && (qs->size == 0)) { - /* Note that vsnprintf() returns the length of what it would like to have - * produced, if it had the space. That length does not include the - * trailing '\0'. - * - * Also note that given a zero length the string address may be NULL, and - * the result is still the length required. - */ - va_copy(ac, args); - qs->len = len = vsnprintf (qs->body, qs->size, format, ac) ; - va_end(ac); - - if (len < 0) - break ; + alen = qs_len_nn(qs) ; /* alias stuff to keep */ + if (slen <= alen) + slen = alen + 1 ; /* making sure can do that. */ + } + else + alen = 0 ; /* no alias stuff to keep */ - if (len < (int)qs->size) - return qs ; + /* Calculate the new size -- multiple of 16, >= 16. */ + size = (slen + 0x10) & ~(usize)(0x10 - 1) ; + dassert(size != 0) ; - qs_make_to_length(qs, len) ; + /* If that requires the body to be extended, do that now */ + if (size > qs->b_size) + { + /* Need to allocate or extend the buffer. + * + * If current size is not zero, extend by doubling size or making at + * least the multiple of 16 calculated for new len. + */ + qs->b_size *= 2 ; + if (qs->b_size < size) + qs->b_size = size ; + qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ; } ; - if (qqs == NULL) - qs_reset_free(qs) ; /* discard what was allocated */ - else - qs->len = 0 ; + /* If this is a non-empty alias, copy all or part of it. */ + if (alen != 0) + memcpy(qs->b_body, qs_body_nn(qs), alen) ; + + /* Update body and size, and no longer known to be terminated */ + qs_set_body_nn(qs, qs->b_body) ; + qs->size = qs->b_size ; + - return NULL ; } ; /*============================================================================== - * Other operations + * Setting value of qstring */ /*------------------------------------------------------------------------------ * Set qstring to be copy of the given string. * - * Allocates a qstring, if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Sets qs->len to the length of the string (excluding trailing '\0') + * Allocates a body and copies src to it, adding '\0'. Treats src == NULL as + * an empty string. * - * NB: if src == NULL, sets qstring to be zero length string. + * Sets qs->len to the length of the string (excluding trailing '\0'). + * Sets qs->cp == 0. * * Returns: address of the qstring copied to. * @@ -276,101 +192,97 @@ qs_vprintf(qstring qs, const char *format, va_list args) extern qstring qs_set(qstring qs, const char* src) { - qs = qs_set_len(qs, (src != NULL) ? strlen(src) : 0) ; - if (qs->len != 0) - memcpy(qs->body, src, qs->len + 1) ; - else - *((char*)qs->body) = '\0' ; - - return qs ; + return qs_set_n(qs, src, (src != NULL ? strlen(src) : 0)) ; } ; /*------------------------------------------------------------------------------ * Set qstring to be leading 'n' bytes of given string. * - * Allocates qstring if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Inserts '\0' terminator after the 'n' bytes copied. + * Allocates a body and copies 'n' bytes from src to it, adding '\0'. The + * src pointer is ignored if n == 0. + * + * Sets qs->len to the length of the string (excluding trailing '\0'). + * Sets qs->cp == 0. * * Returns: address of the qstring copied to. * - * NB: src string MUST be at least 'n' bytes long. + * NB: if n == 0, src may be NULL * - * NB: src may not be NULL unless n == 0. + * NB: if n > 0, src string MUST be at least 'n' bytes long. * * NB: if copying to a dummy qstring, the old body is simply discarded. */ extern qstring -qs_set_n(qstring qs, const char* src, size_t n) +qs_set_n(qstring qs, const char* src, usize len) { - qs = qs_set_len(qs, n) ; /* ensures have body > n */ - if (n != 0) - memcpy(qs->body, src, n) ; + char* p ; + + qs = qs_new_len(qs, len) ; /* ensures have body > n */ + + p = qs_char_nn(qs) ; + + if (len != 0) + memcpy(p, src, len) ; + + *(p + len) = '\0' ; - *((char*)qs->body + n) = '\0' ; + qs_set_term_nn(qs, true) ; + qs->cp = 0 ; return qs ; } ; /*------------------------------------------------------------------------------ - * Append given string to a qstring. + * Append given string to a qstring -- adding at qs->len position. * - * Allocates a qstring, if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Sets qs->len to the length of the result (excluding trailing '\0') + * Allocates or extends the body and copies bytes from src to it, adding '\0'. + * Treats src == NULL as an empty string. * - * NB: if src == NULL, appends nothing -- but result will be '\0' terminated. + * Sets qs->len to the length of the result (excluding trailing '\0') + * Does not change qs->cp. * - * Returns: address of the qstring copied to. + * Returns: address of the qstring appended to. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * NB: if appending to a dummy qstring, the old body is copied first. */ extern qstring qs_append(qstring qs, const char* src) { - size_t n ; - char* ep ; - - n = (src != NULL) ? strlen(src) : 0 ; - - qs = qs_add_len(qs, n, &ep) ; - ep = (char*)qs->body + qs->len ; - - if (n != 0) - memcpy(ep - n, src, n + 1) ; - else - *ep = '\0' ; - - return qs ; + return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; } ; /*------------------------------------------------------------------------------ - * Set qstring to be leading 'n' bytes of given string. + * Append leading 'n' bytes of given string to a qstring -- adding at qs->len + * position. * - * Allocates qstring if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Returns: address of the qstring copied to. + * Allocates or extends the body and copies 'n' bytes from src to it, + * adding '\0'. The src pointer is ignored if n == 0. * - * NB: src string MUST be at least 'n' bytes long. + * Sets qs->len to the length of the result (excluding trailing '\0') + * Does not change qs->cp. * - * NB: src may not be NULL unless n == 0. + * Returns: address of the qstring appended to. * - * NB: if n == 0, appends nothing -- but result will be '\0' terminated. + * NB: if n == 0, src may be NULL * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * NB: if n > 0, src string MUST be at least 'n' bytes long. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * NB: if appending to a dummy qstring, the old body is copied first. */ extern qstring -qs_append_n(qstring qs, const char* src, size_t n) +qs_append_n(qstring qs, const char* src, usize n) { char* ep ; qs = qs_add_len(qs, n, &ep) ; if (n != 0) - memcpy(ep - n, src, n) ; - - *ep = '\0' ; + memcpy(ep, src, n) ; return qs ; } ; @@ -398,7 +310,7 @@ qs_append_n(qstring qs, const char* src, size_t n) extern qstring qs_copy(qstring dst, qstring src) { - size_t n ; + usize n ; if (src == NULL) { @@ -417,17 +329,263 @@ qs_copy(qstring dst, qstring src) dst->cp = src->cp ; } ; - qs_set_len(dst, n) ; + qs_set_len(dst, n) ; /* TODO: Copies alias !! */ if (n > 0) - memcpy(dst->body, src->body, n) ; + memcpy(dst->s.body, src->s.body, n) ; - *((char*)dst->body + n) = '\0' ; + *(dst->s.char_body + n) = '\0' ; return dst ; } ; /*------------------------------------------------------------------------------ + * Construct a qstring which is an alias for the given string. + * + * Allocates a qstring if required. + * + * Given string must be '\0' terminated. + * + * Does NOT copy the given string, but sets the qstring to be a pointer to it. + * + * NB: it is the caller's responsibility to ensure that the original string + * stays put for however long the qstring is an alias for it. + * + * It is also the caller's responsibility to see that the original string + * is discarded as required (once the alias is no longer required.) + * + * NB: if the qstring is changed in any way, a copy of the aliased string will + * be made first. + * + * NB: if a pointer to the body of the qstring is taken, then while that is in + * use, the qstring must not be released, so that the alias is not + * released. + * + * Returns: the address of the qstring. + */ +extern qstring +qs_set_alias(qstring qs, const char* src) +{ + if (qs == NULL) + qs = qs_init_new(NULL, 0) ; + + /* Make the alias. Note that any existing b_body and b_size are preserved, + * so that any current body can be reused at a later date. + */ + qs->s.const_body = (src != NULL) ? src : "" ; + qs->len = strlen(src) ; + qs->cp = 0 ; + qs->size = 0 ; /* <=> this is an alias ! */ + qs->terminated = true ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Construct a qstring which is an alias for the 'n' given characters. + * + * Allocates a qstring if required. + * + * Given characters are assumed not to be '\0' terminated. + * + * Does NOT copy the given characters, but sets the qstring to be a pointer to + * them. + * + * NB: it is the caller's responsibility to ensure that the original characters + * stays put for however long the qstring is an alias for them. + * + * It is also the caller's responsibility to see that the original + * characters are discarded as required (once the alias is no longer + * required.) + * + * NB: if the qstring is changed in any way, a copy of the aliased characters + * will be made first. + * + * NB: if a pointer to the body of the qstring is taken, then while that is in + * use, the qstring must not be released, so that the alias is not + * released. + * + * Returns: the address of the qstring. + */ +extern qstring +qs_set_alias_n(qstring qs, const char* src, usize len) +{ + if (qs == NULL) + qs = qs_init_new(NULL, 0) ; + + if (len == 0) + src = "" ; + else + assert(src != NULL) ; + + /* Make the alias. Note that any existing b_body and b_size are preserved, + * so that any current body can be reused at a later date. + */ + qs->s.const_body = src ; + qs->len = len ; + qs->cp = 0 ; + qs->size = 0 ; /* <=> this is an alias ! */ + qs->terminated = false ; + + return qs ; +} ; + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------------ + * Add 'n' to the current string length, allocating or extending the body. + * + * Allocate qstring if required (setting qs->len = qs->cp = 0). + * + * Returns with a body with size > 0. Allocates to 16 byte boundaries with + * space for '\0' beyond given length. + * + * Does NOT affect qs->cp. + * Does set the new qs->len -- qs->len += n + * Does NOT reterminate. + * + * Returns: address of qstring + * + * also: sets char** p_ep to point at the *end* of the old len. + * + * NB: will allocate a new body even if the new len == 0. + * + * NB: always copies all of any aliased string (even if the slen == 0). + */ +extern qstring +qs_add_len(qstring qs, usize n, char** p_ep) +{ + usize slen ; + usize len ; + + len = qs_len(qs) ; + slen = len + n ; + + /* Set the new length -- creating if required. + * + * Will always return with a body and no longer an alias (if was one). + */ + qs = qs_set_len(qs, slen) ; + + /* Set pointer to old end (len) position. */ + *p_ep = ((char*)qs_body_nn(qs)) + len ; + + return qs ; +} ; + +/*============================================================================== + * printf() and vprintf() type functions + */ + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf printf() + * + * Allocate qstring if required (setting qs->len = qs->cp = 0). + * + * If OK: + * + * Sets qs->len to the length of the null terminated result. + * Does NOT affect qs->cp. + * + * If fails: + * + * Sets qs->len = qs->cp = 0 and terminates to zero length. + * + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) -- qstring set empty. + */ +extern qstring +qs_printf(qstring qs, const char* format, ...) +{ + va_list args; + + va_start (args, format); + qs = qs_vprintf(qs, format, args); + va_end (args); + + return qs; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf vprintf() + * + * Allocate qstring if required (setting qs->len = qs->cp = 0). + * + * If OK: + * + * Sets qs->len to the length of the null terminated result. + * Does NOT affect qs->cp. + * + * If fails: + * + * Sets qs->len = qs->cp = 0 and terminates to zero length. + * + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) + */ +extern qstring +qs_vprintf(qstring qs, const char *format, va_list args) +{ + va_list ac ; + int slen ; + qstring qqs ; + + qqs = qs ; + if (qs == NULL) + qs = qs_new() ; /* sets size == 0 */ + else + qs_set_len_nn(qs, 0) ; /* Forget current contents */ + + while (1) + { + /* Note that vsnprintf() returns the length of what it would like to have + * produced, if it had the space. That length does not include the + * trailing '\0'. + * + * Also note that given a zero length the string address may be NULL, and + * the result is still the length required. + */ + va_copy(ac, args); + slen = vsnprintf (qs_body_nn(qs), qs->size, format, ac) ; + va_end(ac); + + if (slen < 0) + break ; /* Quit if failed */ + + if ((usize)slen < qs->size) + { + qs_set_len_nn(qs, slen) ; + return qs ; /* Exit if succeeded */ + } ; + + qs_make_to_size(qs, slen) ; /* Extend body to required len */ + } ; + + if (qqs == NULL) + qs_reset(qs, free_it) ; /* discard what was allocated */ + else + qs_clear(qs) ; + + return NULL ; +} ; + +/*============================================================================== + * Other operations + */ + +/*------------------------------------------------------------------------------ * Compare significant parts of two qstrings. * * By significant, mean excluding leading/trailing isspace() and treating @@ -455,7 +613,7 @@ qs_cmp_sig(qstring a, qstring b) */ if (a != NULL) { - p_a = a->body ; + p_a = a->s.uchar_body ; e_a = p_a + a->len ; while ((p_a < e_a) && isspace(*p_a)) @@ -471,7 +629,7 @@ qs_cmp_sig(qstring a, qstring b) if (b != NULL) { - p_b = b->body ; + p_b = b->s.uchar_body ; e_b = p_b + b->len ; while ((p_b < e_b) && isspace(*p_b)) @@ -509,3 +667,152 @@ qs_cmp_sig(qstring a, qstring b) else return 0 ; } ; + +/*------------------------------------------------------------------------------ + * Insert 'n' bytes at 'cp' -- moves anything cp..len up. + * + * Increases 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + * + * NB: if this is a aliased qstring, a copy is made of the original body. + */ +extern usize +qs_insert(qstring qs, const void* src, usize n) +{ + usize after ; + usize len ; + char* p ; + + qs->terminated = false ; /* NB: require qs != NULL ! */ + + len = qs_len_nn(qs) ; + if (len < qs->cp) /* make len = max(len, cp) ! */ + len = qs->cp ; + + after = len - qs->cp ; + + qs_set_len(qs, len + n) ; /* set len and ensure have space + Makes copy of any aliased string. */ + p = qs_cp_char(qs) ; + if (after > 0) + memmove (p + n, p, after) ; + + if (n > 0) + memmove(p, src, n) ; + + return after ; +} ; + +/*------------------------------------------------------------------------------ + * Replace 'n' bytes at 'cp' -- extending if required. + * + * May increase 'len'. but does not affect 'cp'. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + * + * NB: if this is a aliased qstring, a copy is made of the original body. + */ +extern void +qs_replace(qstring qs, const void* src, usize n) +{ + usize len ; + + qs->terminated = false ; /* NB: require qs != NULL ! */ + + len = qs_len_nn(qs) ; + if (len < (qs->cp + n)) /* make len = max(len, cp + n) */ + len = qs->cp + n ; + + qs_set_len(qs, len) ; /* set len and ensure have space. + Makes copy of any aliased string. */ + + if (n > 0) + memmove(qs_cp_char(qs), src, n) ; +} ; + +/*------------------------------------------------------------------------------ + * Remove 'n' bytes at 'cp' -- extending if required. + * + * May change 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: qstring MUST NOT be NULL + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + * + * NB: if this is a aliased qstring, a copy is made of the original body. + */ +extern usize +qs_delete(qstring qs, usize n) +{ + usize after ; + char* p ; + usize len ; + + qs->terminated = false ; /* NB: require qs != NULL ! */ + + len = qs_len_nn(qs) ; + + /* If deleting to or beyond 'len', force len to cp */ + if ((qs->cp + n) >= len) + { + len = qs->cp ; + qs_set_len_nn(qs, len) ; /* truncate now, so that if this is an + aliased string, only copy what is + going to be kept. */ + after = 0 ; /* nothing to move */ + } + else + after = len - (qs->cp + n) ; + + qs_set_len(qs, len) ; /* set len and ensure have space. + Makes copy of any aliased string. */ + + + + /* Watch out for "dummy" */ + if (qs->size == 0) + qs_make_to_size(qs, len) ; + + /* If deleting up to or beyond len, then simply set len == cp + * note that this may reduce or increase len ! + */ + if ((qs->cp + n) >= len) + { + if (qs->cp < len) + qs_set_len_nn(qs, qs->cp) ; /* discard stuff after qs->cp */ + + qs_set_len(qs, qs->cp) ; /* set len */ + return 0 ; /* nothing after */ + } + + /* There is at least one byte after cp (so body must exist) */ + after = len - (qs->cp + n) ; + + if (n > 0) + { + p = qs_cp_char(qs) ; + memmove (p, p + n, after) ; + + qs_set_len_nn(qs, len - n) ; + } ; + + return after ; +} ; diff --git a/lib/qstring.h b/lib/qstring.h index 0597eda8..5b1d4932 100644 --- a/lib/qstring.h +++ b/lib/qstring.h @@ -22,23 +22,11 @@ #ifndef _ZEBRA_QSTRING_H #define _ZEBRA_QSTRING_H -#include "zebra.h" - -#include <stddef.h> -#include <stdint.h> - +#include "misc.h" +#include "vargs.h" +#include "zassert.h" #include "memory.h" - -#ifndef Inline -#define Inline static inline -#endif - -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ +#include "elstring.h" /*============================================================================== * These "qstrings" address address the lack of a flexible length string in 'C'. @@ -49,24 +37,32 @@ * * The caller does, however, have to explicitly release the contents of a * qstring when it is done with. + * + * + * */ +struct qstring +{ + elstring_t els ; /* *embedded* */ + + usize size ; /* of the els body */ -typedef struct qstring qstring_t ; + usize cp ; + + usize b_size ; + void* b_body ; +} ; + +typedef struct qstring qstring_t[1] ; typedef struct qstring* qstring ; -struct qstring +/* Setting an qstring object to all zeros is enough to initialise it to + * an empty string -- including the embedded elstring. + */ +CONFIRM(ELSTRING_INIT_ALL_ZEROS) ; +enum { - union - { - void* body ; - const void* const_body ; - char* char_body ; - unsigned char* uchar_body ; - } ; - size_t size ; - - size_t len ; - size_t cp ; + QSTRING_INIT_ALL_ZEROS = true } ; /*------------------------------------------------------------------------------ @@ -75,92 +71,139 @@ struct qstring * NB: if the body has not yet been allocated, these functions will return * NULL or NULL + the offset. */ -Inline char* /* pointer to body of qstring */ -qs_chars(qstring qs) +Inline elstring +qs_els_nn(qstring qs) { - return (char*)qs->body ; + return qs->els ; } ; -Inline unsigned char* /* pointer to body of qstring */ -qs_bytes(qstring qs) +Inline elstring +qs_els(qstring qs) { - return (unsigned char*)qs->body ; + return qs->els ; } ; -Inline char* /* pointer to given offset in qstring */ -qs_chars_at(qstring qs, size_t off) +Inline void* +qs_body_nn(qstring qs) /* pointer to body of qstring (not NULL) */ { - return qs_chars(qs) + off ; + return els_body_nn(qs->els) ; } ; -Inline unsigned char* /* pointer to given offset in qstring */ -qs_bytes_at(qstring qs, size_t off) +Inline void* /* pointer to body of qstring */ +qs_body(qstring qs) { - return qs_bytes(qs) + off ; + return (qs != NULL) ? qs_body_nn(qs) : NULL ; } ; -Inline char* /* pointer to 'cp' offset in qstring */ -qs_cp_char(qstring qs) +Inline void /* set pointer to body of qstring (not NULL) */ +qs_set_body_nn(qstring qs, const void* body) +{ + els_set_body_nn(qs->els, body) ; /* sets term = fase */ +} ; + +Inline ulen /* length of qstring (not NULL) */ +qs_len_nn(qstring qs) +{ + return els_len_nn(qs->els) ; +} ; + +Inline ulen /* length of qstring */ +qs_len(qstring qs) +{ + return (qs != NULL) ? qs_len_nn(qs) : 0 ; +} ; + +Inline void /* set length of qstring (not NULL) */ +qs_do_set_len_nn(qstring qs, ulen len) +{ + els_set_len_nn(qs->els, len) ; /* sets term = false */ +} ; + +Inline ulen /* cp of qstring (not NULL) */ +qs_cp_nn(qstring qs) +{ + return qs->cp ; +} ; + +Inline ulen /* cp of qstring */ +qs_cp(qstring qs) +{ + return (qs != NULL) ? qs_cp_nn(qs) : 0 ; +} ; + +Inline void /* set cp of qstring (not NULL) */ +qs_do_set_cp_nn(qstring qs, ulen cp) +{ + qs->cp = cp ; +} ; + +Inline char* /* pointer to given offset in qstring */ +qs_char_at_nn(qstring qs, usize off) +{ + char* p ; + p = qs_body_nn(qs) ; + return (p != NULL) ? p + off : NULL ; +} ; + +Inline char* /* pointer to given offset in qstring */ +qs_char_at(qstring qs, usize off) { - return qs_chars_at(qs, qs->cp) ; + return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ; } ; -Inline unsigned char* /* pointer to 'cp' offset in qstring */ -qs_cp_byte(qstring qs) +Inline char* /* pointer to 'cp' offset in qstring */ +qs_cp_char(qstring qs) { - return qs_bytes_at(qs, qs->cp) ; + return (qs != NULL) ? qs_char_at_nn(qs, qs_cp_nn(qs)) : NULL ; } ; -Inline char* /* pointer to 'len' offset in qstring */ +Inline char* /* pointer to 'len' offset in qstring */ qs_ep_char(qstring qs) { - return qs_chars_at(qs, qs->len) ; + return (qs != NULL) ? qs_char_at_nn(qs, qs_len_nn(qs)) : NULL ; +} ; + +Inline bool /* whether qstring is known to be terminated */ +qs_term_nn(qstring qs) +{ + return els_term_nn(qs->els) ; } ; -Inline unsigned char* /* pointer to 'len' offset in qstring */ -qs_ep_byte(qstring qs) +Inline void /* set qstring is known to be terminated */ +qs_set_term_nn(qstring qs, bool how) { - return qs_bytes_at(qs, qs->len) ; + return els_set_term_nn(qs->els, how) ; } ; /*============================================================================== * Functions */ -extern qstring qs_init_new(qstring qs, size_t len) ; -extern qstring qs_make_to_length(qstring qs, size_t len) ; -extern void qs_free_body(qstring qs) ; -extern qstring qs_reset(qstring qs, int free_structure) ; +extern qstring qs_new(void) ; +extern qstring qs_init_new(qstring qs, usize len) ; +extern qstring qs_reset(qstring qs, free_keep_b free_structure) ; -#define qs_reset_keep(qs) qs_reset(qs, 0) -#define qs_reset_free(qs) qs_reset(qs, 1) - -Inline qstring qs_new(void) ; -Inline qstring qs_dummy(qstring qs, const char* src, int pos) ; - -extern qstring qs_printf(qstring qs, const char* format, ...) - PRINTF_ATTRIBUTE(2, 3) ; -extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; +Private void qs_make_to_size(qstring qs, usize len, free_keep_b free) ; extern qstring qs_set(qstring qs, const char* src) ; -extern qstring qs_set_n(qstring qs, const char* src, size_t n) ; +extern qstring qs_set_n(qstring qs, const char* src, usize n) ; extern qstring qs_append(qstring qs, const char* src) ; -extern qstring qs_append_n(qstring qs, const char* src, size_t n) ; +extern qstring qs_append_n(qstring qs, const char* src, usize n) ; -Inline qstring qs_need(qstring qs, size_t len) ; -Inline qstring qs_set_len(qstring qs, size_t len) ; -extern qstring qs_add_len(qstring qs, size_t n, char** p_ep) ; -Inline void qs_clear(qstring qs) ; -Inline size_t qs_len(qstring qs) ; -Inline size_t qs_size(qstring qs) ; -Inline void* qs_term(qstring qs) ; +extern qstring qs_copy(qstring dst, qstring src) ; -Inline size_t qs_insert(qstring qs, const void* src, size_t n) ; -Inline void qs_replace(qstring qs, const void* src, size_t n) ; -Inline size_t qs_delete(qstring qs, size_t n) ; +extern qstring qs_set_alias(qstring qs, const char* src) ; +extern qstring qs_set_alias_n(qstring qs, const char* src, usize len) ; + +extern qstring qs_printf(qstring qs, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; + +extern usize qs_insert(qstring qs, const void* src, usize n) ; +extern void qs_replace(qstring qs, const void* src, usize n) ; +extern usize qs_delete(qstring qs, usize n) ; -extern qstring qs_copy(qstring dst, qstring src) ; extern int qs_cmp_sig(qstring a, qstring b) ; /*============================================================================== @@ -168,301 +211,213 @@ extern int qs_cmp_sig(qstring a, qstring b) ; */ /*------------------------------------------------------------------------------ - * Make a brand new, completely empty qstring - */ -Inline qstring -qs_new(void) -{ - /* Zeroising has set: - * - * body = NULL -- no body - * size = 0 -- no body - * - * len = 0 - * cp = 0 - * - * Nothing more to do unless initial size != 0 - */ - return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; -} ; - -/*------------------------------------------------------------------------------ - * Construct a "dummy" qstring from the given string. + * Clear contents of qstring -- preserves any qstring body, but sets len = 0. * - * Allocates a qstring if required. + * Does nothing if qstring is NULL * - * This sets: body = the src - * len = strlen(src) (0 if src is NULL) - * cp = 0 if 'pos' is zero - * len otherwise - * size = 0 + * Sets 'cp' = 'len' = 0. * - * The zero size means that the qstring handling will not attempt to free - * the body, nor will it write to it... Operations which require the qstring - * to have a size will allocate a new body, and discard this one. + * If is an alias qstring, discard the alias. * - * Returns: the address of the dummy qstring. + * NB: does not create a qstring body if there isn't one. */ -Inline qstring -qs_dummy(qstring qs, const char* src, int pos) +Inline void +qs_clear(qstring qs) { - if (qs == NULL) - qs = qs_new() ; - - qs->const_body = src ; - qs->len = (src != NULL) ? strlen(src) : 0 ; - qs->cp = (pos == 0) ? 0 : qs->len ; - qs->size = 0 ; - - return qs ; -} + if (qs != NULL) + { + qs_do_set_len_nn(qs, 0) ; /* sets term == false */ + if (qs->size == 0) + { + qs_set_body_nn(qs, qs->b_body) ; + qs->size = qs->b_size ; + } ; + qs->cp = 0 ; + } ; +} ; /*------------------------------------------------------------------------------ - * Need space for a string of 'len' characters (plus possible '\0'). + * Need space for a string of 'slen' characters (plus possible '\0'). * - * Allocates the qstring, if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Returns: address of qstring + * Returns: address of qstring -- with body that can be written upto and + * including 'slen' + 1. + * + * NB: has no effect on 'len' -- even if 'len' > 'slen'. * - * NB: asking for 0 bytes will cause a body to be allocated, ready for any - * '\0' ! + * NB: has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'. * - * NB: has no effect on 'cp' or 'len'. (Will be zero if new qstring allocated.) + * NB: if this is a aliased qstring, the alias is discarded and term = false. */ Inline qstring -qs_need(qstring qs, size_t len) +qs_need(qstring qs, usize slen) { - if ((qs == NULL) || (len >= qs->size)) - return qs_make_to_length(qs, len) ; + if (qs == NULL) + qs = qs_init_new(NULL, slen) ; /* Make the qstring if required */ + else + if (slen >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, slen, free_it) ; - assert(qs->body != NULL) ; return qs ; } ; /*------------------------------------------------------------------------------ * Set 'len' -- allocate or extend body as required. * - * Allocates the qstring, if required. + * Allocate qstring if required (setting qs->len = qs->cp = 0). * - * Returns: address of qstring + * Returns: address of qstring -- with body that can be written upto and + * including 'len' + 1. * - * NB: setting len == 0 bytes will cause a body to be allocated, ready for any - * '\0' ! + * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. * - * NB: has no effect on 'cp' -- even if 'cp' > 'len'. - * - * NB: if this is a "dummy" qstring, a copy is made of the original body. + * NB: if this is a aliased qstring, a copy is made of all of the original body, + * even if that is longer than the required 'slen'. (And term = false.) */ Inline qstring -qs_set_len(qstring qs, size_t len) +qs_set_len(qstring qs, usize len) { - qs = qs_need(qs, len) ; - qs->len = len ; + if (qs == NULL) + qs = qs_init_new(NULL, len) ; /* Make the qstring if required */ + else + if (len >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, len, keep_it) ; + + qs_do_set_len_nn(qs, len) ; + + if (qs->cp > len) + qs->cp = len ; + return qs ; } ; /*------------------------------------------------------------------------------ - * Reset contents of qstring. + * Chop to given length -- will neither allocate nor extend body. * - * Does nothing if qstring is NULL + * Does nothing if qstring is NULL. + * + * Does not change the 'len' if it is <= length to chop to. * - * Sets 'cp' = 'len' = 0. Sets first byte of body (if any) to NULL. + * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. * - * For "dummy" qstring, discards the body. + * NB: if this is a aliased qstring, then it remains an aliased string, but + * shorter and term = false (unless no change made to the length). */ Inline void -qs_clear(qstring qs) +qs_chop(qstring qs, usize clen) { if (qs != NULL) { - qs->len = 0 ; - qs->cp = 0 ; - if (qs->size > 0) - *((char*)qs->body) = '\0' ; - else - qs->body = NULL ; + usize len = qs_len_nn(qs) ; + if (len > clen) + qs_do_set_len_nn(qs, (len = clen)) ; /* sets term = false */ + if (qs->cp > len) + qs->cp = len ; } ; } ; /*------------------------------------------------------------------------------ - * Get length of qstring -- by doing strlen() -- and record it in qs->len. + * Set 'cp' -- allocate or extend body as required. * - * Returns: the string length - * - * NB: if no body has been allocated, length = 0 - */ -Inline size_t -qs_len(qstring qs) -{ - return (qs != NULL) ? (qs->len = (qs->body != NULL) ? strlen(qs_chars(qs)) - : 0) - : 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Get size of qstring body. - * - * NB: if no body has been allocated, size == 0 - * if qstring is NULL, size == 0 - * - * NB: if this is a "dummy" qstring, size == 0. - */ -Inline size_t -qs_size(qstring qs) -{ - return (qs != NULL) ? qs->size : 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Get address of current end of qstring body -- ie byte at 'len'. - * - * NB: allocates body if required. - * - * There will be space for '\0' after 'len', so the address returned - * is within the real body of the string. - * - * NB: if this is a "dummy" qstring, a copy is made of the original body. + * Allocates the qstring, if required. * - * NB: address of qstring may NOT be NULL. - */ -Inline void* -qs_end(qstring qs) -{ - if (qs->len >= qs->size) - qs_make_to_length(qs, qs->len) ; /* allows for trailing '\0' */ - - return (char*)qs->body + qs->len ; -} ; - -/*------------------------------------------------------------------------------ - * Set '\0' at qs->len -- allocate or extend body as required. + * Returns: address of qstring * - * Returns address of body -- NULL if the qstring is NULL + * NB: if there was no body, allocates a body for the string, even if 'cp' == 0. * - * NB: if this is a "dummy" qstring, a copy is made of the original body. + * NB: if new 'cp' > 'len', extends body (or allocates one), and sets 'len' to + * 'cp'. If this is an alias qstring, a copy of the string is made. */ -Inline void* -qs_term(qstring qs) +Inline qstring +qs_set_cp(qstring qs, usize cp) { - size_t len ; - if (qs == NULL) - return NULL ; + qs = qs_new(qs) ; - if ((len = qs->len) >= qs->size) - qs_make_to_length(qs, len) ; + if (qs->size <= cp) - *qs_chars_at(qs, len) = '\0' ; - return qs->body ; + if ((qs == NULL) || (cp >(cp > qs_len_nn(qs))) + qs = qs_set_len(qs, cp) ; + qs->cp = cp ; + return qs ; } ; /*------------------------------------------------------------------------------ - * Insert 'n' bytes at 'cp' -- moves anything cp..len up. - * - * Increases 'len'. but does not affect 'cp'. - * - * Returns: number of bytes beyond 'cp' that were moved before insert. + * If not "term": set '\0' at qs->len -- extending body as required. * - * NB: qstring MUST NOT be NULL + * Does NOT affect qs->cp or qs->len. * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. + * Returns address of body -- NULL if the qstring is NULL * - * NB: if this is a "dummy" qstring, a copy is made of the original body. + * NB: if this is an alias, and it is not terminated, make a copy before adding + * terminating '\0'. */ -Inline size_t -qs_insert(qstring qs, const void* src, size_t n) +Inline void +qs_terminate_nn(qstring qs) { - size_t after ; - char* p ; - - if (qs->len < qs->cp) - qs->len = qs->cp ; - after = qs->len - qs->cp ; + if (!qs_term_nn(qs)) + { + usize len ; - qs_set_len(qs, qs->len + n) ; /* set len and ensure have space */ + len = qs_len_nn(qs) ; - p = qs_cp_char(qs) ; - if (after > 0) - memmove (p + n, p, after) ; + if (len >= qs->size) /* alias has size == 0 */ + qs_make_to_size(qs, len) ; /* make sure can insert '\0' */ - if (n > 0) - memmove(p, src, n) ; + *qs_char_at_nn(qs, len) = '\0' ; - return after ; + qs_set_term_nn(qs, true) ; + } ; } ; /*------------------------------------------------------------------------------ - * Replace 'n' bytes at 'cp' -- extending if required. + * Return pointer to '\0' terminated string value. * - * May increase 'len'. but does not affect 'cp'. + * If qs is NULL or body is NULL returns pointer to constant empty '\0' + * terminated string. * - * NB: qstring MUST NOT be NULL + * If string is terminated, return address of string, which may be an alias + * address. * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. + * Otherwise, makes sure that there is a '\0' at the qs->len position, making + * a copy of any aliased string if required, and returns address of result. * - * NB: the string is NOT re-terminated. - * - * NB: if this is a "dummy" qstring, a copy is made of the original body. + * NB: value returned may be the address of the qstring body, or the address of + * an aliased string. In any event, the string should not be changed or + * reset until this pointer has been discarded ! */ -Inline void -qs_replace(qstring qs, const void* src, size_t n) +Inline const char* +qs_string(qstring qs) { - if ((qs->len < qs->cp + n) || (qs->size == 0)) - qs_set_len(qs, qs->cp + n) ; /* set len and ensure have space */ + if ((qs == NULL) || (qs_len_nn(qs) == 0)) + return "" ; - if (n > 0) - memmove(qs_cp_char(qs), src, n) ; + qs_terminate_nn(qs) ; + + return qs_body_nn(qs) ; } ; /*------------------------------------------------------------------------------ - * Remove 'n' bytes at 'cp' -- extending if required. - * - * May change 'len'. but does not affect 'cp'. + * Assuming the given address is within the size of the given qstring, + * set qs->len and insert '\0' terminator there. * - * Returns: number of bytes beyond 'cp' that were moved before insert. + * Does NOT affect qs->cp. * - * NB: qstring MUST NOT be NULL + * NB: must NOT be a NULL qs. * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. + * NB: must NOT be an aliased qstring. * - * NB: the string is NOT re-terminated. + * NB: must NOT have a NULL body. */ -Inline size_t -qs_delete(qstring qs, size_t n) +Inline void +qs_term_here(qstring qs, char* here) { - size_t after ; - char* p ; + assert((here >= qs->s.char_body) && (here < (qs->s.char_body + qs->size))) ; - /* Watch out for "dummy" */ - if (qs->size == 0) - qs_make_to_length(qs, qs->len) ; - - /* If deleting up to or beyond len, then simply set len == cp */ - if ((qs->cp + n) >= qs->len) - { - qs_set_len(qs, qs->cp) ; /* set len, looks after cp > len */ - return 0 ; /* nothing after */ - } - - /* There is at least one byte after cp (so body must exist) */ - after = qs->len - (qs->cp + n) ; - - if (n > 0) - { - p = qs_cp_char(qs) ; - memmove (p, p + n, after) ; - - qs->len -= n ; - } ; - - return after ; + qs->len = (here - qs->s.char_body) ; + *here = '\0' ; } ; - #endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtime.h b/lib/qtime.h index 35e1a51b..38e9ac1a 100644 --- a/lib/qtime.h +++ b/lib/qtime.h @@ -22,7 +22,7 @@ #ifndef _ZEBRA_QTIME_H #define _ZEBRA_QTIME_H -#include <stdint.h> +#include "misc.h" #include <stdlib.h> #include <time.h> #include <sys/time.h> @@ -31,10 +31,6 @@ #include "zassert.h" #include "config.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * qtime_t -- signed 64-bit integer. * diff --git a/lib/qtimers.c b/lib/qtimers.c index 8c08a6bc..5c0f1518 100644 --- a/lib/qtimers.c +++ b/lib/qtimers.c @@ -213,11 +213,12 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) * and the process MUST be run to completion. */ qtimer -qtimer_pile_ream(qtimer_pile qtp, int free_structure) +qtimer_pile_ream(qtimer_pile qtp, free_keep_b free_structure) { qtimer qtr ; + confirm(free_it == true) ; - qtr = heap_ream_keep(&qtp->timers) ; /* ream, keeping the heap structure */ + qtr = heap_ream(&qtp->timers, keep_it) ; /* ream, keeping the heap */ if (qtr != NULL) qtr->active = false ; /* has been removed from pile */ else @@ -413,8 +414,8 @@ qtimer_pile_verify(qtimer_pile qtp) { heap th = &qtp->timers ; vector v ; - vector_index i ; - vector_index e ; + vector_index_t i ; + vector_length_t e ; qtimer qtr ; bool seen ; @@ -429,7 +430,7 @@ qtimer_pile_verify(qtimer_pile qtp) assert(th->state == Heap_Has_Backlink) ; assert(th->backlink_offset == offsetof(qtimer_t, backlink)) ; - v = &th->v ; + v = th->v ; e = vector_end(v) ; for (i = 0 ; i < e ; ++i) { diff --git a/lib/qtimers.h b/lib/qtimers.h index 5beb931b..8534a789 100644 --- a/lib/qtimers.h +++ b/lib/qtimers.h @@ -22,16 +22,12 @@ #ifndef _ZEBRA_QTIMERS_H #define _ZEBRA_QTIMERS_H -#include <stdbool.h> +#include "misc.h" #include "zassert.h" #include "qtime.h" #include "heap.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * Quagga Timers -- qtimer_xxxx * @@ -76,7 +72,7 @@ struct qtimer_pile extern qtimer_pile qtimer_pile_init_new(qtimer_pile qtp) ; extern bool qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ; extern qtime_t qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) ; -extern qtimer qtimer_pile_ream(qtimer_pile qtp, int free_structure) ; +extern qtimer qtimer_pile_ream(qtimer_pile qtp, free_keep_b free_structure) ; /* Ream out qtimer pile and free the qtimer structure. */ #define qtimer_pile_ream_free(qtp) qtimer_pile_ream(qtp, 1) diff --git a/lib/routemap.h b/lib/routemap.h index ac6cb999..05f037f1 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -22,7 +22,7 @@ #ifndef _ZEBRA_ROUTEMAP_H #define _ZEBRA_ROUTEMAP_H -#include <stdint.h> +#include "misc.h" /* Route map's type. */ enum route_map_type diff --git a/lib/symtab.c b/lib/symtab.c index 57a49396..cac52962 100644 --- a/lib/symtab.c +++ b/lib/symtab.c @@ -212,11 +212,11 @@ symbol_base(symbol_table table, u_int32_t hash) * any existing chain base array, symbols and symbol references are simply * discarded -- which will leak memory and is probably a mistake. */ -symbol_table +extern symbol_table symbol_table_init_new(symbol_table table, - void* parent, - unsigned int base_count, - unsigned int density, + void* parent, + uint base_count, + uint density, symbol_hash_function* hash_function, symbol_call_back_function* value_call_back) { @@ -240,21 +240,21 @@ symbol_table_init_new(symbol_table table, } ; /* Set "parent" of symbol table. */ -void +extern void symbol_table_set_parent(symbol_table table, void* parent) { table->parent = parent ; } ; /* Get "parent" of symbol table. */ -void* +extern void* symbol_table_get_parent(symbol_table table) { return table->parent ; } ; /* Set the value_call_back */ -void +extern void symbol_table_set_value_call_back(symbol_table table, symbol_call_back_function* value_call_back) { @@ -311,8 +311,8 @@ symbol_table_setup(symbol_table table) * * NB: must only be done when the table is empty -- see assertion ! */ -symbol_table -symbol_table_reset(symbol_table table, int free_structure) +extern symbol_table +symbol_table_reset(symbol_table table, free_keep_b free_structure) { if (table== NULL) return NULL ; /* allow for already freed table */ @@ -322,6 +322,8 @@ symbol_table_reset(symbol_table table, int free_structure) if (table->bases) XFREE(MTYPE_SYMBOL_BASES, table->bases); + confirm(free_it == true) ; + if (free_structure) { XFREE(MTYPE_VECTOR, table) ; @@ -416,8 +418,8 @@ symbol_free(symbol sym) * NB: it is the caller's responsibility to unset all references and release * any that need to be released -- either before or after this operation. */ -void* -symbol_table_ream(symbol_table table, int free_structure) +extern void* +symbol_table_ream(symbol_table table, free_keep_b free_structure) { void* value ; symbol sym ; @@ -470,8 +472,8 @@ symbol_table_ream(symbol_table table, int free_structure) * value and no references. Where that distinction matters, it is * necessary to do an extra lookup. */ -symbol -symbol_lookup(symbol_table table, const void* name, int add) +extern symbol +symbol_lookup(symbol_table table, const void* name, add_b add) { struct symbol* this ; struct symbol** base ; @@ -485,7 +487,7 @@ symbol_lookup(symbol_table table, const void* name, int add) base = symbol_base(table, hash.hash) ; this = *base ; - while (this) + while (this != NULL) { if ((this->hash == hash.hash) && (this->name_len == hash.name_len) @@ -554,7 +556,7 @@ symbol_lookup(symbol_table table, const void* name, int add) * NB: orphan symbols can be deleted. The effect is to free the symbol if * possible. */ -void* +extern void* symbol_delete(symbol sym) { void* old_value = symbol_unset_value(sym) ; @@ -578,7 +580,7 @@ symbol_delete(symbol sym) static u_int32_t crc_table[] ; /* Standard symbol string hash function. */ -void +extern void symbol_hash_string(symbol_hash p_hash, const char* string) { u_int32_t h = 0 ; const char* p = string ; @@ -595,7 +597,7 @@ symbol_hash_string(symbol_hash p_hash, const char* string) { } ; /* Standard symbol byte vector hash function. */ -void +extern void symbol_hash_bytes(symbol_hash p_hash, const void* bytes, size_t len) { assert(len < 0xFFFF) ; @@ -670,8 +672,8 @@ symbol_extend_bases(symbol_table table) /* Zeroise the reference count.*/ -symbol -symbol_zero_ref(symbol sym, int force) +Private symbol +symbol_zero_ref(symbol sym, bool force) { assert((sym->ref_count == 1) || force) ; @@ -807,7 +809,7 @@ symbol_ref_is_bookmark(symbol_ref ref) } ; /* Start walk of symbol references */ -void +extern void symbol_ref_walk_start(symbol sym, symbol_ref walk) { symbol_init_ref(walk) ; /* keeping things tidy */ @@ -817,7 +819,7 @@ symbol_ref_walk_start(symbol sym, symbol_ref walk) } ; /* Step walk and return the next reference (if any). */ -symbol_ref +extern symbol_ref symbol_ref_walk_step(symbol_ref walk) { symbol_ref next_ref ; @@ -850,7 +852,7 @@ symbol_ref_walk_step(symbol_ref walk) * NB: if the symbol is not defined and has no references or bookmarks it * will now be freed. */ -void +extern void symbol_ref_walk_end(symbol_ref walk) { assert(symbol_ref_is_bookmark(walk)) ; /* must be a bookmark ! */ @@ -876,7 +878,7 @@ symbol_ref_walk_end(symbol_ref walk) * * Returns previous value -- which may require releasing. */ -void* +extern void* symbol_set_value(symbol sym, void* new_value) { void* old_value ; @@ -912,7 +914,7 @@ symbol_set_value(symbol sym, void* new_value) */ /* Initialise symbol reference -- allocate if required. */ -symbol_ref +extern symbol_ref symbol_init_ref(symbol_ref ref) { if (ref == NULL) @@ -936,7 +938,7 @@ symbol_init_ref(symbol_ref ref) * * if reference is not allocated, the parent and tag are unchanged. */ -symbol_ref +extern symbol_ref symbol_set_ref(symbol_ref ref, struct symbol* sym) { if (ref != NULL) @@ -944,7 +946,7 @@ symbol_set_ref(symbol_ref ref, struct symbol* sym) if (ref->sym == sym) return ref ; /* Nothing more to do if already set to given value */ if (ref->sym != NULL) - symbol_unset_ref_keep(ref) ; + symbol_unset_ref(ref, keep_it) ; } else ref = symbol_init_ref(NULL) ; @@ -967,8 +969,8 @@ symbol_set_ref(symbol_ref ref, struct symbol* sym) * * NB: copes if the reference is already unset, of course. */ -symbol_ref -symbol_unset_ref(symbol_ref ref, int free_ref_structure) +extern symbol_ref +symbol_unset_ref(symbol_ref ref, free_keep_b free_ref_structure) { if (ref == NULL) return ref ; @@ -979,6 +981,7 @@ symbol_unset_ref(symbol_ref ref, int free_ref_structure) ref->sym = NULL ; } ; + confirm(free_it == true) ; if (free_ref_structure) XFREE(MTYPE_SYMBOL_REF, ref) ; /* ref is set to NULL */ @@ -1008,7 +1011,7 @@ symbol_unset_ref(symbol_ref ref, int free_ref_structure) * progress -- up to and including deleting it. Any other changes to * the table must NOT be attempted. */ -void +extern void symbol_walk_start(symbol_table table, struct symbol_walker* walker) { walker->next = NULL ; @@ -1016,7 +1019,7 @@ symbol_walk_start(symbol_table table, struct symbol_walker* walker) walker->base_count = table->base_count ; } ; -symbol +extern symbol symbol_walk_next(struct symbol_walker* walker) { symbol this = walker->next ; @@ -1049,9 +1052,9 @@ symbol_walk_next(struct symbol_walker* walker) * caller's responsibility to avoid deleting any symbol whose pointer * in the vector they expect to rely on ! */ -vector +extern vector symbol_table_extract(symbol_table table, - symbol_select_cmp* selector, const void* p_val, int most, + symbol_select_cmp* selector, const void* p_val, bool most, symbol_sort_cmp* sort) { vector extract ; @@ -1093,7 +1096,7 @@ symbol_table_extract(symbol_table table, * * This comparison treats substrings of digits as numbers, so "a10" is > "a1". */ -int +extern int symbol_mixed_name_cmp(const symbol* p_a, const symbol* p_b) { diff --git a/lib/symtab.h b/lib/symtab.h index a8a6e622..4e33390e 100644 --- a/lib/symtab.h +++ b/lib/symtab.h @@ -22,14 +22,8 @@ #ifndef _ZEBRA_SYMTAB_H #define _ZEBRA_SYMTAB_H +#include "misc.h" #include "vector.h" -#include <stddef.h> -#include <stdint.h> - -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif /* Maximum number of symbol table bases -- something has gone tragically wrong * if we hit this. Assume can multiply this by 2 and get valid size_t result. @@ -145,21 +139,18 @@ struct symbol_walker /* Symbol Table Operations. */ -extern symbol_table -symbol_table_init_new(symbol_table table, - void* parent, - unsigned bases, - unsigned density, - symbol_hash_function* hash_function, - symbol_call_back_function* value_call_back) ; -void symbol_table_set_parent(symbol_table table, void* parent) ; -void* symbol_table_get_parent(symbol_table table) ; -void* symbol_table_ream(symbol_table table, int free_structure) ; -#define symbol_table_ream_free(table) symbol_table_ream(table, 1) -#define symbol_table_ream_keep(table) symbol_table_ream(table, 0) -symbol_table symbol_table_reset(symbol_table table, int free_structure) ; -#define symbol_table_reset_free(table) symbol_table_reset(table, 1) -#define symbol_table_reset_keep(table) symbol_table_reset(table, 0) +extern symbol_table symbol_table_init_new( + symbol_table table, + void* parent, + uint bases, + uint density, + symbol_hash_function* hash_function, + symbol_call_back_function* value_call_back) ; +extern void symbol_table_set_parent(symbol_table table, void* parent) ; +extern void* symbol_table_get_parent(symbol_table table) ; +extern void* symbol_table_ream(symbol_table table, free_keep_b free_structure) ; +extern symbol_table symbol_table_reset(symbol_table table, + free_keep_b free_structure) ; extern void symbol_hash_string(struct symbol_hash* p_hash, const char* string) ; extern void symbol_hash_bytes(struct symbol_hash* p_hash, const void* bytes, @@ -169,28 +160,31 @@ extern void symbol_table_set_value_call_back(symbol_table table, extern void symbol_table_free(symbol_table) ; -extern symbol symbol_lookup(symbol_table table, const void* name, int add) ; - -#define symbol_seek(table, name) symbol_lookup(table, name, 0) -#define symbol_find(table, name) symbol_lookup(table, name, 1) +extern symbol symbol_lookup(symbol_table table, const void* name, add_b add) ; extern void* symbol_delete(symbol sym) ; extern void* symbol_set_value(symbol sym, void* new_value) ; -#define symbol_unset_value(sym) symbol_set_value(sym, NULL) +Inline void* +symbol_unset_value(symbol sym) +{ + symbol_set_value(sym, NULL) ; +} ; -void symbol_ref_walk_start(symbol sym, symbol_ref walk) ; -symbol_ref symbol_ref_walk_step(symbol_ref walk) ; -void symbol_ref_walk_end(symbol_ref walk) ; +extern void symbol_ref_walk_start(symbol sym, symbol_ref walk) ; +extern symbol_ref symbol_ref_walk_step(symbol_ref walk) ; +extern void symbol_ref_walk_end(symbol_ref walk) ; -void symbol_walk_start(symbol_table table, struct symbol_walker* walker) ; -symbol symbol_walk_next(struct symbol_walker* walker) ; +extern void symbol_walk_start(symbol_table table, struct symbol_walker* walker); +extern symbol symbol_walk_next(struct symbol_walker* walker) ; typedef int symbol_select_cmp(const symbol, const void*) ; typedef int symbol_sort_cmp(const symbol*, const symbol*) ; -vector symbol_table_extract(symbol_table table, - symbol_select_cmp* select, const void* p_value, - int most, symbol_sort_cmp* sort) ; +extern vector symbol_table_extract(symbol_table table, + symbol_select_cmp* select, + const void* p_value, + bool most, + symbol_sort_cmp* sort) ; extern symbol_sort_cmp symbol_mixed_name_cmp ; @@ -226,29 +220,22 @@ symbol_inc_ref(symbol sym) return sym ; } ; -extern symbol symbol_zero_ref(symbol sym, int force) ; +Private symbol symbol_zero_ref(symbol sym, bool force) ; Inline symbol symbol_dec_ref(symbol sym) { if (sym->ref_count <= 1) - return symbol_zero_ref(sym, 0) ; + return symbol_zero_ref(sym, false) ; --sym->ref_count ; return sym ; } ; -extern symbol_ref -symbol_init_ref(symbol_ref ref) ; - -extern symbol_ref -symbol_set_ref(symbol_ref ref, symbol sym) ; - -extern symbol_ref -symbol_unset_ref(symbol_ref ref, int free_ref_structure) ; - -#define symbol_unset_ref_free(ref) symbol_unset_ref(ref, 1) ; -#define symbol_unset_ref_keep(ref) symbol_unset_ref(ref, 0) ; +extern symbol_ref symbol_init_ref(symbol_ref ref) ; +extern symbol_ref symbol_set_ref(symbol_ref ref, symbol sym) ; +extern symbol_ref symbol_unset_ref(symbol_ref ref, + free_keep_b free_ref_structure) ; /* Access functions -- argument is address of symbol_ref. */ /* These cope if address of symbol_ref is null, or reference is undefined. */ diff --git a/lib/temp.c b/lib/temp.c new file mode 100644 index 00000000..759c2bb3 --- /dev/null +++ b/lib/temp.c @@ -0,0 +1,1349 @@ +/* Generic vector interface routine -- functions + * Copyright (C) 1997 Kunihiro Ishiguro + * + * 24-Nov-2009 -- extended to add a number of new operations on vectors. + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * 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 "vector.h" +#include "memory.h" + +/* Vectors are implemented as a structure which points to an array of pointers + * to vector items. That array -- the body of the vector -- can change size, + * and therefore may move around in memory. + * + * The vector structure may be statically allocated, embedded in another + * structure, or allocated dynamically. In any case the vector operations + * require the address of the vector structure -- see typedef for vector. + * + * Vector items are accessed by index, fetching and storing pointers to + * those items. Can also push and pop items. + * + * A vector has a known (logical) end position. Everything beyond the end is + * defined to be NULL. When a vector is initialised it is set empty. + * + * At any given moment the vector body has a limit on the number of items it + * can accommodate (the physical end). + * + * A vector will grow to accommodate what is put into it. Adding items beyond + * the (logical) end moves it. Adding items beyond the (physical) limit causes + * the body to be extended to suit, and to leave some spare space for future + * expansion. + * + * While the vector is small (see VECTOR_LIMIT_DOUBLE_MAX) the body will grow by + * doubling in size. When it is larger, it grows to be multiples of + * VECTOR_LIMIT_DOUBLE_MAX. + * + * Deleting items reduces the (logical) end position, but does NOT release + * memory -- the (physical) limit is not changed. + * + * To release memory: vector_chop will release everything beyond the current + * end; vector_decant will create a new body of exactly the current size, + * releasing the old body; vector_discard will release everything beyond a + * given position. + * + * NB: you can set a vector item to be NULL. If you set a vector item beyond + * the current end, NULL items are inserted in the vector. + * + * NB: when setting a vector item it is the caller's responsibility to + * deallocate any pre-existing value of the item. + * + * NB: when deleting items it is also the caller's responsibility to deallocate + * any values that require it. + * + * Implementation Notes + * + * Everything beyond the (logical) end is implicitly NULL. + * + * Actual memory between (logical) end and (physical) limit is UNDEFINED. So + * when advancing the end some care has to be taken to ensure that any new + * items in the vector are either set to something or cleared to NULL. + * + * It would have been possible to ensure that everything between end and limit + * is cleared to NULL, but that is more work -- in particular it creates work + * when it is not always required. + */ + +#define P_ITEMS_SIZE(n) SIZE(p_vector_item, n) + +/*============================================================================== + * Initialisation, allocation, reset etc. + */ + +/*------------------------------------------------------------------------------ + * Initialise a brand new vector, setting it empty. + * + * Allocates vector structure if none given -- that is, if v == NULL. + * + * If size is given as zero, no body is allocated, otherwise body of exactly + * the required size is allocated. + * + * NB: discards any existing vector body -- so it is the caller's responsibility + * to release any existing body, and any items in that body. + */ +extern vector +vector_init_new(vector v, vector_length_t limit) +{ + if (v == NULL) + v = XCALLOC(MTYPE_VECTOR, sizeof(struct vector)) ; + else + memset(v, 0, sizeof(struct vector)) ; + + if (limit != 0) + { + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(limit)) ; + v->limit = limit ; + } ; + + return v ; +} ; + +/*------------------------------------------------------------------------------ + * Initialize vector : allocate memory and return vector. + * allocates body with at least 1 entry. + * + * This is a "legacy" function. + */ +extern vector +vector_init (vector_length_t limit) +{ + return vector_init_new(NULL, limit ? limit : 1) ; /* at least 1 entry */ +} ; + +/*------------------------------------------------------------------------------ + * Basic: free the vector body and the vector structure. + * + * NB: it is the caller's responsibility to release any vector item values + * *before* doing this. + */ +void +vector_free (vector v) +{ + XFREE (MTYPE_VECTOR_BODY, v->p_items); + XFREE (MTYPE_VECTOR, v); +} ; + +/*------------------------------------------------------------------------------ + * Re-initialise vector (or create new one), setting it empty. + * + * Allocates vector structure if none given -- that is, if v == NULL. + * + * If size is given as zero, no body is allocated, but any existing body is + * retained. (vector_reset() will discard body.) + * + * Otherwise ensures existing body is at least the required size, or a body + * of exactly the required size is allocated. + * + * NB: when re-initialising an existing vector it is the caller's responsibility + * to release any vector item values *before* doing this. + * */ +extern vector +vector_re_init(vector v, vector_length_t) +{ + if ((v == NULL) || (v->p_items == NULL)) + return vector_init_new(v, limit) ; + + v->end = 0 ; + + if (v->limit < size) + { + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, + P_ITEMS_SIZE(limit)) ; + v->limit = limit ; + } ; + + return v ; +} ; + +/*------------------------------------------------------------------------------ + * Free the vector body, and (if required) free the vector structure. + * + * Return NULL if releases vector, otherwise the address of the vector. + * + * NB: it is the caller's responsibility to release any vector item values + * *before* doing this. + */ +vector +vector_reset(vector v, free_keep_b free_structure) +{ + if (v == NULL) + return NULL ; /* allow for already freed vector */ + + if (v->p_items != NULL) + XFREE(MTYPE_VECTOR_BODY, v->p_items) ; + + if (free_structure) + { + confirm(free_it == true) ; + XFREE(MTYPE_VECTOR, v) ; + return NULL ; + } + else + return vector_init_new(v, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Set vector length to be (at least) the given fixed length. + * + * There must be a vector. + * + * Does nothing if the vector is already as long or longer than the given + * length. + * + * If the body is not big enough for the new length, allocates or extends to + * exactly the new length. Otherwise, leaves body as it is. + * + * Appends NULLs as required to extend to the required length. + * + * Note that the existing contents of the vector are preserved in all cases. + */ +Private void +vector_set_new_min_length(vector v, vector_length_t len) +{ + assert (v != NULL) ; + + if (len > v->limit) + { + if (v->p_items == NULL) + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ; + else + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, + P_ITEMS_SIZE(len)) ; + v->limit = len ; + } ; + + if (v->end < len) + vector_extend(v, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Pop item from vector, stepping past any NULLs. + * If vector is empty, free the body and, if required, the vector structure. + * + * Useful for emptying out and discarding a vector: + * + * while ((p_v = vector_ream_out(v, 1))) + * ... do what's required to release the item p_v + * + * Returns NULL if vector was empty and has now been freed as required. + */ +p_vector_item +vector_ream(vector v, free_keep_b free_structure) +{ + p_vector_item p_v ; + + if (v == NULL) + return NULL ; + + while (v->end != 0) + { + p_v = v->p_items[--v->end] ; + if (p_v != NULL) + return p_v ; /* return non-NULL item */ + } ; + + /* vector is empty: free the body, and (if required) the vector structure. */ + vector_reset(v, free_structure) ; + + return NULL ; /* signals end */ +} ; + +/*============================================================================== + * Unset item, condensing and trimming vector. + * + * These are legacy operations. + */ + +/*------------------------------------------------------------------------------ + * Unset item at given index (ie set it NULL). + * + * Return the old value of the item. + * + * If the item at the current (logical) end of the vector is NULL, move the + * end backwards until finds a non-NULL item, or the vector becomes empty. + */ +extern p_vector_item +vector_unset_item(vector v, vector_index_t i) +{ + p_vector_item was ; + + if (i < v->end) + { + was = v->p_items[i] ; + v->p_items[i] = NULL ; + } + else if (v->end == 0) + return NULL ; /* avoid test for last entry NULL if is empty */ + else + was = NULL ; + + if (v->p_items[v->end - 1] == NULL) + vector_trim(v) ; + + return was ; +} ; + +/*------------------------------------------------------------------------------ + * Trim any NULL entries at the current (logical) end of the vector. + * + * Returns the (new) length (end) of the vector. + */ +extern vector_length_t +vector_trim(vector v) +{ + vector_length_t e = v->end ; + while ((e > 0) && (v->p_items[e - 1] == NULL)) + --e ; + v->end = e ; + return e ; +} ; + +/*------------------------------------------------------------------------------ + * Removes any NULL entries from the given vector. + * + * Returns the (new) length (end) of the vector. + */ +extern vector_length_t +vector_condense(vector v) +{ + vector_length_t e = 0 ; + vector_index_t j ; + + /* Find first NULL, if any */ + while ((e < v->end) && (v->p_items[e] != NULL)) + ++e ; + + /* Quit if no NULLs (or vector is empty) */ + if (e == v->end) + return e ; + + /* Shuffle any remaining non-NULL down */ + for (j = e + 1 ; j < v->end ; ++j) + if (v->p_items[j] != NULL) + v->p_items[e++] = v->p_items[j] ; + + v->end = e ; + + return e ; +} ; + +/*============================================================================== + * Inserting and deleting items. + */ + +/*------------------------------------------------------------------------------ + * Insert item in vector, before item at given position + * Move items and extend vector as required. + */ +extern void +vector_insert_item(vector v, vector_index_t i, void* p_v) +{ + if ((i == v->end) && (i < v->limit)) + ++v->end ; + else + vector_insert(v, i, 1) ; + + v->p_items[i] = (p_vector_item)p_v ; +} ; + +/*------------------------------------------------------------------------------ + * Insert item in vector at given position with rider: + * + * rider < 0 -- insert before the item at the given position + * rider == 0 -- insert at the given position -- REPLACING any existing value + * rider > 0 -- insert after the item at the given position + * + * NB: when an item is replaced, it is the caller's responsibility to release + * any memory used by the item, if required. + * + * Move items and extend vector as required. + */ +extern void +vector_insert_item_here(vector v, vector_index_t i, int rider, + p_vector_item p_v) +{ + if (rider == 0) + return vector_set_item(v, i, p_v) ; + + if (rider > 0) + ++i ; /* insert before next item */ + vector_insert_item(v, i, p_v) ; +} ; + +/*------------------------------------------------------------------------------ + * Delete item from vector. + * + * Move items as required. Reduces number of items in the vector (unless + * the item in question is beyond the end of the vector !) + * + * Returns: the item that has just been deleted. + * + * NB: it is the caller's responsibility to release memory used by any + * current value of the item, if required. + * + * NB: does NOT change the size of the vector body. + */ +extern p_vector_item +vector_delete_item(vector v, vector_index_t i) +{ + p_vector_item p_e ; + if (i < v->end) + { + p_e = v->p_items[i] ; /* pick up the current value */ + if (i != (v->end - 1)) + vector_delete(v, i, 1) ; + else + v->end = i ; + return p_e ; + } + else + return NULL ; +} ; + +/*============================================================================== + * Moving items within vector. + */ + +/*------------------------------------------------------------------------------ + * Move item in vector from source position to destination position. + * + * Moves intervening items up or down as required. + * + * Extends vector to include the destination, if required. + * + * A source item beyond the end of the vector is implicitly NULL. + */ +extern void +vector_move_item(vector v, vector_index_t i_dst, vector_index_t i_src) +{ + p_vector_item* pp_s ; + p_vector_item* pp_d ; + p_vector_item p_e ; + + vector_length_t old_end = v->end ; + + /* Worry about whether both source and destination exist. */ + if (i_dst >= old_end) + { + vector_insert(v, i_dst, 1) ; /* ensure destination exists */ + if (i_src >= old_end) + return ; /* both were beyond the end */ + } + else if (i_src >= old_end) + { + i_src = old_end ; /* clamp to just beyond last */ + vector_insert(v, i_src, 1) ; /* create empty entry */ + } ; + + if (i_dst == i_src) /* avoid work and edge case */ + return ; + + /* Both src and dst are within the vector and src != dst */ + pp_s = &v->p_items[i_src] ; /* address of src entry */ + pp_d = &v->p_items[i_dst] ; /* address of dst entry */ + p_e = *pp_s ; /* pick up item to move */ + if (i_src < i_dst) + memmove(pp_s, pp_s+1, P_ITEMS_SIZE(i_dst - i_src)) ; + else + memmove(pp_d+1, pp_d, P_ITEMS_SIZE(i_src - i_dst)) ; + *pp_d = p_e ; /* put down the item to move */ +} ; + +/*------------------------------------------------------------------------------ + * Move item in vector to given position with rider: + * + * rider < 0 -- move to before the item at the given position + * rider == 0 -- move to replace item at the given position + * rider > 0 -- insert after the item at the given position + * + * NB: it is the caller's responsibility to release the any existing value + * that will be replaced. + * + * Move items and extend vector as required. + */ +extern void +vector_move_item_here(vector v, vector_index_t i_dst, int rider, + vector_index_t i_src) +{ + if (rider != 0) + { + if (rider > 0) + ++i_dst ; + vector_move_item(v, i_dst, i_src) ; + } + else + { + /* to replace: copy and then delete. */ + vector_set_item(v, i_dst, vector_get_item(v, i_src)) ; + vector_delete_item(v, i_src) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Reverse vector: reverse the order of items in the vector. + */ +extern void +vector_reverse(vector v) +{ + if (v != NULL) + vector_part_reverse(v, 0, v->end) ; +} ; + +/*------------------------------------------------------------------------------ + * Reverse portion of vector. + */ +extern void +vector_part_reverse(vector v, vector_index_t i, vector_length_t n) +{ + vector_index_t j ; + + if (v == NULL) + return ; + + if ((i + n) > v->limit) + vector_extend(v, i + n) ; /* ensure portion exists */ + + if (n <= 1) + return ; + + j = i + n - 1 ; /* j > i, because n > 1 */ + do + { + p_vector_item p_i = v->p_items[i] ; + v->p_items[i++] = v->p_items[j] ; + v->p_items[j--] = p_i ; + } while (j > i) ; +} ; + +/*============================================================================== + * Copying, moving and appending entire vectors. + */ + +static void vector_new_limit(vector v, vector_length_t new_end) ; + +/*------------------------------------------------------------------------------ + * Shallow vector copy -- copies pointers to item values, not the values. + * + * Creates a new vector. + * + * NB: creates new vector with same limit as existing one, but copies only + * the known items (ie up to end, not up to limit). + */ +vector +vector_copy (vector v) +{ + vector new = vector_init_new(NULL, v->limit) ; + + new->end = v->end; + + if (v->limit > 0) + memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->end)) ; + + return new; +} + +/*------------------------------------------------------------------------------ + * Shallow vector copy -- copies pointers to item values, not the values. + * Creates a new vector or re-initialises an existing one. + * + * NB: creates new vector with same limit as existing one, but copies only + * the known items (ie up to end, not up to limit). + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to release any existing items if that is required. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to ensure the vector structure is currently valid. + * + * NB: do NOT try copying a vector to itself !! + */ +vector +vector_copy_here(vector dst, vector src) +{ + assert((src != NULL) && (src != dst)) ; + + dst = vector_re_init(dst, src->limit) ; + + dst->end = src->end; + + if (src->end > 0) + memcpy(dst->p_items, src->p_items, P_ITEMS_SIZE(src->end)) ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Vector move -- moves body of vector. + * + * Creates a new vector or re-initialises an existing one. + * Leaves the source vector empty -- does not release the structure. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to release any existing items if that is required. + * + * NB: if re-initialising existing vector, it is the caller's responsibility + * to ensure the vector structure is currently valid. + * + * NB: do NOT try moving a vector to itself !! + */ +extern vector +vector_move_here(vector dst, vector src) +{ + assert((src != NULL) && (src != dst)) ; + + if (dst != NULL) + dst = vector_reset(dst, 0) ; /* Reset to deallocate any existing body */ + else + dst = vector_init_new(dst, 0) ; /* Create new structure sans body. */ + + *dst = *src ; /* Copy the vector structure */ + + vector_init_new(src, 0) ; /* Set empty, forgetting body */ + + return dst ; +} + +/*------------------------------------------------------------------------------ + * Shallow vector copy append -- copies pointers to item values, not the values. + * + * Appends copied pointers to the destination vector. + * + * Creates a new destination vector if required. + * + * NB: Can append to self. + */ +extern vector +vector_copy_append(vector dst, vector src) +{ + vector_index_t new_end ; + + assert(src != NULL) ; + + if (dst != NULL) + { + new_end = dst->end + src->end ; + if (new_end > dst->limit) + vector_new_limit(dst, new_end) ; + } + else + { + new_end = src->end ; + vector_init_new(dst, new_end) ; + } ; + + if (src->end) + memcpy(&dst->p_items[dst->end], src->p_items, P_ITEMS_SIZE(src->end)) ; + + dst->end = new_end ; /* Done last, allows for append to self ! */ + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Vector move append -- moves pointers to item values. + * + * Appends moved pointers to the destination vector. + * + * Creates a new destination vector if required (dst == NULL). + * + * Leaves the source vector empty -- does not release the structure. + * + * NB: do NOT try moving a vector to itself !! + */ +extern vector +vector_move_append(vector dst, vector src) +{ + assert((src != NULL) && (src != dst)) ; + + if ((dst == NULL) || (dst->end == 0)) + return vector_move_here(dst, src) ; /* Easy way to do it if dst empty */ + + vector_copy_append(dst, src) ; /* Extend dst and copy src */ + vector_init_new(src, 0) ; /* Set empty, forgetting body */ + + return dst ; +} ; + +/*============================================================================== + * Portmanteau splice/extract/replace function. + * + * All take a portion of 'src' vector and: + * + * splice: + * + * a) replace a portion of the 'dst' vector by a portion of the 'src' vector + * copying the replaced portion of the 'dst' vector to the 'to' vector + * b) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- true + * to -- vector, or NULL => allocate new vector + * dst -- vector + * i_dst -- start of portion to replace + * n_dst -- length of portion to replace. 0 => insertion. + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: the (possibly new) 'to' vector + * + * NB: 'to', 'dst' and 'src' must be distinct vectors. + * + * extract: + * + * a) copy a portion of the 'src' vector to the 'to' vector + * c) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- true + * to -- vector, or NULL => allocate new vector + * dst -- NULL + * i_dst -- ignored + * n_dst -- ignored + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: the (possibly new) 'to' vector + * + * NB: 'to' and 'src' must be distinct vectors. + * + * replace: + * + * a) replace a portion of the 'dst' vector by a portion of the 'src' vector + * b) either: leave 'src' unchanged -- copy + * or: remove the stuff copied from 'src' -- move + * + * Arguments: to_copy -- false + * to -- ignored + * dst -- vector + * i_dst -- start of portion to replace + * n_dst -- length of portion to replace. 0 => insertion. + * src -- vector, or NULL => nothing to copy/move + * i_src -- start of portion to copy/move + * n_src -- length of portion to copy/move. 0 => nothing. + * src_move -- true => move, otherwise copy. + * + * Returns: original 'to' argument + * + * NB: 'dst' and 'src' must be distinct vectors. + * + * All copies are shallow -- pointers to item values are copied, not the values. + * + * NB: any existing contents of the 'to' vector are discarded. + * + * NB: it is the caller's responsibility to release memory allocated to any + * items which are discarded in these operations. + * + * NB: for splice and replace, the resulting destination vector will be at + * least i_dst + n_src long. (Even if is copying actual or implied NULLs + * from the source.) + * + * NB: where new vectors are created, they will be of exactly the required size. + * + * NB: where an existing vector is reused, it is the caller's responsibility + * to ensure the vector structure is currently valid (by vector_init_new() + * or by ensuring it is zeroized). + */ +extern vector +vector_sak(int to_copy, vector to, + vector dst, vector_index_t i_dst, vector_length_t n_dst, + vector src, vector_index_t i_src, vector_length_t n_src, int src_move) +{ + int dst_replace ; /* true => replace portion of 'dst' */ + + vector_index_t new_dst_end = 0 ; /* new end of dst */ + + vector_length_t n_dst_nulls ; /* number of implicit NULLs to add */ + vector_length_t n_dst_move ; /* number of items to move up or down */ + vector_length_t n_src_real ; /* number of items to really copy */ + vector_length_t n_src_nulls ; /* number of implicit NULLs to "copy" */ + + assert((to == NULL) || (dst == NULL) || (to != dst)) ; + assert((src == NULL) || (dst == NULL) || (src != dst)) ; + + /* Worry about how much we really have in the source vector. */ + + n_src_real = n_src ; /* assume all real */ + n_src_nulls = 0 ; /* so no NULLs to "copy" */ + + if (n_src != 0) + { + if ((src == NULL) || (i_src >= src->end)) + n_src_real = 0 ; + else if ((i_src + n_src) > src->end) + n_src_real = src->end - i_src ; + n_src_nulls = n_src - n_src_real ; + } ; + + /* If no 'dst' vector, then this is an extract. */ + + n_dst_move = 0 ; /* assume nothing to move */ + n_dst_nulls = 0 ; /* assume no NULLs to add */ + + if (dst == NULL) + /* For extract: set up dst, i_dst and n_dst so that can copy to the */ + /* 'to' vector as if from 'dst'. */ + { + dst_replace = 0 ; /* no replacement operation */ + dst = src ; /* copy from here */ + i_dst = i_src ; + n_dst = n_src_real ; + } + else + /* Reduce n_dst to the number of actual items to be replaced. */ + /* */ + /* Calculate the new end of 'dst'. */ + { + dst_replace = 1 ; /* have replacement to do */ + if (i_dst >= dst->end) + /* If i_dst is beyond the end of 'dst', then there is nothing */ + /* to replace (so set n_dst == 0). Will be adding n_src items */ + /* at i_dst -- so new end must be i_dst + n_src. */ + { + n_dst_nulls = i_dst - dst->end ; /* fill from end to i_dst */ + n_dst = 0 ; /* nothing to replace */ + new_dst_end = i_dst + n_src ; /* all beyond current end */ + } + else + /* If i_dst + n_dst is beyond the end of 'dst', reduce n_dst to */ + /* number of items up to the end. */ + /* Will remove n_dst items and insert n_src, so end will move */ + /* by n_src - n_dst. */ + { + if ((i_dst + n_dst) > dst->end) + n_dst = dst->end - i_dst ; /* what we actually replace */ + else if (n_dst != n_src) + n_dst_move = dst->end - (i_dst + n_dst) ; + /* what we move up or down */ + + new_dst_end = dst->end + n_src - n_dst ; + /* end depends on amount added */ + /* & amount actually replaced */ + } ; + } ; + + /* Copy portion of 'dst' (or of 'src') to 'to', if required. */ + /* */ + /* Have arranged: n_dst -- number of items to copy, all existent */ + /* dst -- vector to copy from -- if n_dst > 0 */ + /* i_dst -- first item to copy -- if n_dst > 0 */ + + if (to_copy) + { + to = vector_re_init(to, n_dst) ; /* reinitialise or create */ + to->end = n_dst ; + if (n_dst > 0) + memcpy(to->p_items, &dst->p_items[i_dst], P_ITEMS_SIZE(n_dst)) ; + } ; + + /* Replace portion of 'dst' by portion of 'src', if required. */ + /* */ + /* Have arranged: */ + /* */ + /* new_dst_end -- end of dst once dust settles */ + /* n_dst_nulls -- number of NULLs to insert at dst->end to fill up */ + /* to i_dst (when i_dst is beyond old end.) */ + /* n_dst_move -- number of items in dst to move up or down to */ + /* leave n_src item hole at i_dst to fill in. */ + /* n_src_real -- number of real src items at i_src to copy to dst */ + /* at i_dst. */ + /* n_src_nulls -- number of nulls to add to fill to i_dst + n_src. */ + + if (dst_replace) + { + if (new_dst_end > dst->limit) /* extend body if required */ + vector_new_limit(dst, new_dst_end) ; + + if (n_dst_nulls > 0) + memset(&dst->p_items[dst->end], 0, P_ITEMS_SIZE(n_dst_nulls)) ; + if (n_dst_move > 0) + memmove(&dst->p_items[i_dst + n_dst], &dst->p_items[i_dst + n_src], + P_ITEMS_SIZE(n_dst_move)) ; + if (n_src_real > 0) + memcpy(&dst->p_items[i_dst], &src->p_items[i_src], + P_ITEMS_SIZE(n_src_real)) ; + if (n_src_nulls > 0) + memset(&dst->p_items[i_dst + n_src_real], 0, + P_ITEMS_SIZE(n_src_nulls)) ; + dst->end = new_dst_end ; + } ; + + /* Delete portion of 'src', if required (and have 'src' !) */ + + if (src_move && (n_src_real != 0)) + vector_delete(src, i_src, n_src_real) ; + + /* Done -- return 'to' vector as promised. */ + + return to ; +} ; + +/*============================================================================== + * Legacy Vector Operations + */ + +/*------------------------------------------------------------------------------ + * Set value to the smallest empty slot. + * + * Returns: index of slot used. + */ +extern int +vector_set (vector v, void *val) +{ + vector_index_t i; + + i = 0 ; + while (1) + { + if (i == v->end) + { + i = vector_extend_by_1(v) ; + break ; + } + + if (v->p_items[i] == NULL) + break ; + + ++i ; + } ; + + v->p_items[i] = val; + + return i ; +} + +/*------------------------------------------------------------------------------ + * Set value to specified index slot. + * + * Returns: index of slot (as given) + */ +extern int +vector_set_index (vector v, vector_index_t i, void *val) +{ + vector_ensure (v, i); + v->p_items[i] = val; + return i; +} + +/*------------------------------------------------------------------------------ + * Look up vector -- get the i'th item. + * + * Returns: the i'th item -- NULL if item is null, or i >= logical len of vector + */ +extern p_vector_item +vector_lookup (vector v, vector_index_t i) +{ + if (i >= v->end) + return NULL; + return v->p_items[i]; +} + +/*------------------------------------------------------------------------------ + * Lookup vector, ensure it -- get i'th item and ensure logical len > i. + * + * Returns: the i'th item -- NULL if item is null + */ +extern p_vector_item +vector_lookup_ensure (vector v, vector_index_t i) +{ + vector_ensure (v, i); + return v->p_items[i]; +} + +/*------------------------------------------------------------------------------ + * Count the number of not empty slots. + */ +extern vector_length_t +vector_count (vector v) +{ + vector_index_t i; + vector_length_t count = 0; + + for (i = 0; i < v->end; i++) + if (v->p_items[i] != NULL) + count++; + + return count; +} + +/*============================================================================== + * Sorting and Searching vector. + */ + +/*------------------------------------------------------------------------------ + * Sort the given vector. + * + * NB: the comparison function receives a pointer to the pointer to the + * vector item's value. + * + * NB: if there are NULL items in the vector, the comparison function MUST + * be ready for them. + */ +extern void +vector_sort(vector v, vector_sort_cmp* cmp) +{ + if (v->end <= 1) + return ; /* Stop dead if 0 or 1 items */ + + typedef int qsort_cmp(const void*, const void*) ; + + qsort(v->p_items, v->end, sizeof(p_vector_item), (qsort_cmp*)cmp) ; +} ; + +/*------------------------------------------------------------------------------ + * Perform binary search on the given vector. + * + * The vector MUST be sorted in the order implied by the comparison function + * given. The vector need not contain unique values, but this search makes + * no effort to select any particular instance of a sequence of equals. + * + * Returns: + * + * result == 0: found an equal value. + * index returned is of first entry found which is equal to + * the value sought. There may be other equal values, before + * and/or after this one in the vector. + * + * result == +1: value not found and vector not empty. + * index returned is of the largest entry whose value is less + * than the value sought. + * (The value sought belongs after this point.) + * + * result == -1: value is less than everything in the vector, or the + * vector is empty. + * index returned is 0 + * + * NB: The comparison function takes arguments which are: + * + * const void** pointer to pointer to value being searched for. + * const void** pointer to pointer to vector item to compare with + * + * The value being searched for need not be in the same form as a vector + * item. However, if it is then the same comparison function can be used + * to sort and search. + * + * NB: if there are NULL items in the vector, the comparison function MUST + * be ready for them. + */ +extern vector_index_t +vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, + int* result) +{ + vector_index_t il, iv, ih ; + int c ; + + if (v->end <= 1) + { + *result = (v->end == 0) ? -1 : cmp(&p_val, (const void**)&v->p_items[0]) ; + return 0 ; /* Stop dead if 0 or 1 items */ + } ; + + /* We have at least two items. */ + il = 0 ; + ih = v->end - 1 ; + + /* Pick off the edge cases: >= last and <= first. */ + if ((c = cmp(&p_val, (const void**)&v->p_items[ih])) >= 0) + { + *result = c ; /* 0 => found. +1 => val > last */ + return ih ; /* return high index. */ + } ; + if ((c = cmp(&p_val, (const void**)&v->p_items[il])) <= 0) + { + *result = c ; /* 0 => found. -1 => val < first */ + return il ; /* return low index. */ + } + + /* Now binary chop. We know that item[il] < val < item[ih] */ + /* We also know that il < ih */ + while (1) + { + iv = (il + ih) / 2 ; + if (iv == il) /* true if (ih == il+1) */ + { + *result = +1 ; + return il ; /* return il: item[il] < val < item[il+1] */ + } ; + /* We now know that il < iv < ih */ + c = cmp(&p_val, (const void**)&v->p_items[iv]) ; + if (c == 0) + { + *result = 0 ; + return iv ; /* found !! */ + } + if (c < 0) + ih = iv ; /* step down iv > il, so new ih > il */ + else + il = iv ; /* step up iv < ih, so new il < ih */ + } ; +} ; + +/*============================================================================== + * Mechanics for adding/deleting items and managing the vector (logical) end + * and (physical) limit. + */ + +/* Extract the LS bit of unsigned integer 'x'. */ +#define lsbit(x) ((x) & ((~(x)) + 1)) +/* Round 'x' up to a multiple of 'm' */ +#define multiple(x, m) ((((x) + (m) - 1) / (m)) * (m)) + +/*------------------------------------------------------------------------------ + * Set new limit to suit new end for given vector. + * + * The new limit will be at least: VECTOR_LIMIT_MIN. + * + * While the vector is relatively small, the limit is doubled until there + * is at least 1/8 of the new vector free. + * + * Beyond VECTOR_LIMIT_DOUBLE_MAX, however, the limit is set to the + * smallest multiple of VECTOR_LIMIT_DOUBLE_MAX which gives at least + * VECTOR_LIMIT_SLACK_MIN free entries beyond the new end. + * + * This is an attempt to balance the cost of repeated reallocations of + * memory against the cost of possible wasted space at the end of the + * vector. + * + * NB: the new_limit depends entirely on the new end position. (Current + * end position is ignored.) + * + * NB: the new limit may be less than the current limit, in which case the + * vector body is reduced in size. + * + * Except for any size set when the vector is initialised, the vector body + * size will be a power of 2 or a multiple of VECTOR_LIMIT_DOUBLE_MAX. + * (Vectors are regular in their habits, which may help the memory allocator). + * + * TODO: what to do if calculation of new_limit overflows, or calculation + * of P_ITEMS_SIZE will ? + */ +static void +vector_new_limit(vector v, vector_index_t new_end) +{ + vector_length_t old_limit = v->limit ; + vector_length_t new_limit ; + + if (new_end > ((VECTOR_LIMIT_DOUBLE_MAX * 7) / 8)) + { + new_limit = multiple(new_end + VECTOR_LIMIT_SLACK_MIN, + VECTOR_LIMIT_DOUBLE_MAX) ; + } + else + { + /* Want the new_limit to be a power of 2. */ + /* If the old_limit was a power of 2, start from there. */ + /* Otherwise start from a power of 2 less than new_end: either the */ + /* minimum value or a value mid way to VECTOR_LIMIT_DOUBLE_MAX. */ + if ( (old_limit != 0) && (old_limit == lsbit(old_limit)) + && (new_end >= old_limit) ) + new_limit = old_limit ; + else + new_limit = (new_end < VECTOR_LIMIT_MID) ? VECTOR_LIMIT_MIN + : VECTOR_LIMIT_MID ; + while (new_end > ((new_limit * 7) / 8)) + new_limit *= 2 ; + } ; + + v->p_items = + XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ; + + v->limit = new_limit ; +} ; + +/*------------------------------------------------------------------------------ + * Extend vector and set new (logical) end. + * + * Extends body if required. + * Ensures everything between old and new end is set NULL. + * + * NB: expects new end > old end, but copes with new end <= old end. + */ +extern void +vector_extend(vector v, vector_length_t new_end) +{ + vector_length_t old_end = v->end ; + + if (new_end > v->limit) + vector_new_limit(v, new_end) ; + v->end = new_end ; + + if (new_end > old_end) + memset(&v->p_items[old_end], 0, P_ITEMS_SIZE(new_end - old_end)) ; +} ; + +/*------------------------------------------------------------------------------ + * Insert entries into vector: insert 'n' NULL entries at location 'i'. + * + * Updates end (and limit) to be at least 'i' + 'n'. + * (So if 'i' < end then end becomes end + 'n', else end becomes 'i' + 'n'.) + */ +extern void +vector_insert(vector v, vector_index_t i, vector_length_t n) +{ + vector_length_t old_end, new_end ; + vector_length_t n_above ; + + /* If i < old end, then we are inserting n NULLs, and need + * to shuffle at least one item up. + * else we are setting new end to i + n and need to NULL + * fill from old end to the new end. + */ + old_end = v->end ; + if (i < old_end) + { + if (n == 0) + return ; /* give up now if not inserting anything */ + n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */ + new_end = old_end + n ; + } + else + { + n_above = 0 ; /* nothing to shuffle up. */ + new_end = i + n ; + i = old_end ; /* where to zeroize from */ + n = new_end - old_end ; /* how much to zeroize */ + } ; + + /* Now we extend the body if we need to. */ + if (new_end > v->limit) + vector_new_limit(v, new_end) ; + v->end = new_end ; + + if (n_above > 0) + memmove(&v->p_items[i + n], &v->p_items[i], P_ITEMS_SIZE(n_above)) ; + + memset(&v->p_items[i], 0, P_ITEMS_SIZE(n)) ; +} ; + +/*------------------------------------------------------------------------------ + * Delete items from vector: delete 'n' items at location 'i'. + * + * Does nothing if 'i' is beyond current end of vector or if 'n' == 0. + * + * Deletes from 'i' to end if less than 'n' items to the end. + * + * NB: does NOT change the size of the body. + * + * NB: it is the caller's responsibility to have released any memory allocated + * for the items that are being deleted. +*/ +extern void +vector_delete(vector v, vector_index_t i, vector_length_t n) +{ + vector_length_t old_end, new_end ; + + old_end = v->end ; + + if ((i >= old_end) || (n == 0)) + return ; + + /* If i + n < old_end, we have 1 or more items to keep and move down */ + if ((i + n) < old_end) + { + memmove(&v->p_items[i], &v->p_items[i + n], + P_ITEMS_SIZE(old_end - (i + n))) ; + new_end = old_end - n ; + } + else + { + new_end = i ; /* We are keeping nothing above 'i' */ + /* NB: new_end < old_end */ + } ; + + v->end = new_end ; /* account for stuff dropped */ +} ; + +/*------------------------------------------------------------------------------ + * Discard entries from vector: discard entries from location 'i' onwards. + * + * Releases memory from 'i' onwards. + * Releases the body altogether if this sets the vector empty ('i' == 0). + * Sets new end of vector iff 'i' < current end. + * + * Does nothing if 'i' is beyond current limit (physical end) of vector. + * + * NB: it is the caller's responsibility to have released any memory allocated + * for the items that are being discarded. +*/ +extern void +vector_discard(vector v, vector_index_t i) +{ + if (i >= v->limit) + return ; + + if (i == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else + { + v->limit = i ; + if (i < v->end) + v->end = i ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(i)) ; + } ; +} ; + +/* Chop vector at the current (logical) end. + * + * Releases the body altogether if the vector is currently empty. + */ +extern void +vector_chop(vector v) +{ + vector_length_t new_limit = v->end ; + + if (new_limit == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else if (new_limit != v->limit) + { + v->limit = new_limit ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ; + } ; +} ; + +/* Decant vector into a new body allocated to current logical size, and + * release the old body. + * + * Releases the body altogether if the vector is currently empty. +*/ +void +vector_decant(vector v) +{ + p_vector_item* p_old_body ; + vector_length_t new_limit = v->end ; + + if (new_limit == 0) + vector_reset(v, 0) ; /* reset, without releasing the structure */ + else + { + p_old_body = v->p_items ; + + vector_init_new(v, new_limit) ; /* initialise with new body */ + + memcpy(v->p_items, p_old_body, P_ITEMS_SIZE(new_limit)) ; + /* copy the old body across */ + v->end = new_limit ; + + XFREE(MTYPE_VECTOR_BODY, p_old_body) ; + } ; +} ; diff --git a/lib/tstring.h b/lib/tstring.h new file mode 100644 index 00000000..4dd45edb --- /dev/null +++ b/lib/tstring.h @@ -0,0 +1,139 @@ +/* Temporary string handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_TSTRING_H +#define _ZEBRA_TSTRING_H + +#include "misc.h" +#include "zassert.h" +#include "memory.h" + +/*============================================================================== + * tstrings are allocated on the stack, but if (unexpectedly) the standard + * size is not enough, then they can be allocated dynamically. + * + * To declare a "tstring": + * + * tstring(foo, 64) ; // creates a "tstring" variable called "foo" + * // with 64 char buffer. + * + * Can then: + * + * s = tstring_set_len(foo, n) ; // ensures have buffer for n+1 chars + * s = tstring_set(foo, "...") ; // copies "..." (with '\0') to buffer + * s = tstring_set_n(foo, q, n) ; // copies n characters from q to buffer + * and '\0' terminates + * + * If can fit stuff in the buffer, will do so. Otherwise will allocate an + * MTYPE_TMP buffer to work in. + * + * And before leaving the scope of "foo" must: + * + * tstring_free(foo) ; // releases any dynamically allocated memory. + */ + +struct tstring +{ + usize size ; + char* str ; + char* alloc ; +} ; + +typedef struct tstring tstring[1] ; + +/* tstring(foo, 93) ; -- declare the variable "foo". */ +#define tstring_t(name, sz) \ + char _zlxq_##name##_b[ ((sz) + 7) & 0xFFFFFFF8] ; \ + tstring name = { { .size = ((sz) + 7) & 0xFFFFFFF8, \ + .str = _zlxq_##name##_b, \ + .alloc = NULL } } + +/*------------------------------------------------------------------------------ + * Ensure the tstring "foo" can accomodate at least "len" characters plus the + * terminating '\0'. + * + * Returns: address of buffer + * + * NB: address of buffer may not be the same as returned by a previous operation + * on foo. Also, previous contents of foo may be lost. + */ +Inline char* +tstring_set_len(struct tstring* ts, usize len) +{ + if (len >= ts->size) + { + ts->size = len + 1 ; + ts->str = ts->alloc = XREALLOC(MTYPE_TMP, ts->alloc, len + 1) ; + } ; + + return ts->str ; +} ; + +/*------------------------------------------------------------------------------ + * Copy "len" characters from "src" to the tstring "foo", and append a + * terminating '\0'. + * + * The "src" address is ignored if "len" == 0 (sets "foo" to be empty string). + * + * Returns: address of buffer + * + * NB: address of buffer may not be the same as returned by a previous operation + * on foo. Also, previous contents of foo may be lost. + */ +static inline char* +tstring_set_n(struct tstring* ts, const char* str, usize len) +{ + char* tss = tstring_set_len(ts, len) ; + + if (len > 0) + memcpy(tss, str, len) ; + *(tss + len) = '\0' ; + + return tss ; +} ; + +/*------------------------------------------------------------------------------ + * Copy the string "str" to the tstring "foo", with terminating '\0'. + * + * If "str" is NULL, sets "foo" to be an empty string. + * + * Returns: address of buffer + * + * NB: address of buffer may not be the same as returned by a previous operation + * on foo. Also, previous contents of foo may be lost. + */ +static inline char* +tstring_set(struct tstring* ts, const char* str) +{ + return tstring_set_n(ts, str, (str != NULL) ? strlen(str) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * If have dynamically allocated buffer for tstring "foo", release it now. + */ +static inline void +tstring_free(struct tstring* ts) +{ + if (ts->alloc != NULL) + XFREE(MTYPE_TMP, ts->alloc) ; /* sets ts->alloc NULL */ +} ; + +#endif /* _ZEBRA_TSTRING_H */ @@ -24,20 +24,17 @@ #ifndef _ZEBRA_UTY_H #define _ZEBRA_UTY_H -#include <stdbool.h> +#include "misc.h" +#include "vargs.h" #include "qpthreads.h" #include "qpnexus.h" #include "thread.h" #include "list_util.h" #include "vty.h" +#include "vty_io_basic.h" #include "node_type.h" -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif - /*============================================================================== * This is stuff which is used by the close family of: * @@ -67,15 +64,6 @@ extern vty_io vio_list_base ; extern vty_io vio_monitors_base ; extern vty_io vio_death_watch ; -union vty_watch_dog -{ - qtimer qnexus ; /* when running qnexus */ - struct thread* thread; /* when running threads */ - void* anon ; -}; - -extern union vty_watch_dog vty_watch_dog ; - extern struct thread_master* vty_master ; extern unsigned long vty_timeout_val ; @@ -125,7 +113,7 @@ extern int vty_assert_fail ; #endif Inline void -VTY_LOCK(void) +VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */ { qpt_mutex_lock(&vty_mutex) ; if (VTY_DEBUG) @@ -133,13 +121,19 @@ VTY_LOCK(void) } ; Inline void -VTY_UNLOCK(void) +VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */ { if (VTY_DEBUG) --vty_lock_count ; qpt_mutex_unlock(&vty_mutex) ; } ; +Inline bool /* true => is (effectively) cli thread */ +vty_is_cli_thread(void) +{ + return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ; +} ; + /* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that * is required. * @@ -167,9 +161,8 @@ VTY_ASSERT_LOCKED(void) Inline void VTY_ASSERT_CLI_THREAD(void) { - if (qpthreads_enabled) - if (!qpt_thread_is_self(vty_cli_nexus->thread_id)) - VTY_ASSERT_FAILED() ; + if (!vty_is_cli_thread()) + VTY_ASSERT_FAILED() ; } ; #else diff --git a/lib/vargs.h b/lib/vargs.h new file mode 100644 index 00000000..5f0207cb --- /dev/null +++ b/lib/vargs.h @@ -0,0 +1,34 @@ +/* Variable arguments definitions + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VARGS_H +#define _ZEBRA_VARGS_H + +#include <stdarg.h> + +/* GCC has printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#endif /* _ZEBRA_VARGS_H */ diff --git a/lib/vector.c b/lib/vector.c index 646f19a5..6df8d409 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -84,11 +84,13 @@ */ #define P_ITEMS_SIZE(n) SIZE(p_vector_item, n) + /*============================================================================== * Initialisation, allocation, reset etc. */ -/* Initialise a brand new vector, setting it empty. +/*------------------------------------------------------------------------------ + * Initialise a brand new vector, setting it empty. * * Allocates vector structure if none given -- that is, if v == NULL. * @@ -98,41 +100,50 @@ * NB: discards any existing vector body -- so it is the caller's responsibility * to release any existing body, and any items in that body. */ -vector -vector_init_new(vector v, unsigned int size) +extern vector +vector_init_new(vector v, vector_length_t limit) { if (v == NULL) v = XCALLOC(MTYPE_VECTOR, sizeof(struct vector)) ; else memset(v, 0, sizeof(struct vector)) ; - if (size != 0) + if (limit != 0) { - v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(size)) ; - v->limit = size ; + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(limit)) ; + v->limit = limit ; } ; return v ; } ; -/* Initialize vector : allocate memory and return vector. +/*------------------------------------------------------------------------------ + * Initialize vector : allocate memory and return vector. * allocates body with at least 1 entry. + * + * This is a "legacy" function. */ -vector -vector_init (unsigned int size) +extern vector +vector_init (vector_length_t limit) { - return vector_init_new(NULL, size ? size : 1) ; /* at least 1 entry */ + return vector_init_new(NULL, limit ? limit : 1) ; /* at least 1 entry */ } ; -/* Basic: free the vector body and the vector structure. */ +/*------------------------------------------------------------------------------ + * Basic: free the vector body and the vector structure. + * + * NB: it is the caller's responsibility to release any vector item values + * *before* doing this. + */ void vector_free (vector v) { XFREE (MTYPE_VECTOR_BODY, v->p_items); XFREE (MTYPE_VECTOR, v); -} +} ; -/* Re-Initialize vector (or create new one), setting it empty. +/*------------------------------------------------------------------------------ + * Re-initialise vector (or create new one), setting it empty. * * Allocates vector structure if none given -- that is, if v == NULL. * @@ -142,27 +153,29 @@ vector_free (vector v) * Otherwise ensures existing body is at least the required size, or a body * of exactly the required size is allocated. * - * NB: when re-initialising an existing vector it is the caller's - * responsibility to release any vector item values *before* doing this. + * NB: when re-initialising an existing vector it is the caller's responsibility + * to release any vector item values *before* doing this. * */ -vector -vector_re_init(vector v, unsigned int size) +extern vector +vector_re_init(vector v, vector_length_t limit) { if ((v == NULL) || (v->p_items == NULL)) - return vector_init_new(v, size) ; + return vector_init_new(v, limit) ; v->end = 0 ; - if (v->limit < size) + if (v->limit < limit) { - v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(size)) ; - v->limit = size ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, + P_ITEMS_SIZE(limit)) ; + v->limit = limit ; } ; return v ; } ; -/* Free the vector body, and free the vector structure or reset it. +/*------------------------------------------------------------------------------ + * Free the vector body, and (if required) free the vector structure. * * Return NULL if releases vector, otherwise the address of the vector. * @@ -170,16 +183,17 @@ vector_re_init(vector v, unsigned int size) * *before* doing this. */ vector -vector_reset(vector v, int free_structure) +vector_reset(vector v, free_keep_b free_structure) { if (v == NULL) - return NULL ; /* allow for already freed vector */ + return NULL ; /* allow for already freed vector */ if (v->p_items != NULL) XFREE(MTYPE_VECTOR_BODY, v->p_items) ; if (free_structure) { + confirm(free_it == true) ; XFREE(MTYPE_VECTOR, v) ; return NULL ; } @@ -187,7 +201,8 @@ vector_reset(vector v, int free_structure) return vector_init_new(v, 0) ; } ; -/* Set vector length to be (at least) the given fixed length. +/*------------------------------------------------------------------------------ + * Set vector length to be (at least) the given fixed length. * * There must be a vector. * @@ -201,8 +216,8 @@ vector_reset(vector v, int free_structure) * * Note that the existing contents of the vector are preserved in all cases. */ -extern void -vector_set_new_min_length(vector v, unsigned int len) +Private void +vector_set_new_min_length(vector v, vector_length_t len) { assert (v != NULL) ; @@ -211,7 +226,8 @@ vector_set_new_min_length(vector v, unsigned int len) if (v->p_items == NULL) v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ; else - v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(len)); + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, + P_ITEMS_SIZE(len)) ; v->limit = len ; } ; @@ -219,8 +235,8 @@ vector_set_new_min_length(vector v, unsigned int len) vector_extend(v, len) ; } ; -/* Pop item from vector, stepping past any NULLs. - * +/*------------------------------------------------------------------------------ + * Pop item from vector, stepping past any NULLs. * If vector is empty, free the body and, if required, the vector structure. * * Useful for emptying out and discarding a vector: @@ -231,7 +247,7 @@ vector_set_new_min_length(vector v, unsigned int len) * Returns NULL if vector was empty and has now been freed as required. */ p_vector_item -vector_ream(vector v, int free_structure) +vector_ream(vector v, free_keep_b free_structure) { p_vector_item p_v ; @@ -242,61 +258,23 @@ vector_ream(vector v, int free_structure) { p_v = v->p_items[--v->end] ; if (p_v != NULL) - return p_v ; /* return non-NULL item */ + return p_v ; /* return non-NULL item */ } ; /* vector is empty: free the body, and (if required) the vector structure. */ vector_reset(v, free_structure) ; - return NULL ; /* signals end */ + return NULL ; /* signals end */ } ; /*============================================================================== * Unset item, condensing and trimming vector. - */ - -/* Trim any NULL entries at the current (logical) end of the vector. * - * Returns the (new) end of the vector. + * These are legacy operations. */ -extern vector_index -vector_trim(vector v) -{ - vector_index i = v->end ; - while ((i > 0) && (v->p_items[i - 1] == NULL)) - --i ; - v->end = i ; - return i ; -} ; -/* Removes any NULL entries from the given vector. - * - * Returns the (new) end of the vector. - */ -extern vector_index vector_condense(vector v) -{ - vector_index i = 0 ; - vector_index j ; - - /* Find first NULL, if any */ - while ((i < v->end) && (v->p_items[i] != NULL)) - ++i ; - - /* Quit if no NULLs (or vector is empty) */ - if (i == v->end) - return i ; - - /* Shuffle any remaining non-NULL down */ - for (j = i + 1 ; j < v->end ; ++j) - if (v->p_items[j] != NULL) - v->p_items[i++] = v->p_items[j] ; - - v->end = i ; - - return i ; -} ; - -/* Unset item at given index (ie set it NULL). +/*------------------------------------------------------------------------------ + * Unset item at given index (ie set it NULL). * * Return the old value of the item. * @@ -304,7 +282,7 @@ extern vector_index vector_condense(vector v) * end backwards until finds a non-NULL item, or the vector becomes empty. */ extern p_vector_item -vector_unset_item(vector v, vector_index i) +vector_unset_item(vector v, vector_index_t i) { p_vector_item was ; @@ -324,15 +302,60 @@ vector_unset_item(vector v, vector_index i) return was ; } ; +/*------------------------------------------------------------------------------ + * Trim any NULL entries at the current (logical) end of the vector. + * + * Returns the (new) length (end) of the vector. + */ +extern vector_length_t +vector_trim(vector v) +{ + vector_length_t e = v->end ; + while ((e > 0) && (v->p_items[e - 1] == NULL)) + --e ; + v->end = e ; + return e ; +} ; + +/*------------------------------------------------------------------------------ + * Removes any NULL entries from the given vector. + * + * Returns the (new) length (end) of the vector. + */ +extern vector_length_t +vector_condense(vector v) +{ + vector_length_t e = 0 ; + vector_index_t j ; + + /* Find first NULL, if any */ + while ((e < v->end) && (v->p_items[e] != NULL)) + ++e ; + + /* Quit if no NULLs (or vector is empty) */ + if (e == v->end) + return e ; + + /* Shuffle any remaining non-NULL down */ + for (j = e + 1 ; j < v->end ; ++j) + if (v->p_items[j] != NULL) + v->p_items[e++] = v->p_items[j] ; + + v->end = e ; + + return e ; +} ; + /*============================================================================== * Inserting and deleting items. */ -/* Insert item in vector, before item at given position +/*------------------------------------------------------------------------------ + * Insert item in vector, before item at given position * Move items and extend vector as required. */ -void -vector_insert_item(vector v, vector_index i, void* p_v) +extern void +vector_insert_item(vector v, vector_index_t i, void* p_v) { if ((i == v->end) && (i < v->limit)) ++v->end ; @@ -342,7 +365,8 @@ vector_insert_item(vector v, vector_index i, void* p_v) v->p_items[i] = (p_vector_item)p_v ; } ; -/* Insert item in vector at given position with rider: +/*------------------------------------------------------------------------------ + * Insert item in vector at given position with rider: * * rider < 0 -- insert before the item at the given position * rider == 0 -- insert at the given position -- REPLACING any existing value @@ -353,8 +377,8 @@ vector_insert_item(vector v, vector_index i, void* p_v) * * Move items and extend vector as required. */ -void -vector_insert_item_here(vector v, vector_index i, int rider, +extern void +vector_insert_item_here(vector v, vector_index_t i, int rider, p_vector_item p_v) { if (rider == 0) @@ -365,18 +389,21 @@ vector_insert_item_here(vector v, vector_index i, int rider, vector_insert_item(v, i, p_v) ; } ; -/* Delete item from vector. +/*------------------------------------------------------------------------------ + * Delete item from vector. * * Move items as required. Reduces number of items in the vector (unless * the item in question is beyond the end of the vector !) * + * Returns: the item that has just been deleted. + * * NB: it is the caller's responsibility to release memory used by any * current value of the item, if required. * * NB: does NOT change the size of the vector body. */ -p_vector_item -vector_delete_item(vector v, vector_index i) +extern p_vector_item +vector_delete_item(vector v, vector_index_t i) { p_vector_item p_e ; if (i < v->end) @@ -396,19 +423,23 @@ vector_delete_item(vector v, vector_index i) * Moving items within vector. */ -/* Move item in vector from source position to destination position. +/*------------------------------------------------------------------------------ + * Move item in vector from source position to destination position. + * * Moves intervening items up or down as required. + * * Extends vector to include the destination, if required. + * * A source item beyond the end of the vector is implicitly NULL. */ -void -vector_move_item(vector v, vector_index i_dst, vector_index i_src) +extern void +vector_move_item(vector v, vector_index_t i_dst, vector_index_t i_src) { p_vector_item* pp_s ; p_vector_item* pp_d ; p_vector_item p_e ; - vector_index old_end = v->end ; + vector_length_t old_end = v->end ; /* Worry about whether both source and destination exist. */ if (i_dst >= old_end) @@ -437,7 +468,8 @@ vector_move_item(vector v, vector_index i_dst, vector_index i_src) *pp_d = p_e ; /* put down the item to move */ } ; -/* Move item in vector to given position with rider: +/*------------------------------------------------------------------------------ + * Move item in vector to given position with rider: * * rider < 0 -- move to before the item at the given position * rider == 0 -- move to replace item at the given position @@ -448,14 +480,14 @@ vector_move_item(vector v, vector_index i_dst, vector_index i_src) * * Move items and extend vector as required. */ -void -vector_move_item_here(vector v, vector_index i_dst, int rider, - vector_index i_src) +extern void +vector_move_item_here(vector v, vector_index_t i_dst, int rider, + vector_index_t i_src) { if (rider != 0) { if (rider > 0) - ++i_dst ; + ++i_dst ; vector_move_item(v, i_dst, i_src) ; } else @@ -466,21 +498,23 @@ vector_move_item_here(vector v, vector_index i_dst, int rider, } ; } ; -/* Reverse vector: reverse the order of items in the vector. +/*------------------------------------------------------------------------------ + * Reverse vector: reverse the order of items in the vector. */ -void +extern void vector_reverse(vector v) { if (v != NULL) vector_part_reverse(v, 0, v->end) ; } ; -/* Reverse portion of vector. +/*------------------------------------------------------------------------------ + * Reverse portion of vector. */ -void -vector_part_reverse(vector v, vector_index i, unsigned int n) +extern void +vector_part_reverse(vector v, vector_index_t i, vector_length_t n) { - vector_index j ; + vector_index_t j ; if (v == NULL) return ; @@ -504,12 +538,16 @@ vector_part_reverse(vector v, vector_index i, unsigned int n) * Copying, moving and appending entire vectors. */ -static void vector_new_limit(vector v, vector_index new_end) ; +static void vector_new_limit(vector v, vector_length_t new_end) ; -/* Shallow vector copy -- copies pointers to item values, not the values. */ -/* Creates a new vector. */ -/* NB: copies whole body, including stuff beyond (logical) end ! */ -/* TODO: is this behaviour required ? */ +/*------------------------------------------------------------------------------ + * Shallow vector copy -- copies pointers to item values, not the values. + * + * Creates a new vector. + * + * NB: creates new vector with same limit as existing one, but copies only + * the known items (ie up to end, not up to limit). + */ vector vector_copy (vector v) { @@ -518,12 +556,13 @@ vector_copy (vector v) new->end = v->end; if (v->limit > 0) - memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->limit)) ; + memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->end)) ; return new; } -/* Shallow vector copy -- copies pointers to item values, not the values. +/*------------------------------------------------------------------------------ + * Shallow vector copy -- copies pointers to item values, not the values. * Creates a new vector or re-initialises an existing one. * * NB: creates new vector with same limit as existing one, but copies only @@ -552,7 +591,9 @@ vector_copy_here(vector dst, vector src) return dst ; } ; -/* Vector move -- moves body of vector. +/*------------------------------------------------------------------------------ + * Vector move -- moves body of vector. + * * Creates a new vector or re-initialises an existing one. * Leaves the source vector empty -- does not release the structure. * @@ -564,7 +605,7 @@ vector_copy_here(vector dst, vector src) * * NB: do NOT try moving a vector to itself !! */ -vector +extern vector vector_move_here(vector dst, vector src) { assert((src != NULL) && (src != dst)) ; @@ -574,23 +615,26 @@ vector_move_here(vector dst, vector src) else dst = vector_init_new(dst, 0) ; /* Create new structure sans body. */ - *dst = *src ; /* Copy the vector structure */ + *dst = *src ; /* Copy the vector structure */ - vector_init_new(src, 0) ; /* Set empty, forgetting body */ + vector_init_new(src, 0) ; /* Set empty, forgetting body */ return dst ; } -/* Shallow vector copy append -- copies pointers to item values, not the values. +/*------------------------------------------------------------------------------ + * Shallow vector copy append -- copies pointers to item values, not the values. + * * Appends copied pointers to the destination vector. + * * Creates a new destination vector if required. * * NB: Can append to self. */ -vector +extern vector vector_copy_append(vector dst, vector src) { - vector_index new_end ; + vector_index_t new_end ; assert(src != NULL) ; @@ -613,14 +657,18 @@ vector_copy_append(vector dst, vector src) return dst ; } ; -/* Vector move append -- moves pointers to item values. +/*------------------------------------------------------------------------------ + * Vector move append -- moves pointers to item values. + * * Appends moved pointers to the destination vector. + * * Creates a new destination vector if required (dst == NULL). + * * Leaves the source vector empty -- does not release the structure. * * NB: do NOT try moving a vector to itself !! */ -vector +extern vector vector_move_append(vector dst, vector src) { assert((src != NULL) && (src != dst)) ; @@ -629,7 +677,7 @@ vector_move_append(vector dst, vector src) return vector_move_here(dst, src) ; /* Easy way to do it if dst empty */ vector_copy_append(dst, src) ; /* Extend dst and copy src */ - vector_init_new(src, 0) ; /* Set empty, forgetting body */ + vector_init_new(src, 0) ; /* Set empty, forgetting body */ return dst ; } ; @@ -717,19 +765,19 @@ vector_move_append(vector dst, vector src) * to ensure the vector structure is currently valid (by vector_init_new() * or by ensuring it is zeroized). */ -vector +extern vector vector_sak(int to_copy, vector to, - vector dst, vector_index i_dst, unsigned int n_dst, - vector src, vector_index i_src, unsigned int n_src, int src_move) + vector dst, vector_index_t i_dst, vector_length_t n_dst, + vector src, vector_index_t i_src, vector_length_t n_src, int src_move) { int dst_replace ; /* true => replace portion of 'dst' */ - vector_index new_dst_end = 0 ; /* new end of dst */ + vector_index_t new_dst_end = 0 ; /* new end of dst */ - unsigned int n_dst_nulls ; /* number of implicit NULLs to add */ - unsigned int n_dst_move ; /* number of items to move up or down */ - unsigned int n_src_real ; /* number of items to really copy */ - unsigned int n_src_nulls ; /* number of implicit NULLs to "copy" */ + vector_length_t n_dst_nulls ; /* number of implicit NULLs to add */ + vector_length_t n_dst_move ; /* number of items to move up or down */ + vector_length_t n_src_real ; /* number of items to really copy */ + vector_length_t n_src_nulls ; /* number of implicit NULLs to "copy" */ assert((to == NULL) || (dst == NULL) || (to != dst)) ; assert((src == NULL) || (dst == NULL) || (src != dst)) ; @@ -855,11 +903,15 @@ vector_sak(int to_copy, vector to, * Legacy Vector Operations */ -/* Set value to the smallest empty slot. */ -int +/*------------------------------------------------------------------------------ + * Set value to the smallest empty slot. + * + * Returns: index of slot used. + */ +extern int vector_set (vector v, void *val) { - vector_index i; + vector_index_t i; i = 0 ; while (1) @@ -878,41 +930,55 @@ vector_set (vector v, void *val) v->p_items[i] = val; - return i; + return i ; } -/* Set value to specified index slot. */ -int -vector_set_index (vector v, vector_index i, void *val) +/*------------------------------------------------------------------------------ + * Set value to specified index slot. + * + * Returns: index of slot (as given) + */ +extern int +vector_set_index (vector v, vector_index_t i, void *val) { vector_ensure (v, i); v->p_items[i] = val; return i; } -/* Look up vector. */ -p_vector_item -vector_lookup (vector v, vector_index i) +/*------------------------------------------------------------------------------ + * Look up vector -- get the i'th item. + * + * Returns: the i'th item -- NULL if item is null, or i >= logical len of vector + */ +extern p_vector_item +vector_lookup (vector v, vector_index_t i) { if (i >= v->end) return NULL; return v->p_items[i]; } -/* Lookup vector, ensure it. */ -p_vector_item -vector_lookup_ensure (vector v, vector_index i) +/*------------------------------------------------------------------------------ + * Lookup vector, ensure it -- get i'th item and ensure logical len > i. + * + * Returns: the i'th item -- NULL if item is null + */ +extern p_vector_item +vector_lookup_ensure (vector v, vector_index_t i) { vector_ensure (v, i); return v->p_items[i]; } -/* Count the number of not empty slots. */ -vector_index +/*------------------------------------------------------------------------------ + * Count the number of not empty slots. + */ +extern vector_length_t vector_count (vector v) { - vector_index i; - unsigned count = 0; + vector_index_t i; + vector_length_t count = 0; for (i = 0; i < v->end; i++) if (v->p_items[i] != NULL) @@ -925,7 +991,8 @@ vector_count (vector v) * Sorting and Searching vector. */ -/* Sort the given vector. +/*------------------------------------------------------------------------------ + * Sort the given vector. * * NB: the comparison function receives a pointer to the pointer to the * vector item's value. @@ -933,18 +1000,19 @@ vector_count (vector v) * NB: if there are NULL items in the vector, the comparison function MUST * be ready for them. */ -void +extern void vector_sort(vector v, vector_sort_cmp* cmp) { if (v->end <= 1) - return ; /* Stop dead if 0 or 1 items */ + return ; /* Stop dead if 0 or 1 items */ typedef int qsort_cmp(const void*, const void*) ; qsort(v->p_items, v->end, sizeof(p_vector_item), (qsort_cmp*)cmp) ; } ; -/* Perform binary search on the given vector. +/*------------------------------------------------------------------------------ + * Perform binary search on the given vector. * * The vector MUST be sorted in the order implied by the comparison function * given. The vector need not contain unique values, but this search makes @@ -978,17 +1046,17 @@ vector_sort(vector v, vector_sort_cmp* cmp) * NB: if there are NULL items in the vector, the comparison function MUST * be ready for them. */ -vector_index +extern vector_index_t vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, - int* result) + int* result) { - vector_index il, iv, ih ; + vector_index_t il, iv, ih ; int c ; if (v->end <= 1) { *result = (v->end == 0) ? -1 : cmp(&p_val, (const void**)&v->p_items[0]) ; - return 0 ; /* Stop dead if 0 or 1 items */ + return 0 ; /* Stop dead if 0 or 1 items */ } ; /* We have at least two items. */ @@ -998,36 +1066,36 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, /* Pick off the edge cases: >= last and <= first. */ if ((c = cmp(&p_val, (const void**)&v->p_items[ih])) >= 0) { - *result = c ; /* 0 => found. +1 => val > last */ - return ih ; /* return high index. */ + *result = c ; /* 0 => found. +1 => val > last */ + return ih ; /* return high index. */ } ; if ((c = cmp(&p_val, (const void**)&v->p_items[il])) <= 0) { - *result = c ; /* 0 => found. -1 => val < first */ - return il ; /* return low index. */ + *result = c ; /* 0 => found. -1 => val < first */ + return il ; /* return low index. */ } - /* Now binary chop. We know that item[il] < val < item[ih] */ - /* We also know that il < ih */ + /* Now binary chop. We know that item[il] < val < item[ih] */ + /* We also know that il < ih */ while (1) { iv = (il + ih) / 2 ; - if (iv == il) /* true if (ih == il+1) */ - { - *result = +1 ; - return il ; /* return il: item[il] < val < item[il+1] */ - } ; - /* We now know that il < iv < ih */ + if (iv == il) /* true if (ih == il+1) */ + { + *result = +1 ; + return il ; /* return il: item[il] < val < item[il+1] */ + } ; + /* We now know that il < iv < ih */ c = cmp(&p_val, (const void**)&v->p_items[iv]) ; if (c == 0) - { - *result = 0 ; - return iv ; /* found !! */ - } + { + *result = 0 ; + return iv ; /* found !! */ + } if (c < 0) - ih = iv ; /* step down iv > il, so new ih > il */ + ih = iv ; /* step down iv > il, so new ih > il */ else - il = iv ; /* step up iv < ih, so new il < ih */ + il = iv ; /* step up iv < ih, so new il < ih */ } ; } ; @@ -1041,7 +1109,8 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, /* Round 'x' up to a multiple of 'm' */ #define multiple(x, m) ((((x) + (m) - 1) / (m)) * (m)) -/* Set new limit to suit new end for given vector. +/*------------------------------------------------------------------------------ + * Set new limit to suit new end for given vector. * * The new limit will be at least: VECTOR_LIMIT_MIN. * @@ -1070,10 +1139,10 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, * of P_ITEMS_SIZE will ? */ static void -vector_new_limit(vector v, vector_index new_end) +vector_new_limit(vector v, vector_index_t new_end) { - vector_index old_limit = v->limit ; - vector_index new_limit ; + vector_length_t old_limit = v->limit ; + vector_length_t new_limit ; if (new_end > ((VECTOR_LIMIT_DOUBLE_MAX * 7) / 8)) { @@ -1102,17 +1171,18 @@ vector_new_limit(vector v, vector_index new_end) v->limit = new_limit ; } ; -/* Extend vector and set new (logical) end. +/*------------------------------------------------------------------------------ + * Extend vector and set new (logical) end. * * Extends body if required. * Ensures everything between old and new end is set NULL. * * NB: expects new end > old end, but copes with new end <= old end. */ -void -vector_extend(vector v, vector_index new_end) +extern void +vector_extend(vector v, vector_length_t new_end) { - vector_index old_end = v->end ; + vector_length_t old_end = v->end ; if (new_end > v->limit) vector_new_limit(v, new_end) ; @@ -1122,16 +1192,17 @@ vector_extend(vector v, vector_index new_end) memset(&v->p_items[old_end], 0, P_ITEMS_SIZE(new_end - old_end)) ; } ; -/* Insert entries into vector: insert 'n' NULL entries at location 'i'. +/*------------------------------------------------------------------------------ + * Insert entries into vector: insert 'n' NULL entries at location 'i'. * * Updates end (and limit) to be at least 'i' + 'n'. * (So if 'i' < end then end becomes end + 'n', else end becomes 'i' + 'n'.) */ -void -vector_insert(vector v, vector_index i, unsigned int n) +extern void +vector_insert(vector v, vector_index_t i, vector_length_t n) { - vector_index old_end, new_end ; - unsigned int n_above ; + vector_length_t old_end, new_end ; + vector_length_t n_above ; /* If i < old end, then we are inserting n NULLs, and need * to shuffle at least one item up. @@ -1142,19 +1213,19 @@ vector_insert(vector v, vector_index i, unsigned int n) if (i < old_end) { if (n == 0) - return ; /* give up now if not inserting anything */ - n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */ + return ; /* give up now if not inserting anything */ + n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */ new_end = old_end + n ; } else { - n_above = 0 ; /* nothing to shuffle up. */ + n_above = 0 ; /* nothing to shuffle up. */ new_end = i + n ; i = old_end ; /* where to zeroize from */ n = new_end - old_end ; /* how much to zeroize */ } ; - /* Now we extend the body if we need to. */ + /* Now we extend the body if we need to. */ if (new_end > v->limit) vector_new_limit(v, new_end) ; v->end = new_end ; @@ -1165,7 +1236,8 @@ vector_insert(vector v, vector_index i, unsigned int n) memset(&v->p_items[i], 0, P_ITEMS_SIZE(n)) ; } ; -/* Delete items from vector: delete 'n' items at location 'i'. +/*------------------------------------------------------------------------------ + * Delete items from vector: delete 'n' items at location 'i'. * * Does nothing if 'i' is beyond current end of vector or if 'n' == 0. * @@ -1176,10 +1248,10 @@ vector_insert(vector v, vector_index i, unsigned int n) * NB: it is the caller's responsibility to have released any memory allocated * for the items that are being deleted. */ -void -vector_delete(vector v, vector_index i, unsigned int n) +extern void +vector_delete(vector v, vector_index_t i, vector_length_t n) { - vector_index old_end, new_end ; + vector_length_t old_end, new_end ; old_end = v->end ; @@ -1190,7 +1262,7 @@ vector_delete(vector v, vector_index i, unsigned int n) if ((i + n) < old_end) { memmove(&v->p_items[i], &v->p_items[i + n], - P_ITEMS_SIZE(old_end - (i + n))) ; + P_ITEMS_SIZE(old_end - (i + n))) ; new_end = old_end - n ; } else @@ -1202,7 +1274,8 @@ vector_delete(vector v, vector_index i, unsigned int n) v->end = new_end ; /* account for stuff dropped */ } ; -/* Discard entries from vector: discard entries from location 'i' onwards. +/*------------------------------------------------------------------------------ + * Discard entries from vector: discard entries from location 'i' onwards. * * Releases memory from 'i' onwards. * Releases the body altogether if this sets the vector empty ('i' == 0). @@ -1213,8 +1286,8 @@ vector_delete(vector v, vector_index i, unsigned int n) * NB: it is the caller's responsibility to have released any memory allocated * for the items that are being discarded. */ -void -vector_discard(vector v, vector_index i) +extern void +vector_discard(vector v, vector_index_t i) { if (i >= v->limit) return ; @@ -1230,34 +1303,37 @@ vector_discard(vector v, vector_index i) } ; } ; -/* Chop vector at the current (logical) end. +/*------------------------------------------------------------------------------ + * Chop vector at the current (logical) end. * * Releases the body altogether if the vector is currently empty. -*/ -void + */ +extern void vector_chop(vector v) { - vector_index new_limit = v->end ; + vector_length_t new_limit = v->end ; if (new_limit == 0) vector_reset(v, 0) ; /* reset, without releasing the structure */ else if (new_limit != v->limit) { v->limit = new_limit ; - v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ; + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, + P_ITEMS_SIZE(new_limit)) ; } ; } ; -/* Decant vector into a new body allocated to current logical size, and +/*------------------------------------------------------------------------------ + * Decant vector into a new body allocated to current logical size, and * release the old body. * * Releases the body altogether if the vector is currently empty. */ -void +extern void vector_decant(vector v) { p_vector_item* p_old_body ; - vector_index new_limit = v->end ; + vector_length_t new_limit = v->end ; if (new_limit == 0) vector_reset(v, 0) ; /* reset, without releasing the structure */ diff --git a/lib/vector.h b/lib/vector.h index f098d78a..58b9e415 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -25,30 +25,31 @@ #ifndef _ZEBRA_VECTOR_H #define _ZEBRA_VECTOR_H -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif +#include "misc.h" /*------------------------------------------------------------------------------ * types and struct for vector * * NB: an entirely zero structure represents an entirely empty vector. - * - * TODO: could force vector_index to be 32 bits ? */ typedef void* p_vector_item ; -typedef unsigned int vector_index ; +typedef unsigned int vector_index_t ; +typedef unsigned int vector_length_t ; + +enum { + VECTOR_LENGTH_MAX = (vector_length_t)INT_MAX + 1 +} ; -typedef struct vector* vector ; /* pointer to vector structure */ -typedef struct vector vector_t ; /* embedded vector structure */ struct vector { - p_vector_item* p_items ; /* pointer to array of vector item pointers */ - vector_index end ; /* number of "active" item entries */ - vector_index limit ; /* number of allocated item entries */ + p_vector_item* p_items ; /* pointer to array of vector item pointers */ + vector_length_t end ; /* number of "active" item entries */ + vector_length_t limit ; /* number of allocated item entries */ }; +typedef struct vector vector_t[1] ; /* embedded vector structure */ +typedef struct vector* vector ; /* pointer to vector structure */ + /* Under very controlled circumstances, may access the vector body */ typedef p_vector_item const* vector_body_t ; @@ -83,7 +84,7 @@ typedef p_vector_item const* vector_body_t ; /* To walk all items in a vector: * - * vector_index i ; + * vector_index_t i ; * xxxxx* p_v ; * * for (VECTOR_ITEMS(v, p_v, i)) @@ -104,71 +105,65 @@ typedef p_vector_item const* vector_body_t ; * Prototypes. */ -extern vector vector_init (unsigned int size); -Inline void vector_ensure(vector v, vector_index i) ; +extern vector vector_init (vector_length_t size); +Inline void vector_ensure(vector v, vector_index_t i) ; extern int vector_set (vector v, void *val); -extern int vector_set_index (vector v, vector_index i, void *val); +extern int vector_set_index (vector v, vector_index_t i, void *val); #define vector_unset(v, i) (void)vector_unset_item(v, i) -extern vector_index vector_count (vector v); +extern vector_length_t vector_count (vector v); extern void vector_free (vector v); extern vector vector_copy (vector v); -extern void *vector_lookup (vector, vector_index); -extern void *vector_lookup_ensure (vector, vector_index); +extern void *vector_lookup (vector, vector_index_t); +extern void *vector_lookup_ensure (vector, vector_index_t); -extern vector vector_init_new(vector v, unsigned int size) ; -extern vector vector_re_init(vector v, unsigned int size) ; -extern vector vector_reset(vector v, int free_structure) ; -extern p_vector_item vector_ream(vector v, int free_structure) ; +extern vector vector_init_new(vector v, vector_length_t size) ; +extern vector vector_re_init(vector v, vector_length_t size) ; +extern vector vector_reset(vector v, free_keep_b free_structure) ; +extern p_vector_item vector_ream(vector v, free_keep_b free_structure) ; -/* Reset vector and free the vector structure. */ -#define vector_reset_free(v) vector_reset(v, 1) -/* Reset vector but free the heap structure. */ -#define vector_reset_keep(v) vector_reset(v, 0) -/* Ream out vector and free the vector structure. */ -#define vector_ream_free(v) vector_ream(v, 1) -/* Ream out vector but keep the vector structure. */ -#define vector_ream_keep(v) vector_ream(v, 0) +Inline void vector_set_min_length(vector v, vector_length_t len) ; +Private void vector_set_new_min_length(vector v, vector_length_t len) ; -Inline void vector_set_min_length(vector v, unsigned int len) ; -extern void vector_set_new_min_length(vector v, unsigned int len) ; - -Inline void vector_set_length(vector v, unsigned int len) ; +Inline void vector_set_length(vector v, vector_length_t len) ; #define vector_set_end(v, l) vector_set_length(v, l) -Inline vector_index vector_length(vector v) ; -#define vector_end(v) vector_length(v) -Inline int vector_is_empty(vector v) ; +Inline vector_length_t vector_length(vector v) ; +#define vector_end(v) vector_length(v) +Inline bool vector_is_empty(vector v) ; Inline vector_body_t vector_body(vector v) ; -Inline p_vector_item vector_get_item(vector v, vector_index i) ; +Inline p_vector_item vector_get_item(vector v, vector_index_t i) ; Inline p_vector_item vector_get_first_item(vector v) ; Inline p_vector_item vector_get_last_item(vector v) ; -Inline void vector_set_item(vector v, vector_index i, p_vector_item p_v) ; -Inline void vector_assign_item(vector v, vector_index dst, vector_index src) ; -extern p_vector_item vector_unset_item(vector v, vector_index i) ; -extern vector_index vector_trim(vector v) ; -extern vector_index vector_condense(vector v) ; - -extern void vector_insert_item(vector v, vector_index i, p_vector_item p_v) ; -extern void vector_insert_item_here(vector v, vector_index i, int rider, +Inline void vector_set_item(vector v, vector_index_t i, p_vector_item p_v) ; +Inline void vector_assign_item(vector v, vector_index_t dst, + vector_index_t src) ; +extern p_vector_item vector_unset_item(vector v, vector_index_t i) ; +extern vector_length_t vector_trim(vector v) ; +extern vector_length_t vector_condense(vector v) ; + +extern void vector_insert_item(vector v, vector_index_t i, p_vector_item p_v) ; +extern void vector_insert_item_here(vector v, vector_index_t i, int rider, p_vector_item p_v) ; -extern void vector_move_item(vector v, vector_index dst, vector_index src) ; -extern void vector_move_item_here(vector v, vector_index dst, int rider, - vector_index src) ; -extern p_vector_item vector_delete_item(vector v, vector_index i) ; +extern void vector_move_item(vector v, vector_index_t dst, vector_index_t src) ; +extern void vector_move_item_here(vector v, vector_index_t dst, int rider, + vector_index_t src) ; +extern p_vector_item vector_delete_item(vector v, vector_index_t i) ; extern void vector_reverse(vector v) ; -extern void vector_part_reverse(vector v, vector_index i, unsigned int n) ; +extern void vector_part_reverse(vector v, vector_index_t i, vector_length_t n) ; Inline void vector_push_item(vector v, p_vector_item p_v) ; Inline p_vector_item vector_pop_item(vector v) ; +Inline void vector_unshift_item(vector v, p_vector_item p_v) ; +Inline p_vector_item vector_shift_item(vector v) ; -extern void vector_insert(vector v, vector_index i, unsigned int n) ; -extern void vector_delete(vector v, vector_index i, unsigned int n) ; +extern void vector_insert(vector v, vector_index_t i, vector_length_t n) ; +extern void vector_delete(vector v, vector_index_t i, vector_length_t n) ; typedef int vector_bsearch_cmp(const void** pp_val, const void** item) ; -vector_index vector_bsearch(vector v, vector_bsearch_cmp* cmp, +vector_index_t vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, int* result) ; typedef int vector_sort_cmp(const void** a, const void** b) ; void vector_sort(vector v, vector_sort_cmp* cmp) ; @@ -192,15 +187,15 @@ extern vector vector_move_append(vector dst, vector src) ; vector_sak(0, NULL, dst, i_dst, n_dst, src, i_src, n_src, 1) extern vector vector_sak(int to_copy, vector to, - vector dst, vector_index i_dst, unsigned int n_dst, - vector src, vector_index i_src, unsigned int n_src, int src_move) ; + vector dst, vector_index_t i_dst, vector_length_t n_dst, + vector src, vector_index_t i_src, vector_length_t n_src, int src_move) ; -extern void vector_discard(vector v, vector_index i) ; +extern void vector_discard(vector v, vector_index_t i) ; extern void vector_chop(vector v) ; extern void vector_decant(vector v) ; -Inline vector_index vector_extend_by_1(vector v) ; -extern void vector_extend(vector v, vector_index new_end) ; +Inline vector_index_t vector_extend_by_1(vector v) ; +extern void vector_extend(vector v, vector_length_t new_end) ; /*============================================================================== * The inline functions: @@ -209,10 +204,10 @@ extern void vector_extend(vector v, vector_index new_end) ; /* Extend vector by one item at the end, which is about to be set. */ /* Returns index of new least item in the vector. */ /* NB: if left unset, the item may be UNDEFINED. */ -Inline vector_index +Inline vector_index_t vector_extend_by_1(vector v) { - vector_index i = v->end ; + vector_index_t i = v->end ; if (i < v->limit) return v->end++ ; /* simple if we have room */ @@ -225,7 +220,7 @@ vector_extend_by_1(vector v) /* Adjusts logical and physical end of the vector as required, filling */ /* with NULLs upto any new logical end. */ Inline void -vector_ensure(vector v, vector_index i) +vector_ensure(vector v, vector_index_t i) { if (i < v->end) /* trivial if within vector */ return ; @@ -241,7 +236,7 @@ vector_ensure(vector v, vector_index i) /* with NULLs upto any new logical end -- does not allocate any more */ /* than is exactly necessary. */ Inline void -vector_set_min_length(vector v, unsigned int len) +vector_set_min_length(vector v, vector_length_t len) { if (len > v->end) /* will not reduce the length */ vector_set_new_min_length(v, len) ; @@ -256,7 +251,7 @@ vector_set_min_length(vector v, unsigned int len) /* with NULLs upto any new logical end -- does not allocate any more */ /* than is exactly necessary. */ Inline void -vector_set_length(vector v, unsigned int len) +vector_set_length(vector v, vector_length_t len) { if (len > v->end) vector_set_new_min_length(v, len) ; /* Extend if new length greater */ @@ -265,14 +260,14 @@ vector_set_length(vector v, unsigned int len) } ; /* Return index of end of vector (index of last item + 1) */ -Inline vector_index +Inline vector_length_t vector_length(vector v) { return v->end ; } ; /* Returns whether vector is empty or not. */ -Inline int +Inline bool vector_is_empty(vector v) { return (v->end == 0) ; @@ -295,7 +290,7 @@ vector_body(vector v) /* Get pointer to item. Returns NULL if accessing beyond end. */ Inline p_vector_item -vector_get_item(vector v, vector_index i) +vector_get_item(vector v, vector_index_t i) { return (i < v->end) ? v->p_items[i] : NULL ; } ; @@ -318,7 +313,7 @@ vector_get_last_item(vector v) /* NB: it is the caller's responsibility to release memory used by any */ /* current value of the item, if required. */ Inline void -vector_set_item(vector v, vector_index i, void* p_v) +vector_set_item(vector v, vector_index_t i, p_vector_item p_v) { vector_ensure(v, i) ; v->p_items[i] = (p_vector_item)p_v ; @@ -330,16 +325,16 @@ vector_set_item(vector v, vector_index i, void* p_v) * used by the current dst item or the new (duplicated) src item. */ Inline void -vector_assign_item(vector v, vector_index dst, vector_index src) +vector_assign_item(vector v, vector_index_t dst, vector_index_t src) { vector_set_item(v, dst, vector_get_item(v, src)) ; } ; /* Push value onto vector, extending as required. */ Inline void -vector_push_item(vector v, void* p_v) +vector_push_item(vector v, p_vector_item p_v) { - vector_index i = vector_extend_by_1(v) ; + vector_index_t i = vector_extend_by_1(v) ; v->p_items[i] = (p_vector_item)p_v ; } ; @@ -351,4 +346,22 @@ vector_pop_item(vector v) return (v->end > 0) ? v->p_items[--v->end] : NULL ; } ; +/* Unshift value onto start of vector, extending as required. */ +Inline void +vector_unshift_item(vector v, p_vector_item p_v) +{ + vector_insert(v, 0, 1) ; + v->p_items[0] = p_v ; +} ; + +/* Pop value from vector. Returns NULL if vector is empty. */ +/* NB: does NOT change the size of the vector body. */ +Inline p_vector_item +vector_shift_item(vector v) +{ + p_vector_item p_v = vector_get_first_item(v) ; + vector_delete(v, 0, 1) ; + return p_v ; +} ; + #endif /* _ZEBRA_VECTOR_H */ diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index 685f33ec..10c4a343 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -19,7 +19,7 @@ * Boston, MA 02111-1307, USA. */ -#include <stddef.h> +#include "misc.h" #include <string.h> #include "vio_fifo.h" @@ -215,15 +215,18 @@ vio_fifo_ptr_unset(vio_fifo vf) * Clear out contents of FIFO -- will continue to use the FIFO. * * Keeps one FIFO lump. (Frees everything else, including any spare.) + * + * Does nothing if there is no FIFO ! */ extern void vio_fifo_clear(vio_fifo vf) { vio_fifo_lump tail ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vf == NULL) + return ; - assert(vf != NULL) ; + VIO_FIFO_DEBUG_VERIFY(vf) ; tail = ddl_tail(vf->base) ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h index 52f3455e..08af4590 100644 --- a/lib/vio_fifo.h +++ b/lib/vio_fifo.h @@ -23,20 +23,11 @@ #define _ZEBRA_VIO_FIFO_H #include "zebra.h" -#include <stdint.h> -#include <stdbool.h> +#include "misc.h" #include "list_util.h" #include "zassert.h" -#ifndef Inline /* in case of compiler issues */ -#define Inline static inline -#endif - -#ifndef Private /* extern, but for "friends" only */ -#define Private extern -#endif - /* GCC have printf type attribute check. */ #ifdef __GNUC__ #define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) diff --git a/lib/vio_lines.c b/lib/vio_lines.c index c2e9c43c..2ac874d1 100644 --- a/lib/vio_lines.c +++ b/lib/vio_lines.c @@ -19,8 +19,7 @@ * Boston, MA 02111-1307, USA. */ -#include <stdint.h> - +#include "misc.h" #include "memory.h" #include "zassert.h" @@ -148,6 +147,9 @@ vio_lc_reset(vio_line_control lc, bool free_structure) extern void vio_lc_clear(vio_line_control lc) { + if (lc == NULL) + return ; + qiovec_clear(&lc->qiov) ; lc->pause = 0 ; diff --git a/lib/vio_lines.h b/lib/vio_lines.h index ffef94ec..7097fe9c 100644 --- a/lib/vio_lines.h +++ b/lib/vio_lines.h @@ -23,16 +23,9 @@ #define _ZEBRA_VIO_LINES_H #include "zebra.h" - -#include <stddef.h> -#include <stdint.h> - +#include "misc.h" #include "qiovec.h" -#ifndef Inline -#define Inline static inline -#endif - /*============================================================================== * */ @@ -22,7 +22,7 @@ */ #include "zebra.h" -#include <stdbool.h> +#include "misc.h" #include "lib/version.h" #include "vty_io.h" @@ -94,9 +94,6 @@ bool no_password_check = 0; const bool restricted_mode_default = 0 ; bool restricted_mode = 0 ; -/* Watch-dog timer. */ -union vty_watch_dog vty_watch_dog = { NULL } ; - /*------------------------------------------------------------------------------ * VTYSH stuff */ @@ -110,6 +107,9 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; static void uty_reset (bool final, const char* why) ; static void uty_init_commands (void) ; static void vty_save_cwd (void) ; +static bool vty_terminal (struct vty *); +static bool vty_shell_server (struct vty *); +static bool vty_shell_client (struct vty *); /*------------------------------------------------------------------------------ * Tracking the initialisation state. @@ -159,7 +159,7 @@ vty_init (struct thread_master *master_thread) vty_cli_nexus = NULL ; /* not running qnexus-wise */ vty_cmd_nexus = NULL ; - vty_watch_dog.anon = NULL ; /* no watch dog */ + uty_watch_dog_init() ; /* empty watch dog */ uty_init_commands() ; /* install nodes */ @@ -440,16 +440,13 @@ uty_reset (bool curtains, const char* why) vio = next ; next = sdl_next(vio, vio_list) ; - if (vio->type == VTY_TERM) + if (uty_is_terminal(vio->vty)) cq_revoke(vio->vty) ; - if (why != NULL) - vio->close_reason = why ; - if (curtains) - uty_close(vio) ; + uty_close_final(vio, why) ; else - uty_half_close(vio, why) ; + uty_close(vio, why) ; } ; vty_timeout_val = VTY_TIMEOUT_DEFAULT; @@ -512,31 +509,79 @@ vty_close (struct vty *vty) /*============================================================================== * General VTY output. * - * This is mostly used during command execution, to output the results of the - * command. + * This is used during command execution, to output the results of commands. * * All these end up in uty_vout -- see vty_io. */ /*------------------------------------------------------------------------------ * VTY output -- cf fprintf ! + * + * This is for command output, which may be suppressed + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) */ extern int vty_out (struct vty *vty, const char *format, ...) { - int result; + int ret ; + va_list args ; VTY_LOCK() ; - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); + + if (vty->output_enabled) + { + va_start (args, format) ; + ret = uty_vprintf(vty, format, args) ; + va_end (args) ; + } + else + ret = 0 ; + VTY_UNLOCK() ; - return result; + return ret ; +} + +/*------------------------------------------------------------------------------ + * VTY output error message -- cf fprintf ! + * + * If command has not yet been reflected, do that first. + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + */ +extern int +vty_out_error (struct vty *vty, const char *format, ...) +{ + int ret ; + va_list args ; + + VTY_LOCK() ; + + if (!vty->reflected) + ret = uty_reflect(vty) ; + else + ret = 0 ; + + if (ret == 0) + { + va_start (args, format) ; + ret = uty_vprintf(vty, format, args) ; + va_end (args) ; + } ; + + VTY_UNLOCK() ; + return ret ; } /*------------------------------------------------------------------------------ * VTY output -- output a given numnber of spaces + * + * This is for command output, which may be suppressed + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) */ /* 1 2 3 4 */ @@ -544,14 +589,19 @@ vty_out (struct vty *vty, const char *format, ...) const char vty_spaces_string[] = " " ; CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ; -extern void +extern int vty_out_indent(struct vty *vty, int indent) { - while (indent > 0) + int ret ; + + ret = 0 ; + while ((indent > 0) && (ret >= 0)) { - vty_out(vty, VTY_SPACES(indent)) ; + ret = vty_out(vty, VTY_SPACES(indent)) ; indent -= VTY_MAX_SPACES ; } + + return ret ; } ; /*------------------------------------------------------------------------------ @@ -565,9 +615,9 @@ vty_time_print (struct vty *vty, int cr) quagga_timestamp(0, buf, sizeof(buf)) ; if (cr) - vty_out (vty, "%s%s", buf, VTY_NEWLINE); + vty_out(vty, "%s\n", buf); else - vty_out (vty, "%s ", buf); + vty_out(vty, "%s ", buf); return; } @@ -581,7 +631,7 @@ vty_hello (struct vty *vty) VTY_LOCK() ; #ifdef QDEBUG - uty_out (vty, "%s%s", debug_banner, VTY_NEWLINE); + uty_out (vty, "%s\n", debug_banner); #endif if (host.motdfile) { @@ -598,15 +648,15 @@ vty_hello (struct vty *vty) for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); s--); *s = '\0'; - uty_out (vty, "%s%s", buf, VTY_NEWLINE); + uty_output (vty, "%s\n", buf); } fclose (f); } else - uty_out (vty, "MOTD file %s not found%s", host.motdfile, VTY_NEWLINE); + uty_output (vty, "MOTD file %s not found\n", host.motdfile); } else if (host.motd) - uty_out (vty, "%s", host.motd); + uty_output (vty, "%s", host.motd); VTY_UNLOCK() ; } @@ -643,7 +693,7 @@ uty_command(struct vty *vty) VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; - assert(vty->vio->type == VTY_TERM) ; + assert(uty_is_terminal(vty)) ; /* Parse the command and add to history (if not empty) */ ret = cmd_parse_command(vty, @@ -679,15 +729,15 @@ uty_command(struct vty *vty) switch (ret) { case CMD_ERR_AMBIGUOUS: - uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + vty_out_error(vty, "%% Ambiguous command.\n"); break; case CMD_ERR_NO_MATCH: - uty_out (vty, "%% Unknown command.%s", VTY_NEWLINE) ; + vty_out_error(vty, "%% Unknown command.\n") ; break; case CMD_ERR_INCOMPLETE: - uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); + vty_out_error(vty, "%% Command incomplete.\n"); break; default: @@ -795,14 +845,14 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) { if (vty->node == AUTH_NODE) { - ret = uty_cmd_close(vty, "Bad passwords, too many failures!%s") ; + ret = uty_cmd_close(vty, "Bad passwords, too many failures!") ; } else { /* AUTH_ENABLE_NODE */ vio->fail = 0; - uty_out (vty, "%% Bad enable passwords, too many failures!%s", - VTY_NEWLINE); + vty_out_error(vty, + "%% Bad enable passwords, too many failures!\n") ; vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; ret = CMD_WARNING ; @@ -833,7 +883,7 @@ vty_cmd_exit(struct vty* vty) case VIEW_NODE: case ENABLE_NODE: case RESTRICTED_NODE: - if (vty_shell (vty)) + if (uty_shell_client(vty)) exit (0); else ret = uty_cmd_close(vty, "Exit") ; @@ -1349,6 +1399,33 @@ vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings) VTY_UNLOCK() ; } ; +/*------------------------------------------------------------------------------ + * Flush the contents of the command output FIFO to the given file. + * + * Takes no notice of any errors ! + */ +extern void +uty_out_fflush(vty_io vio, FILE* file) +{ + char* src ; + size_t have ; + + VTY_ASSERT_LOCKED() ; + + fflush(file) ; + + while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + { + fwrite(src, 1, have, file) ; + vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + } ; + + fflush(file) ; +} ; + + + + /*============================================================================== * Configuration node/state handling * @@ -1369,13 +1446,13 @@ vty_config_lock (struct vty *vty, enum node_type node) VTY_LOCK() ; - if (vty_config == 0) + if (!vty_config) { - vty->vio->config = 1 ; - vty_config = 1 ; + vty->config = true ; + vty_config = true ; } ; - result = vty->vio->config; + result = vty->config; if (result) vty->node = node ; @@ -1407,10 +1484,10 @@ extern void uty_config_unlock (struct vty *vty, enum node_type node) { VTY_ASSERT_LOCKED() ; - if ((vty_config == 1) && (vty->vio->config == 1)) + if (vty_config && vty->config) { - vty->vio->config = 0; - vty_config = 0; + vty->config = false ; + vty_config = false ; } assert(node <= MAX_NON_CONFIG_NODE) ; @@ -1434,9 +1511,8 @@ DEFUN_CALL (config_who, vio = vio_list_base ; while (vio != NULL) /* TODO: show only VTY_TERM ??? */ { - uty_out (vty, "%svty[%d] connected from %s.%s", - vio->config ? "*" : " ", - i, uty_get_name(vio), VTY_NEWLINE); + uty_output (vty, "%svty[%d] connected from %s.\n", + vio->vty->config ? "*" : " ", i, uty_get_name(vio)); vio = sdl_next(vio, vio_list) ; } ; VTY_UNLOCK() ; @@ -1476,7 +1552,7 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) vty_timeout_val = timeout; - if (vty_term(vty) || vty_shell_serv(vty)) + if (uty_is_terminal(vty) || uty_is_shell_server(vty)) uty_sock_set_timer(&vty->vio->sock, timeout) ; VTY_UNLOCK() ; @@ -1542,8 +1618,7 @@ DEFUN_CALL (no_vty_access_class, VTY_LOCK() ; if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0]))) { - uty_out (vty, "Access-class is not currently applied to vty%s", - VTY_NEWLINE); + vty_out_error(vty, "Access-class is not currently applied to vty\n"); result = CMD_WARNING; } else @@ -1591,8 +1666,7 @@ DEFUN_CALL (no_vty_ipv6_access_class, if (! vty_ipv6_accesslist_name || (argc && strcmp(vty_ipv6_accesslist_name, argv[0]))) { - uty_out (vty, "IPv6 access-class is not currently applied to vty%s", - VTY_NEWLINE); + vty_out_error(vty, "IPv6 access-class is not currently applied to vty\n") ; result = CMD_WARNING; } else @@ -1732,9 +1806,9 @@ DEFUN_CALL (show_history, continue; } - line = vector_get_item(&vty->vio->hist, index) ; + line = vector_get_item(vty->vio->hist, index) ; if (line != NULL) - uty_out (vty, " %s%s", line->char_body, VTY_NEWLINE); + uty_output (vty, " %s\n", line->char_body); index++; } @@ -1751,35 +1825,32 @@ DEFUN_CALL (show_history, static int vty_config_write (struct vty *vty) { - vty_out (vty, "line vty%s", VTY_NEWLINE); + vty_out (vty, "line vty\n"); if (vty_accesslist_name) - vty_out (vty, " access-class %s%s", - vty_accesslist_name, VTY_NEWLINE); + vty_out (vty, " access-class %s\n", vty_accesslist_name); if (vty_ipv6_accesslist_name) - vty_out (vty, " ipv6 access-class %s%s", - vty_ipv6_accesslist_name, VTY_NEWLINE); + vty_out (vty, " ipv6 access-class %s\n", vty_ipv6_accesslist_name); /* exec-timeout */ if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) - vty_out (vty, " exec-timeout %ld %ld%s", - vty_timeout_val / 60, - vty_timeout_val % 60, VTY_NEWLINE); + vty_out (vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, + vty_timeout_val % 60); /* login */ if (no_password_check) - vty_out (vty, " no login%s", VTY_NEWLINE); + vty_out (vty, " no login\n"); if (restricted_mode != restricted_mode_default) { if (restricted_mode_default) - vty_out (vty, " no anonymous restricted%s", VTY_NEWLINE); + vty_out (vty, " no anonymous restricted\n"); else - vty_out (vty, " anonymous restricted%s", VTY_NEWLINE); + vty_out (vty, " anonymous restricted\n"); } - vty_out (vty, "!%s", VTY_NEWLINE); + vty_out (vty, "!\n"); return CMD_SUCCESS; } @@ -1824,32 +1895,32 @@ vty_get_cwd () * Access functions for VTY values, where locking is or might be required. */ -bool -vty_shell (struct vty *vty) +static bool +vty_is_terminal(struct vty *vty) { bool result; VTY_LOCK() ; - result = (vty->vio->type == VTY_SHELL) ; + result = uty_is_terminal(vty) ; VTY_UNLOCK() ; return result; } -bool -vty_term(struct vty *vty) +static bool +vty_is_shell_server (struct vty *vty) { bool result; VTY_LOCK() ; - result = (vty->vio->type == VTY_TERM); + result = uty_is_shell_server(vty) ; VTY_UNLOCK() ; return result; } -bool -vty_shell_serv (struct vty *vty) +static bool +vty_is_shell_client (struct vty *vty) { bool result; VTY_LOCK() ; - result = (vty->vio->type == VTY_SHELL_SERV); + result = uty_is_shell_client(vty) ; VTY_UNLOCK() ; return result; } @@ -24,7 +24,7 @@ #ifndef _ZEBRA_VTY_H #define _ZEBRA_VTY_H -#include <stdbool.h> +#include "misc.h" #include "thread.h" #include "log.h" @@ -37,11 +37,6 @@ #include "qstring.h" #include "node_type.h" -/* Macro in case there are particular compiler issues. */ -#ifndef Inline - #define Inline static inline -#endif - /*============================================================================== * The VTYSH uses a unix socket to talk to the daemon. * @@ -50,50 +45,59 @@ * option is turned into a testable constant. */ #ifdef VTYSH -# define VTYSH_DEFINED 1 + enum { VTYSH_ENABLED = true } ; #else -# define VTYSH_DEFINED 0 + enum { VTYSH_ENABLED = false } ; #endif -enum { VTYSH_ENABLED = VTYSH_DEFINED } ; - -#undef VTYSH_DEFINED - /*============================================================================== * VTY Types */ -enum vty_type +enum vty_type /* Command output */ { - VTY_NONE = 0, /* no type at all */ + VTY_TERMINAL, /* a telnet terminal server */ + VTY_SHELL_SERVER, /* a vty_shell server */ - VTY_TERM, /* a telnet terminal -- input and output */ - VTY_SHELL_SERV, /* a vty_shell slave -- input and output */ + VTY_SHELL_CLIENT, /* a vty_shell client */ - VTY_CONFIG_READ, /* reading config file -- output is to buffer - -- no input */ + VTY_CONFIG_READ, /* configuration file reader */ - VTY_CONFIG_WRITE, /* writing config file -- output is to file - -- no input */ + VTY_STDOUT, /* stdout */ + VTY_STDERR, /* stderr */ +} ; +typedef enum vty_type vty_type_t ; - VTY_STDOUT, /* general output -- output is to stdout - -- no input */ +/*============================================================================== + * + * + * + * + */ - VTY_STDERR, /* general output -- output is to stderr - -- no input */ +typedef unsigned long vty_timer_time ; /* Time out time in seconds */ - VTY_SHELL, /* vty in vtysh -- output is to stdout */ +enum +{ + VTY_WATCH_DOG_INTERVAL = 5, /* interval between barks */ + + VTY_HALF_CLOSE_TIMEOUT = 120, /* timeout after half_close */ + + VTY_TIMEOUT_DEFAULT = 600, /* terminal timeout value */ } ; /*============================================================================== * VTY struct. */ -typedef struct vty_io* vty_io ; /* private to vty.c */ +typedef struct vty_io* vty_io ; /* private to vty.c */ struct cmd_parsed ; /* in case vty.h expanded before command.h */ +typedef struct vty* vty ; struct vty { + vty_type_t type ; + /*---------------------------------------------------------------------- * The following are the context in which commands are executed. */ @@ -120,8 +124,8 @@ struct vty */ void *index_sub ; - /* String which is newline... read only -- no locking */ - const char* newline ; + /* In configure mode. */ + bool config; /*---------------------------------------------------------------------------- * The current command line. @@ -135,13 +139,17 @@ struct vty struct cmd_parsed* parsed ; unsigned lineno ; + bool output_enabled ; + bool reflect_enabled ; + bool more_enabled ; + + bool reflected ; + /*---------------------------------------------------------------------- * The following are used inside vty.c only. */ - - /* Pointer to related vty_io structure */ - vty_io vio ; -}; + vty_io vio ; /* one vio object per vty */ +} ; /*------------------------------------------------------------------------------ * Can now include this @@ -158,18 +166,15 @@ struct vty /* Integrated configuration file. */ #define INTEGRATE_DEFAULT_CONFIG "Quagga.conf" -/* Small macro to determine newline is newline only or linefeed needed. */ -#define VTY_NEWLINE (((vty) != NULL) ? (vty)->newline : "\n") +/* Conversion of "\n" to "\r\n" is done at output time. */ +#define VTY_NEWLINE "\n" +#define VTY_NL "\n" /* For indenting, mostly. */ extern const char vty_spaces_string[] ; enum { VTY_MAX_SPACES = 40 } ; #define VTY_SPACES(n) (vty_spaces_string + ((n) < VTY_MAX_SPACES \ ? VTY_MAX_SPACES - (n) : 0)) - -/* Default time out value */ -#define VTY_TIMEOUT_DEFAULT 600 - /* Vty read buffer size. */ #define VTY_READ_BUFSIZ 512 @@ -217,7 +222,7 @@ do { \ VTY_GET_INTEGER_RANGE(NAME,V,STR,0U,UINT32_MAX) #define VTY_GET_IPV4_ADDRESS(NAME,V,STR) \ -do { \ +do { \ int retv; \ retv = inet_aton ((STR), &(V)); \ if (!retv) \ @@ -228,7 +233,7 @@ do { } while (0) #define VTY_GET_IPV4_PREFIX(NAME,V,STR) \ -do { \ +do { \ int retv; \ retv = str2prefix_ipv4 ((STR), &(V)); \ if (retv <= 0) \ @@ -253,7 +258,7 @@ extern void vty_start(const char *addr, unsigned short port, const char *path) ; extern void vty_restart(const char *addr, unsigned short port, const char *path) ; extern struct vty* vty_open(enum vty_type type) ; -extern void vty_close(struct vty *); +extern void vty_close_final(struct vty *); extern void vty_init_vtysh (void); extern void vty_terminate (void); @@ -261,7 +266,9 @@ extern void vty_reset (void); extern void vty_reset_because(const char* why) ; extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); -extern void vty_out_indent(struct vty *vty, int indent) ; +extern int vty_out_indent(struct vty *vty, int indent) ; +extern int vty_out_error (struct vty *vty, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3); extern void vty_out_clear(struct vty *vty) ; extern void vty_read_config (char *config_file, char *config_default); @@ -273,9 +280,6 @@ extern void vty_time_print (struct vty *, int); extern char *vty_get_cwd (void); -extern bool vty_shell (struct vty *); -extern bool vty_term (struct vty *); -extern bool vty_shell_serv (struct vty *); extern void vty_hello (struct vty *); extern enum node_type vty_get_node(struct vty *); extern void vty_set_node(struct vty *, enum node_type); diff --git a/lib/vty_cli.c b/lib/vty_cli.c index b4791365..17351f58 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -31,6 +31,7 @@ #include "vio_lines.h" #include "command.h" +#include "command_parse.h" #include "command_execute.h" #include "command_queue.h" @@ -262,7 +263,7 @@ uty_new_host_name(const char* name) * The CLI */ -#define CONTROL(X) ((X) - '@') +#define CONTROL(X) ((X) & 0x1F) static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; static enum vty_readiness uty_cli_standard(vty_io vio) ; @@ -309,7 +310,7 @@ static void uty_dont_lflow_ahead (vty_io vio) ; extern void uty_cli_init(vty_io vio) { - assert(vio->type == VTY_TERM) ; + assert(vio->vty->type == VTY_TERMINAL) ; vio->cmd_in_progress = 1 ; vio->cli_blocked = 1 ; @@ -350,7 +351,7 @@ extern void uty_cli_close(vty_io vio) { VTY_ASSERT_LOCKED() ; - assert(vio->type == VTY_TERM) ; + assert(vio->vty->type == VTY_TERMINAL) ; cq_revoke(vio->vty) ; @@ -373,7 +374,7 @@ extern enum vty_readiness uty_cli(vty_io vio) { VTY_ASSERT_LOCKED() ; - assert(vio->type == VTY_TERM) ; + assert(vio->vty_type == VTY_TERM) ; if (vio->half_closed) return not_ready ; /* Nothing more if half closed */ @@ -411,7 +412,7 @@ static enum vty_readiness uty_cli_standard(vty_io vio) { VTY_ASSERT_LOCKED() ; - assert(vio->type == VTY_TERM) ; + assert(vio->vty_type == VTY_TERM) ; /* cli_blocked is set when is waiting for a command, or its output to * complete -- unless either of those has happened, is still blocked. @@ -598,7 +599,7 @@ vty_queued_result(struct vty *vty, enum cmd_return_code ret) VTY_LOCK() ; - vio = vty->vio ; + vio = vty->tos ; if (!vio->closed) { @@ -622,9 +623,7 @@ vty_queued_result(struct vty *vty, enum cmd_return_code ret) /* If the VTY is closed, the only reason it still exists is because * there was cmd_in_progress. */ - vio->cmd_in_progress = 0 ; - - uty_close(vio) ; /* Final close */ + vio->cmd_in_progress = 0 ; /* death watch will apply coup de grace */ } ; VTY_UNLOCK() ; @@ -660,7 +659,7 @@ uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; if (ret == CMD_CLOSE) - uty_half_close(vio, NULL) ; + uty_close(vio, NULL) ; vio->cmd_in_progress = 0 ; /* command complete */ vio->cmd_out_enabled = 1 ; /* enable the output */ @@ -1405,7 +1404,7 @@ uty_cli_process(vty_io vio, enum node_type node) /* Now process as much as possible of what there is */ ret = cli_do_nothing ; - while (1) + while (ret == cli_do_nothing) { if (!vio->cli_drawn) uty_cli_draw_this(vio, node) ; @@ -1565,6 +1564,7 @@ uty_cli_process(vty_io vio, enum node_type node) case ('D'): uty_cli_backwards(vio, 1); break; + default: break ; } ; @@ -1580,17 +1580,13 @@ uty_cli_process(vty_io vio, enum node_type node) zabort("unknown keystroke type") ; } ; - /* After each keystroke..... */ - - if (ret != cli_do_nothing) - { - uty_cli_eol (vio) ; /* go to the end of the line */ - break ; /* stop processing */ - } ; } ; /* Tidy up and return where got to. */ + if (ret != cli_do_nothing) + uty_cli_eol (vio) ; /* go to the end of the line */ + qs_term(&vio->cl) ; /* add '\0' */ return ret ; @@ -1997,14 +1993,14 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ /* make sure have a suitable history vector */ - vector_set_min_length(&vio->hist, VTY_MAXHIST) ; + vector_set_min_length(vio->hist, VTY_MAXHIST) ; /* find the previous command line in the history */ prev_index = vio->hindex - 1 ; if (prev_index < 0) prev_index = VTY_MAXHIST - 1 ; - prev_line = vector_get_item(&vio->hist, prev_index) ; + prev_line = vector_get_item(vio->hist, prev_index) ; /* If the previous line is NULL, that means the history is empty. * @@ -2018,10 +2014,10 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) vio->hindex = prev_index ; else - prev_line = vector_get_item(&vio->hist, vio->hindex) ; + prev_line = vector_get_item(vio->hist, vio->hindex) ; /* Now replace the hindex entry */ - vector_set_item(&vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; /* Advance to the near future and reset the history pointer */ vio->hindex++; @@ -2063,8 +2059,8 @@ uty_cli_history_use(vty_io vio, int step) /* before stepping back from the present, take a copy of the * current command line -- so can get back to it. */ - hist = vector_get_item(&vio->hist, vio->hindex) ; - vector_set_item(&vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + hist = vector_get_item(vio->hist, vio->hindex) ; + vector_set_item(vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; } ; /* Advance or retreat */ @@ -2074,7 +2070,7 @@ uty_cli_history_use(vty_io vio, int step) else if (index >= VTY_MAXHIST) index = 0 ; - hist = vector_get_item(&vio->hist, index) ; + hist = vector_get_item(vio->hist, index) ; /* If moving backwards in time, may not move back to the insertion * point (that would be wrapping round to the present) and may not @@ -2232,35 +2228,32 @@ uty_cli_complete_command (vty_io vio, enum node_type node) * Command Description */ static void -uty_cli_describe_command (vty_io vio, enum node_type node) +uty_cli_describe_command (vty_io vio, node_type_t node) { - int ret; - vector vline; - vector describe; + cmd_return_code_t ret ; + vector describe ; VTY_ASSERT_LOCKED() ; /* Try and match the tokenised command line */ - vline = uty_cli_cmd_prepare(vio, 1) ; - describe = cmd_describe_command (vline, node, &ret); - cmd_free_strvec (vline); + describe = cmd_describe_command (qs_term(&vio->cl), node, &ret); uty_cli_out_newline(vio); /* clears cli_drawn */ /* Deal with result. */ switch (ret) { - case CMD_ERR_AMBIGUOUS: - uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; - break ; + case CMD_ERR_AMBIGUOUS: + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; - case CMD_ERR_NO_MATCH: - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; + case CMD_ERR_NO_MATCH: + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; - default: - uty_cli_describe_show(vio, describe) ; - break ; + default: + uty_cli_describe_show(vio, describe) ; + break ; } ; if (describe != NULL) diff --git a/lib/vty_io.c b/lib/vty_io.c index 2eadc2d1..c116e6b9 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -1,4 +1,4 @@ -/* VTY IO Functions +/* VTY IO Functions -- top level of VTY IO hierarchy * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -21,13 +21,13 @@ * 02111-1307, USA. */ -#include "zebra.h" - #include "vty.h" #include "vty_io.h" +#include "vty_io_term.h" #include "vty_cli.h" #include "qstring.h" #include "keystroke.h" +#include "list_util.h" #include "memory.h" @@ -44,33 +44,58 @@ #define VTYSH_DEBUG 0 /*============================================================================== - * VTY Command Output -- base functions + * Basic output to VTY. + * * - * During command processing the output sent here is held until the command - * completes. */ -static int uty_config_write(vty_io vio, bool all) ; - /*------------------------------------------------------------------------------ - * VTY output function -- cf fprintf + * UTY output function -- cf fprintf + * + * NB: this is NOT suppressed by ! vty->output_enabled * * Returns: >= 0 => OK * < 0 => failed (see errno) */ extern int -uty_out (struct vty *vty, const char *format, ...) +uty_output(struct vty *vty, const char *format, ...) { - int result; + int ret ; + va_list args ; + VTY_ASSERT_LOCKED() ; - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); - return result; + + va_start (args, format) ; + ret = uty_vprintf(vty, format, args) ; + va_end (args) ; + + return ret ; } /*------------------------------------------------------------------------------ + * UTY reflect command line, if not already reflected + * + * NB: this is NOT suppressed by ! vty->output_enabled + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + */ +extern int +uty_reflect(struct vty *vty) +{ + int ret ; + + if (!vty->reflected) + ret = uty_output(vty, "%s\n", vty->buf) ; + else + ret = 0 ; + + vty->reflected = true ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ * VTY output function -- cf vfprintf * * Returns: >= 0 => OK @@ -95,47 +120,47 @@ uty_out (struct vty *vty, const char *format, ...) * * output is discarded if the vty is no longer write_open */ extern int -uty_vout(struct vty *vty, const char *format, va_list args) +uty_vprintf(struct vty *vty, const char *format, va_list args) { - vty_io vio ; + vio_vf vf ; int ret ; VTY_ASSERT_LOCKED() ; - vio = vty->vio ; + vf = vty->vio->vout ; - switch (vio->type) + switch (vf->vout_type) { - case VTY_STDOUT: - case VTY_SHELL: - ret = vprintf (format, args) ; + case VOUT_NONE: + ret = 0 ; break ; - case VTY_STDERR: - ret = vfprintf (stderr, format, args) ; + case VOUT_TERM: + ret = uty_term_vprintf(vf, format, args) ; break ; - case VTY_CONFIG_WRITE: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; - if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) - ret = uty_config_write(vio, false) ; + case VOUT_SHELL: + ret = uty_shell_vprintf(vf, format, args) ; break ; - case VTY_TERM: - case VTY_SHELL_SERV: - assert(vio->cmd_in_progress) ; + case VOUT_FILE: + ret = uty_file_vprintf(vf, format, args) ; + break ; - if (!vio->sock.write_open) - return 0 ; /* discard output if not open ! */ + case VOUT_PIPE: + ret = uty_pipe_vprintf(vf, format, args) ; + break ; - /* fall through.... */ + case VOUT_STDOUT: + ret = uty_stdout_vprintf(vf, format, args) ; + break ; - case VTY_CONFIG_READ: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + case VOUT_STDERR: + ret = uty_stderr_vprintf(vf, format, args) ; break ; default: - zabort("impossible VTY type") ; + zabort("impossible VTY Output type") ; } ; return ret ; @@ -152,34 +177,11 @@ uty_out_clear(vty_io vio) { VTY_ASSERT_LOCKED() ; - vio_fifo_clear(&vio->cmd_obuf) ; - - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Flush the contents of the command output FIFO to the given file. - * - * Takes no notice of any errors ! - */ -extern void -uty_out_fflush(vty_io vio, FILE* file) -{ - char* src ; - size_t have ; - - VTY_ASSERT_LOCKED() ; - - fflush(file) ; - - while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + if (vio->vout != NULL) { - fwrite(src, 1, have, file) ; - vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + vio_fifo_clear(vio->vout->obuf) ; + vio_lc_clear(vio->vout->olc) ; } ; - - fflush(file) ; } ; /*============================================================================== @@ -193,97 +195,54 @@ uty_out_fflush(vty_io vio, FILE* file) * * the death watch list */ -enum { vty_watch_dog_interval = 5 } ; +/* Watch-dog timer. */ +static vio_timer_t vty_watch_dog ; -static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ; -static int vty_watch_dog_thread(struct thread *thread) ; - -static void uty_watch_dog_bark(void) ; -static bool uty_death_watch_scan(void) ; +static vty_timer_time uty_watch_dog_bark(vio_timer_t* timer, void* info) ; +static bool uty_death_watch_scan(bool final) ; /*------------------------------------------------------------------------------ - * Start watch dog -- the first time a VTY is created. + * Initialise watch dog -- at start-up time. */ extern void -uty_watch_dog_start() +uty_watch_dog_init(void) { - if (vty_cli_nexus) - vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile, - NULL, NULL) ; + vio_timer_init(&vty_watch_dog, NULL, NULL) ; /* empty */ +} ; - uty_watch_dog_bark() ; /* start up by barking the first time */ -} +/*------------------------------------------------------------------------------ + * Start watch dog -- before a VTY is created. + */ +extern void +uty_watch_dog_start(void) +{ + vio_timer_init(&vty_watch_dog, uty_watch_dog_bark, NULL) ; + vio_timer_set(&vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; +} ; /*------------------------------------------------------------------------------ * Stop watch dog timer -- at close down. * * Final run along the death-watch - * */ extern void uty_watch_dog_stop(void) { - if (vty_watch_dog.anon != NULL) - { - if (vty_cli_nexus) - qtimer_free(vty_watch_dog.qnexus) ; - else - thread_cancel(vty_watch_dog.thread) ; - } ; - - uty_death_watch_scan() ; /* scan the death-watch list */ + vio_timer_reset(&vty_watch_dog) ; + uty_death_watch_scan(true) ; /* scan the death-watch list */ } /*------------------------------------------------------------------------------ - * qnexus watch dog action + * Watch dog vio_timer action */ -static void -vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) -{ - VTY_LOCK() ; - - uty_watch_dog_bark() ; - - VTY_UNLOCK() ; -} ; - -/*------------------------------------------------------------------------------ - * thread watch dog action - */ -static int -vty_watch_dog_thread(struct thread *thread) -{ - VTY_LOCK() ; - - vty_watch_dog.thread = NULL ; - uty_watch_dog_bark() ; - - VTY_UNLOCK() ; - return 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Watch dog action - */ -static void -uty_watch_dog_bark(void) +static vty_timer_time +uty_watch_dog_bark(vio_timer_t* timer, void* info) { uty_check_host_name() ; /* check for host name change */ - uty_death_watch_scan() ; /* scan the death-watch list */ + uty_death_watch_scan(false) ; /* scan the death-watch list */ - /* Set timer to go off again later */ - if (vty_cli_nexus) - qtimer_set(vty_watch_dog.qnexus, - qt_add_monotonic(QTIME(vty_watch_dog_interval)), - vty_watch_dog_qnexus) ; - else - { - if (vty_watch_dog.thread != NULL) - thread_cancel (vty_watch_dog.thread); - vty_watch_dog.thread = thread_add_timer (vty_master, - vty_watch_dog_thread, NULL, vty_watch_dog_interval) ; - } ; + return VTY_WATCH_DOG_INTERVAL ; } ; /*------------------------------------------------------------------------------ @@ -293,7 +252,7 @@ uty_watch_dog_bark(void) * progress. */ static bool -uty_death_watch_scan(void) +uty_death_watch_scan(bool final) { vty_io vio ; vty_io next ; @@ -304,6 +263,12 @@ uty_death_watch_scan(void) vio = next ; next = sdl_next(vio, vio_list) ; + if (final && !vio->closed) + + + if (vio->closed) + + if (vio->closed && !vio->cmd_in_progress) { uty_close(vio) ; /* closes again to ensure that all buffers @@ -322,22 +287,9 @@ uty_death_watch_scan(void) /*============================================================================== * Prototypes. */ -static void uty_sock_init_new(vio_sock sock, int fd, void* info) ; -static void uty_sock_half_close(vio_sock sock) ; -static void uty_sock_close(vio_sock sock) ; - -static void vty_read_qnexus (qps_file qf, void* file_info) ; -static void vty_write_qnexus (qps_file qf, void* file_info) ; -static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ; -static int vty_read_thread (struct thread *thread) ; -static int vty_write_thread (struct thread *thread) ; -static int vty_timer_thread (struct thread *thread) ; - -static void vtysh_read_qnexus (qps_file qf, void* file_info) ; -static int vtysh_read_thread (struct thread *thread) ; - -static enum vty_readiness uty_write(vty_io vio) ; +static void uty_vf_half_close(vio_vf vf) ; +static void uty_vf_close(vio_vf vf) ; /*============================================================================== * Creation and destruction of VTY objects @@ -346,56 +298,53 @@ static enum vty_readiness uty_write(vty_io vio) ; /*------------------------------------------------------------------------------ * Allocate new vty struct * - * Allocates and initialises basic vty and vty_io structures, setting the - * given type. + * VTY_TERMINAL a telnet terminal server + * + * Must be in cli thread. + * + * Requires fd = socket for telnet terminal. + * + * VTY_SHELL_SERVER a vty_shell server + * + * Must be in cli thread. + * + * Requires fd = socket for talking to the VTY_SHELL_CLIENT + * + * VTY_SHELL_CLIENT a vty_shell client + * + * VTY_CONFIG_READ configuration file reader + * + * Requires fd = file descriptor for reading config file + * + * VTY_STDOUT stdout + * VTY_STDERR stderr + * * - * Note that where is not setting up a vty_sock, this *may* be called from - * any thread. * - * NB: may not create a VTY_CONFIG_WRITE type vty directly * - * see: vty_open_config_write() and vty_close_config_write() * - * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. - * (So MUST be in the CLI thread to set those up !) * - * the sock_fd is ignored for everything else. + * + * + * Allocates and initialises basic vty and vty_io structures, setting the + * given type. + * + * Note that where is not setting up a vty_file, this *may* be called from + * any thread. * * Returns: new vty */ extern struct vty * -uty_new(enum vty_type type, int sock_fd) +uty_new(enum vty_type type, int fd) { - struct vty *vty ; - struct vty_io* vio ; + vty vty ; + vty_io vio ; VTY_ASSERT_LOCKED() ; - /* If this is a VTY_TERM or a VTY_SHELL, place */ - switch (type) - { - case VTY_TERM: /* Require fd -- Telnet session */ - case VTY_SHELL_SERV: /* Require fd -- Unix socket */ - assert(sock_fd >= 0) ; - break ; - - case VTY_CONFIG_WRITE: - zabort("may not make a new VTY_CONFIG_WRITE VTY") ; - break ; - - case VTY_CONFIG_READ: - case VTY_STDOUT: - case VTY_STDERR: - case VTY_SHELL: - sock_fd = -1 ; /* No fd -- output to stdout/stderr */ - break ; - - default: - zabort("unknown VTY type") ; - } ; - /* Basic allocation */ - vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + + vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)) ; vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; vty->vio = vio ; @@ -460,7 +409,11 @@ uty_new(enum vty_type type, int sock_fd) confirm(cli_do_nothing == 0) ; confirm(AUTH_NODE == 0) ; /* default node type */ - vio->type = type ; + vty->type = type ; + + + + /* Zeroising the vty structure has set: * @@ -471,13 +424,40 @@ uty_new(enum vty_type type, int sock_fd) * index = NULL -- nothing, yet * index_sub = NULL -- nothing, yet */ - if (type == VTY_TERM) - vty->newline = "\n" ; /* line control looks after "\r\n" */ - else - vty->newline = "\n" ; - /* Initialise the vio_sock, */ - uty_sock_init_new(&vio->sock, sock_fd, vio) ; + /* If this is a VTY_TERM or a VTY_SHELL, place */ + switch (type) + { + case VTY_TERMINAL: /* Require fd -- Telnet session */ + VTY_ASSERT_CLI_THREAD() ; + assert(fd >= 0) ; + + uty_term_new(vio, fd) ; + break ; + + case VTY_SHELL_SERVER: /* Require fd -- Unix socket */ + VTY_ASSERT_CLI_THREAD() ; + assert(fd >= 0) ; + + + break ; + + case VTY_CONFIG_READ: /* Require fd -- file to read */ + assert(fd >= 0) ; + break ; + + case VTY_STDOUT: + case VTY_STDERR: + case VTY_SHELL_CLIENT: + fd = -1 ; /* No fd -- output to stdout/stderr */ + break ; + + default: + zabort("unknown VTY type") ; + } ; + + + /* Make sure all buffers etc. are initialised clean and empty. * @@ -499,132 +479,77 @@ uty_new(enum vty_type type, int sock_fd) } ; /*------------------------------------------------------------------------------ - * Create new vty of type VTY_TERM -- ie attached to a telnet session. + * Add a new vf to the vio->vin stack, and set read stuff. * - * Returns: new vty + * Sets the vf->vin_type and set vf->read_open. + * + * Sets the read ready action and the read timer timeout action. + * + * NB: may add a VIN_NONE *only* as the first vin item. + * + * Can have a write only VTY_XXX object, which requires a vin entry so that + * do not have to everywhere deal with NULL vio_vf pointers. + * + * But for subsequent write only vio_vf objects, must not add to the vin + * stack. */ -static struct vty * -uty_new_term(int sock_fd, union sockunion *su) +extern void +uty_vin_add(vty_io vio, vio_vf vf, vio_in_type_t type, + vio_fd_action* read_action, vio_timer_action* read_timer_action) { - struct vty *vty ; - vty_io vio ; - enum vty_readiness ready ; + vf->vin_type = type ; + vf->read_open = true ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_TERM, sock_fd) ; - vio = vty->vio ; - - /* Allocate and initialise a keystroke stream TODO: CSI ?? */ - vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + vio_fd_set_read_action(vf->vfd, read_action) ; + vio_fd_set_read_timeout_action(vf->vfd, read_timer_action) ; - /* Set the socket action functions */ - if (vty_cli_nexus) - { - vio->sock.action.read.qnexus = vty_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = vty_timer_qnexus ; - } + ssl_push(vio->vin, vf, vin_next) ; + if (vio->vin_base == NULL) + vio->vin_base = vf ; else - { - vio->sock.action.read.thread = vty_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = vty_timer_thread ; - } ; - - /* The text form of the address identifies the VTY */ - vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); - - /* Set the initial node */ - if (no_password_check) - { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; - else - vty->node = VIEW_NODE; - } - else - vty->node = AUTH_NODE; - - /* Pick up current timeout setting */ - vio->sock.v_timeout = vty_timeout_val; - - /* Use global 'lines' setting, as default. May be -1 => unset */ - vio->lines = host.lines ; + assert(type != VIN_NONE) ; +} ; - /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ - vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; - uty_set_height(vio) ; /* set initial state */ +/*------------------------------------------------------------------------------ + * Add a new vf to the vio->vout stack, and set write stuff. + * + * Sets the vf->vout_type and set vf->write_open. + * + * Sets the write ready action and the write timer timeout action. + * + * NB: may add a VOUT_NONE *only* as the first vout item. + * + * Can have a read only VTY_XXX object, which requires a vout entry so that + * do not have to everywhere deal with NULL vio_vf pointers. + * + * But for subsequent read only vio_vf objects, must not add to the vout + * stack. + */ +extern void +uty_vout_add(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_fd_action* write_action, vio_timer_action* write_timer_action) +{ + vf->vout_type = type ; + vf->write_open = true ; - /* Initialise the CLI, ready for start-up messages etc. */ - uty_cli_init(vio) ; + vio_fd_set_write_action(vf->vfd, write_action) ; + vio_fd_set_write_timeout_action(vf->vfd, write_timer_action) ; - /* Reject connection if password isn't set, and not "no password" */ - if ((host.password == NULL) && (host.password_encrypt == NULL) - && ! no_password_check) - { - uty_half_close (vio, "Vty password is not set."); - vty = NULL; - } + ssl_push(vio->vout, vf, vout_next) ; + if (vio->vout_base == NULL) + vio->vout_base = vf ; else - { - /* Say hello to the world. */ - vty_hello (vty); + assert(type != VOUT_NONE) ; +} ; - if (! no_password_check) - uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE); - } ; - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; - return vty; -} ; -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. - * - * Returns: new vty - */ -static struct vty * -uty_new_shell_serv(int sock_fd) -{ - struct vty *vty ; - vty_io vio ; - VTY_ASSERT_LOCKED() ; - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_SHELL_SERV, sock_fd) ; - vio = vty->vio ; - /* Set the action functions */ - if (vty_cli_nexus) - { - vio->sock.action.read.qnexus = vtysh_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = NULL ; - } - else - { - vio->sock.action.read.thread = vtysh_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = NULL ; - } ; - vty->node = VIEW_NODE; - /* Kick start the CLI etc. */ - uty_sock_set_readiness(&vio->sock, write_ready) ; - - return vty; -} ; /*------------------------------------------------------------------------------ * Set/Clear "monitor" state: @@ -639,7 +564,7 @@ uty_set_monitor(vty_io vio, bool on) if (on && !vio->monitor) { - if ((vio->type == VTY_TERM) && vio->sock.write_open) + if ((vio->vty->type == VTY_TERMINAL) && !vio->half_closed) { vio->monitor = 1 ; sdl_push(vio_monitors_base, vio, mon_list) ; @@ -664,87 +589,141 @@ uty_get_name(vty_io vio) } ; /*------------------------------------------------------------------------------ - * Closing down VTY for reading. + * Close all the readers. + * + * Half-close everything on the vin stack. Empties the vin stack down to the + * base entry. Discards any read-only vio_vf (except for last vin entry). * - * For VTY_TERM (must be in CLI thread): + * VIO is placed on death watch, and will stay there until: + * + * * outstanding commands complete. + * + * * outstanding output completes, or times out, or program terminates. + * + * For VTY_TERMINAL (must be in CLI thread): * * * shut the socket for reading * * discard all buffered input, setting it to "EOF" * * turns off any monitor status ! * * drop down to RESTRICTED_NODE * - * For VTY_SHELL_SERV (must be in CLI thread): + * For VTY_SHELL_SERVER (must be in CLI thread): * * * shut the socket for reading * * discard all buffered input * * drop down to RESTRICTED_NODE * - * In all cases: - * - * * place on death watch - * * set the vty half_closed - * * sets the reason for closing (if any given) + * If no reason for the close has already been set, sets the given reason. * - * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all - * the buffers, the VTY is closed. + * If already half-closed, does nothing else. * - * May already have set the vio->close_reason, or can set it now. (Passing a - * NULL reason has no effect on any existing posted reason.) + * Returns true <=> first half close. */ -extern void -uty_half_close (vty_io vio, const char* reason) +static bool +uty_do_half_close(vty_io vio, const char* reason) { + vio_vf vf ; + VTY_ASSERT_LOCKED() ; - if (vio->half_closed) - return ; + if ((vio->close_reason == NULL) && (reason != NULL)) + vio->close_reason = XSTRDUP(MTYPE_TMP, reason) ; - if (reason != NULL) - vio->close_reason = reason ; + if (vio->half_closed) + return false ; - /* Do the file side of things + /* Half close everything on the vin stack. * - * Note that half closing the file sets a new timeout, sets read off - * and write on. + * Leave stack with just the base entry (closed for read). */ - uty_sock_half_close(&vio->sock) ; - uty_set_monitor(vio, 0) ; + while (1) + { + vf = vio->vin ; /* Current first on list */ - /* Discard everything in the keystroke stream and force it to EOF */ - if (vio->key_stream != NULL) - keystroke_stream_set_eof(vio->key_stream) ; + uty_vf_half_close(vf) ; /* fd level half close etc. */ - /* Turn off "--more--" so that all output clears without interruption. - * - * If is sitting on a "--more--" prompt, then exit the wait_more CLI. - */ - vio->cli_more_enabled = 0 ; + switch(vf->vin_type) /* tidy up each type */ + { + case VIN_NONE: + break ; - if (vio->cli_more_wait) - uty_cli_exit_more_wait(vio) ; + case VIN_TERM: + uty_term_half_close(vf) ; + uty_cli_close(vio) ; /* tell the CLI to stop */ + break ; - /* If a command is not in progress, enable output, which will clear - * the output buffer if there is anything there, plus any close reason, - * and then close. - * - * If command is in progress, then this process will start when it - * completes. - */ - if (!vio->cmd_in_progress) - vio->cmd_out_enabled = 1 ; + case VIN_SHELL: + break ; + + case VIN_FILE: + break ; + + case VIN_PIPE: + break ; + + case VIN_CONFIG: + break ; + + default: + zabort("unknown VIN type") ; + } ; + + /* Finished if half closed the last on the list */ + if (vf == vio->vin_base) + break ; + + /* Hack off head of list. If it is read-only, close & free. */ + ssl_del_head(vio->vin, vin_next) ; + + if (vf->vout_type == VOUT_NONE) + uty_vf_close(vf) ; + } ; /* Make sure no longer holding the config symbol of power */ uty_config_unlock(vio->vty, RESTRICTED_NODE) ; - /* Log closing of VTY_TERM */ - if (vio->type == VTY_TERM) - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; - /* Move to the death watch list */ sdl_del(vio_list_base, vio, vio_list) ; sdl_push(vio_death_watch, vio, vio_list) ; - vio->half_closed = 1 ; + vio->half_closed = true ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Close VTY. + * + * + * + * If no reason for the close has already been set, sets the given reason. + * + * If not already half-closed, close all readers as described above, and then + * kick everything on the vout stack and apply VTY_HALF_CLOSE_TIMEOUT. + * + * + */ +extern void +uty_close (vty_io vio, const char* reason) +{ + if (uty_do_half_close(vio, reason)) + { + vio_vf vf ; + + /* Run down the vout stack, and write-ready enable everything with + * the half close timeout set. + * + * This will have the effect of kicking all output. + */ + vf = vio->vout ; + while (vf != NULL) + { + vf->write_timeout = 0 ; + uty_vf_set_write(vf, on) ; + + vf = ssl_next(vf, vout_next) ; + } ; + } ; } ; /*------------------------------------------------------------------------------ @@ -759,33 +738,70 @@ uty_half_close (vty_io vio, const char* reason) * * The vty structure is placed on death watch, which will finally free the * structure once no longer cmd_in_progress. + * + * + * If no reason for the close has already been set, sets the given reason. + * */ extern void -uty_close (vty_io vio) +uty_close_final(vty_io vio, const char* reason) { VTY_ASSERT_LOCKED() ; - /* Empty all the output buffers */ - vio_fifo_reset_keep(&vio->cli_obuf) ; - vio_fifo_reset_keep(&vio->cmd_obuf) ; - vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; - /* If not already closed, close. */ + uty_do_half_close(vio, reason) ; /* set reason if required, and + make sure is half closed. */ if (!vio->closed) { - uty_half_close(vio, NULL) ; /* place on death watch -- if not - already done */ - if (vio->type == VTY_TERM) - uty_cli_close(vio) ; /* tell the CLI to stop */ + vio_vf vf ; + + /* Empty the vin stack */ + assert(vio->vin != NULL) ; + assert(vio->vin == vio->vin_base) ; + if (vio->vin != vio->vout_base) + uty_vf_close(vio->vin) ; + vio->vin_base = vio->vin = NULL ; + + /* Now kick everything in the vout stack, in case can get stuff + * written away. And then close. + */ + while (ssl_pop(&vf, vio->vout, vout_next) != NULL) + { + uty_vf_set_write(vf, off) ; /* stop any write ready... */ + vf->closing = true ; /* ...permanently. */ + + switch(vf->vout_type) /* tidy up each type */ + { + case VOUT_NONE: + break ; + + case VOUT_TERM: + break ; + + case VOUT_SHELL: + break ; + + case VOUT_FILE: + break ; + + case VOUT_PIPE: + break ; - vio->closed = 1 ; /* now closed (stop uty_write() - from recursing) */ + case VOUT_STDOUT: + break ; - if (vio->sock.write_open) - uty_write(vio) ; /* last gasp attempt */ + case VOUT_STDERR: + break ; - uty_sock_close(&vio->sock) ; + default: + zabort("unknown VOUT type") ; + } ; + uty_vf_close(vf) ; /* close and free */ + } ; + + vio->vout_base = NULL ; /* stack is empty */ + vio->closed = true ; /* now closed */ } ; /* Nothing more should happen, so can now release almost everything, @@ -799,12 +815,12 @@ uty_close (vty_io vio) vio->key_stream = keystroke_stream_free(vio->key_stream) ; - qs_free_body(&vio->cli_prompt_for_node) ; - qs_free_body(&vio->cl) ; + qs_reset(&vio->cli_prompt_for_node, keep_it) ; + qs_reset(&vio->cl, keep_it) ; { qstring line ; - while ((line = vector_ream_keep(&vio->hist)) != NULL) + while ((line = vector_ream(vio->hist, keep_it)) != NULL) qs_reset_free(line) ; } ; @@ -817,404 +833,98 @@ uty_close (vty_io vio) */ if (!vio->cmd_in_progress) { - qs_free_body(&vio->clx) ; + qs_reset(&vio->clx, keep_it) ; vio->vty->buf = NULL ; } ; } ; /*============================================================================== - * For writing configuration file by command, temporarily redirect output to - * an actual file. - */ - -/*------------------------------------------------------------------------------ - * Set the given fd as the VTY_FILE output. + * vio_vf level operations */ -extern void -vty_open_config_write(struct vty* vty, int fd) -{ - vty_io vio ; - - VTY_LOCK() ; - - vio = vty->vio ; - - assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; - - vio->real_type = vio->type ; - - vio->type = VTY_CONFIG_WRITE ; - vio->file_fd = fd ; - vio->file_error = 0 ; - - VTY_UNLOCK() ; -} ; /*------------------------------------------------------------------------------ - * Write away configuration file stuff -- all or just the full lump(s). + * Create and initialise a new vio_vf structure. * - * Returns: > 0 => blocked - * 0 => all gone (up to last lump if !all) - * < 0 => failed -- see vio->file_error - */ -static int -uty_config_write(vty_io vio, bool all) -{ - int ret ; - - VTY_ASSERT_LOCKED() ; - - if (vio->file_error == 0) - { - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; - - if (ret < 0) - vio->file_error = errno ; - } - else - ret = -1 ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Write away any pending stuff, and return the VTY to normal. - */ -extern int -vty_close_config_write(struct vty* vty) -{ - vty_io vio ; - int err ; - - VTY_LOCK() ; - - vio = vty->vio ; - - assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; - - uty_config_write(vio, true) ; /* write all that is left */ - - err = vio->file_error ; - - vio->type = vio->real_type ; - vio->file_fd = -1 ; - vio->file_error = 0 ; - - VTY_UNLOCK() ; - - return err ; -} ; - -/*============================================================================== - * vio_sock level operations - */ - -/*------------------------------------------------------------------------------ - * Initialise a new vio_sock structure. + * There are no errors, yet. * - * Requires that: the vio_sock structure is not currently in use. + * This leaves most things unset/NULL/false. Caller will want to set: * - * if fd >= 0 then: sock is open and ready read and write - * otherwise: sock is not open * - * there are no errors, yet. * * Sets timeout to no timeout at all -- timeout is optional. - * - * NB: MUST be in the CLI thread if the fd is >= 0 ! */ -static void -uty_sock_init_new(vio_sock sock, int fd, void* info) +extern vio_vf +uty_vf_new(vty_io vio, int fd, vfd_type_t type, vfd_io_type_t io_type) { - VTY_ASSERT_LOCKED() ; + vio_vf vf ; - if (fd >= 0) - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_LOCKED() ; - memset(sock, 0, sizeof(struct vio_sock)) ; + vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ; /* Zeroising the structure has set: * - * action = all the actions set NULL + * vin_type = 0 -- VIN_NONE + * vin_next = NULL -- not on a vin list, yet + * + * vout_type = NULL -- VOUT_NONE + * vout_next = NULL -- not on a vout list, yet * - * error_seen = 0 -- no error, yet + * obuf = NULL -- none, yet + * olc = NULL -- none, yet * - * qf = NULL -- no qfile, yet - * t_read = NULL ) no threads, yet - * t_write = NULL ) + * blocking = false + * closing = false * - * v_timeout = 0 -- no timeout set - * timer_runing = 0 -- not running, yet - * t_timer = NULL -- no timer thread, yet - * qtr = NULL -- no qtimer, yet + * read_open = false + * write_open = false + * error_seen = 0 -- no error seen, yet + * + * read_timeout = 0 -- none + * write_timeout = 0 -- none */ - sock->fd = fd ; - sock->info = info ; + confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ; - sock->read_open = (fd >= 0) ; - sock->write_open = (fd >= 0) ; + vf->vio = vio ; + vf->vfd = vio_fd_new(fd, type, io_type, vf) ; - if ((fd >= 0) && vty_cli_nexus) - { - sock->qf = qps_file_init_new(NULL, NULL); - qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); - } ; + return vf ; } ; /*------------------------------------------------------------------------------ - * Restart the timer. - * - * If a timeout time is set, then start or restart the timer with that value. + * Half close a vio_vf. * - * If no timeout time is set, and the timer is running, unset it. + * Half closes the vio_fd and shuts down all reading. If the vio_fd was + * read-only, the half-close will fully close it, and the vio_fd will have + * been freed. */ static void -uty_sock_restart_timer(vio_sock sock) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->v_timeout != 0) - { - assert(sock->action.timer.anon != NULL) ; - - if (vty_cli_nexus) - { - if (sock->qtr == NULL) /* allocate qtr if required */ - sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, - NULL, sock->info) ; - qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), - sock->action.timer.qnexus) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer); - sock->t_timer = thread_add_timer (vty_master, - sock->action.timer.thread, sock->info, sock->v_timeout) ; - } ; - - sock->timer_running = 1 ; - } - else if (sock->timer_running) - { - if (vty_cli_nexus) - { - if (sock->qtr != NULL) - qtimer_unset(sock->qtr) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer) ; - } ; - - sock->timer_running = 0 ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Set read on/off - * - * Returns: the on/off state set - */ -static bool -uty_sock_set_read(vio_sock sock, bool on) +uty_vf_half_close(vio_vf vf) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->fd < 0) - return 0 ; - - if (on) - { - assert(sock->action.read.anon != NULL) ; - - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; - else - { - if (sock->t_read != NULL) - thread_cancel(sock->t_read) ; + /* the vfd level half close will close completely if is read only. */ + vf->vfd = vio_fd_half_close(vf->vfd) ; - sock->t_read = thread_add_read(vty_master, - sock->action.read.thread, sock->info, sock->fd) ; - } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_read_mbit) ; - else - { - if (sock->t_read != NULL) - thread_cancel (sock->t_read) ; - } ; - } ; + vf->read_open = false ; /* no more read operations */ + vf->read_on = off ; /* of course */ - return on ; + if (vf->vfd == NULL) /* check really was read-only */ + assert(!vf->write_open && !vf->read_on) ; } ; /*------------------------------------------------------------------------------ - * Set write on/off + * Close given vio_vf and free the vio_vf structure and all its contents. * - * Returns: the on/off state set - */ -static bool -uty_sock_set_write(vio_sock sock, bool on) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->fd < 0) - return 0 ; - - if (on) - { - assert(sock->action.write.anon != NULL) ; - - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; - else - { - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; - - sock->t_write = thread_add_write(vty_master, - sock->action.write.thread, sock->info, sock->fd) ; - } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_write_mbit) ; - else - { - if (sock->t_write != NULL) - thread_cancel (sock->t_write) ; - } ; - } ; - - return on ; -} ; - -/*------------------------------------------------------------------------------ - * Set read/write readiness -- for VTY_TERM - * - * Note that for VTY_TERM, set only one of read or write, and sets write for - * preference. - */ -extern void -uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - uty_sock_set_read(sock, (ready == read_ready)) ; - uty_sock_set_write(sock, (ready >= write_ready)) ; -} ; - -/*------------------------------------------------------------------------------ - * Set a new timer value. - */ -extern void -uty_sock_set_timer(vio_sock sock, unsigned long timeout) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - sock->v_timeout = timeout ; - if (sock->timer_running) - uty_sock_restart_timer(sock) ; -} ; - -/*------------------------------------------------------------------------------ - * Close given vty sock for reading. - * - * Sets timer to timeout for clearing any pending output. - * - * NB: if there is a socket, MUST be in the CLI thread - */ -static void -uty_sock_half_close(vio_sock sock) -{ - VTY_ASSERT_LOCKED() ; - - sock->read_open = 0 ; /* make sure */ - - if (sock->fd < 0) - return ; /* nothing more if no socket */ - - VTY_ASSERT_CLI_THREAD() ; - - shutdown(sock->fd, SHUT_RD) ; /* actual half close */ - - uty_sock_set_read(sock, off) ; - uty_sock_set_write(sock, on) ; - sock->v_timeout = 30 ; /* for output to clear */ - uty_sock_restart_timer(sock) ; -} ; - -/*------------------------------------------------------------------------------ - * Close given vio_sock, completely -- shut down any timer. - * - * Structure is cleared of everything except the last error ! - * - * NB: if there is a socket, MUST be in the CLI thread + * Assumes has been removed from vio->vin and vio->vout ! */ static void -uty_sock_close(vio_sock sock) +uty_vf_close(vio_vf vf) { - VTY_ASSERT_LOCKED() ; - - sock->read_open = 0 ; /* make sure */ - sock->write_open = 0 ; + vf->vfd = vio_fd_close(vf->vfd) ; - if (sock->fd < 0) - { - assert( (sock->qf == NULL) - && (sock->qtr == NULL) - && (sock->t_read == NULL) - && (sock->t_write == NULL) - && (sock->t_timer == NULL) ) ; - return ; /* no more to be done here */ - } ; - - VTY_ASSERT_CLI_THREAD() ; - close(sock->fd) ; + vf->obuf = vio_fifo_reset_free(vf->obuf) ; + vf->olc = vio_lc_reset_free(vf->olc) ; - if (vty_cli_nexus) - { - assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; - qps_remove_file(sock->qf) ; - qps_file_free(sock->qf) ; - sock->qf = NULL ; - } ; - - sock->fd = -1 ; - - if (sock->t_read != NULL) - thread_cancel(sock->t_write) ; - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; - - sock->t_read = NULL ; - sock->t_write = NULL ; - - sock->info = NULL ; - sock->action.read.anon = NULL ; - sock->action.write.anon = NULL ; - sock->action.timer.anon = NULL ; - - if (sock->qtr != NULL) - qtimer_free(sock->qtr) ; - if (sock->t_timer != NULL) - thread_cancel(sock->t_timer) ; - - sock->v_timeout = 0 ; - sock->qtr = NULL ; - sock->t_timer = NULL ; + XFREE(MTYPE_VTY, vf) ; } ; /*------------------------------------------------------------------------------ @@ -1225,7 +935,7 @@ uty_sock_close(vio_sock sock) * If is a "monitor", turn that off, *before* issuing log message. */ static int -uty_sock_error(vty_io vio, const char* what) +uty_vf_error(vty_io vio, const char* what) { VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; @@ -1237,16 +947,16 @@ uty_sock_error(vty_io vio, const char* what) if (vio->sock.error_seen == 0) { const char* type ; - switch (vio->type) + switch (vio->vty_type) { case VTY_TERM: type = "VTY Terminal" ; break ; - case VTY_SHELL_SERV: + case VTY_SHELL: type = "VTY Shell Server" ; break ; default: - zabort("unknown VTY type for uty_sock_error()") ; + zabort("unknown VTY type for uty_file_error()") ; } ; vio->sock.error_seen = errno ; @@ -1257,1488 +967,134 @@ uty_sock_error(vty_io vio, const char* what) return -1 ; } ; -/*============================================================================== - * Readiness and the VTY_TERM type VTY. - * - * For VTY_TERM the driving force is write ready. This is used to prompt the - * VTY_TERM when there is outstanding output (obviously), but also if there - * is buffered input in the keystroke stream. - * - * The VTY_TERM uses read ready only when it doesn't set write ready. Does - * not set both at once. - * - * So there is only one, common, uty_ready function, which: - * - * 1. attempts to clear any output it can. - * - * The state of the output affects the CLI, so must always do this before - * before invoking the CLI. - * - * If this write enters the "--more--" state, then will have tried to - * write away the prompt. - * - * 2. invokes the CLI - * - * Which will do either the standard CLI stuff or the special "--more--" - * stuff. - * - * 3. attempts to write any output there now is. - * - * If the CLI generated new output, as much as possible is written away - * now. - * - * If this write enters the "--more--" state, then it returns now_ready, - * if the prompt was written away, which loops back to the CLI. - * - * Note that this is arranging: - * - * a. to write away the "--more--" prompt as soon as the tranche of output to - * which it refers, completes - * - * b. to enter the cli_more_wait CLI for the first time immediately after the - * "--more--" prompt is written away. - * - * The loop limits itself to one trache of command output each time. - * - * Resets the timer because something happened. - */ -static void -uty_ready(vty_io vio) -{ - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - - vio->cmd_out_done = 0 ; /* not done any command output yet */ - - uty_write(vio) ; /* try to clear outstanding stuff */ - do - { - ready = uty_cli(vio) ; /* do any CLI work... */ - ready |= uty_write(vio) ; /* ...and any output that generates */ - } while (ready >= now_ready) ; - - uty_sock_set_readiness(&vio->sock, ready) ; - uty_sock_restart_timer(&vio->sock) ; -} ; - -/*============================================================================== - * Reading from VTY_TERM. - * - * The select/pselect call-back ends up in uty_read_ready(). - * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. - */ - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking CLI - */ -static void -vty_read_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - uty_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking CLI - */ -static int -vty_read_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_read = NULL ; /* implicitly */ - uty_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the keystroke stream - * - * Steal keystroke if required -- see keystroke_input() - * - * Returns: 0 => nothing available - * > 0 => read at least one byte - * -1 => EOF (or not open, or failed) - */ -extern int -uty_read (vty_io vio, keystroke steal) -{ - unsigned char buf[500] ; - int get ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open */ - - get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; - if (get >= 0) - keystroke_input(vio->key_stream, buf, get, steal) ; - else if (get < 0) - { - if (get == -1) - uty_sock_error(vio, "read") ; - - vio->sock.read_open = 0 ; - keystroke_input(vio->key_stream, NULL, 0, steal) ; - - get = -1 ; - } ; - - return get ; -} ; - -/*============================================================================== - * The write sock action for VTY_TERM type VTY - * - * There are two sets of buffering: - * - * cli -- command line -- which reflects the status of the command line - * - * cmd -- command output -- which is written to the file only while - * cmd_out_enabled. - * - * The cli output takes precedence. - * - * Output of command stuff is subject to line_control, and may go through the - * "--more--" mechanism. - */ - -static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; -static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to write -> try to empty buffers - */ -static void -vty_write_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info ; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - uty_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: ready to write -> try to empty buffers - */ -static int -vty_write_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_write = NULL; /* implicitly */ - uty_ready(vio) ; - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Write as much as possible of what there is. - * - * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, - * empty. - * - * Note that if !write_open, or becomes !write_open, then the FIFOs are empty - * and all output instantly successful. - * - * Sets write on if prevented from writing everything available for output - * by write() threatening to block. - * - * Returns: write_ready if should now set write on - * now_ready if should loop back and try again - * not_ready otherwise - */ -static enum vty_readiness -uty_write(vty_io vio) -{ - int ret ; - - VTY_ASSERT_LOCKED() ; - - ret = -1 ; - while (vio->sock.write_open) - { - /* Any outstanding line control output takes precedence */ - if (vio->cmd_lc != NULL) - { - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - if (ret != 0) - break ; - } - - /* Next: empty out the cli output */ - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret != 0) - break ; - - /* Finished now if not allowed to progress the command stuff */ - if (!vio->cmd_out_enabled) - return not_ready ; /* done all can do */ - - /* Last: if there is something in the command buffer, do that */ - if (!vio_fifo_empty(&vio->cmd_obuf)) - { - if (vio->cmd_out_done) - break ; /* ...but not if done once */ - - vio->cmd_out_done = 1 ; /* done this once */ - - assert(!vio->cli_more_wait) ; - - if (vio->cmd_lc != NULL) - ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - else - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; - - /* If moved into "--more--" state@ - * - * * the "--more--" prompt is ready to be written, so do that now - * - * * if that completes, then want to run the CLI *now* to perform the - * first stage of the "--more--" process. - */ - if (vio->cli_more_wait) - { - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret == 0) - return now_ready ; - } ; - - if (ret != 0) - break ; - } - - /* Exciting stuff: there is nothing left to output... - * - * ... watch out for half closed state. - */ - if (vio->half_closed) - { - if (vio->close_reason != NULL) - { - vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ - - struct vty* vty = vio->vty ; - if (vio->cli_drawn || vio->cli_dirty) - vty_out(vty, VTY_NEWLINE) ; - vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; - - vio->cmd_in_progress = 0 ; - - vio->close_reason = NULL ; /* MUST discard now... */ - continue ; /* ... and write away */ - } ; - - if (!vio->closed) /* avoid recursion */ - uty_close(vio) ; - - return not_ready ; /* it's all over */ - } ; - - /* For VTY_TERM: if the command line is not drawn, now is a good - * time to do that. - */ - if (vio->type == VTY_TERM) - if (uty_cli_draw_if_required(vio)) - continue ; /* do that now. */ - - /* There really is nothing left to output */ - return not_ready ; - } ; - - /* Arrives here if there is more to do, or failed (or was !write_open) */ - - if (ret >= 0) - return write_ready ; - - /* If is write_open, then report the error - * - * If still read_open, let the reader pick up and report the error, when it - * has finished anything it has buffered. - */ - if (vio->sock.write_open) - { - if (!vio->sock.read_open) - uty_sock_error(vio, "write") ; - - vio->sock.write_open = 0 ; /* crash close write */ - } ; - - /* For whatever reason, is no longer write_open -- clear all buffers. - */ - vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ - uty_out_clear(vio) ; /* throw away cmd stuff */ - - vio->close_reason = NULL ; /* too late for this */ - - return not_ready ; /* NB: NOT blocked by I/O */ -} ; - /*------------------------------------------------------------------------------ - * Write as much as possible -- for "monitor" output. + * Set required read ready state. Applies the current read timeout. * - * Outputs only: + * Forces off if: vf->closing * - * a. outstanding line control stuff. - * - * b. contents of CLI buffer - * - * And: - * - * a. does not report any errors. - * - * b. does not change anything except the state of the buffers. - * - * In particular, for the qpthreaded world, does not attempt to change - * the state of the qfile or any other "thread private" structures. - * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed (or !write_open) + * Does nothing if: !vf->read_open + * or: vf->vfd == NULL */ -static int -uty_write_monitor(vty_io vio) +extern on_off_t +uty_vf_set_read(vio_vf vf, on_off_t how) { - VTY_ASSERT_LOCKED() ; - - if (!vio->sock.write_open) - return -1 ; - - if (vio->cmd_lc != NULL) - { - int ret ; - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - - if (ret != 0) - return ret ; - } ; - - return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (vf->closing) + how = off ; + return vf->read_on = vf->read_open + ? vio_fd_set_read(vf->vfd, how, vf->read_timeout) + : off ; } ; /*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to possible line control. - * - * Note that even if no "--more--" is set, will have set some height, so - * that does not attempt to empty the FIFO completely all in one go. - * - * If the line control becomes "paused", it is time to enter "--more--" state - * -- unless the FIFO is empty (or "--more--" is not enabled). + * Set required read ready timeout -- if already read_on, restart it. * - * NB: expects that the sock is write_open + * Forces read ready off if: vf->closing * - * Returns: > 0 => blocked or completed one tranche - * 0 => all gone - * < 0 => failed + * Does nothing if: !vf->read_open + * or: vf->vfd == NULL */ -static int -uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +extern on_off_t +uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) { - int ret ; - char* src ; - size_t have ; - - /* Collect another line_control height's worth of output. - * - * Expect the line control to be empty at this point, but it does not have - * to be. - */ - vio_lc_set_pause(lc) ; /* clears lc->paused */ - - src = vio_fifo_get_rdr(vf, &have) ; - - while ((src != NULL) && (!lc->paused)) - { - size_t take ; - take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_rdr(vf, &have, take) ; - } ; - - vio->cli_dirty = (lc->col != 0) ; - - /* Write the contents of the line control */ - ret = uty_write_lc(vio, vf, lc) ; - - if (ret < 0) - return ret ; /* give up now if failed. */ - - if ((ret == 0) && vio_fifo_empty(vf)) - return 0 ; /* FIFO and line control empty */ - - /* If should now do "--more--", now is the time to prepare for that. - * - * Entering more state issues a new prompt in the CLI buffer, which can - * be written once line control write completes. - * - * The "--more--" cli will not do anything until the CLI buffer has - * cleared. - */ - if (lc->paused && vio->cli_more_enabled) - uty_cli_enter_more_wait(vio) ; - - return 1 ; /* FIFO or line control, not empty */ + vf->read_timeout = read_timeout ; + return vf->read_on ? uty_vf_set_read(vf, on) + : off ; } ; /*------------------------------------------------------------------------------ - * Write contents of line control (if any). - * - * NB: expects that the sock is write_open + * Set required write ready state. Applies the current write timeout. * - * NB: does nothing other than write() and buffer management. + * Forces off if: vf->closing * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed + * Does nothing if: !vf->write_open + * or: vf->vfd == NULL */ -static int -uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +extern on_off_t +uty_vf_set_write(vio_vf vf, on_off_t how) { - int ret ; - - ret = vio_lc_write_nb(vio->sock.fd, lc) ; + if (vf->closing) + how = off ; - if (ret <= 0) - vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ - - return ret ; + return vf->write_on = vf->write_open + ? vio_fd_set_write(vf->vfd, how, vf->write_timeout) + : off ; } ; /*------------------------------------------------------------------------------ - * Start command output -- clears down the line control. + * Set required write ready timeout -- if already write_on, restart it. * - * Requires that that current line is empty -- restarts the line control - * on the basis that is at column 0. - */ -extern void -uty_cmd_output_start(vty_io vio) -{ - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Set the effective height for line control (if any) - * - * If using line_control, may enable the "--more--" output handling. - * - * If not, want some limit on the amount of stuff output at a time. + * Forces write ready off if: vf->closing * - * Sets the line control window width and height. - * Sets cli_more_enabled if "--more--" is enabled. + * Does nothing if: !vf->write_open + * or: vf->vfd == NULL */ -extern void -uty_set_height(vty_io vio) +extern on_off_t +uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) { - bool on ; - - on = 0 ; /* default state */ - - if ((vio->cmd_lc != NULL) && !vio->half_closed) - { - int height ; - - height = 0 ; /* default state */ - - if ((vio->width) != 0) - { - /* If window size is known, use lines or given height */ - if (vio->lines >= 0) - height = vio->lines ; - else - { - /* Window height, leaving one line from previous "page" - * and one line for the "--more--" -- if at all possible - */ - height = vio->height - 2 ; - if (height < 1) - height = 1 ; - } ; - } - else - { - /* If window size not known, use lines if that has been set - * explicitly for this terminal. - */ - if (vio->lines_set) - height = vio->lines ; - } ; - - if (height > 0) - on = 1 ; /* have a defined height */ - else - height = 200 ; /* but no "--more--" */ - - vio_lc_set_window(vio->cmd_lc, vio->width, height) ; - } ; - - vio->cli_more_enabled = on ; + vf->write_timeout = write_timeout ; + return vf->write_on ? uty_vf_set_write(vf, on) + : off ; } ; /*============================================================================== - * Timer for VTY_TERM (and VTY_SHELL_SERV). - */ - -/*------------------------------------------------------------------------------ - * Timer has expired. - * - * If half_closed, then this is curtains -- have waited long enough ! - * - * Otherwise, half close the VTY and leave it to the death-watch to sweep up. - */ -static void -uty_timer_expired (vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->half_closed) - return uty_close(vio) ; /* curtains */ - - uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ - } ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: deal with timer timeout. - */ -static void -vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) -{ - vty_io vio = timer_info ; - - VTY_LOCK() ; - - uty_timer_expired(vio); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: deal with timer timeout. - */ -static int -vty_timer_thread (struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - vio->sock.t_timer = NULL ; /* implicitly */ - - uty_timer_expired(vio) ; - - VTY_UNLOCK() ; - return 0; -} - -/*============================================================================== * VTY Listener(s) * - * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. + * Have listeners for VTY_TERMINAL and VTY_SHELL_SERVER types of VTY. */ -typedef struct vty_listener* vty_listener ; - -struct vty_listener -{ - vty_listener next ; /* ssl type list */ - - enum vty_type type ; - - struct vio_sock sock ; -}; - /* List of listeners so can tidy up. */ -static vty_listener vty_listeners_list = NULL ; - -/* Prototypes for listener stuff */ -static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ; -static int uty_serv_sock(const char* addr, unsigned short port) ; -static int uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) ; -static int uty_serv_vtysh(const char *path) ; -static int vty_accept_thread(struct thread *thread) ; -static void vty_accept_qnexus(qps_file qf, void* listener) ; -static int uty_accept(vty_listener listener, int listen_sock) ; -static int uty_accept_term(vty_listener listener) ; -static int uty_accept_shell_serv (vty_listener listener) ; - -static void uty_serv_start_listener(int fd, enum vty_type type) ; - -/*------------------------------------------------------------------------------ - * If possible, will use getaddrinfo() to find all the things to listen on. - */ - -#if defined(HAVE_IPV6) && !defined(NRL) -# define VTY_USE_ADDRINFO 1 -#else -# define VTY_USE_ADDRINFO 0 -#endif +static vio_listener vty_listeners_list = NULL ; /*------------------------------------------------------------------------------ - * Open VTY listener(s) + * Open VTY listener(s) for VTY_TERMINAL and VTY_SHELL_SERVER. * - * addr -- address ) to listen for VTY_TERM connections + * addr -- address ) to listen for VTY_TERMINAL connections * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED + * path -- path for VTY_SHELL_SERVER connections -- if VTYSH_ENABLED */ extern void uty_open_listeners(const char *addr, unsigned short port, const char *path) { VTY_ASSERT_LOCKED() ; - /* If port is set to 0, do not listen on TCP/IP at all! */ + /* If port is set to 0, do not listen for VTY_TERMINAL at all! */ if (port) - { - int n ; - - if (VTY_USE_ADDRINFO) - n = uty_serv_sock_addrinfo(addr, port); - else - n = uty_serv_sock(addr, port); - - if (n == 0) - uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; - } + uty_term_open_listeners(addr, port) ; - /* If want to listen for vtysh, set up listener now */ + /* If want to listen for vtysh, set up listener now */ if (VTYSH_ENABLED && (path != NULL)) uty_serv_vtysh(path) ; } ; /*------------------------------------------------------------------------------ - * Close VTY listener + * Create listener and set it ready to accept. * - * addr -- address ) to listen for VTY_TERM connections - * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED + * Adds to list of listeners for close down. */ extern void -uty_close_listeners(void) +uty_add_listener(int fd, vio_fd_accept* accept_action) { - vty_listener listener ; + vio_listener vl ; VTY_ASSERT_LOCKED() ; - while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) - { - uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ - XFREE(MTYPE_VTY, listener) ; - } ; -} ; + vl = vio_listener_new(fd, accept_action) ; -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- using getaddrinfo(). - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock_addrinfo (const char *hostname, unsigned short port) -{ -#if VTY_USE_ADDRINFO - -# ifndef HAVE_IPV6 -# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? -# endif - - int ret; - int n ; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - char port_str[16]; - - VTY_ASSERT_LOCKED() ; - - /* Want to listen, TCP-wise, on all available address families, on the - * given port. - */ - memset (&req, 0, sizeof (struct addrinfo)); - req.ai_flags = AI_PASSIVE; - req.ai_family = AF_UNSPEC; - req.ai_socktype = SOCK_STREAM; - snprintf(port_str, sizeof(port_str), "%d", port); - - ret = getaddrinfo (hostname, port_str, &req, &ainfo); - - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); - exit (1); - } - - /* Open up sockets on all AF_INET and AF_INET6 addresses */ - ainfo_save = ainfo; - - n = 0 ; - do - { - if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) - continue; - - assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; - - ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype, - ainfo->ai_protocol, ainfo->ai_addr, port) ; - if (ret >= 0) - ++n ; - } - while ((ainfo = ainfo->ai_next) != NULL); - - freeaddrinfo (ainfo_save); - - return n ; - -#else - zabort("uty_serv_sock_addrinfo not implemented") ; -#endif /* VTY_USE_ADDRINFO */ -} - -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock(const char* addr, unsigned short port) -{ - int ret; - int n ; - union sockunion su_addr ; - struct sockaddr* sa ; - - VTY_ASSERT_LOCKED() ; - - n = 0 ; /* nothing opened yet */ - - /* If have an address, see what kind and whether valid */ - sa = NULL ; - - if (addr != NULL) - { - ret = str2sockunion (addr, &su_addr) ; - if (ret == 0) - sa = &su_addr.sa ; - else - uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); - } ; - - /* Try for AF_INET */ - ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ - -#if HAVE_IPV6 - /* Try for AF_INET6 */ - ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ -#endif - - /* If not used the address... something wrong */ - if (sa != NULL) - uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); - - /* Done */ - return n ; -} - -/*------------------------------------------------------------------------------ - * Open a VTY_TERM listener socket. - * - * The sockaddr 'sa' may be NULL or of a different address family, in which - * case "any" address is used. - * - * If the sockaddr 'sa' is used, only the address portion is used. - * - * Returns: < 0 => failed - * == 0 => OK -- did not use the sockaddr 'sa'. - * > 1 => OK -- and did use the sockaddr 'sa' - */ -static int -uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) -{ - union sockunion su ; - int sock ; - int ret ; - - VTY_ASSERT_LOCKED() ; - - /* Is there an address and is it for this family ? */ - if ((sa != NULL) || (sa->sa_family == family)) - /* Set up sockunion containing required family and address */ - sockunion_new_sockaddr(&su, sa) ; - else - { - /* no address or wrong family -- set up empty sockunion of - * required family */ - sockunion_init_new(&su, family) ; - sa = NULL ; - } ; - - /* Open the socket and set its properties */ - sock = sockunion_socket(family, type, protocol) ; - if (sock < 0) - return -1 ; - - ret = sockopt_reuseaddr (sock); - - if (ret >= 0) - ret = sockopt_reuseport (sock); - - if (ret >= 0) - ret = set_nonblocking(sock); - -#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) - /* Want only IPV6 on ipv6 socket (not mapped addresses) - * - * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the - * attempt to bind to :: after binding to 0.0.0.0. - */ - if ((ret >= 0) && (sa->sa_family == AF_INET6)) - { - int on = 1; - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); - } -#endif - - if (ret >= 0) - ret = sockunion_bind (sock, &su, port, sa) ; - - if (ret >= 0) - ret = sockunion_listen (sock, 3); - - if (ret < 0) - { - close (sock); - return -1 ; - } - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_TERM) ; - - /* Return OK and signal whether used address or not */ - return (sa != NULL) ? 1 : 0 ; + ssl_push(vty_listeners_list, vl, next) ; } ; /*------------------------------------------------------------------------------ - * Open a VTY_SHEL_SERV listener socket (UNIX domain). - * - * Returns: < 0 => failed - * >= 0 => OK - */ -static int -uty_serv_vtysh(const char *path) -{ - int ret; - int sock, sa_len, path_len ; - struct sockaddr_un sa_un ; - mode_t old_mask; - struct zprivs_ids_t ids; - - VTY_ASSERT_LOCKED() ; - - /* worry about the path length */ - path_len = strlen(path) + 1 ; - if (path_len >= (int)sizeof(sa_un.sun_path)) - { - uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); - return -1 ; - } ; - - /* First of all, unlink existing socket */ - unlink (path); - - /* Make UNIX domain socket. */ - sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", - errtoa(errno, 0).str) ; - return -1 ; - } - - /* Bind to the required path */ - memset (&sa_un, 0, sizeof(sa_un)); - sa_un.sun_family = AF_UNIX; - strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); - - sa_len = SUN_LEN(&sa_un) ; - -#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN - sa_un.sun_len = sa_len ; -#endif - - old_mask = umask (0007); - - ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; - if (ret < 0) - uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); - - if (ret >= 0) - ret = set_nonblocking(sock); - - if (ret >= 0) - { - ret = listen (sock, 5); - if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, - errtoa(errno, 0).str) ; - } ; - - zprivs_get_ids(&ids); - - if (ids.gid_vty > 0) - { - /* set group of socket */ - if ( chown (path, -1, ids.gid_vty) ) - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", - errtoa(errno, 0).str) ; - } - - umask (old_mask); - - /* Give up now if failed along the way */ - if (ret < 0) - { - close (sock) ; - return -1 ; - } ; - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_SHELL_SERV) ; - - return 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Socket is open -- set a VTY listener going - * - * Note that the vyt_listener structure is passed to the accept action function. - */ -static void -uty_serv_start_listener(int fd, enum vty_type type) -{ - vty_listener listener ; - - listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); - - ssl_push(vty_listeners_list, listener, next) ; - uty_sock_init_new(&listener->sock, fd, listener) ; - - listener->type = type ; - - if (vty_cli_nexus) - listener->sock.action.read.qnexus = vty_accept_qnexus ; - else - listener->sock.action.read.thread = vty_accept_thread ; - - uty_sock_set_read(&listener->sock, on) ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the thread world -- create and dispatch VTY - */ -static int -vty_accept_thread(struct thread *thread) -{ - vty_listener listener = THREAD_ARG(thread) ; - int result ; - - VTY_LOCK() ; - - result = uty_accept(listener, THREAD_FD(thread)); - - uty_sock_set_read(&listener->sock, on) ; - - VTY_UNLOCK() ; - return result ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the qnexus world -- create and dispatch VTY - */ -static void -vty_accept_qnexus(qps_file qf, void* listener) -{ - VTY_LOCK() ; - - uty_accept(listener, qf->fd); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV - */ -static int -uty_accept(vty_listener listener, int listen_sock) -{ - VTY_ASSERT_LOCKED() ; - - assert(listener->sock.fd == listen_sock) ; - - switch (listener->type) - { - case VTY_TERM: - return uty_accept_term(listener) ; - - case VTY_SHELL_SERV: - return uty_accept_shell_serv(listener) ; - - default: - zabort("unknown vty type") ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM - */ -static int -uty_accept_term(vty_listener listener) -{ - int sock_fd; - union sockunion su; - int ret; - unsigned int on; - struct prefix *p ; - - VTY_ASSERT_LOCKED() ; - - /* We can handle IPv4 or IPv6 socket. */ - sockunion_init_new(&su, AF_UNSPEC) ; - - sock_fd = sockunion_accept (listener->sock.fd, &su); - - if (sock_fd < 0) - { - if (sock_fd == -1) - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", - errtoa(errno, 0).str) ; - return -1; - } - - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; - } ; - - /* New socket is open... worry about access lists */ - p = sockunion2hostprefix (&su); - ret = 0 ; /* so far, so good */ - - if ((p->family == AF_INET) && vty_accesslist_name) - { - /* VTY's accesslist apply. */ - struct access_list* acl ; - - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } - -#ifdef HAVE_IPV6 - if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) - { - /* VTY's ipv6 accesslist apply. */ - struct access_list* acl ; - - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } -#endif /* HAVE_IPV6 */ - - prefix_free (p); - - if (ret != 0) - { - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; - close (sock_fd); - return 0; - } ; - - /* Final options (optional) */ - on = 1 ; - ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, - (void*)&on, sizeof (on)); - if (ret < 0) - uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", - sock_fd, errtoa(errno, 0).str) ; - - /* All set -- create the VTY_TERM */ - uty_new_term(sock_fd, &su); - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, - sock_fd) ; - - return 0; -} - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_SHELL_SERV - */ -static int -uty_accept_shell_serv (vty_listener listener) -{ - int sock_fd ; - int ret ; - int client_len ; - struct sockaddr_un client ; - - VTY_ASSERT_LOCKED() ; - - client_len = sizeof(client); - memset (&client, 0, client_len); - - sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, - (socklen_t *) &client_len) ; - - if (sock_fd < 0) - { - uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", - errtoa(errno, 0).str) ; - return -1; - } - - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; - } ; - - /* All set -- create the VTY_SHELL_SERV */ - if (VTYSH_DEBUG) - printf ("VTY shell accept\n"); - - uty_new_shell_serv(sock_fd) ; - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); - return 0; -} - -/*============================================================================== - * Reading from the VTY_SHELL_SERV type sock. - * - * The select/pselect call-back ends up in utysh_read_ready(). - */ - -/*------------------------------------------------------------------------------ - * Ready to read -> kicking the "SHELL_SERV CLI" - * - * End up here when there is something ready to be read. - * - * Will also end up here if an error has occurred, the other end has closed, - * this end has half closed, etc. This fact is used to kick the CLI even when - * there is no data to be read. - * - * Note that nothing is actually read here -- reading is done in the CLI itself, - * if required. - * - * The CLI decides whether to re-enable read, or enable write, or both. - */ -static void -utysh_read_ready(vty_io vio) -{ - uty_sock_set_read(&vio->sock, off) ; - - /* TODO: need minimal "CLI" for VTY_SHELL_SERV - * NB: when output from command is flushed out, must append the - * following four bytes: '\0' '\0' '\0' <ret> - * Where <ret> is the command return code. - */ -} ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" - */ -static void -vtysh_read_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - utysh_read_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" - */ -static int -vtysh_read_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_read = NULL ; /* implicitly */ - utysh_read_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the command line buffer - * - * Lines coming in are terminated by '\0'. - * - * Assumes that the incoming command line is empty or otherwise incomplete. - * - * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping - * when get '\0' or empties the "buf". - * - * When empties "buf", reads a lump from the sock. - * - * Returns: 0 => command line is incomplete - * 1 => have a complete command line - * -1 => EOF (or not open, or failed) - */ -extern int -utysh_read (vty_io vio, qstring cl, qstring buf) -{ - int get ; - char* cp ; - char* ep ; - size_t have ; - - while (1) - { - /* process what there is in the buffer */ - if (buf->len > buf->cp) - { - cp = qs_cp_char(buf) ; - ep = qs_ep_char(buf) ; - have = ep - cp ; - - ep = memchr(cp, '\0', have) ; - if (ep != NULL) - have = ep - cp ; /* have upto, but excluding '\0' */ - - if (have > 0) /* take what have */ - { - qs_insert(cl, cp, have) ; - cl->cp += have ; - buf->cp += have ; - } ; - - if (ep != NULL) /* if found '\0' */ - { - qs_term(cl) ; /* '\0' terminate */ - ++buf->cp ; /* step past it */ - return 1 ; /* have a complete line <<<<<<<<<<<<< */ - } - } ; - - /* buffer is empty -- try and get some more stuff */ - assert(buf->len == buf->cp) ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ - - qs_need(buf, 500) ; /* need a reasonable lump */ - qs_clear(buf) ; /* set cp = len = 0 */ - - get = read_nb(vio->sock.fd, buf->body, buf->size) ; - if (get > 0) - buf->len = get ; - else if (get == 0) - return 0 ; /* have an incomplete line <<<<<<<<<<<< */ - else - { - if (get == -1) - uty_sock_error(vio, "read") ; - - vio->sock.read_open = 0 ; - - return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ - } ; - } ; -} ; - -/*============================================================================== - * Output to vty which are set to "monitor". - * - * This is VERY TRICKY. - * - * If not running qpthreaded, then the objective is to get the message away - * immediately -- do not wish it to be delayed in any way by the thread - * system. - * - * So proceed as follows: - * - * a. wipe command line -- which adds output to the CLI buffer - * - * b. write the CLI buffer to the sock and any outstanding line control. - * - * c. write the monitor output. - * - * If that does not complete, put the tail end to the CLI buffer. - * - * d. restore any command line -- which adds output to the CLI buffer - * - * e. write the CLI buffer to the sock - * - * If that all succeeds, nothing has changed as far as the VTY stuff is - * concerned -- except that possibly some CLI output was sent before it got - * round to it. - * - * Note that step (b) will deal with any output hanging around from an - * earlier step (e). If cannot complete that, then does not add fuel to the - * fire -- but the message will be discarded. - * - * If that fails, or does not complete, then can set write on, to signal that - * there is some output in the CLI buffer that needs to be sent, or some - * error to be dealt with. - * - * The output should be tidy. - * - * To cut down the clutter, step (d) is performed only if the command line - * is not empty (or if in cli_more_wait). Once a the user has started to enter - * a command, the prompt and the command will remain visible. - * - * When logging an I/O error for a vty that happens to be a monitor, the - * monitor-ness has already been turned off. The monitor output code does not - * attempt to log any errors, sets write on so that the error will be picked - * up that way. - * - * However, in the event of an assertion failure, it is possible that an - * assertion will fail inside the monitor output. The monitor_busy flag - * prevents disaster. It is also left set if I/O fails in monitor output, so - * will not try to use the monitor again. - * - * Note that an assertion which is false for all vty monitors will recurse - * through all the monitors, setting each one busy, in turn ! - * - - - * TODO: sort out write on in the qpthreads world ?? - * - * The problem is that the qpselect structure is designed to be accessed ONLY - * within the thread to which it belongs. This makes it impossible for the - * monitor output to set/clear read/write on the vty sock... so some way - * around this is required. - */ - -/*------------------------------------------------------------------------------ - * Output logging information to all vty which are set to "monitor". + * Close all VTY listeners */ extern void -uty_log(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va) +uty_close_listeners(void) { - vty_io vio ; + vio_listener listener ; VTY_ASSERT_LOCKED() ; - vio = sdl_head(vio_monitors_base) ; - - if (vio == NULL) - return ; /* go no further if no "monitor" vtys */ - - /* Prepare line for output. */ - uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ - - /* write to all known "monitor" vty - * - */ - while (vio != NULL) - { - if (!vio->monitor_busy) - { - int ret ; - - vio->monitor_busy = 1 ; /* close the door */ - - uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ - - ret = uty_write_monitor(vio) ; - if (ret == 0) - { - ret = write_nb(vio->sock.fd, ll->line, ll->len) ; - - if (ret >= 0) - { - ret = uty_cli_post_monitor(vio, ll->line + ret, - ll->len - ret) ; - if (ret > 0) - ret = uty_write_monitor(vio) ; - } ; - } ; - - if (ret != 0) - /* need to prod */ ; - - if (ret >= 0) - vio->monitor_busy = 0 ; - } ; - - vio = sdl_next(vio, mon_list) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Async-signal-safe version of vty_log for fixed strings. - * - * This is last gasp operation. - */ -void -vty_log_fixed (const char *buf, size_t len) -{ - vty_io vio ; - - /* Write to all known "monitor" vty - * - * Forget all the niceties -- about to die in any case. - */ - vio = sdl_head(vio_monitors_base) ; - while (vio != NULL) + while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) { - write(vio->sock.fd, buf, len) ; - write(vio->sock.fd, "\r\n", 2) ; - - vio = sdl_next(vio, mon_list) ; + vio_listener_close(listener) ; /* no ceremony, no flowers */ } ; } ; diff --git a/lib/vty_io.h b/lib/vty_io.h index 19689853..ecd7a451 100644 --- a/lib/vty_io.h +++ b/lib/vty_io.h @@ -25,9 +25,12 @@ #ifndef _ZEBRA_VTY_IO_H #define _ZEBRA_VTY_IO_H -#include <stdbool.h> +#include "zebra.h" +#include "misc.h" + #include <errno.h> +#include "vty_io_basic.h" #include "uty.h" #include "vty.h" #include "vio_fifo.h" @@ -62,113 +65,158 @@ * */ -/*------------------------------------------------------------------------------ - * VTY sock structure - * - * Used for VTY_TERM and VTY_SHELL_SERV VTY types, which are attached to TCP - * and UNIX sockets, respectively. - * - * Also used for the associated listeners. +/*============================================================================== + * VTY CLI and OUT types */ +enum vio_in_type /* Command input */ +{ + VIN_NONE = 0, /* no input at all */ -typedef int thread_action(struct thread *) ; + VIN_TERM, /* telnet terminal */ + VIN_SHELL, /* vty_shell input */ -union sock_action -{ - qps_action* qnexus ; - thread_action* thread ; - void* anon ; -} ; + VIN_FILE, /* ordinary file input */ + VIN_PIPE, /* pipe (from child process) */ -union timer_action -{ - qtimer_action* qnexus ; - thread_action* thread ; - void* anon ; + VIN_CONFIG, /* config file ?? */ } ; +typedef enum vio_in_type vio_in_type_t ; -struct vio_sock_actions +enum vio_out_type /* Command output */ { - union sock_action read ; - union sock_action write ; - union timer_action timer ; + VOUT_NONE = 0, /* no output at all */ + + VOUT_TERM, /* a telnet terminal */ + VOUT_SHELL, /* a vty_shell output pipe */ + + VOUT_FILE, /* ordinary file */ + VOUT_PIPE, /* pipe (to child process) */ + + VOUT_STDOUT, /* stdout */ + VOUT_STDERR, /* stderr */ }; +typedef enum vio_out_type vio_out_type_t ; -typedef struct vio_sock* vio_sock ; -struct vio_sock +/*------------------------------------------------------------------------------ + * VIO file structure + * + * All I/O is non-blocking for all sources and sinks of VIO stuff. + * + * Also used for the associated listeners. + */ +typedef struct vio_vf* vio_vf ; + +struct vio_vf { - int fd ; + vty_io vio ; /* parent */ - void* info ; /* for action routines */ + vio_in_type_t vin_type ; + vio_vf vin_next ; /* list of inputs */ - struct vio_sock_actions action ; + vio_out_type_t vout_type ; + vio_vf vout_next ; /* list of outputs */ - bool read_open ; /* read returns 0 if not open */ - bool write_open ; /* write completes instantly if not open */ - int error_seen ; /* non-zero => failed */ + vio_fifo obuf ; /* pointer to fifo */ + vio_line_control olc ; /* pointer to lc */ - qps_file qf ; /* when running qnexus */ + vio_fd vfd ; - struct thread *t_read; /* when running threads */ - struct thread *t_write; + bool blocking ; /* using blocking reads */ + bool closing ; /* suppress read/write ready */ - unsigned long v_timeout; /* time-out in seconds -- 0 => none */ - bool timer_running ; /* true when timer is running */ + bool read_open ; /* reads returns 0 if not */ + bool write_open ; /* writes complete instantly if not */ + int error_seen ; /* non-zero => failed */ - qtimer qtr; /* when running qnexus */ - struct thread *t_timer; /* when running threads */ + on_off_b read_on ; + on_off_b write_on ; -} ; - -enum -{ - on = true, - off = false + vty_timer_time read_timeout ; + vty_timer_time write_timeout ; } ; enum vty_readiness /* bit significant */ { - not_ready = 0, - read_ready = 1, - write_ready = 2, /* takes precedence */ - now_ready = 4 + not_ready = 0, + read_ready = 1, + write_ready = 2, /* takes precedence */ + now_ready = 4 } ; /*------------------------------------------------------------------------------ * The vty_io structure + * + * + * + * + * */ -struct vty_io { +struct vty_io +{ struct vty* vty ; /* the related vty */ char *name ; /* for VTY_TERM is IP address) */ + /* vin stack */ + vio_vf vin ; + vio_vf vin_base ; + + /* vout stack */ + vio_vf vout ; + vio_vf vout_base ; + /* List of all vty_io objects */ struct dl_list_pair(vty_io) vio_list ; /* List of all vty_io that are in monitor state */ struct dl_list_pair(vty_io) mon_list ; - /* VTY type and sock stuff */ - enum vty_type type; + /* VTY state */ - struct vio_sock sock ; /* for VTY_TERM and VTY_SHELL_SERV */ - - bool half_closed ; /* => on death watch list */ + bool half_closed ; /* => on death watch list until closed */ bool closed ; /* => all I/O terminated will also be half_closed */ - const char* close_reason ; /* message to be sent, once all other + char* close_reason ; /* message to be sent, once all other output has completed, giving reason for closing the VTY. */ + + + + /* When writing configuration file */ enum vty_type real_type ; int file_fd ; int file_error ; - /*--------------------------------------------------------------------*/ - /* Command line and related state */ + /* Failure count for login attempts */ + int fail; + + /* History of commands */ + vector_t hist ; + int hp ; /* History lookup current point */ + int hindex; /* History insert end point */ + + /* Window width/height as reported by Telnet. 0 => unknown */ + int width; + int height; + + /* Configure lines. */ + int lines; + bool lines_set ; /* true <=> explicitly set */ + + /* Terminal monitor. */ + bool monitor ; + bool monitor_busy ; + + /* Terminal timeout in seconds -- 0 => none */ + vty_timer_time v_timeout ; + + /*------------------------------------------------------------------------- + * CLI_TERM stuff. + */ keystroke_stream key_stream ; @@ -242,69 +290,111 @@ struct vty_io { /* CLI output buffering */ vio_fifo_t cli_obuf ; - /* Command output buffering */ - vio_fifo_t cmd_obuf ; +} ; - vio_line_control cmd_lc ; +/*============================================================================== + * If possible, will use getaddrinfo() to find all the things to listen on. + */ +enum { +#if defined(HAVE_IPV6) && !defined(NRL) + VTY_USE_ADDRINFO = 1, +#else + VTY_USE_ADDRINFO = 0, +#endif +} ; - /* Failure count for login attempts */ - int fail; +/*============================================================================== + * Functions + */ - /* History of commands */ - vector_t hist ; - int hp ; /* History lookup current point */ - int hindex; /* History insert end point */ +extern vty uty_new (vty_type_t type, int sock_fd) ; +extern void uty_close (vty_io vio, const char* reason) ; +extern void uty_close_final(vty_io vio, const char* reason) ; - /* Window width/height as reported by Telnet. 0 => unknown */ - int width; - int height; - /* Configure lines. */ - int lines; - bool lines_set ; /* true <=> explicitly set */ +extern void uty_vin_add(vty_io vio, vio_vf vf, vio_in_type_t type, + vio_fd_action* read_action, vio_timer_action* read_timer_action) ; +extern void uty_vout_add(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_fd_action* write_action, vio_timer_action* write_timer_action) ; - /* Terminal monitor. */ - bool monitor ; - bool monitor_busy ; - /* In configure mode. */ - bool config; -} ; -/*============================================================================== - * Functions - */ +extern vio_vf uty_vf_new(vty_io vio, int fd, vfd_type_t type, + vfd_io_type_t io_type) ; +Inline int uty_vf_fd(vio_vf vf) ; +extern on_off_t uty_vf_set_read(vio_vf vf, on_off_t on) ; +extern on_off_t uty_vf_set_read_timeout(vio_vf vf, + vty_timer_time read_timeout) ; +extern on_off_t uty_vf_set_write(vio_vf vf, on_off_t on) ; +extern on_off_t uty_vf_set_write_timeout(vio_vf vf, + vty_timer_time write_timeout) ; + -extern struct vty* uty_new (enum vty_type type, int sock_fd) ; extern void uty_open_listeners(const char *addr, unsigned short port, const char *path) ; +extern void uty_add_listener(int fd, vio_fd_accept* accept) ; extern void uty_close_listeners(void) ; +extern void uty_watch_dog_init(void) ; extern void uty_watch_dog_start(void) ; extern void uty_watch_dog_stop(void) ; -extern void uty_half_close (vty_io vio, const char* reason) ; -extern void uty_close (vty_io vio) ; -extern int uty_out (struct vty *vty, const char *format, ...) + +extern int uty_output (struct vty *vty, const char *format, ...) PRINTF_ATTRIBUTE(2, 3) ; -extern int uty_vout(struct vty *vty, const char *format, va_list args) ; +extern int uty_vprintf(struct vty *vty, const char *format, va_list args) ; +extern int uty_reflect(struct vty *vty) ; extern void uty_out_clear(vty_io vio) ; extern void uty_out_fflush(vty_io vio, FILE* file) ; extern void uty_set_height(vty_io vio) ; extern void uty_cmd_output_start(vty_io vio) ; -extern void uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) ; -extern void uty_sock_set_timer(vio_sock sock, unsigned long timeout) ; +extern void uty_file_set_readiness(vio_vf vf, enum vty_readiness ready) ; +extern void uty_file_set_timer(vio_vf vf, unsigned long timeout) ; extern int uty_read (vty_io vio, keystroke steal) ; extern int utysh_read (vty_io vio, qstring cl, qstring buf) ; - extern const char* uty_get_name(vty_io vio) ; extern void uty_set_monitor(vty_io vio, bool on) ; +/*============================================================================== + * Inline Functions + */ + +/*------------------------------------------------------------------------------ + * Return the fd from a vio_fd structure + */ +Inline int +uty_vf_fd(vio_vf vf) +{ + return vio_fd_fd(vf->vfd) ; +} ; + +/*------------------------------------------------------------------------------ + * Return the fd from a vio_fd structure + */ + +Inline bool +uty_is_terminal(struct vty *vty) +{ + return vty->type == VTY_TERMINAL ; +} + +Inline bool +uty_is_shell_server(struct vty *vty) +{ + return vty->type == VTY_SHELL_SERVER ; +} + +Inline bool +uty_is_shell_client(struct vty *vty) +{ + return vty->type == VTY_SHELL_CLIENT ; +} + #endif /* _ZEBRA_VTY_IO_H */ diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c new file mode 100644 index 00000000..3bead51f --- /dev/null +++ b/lib/vty_io_basic.c @@ -0,0 +1,1063 @@ +/* VTY IO Basic Functions -- bottom level of VTY IO hierarchy + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty_io_basic.h" + +/*============================================================================== + * Base level I/O and Timer handling.... + * + * This is separated out so that the differences between running in a qpnexus + * and an old thread environment are encapsulated here. + */ + +struct vio_io_set_args /* to CLI thread */ +{ + bool active ; /* set when queued, cleared when dequeued */ + bool die ; /* set when is queued and vio_fd is closed */ + bool close ; /* close and free the vio_fd and mqb */ + + bool readable ; /* set when read state to be changed */ + on_off_t read_on ; /* what to change read to */ + vty_timer_time read_timeout ; + /* what to set the timeout to, if any */ + + bool writable ; /* set when write state to be changed */ + on_off_t write_on ; /* what to change write to */ + vty_timer_time write_timeout ; + /* what to set the timeout to, if any */ +} ; +MQB_ARGS_SIZE_OK(vio_io_set_args) ; + +static void vio_fd_mqb_dispatch(vio_fd vfd) ; +static void vio_fd_mqb_free(vio_fd vfd) ; +static struct vio_io_set_args* vio_fd_mqb_args(vio_fd vfd) ; + +/*============================================================================== + * File Descriptor handling + * + * Provides read/write ready handling in consistent manner -- so don't care + * whether is qpnexus or old thread environment. + * + * NB: in all cases, when a read/write event goes off, the read/write readiness + * is cleared and any read/write timer is stopped. + * + * In the qpnexus world, there is a small complication... the qpselect stuff + * for all vty lives in the cli thread, so there is a mechanism here to allow + * for messages from other threads to implement the necessary qpselect things, + * see above. + */ + +static void vio_fd_qps_read_action(qps_file qf, void* file_info) ; +static void vio_fd_qps_write_action(qps_file qf, void* file_info) ; +static int vio_fd_thread_read_action(struct thread *thread) ; +static int vio_fd_thread_write_action(struct thread *thread) ; + +static void vio_timer_squelch(vio_timer_t* timer) ; + +Inline void +vio_fd_do_read_action(vio_fd vfd) +{ + if (vfd->active) + vfd->read_action(vfd, vfd->action_info) ; +} + +Inline void +vio_fd_do_write_action(vio_fd vfd) +{ + if (vfd->active) + vfd->write_action(vfd, vfd->action_info) ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new vfd structure. + */ +extern vio_fd +vio_fd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) +{ + vio_fd vfd ; + + vfd = XCALLOC(MTYPE_VTY, sizeof(struct vio_fd)) ; + + /* Has set: + * + * active -- false ! + * + * read_action -- NULL + * write_action -- NULL + * + * f.qf -- NULL + * f.thread.read -- NULL + * f.thread.write -- NULL + * + * mqb -- NULL + */ + + vio_fd_set_fd(vfd, fd, type, io_type) ; + + vio_timer_init(&vfd->read_timer, NULL, NULL) ; + vio_timer_init(&vfd->write_timer, NULL, NULL) ; + + vio_fd_set_action_info(vfd, action_info) ; + + return vfd ; +} ; + +/*------------------------------------------------------------------------------ + * If vfd was not fully set up when created, set it up now. + * + * To close an active vfd, use vio_fd_close() ! + * + * NB: for use when vfd has been created, but the fd was not known at that + * time -- ie the vfd is NOT active. + */ +extern void +vio_fd_set_fd(vio_fd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) +{ + assert(!vfd->active) ; + + vfd->fd = fd ; + vfd->active = (fd >= 0) ; + vfd->type = type ; + vfd->io_type = io_type ; +} ; + +/*------------------------------------------------------------------------------ + * Set the read action field for the given vio_fd. + */ +extern void +vio_fd_set_read_action(vio_fd vfd, vio_fd_action* action) +{ + vfd->read_action = action ; +} ; + +/*------------------------------------------------------------------------------ + * Set the write action field for the given vio_fd. + */ +extern void +vio_fd_set_write_action(vio_fd vfd, vio_fd_action* action) +{ + vfd->write_action = action ; +} ; + +/*------------------------------------------------------------------------------ + * Set the read action field for the given vio_fd. + */ +extern void +vio_fd_set_read_timeout_action(vio_fd vfd, vio_timer_action* action) +{ + vio_timer_set_action(&vfd->read_timer, action) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the write action field for the given vio_fd. + */ +extern void +vio_fd_set_write_timeout_action(vio_fd vfd, vio_timer_action* action) +{ + vio_timer_set_action(&vfd->write_timer, action) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the action_info field for the given vio_fd read/write action. + */ +extern void +vio_fd_set_action_info(vio_fd vfd, void* action_info) +{ + vfd->action_info = action_info ; + vio_timer_set_info(&vfd->read_timer, action_info) ; + vio_timer_set_info(&vfd->write_timer, action_info) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is a read action set for the give vio_fd (if any), then kick it. + */ +extern void +vio_fd_do_read_action(vio_fd vfd) +{ + if ((vfd != NULL) && (vfd->read_action != NULL)) + vio_fd_do_read_action(vfd) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is a write action set for the give vio_fd (if any), then kick it. + */ +extern void +vio_fd_do_write_action(vio_fd vfd) +{ + if ((vfd != NULL) && (vfd->write_action != NULL)) + vio_fd_do_read_action(vfd) ; +} ; + +/*------------------------------------------------------------------------------ + * Half close the given vfd (if any). + * + * If the vfd is a socket, then does a shutdown of the read side. + * + * If the vfd is not a socket and is read (only) closes the vfd. + * + * In any case, turns off any read ready and read ready timeout. + * + * Returns original vfd, or NULL if it has been closed. + */ +extern vio_fd +vio_fd_half_close(vio_fd vfd) +{ + VTY_ASSERT_LOCKED() ; + + if (vfd == NULL) + return NULL ; + + if (vfd->fd >= 0) + { + assert(vfd->active) ; + + if (vfd->io_type & vfd_io_read) + { + if (vfd->io_type & vfd_io_write) + { + /* read & write, so really half-close if can */ + if (vfd->type == vfd_socket) + shutdown(vfd->fd, SHUT_RD) ; + vio_fd_set_read(vfd, off, 0) ; + vfd->io_type ^= vfd_io_read ; /* now write only ! */ + } + else + { + /* read only, so fully close */ + vfd = vio_fd_close(vfd) ; + } ; + } ; + } + else + assert(!vfd->active) ; + + return vfd ; +} ; + +/*------------------------------------------------------------------------------ + * If there is an fd, close it. + * + * Stops any read/write waiting and releases all memory. + * + * NB: this can done from any thread, but if not done from the CLI thread, + * and there is a qf, a message must be sent to the CLI thread to actually + * implement: which passes the vio_fd to the CLI thread for later + * close and destruction. + * + * The actual close has to be delayed, so that cannot open another fd + * and bang into a still active qps_file ! + * + * The message looks after freeing the vio_fd, the qps_file and the mqb. + */ +static void +vio_fd_do_close(vio_fd vfd) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (vty_cli_nexus) + { + if (vfd->f.qf != NULL) + { + assert(vfd->fd == qps_file_fd(vfd->f.qf)) ; + vfd->f.qf = qps_file_free(vfd->f.qf) ; + } ; + vio_fd_mqb_free(vfd) ; + } + else + { + if (vfd->f.thread.read != NULL) + { + assert(vfd->fd == THREAD_FD(vfd->f.thread.read)) ; + thread_cancel(vfd->f.thread.read) ; + vfd->f.thread.read = NULL ; + } ; + + if (vfd->f.thread.write != NULL) + { + assert(vfd->fd == THREAD_FD(vfd->f.thread.write)) ; + thread_cancel(vfd->f.thread.write) ; + vfd->f.thread.write = NULL ; + } ; + + assert(vfd->mqb == NULL) ; + } ; + + if (vfd->fd >= 0) + close(vfd->fd) ; + + vio_timer_reset(&vfd->read_timer) ; + vio_timer_reset(&vfd->write_timer) ; + + XFREE(MTYPE_VTY, vfd) ; +} ; + +/*------------------------------------------------------------------------------ + * Close the given vfd (if any). + * + * If there is an fd, close it. Stops any read/write waiting and releases all + * memory. + * + * NB: this can done from any thread, but if not done from the CLI thread, + * and there is a qf, a message must be sent to the CLI thread to actually + * implement: which passes the vio_fd to the CLI thread for later + * close and destruction. + * + * The actual close has to be delayed, so that cannot open another fd + * and bang into a still active qps_file ! + * + * The message looks after freeing the vio_fd, the qps_file and the mqb. + */ +extern vio_fd +vio_fd_close(vio_fd vfd) +{ + VTY_ASSERT_LOCKED() ; + + if (vfd == NULL) + return NULL ; + + if (vfd->fd < 0) + { + /* closing an inactive vio_fd -- make sure all is quiet */ + assert(!vfd->active) ; + if (vty_cli_nexus) + { + assert(vfd->f.qf == NULL) ; + } + else + { + assert(vfd->f.thread.read == NULL) ; + assert(vfd->f.thread.write == NULL) ; + } ; + assert(vfd->mqb == NULL) ; + } + else + { + /* closing an active vio_fd */ + if (vty_is_cli_thread()) + { + /* In cli thread, so close directly */ + vio_fd_do_close(vfd) ; + } + else + { + /* Rats... have to send message to cli thread to close */ + struct vio_io_set_args* args = vio_fd_mqb_args(vfd) ; + + args->close = true ; + args->die = true ; + + /* in case something goes ready before the close message + * is processed, squelch. + */ + vfd->active = false ; + vfd->read_action = NULL ; + vfd->write_action = NULL ; + vfd->action_info = NULL ; + + vio_timer_squelch(&vfd->read_timer) ; + vio_timer_squelch(&vfd->write_timer) ; + + assert(vfd == mqb_get_arg0(vfd->mqb)) ; + vio_fd_mqb_dispatch(vfd) ; + } ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Set or unset read ready state on given vio_fd (if any) if it is active. + * + * If setting read_on, starts any read timeout timer. + * If setting read off, stops any read timeout timer. + * + * NB: this can done from any thread, but if not done from the CLI thread, + * a message must be sent to the CLI thread to actually implement. + */ +extern on_off_t +vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) +{ + struct vio_io_set_args* args ; + + VTY_ASSERT_LOCKED() ; + + if ((vfd == NULL) || (!vfd->active)) + return off ; + + if (vty_is_cli_thread()) + { + /* In the cli thread (effectively) so do things directly. */ + + if (vfd->mqb != NULL) + { + /* discard/override any pending message setting */ + args = mqb_get_args(vfd->mqb) ; + args->readable = false ; + } ; + + if (on) + { + assert(vfd->read_action != NULL) ; + + if (vty_cli_nexus) + { + if (vfd->f.qf == NULL) + { + vfd->f.qf = qps_file_init_new(NULL, NULL); + qps_add_file(vty_cli_nexus->selection, vfd->f.qf, + vfd->fd, vfd) ; + } ; + qps_enable_mode(vfd->f.qf, qps_read_mnum, + vio_fd_qps_read_action) ; + } + else + { + if (vfd->f.thread.read == NULL) + vfd->f.thread.read = thread_add_read(vty_master, + vio_fd_thread_read_action, vfd, vfd->fd) ; + } ; + + vio_timer_set(&vfd->read_timer, timeout) ; + } + else + { + if (vty_cli_nexus) + { + if (vfd->f.qf != NULL) + qps_disable_modes(vfd->f.qf, qps_read_mbit) ; + } + else + { + if (vfd->f.thread.read != NULL) + thread_cancel (vfd->f.thread.read) ; + } ; + + vio_timer_unset(&vfd->read_timer) ; + } ; + } + else + { + /* In other threads, must send message to cli thread */ + + args = vio_fd_mqb_args(vfd) ; + args->readable = true ; + args->read_on = on ; + args->read_timeout = timeout ; + vio_timer_squelch(&vfd->read_timer) ; + vio_fd_mqb_dispatch(vfd) ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set or unset write ready state on given vio_fd (if any) if it is active. + * + * If setting write_on, starts any write timeout timer. + * If setting write off, stops any write timeout timer. + * + * NB: this can done from any thread, but if not done from the CLI thread, + * a message must be sent to the CLI thread to actually implement. + */ +extern on_off_t +vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) +{ + struct vio_io_set_args* args ; + + VTY_ASSERT_LOCKED() ; + + if ((vfd == NULL) || (!vfd->active)) + return off ; + + if (vty_is_cli_thread()) + { + /* In the cli thread (effectively) so do things directly. */ + + if (vfd->mqb != NULL) + { + /* discard/override any pending message setting */ + args = mqb_get_args(vfd->mqb) ; + args->writable = false ; + } ; + + if (on) + { + assert(vfd->write_action != NULL) ; + + if (vty_cli_nexus) + { + if (vfd->f.qf == NULL) + { + vfd->f.qf = qps_file_init_new(NULL, NULL); + qps_add_file(vty_cli_nexus->selection, vfd->f.qf, + vfd->fd, vfd) ; + } ; + qps_enable_mode(vfd->f.qf, qps_write_mnum, + vio_fd_qps_write_action) ; + } + else + { + if (vfd->f.thread.write == NULL) + vfd->f.thread.write = thread_add_write(vty_master, + vio_fd_thread_write_action, vfd, vfd->fd) ; + } ; + + vio_timer_set(&vfd->write_timer, timeout) ; + } + else + { + if (vty_cli_nexus) + { + if (vfd->f.qf != NULL) + qps_disable_modes(vfd->f.qf, qps_write_mbit) ; + } + else + { + if (vfd->f.thread.write != NULL) + thread_cancel (vfd->f.thread.write) ; + } ; + + vio_timer_unset(&vfd->write_timer) ; + } ; + } + else + { + /* In other threads, must send message to cli thread */ + + args = vio_fd_mqb_args(vfd) ; + args->writable = true ; + args->write_on = on ; + args->write_timeout = timeout ; + vio_timer_squelch(&vfd->write_timer) ; + vio_fd_mqb_dispatch(vfd) ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read + * + * Clears read ready state and unsets any read timer. + * + * NB: if !vfd->active, then has been closed in another thread, but close + * message is yet to be procesed. + */ +static void +vio_fd_qps_read_action(qps_file qf, void* file_info) +{ + vio_fd vfd = file_info ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ; + + qps_disable_modes(vfd->f.qf, qps_read_mbit) ; + vio_timer_unset(&vfd->read_timer) ; + + vio_fd_do_read_action(vfd) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- thread: ready to read + * + * Clears read ready state and unsets any read timer. + * + * NB: if !vfd->active, then has been closed in another thread, but close + * message is yet to be procesed. + */ +static int +vio_fd_thread_read_action(struct thread *thread) +{ + vio_fd vfd = THREAD_ARG (thread); + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + assert(vfd->fd == THREAD_FD(thread)) ; + + vfd->f.thread.read = NULL ; /* implicitly */ + vio_timer_unset(&vfd->read_timer) ; + + vio_fd_do_read_action(vfd) ; + + VTY_UNLOCK() ; + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to write + * + * Clears write ready state and unsets any write timer. + * + * NB: if !vfd->active, then has been closed in another thread, but close + * message is yet to be procesed. + */ +static void +vio_fd_qps_write_action(qps_file qf, void* file_info) +{ + vio_fd vfd = file_info ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ; + + qps_disable_modes(vfd->f.qf, qps_write_mbit) ; + vio_timer_unset(&vfd->write_timer) ; + + vio_fd_do_write_action(vfd) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- thread: ready to write + * + * Clears write ready state and unsets any write timer. + * + * NB: if !vfd->active, then has been closed in another thread, but close + * message is yet to be procesed. + */ +static int +vio_fd_thread_write_action(struct thread *thread) +{ + vio_fd vfd = THREAD_ARG (thread); + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + assert(vfd->fd == THREAD_FD(thread)) ; + vio_timer_unset(&vfd->write_timer) ; + + vfd->f.thread.write = NULL ; /* implicitly */ + + vio_fd_do_write_action(vfd) ; + + VTY_UNLOCK() ; + return 0 ; +} ; + +/*============================================================================== + * Message handling, so that other threads can signal for output to be + * dispatched ! + * + * There is one message block per vfd. It is only every touched under the + * vty_mutex. + * + * Once it is dispatched it is marked 'active'. Can still be changed, but no + * further dispatch is required. When it has been dequeued and processed, + * it is marked inactive. + * + * If the vfd is closed while the message is active, it is marked to die, + * which it will do when it is dequeued and actioned. + */ + +static void vio_fd_set_action(mqueue_block mqb, mqb_flag_t flag) ; + +/*------------------------------------------------------------------------------ + * Get mqb for the given vfd -- make one if required. + */ +static struct vio_io_set_args* +vio_fd_mqb_args(vio_fd vfd) +{ + VTY_ASSERT_LOCKED() ; + + if (vfd->mqb == NULL) + vfd->mqb = mqb_init_new(NULL, vio_fd_set_action, vfd) ; + + return mqb_get_args(vfd->mqb) ; +} ; + +/*------------------------------------------------------------------------------ + * Free mqb for the given vfd -- if any. + */ +static void +vio_fd_mqb_free(vio_fd vfd) +{ + VTY_ASSERT_LOCKED() ; + + if (vfd->mqb != NULL) + { + struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ; + + if (args->active) + { + args->die = true ; + mqb_set_arg0(vfd->mqb, NULL) ; + } + else + { + mqb_free(vfd->mqb) ; + } ; + + vfd->mqb = NULL ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch mqb, if not already active + */ +static void +vio_fd_mqb_dispatch(vio_fd vfd) +{ + struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ; + + VTY_ASSERT_LOCKED() ; + + if (!args->active) + { + args->active = true ; + mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_ordinary) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Action routine for the read/write on/off setting message. + * + * If the mqb is marked to die, then it and any qps_file it points to have been + * cut loose, and now is the time to close the fd and release the qps_file, + * along with releasing the mqb. + */ +static void +vio_fd_set_action(mqueue_block mqb, mqb_flag_t flag) +{ + struct vio_io_set_args* args = mqb_get_args(mqb) ; + vio_fd vfd = mqb_get_arg0(mqb) ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + args->active = false ; + + if ((flag != mqb_destroy) && (!args->die)) + { + if (args->readable) + vio_fd_set_read(vfd, args->read_on, args->read_timeout) ; + if (args->writable) + vio_fd_set_write(vfd, args->write_on, args->write_timeout) ; + } + else + { + if (args->close) + vio_fd_do_close(vfd) ; + mqb_free(mqb) ; + } ; + + VTY_UNLOCK() ; +} ; + +/*============================================================================== + * Listener Handling + * + * + */ + +static void vio_accept(vio_fd vfd, void* info) ; + +/*------------------------------------------------------------------------------ + * Create a new listener object for the newly opened listener socket. + * + * Sets the accept action that will be called, and passed the fd of the listener + * socket, when the listen socket goes 'read ready'. + * + * Returns address of newly created listener structure. + */ +extern vio_listener +vio_listener_new(int fd, vio_fd_accept* accept_action) +{ + vio_listener listener ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + listener = XCALLOC(MTYPE_VTY, sizeof(struct vio_listener)) ; + /* sets the next pointer to NULL */ + + listener->vfd = vio_fd_new(fd, vfd_listener, vfd_io_read, listener) ; + + listener->accept_action = accept_action ; + + vio_fd_set_read_action(listener->vfd, vio_accept) ; + vio_fd_set_read(listener->vfd, on, 0) ; + + return listener ; +} ; + +/*------------------------------------------------------------------------------ + * Close listener and free listener structure. + * Stops any read waiting and releases all memory. + * + * NB: assumes that the structure has been removed from any list. + */ +extern void +vio_listener_close(vio_listener listener) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + vio_fd_close(listener->vfd) ; + XFREE(MTYPE_VTY, listener) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action -- this is the read_action from the listener vfd. + * + * info points at the listener object. + */ +static void +vio_accept(vio_fd vfd, void* info) +{ + vio_listener listener ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + listener = info ; + assert(vfd == listener->vfd) ; + + listener->accept_action(vfd->fd) ; +} ; + +/*============================================================================== + * Timer Handling + * + * Provides timer primitives that work either in qnexus environment or in + * a thread environment. + * + * The main difference is that thread environment timers are 'one-shot', set up + * for one timing run and then destroyed. + */ + +static void vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) ; +static int vio_timer_thread_action(struct thread *thread) ; + +/*------------------------------------------------------------------------------ + * Initialise vio_timer structure. Assumes is all new. + * + * This assumes the vio_timer structure is embedded in another structure. + */ +extern void +vio_timer_init(vio_timer_t* timer, vio_timer_action* action, void* action_info) +{ + memset(timer, 0, sizeof(vio_timer_t)) ; + + /* active -- 0, false + * squelch -- 0, false + * t -- NULL, no qtr and no thread + */ + + timer->action = action ; + timer->action_info = action_info ; +} ; + +/*------------------------------------------------------------------------------ + * Set the action field for the given timer. + */ +extern void +vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action) +{ + timer->action = action ; +} ; + +/*------------------------------------------------------------------------------ + * Set the info field for the given timer. + */ +extern void +vio_timer_set_info(vio_timer_t* timer, void* action_info) +{ + timer->action_info = action_info ; +} ; + +/*------------------------------------------------------------------------------ + * Kill vio_timer -- used when closing . + */ +static void +vio_timer_squelch(vio_timer_t* timer) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + timer->squelch = true ; +} ; + +/*------------------------------------------------------------------------------ + * Reset vio_timer structure. Stops any timer and releases all memory. + * + * This assumes the vio_timer structure is embedded in another structure. + */ +extern void +vio_timer_reset(vio_timer_t* timer) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (timer->t.anon != NULL) + { + if (vty_cli_nexus) + qtimer_free(timer->t.qtr) ; + else + thread_cancel(timer->t.thread) ; + + timer->t.anon = NULL ; + } ; + + timer->active = false ; + timer->squelch = false ; +} ; + +/*------------------------------------------------------------------------------ + * Set vio_timer going, with the given time. + * + * If timer is running, set to new time. + * + * If the time == 0, stop any current timer, do not restart. + */ +extern void +vio_timer_set(vio_timer_t* timer, vty_timer_time time) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (time == 0) + { + vio_timer_unset(timer) ; + return ; + } ; + + assert(timer->action != NULL) ; + + if (vty_cli_nexus) + { + if (timer->t.qtr == NULL) /* allocate qtr if required */ + timer->t.qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, + vio_timer_qtr_action, timer) ; + qtimer_set(timer->t.qtr, qt_add_monotonic(QTIME(time)), NULL) ; + } + else + { + if (timer->t.thread != NULL) + thread_cancel(timer->t.thread) ; + timer->t.thread = thread_add_timer(vty_master, + vio_timer_thread_action, timer, time) ; + } ; + + timer->active = true ; + timer->squelch = false ; +} ; + +/*------------------------------------------------------------------------------ + * Stop vio_timer, if any. + */ +extern void +vio_timer_unset(vio_timer_t* timer) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (timer->active) + { + if (vty_cli_nexus) + { + assert(timer->t.qtr != NULL) ; + qtimer_unset(timer->t.qtr) ; + } + else + { + assert(timer->t.thread != NULL) ; + thread_cancel(timer->t.thread) ; + timer->t.thread = NULL ; + } ; + + timer->active = false ; + } ; + + timer->squelch = false ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: deal with timer timeout. + */ +static void +vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) +{ + vio_timer_t* timer = timer_info ; + vty_timer_time time ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + if (!timer->squelch) /* do nothing if squelched */ + { + time = timer->action(timer, timer->action_info) ; + if (time != 0) + vio_timer_set(timer, time) ; + else + timer->active = false ; + } + else + { + timer->squelch = false ; + timer->active = false ; + } ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Callback -- thread: deal with timer timeout. + */ +static int +vio_timer_thread_action(struct thread *thread) +{ + vio_timer_t* timer = THREAD_ARG (thread); + vty_timer_time time ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + timer->t.thread = NULL ; /* implicitly */ + + if (!timer->squelch) /* do nothing if squelched */ + { + time = timer->action(timer, timer->action_info) ; + if (time != 0) + vio_timer_set(timer, time) ; + else + timer->active = false ; + } + else + { + timer->squelch = false ; + timer->active = false ; + } ; + + VTY_UNLOCK() ; + return 0; +} ; diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h new file mode 100644 index 00000000..a2b1cbb3 --- /dev/null +++ b/lib/vty_io_basic.h @@ -0,0 +1,186 @@ +/* VTY IO Structure and Functions -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_BASIC_H +#define _ZEBRA_VTY_IO_BASIC_H + +#include "misc.h" + +#include "vty.h" + +#include "qpselect.h" +#include "thread.h" +#include "mqueue.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by all the + * VTY I/O hierarchy, providing the basic read/write ready functions and + * timer functions. + * + * This is separated out so that the differences between running in a qpnexus + * and an old thread environment are encapsulated here. + */ + +enum vfd_type +{ + vfd_none = 0, + vfd_socket, + vfd_file, + vfd_pipe, + vfd_listener, +} ; +typedef enum vfd_type vfd_type_t ; + +enum vfd_io_type /* NB: *bit*significant* */ +{ + vfd_io_none = 0, + vfd_io_read = 1, + vfd_io_write = 2, + vfd_io_read_write = 3, +} ; +typedef enum vfd_io_type vfd_io_type_t ; + +/*------------------------------------------------------------------------------ + * Timers -- implemented as qtimer or thread timer, depending on environment. + */ +typedef struct vio_timer vio_timer_t ; +typedef vty_timer_time vio_timer_action(vio_timer_t* timer, void* action_info) ; + +struct vio_timer +{ + vio_timer_action* action ; /* who do we call */ + void* action_info ; + + bool active ; + bool squelch ; /* used when message pending */ + + union { + qtimer qtr ; /* when running qnexus */ + struct thread* thread ; /* when running threads */ + void* anon ; + } t ; +} ; + +/*------------------------------------------------------------------------------ + * File descriptors -- looks after ready to read and/or write. + * + * Implemented as qps_file or as read/write thread, depending on the + * environment. + */ +typedef struct vio_fd* vio_fd ; +typedef void vio_fd_action(vio_fd vfd, void* action_info) ; + +struct vio_fd +{ + int fd ; + bool active ; /* used for close message */ + + vfd_type_t type ; /* used for half-close */ + vfd_io_type_t io_type ; /* read, write, read/write */ + + vio_fd_action* read_action ; + vio_fd_action* write_action ; + + vio_timer_t read_timer ; + vio_timer_t write_timer ; + + void* action_info ; /* for all action and time-out */ + + union + { + qps_file qf ; /* when running qnexus */ + + struct /* when running threads */ + { + struct thread* read ; + struct thread* write ; + } thread ; + } f ; + + mqueue_block mqb ; +} ; + +/*------------------------------------------------------------------------------ + * Listeners + */ + +typedef struct vio_listener* vio_listener ; + +typedef void vio_fd_accept(int fd) ; + +struct vio_listener +{ + vio_listener next ; /* ssl type list */ + vio_fd vfd ; + vio_fd_accept* accept_action ; +}; + +/*============================================================================== + * Functions + */ + +extern vio_fd vio_fd_new(int fd, vfd_type_t type, + vfd_io_type_t io_type, void* action_info) ; +extern void vio_fd_set_fd(vio_fd vfd, int fd, vfd_type_t type, + vfd_io_type_t io_type) ; +extern void vio_fd_set_read_action(vio_fd vfd, vio_fd_action* action) ; +extern void vio_fd_set_write_action(vio_fd vfd, vio_fd_action* action) ; +extern void vio_fd_set_read_timeout_action(vio_fd vfd, + vio_timer_action* action) ; +extern void vio_fd_set_write_timeout_action(vio_fd vfd, + vio_timer_action* action) ; +extern void vio_fd_set_action_info(vio_fd vfd, void* action_info) ; +extern vio_fd vio_fd_half_close(vio_fd vfd) ; +extern vio_fd vio_fd_close(vio_fd vfd) ; +extern on_off_t vio_fd_set_read(vio_fd vfd, on_off_t on, + vty_timer_time timeout) ; +extern on_off_t vio_fd_set_write(vio_fd vfd, on_off_t on, + vty_timer_time timeout) ; +Inline int vio_fd_fd(vio_fd vfd) ; + +extern vio_listener vio_listener_new(int fd, vio_fd_accept* accept) ; +extern void vio_listener_close(vio_listener listener) ; + +extern void vio_timer_init(vio_timer_t* timer, vio_timer_action* action, + void* action_info) ; +extern void vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action) ; +extern void vio_timer_set_info(vio_timer_t* timer, void* action_info) ; +extern void vio_timer_reset(vio_timer_t* timer) ; +extern void vio_timer_set(vio_timer_t* timer, vty_timer_time time) ; +extern void vio_timer_unset(vio_timer_t* timer) ; + +/*============================================================================== + * Inline Functions + */ + +/*------------------------------------------------------------------------------ + * Return the fd from a vio_fd structure + */ +Inline int +vio_fd_fd(vio_fd vfd) +{ + return vfd->fd ; +} ; + +#endif /* _ZEBRA_VTY_IO_BASIC_H */ diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c new file mode 100644 index 00000000..ed2c35e8 --- /dev/null +++ b/lib/vty_io_file.c @@ -0,0 +1,2608 @@ +/* VTY Log Functions + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty.h" +#include "vty_io.h" +#include "vty_cli.h" +#include "qstring.h" +#include "keystroke.h" + +#include "memory.h" + +#include "prefix.h" +#include "filter.h" +#include "privs.h" +#include "sockunion.h" +#include "network.h" + +#include <arpa/telnet.h> +#include <sys/un.h> /* for VTYSH */ +#include <sys/socket.h> + +#define VTYSH_DEBUG 0 + +/*============================================================================== + * VTY Log Output -- provides a "vty" interface to logging. + * + * Once a VTY_LOG object is created, can be used as an (output only) vty. + * + * All output is sent to the logging system, + * + * + * During command processing the output sent here is held until the command + * completes. + */ + +static int uty_config_write(vty_io vio, bool all) ; + +/*------------------------------------------------------------------------------ + * VTY output function -- cf fprintf + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + */ +extern int +uty_out (struct vty *vty, const char *format, ...) +{ + int result; + VTY_ASSERT_LOCKED() ; + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + return result; +} + +/*------------------------------------------------------------------------------ + * VTY output function -- cf vfprintf + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + * + * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: + * + * * MAY NOT do any command output if !cmd_enabled + * + * * first, the life of a vty is not guaranteed unless cmd_in_progress, + * so should not attempt to use a vty anywhere other than command + * execution. + * + * * second, cmd_out_enabled is false most of the time, and is only + * set true when a command completes, and it is time to write away + * the results. + * + * * all output is placed in the vio->cmd_obuf. When the command completes, + * the contents of the cmd_obuf will be written away -- subject to line + * control. + * + * * output is discarded if the vty is no longer write_open + */ +extern int +uty_vout(struct vty *vty, const char *format, va_list args) +{ + vty_io vio ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + vio = vty->vio ; + + switch (vio->type) + { + case VTY_STDOUT: + case VTY_SHELL: + ret = vprintf (format, args) ; + break ; + + case VTY_STDERR: + ret = vfprintf (stderr, format, args) ; + break ; + + case VTY_CONFIG_WRITE: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) + ret = uty_config_write(vio, false) ; + break ; + + case VTY_TERM: + case VTY_SHELL_SERV: + assert(vio->cmd_in_progress) ; + + if (!vio->sock.write_open) + return 0 ; /* discard output if not open ! */ + + /* fall through.... */ + + case VTY_CONFIG_READ: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + break ; + + default: + zabort("impossible VTY type") ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Clear the contents of the command output FIFO etc. + * + * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc + * flags -- competent parties must deal with those + */ +extern void +uty_out_clear(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + vio_fifo_clear(&vio->cmd_obuf) ; + + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Flush the contents of the command output FIFO to the given file. + * + * Takes no notice of any errors ! + */ +extern void +uty_out_fflush(vty_io vio, FILE* file) +{ + char* src ; + size_t have ; + + VTY_ASSERT_LOCKED() ; + + fflush(file) ; + + while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + { + fwrite(src, 1, have, file) ; + vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + } ; + + fflush(file) ; +} ; + +/*============================================================================== + * Prototypes. + */ +static void uty_sock_init_new(vio_sock sock, int fd, void* info) ; +static void uty_sock_half_close(vio_sock sock) ; +static void uty_sock_close(vio_sock sock) ; + +static void vty_read_qnexus (qps_file qf, void* file_info) ; +static void vty_write_qnexus (qps_file qf, void* file_info) ; +static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ; + +static int vty_read_thread (struct thread *thread) ; +static int vty_write_thread (struct thread *thread) ; +static int vty_timer_thread (struct thread *thread) ; + +static void vtysh_read_qnexus (qps_file qf, void* file_info) ; +static int vtysh_read_thread (struct thread *thread) ; + +static enum vty_readiness uty_write(vty_io vio) ; + +/*============================================================================== + * Creation and destruction of VTY objects + */ + +/*------------------------------------------------------------------------------ + * Allocate new vty struct + * + * Allocates and initialises basic vty and vty_io structures, setting the + * given type. + * + * Note that where is not setting up a vty_sock, this *may* be called from + * any thread. + * + * NB: may not create a VTY_CONFIG_WRITE type vty directly + * + * see: vty_open_config_write() and vty_close_config_write() + * + * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. + * (So MUST be in the CLI thread to set those up !) + * + * the sock_fd is ignored for everything else. + * + * Returns: new vty + */ +extern struct vty * +uty_new(enum vty_type type, int sock_fd) +{ + struct vty *vty ; + struct vty_io* vio ; + + VTY_ASSERT_LOCKED() ; + + /* If this is a VTY_TERM or a VTY_SHELL, place */ + switch (type) + { + case VTY_TERM: /* Require fd -- Telnet session */ + case VTY_SHELL_SERV: /* Require fd -- Unix socket */ + assert(sock_fd >= 0) ; + break ; + + case VTY_CONFIG_WRITE: + zabort("may not make a new VTY_CONFIG_WRITE VTY") ; + break ; + + case VTY_CONFIG_READ: + case VTY_STDOUT: + case VTY_STDERR: + case VTY_SHELL: + sock_fd = -1 ; /* No fd -- output to stdout/stderr */ + break ; + + default: + zabort("unknown VTY type") ; + } ; + + /* Basic allocation */ + vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; + + vty->vio = vio ; + vio->vty = vty ; + + /* Zeroising the vty_io structure has set: + * + * name = NULL -- no name, yet + * + * vio_list both pointers NULL + * mon_list both pointers NULL + * + * half_closed = 0 -- NOT half closed (important !) + * closed = 0 -- NOT closed (important !) + * close_reason = NULL -- no reason, yet + * + * real_type = 0 -- not material + * file_fd = 0 -- not material + * file_error = 0 -- not material + * + * key_stream = NULL -- no key stream (always empty, at EOF) + * + * cli_drawn = 0 -- not drawn + * cli_dirty = 0 -- not dirty + * cli_prompt_len = 0 ) + * cli_extra_len = 0 ) not material + * cli_echo_suppress = 0 ) + * + * cli_prompt_node = 0 -- not material + * cli_prompt_set = 0 -- so prompt needs to be constructed + * + * cli_blocked = 0 -- not blocked + * cmd_in_progress = 0 -- no command in progress + * cmd_out_enabled = 0 -- command output is disabled + * cli_wait_more = 0 -- not waiting for response to "--more--" + * + * cli_more_enabled = 0 -- not enabled for "--more--" + * + * cmd_out_done = 0 -- not material + * + * cli_do = 0 == cli_do_nothing + * + * cmd_lc = NULL -- no line control + * + * fail = 0 -- no login failures yet + * + * hist = empty vector + * hp = 0 -- at the beginning + * hindex = 0 -- the beginning + * + * width = 0 -- unknown console width + * height = 0 -- unknown console height + * + * lines = 0 -- no limit + * lines_set = 0 -- no explicit setting + * + * monitor = 0 -- not a monitor + * monitor_busy = 0 -- not a busy monitor + * + * config = 0 -- not holder of "config" mode + */ + confirm(cli_do_nothing == 0) ; + confirm(AUTH_NODE == 0) ; /* default node type */ + + vio->type = type ; + + /* Zeroising the vty structure has set: + * + * node = 0 TODO: something better for node value ???? + * buf = NULL -- no command line, yet + * parsed = NULL -- no parsed command, yet + * lineno = 0 -- nothing read, yet + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet + */ + + /* Initialise the vio_sock, */ + uty_sock_init_new(&vio->sock, sock_fd, vio) ; + + /* Make sure all buffers etc. are initialised clean and empty. + * + * Note that no buffers are actually allocated at this stage. + */ + qs_init_new(&vio->cli_prompt_for_node, 0) ; + + qs_init_new(&vio->cl, 0) ; + qs_init_new(&vio->clx, 0) ; + + vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ + + vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; + + /* Place on list of known vio/vty */ + sdl_push(vio_list_base, vio, vio_list) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_TERM -- ie attached to a telnet session. + * + * Returns: new vty + */ +static struct vty * +uty_new_term(int sock_fd, union sockunion *su) +{ + struct vty *vty ; + vty_io vio ; + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (VTY_TERM, sock_fd) ; + vio = vty->vio ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + + /* Set the socket action functions */ + if (vty_cli_nexus) + { + vio->sock.action.read.qnexus = vty_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = vty_timer_qnexus ; + } + else + { + vio->sock.action.read.thread = vty_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = vty_timer_thread ; + } ; + + /* The text form of the address identifies the VTY */ + vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); + + /* Set the initial node */ + if (no_password_check) + { + if (restricted_mode) + vty->node = RESTRICTED_NODE; + else if (host.advanced) + vty->node = ENABLE_NODE; + else + vty->node = VIEW_NODE; + } + else + vty->node = AUTH_NODE; + + /* Pick up current timeout setting */ + vio->sock.v_timeout = vty_timeout_val; + + /* Use global 'lines' setting, as default. May be -1 => unset */ + vio->lines = host.lines ; + + /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ + vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; + uty_set_height(vio) ; /* set initial state */ + + /* Initialise the CLI, ready for start-up messages etc. */ + uty_cli_init(vio) ; + + /* Reject connection if password isn't set, and not "no password" */ + if ((host.password == NULL) && (host.password_encrypt == NULL) + && ! no_password_check) + { + uty_half_close (vio, "Vty password is not set."); + vty = NULL; + } + else + { + /* Say hello to the world. */ + vty_hello (vty); + + if (! no_password_check) + uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, + VTY_NEWLINE, VTY_NEWLINE); + } ; + + /* Now start the CLI and set a suitable state of readiness */ + ready = uty_cli_start(vio) ; + uty_sock_set_readiness(&vio->sock, ready) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. + * + * Returns: new vty + */ +static struct vty * +uty_new_shell_serv(int sock_fd) +{ + struct vty *vty ; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (VTY_SHELL_SERV, sock_fd) ; + vio = vty->vio ; + + /* Set the action functions */ + if (vty_cli_nexus) + { + vio->sock.action.read.qnexus = vtysh_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = NULL ; + } + else + { + vio->sock.action.read.thread = vtysh_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = NULL ; + } ; + + vty->node = VIEW_NODE; + + /* Kick start the CLI etc. */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + + return vty; +} ; + +/*------------------------------------------------------------------------------ + * Set/Clear "monitor" state: + * + * set: if VTY_TERM and not already "monitor" (and write_open !) + * clear: if is "monitor" + */ +extern void +uty_set_monitor(vty_io vio, bool on) +{ + VTY_ASSERT_LOCKED() ; + + if (on && !vio->monitor) + { + if ((vio->type == VTY_TERM) && vio->sock.write_open) + { + vio->monitor = 1 ; + sdl_push(vio_monitors_base, vio, mon_list) ; + } ; + } + else if (!on && vio->monitor) + { + vio->monitor = 0 ; + sdl_del(vio_monitors_base, vio, mon_list) ; + } +} ; + +/*------------------------------------------------------------------------------ + * Return "name" of VTY + * + * For VTY_TERM this is the IP address of the far end of the telnet connection. + */ +extern const char* +uty_get_name(vty_io vio) +{ + return (vio->name != NULL) ? vio->name : "?" ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY for reading. + * + * For VTY_TERM (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input, setting it to "EOF" + * * turns off any monitor status ! + * * drop down to RESTRICTED_NODE + * + * For VTY_SHELL_SERV (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input + * * drop down to RESTRICTED_NODE + * + * In all cases: + * + * * place on death watch + * * set the vty half_closed + * * sets the reason for closing (if any given) + * + * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all + * the buffers, the VTY is closed. + * + * May already have set the vio->close_reason, or can set it now. (Passing a + * NULL reason has no effect on any existing posted reason.) + */ +extern void +uty_half_close (vty_io vio, const char* reason) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return ; + + if (reason != NULL) + vio->close_reason = reason ; + + /* Do the file side of things + * + * Note that half closing the file sets a new timeout, sets read off + * and write on. + */ + uty_sock_half_close(&vio->sock) ; + uty_set_monitor(vio, 0) ; + + /* Discard everything in the keystroke stream and force it to EOF */ + if (vio->key_stream != NULL) + keystroke_stream_set_eof(vio->key_stream) ; + + /* Turn off "--more--" so that all output clears without interruption. + * + * If is sitting on a "--more--" prompt, then exit the wait_more CLI. + */ + vio->cli_more_enabled = 0 ; + + if (vio->cli_more_wait) + uty_cli_exit_more_wait(vio) ; + + /* If a command is not in progress, enable output, which will clear + * the output buffer if there is anything there, plus any close reason, + * and then close. + * + * If command is in progress, then this process will start when it + * completes. + */ + if (!vio->cmd_in_progress) + vio->cmd_out_enabled = 1 ; + + /* Make sure no longer holding the config symbol of power */ + uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + + /* Log closing of VTY_TERM */ + if (vio->type == VTY_TERM) + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; + + /* Move to the death watch list */ + sdl_del(vio_list_base, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; + + vio->half_closed = 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY. + * + * Shuts down everything and discards all buffers etc. etc. + * + * If cmd_in_progress, cannot complete the process -- but sets the closed + * flag. + * + * Can call vty_close() any number of times. + * + * The vty structure is placed on death watch, which will finally free the + * structure once no longer cmd_in_progress. + */ +extern void +uty_close (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + /* Empty all the output buffers */ + vio_fifo_reset_keep(&vio->cli_obuf) ; + vio_fifo_reset_keep(&vio->cmd_obuf) ; + vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; + + /* If not already closed, close. */ + if (!vio->closed) + { + uty_half_close(vio, NULL) ; /* place on death watch -- if not + already done */ + if (vio->type == VTY_TERM) + uty_cli_close(vio) ; /* tell the CLI to stop */ + + vio->closed = 1 ; /* now closed (stop uty_write() + from recursing) */ + + if (vio->sock.write_open) + uty_write(vio) ; /* last gasp attempt */ + + uty_sock_close(&vio->sock) ; + + } ; + + /* Nothing more should happen, so can now release almost everything, + * the exceptions being the things that are related to a cmd_in_progress. + * + * All writing to buffers is suppressed, and as the sock has been closed, + * there will be no more read_ready or write_ready events. + */ + if (vio->name != NULL) + XFREE(MTYPE_VTY_NAME, vio->name) ; + + vio->key_stream = keystroke_stream_free(vio->key_stream) ; + + qs_reset(&vio->cli_prompt_for_node, keep_it) ; + qs_reset(&vio->cl, keep_it) ; + + { + qstring line ; + while ((line = vector_ream(vio->hist, keep_it)) != NULL) + qs_reset_free(line) ; + } ; + + /* The final stage cannot be completed if cmd_in_progress. + * + * The clx is pointed at by vty->buf -- containing the current command. + * + * Once everything is released, can take the vty off death watch, and + * release the vio and the vty. + */ + if (!vio->cmd_in_progress) + { + qs_reset(&vio->clx, keep_it) ; + vio->vty->buf = NULL ; + } ; +} ; + +/*============================================================================== + * For writing configuration file by command, temporarily redirect output to + * an actual file. + */ + +/*------------------------------------------------------------------------------ + * Set the given fd as the VTY_FILE output. + */ +extern void +vty_open_config_write(struct vty* vty, int fd) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; + + vio->real_type = vio->type ; + + vio->type = VTY_CONFIG_WRITE ; + vio->file_fd = fd ; + vio->file_error = 0 ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Write away configuration file stuff -- all or just the full lump(s). + * + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see vio->file_error + */ +static int +uty_config_write(vty_io vio, bool all) +{ + int ret ; + + VTY_ASSERT_LOCKED() ; + + if (vio->file_error == 0) + { + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; + + if (ret < 0) + vio->file_error = errno ; + } + else + ret = -1 ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Write away any pending stuff, and return the VTY to normal. + */ +extern int +vty_close_config_write(struct vty* vty) +{ + vty_io vio ; + int err ; + + VTY_LOCK() ; + + vio = vty->vio ; + + assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; + + uty_config_write(vio, true) ; /* write all that is left */ + + err = vio->file_error ; + + vio->type = vio->real_type ; + vio->file_fd = -1 ; + vio->file_error = 0 ; + + VTY_UNLOCK() ; + + return err ; +} ; + +/*============================================================================== + * vio_sock level operations + */ + +/*------------------------------------------------------------------------------ + * Initialise a new vio_sock structure. + * + * Requires that: the vio_sock structure is not currently in use. + * + * if fd >= 0 then: sock is open and ready read and write + * otherwise: sock is not open + * + * there are no errors, yet. + * + * Sets timeout to no timeout at all -- timeout is optional. + * + * NB: MUST be in the CLI thread if the fd is >= 0 ! + */ +static void +uty_sock_init_new(vio_sock sock, int fd, void* info) +{ + VTY_ASSERT_LOCKED() ; + + if (fd >= 0) + VTY_ASSERT_CLI_THREAD() ; + + memset(sock, 0, sizeof(struct vio_sock)) ; + + /* Zeroising the structure has set: + * + * action = all the actions set NULL + * + * error_seen = 0 -- no error, yet + * + * qf = NULL -- no qfile, yet + * t_read = NULL ) no threads, yet + * t_write = NULL ) + * + * v_timeout = 0 -- no timeout set + * timer_runing = 0 -- not running, yet + * t_timer = NULL -- no timer thread, yet + * qtr = NULL -- no qtimer, yet + */ + sock->fd = fd ; + sock->info = info ; + + sock->read_open = (fd >= 0) ; + sock->write_open = (fd >= 0) ; + + if ((fd >= 0) && vty_cli_nexus) + { + sock->qf = qps_file_init_new(NULL, NULL); + qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); + } ; +} ; + +/*------------------------------------------------------------------------------ + * Restart the timer. + * + * If a timeout time is set, then start or restart the timer with that value. + * + * If no timeout time is set, and the timer is running, unset it. + */ +static void +uty_sock_restart_timer(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->v_timeout != 0) + { + assert(sock->action.timer.anon != NULL) ; + + if (vty_cli_nexus) + { + if (sock->qtr == NULL) /* allocate qtr if required */ + sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, sock->info) ; + qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), + sock->action.timer.qnexus) ; + } + else + { + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer); + sock->t_timer = thread_add_timer (vty_master, + sock->action.timer.thread, sock->info, sock->v_timeout) ; + } ; + + sock->timer_running = 1 ; + } + else if (sock->timer_running) + { + if (vty_cli_nexus) + { + if (sock->qtr != NULL) + qtimer_unset(sock->qtr) ; + } + else + { + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer) ; + } ; + + sock->timer_running = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set read on/off + * + * Returns: the on/off state set + */ +static bool +uty_sock_set_read(vio_sock sock, bool on) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->fd < 0) + return 0 ; + + if (on) + { + assert(sock->action.read.anon != NULL) ; + + if (vty_cli_nexus) + qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; + else + { + if (sock->t_read != NULL) + thread_cancel(sock->t_read) ; + + sock->t_read = thread_add_read(vty_master, + sock->action.read.thread, sock->info, sock->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + qps_disable_modes(sock->qf, qps_read_mbit) ; + else + { + if (sock->t_read != NULL) + thread_cancel (sock->t_read) ; + } ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set write on/off + * + * Returns: the on/off state set + */ +static bool +uty_sock_set_write(vio_sock sock, bool on) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->fd < 0) + return 0 ; + + if (on) + { + assert(sock->action.write.anon != NULL) ; + + if (vty_cli_nexus) + qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; + else + { + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; + + sock->t_write = thread_add_write(vty_master, + sock->action.write.thread, sock->info, sock->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + qps_disable_modes(sock->qf, qps_write_mbit) ; + else + { + if (sock->t_write != NULL) + thread_cancel (sock->t_write) ; + } ; + } ; + + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set read/write readiness -- for VTY_TERM + * + * Note that for VTY_TERM, set only one of read or write, and sets write for + * preference. + */ +extern void +uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + uty_sock_set_read(sock, (ready == read_ready)) ; + uty_sock_set_write(sock, (ready >= write_ready)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a new timer value. + */ +extern void +uty_sock_set_timer(vio_sock sock, unsigned long timeout) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + sock->v_timeout = timeout ; + if (sock->timer_running) + uty_sock_restart_timer(sock) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vty sock for reading. + * + * Sets timer to timeout for clearing any pending output. + * + * NB: if there is a socket, MUST be in the CLI thread + */ +static void +uty_sock_half_close(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + + sock->read_open = 0 ; /* make sure */ + + if (sock->fd < 0) + return ; /* nothing more if no socket */ + + VTY_ASSERT_CLI_THREAD() ; + + shutdown(sock->fd, SHUT_RD) ; /* actual half close */ + + uty_sock_set_read(sock, off) ; + uty_sock_set_write(sock, on) ; + sock->v_timeout = 30 ; /* for output to clear */ + uty_sock_restart_timer(sock) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vio_sock, completely -- shut down any timer. + * + * Structure is cleared of everything except the last error ! + * + * NB: if there is a socket, MUST be in the CLI thread + */ +static void +uty_sock_close(vio_sock sock) +{ + VTY_ASSERT_LOCKED() ; + + sock->read_open = 0 ; /* make sure */ + sock->write_open = 0 ; + + if (sock->fd < 0) + { + assert( (sock->qf == NULL) + && (sock->qtr == NULL) + && (sock->t_read == NULL) + && (sock->t_write == NULL) + && (sock->t_timer == NULL) ) ; + return ; /* no more to be done here */ + } ; + + VTY_ASSERT_CLI_THREAD() ; + close(sock->fd) ; + + if (vty_cli_nexus) + { + assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; + qps_remove_file(sock->qf) ; + qps_file_free(sock->qf) ; + sock->qf = NULL ; + } ; + + sock->fd = -1 ; + + if (sock->t_read != NULL) + thread_cancel(sock->t_write) ; + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; + + sock->t_read = NULL ; + sock->t_write = NULL ; + + sock->info = NULL ; + sock->action.read.anon = NULL ; + sock->action.write.anon = NULL ; + sock->action.timer.anon = NULL ; + + if (sock->qtr != NULL) + qtimer_free(sock->qtr) ; + if (sock->t_timer != NULL) + thread_cancel(sock->t_timer) ; + + sock->v_timeout = 0 ; + sock->qtr = NULL ; + sock->t_timer = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Dealing with an I/O error on VTY socket + * + * If this is the first error for this VTY, produce suitable log message. + * + * If is a "monitor", turn that off, *before* issuing log message. + */ +static int +uty_sock_error(vty_io vio, const char* what) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* can no longer be a monitor ! *before* any logging ! */ + uty_set_monitor(vio, 0) ; + + /* if this is the first error, log it */ + if (vio->sock.error_seen == 0) + { + const char* type ; + switch (vio->type) + { + case VTY_TERM: + type = "VTY Terminal" ; + break ; + case VTY_SHELL_SERV: + type = "VTY Shell Server" ; + break ; + default: + zabort("unknown VTY type for uty_sock_error()") ; + } ; + + vio->sock.error_seen = errno ; + uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ; + } ; + + return -1 ; +} ; + +/*============================================================================== + * Readiness and the VTY_TERM type VTY. + * + * For VTY_TERM the driving force is write ready. This is used to prompt the + * VTY_TERM when there is outstanding output (obviously), but also if there + * is buffered input in the keystroke stream. + * + * The VTY_TERM uses read ready only when it doesn't set write ready. Does + * not set both at once. + * + * So there is only one, common, uty_ready function, which: + * + * 1. attempts to clear any output it can. + * + * The state of the output affects the CLI, so must always do this before + * before invoking the CLI. + * + * If this write enters the "--more--" state, then will have tried to + * write away the prompt. + * + * 2. invokes the CLI + * + * Which will do either the standard CLI stuff or the special "--more--" + * stuff. + * + * 3. attempts to write any output there now is. + * + * If the CLI generated new output, as much as possible is written away + * now. + * + * If this write enters the "--more--" state, then it returns now_ready, + * if the prompt was written away, which loops back to the CLI. + * + * Note that this is arranging: + * + * a. to write away the "--more--" prompt as soon as the tranche of output to + * which it refers, completes + * + * b. to enter the cli_more_wait CLI for the first time immediately after the + * "--more--" prompt is written away. + * + * The loop limits itself to one trache of command output each time. + * + * Resets the timer because something happened. + */ +static void +uty_ready(vty_io vio) +{ + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + + vio->cmd_out_done = 0 ; /* not done any command output yet */ + + uty_write(vio) ; /* try to clear outstanding stuff */ + do + { + ready = uty_cli(vio) ; /* do any CLI work... */ + ready |= uty_write(vio) ; /* ...and any output that generates */ + } while (ready >= now_ready) ; + + uty_sock_set_readiness(&vio->sock, ready) ; + uty_sock_restart_timer(&vio->sock) ; +} ; + +/*============================================================================== + * Reading from VTY_TERM. + * + * The select/pselect call-back ends up in uty_read_ready(). + * + * Note that uty_write_ready() also calls uty_read_ready, in order to kick the + * current CLI. + */ + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read -> kicking CLI + */ +static void +vty_read_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + uty_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: ready to read -> kicking CLI + */ +static int +vty_read_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_read = NULL ; /* implicitly */ + uty_ready(vio); + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the keystroke stream + * + * Steal keystroke if required -- see keystroke_input() + * + * Returns: 0 => nothing available + * > 0 => read at least one byte + * -1 => EOF (or not open, or failed) + */ +extern int +uty_read (vty_io vio, keystroke steal) +{ + unsigned char buf[500] ; + int get ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open */ + + get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; + if (get >= 0) + keystroke_input(vio->key_stream, buf, get, steal) ; + else if (get < 0) + { + if (get == -1) + uty_sock_error(vio, "read") ; + + vio->sock.read_open = 0 ; + keystroke_input(vio->key_stream, NULL, 0, steal) ; + + get = -1 ; + } ; + + return get ; +} ; + +/*============================================================================== + * The write sock action for VTY_TERM type VTY + * + * There are two sets of buffering: + * + * cli -- command line -- which reflects the status of the command line + * + * cmd -- command output -- which is written to the file only while + * cmd_out_enabled. + * + * The cli output takes precedence. + * + * Output of command stuff is subject to line_control, and may go through the + * "--more--" mechanism. + */ + +static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; +static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to write -> try to empty buffers + */ +static void +vty_write_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info ; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + uty_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- thread: ready to write -> try to empty buffers + */ +static int +vty_write_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_write = NULL; /* implicitly */ + uty_ready(vio) ; + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Write as much as possible of what there is. + * + * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, + * empty. + * + * Note that if !write_open, or becomes !write_open, then the FIFOs are empty + * and all output instantly successful. + * + * Sets write on if prevented from writing everything available for output + * by write() threatening to block. + * + * Returns: write_ready if should now set write on + * now_ready if should loop back and try again + * not_ready otherwise + */ +static enum vty_readiness +uty_write(vty_io vio) +{ + int ret ; + + VTY_ASSERT_LOCKED() ; + + ret = -1 ; + while (vio->sock.write_open) + { + /* Any outstanding line control output takes precedence */ + if (vio->cmd_lc != NULL) + { + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + if (ret != 0) + break ; + } + + /* Next: empty out the cli output */ + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret != 0) + break ; + + /* Finished now if not allowed to progress the command stuff */ + if (!vio->cmd_out_enabled) + return not_ready ; /* done all can do */ + + /* Last: if there is something in the command buffer, do that */ + if (!vio_fifo_empty(&vio->cmd_obuf)) + { + if (vio->cmd_out_done) + break ; /* ...but not if done once */ + + vio->cmd_out_done = 1 ; /* done this once */ + + assert(!vio->cli_more_wait) ; + + if (vio->cmd_lc != NULL) + ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + else + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; + + /* If moved into "--more--" state@ + * + * * the "--more--" prompt is ready to be written, so do that now + * + * * if that completes, then want to run the CLI *now* to perform the + * first stage of the "--more--" process. + */ + if (vio->cli_more_wait) + { + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret == 0) + return now_ready ; + } ; + + if (ret != 0) + break ; + } + + /* Exciting stuff: there is nothing left to output... + * + * ... watch out for half closed state. + */ + if (vio->half_closed) + { + if (vio->close_reason != NULL) + { + vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ + + struct vty* vty = vio->vty ; + if (vio->cli_drawn || vio->cli_dirty) + vty_out(vty, VTY_NEWLINE) ; + vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; + + vio->cmd_in_progress = 0 ; + + vio->close_reason = NULL ; /* MUST discard now... */ + continue ; /* ... and write away */ + } ; + + if (!vio->closed) /* avoid recursion */ + uty_close(vio) ; + + return not_ready ; /* it's all over */ + } ; + + /* For VTY_TERM: if the command line is not drawn, now is a good + * time to do that. + */ + if (vio->type == VTY_TERM) + if (uty_cli_draw_if_required(vio)) + continue ; /* do that now. */ + + /* There really is nothing left to output */ + return not_ready ; + } ; + + /* Arrives here if there is more to do, or failed (or was !write_open) */ + + if (ret >= 0) + return write_ready ; + + /* If is write_open, then report the error + * + * If still read_open, let the reader pick up and report the error, when it + * has finished anything it has buffered. + */ + if (vio->sock.write_open) + { + if (!vio->sock.read_open) + uty_sock_error(vio, "write") ; + + vio->sock.write_open = 0 ; /* crash close write */ + } ; + + /* For whatever reason, is no longer write_open -- clear all buffers. + */ + vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ + uty_out_clear(vio) ; /* throw away cmd stuff */ + + vio->close_reason = NULL ; /* too late for this */ + + return not_ready ; /* NB: NOT blocked by I/O */ +} ; + +/*------------------------------------------------------------------------------ + * Write as much as possible -- for "monitor" output. + * + * Outputs only: + * + * a. outstanding line control stuff. + * + * b. contents of CLI buffer + * + * And: + * + * a. does not report any errors. + * + * b. does not change anything except the state of the buffers. + * + * In particular, for the qpthreaded world, does not attempt to change + * the state of the qfile or any other "thread private" structures. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed (or !write_open) + */ +static int +uty_write_monitor(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (!vio->sock.write_open) + return -1 ; + + if (vio->cmd_lc != NULL) + { + int ret ; + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + + if (ret != 0) + return ret ; + } ; + + return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; +} ; + +/*------------------------------------------------------------------------------ + * Write the given FIFO to output -- subject to possible line control. + * + * Note that even if no "--more--" is set, will have set some height, so + * that does not attempt to empty the FIFO completely all in one go. + * + * If the line control becomes "paused", it is time to enter "--more--" state + * -- unless the FIFO is empty (or "--more--" is not enabled). + * + * NB: expects that the sock is write_open + * + * Returns: > 0 => blocked or completed one tranche + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +{ + int ret ; + char* src ; + size_t have ; + + /* Collect another line_control height's worth of output. + * + * Expect the line control to be empty at this point, but it does not have + * to be. + */ + vio_lc_set_pause(lc) ; /* clears lc->paused */ + + src = vio_fifo_get_rdr(vf, &have) ; + + while ((src != NULL) && (!lc->paused)) + { + size_t take ; + take = vio_lc_append(lc, src, have) ; + src = vio_fifo_step_rdr(vf, &have, take) ; + } ; + + vio->cli_dirty = (lc->col != 0) ; + + /* Write the contents of the line control */ + ret = uty_write_lc(vio, vf, lc) ; + + if (ret < 0) + return ret ; /* give up now if failed. */ + + if ((ret == 0) && vio_fifo_empty(vf)) + return 0 ; /* FIFO and line control empty */ + + /* If should now do "--more--", now is the time to prepare for that. + * + * Entering more state issues a new prompt in the CLI buffer, which can + * be written once line control write completes. + * + * The "--more--" cli will not do anything until the CLI buffer has + * cleared. + */ + if (lc->paused && vio->cli_more_enabled) + uty_cli_enter_more_wait(vio) ; + + return 1 ; /* FIFO or line control, not empty */ +} ; + +/*------------------------------------------------------------------------------ + * Write contents of line control (if any). + * + * NB: expects that the sock is write_open + * + * NB: does nothing other than write() and buffer management. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +{ + int ret ; + + ret = vio_lc_write_nb(vio->sock.fd, lc) ; + + if (ret <= 0) + vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Start command output -- clears down the line control. + * + * Requires that that current line is empty -- restarts the line control + * on the basis that is at column 0. + */ +extern void +uty_cmd_output_start(vty_io vio) +{ + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the effective height for line control (if any) + * + * If using line_control, may enable the "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. + */ +extern void +uty_set_height(vty_io vio) +{ + bool on ; + + on = 0 ; /* default state */ + + if ((vio->cmd_lc != NULL) && !vio->half_closed) + { + int height ; + + height = 0 ; /* default state */ + + if ((vio->width) != 0) + { + /* If window size is known, use lines or given height */ + if (vio->lines >= 0) + height = vio->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = vio->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (vio->lines_set) + height = vio->lines ; + } ; + + if (height > 0) + on = 1 ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(vio->cmd_lc, vio->width, height) ; + } ; + + vio->cli_more_enabled = on ; +} ; + +/*============================================================================== + * Timer for VTY_TERM (and VTY_SHELL_SERV). + */ + +/*------------------------------------------------------------------------------ + * Timer has expired. + * + * If half_closed, then this is curtains -- have waited long enough ! + * + * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + */ +static void +uty_timer_expired (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return uty_close(vio) ; /* curtains */ + + uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ + } ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: deal with timer timeout. + */ +static void +vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) +{ + vty_io vio = timer_info ; + + VTY_LOCK() ; + + uty_timer_expired(vio); + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- thread: deal with timer timeout. + */ +static int +vty_timer_thread (struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + vio->sock.t_timer = NULL ; /* implicitly */ + + uty_timer_expired(vio) ; + + VTY_UNLOCK() ; + return 0; +} + +/*============================================================================== + * VTY Listener(s) + * + * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. + */ + +typedef struct vty_listener* vty_listener ; + +struct vty_listener +{ + vty_listener next ; /* ssl type list */ + + enum vty_type type ; + + struct vio_sock sock ; +}; + +/* List of listeners so can tidy up. */ +static vty_listener vty_listeners_list = NULL ; + +/* Prototypes for listener stuff */ +static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ; +static int uty_serv_sock(const char* addr, unsigned short port) ; +static int uty_serv_sock_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) ; +static int uty_serv_vtysh(const char *path) ; +static int vty_accept_thread(struct thread *thread) ; +static void vty_accept_qnexus(qps_file qf, void* listener) ; +static int uty_accept(vty_listener listener, int listen_sock) ; +static int uty_accept_term(vty_listener listener) ; +static int uty_accept_shell_serv (vty_listener listener) ; + +static void uty_serv_start_listener(int fd, enum vty_type type) ; + +/*------------------------------------------------------------------------------ + * If possible, will use getaddrinfo() to find all the things to listen on. + */ + +#if defined(HAVE_IPV6) && !defined(NRL) +# define VTY_USE_ADDRINFO 1 +#else +# define VTY_USE_ADDRINFO 0 +#endif + +/*------------------------------------------------------------------------------ + * Open VTY listener(s) + * + * addr -- address ) to listen for VTY_TERM connections + * port -- port ) + * path -- path for VTYSH connections -- if VTYSH_ENABLED + */ +extern void +uty_open_listeners(const char *addr, unsigned short port, const char *path) +{ + VTY_ASSERT_LOCKED() ; + + /* If port is set to 0, do not listen on TCP/IP at all! */ + if (port) + { + int n ; + + if (VTY_USE_ADDRINFO) + n = uty_serv_sock_addrinfo(addr, port); + else + n = uty_serv_sock(addr, port); + + if (n == 0) + uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; + } + + /* If want to listen for vtysh, set up listener now */ + if (VTYSH_ENABLED && (path != NULL)) + uty_serv_vtysh(path) ; +} ; + +/*------------------------------------------------------------------------------ + * Close VTY listener + * + * addr -- address ) to listen for VTY_TERM connections + * port -- port ) + * path -- path for VTYSH connections -- if VTYSH_ENABLED + */ +extern void +uty_close_listeners(void) +{ + vty_listener listener ; + + VTY_ASSERT_LOCKED() ; + + while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) + { + uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ + XFREE(MTYPE_VTY, listener) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERM -- using getaddrinfo(). + * + * Returns: number of listeners successfully opened. + */ +static int +uty_serv_sock_addrinfo (const char *hostname, unsigned short port) +{ +#if VTY_USE_ADDRINFO + +# ifndef HAVE_IPV6 +# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? +# endif + + int ret; + int n ; + struct addrinfo req; + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + char port_str[16]; + + VTY_ASSERT_LOCKED() ; + + /* Want to listen, TCP-wise, on all available address families, on the + * given port. + */ + memset (&req, 0, sizeof (struct addrinfo)); + req.ai_flags = AI_PASSIVE; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + snprintf(port_str, sizeof(port_str), "%d", port); + + ret = getaddrinfo (hostname, port_str, &req, &ainfo); + + if (ret != 0) + { + fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); + exit (1); + } + + /* Open up sockets on all AF_INET and AF_INET6 addresses */ + ainfo_save = ainfo; + + n = 0 ; + do + { + if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) + continue; + + assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; + + ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol, ainfo->ai_addr, port) ; + if (ret >= 0) + ++n ; + } + while ((ainfo = ainfo->ai_next) != NULL); + + freeaddrinfo (ainfo_save); + + return n ; + +#else + zabort("uty_serv_sock_addrinfo not implemented") ; +#endif /* VTY_USE_ADDRINFO */ +} + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! + * + * Returns: number of listeners successfully opened. + */ +static int +uty_serv_sock(const char* addr, unsigned short port) +{ + int ret; + int n ; + union sockunion su_addr ; + struct sockaddr* sa ; + + VTY_ASSERT_LOCKED() ; + + n = 0 ; /* nothing opened yet */ + + /* If have an address, see what kind and whether valid */ + sa = NULL ; + + if (addr != NULL) + { + ret = str2sockunion (addr, &su_addr) ; + if (ret == 0) + sa = &su_addr.sa ; + else + uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); + } ; + + /* Try for AF_INET */ + ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ + +#if HAVE_IPV6 + /* Try for AF_INET6 */ + ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ +#endif + + /* If not used the address... something wrong */ + if (sa != NULL) + uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); + + /* Done */ + return n ; +} + +/*------------------------------------------------------------------------------ + * Open a VTY_TERM listener socket. + * + * The sockaddr 'sa' may be NULL or of a different address family, in which + * case "any" address is used. + * + * If the sockaddr 'sa' is used, only the address portion is used. + * + * Returns: < 0 => failed + * == 0 => OK -- did not use the sockaddr 'sa'. + * > 1 => OK -- and did use the sockaddr 'sa' + */ +static int +uty_serv_sock_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) +{ + union sockunion su ; + int sock ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + /* Is there an address and is it for this family ? */ + if ((sa != NULL) || (sa->sa_family == family)) + /* Set up sockunion containing required family and address */ + sockunion_new_sockaddr(&su, sa) ; + else + { + /* no address or wrong family -- set up empty sockunion of + * required family */ + sockunion_init_new(&su, family) ; + sa = NULL ; + } ; + + /* Open the socket and set its properties */ + sock = sockunion_socket(family, type, protocol) ; + if (sock < 0) + return -1 ; + + ret = sockopt_reuseaddr (sock); + + if (ret >= 0) + ret = sockopt_reuseport (sock); + + if (ret >= 0) + ret = set_nonblocking(sock); + +#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + /* Want only IPV6 on ipv6 socket (not mapped addresses) + * + * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the + * attempt to bind to :: after binding to 0.0.0.0. + */ + if ((ret >= 0) && (sa->sa_family == AF_INET6)) + { + int on = 1; + ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); + } +#endif + + if (ret >= 0) + ret = sockunion_bind (sock, &su, port, sa) ; + + if (ret >= 0) + ret = sockunion_listen (sock, 3); + + if (ret < 0) + { + close (sock); + return -1 ; + } + + /* Socket is open -- set VTY Term listener going */ + uty_serv_start_listener(sock, VTY_TERM) ; + + /* Return OK and signal whether used address or not */ + return (sa != NULL) ? 1 : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Open a VTY_SHEL_SERV listener socket (UNIX domain). + * + * Returns: < 0 => failed + * >= 0 => OK + */ +static int +uty_serv_vtysh(const char *path) +{ + int ret; + int sock, sa_len, path_len ; + struct sockaddr_un sa_un ; + mode_t old_mask; + struct zprivs_ids_t ids; + + VTY_ASSERT_LOCKED() ; + + /* worry about the path length */ + path_len = strlen(path) + 1 ; + if (path_len >= (int)sizeof(sa_un.sun_path)) + { + uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); + return -1 ; + } ; + + /* First of all, unlink existing socket */ + unlink (path); + + /* Make UNIX domain socket. */ + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + { + uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", + errtoa(errno, 0).str) ; + return -1 ; + } + + /* Bind to the required path */ + memset (&sa_un, 0, sizeof(sa_un)); + sa_un.sun_family = AF_UNIX; + strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); + + sa_len = SUN_LEN(&sa_un) ; + +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + sa_un.sun_len = sa_len ; +#endif + + old_mask = umask (0007); + + ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; + if (ret < 0) + uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); + + if (ret >= 0) + ret = set_nonblocking(sock); + + if (ret >= 0) + { + ret = listen (sock, 5); + if (ret < 0) + uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, + errtoa(errno, 0).str) ; + } ; + + zprivs_get_ids(&ids); + + if (ids.gid_vty > 0) + { + /* set group of socket */ + if ( chown (path, -1, ids.gid_vty) ) + uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + errtoa(errno, 0).str) ; + } + + umask (old_mask); + + /* Give up now if failed along the way */ + if (ret < 0) + { + close (sock) ; + return -1 ; + } ; + + /* Socket is open -- set VTY Term listener going */ + uty_serv_start_listener(sock, VTY_SHELL_SERV) ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Socket is open -- set a VTY listener going + * + * Note that the vyt_listener structure is passed to the accept action function. + */ +static void +uty_serv_start_listener(int fd, enum vty_type type) +{ + vty_listener listener ; + + listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); + + ssl_push(vty_listeners_list, listener, next) ; + uty_sock_init_new(&listener->sock, fd, listener) ; + + listener->type = type ; + + if (vty_cli_nexus) + listener->sock.action.read.qnexus = vty_accept_qnexus ; + else + listener->sock.action.read.thread = vty_accept_thread ; + + uty_sock_set_read(&listener->sock, on) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action for the thread world -- create and dispatch VTY + */ +static int +vty_accept_thread(struct thread *thread) +{ + vty_listener listener = THREAD_ARG(thread) ; + int result ; + + VTY_LOCK() ; + + result = uty_accept(listener, THREAD_FD(thread)); + + uty_sock_set_read(&listener->sock, on) ; + + VTY_UNLOCK() ; + return result ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action for the qnexus world -- create and dispatch VTY + */ +static void +vty_accept_qnexus(qps_file qf, void* listener) +{ + VTY_LOCK() ; + + uty_accept(listener, qf->fd); + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV + */ +static int +uty_accept(vty_listener listener, int listen_sock) +{ + VTY_ASSERT_LOCKED() ; + + assert(listener->sock.fd == listen_sock) ; + + switch (listener->type) + { + case VTY_TERM: + return uty_accept_term(listener) ; + + case VTY_SHELL_SERV: + return uty_accept_shell_serv(listener) ; + + default: + zabort("unknown vty type") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_TERM + */ +static int +uty_accept_term(vty_listener listener) +{ + int sock_fd; + union sockunion su; + int ret; + unsigned int on; + struct prefix *p ; + + VTY_ASSERT_LOCKED() ; + + /* We can handle IPv4 or IPv6 socket. */ + sockunion_init_new(&su, AF_UNSPEC) ; + + sock_fd = sockunion_accept (listener->sock.fd, &su); + + if (sock_fd < 0) + { + if (sock_fd == -1) + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", + errtoa(errno, 0).str) ; + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return -1 ; + } ; + + /* New socket is open... worry about access lists */ + p = sockunion2hostprefix (&su); + ret = 0 ; /* so far, so good */ + + if ((p->family == AF_INET) && vty_accesslist_name) + { + /* VTY's accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } + +#ifdef HAVE_IPV6 + if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) + { + /* VTY's ipv6 accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } +#endif /* HAVE_IPV6 */ + + prefix_free (p); + + if (ret != 0) + { + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; + close (sock_fd); + return 0; + } ; + + /* Final options (optional) */ + on = 1 ; + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, + (void*)&on, sizeof (on)); + if (ret < 0) + uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", + sock_fd, errtoa(errno, 0).str) ; + + /* All set -- create the VTY_TERM */ + uty_new_term(sock_fd, &su); + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, + sock_fd) ; + + return 0; +} + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_SHELL_SERV + */ +static int +uty_accept_shell_serv (vty_listener listener) +{ + int sock_fd ; + int ret ; + int client_len ; + struct sockaddr_un client ; + + VTY_ASSERT_LOCKED() ; + + client_len = sizeof(client); + memset (&client, 0, client_len); + + sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, + (socklen_t *) &client_len) ; + + if (sock_fd < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + errtoa(errno, 0).str) ; + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return -1 ; + } ; + + /* All set -- create the VTY_SHELL_SERV */ + if (VTYSH_DEBUG) + printf ("VTY shell accept\n"); + + uty_new_shell_serv(sock_fd) ; + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); + return 0; +} + +/*============================================================================== + * Reading from the VTY_SHELL_SERV type sock. + * + * The select/pselect call-back ends up in utysh_read_ready(). + */ + +/*------------------------------------------------------------------------------ + * Ready to read -> kicking the "SHELL_SERV CLI" + * + * End up here when there is something ready to be read. + * + * Will also end up here if an error has occurred, the other end has closed, + * this end has half closed, etc. This fact is used to kick the CLI even when + * there is no data to be read. + * + * Note that nothing is actually read here -- reading is done in the CLI itself, + * if required. + * + * The CLI decides whether to re-enable read, or enable write, or both. + */ +static void +utysh_read_ready(vty_io vio) +{ + uty_sock_set_read(&vio->sock, off) ; + + /* TODO: need minimal "CLI" for VTY_SHELL_SERV + * NB: when output from command is flushed out, must append the + * following four bytes: '\0' '\0' '\0' <ret> + * Where <ret> is the command return code. + */ +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" + */ +static void +vtysh_read_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + utysh_read_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" + */ +static int +vtysh_read_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_read = NULL ; /* implicitly */ + utysh_read_ready(vio); + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the command line buffer + * + * Lines coming in are terminated by '\0'. + * + * Assumes that the incoming command line is empty or otherwise incomplete. + * + * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping + * when get '\0' or empties the "buf". + * + * When empties "buf", reads a lump from the sock. + * + * Returns: 0 => command line is incomplete + * 1 => have a complete command line + * -1 => EOF (or not open, or failed) + */ +extern int +utysh_read (vty_io vio, qstring cl, qstring buf) +{ + int get ; + char* cp ; + char* ep ; + size_t have ; + + while (1) + { + /* process what there is in the buffer */ + if (buf->len > buf->cp) + { + cp = qs_cp_char(buf) ; + ep = qs_ep_char(buf) ; + have = ep - cp ; + + ep = memchr(cp, '\0', have) ; + if (ep != NULL) + have = ep - cp ; /* have upto, but excluding '\0' */ + + if (have > 0) /* take what have */ + { + qs_insert(cl, cp, have) ; + cl->cp += have ; + buf->cp += have ; + } ; + + if (ep != NULL) /* if found '\0' */ + { + qs_term(cl) ; /* '\0' terminate */ + ++buf->cp ; /* step past it */ + return 1 ; /* have a complete line <<<<<<<<<<<<< */ + } + } ; + + /* buffer is empty -- try and get some more stuff */ + assert(buf->len == buf->cp) ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ + + qs_need(buf, 500) ; /* need a reasonable lump */ + qs_clear(buf) ; /* set cp = len = 0 */ + + get = read_nb(vio->sock.fd, buf->body, buf->size) ; + if (get > 0) + buf->len = get ; + else if (get == 0) + return 0 ; /* have an incomplete line <<<<<<<<<<<< */ + else + { + if (get == -1) + uty_sock_error(vio, "read") ; + + vio->sock.read_open = 0 ; + + return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ + } ; + } ; +} ; + +/*============================================================================== + * Output to vty which are set to "monitor". + * + * This is VERY TRICKY. + * + * If not running qpthreaded, then the objective is to get the message away + * immediately -- do not wish it to be delayed in any way by the thread + * system. + * + * So proceed as follows: + * + * a. wipe command line -- which adds output to the CLI buffer + * + * b. write the CLI buffer to the sock and any outstanding line control. + * + * c. write the monitor output. + * + * If that does not complete, put the tail end to the CLI buffer. + * + * d. restore any command line -- which adds output to the CLI buffer + * + * e. write the CLI buffer to the sock + * + * If that all succeeds, nothing has changed as far as the VTY stuff is + * concerned -- except that possibly some CLI output was sent before it got + * round to it. + * + * Note that step (b) will deal with any output hanging around from an + * earlier step (e). If cannot complete that, then does not add fuel to the + * fire -- but the message will be discarded. + * + * If that fails, or does not complete, then can set write on, to signal that + * there is some output in the CLI buffer that needs to be sent, or some + * error to be dealt with. + * + * The output should be tidy. + * + * To cut down the clutter, step (d) is performed only if the command line + * is not empty (or if in cli_more_wait). Once a the user has started to enter + * a command, the prompt and the command will remain visible. + * + * When logging an I/O error for a vty that happens to be a monitor, the + * monitor-ness has already been turned off. The monitor output code does not + * attempt to log any errors, sets write on so that the error will be picked + * up that way. + * + * However, in the event of an assertion failure, it is possible that an + * assertion will fail inside the monitor output. The monitor_busy flag + * prevents disaster. It is also left set if I/O fails in monitor output, so + * will not try to use the monitor again. + * + * Note that an assertion which is false for all vty monitors will recurse + * through all the monitors, setting each one busy, in turn ! + * + + + * TODO: sort out write on in the qpthreads world ?? + * + * The problem is that the qpselect structure is designed to be accessed ONLY + * within the thread to which it belongs. This makes it impossible for the + * monitor output to set/clear read/write on the vty sock... so some way + * around this is required. + */ + +/*------------------------------------------------------------------------------ + * Output logging information to all vty which are set to "monitor". + */ +extern void +uty_log(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + vio = sdl_head(vio_monitors_base) ; + + if (vio == NULL) + return ; /* go no further if no "monitor" vtys */ + + /* Prepare line for output. */ + uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ + + /* write to all known "monitor" vty + * + */ + while (vio != NULL) + { + if (!vio->monitor_busy) + { + int ret ; + + vio->monitor_busy = 1 ; /* close the door */ + + uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ + + ret = uty_write_monitor(vio) ; + if (ret == 0) + { + ret = write_nb(vio->sock.fd, ll->line, ll->len) ; + + if (ret >= 0) + { + ret = uty_cli_post_monitor(vio, ll->line + ret, + ll->len - ret) ; + if (ret > 0) + ret = uty_write_monitor(vio) ; + } ; + } ; + + if (ret != 0) + /* need to prod */ ; + + if (ret >= 0) + vio->monitor_busy = 0 ; + } ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Async-signal-safe version of vty_log for fixed strings. + * + * This is last gasp operation. + */ +void +vty_log_fixed (const char *buf, size_t len) +{ + vty_io vio ; + + /* Write to all known "monitor" vty + * + * Forget all the niceties -- about to die in any case. + */ + vio = sdl_head(vio_monitors_base) ; + while (vio != NULL) + { + write(vio->sock.fd, buf, len) ; + write(vio->sock.fd, "\r\n", 2) ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h new file mode 100644 index 00000000..b4a79f52 --- /dev/null +++ b/lib/vty_io_file.h @@ -0,0 +1,56 @@ +/* VTY IO FILE -- File I/O -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_FILE_H +#define _ZEBRA_VTY_IO_FILE_H + +#include "misc.h" +#include <errno.h> + +#include "uty.h" +#include "vty.h" +#include "vty_io.h" +#include "vio_fifo.h" +#include "vio_lines.h" +#include "keystroke.h" +#include "thread.h" +#include "command.h" +#include "qstring.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by: + * + * vty_io.c -- the main VTY I/O stuff + * + * for I/O to files + */ + +/*============================================================================== + * Functions + */ + +extern int uty_vprintf_file(vty_io vio, const char *format, va_list args) ; + + +#endif /* _ZEBRA_VTY_IO_FILE_H */ diff --git a/lib/vty_io_shell.c b/lib/vty_io_shell.c new file mode 100644 index 00000000..c56cc2d2 --- /dev/null +++ b/lib/vty_io_shell.c @@ -0,0 +1,366 @@ +/* VTY IO SHELL -- VTY Shell I/O + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty.h" +#include "vty_io.h" +#include "vty_cli.h" +#include "qstring.h" +#include "keystroke.h" + +#include "memory.h" + +#include "prefix.h" +#include "filter.h" +#include "privs.h" +#include "sockunion.h" +#include "network.h" + +#include <arpa/telnet.h> +#include <sys/un.h> /* for VTYSH */ +#include <sys/socket.h> + +#define VTYSH_DEBUG 0 + + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. + * + * Returns: new vty + */ +static struct vty * +uty_new_shell_serv(int sock_fd) +{ + struct vty *vty ; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (VTY_SHELL_SERV, sock_fd) ; + vio = vty->vio ; + + /* Set the action functions */ + if (vty_cli_nexus) + { + vio->sock.action.read.qnexus = vtysh_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = NULL ; + } + else + { + vio->sock.action.read.thread = vtysh_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = NULL ; + } ; + + vty->node = VIEW_NODE; + + /* Kick start the CLI etc. */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + + return vty; +} ; + + +/*------------------------------------------------------------------------------ + * Open a VTY_SHEL_SERV listener socket (UNIX domain). + * + * Returns: < 0 => failed + * >= 0 => OK + */ +static int +uty_serv_vtysh(const char *path) +{ + int ret; + int sock, sa_len, path_len ; + struct sockaddr_un sa_un ; + mode_t old_mask; + struct zprivs_ids_t ids; + + VTY_ASSERT_LOCKED() ; + + /* worry about the path length */ + path_len = strlen(path) + 1 ; + if (path_len >= (int)sizeof(sa_un.sun_path)) + { + uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); + return -1 ; + } ; + + /* First of all, unlink existing socket */ + unlink (path); + + /* Make UNIX domain socket. */ + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + { + uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", + errtoa(errno, 0).str) ; + return -1 ; + } + + /* Bind to the required path */ + memset (&sa_un, 0, sizeof(sa_un)); + sa_un.sun_family = AF_UNIX; + strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); + + sa_len = SUN_LEN(&sa_un) ; + +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + sa_un.sun_len = sa_len ; +#endif + + old_mask = umask (0007); + + ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; + if (ret < 0) + uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); + + if (ret >= 0) + ret = set_nonblocking(sock); + + if (ret >= 0) + { + ret = listen (sock, 5); + if (ret < 0) + uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, + errtoa(errno, 0).str) ; + } ; + + zprivs_get_ids(&ids); + + if (ids.gid_vty > 0) + { + /* set group of socket */ + if ( chown (path, -1, ids.gid_vty) ) + uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + errtoa(errno, 0).str) ; + } + + umask (old_mask); + + /* Give up now if failed along the way */ + if (ret < 0) + { + close (sock) ; + return -1 ; + } ; + + /* Socket is open -- set VTY Term listener going */ + uty_serv_start_listener(sock, VTY_SHELL_SERV) ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_SHELL_SERV + */ +static int +uty_accept_shell_serv (vty_listener listener) +{ + int sock_fd ; + int ret ; + int client_len ; + struct sockaddr_un client ; + + VTY_ASSERT_LOCKED() ; + + client_len = sizeof(client); + memset (&client, 0, client_len); + + sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, + (socklen_t *) &client_len) ; + + if (sock_fd < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + errtoa(errno, 0).str) ; + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return -1 ; + } ; + + /* All set -- create the VTY_SHELL_SERV */ + if (VTYSH_DEBUG) + printf ("VTY shell accept\n"); + + uty_new_shell_serv(sock_fd) ; + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); + return 0; +} + +/*============================================================================== + * Reading from the VTY_SHELL_SERV type sock. + * + * The select/pselect call-back ends up in utysh_read_ready(). + */ + +/*------------------------------------------------------------------------------ + * Ready to read -> kicking the "SHELL_SERV CLI" + * + * End up here when there is something ready to be read. + * + * Will also end up here if an error has occurred, the other end has closed, + * this end has half closed, etc. This fact is used to kick the CLI even when + * there is no data to be read. + * + * Note that nothing is actually read here -- reading is done in the CLI itself, + * if required. + * + * The CLI decides whether to re-enable read, or enable write, or both. + */ +static void +utysh_read_ready(vty_io vio) +{ + uty_sock_set_read(&vio->sock, off) ; + + /* TODO: need minimal "CLI" for VTY_SHELL_SERV + * NB: when output from command is flushed out, must append the + * following four bytes: '\0' '\0' '\0' <ret> + * Where <ret> is the command return code. + */ +} ; + +/*------------------------------------------------------------------------------ + * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" + */ +static void +vtysh_read_qnexus(qps_file qf, void* file_info) +{ + vty_io vio = file_info; + + VTY_LOCK() ; + + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; + + utysh_read_ready(vio) ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" + */ +static int +vtysh_read_thread(struct thread *thread) +{ + vty_io vio = THREAD_ARG (thread); + + VTY_LOCK() ; + + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; + + vio->sock.t_read = NULL ; /* implicitly */ + utysh_read_ready(vio); + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the command line buffer + * + * Lines coming in are terminated by '\0'. + * + * Assumes that the incoming command line is empty or otherwise incomplete. + * + * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping + * when get '\0' or empties the "buf". + * + * When empties "buf", reads a lump from the sock. + * + * Returns: 0 => command line is incomplete + * 1 => have a complete command line + * -1 => EOF (or not open, or failed) + */ +extern int +utysh_read (vty_io vio, qstring cl, qstring buf) +{ + int get ; + char* cp ; + char* ep ; + size_t have ; + + while (1) + { + /* process what there is in the buffer */ + if (buf->len > buf->cp) + { + cp = qs_cp_char(buf) ; + ep = qs_ep_char(buf) ; + have = ep - cp ; + + ep = memchr(cp, '\0', have) ; + if (ep != NULL) + have = ep - cp ; /* have upto, but excluding '\0' */ + + if (have > 0) /* take what have */ + { + qs_insert(cl, cp, have) ; + cl->cp += have ; + buf->cp += have ; + } ; + + if (ep != NULL) /* if found '\0' */ + { + qs_term(cl) ; /* '\0' terminate */ + ++buf->cp ; /* step past it */ + return 1 ; /* have a complete line <<<<<<<<<<<<< */ + } + } ; + + /* buffer is empty -- try and get some more stuff */ + assert(buf->len == buf->cp) ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ + + qs_need(buf, 500) ; /* need a reasonable lump */ + qs_clear(buf) ; /* set cp = len = 0 */ + + get = read_nb(vio->sock.fd, buf->body, buf->size) ; + if (get > 0) + buf->len = get ; + else if (get == 0) + return 0 ; /* have an incomplete line <<<<<<<<<<<< */ + else + { + if (get == -1) + uty_sock_error(vio, "read") ; + + vio->sock.read_open = 0 ; + + return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ + } ; + } ; +} ; diff --git a/lib/vty_io_shell.h b/lib/vty_io_shell.h new file mode 100644 index 00000000..87cd92d0 --- /dev/null +++ b/lib/vty_io_shell.h @@ -0,0 +1,55 @@ +/* VTY IO SHELL -- VTY Shell I/O -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_SHELL_H +#define _ZEBRA_VTY_IO_SHELL_H + +#include "misc.h" +#include <errno.h> + +#include "uty.h" +#include "vty.h" +#include "vty_io.h" +#include "vio_fifo.h" +#include "vio_lines.h" +#include "keystroke.h" +#include "thread.h" +#include "command.h" +#include "qstring.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by: + * + * vty_io.c -- the main VTY I/O stuff + * + * for I/O for VTY Shell Server. + */ + +/*============================================================================== + * Functions + */ + +extern int uty_vprintf_shell(vty_io vio, const char *format, va_list args) ; + +#endif /* _ZEBRA_VTY_IO_SHELL_H */ diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c new file mode 100644 index 00000000..2053dcc5 --- /dev/null +++ b/lib/vty_io_term.c @@ -0,0 +1,1288 @@ +/* VTY IO TERM -- Telnet Terminal I/O + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty_io_term.h" +#include "vty_cli.h" + +#include "qstring.h" +#include "keystroke.h" + +#include "memory.h" + +#include "prefix.h" +#include "filter.h" +#include "privs.h" +#include "sockunion.h" +#include "network.h" + +#include <arpa/telnet.h> +#include <sys/un.h> /* for VTYSH */ +#include <sys/socket.h> + +#define VTYSH_DEBUG 0 + +/*------------------------------------------------------------------------------ + * Create new vty of type VTY_TERMINAL -- ie attached to a telnet session. + * + * This is called by the accept action for the VTY_TERMINAL listener. + */ +static void +uty_term_accept_new(int sock_fd, union sockunion *su) +{ + struct vty *vty ; + vty_io vio ; + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new(VTY_TERMINAL, sock_fd) ; + vio = vty->vio ; + + /* The text form of the address identifies the VTY */ + vty->vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); + + /* Set the initial node */ + if (no_password_check) + { + if (restricted_mode) + vty->node = RESTRICTED_NODE; + else if (host.advanced) + vty->node = ENABLE_NODE; + else + vty->node = VIEW_NODE; + } + else + vty->node = AUTH_NODE; + + /* Pick up current timeout setting */ + vio->sock.v_timeout = vty_timeout_val; + + /* Use global 'lines' setting, as default. May be -1 => unset */ + vio->lines = host.lines ; + + /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ + vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; + uty_set_height(vio) ; /* set initial state */ + + /* Initialise the CLI, ready for start-up messages etc. */ + uty_cli_init(vio) ; + + /* Reject connection if password isn't set, and not "no password" */ + if ((host.password == NULL) && (host.password_encrypt == NULL) + && ! no_password_check) + { + uty_close(vio, "Vty password is not set."); + vty = NULL; + } + else + { + /* Say hello to the world. */ + vty_hello (vty); + + if (! no_password_check) + uty_output (vty, "\nUser Access Verification\n\n"); + } ; + + /* Now start the CLI and set a suitable state of readiness */ + ready = uty_cli_start(vio) ; + uty_sock_set_readiness(&vio->sock, ready) ; +} ; + +/*------------------------------------------------------------------------------ + * Construct vio_vf structure for new VTY_TERMINAL type VTY, and add same. + * + * + */ + +static void +uty_term_new(vty_io vio, int sock_fd) +{ + vio_vf vf ; + + enum vty_readiness ready ; + + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + /* May only be the first vio_vf ! */ + assert((vio->vin_base == NULL) && (vio->vout_base == NULL)) ; + + /* Construct and add to the vio */ + vf = uty_vf_new(vio, sock_fd, vfd_socket, vfd_io_read_write) ; + + uty_vin_add(vio, vf, VIN_TERM, uty_term_ready, uty_term_read_timeout) ; + uty_vout_add(vio, vf, VIN_TERM, uty_term_ready, uty_term_write_timeout) ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + + /* Pick up current timeout setting */ + vf->read_timeout = vty_timeout_val; + + /* Use global 'lines' setting, as default. May be -1 => unset */ + vio->lines = host.lines ; + + /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ + vf->olc = vio_lc_init_new(NULL, 0, 0) ; + uty_set_height(vio) ; /* set initial state */ + + /* Initialise the CLI, ready for start-up messages etc. */ + uty_cli_init(vio) ; + + /* Reject connection if password isn't set, and not "no password" */ + if ((host.password == NULL) && (host.password_encrypt == NULL) + && ! no_password_check) + { + uty_close(vio, "Vty password is not set."); + vty = NULL; + } + else + { + /* Say hello to the world. */ + vty_hello (vty); + + if (! no_password_check) + uty_output (vty, "\nUser Access Verification\n\n"); + } ; + + /* Now start the CLI and set a suitable state of readiness */ + ready = uty_cli_start(vio) ; + uty_sock_set_readiness(&vio->sock, ready) ; +} ; + + + +/*------------------------------------------------------------------------------ + * + */ +extern void +uty_term_half_close(vio_vf vf) +{ + vty_io vio ; + + /* Get the vio and ensure that we are all straight */ + vio = vf->vio ; + assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ; + + /* Do the file side of things + * + * Note that half closing the file sets a new timeout, sets read off + * and write on. + */ + + + + uty_set_monitor(vio, 0) ; + + /* Discard everything in the keystroke stream and force it to EOF */ + if (vio->key_stream != NULL) + keystroke_stream_set_eof(vio->key_stream) ; + + /* Turn off "--more--" so that all output clears without interruption. + * + * If is sitting on a "--more--" prompt, then exit the wait_more CLI. + */ + vio->cli_more_enabled = 0 ; + + if (vio->cli_more_wait) + uty_cli_exit_more_wait(vio) ; + + /* If a command is not in progress, enable output, which will clear + * the output buffer if there is anything there, plus any close reason, + * and then close. + * + * If command is in progress, then this process will start when it + * completes. + */ + if (!vio->cmd_in_progress) + vio->cmd_out_enabled = 1 ; + + /* Log closing of VTY_TERM */ + assert(vio->vty->type == VTY_TERMINAL) ; + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", uty_vf_fd(vf)) ; +} ; + +/*------------------------------------------------------------------------------ + * vprintf to VTY_TERM + * + * All output goes to output fifo until command completes. + * + * NB: MUST be cmd_in_progress + * + * Discards output if the socket is not open for whatever reason. + */ +extern int +uty_term_vprintf(vio_vf vf, const char *format, va_list args) +{ + VTY_ASSERT_LOCKED() ; + + if (!vf->write_open) + return 0 ; /* discard output if not open ! */ + + assert(vf->vio->cmd_in_progress) ; + + return vio_fifo_vprintf(vf->obuf, format, args) ; +} ; + +/*------------------------------------------------------------------------------ + * Set/Clear "monitor" state: + * + * set: if VTY_TERM and not already "monitor" (and write_open !) + * clear: if is "monitor" + */ +extern void +uty_set_monitor(vty_io vio, bool on) +{ + VTY_ASSERT_LOCKED() ; + + if (on && !vio->monitor) + { + if ((vio->type == VTY_TERM) && vio->sock.write_open) + { + vio->monitor = 1 ; + sdl_push(vio_monitors_base, vio, mon_list) ; + } ; + } + else if (!on && vio->monitor) + { + vio->monitor = 0 ; + sdl_del(vio_monitors_base, vio, mon_list) ; + } +} ; + +/*============================================================================== + * Action routines for VIN_TERM/VOUT_TERM type vin/vout objects + */ + +static enum vty_readiness uty_term_write(vio_vf vf) ; + +/*============================================================================== + * Readiness and the VIN_TERM type vin. + * + * For TERM stuff the driving force is write ready. This is used to prompt the + * VOUT_TERM when there is outstanding output (obviously), but also if there + * is buffered input in the keystroke stream. + * + * The VIN_TERM uses read ready only when it doesn't set write ready. Does + * not set both at once. + * + * So there is only one, common, uty_term_ready function, which: + * + * 1. attempts to clear any output it can. + * + * The state of the output affects the CLI, so must always do this before + * before invoking the CLI. + * + * If this write enters the "--more--" state, then will have tried to + * write away the prompt. + * + * 2. invokes the CLI + * + * Which will do either the standard CLI stuff or the special "--more--" + * stuff. + * + * 3. attempts to write any output there now is. + * + * If the CLI generated new output, as much as possible is written away + * now. + * + * If this write enters the "--more--" state, then it returns now_ready, + * if the prompt was written away, which loops back to the CLI. + * + * Note that this is arranging: + * + * a. to write away the "--more--" prompt as soon as the tranche of output to + * which it refers, completes + * + * b. to enter the cli_more_wait CLI for the first time immediately after the + * "--more--" prompt is written away. + * + * The loop limits itself to one trache of command output each time. + * + * Resets the timer because something happened. + */ +static void +uty_term_ready(vio_fd vfd, void* action_info) +{ + enum vty_readiness ready ; + + vio_vf vf = action_info ; + vty_io vio = vf->vio ; + + VTY_ASSERT_LOCKED() ; + + vio->cmd_out_done = 0 ; /* not done any command output yet */ + + uty_term_write(vf) ; /* try to clear outstanding stuff */ + do + { + ready = uty_cli(vio) ; /* do any CLI work... */ + ready |= uty_term_write(vf) ; /* ...and any output that generates */ + } while (ready >= now_ready) ; + + uty_file_set_readiness(vf, ready) ; +} ; + +/*============================================================================== + * Reading from VTY_TERM. + * + * The select/pselect call-back ends up in uty_read_ready(). + * + * Note that uty_write_ready() also calls uty_read_ready, in order to kick the + * current CLI. + */ + +/*------------------------------------------------------------------------------ + * Read a lump of bytes and shovel into the keystroke stream + * + * Steal keystroke if required -- see keystroke_input() + * + * Returns: 0 => nothing available + * > 0 => read at least one byte + * -1 => EOF (or not open, or failed) + */ +extern int +uty_read (vty_io vio, keystroke steal) +{ + unsigned char buf[500] ; + int get ; + + if (!vio->sock.read_open) + return -1 ; /* at EOF if not open */ + + get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; + if (get >= 0) + keystroke_input(vio->key_stream, buf, get, steal) ; + else if (get < 0) + { + if (get == -1) + uty_file_error(vio, "read") ; + + vio->sock.read_open = 0 ; + keystroke_input(vio->key_stream, NULL, 0, steal) ; + + get = -1 ; + } ; + + return get ; +} ; + +/*============================================================================== + * Writing to VOUT_TERM -- driven by ready state. + * + * There are two sets of buffering: + * + * cli -- command line -- which reflects the status of the command line + * + * cmd -- command output -- which is written to the file only while + * cmd_out_enabled. + * + * The cli output takes precedence. + * + * Output of command stuff is subject to line_control, and may go through the + * "--more--" mechanism. + */ + +static int uty_write_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) ; +static int uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) ; + +/*------------------------------------------------------------------------------ + * Write as much as possible of what there is. + * + * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, + * empty. + * + * Note that if !write_open, or becomes !write_open, then the FIFOs are empty + * and all output instantly successful. + * + * Sets write on if prevented from writing everything available for output + * by write() threatening to block. + * + * Returns: write_ready if should now set write on + * now_ready if should loop back and try again + * not_ready otherwise + */ +static enum vty_readiness +uty_term_write(vio_vf vf) +{ + vty_io vio = vf->vio ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + ret = -1 ; + while (vf->write_open) + { + /* Any outstanding line control output takes precedence */ + if (vf->olc != NULL) + { + ret = uty_write_lc(vf, vf->obuf, vf->olc) ; + if (ret != 0) + break ; + } + + /* Next: empty out the cli output */ + ret = vio_fifo_write_nb(&vf->cli_obuf, vio->sock.fd, true) ; + if (ret != 0) + break ; + + /* Finished now if not allowed to progress the command stuff */ + if (!vio->cmd_out_enabled) + return not_ready ; /* done all can do */ + + /* Last: if there is something in the command buffer, do that */ + if (!vio_fifo_empty(vf->obuf)) + { + if (vio->cmd_out_done) + break ; /* ...but not if done once */ + + vio->cmd_out_done = 1 ; /* done this once */ + + assert(!vio->cli_more_wait) ; + + if (vio->cmd_lc != NULL) + ret = uty_write_fifo_lc(vf, vf->obuf, vf->olc) ; + else + ret = vio_fifo_write_nb(vf->obuf, vio->sock.fd, true) ; + + /* If moved into "--more--" state@ + * + * * the "--more--" prompt is ready to be written, so do that now + * + * * if that completes, then want to run the CLI *now* to perform the + * first stage of the "--more--" process. + */ + if (vio->cli_more_wait) + { + ret = vio_fifo_write_nb(vf->obuf, vio->sock.fd, true) ; + if (ret == 0) + return now_ready ; + } ; + + if (ret != 0) + break ; + } + + /* Exciting stuff: there is nothing left to output... + * + * ... watch out for half closed state. + */ + if (vio->half_closed) + { + if (vio->close_reason != NULL) + { + vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ + + struct vty* vty = vio->vty ; + if (vio->cli_drawn || vio->cli_dirty) + vty_out(vty, VTY_NEWLINE) ; + vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; + + vio->cmd_in_progress = 0 ; + + vio->close_reason = NULL ; /* MUST discard now... */ + continue ; /* ... and write away */ + } ; + + if (!vio->closed) /* avoid recursion */ + uty_close(vio) ; + + return not_ready ; /* it's all over */ + } ; + + /* For VTY_TERM: if the command line is not drawn, now is a good + * time to do that. + */ + if (vio->vty_type == VTY_TERM) + if (uty_cli_draw_if_required(vio)) + continue ; /* do that now. */ + + /* There really is nothing left to output */ + return not_ready ; + } ; + + /* Arrives here if there is more to do, or failed (or was !write_open) */ + + if (ret >= 0) + return write_ready ; + + /* If is write_open, then report the error + * + * If still read_open, let the reader pick up and report the error, when it + * has finished anything it has buffered. + */ + if (vf->write_open) + { + if (!vf->read_open) + uty_file_error(vio, "write") ; + + vf->write_open = false ; /* crash close write */ + } ; + + /* For whatever reason, is no longer write_open -- clear all buffers. + */ + vio_fifo_clear(vf->obuf) ; /* throw away cli stuff */ + uty_out_clear(vio) ; /* throw away cmd stuff */ + + vio->close_reason = NULL ; /* too late for this */ + + return not_ready ; /* NB: NOT blocked by I/O */ +} ; + +/*------------------------------------------------------------------------------ + * Write as much as possible -- for "monitor" output. + * + * Outputs only: + * + * a. outstanding line control stuff. + * + * b. contents of CLI buffer + * + * And: + * + * a. does not report any errors. + * + * b. does not change anything except the state of the buffers. + * + * In particular, for the qpthreaded world, does not attempt to change + * the state of the qfile or any other "thread private" structures. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed (or !write_open) + */ +static int +uty_write_monitor(vio_vf vf) +{ + VTY_ASSERT_LOCKED() ; + + if (!vf->write_open) + return -1 ; + + if (vf->olc != NULL) + { + int ret ; + ret = uty_write_lc(vf, vf->obuf, vf->olc) ; + + if (ret != 0) + return ret ; + } ; + + return vio_fifo_write_nb(vf->obuf, uty_vf_fd(vf), true) ; +} ; + +/*------------------------------------------------------------------------------ + * Write the given FIFO to output -- subject to possible line control. + * + * Note that even if no "--more--" is set, will have set some height, so + * that does not attempt to empty the FIFO completely all in one go. + * + * If the line control becomes "paused", it is time to enter "--more--" state + * -- unless the FIFO is empty (or "--more--" is not enabled). + * + * NB: expects that the sock is write_open + * + * Returns: > 0 => blocked or completed one tranche + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) +{ + int ret ; + char* src ; + size_t have ; + + /* Collect another line_control height's worth of output. + * + * Expect the line control to be empty at this point, but it does not have + * to be. + */ + vio_lc_set_pause(lc) ; /* clears lc->paused */ + + src = vio_fifo_get_rdr(vfifo, &have) ; + + while ((src != NULL) && (!lc->paused)) + { + size_t take ; + take = vio_lc_append(lc, src, have) ; + src = vio_fifo_step_rdr(vfifo, &have, take) ; + } ; + + vf->vio->cli_dirty = (lc->col != 0) ; + + /* Write the contents of the line control */ + ret = uty_write_lc(vf, vfifo, lc) ; + + if (ret < 0) + return ret ; /* give up now if failed. */ + + if ((ret == 0) && vio_fifo_empty(vfifo)) + return 0 ; /* FIFO and line control empty */ + + /* If should now do "--more--", now is the time to prepare for that. + * + * Entering more state issues a new prompt in the CLI buffer, which can + * be written once line control write completes. + * + * The "--more--" cli will not do anything until the CLI buffer has + * cleared. + */ + if (lc->paused && vf->vio->cli_more_enabled) + uty_cli_enter_more_wait(vf->vio) ; + + return 1 ; /* FIFO or line control, not empty */ +} ; + +/*------------------------------------------------------------------------------ + * Write contents of line control (if any). + * + * NB: expects that the sock is write_open + * + * NB: does nothing other than write() and buffer management. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) +{ + int ret ; + + ret = vio_lc_write_nb(uty_vf_fd(vf), lc) ; + + if (ret <= 0) + vio_fifo_sync_rdr(vfifo) ; /* finished with FIFO contents */ + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Start command output -- clears down the line control. + * + * Requires that that current line is empty -- restarts the line control + * on the basis that is at column 0. + */ +extern void +uty_cmd_output_start(vio_vf vf) +{ + if (vf->olc != NULL) + vio_lc_clear(vf->olc) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the effective height for line control (if any) + * + * If using line_control, may enable the "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. + */ +extern void +uty_set_height(vio_vf vf) +{ + vty_io vio = vf->vio ; + bool on ; + + on = 0 ; /* default state */ + + if ((vf->olc != NULL) && !vio->half_closed) + { + int height ; + + height = 0 ; /* default state */ + + if ((vio->width) != 0) + { + /* If window size is known, use lines or given height */ + if (vio->lines >= 0) + height = vio->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = vio->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (vio->lines_set) + height = vio->lines ; + } ; + + if (height > 0) + on = 1 ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(vio->cmd_lc, vio->width, height) ; + } ; + + vio->cli_more_enabled = on ; +} ; + +/*============================================================================== + * Timer for VTY_TERM (and VTY_SHELL_SERV). + */ + +/*------------------------------------------------------------------------------ + * Timer has expired. + * + * If half_closed, then this is curtains -- have waited long enough ! + * + * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + */ +static void +uty_timer_expired (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return uty_close(vio) ; /* curtains */ + + uty_close(vio, "Timed out") ; /* bring input side to a halt */ + } ; + + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------------ + * Set read/write readiness -- for VTY_TERM + * + * Note that for VTY_TERM, set only one of read or write, and sets write for + * preference. + */ +extern void +uty_file_set_readiness(vio_vf vf, enum vty_readiness ready) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + vio_fd_set_read(vf, (ready == read_ready)) ; + vio_fd_set_write(vf, (ready >= write_ready)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a new timer value. + */ +extern void +uty_file_set_timer(vio_vf vf, unsigned long timeout) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + vf->v_timeout = timeout ; +} ; + + + + + + +/*============================================================================== + * VTY Listener(s) for VTY_TERMINAL + */ + +/* Prototypes for listener stuff */ + +static int uty_term_listen_addrinfo(const char *addr, unsigned short port) ; +static int uty_term_listen_simple(const char *addr, unsigned short port) ; +static int uty_term_listen_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) ; +static void uty_term_accept(int sock_listen) ; + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERMINAL -- using getaddrinfo() for preference. + */ +extern void +uty_term_open_listeners(const char *addr, unsigned short port) +{ + int n ; + + if (VTY_USE_ADDRINFO) + n = uty_term_listen_addrinfo(addr, port); + else + n = uty_term_listen_simple(addr, port); + + if (n == 0) + uzlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ; +} ; + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERMINAL -- using getaddrinfo() + * + * Returns: number of listeners successfully opened. + */ +static int +uty_term_listen_addrinfo(const char *addr, unsigned short port) +{ +#if VTY_USE_ADDRINFO /******************************************************/ + +# ifndef HAVE_IPV6 +# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? +# endif + + int ret; + int n ; + struct addrinfo req; + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + char port_str[16]; + + VTY_ASSERT_LOCKED() ; + + /* Want to listen, TCP-wise, on all available address families, on the + * given port. + */ + memset (&req, 0, sizeof (struct addrinfo)); + req.ai_flags = AI_PASSIVE; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + snprintf(port_str, sizeof(port_str), "%d", port); + + ret = getaddrinfo (addr, port_str, &req, &ainfo); + + if (ret != 0) + { + fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); + exit (1); + } + + /* Open up sockets on all AF_INET and AF_INET6 addresses */ + ainfo_save = ainfo; + + n = 0 ; + do + { + if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) + continue; + + assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; + + ret = uty_term_listen_open(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol, ainfo->ai_addr, port) ; + + if (ret >= 0) + ++n ; + } + while ((ainfo = ainfo->ai_next) != NULL); + + freeaddrinfo (ainfo_save); + + return n ; + +#else + zabort("uty_serv_sock_addrinfo not implemented") ; +#endif /* VTY_USE_ADDRINFO ****************************************************/ +} + +/*------------------------------------------------------------------------------ + * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! + * + * Returns: number of listeners successfully opened. + */ +static int +uty_term_listen_simple(const char *addr, unsigned short port) +{ + int ret; + int n ; + union sockunion su_addr ; + struct sockaddr* sa ; + + VTY_ASSERT_LOCKED() ; + + n = 0 ; /* nothing opened yet */ + + /* If have an address, see what kind and whether valid */ + sa = NULL ; + + if (addr != NULL) + { + ret = str2sockunion (addr, &su_addr) ; + if (ret == 0) + sa = &su_addr.sa ; + else + uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); + } ; + + /* Try for AF_INET */ + ret = uty_term_listen_open(AF_INET, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ + +#if HAVE_IPV6 + /* Try for AF_INET6 */ + ret = uty_term_listen_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; + if (ret >= 0) + ++n ; /* opened socket */ + if (ret == 1) + sa = NULL ; /* used the address */ +#endif + + /* If not used the address... something wrong */ + if (sa != NULL) + uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); + + /* Done */ + return n ; +} + +/*------------------------------------------------------------------------------ + * Open a VTY_TERMINAL listener socket. + * + * The sockaddr 'sa' may be NULL or of a different address family, in which + * case "any" address is used. + * + * If the sockaddr 'sa' is used, only the address portion is used. + * + * Returns: < 0 => failed + * == 0 => OK -- did not use the sockaddr 'sa'. + * > 1 => OK -- and did use the sockaddr 'sa' + */ +static int +uty_term_listen_open(sa_family_t family, int type, int protocol, + struct sockaddr* sa, unsigned short port) +{ + union sockunion su ; + int sock ; + int ret ; + + VTY_ASSERT_LOCKED() ; + + /* Is there an address and is it for this family ? */ + if ((sa != NULL) || (sa->sa_family == family)) + /* Set up sockunion containing required family and address */ + sockunion_new_sockaddr(&su, sa) ; + else + { + /* no address or wrong family -- set up empty sockunion of + * required family */ + sockunion_init_new(&su, family) ; + sa = NULL ; + } ; + + /* Open the socket and set its properties */ + sock = sockunion_socket(family, type, protocol) ; + if (sock < 0) + return -1 ; + + ret = sockopt_reuseaddr (sock); + + if (ret >= 0) + ret = sockopt_reuseport (sock); + + if (ret >= 0) + ret = set_nonblocking(sock); + +#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + /* Want only IPV6 on ipv6 socket (not mapped addresses) + * + * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the + * attempt to bind to :: after binding to 0.0.0.0. + */ + if ((ret >= 0) && (sa->sa_family == AF_INET6)) + { + int on = 1; + ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); + } +#endif + + if (ret >= 0) + ret = sockunion_bind (sock, &su, port, sa) ; + + if (ret >= 0) + ret = sockunion_listen (sock, 3); + + if (ret < 0) + { + close (sock); + return -1 ; + } + + /* Socket is open -- set VTY_TERMINAL listener going */ + uty_add_listener(sock, uty_term_accept) ; + + /* Return OK and signal whether used address or not */ + return (sa != NULL) ? 1 : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_TERM + */ +static void +uty_term_accept(int sock_listen) +{ + int sock_fd; + union sockunion su; + int ret; + unsigned int on; + struct prefix *p ; + + VTY_ASSERT_LOCKED() ; + + /* We can handle IPv4 or IPv6 socket. */ + sockunion_init_new(&su, AF_UNSPEC) ; + + sock_fd = sockunion_accept (sock_listen, &su); + + if (sock_fd < 0) + { + if (sock_fd == -1) + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", + errtoa(errno, 0).str) ; + return ; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock_fd) ; + return ; + } ; + + /* New socket is open... worry about access lists */ + p = sockunion2hostprefix (&su); + ret = 0 ; /* so far, so good */ + + if ((p->family == AF_INET) && vty_accesslist_name) + { + /* VTY's accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } + +#ifdef HAVE_IPV6 + if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) + { + /* VTY's ipv6 accesslist apply. */ + struct access_list* acl ; + + if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + ret = -1 ; + } +#endif /* HAVE_IPV6 */ + + prefix_free (p); + + if (ret != 0) + { + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; + close (sock_fd); + return ; + } ; + + /* Final options (optional) */ + on = 1 ; + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, + (void*)&on, sizeof (on)); + if (ret < 0) + uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", + sock_fd, errtoa(errno, 0).str) ; + + /* All set -- create the VTY_TERM */ + uty_term_accept_new(sock_fd, &su); + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, + sock_fd) ; + + return ; +} ; + +/*============================================================================== + * Output to vty which are set to "monitor". + * + * This is VERY TRICKY. + * + * If not running qpthreaded, then the objective is to get the message away + * immediately -- do not wish it to be delayed in any way by the thread + * system. + * + * So proceed as follows: + * + * a. wipe command line -- which adds output to the CLI buffer + * + * b. write the CLI buffer to the sock and any outstanding line control. + * + * c. write the monitor output. + * + * If that does not complete, put the tail end to the CLI buffer. + * + * d. restore any command line -- which adds output to the CLI buffer + * + * e. write the CLI buffer to the sock + * + * If that all succeeds, nothing has changed as far as the VTY stuff is + * concerned -- except that possibly some CLI output was sent before it got + * round to it. + * + * Note that step (b) will deal with any output hanging around from an + * earlier step (e). If cannot complete that, then does not add fuel to the + * fire -- but the message will be discarded. + * + * If that fails, or does not complete, then can set write on, to signal that + * there is some output in the CLI buffer that needs to be sent, or some + * error to be dealt with. + * + * The output should be tidy. + * + * To cut down the clutter, step (d) is performed only if the command line + * is not empty (or if in cli_more_wait). Once a the user has started to enter + * a command, the prompt and the command will remain visible. + * + * When logging an I/O error for a vty that happens to be a monitor, the + * monitor-ness has already been turned off. The monitor output code does not + * attempt to log any errors, sets write on so that the error will be picked + * up that way. + * + * However, in the event of an assertion failure, it is possible that an + * assertion will fail inside the monitor output. The monitor_busy flag + * prevents disaster. It is also left set if I/O fails in monitor output, so + * will not try to use the monitor again. + * + * Note that an assertion which is false for all vty monitors will recurse + * through all the monitors, setting each one busy, in turn ! + * + + + * TODO: sort out write on in the qpthreads world ?? + * + * The problem is that the qpselect structure is designed to be accessed ONLY + * within the thread to which it belongs. This makes it impossible for the + * monitor output to set/clear read/write on the vty sock... so some way + * around this is required. + */ + +/*------------------------------------------------------------------------------ + * Output logging information to all vty which are set to "monitor". + */ +extern void +uty_log(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + vio = sdl_head(vio_monitors_base) ; + + if (vio == NULL) + return ; /* go no further if no "monitor" vtys */ + + /* Prepare line for output. */ + uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ + + /* write to all known "monitor" vty + * + */ + while (vio != NULL) + { + if (!vio->monitor_busy) + { + int ret ; + + vio->monitor_busy = 1 ; /* close the door */ + + uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ + + ret = uty_write_monitor(vio) ; + if (ret == 0) + { + ret = write_nb(uty_vf_fd(vf), ll->line, ll->len) ; + + if (ret >= 0) + { + ret = uty_cli_post_monitor(vio, ll->line + ret, + ll->len - ret) ; + if (ret > 0) + ret = uty_write_monitor(vio) ; + } ; + } ; + + if (ret != 0) + /* need to prod */ ; + + if (ret >= 0) + vio->monitor_busy = 0 ; + } ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Async-signal-safe version of vty_log for fixed strings. + * + * This is last gasp operation. + */ +void +vty_log_fixed (const char *buf, size_t len) +{ + vty_io vio ; + + /* Write to all known "monitor" vty + * + * Forget all the niceties -- about to die in any case. + */ + vio = sdl_head(vio_monitors_base) ; + while (vio != NULL) + { + write(uty_vf_fd(vf), buf, len) ; + write(uty_vf_fd(vf), "\r\n", 2) ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h new file mode 100644 index 00000000..ef975a71 --- /dev/null +++ b/lib/vty_io_term.h @@ -0,0 +1,64 @@ +/* VTY IO TERM -- Telnet Terminal I/O -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_TERM_H +#define _ZEBRA_VTY_IO_TERM_H + +#include "misc.h" +#include <errno.h> + +#include "uty.h" +#include "vty.h" +#include "vty_io_basic.h" +#include "vty_io.h" + +#include "vio_fifo.h" +#include "vio_lines.h" +#include "keystroke.h" +#include "thread.h" +#include "command.h" +#include "qstring.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by: + * + * vty_io.c -- the main VTY I/O stuff + * + * for I/O to Telnet Terminal. + */ + +/*============================================================================== + * Functions + */ + +extern void uty_term_new(vty_io vio, int sock_fd) ; + +extern void uty_term_half_close(vio_vf vf) ; +extern int uty_term_vprintf(vio_vf vf, const char *format, va_list args) ; + + +extern void uty_term_open_listeners(const char *addr, unsigned short port) ; + + +#endif /* _ZEBRA_VTY_IO_TERM_H */ diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c new file mode 100644 index 00000000..d4f1c9ba --- /dev/null +++ b/lib/vty_pipe.c @@ -0,0 +1,2867 @@ +/* VTY Command Line Pipe Handling + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include "vty.h" +#include "uty.h" +#include "vty_cli.h" +#include "vty_io.h" +#include "vio_lines.h" + +#include "command.h" +#include "command_execute.h" +#include "command_queue.h" + +#include "memory.h" + +/*============================================================================== + * VTY Command Line Input Pipe + * + * Here are the mechanics which support the: + * + * < file_name + * + * <| shell_command + * + * and the: + * + * > file_name + * >> file_name + * | shell_command + * + *============================================================================== + * The file_name handling + * + * Two directories are supported: + * + * "home" -- being the root for configuration files + * + * "cd" -- being the root for relative filenames, in the usual way + * + * "here" -- being the directory for the enclosing "< filename" + * see below for more detailed semantics + * + * There are the global values: + * + * config home -- defaults to directory for configuration file. + * may be set by command. + * + * config cd -- defaults to cwd at start up + * + * There are the local values, within a given CLI instance (ie VTY): + * + * cli home -- set to config home when CLI instance starts, or when + * config home is set within the CLI. + * + * cli cd -- similarly + * + * cli here -- outside < is same as cli home, unless explicitly set + * - set by cli here + * - pushed each time executes <, and set to directory for + * the < filename. + * - pushed each time executes <| and left unchanged + * - popped each time exits < or <| + * - set to parent by "no cli here" inside <, or to + * default state outside < + * + * And then the filename syntax: + * + * /path -- absolute path -- in the usual way + * + * path -- path wrt "cli cd" -- in the usual way + * + * ~/path -- path in "cli home" -- so "config" stuff + * + * ~./path -- path in "here" -- so wrt to enclosing < file + * + *============================================================================== + * The Input Pipe Commands + * + * These are "universal commands". + * + * They are: + * + * < filename -- filename as above + * + * treat contents of given file as command lines. + * (That may include further < commands.) + * + * See notes on cli here for pushing/popping the here + * directory. + * + * TODO: Filename and quotes ?? + * + * <| shell_command -- the shell command is executed "as is" by system(). + * + * the stdout and stderr are collected. + * + * treats stdout as command lines. + * (That may include further < commands.) + * + * anything from stderr is sent to the VTY output. + * + * As far as the top level CLI is concerned, these are discrete commands. + * That is to say: + * + * -- except where blocked while reading the "pipe", all commands are + * executed one after another, in one Routing Engine operation. + * + * -- in any event, all output is gathered in the VTY buffering, and will + * be sent to the console (or where ever) only when the outermost command + * completes. + * + * There are three options associated with the output from a < operation: + * + * -- suppress command line echo + * + * whether to suppress echo of commands to the VTY before they are + * dispatched. + * + * The default is not to suppress command line echo. + * + * -- suppress command results + * + * whether to suppress any output generated by each command. + * + * The default is not to suppress command results. + * + * -- suppress "more" + * + * whether to do "--more--", if currently applies, when finally + * outputting all the command results. + * + * The default is not to suppress "more". + * + * This option can only be set for the outermost < operation. + * + * Remembering that all output occurs in one go when the outermost < operation + * completes. + * + * The default is to show everything and implement "--more--", pretty much as + * if the commands had been typed in. Except that "--more--" applies to + * everything (including the command lines) together, rather than to the output + * of each command individually. + * + * These options may be changed by flags attached to the <, as follows: + * + * ! suppress command line echo + * + * ? suppress "--more--" + * + * * suppress command output + * + * TODO: cli xxx commands for echo and output suppression and notes .... + * + * TODO: Error handling.... + * + * TODO: Closing the CLI.... + * + * TODO: Time out and the CLI.... + * + * + * + * + * + * + * + * + * + * + */ + +/*============================================================================== + * + */ + + + + + + + + + + + + + +static char* vty_host_name = NULL ; +int vty_host_name_set = 0 ; + +static void uty_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Update vty_host_name as per "hostname" or "no hostname" command + */ +extern void +uty_set_host_name(const char* name) +{ + VTY_ASSERT_LOCKED() ; + + vty_host_name_set = (name != NULL) ; + + if (vty_host_name_set) + uty_new_host_name(name) ; + else + uty_check_host_name() ; +} ; + +/*------------------------------------------------------------------------------ + * If vty_host_name is set, free it. + */ +extern void +uty_free_host_name(void) +{ + if (vty_host_name != NULL) + XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ +} ; + +/*------------------------------------------------------------------------------ + * If the host name is not set by command, see if the actual host name has + * changed, and if so change it. + * + * This is done periodically in case the actual host name changes ! + */ +extern void +uty_check_host_name(void) +{ + struct utsname names ; + + VTY_ASSERT_LOCKED() ; + + if (vty_host_name_set) + return ; /* nothing to do if set by command */ + + uname (&names) ; + + if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) + uty_new_host_name(names.nodename) ; +} ; + +/*------------------------------------------------------------------------------ + * Set new vty_host_name and run along list of VTYs to mark the change. + */ +static void +uty_new_host_name(const char* name) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + uty_free_host_name() ; + vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + + vio = vio_list_base ; + while (vio != NULL) + { + vio->cli_prompt_set = 0 ; + vio = sdl_next(vio, vio_list) ; + } ; +} ; + +/*============================================================================== + * General mechanism for command execution. + * + * Command execution is driven by select/pselect -- which means that the + * processing of commands is multiplexed with all other activity. In the + * following: + * + * -- read_ready and write_ready are events signalled by select/pselect + * + * -- setting read or write on, means enabling the file for select/pselect to + * consider it for read_ready or write_ready, respectively. + * + * State of the CLI: + * + * cli_blocked -- the CLI is unable to process any further keystrokes. + * + * cmd_in_progress -- a command has been dispatched and has not yet + * completed (may have been queued). + * + * cmd_out_enabled -- the command FIFO is may be emptied. + * + * This is set when a command completes, and cleared when + * everything is written away. + * + * cli_more_wait -- is in "--more--" wait state + * + * The following are the valid combinations: + * + * blkd : cip : o_en : m_wt : + * -----:------:------:------:-------------------------------------------- + * 0 : 0 : 0 : 0 : collecting a new command + * 0 : 1 : 0 : 0 : command dispatched + * 1 : 1 : 0 : 0 : waiting for (queued) command to complete + * 1 : 0 : 1 : 0 : waiting for command output to complete + * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away + * 0 : 0 : 0 : 1 : waiting for "--more--" response + * 1 : 1 : 1 : 0 : waiting for command to complete, after the + * CLI has been closed + * + * There are two output FIFOs: + * + * 1. for the CLI itself -- uty_cli_out and friends + * + * 2. for output generated by commands -- vty_out and friends. + * + * The CLI FIFO is emptied whenever possible, in preference to the command + * FIFO. The command FIFO is emptied when cmd_out_enabled. While + * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a + * given command is collected together before being sent to the file. + * + * Note that only sets read on when the keystroke stream is empty and has not + * yet hit eof. The CLI process is driven mostly by write_ready -- which + * invokes read_ready. + * + * Note also that after each command dispatch the CLI processor exits, to be + * re-entered again on write_ready/read_ready -- so does one command line at + * a time, yielding the processor after each one. + * + * Note that select/pselect treat a socket which is at "EOF", or has seen an + * error, or has been half closed, etc. as readable and writable. This means + * that the CLI will continue to move forward even after the socket is no + * longer delivering any data. + * + *------------------------------------------------------------------------------ + * The "--more--" handling. + * + * This is largely buried in the output handling. + * + * While cmd_in_progress is true cmd_out_enabled will be false. When the + * command completes: + * + * * cmd_in_progress is cleared + * + * * cmd_out_enabled is set + * + * * cli_blocked will be set + * + * * the line_control structure is reset + * + * * the output process is kicked off by setting write on + * + * The output process used the line_control structure to manage the output, and + * occasionally enter the trivial "--more--" CLI. This is invisible to the + * main CLI. (See the cli_more_wait flag and its handling.) + * + * When all the output has completed the CLI will be kicked, which will see + * that the output buffer is now empty, and it can proceed. + * + * It is expected that the output will end with a newline -- so that when the + * CLI is kicked, the cursor will be at the start of an empty line. + * + * This mechanism means that the command output FIFO only ever contains the + * output from the last command executed. + * + * If the user decides to abandon output at the "--more--" prompt, then the + * contents of the command output FIFO are discarded. + * + *------------------------------------------------------------------------------ + * The qpthreads/qpnexus extension. + * + * When running in qnexus mode, many commands are not executed directly in the + * CLI, but are queued for execution by the main "routeing" nexus. + * + * The parsing of commands is context sensitive. The context depends may + * change during the execution of a command. So... it is not possible to + * dispatch a command until the previous one has completed. + * + * In qnexus mode, when a command is queued, the CLI does not go cli_blocked, + * even if some output has already been generated. This allows a further + * command to be entered while the previous one is executed. However, if the + * command is dispatched before the previous one completes, then the cli will + * block. + * + * While the previous command is executing, the current command line has a + * minimal prompt -- to show that the context is not known. When the previous + * command completes, the command line is redrawn in the new context. + * + *------------------------------------------------------------------------------ + * Command line drawn state. + * + * When the cli_drawn flag is set, the current console line contains the + * current prompt and the user input to date. The cursor is positioned where + * the user last placed it. + * + * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all + * output and prompt, and leaves the console at the start of an empty line + * where the command line used to be. + * + * On entry to the CLI, it will draw the command line again if it has been + * wiped. + * + * This mechanism is used to support the partial command line that may be + * entered while a queued command executes. + * + * It is also used for the command help/completion system. + * + * It is also used to support the "monitor" output. + */ + +/*============================================================================== + * The CLI + */ + +#define CONTROL(X) ((X) - '@') + +static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; +static enum vty_readiness uty_cli_standard(vty_io vio) ; +static enum vty_readiness uty_cli_more_wait(vty_io vio) ; +static void uty_cli_draw(vty_io vio) ; +static void uty_cli_draw_this(vty_io vio, enum node_type node) ; +static void uty_cli_wipe(vty_io vio, int len) ; + +static void uty_will_echo (vty_io vio) ; +static void uty_will_suppress_go_ahead (vty_io vio) ; +static void uty_dont_linemode (vty_io vio) ; +static void uty_do_window_size (vty_io vio) ; +static void uty_dont_lflow_ahead (vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Initialise CLI. + * + * It is assumed that the following have been initialised, empty or zero: + * + * cli_prompt_for_node + * cl + * clx + * cli_vbuf + * cli_obuf + * + * cli_drawn + * cli_dirty + * + * cli_prompt_set + * + * cli_blocked + * cmd_in_progress + * cmd_out_enabled + * cli_wait_more + * + * cli_more_enabled + * + * Sets the CLI such that there is apparently a command in progress, so that + * further initialisation (in particular hello messages and the like) is + * treated as a "start up command". + * + * Sends a suitable set of Telnet commands to start the process. + */ +extern void +uty_cli_init(vty_io vio) +{ + assert(vio->type == VTY_TERM) ; + + vio->cmd_in_progress = 1 ; + vio->cli_blocked = 1 ; + + vio->cli_do = cli_do_nothing ; + + /* Setting up terminal. */ + uty_will_echo (vio); + uty_will_suppress_go_ahead (vio); + uty_dont_linemode (vio); + uty_do_window_size (vio); + if (0) + uty_dont_lflow_ahead (vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Start the CLI. + * + * All start-up operations are complete -- so the "command" is now complete. + * + * Returns: write_ready -- so the first event is a write event, to flush + * any output to date. + */ +extern enum vty_readiness +uty_cli_start(vty_io vio) +{ + uty_cli_cmd_complete(vio, CMD_SUCCESS) ; + return write_ready ; +} ; + +/*------------------------------------------------------------------------------ + * Close the CLI + * + * Note that if any command is revoked, then will clear cmd_in_progress and + * set cmd_out_enabled -- so any output can now clear. + */ +extern void +uty_cli_close(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; + + cq_revoke(vio->vty) ; + + vio->cli_blocked = 1 ; /* don't attempt any more */ + vio->cmd_out_enabled = 1 ; /* allow output to clear */ +} ; + +/*------------------------------------------------------------------------------ + * CLI for VTY_TERM + * + * Do nothing at all if half closed. + * + * Otherwise do: standard CLI + * or: "--more--" CLI + * + * NB: on return, requires that an attempt is made to write away anything that + * may be ready for that. + */ +extern enum vty_readiness +uty_cli(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; + + if (vio->half_closed) + return not_ready ; /* Nothing more if half closed */ + + /* Standard or "--more--" CLI ? */ + if (vio->cli_more_wait) + return uty_cli_more_wait(vio) ; + else + return uty_cli_standard(vio) ; +} ; + +/*============================================================================== + * The Standard CLI + */ + +static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ; +static void uty_cli_response(vty_io vio, enum cli_do cli_do) ; +static bool uty_cli_dispatch(vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Standard CLI for VTY_TERM -- if not blocked, runs until: + * + * * runs out of keystrokes + * * executes a command + * + * Note that this executes at most one command each time it is called. This + * is to allow for a modicum of sharing of the system. For real keyboard input + * this will make no difference at all ! + * + * Returns: not_ready blocked and was blocked when entered + * write_ready if there is anything in the keystroke stream + * read_ready otherwise + */ +static enum vty_readiness +uty_cli_standard(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; + + /* cli_blocked is set when is waiting for a command, or its output to + * complete -- unless either of those has happened, is still blocked. + * + * NB: in both these cases, assumes that other forces are at work to + * keep things moving. + */ + if (vio->cli_blocked) + { + assert(vio->cmd_in_progress || vio->cmd_out_enabled) ; + + if (vio->cmd_in_progress) + { + assert(!vio->cmd_out_enabled) ; + return not_ready ; + } ; + + if (!vio_fifo_empty(&vio->cmd_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + vio->cmd_out_enabled = 0 ; + } ; + + /* If there is nothing pending, then can run the CLI until there is + * something to do, or runs out of input. + * + * If there is something to do, that is because a previous command has + * now completed, which may have wiped the pending command or changed + * the required prompt. + */ + if (vio->cli_do == cli_do_nothing) + vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + else + uty_cli_draw_this(vio, vio->vty->node) ; + + /* If have something to do, do it. */ + if (vio->cli_do != cli_do_nothing) + { + /* Reflect immediate response */ + uty_cli_response(vio, vio->cli_do) ; + + /* If command not already in progress, dispatch this one, which may + * set the CLI blocked. + * + * Otherwise is now blocked until queued command completes. + */ + if (!vio->cmd_in_progress) + vio->cli_blocked = uty_cli_dispatch(vio) ; + else + vio->cli_blocked = 1 ; + } ; + + /* Use write_ready as a proxy for read_ready on the keystroke stream. + * + * Also, if the command line is not drawn, then return write_ready, so + * that + * + * Note that if has just gone cli_blocked, still returns ready. This is + * defensive: at worst will generate one unnecessary read_ready/write_ready + * event. + */ + if (keystroke_stream_empty(vio->key_stream)) + return read_ready ; + else + return write_ready ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch the current vio->cli_do -- queueing it if necessary. + * + * Requires that are NOT blocked and NO command is queued. + * + * Expects to be on new blank line, and when returns will be on new, blank + * line. + * + * Returns: true <=> command completed and output is pending + * false => command has been queued and is now in progress + * + * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. + * + * Can set vio->cl_do = and vio->cl to be a follow-on command. + */ +static bool +uty_cli_dispatch(vty_io vio) +{ + qstring_t tmp ; + enum cli_do cli_do ; + enum cmd_return_code ret ; + + struct vty* vty = vio->vty ; + + VTY_ASSERT_LOCKED() ; + assert(!vio->cli_blocked && !vio->cmd_in_progress) ; + + /* Set vio->clx to the command about to execute. + * + * Clear vio->cl and vio->cl_do. + */ + vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ + vio->cmd_out_enabled = 0 ; /* => collect all output */ + + tmp = vio->clx ; /* swap clx and cl */ + vio->clx = vio->cl ; + vio->cl = tmp ; + + qs_term(&vio->clx) ; /* ensure string is terminated */ + vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ + cli_do = vio->cli_do ; /* current operation */ + + vio->cli_do = cli_do_nothing ; /* clear */ + qs_clear(&vio->cl) ; /* set cl empty (with '\0') */ + + /* Reset the command output FIFO and line_control */ + assert(vio_fifo_empty(&vio->cmd_obuf)) ; + uty_out_clear(vio) ; /* clears FIFO and line control */ + + /* Dispatch command */ + if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) + { + /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ + ret = uty_auth(vty, vty->buf, cli_do) ; + } + else + { + /* All other nodes... */ + switch (cli_do) + { + case cli_do_nothing: + ret = CMD_SUCCESS ; + break ; + + case cli_do_command: + ret = uty_command(vty) ; + break ; + + case cli_do_ctrl_c: + ret = uty_stop_input(vty) ; + break ; + + case cli_do_ctrl_d: + ret = uty_down_level(vty) ; + break ; + + case cli_do_ctrl_z: + ret = uty_command(vty) ; + if (ret == CMD_QUEUED) + vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ + else + ret = uty_end_config(vty) ; /* do the ^Z now */ + break ; + + case cli_do_eof: + ret = uty_cmd_close(vio->vty, "End") ; + break ; + + default: + zabort("unknown cli_do_xxx value") ; + } ; + } ; + + if (ret == CMD_QUEUED) + { + uty_cli_draw(vio) ; /* draw the prompt */ + return false ; /* command not complete */ + } + else + { + uty_cli_cmd_complete(vio, ret) ; + return true ; /* command complete */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * Queued command has completed. + * + * Note that sets write on whether there is anything in the output buffer + * or not... write_ready will kick read_ready. + */ +extern void +vty_queued_result(struct vty *vty, enum cmd_return_code ret) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + if (!vio->closed) + { + uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ + + /* Do the command completion actions that were deferred because the + * command was queued. + * + * Return of CMD_QUEUED => command was revoked before being executed. + * However interesting that might be... frankly don't care. + */ + uty_cli_cmd_complete(vio, ret) ; + + /* Kick the socket -- to write away any outstanding output, and + * re-enter the CLI when that's done. + */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + } + else + { + /* If the VTY is closed, the only reason it still exists is because + * there was cmd_in_progress. + */ + vio->cmd_in_progress = 0 ; + + uty_close(vio) ; /* Final close */ + } ; + + VTY_UNLOCK() ; +} + +/*------------------------------------------------------------------------------ + * Command has completed, so: + * + * * clear cmd_in_progress + * * set cmd_out_enabled -- so any output can now proceed + * * set cli_blocked -- waiting for output to complete + * * and prepare the line control for output + * + * If the return is CMD_CLOSE, then also now does the required half close. + * + * Note that apart from CMD_CLOSE, don't really care what the return was. Any + * diagnostics or other action must be dealt with elsewhere (as part of the + * command execution. + * + * Note that everything proceeds as if there is some output. So after every + * command goes through at least one write_ready event. + * + * This ensures some multiplexing at the command level. + * + * It also means that the decision about whether there is anything to output + * is left to the output code. + */ +static void +uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; + + if (ret == CMD_CLOSE) + uty_half_close(vio, NULL) ; + + vio->cmd_in_progress = 0 ; /* command complete */ + vio->cmd_out_enabled = 1 ; /* enable the output */ + vio->cli_blocked = 1 ; /* now blocked waiting for output */ + + vio->vty->buf = NULL ; /* finished with command line */ + + uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ +} ; + +/*============================================================================== + * The "--more--" CLI + * + * While command output is being cleared from its FIFO, the CLI is cli_blocked. + * + * When the output side signals that "--more--" is required, it sets the + * cli_more_wait flag and clears the cmd_out_enabled flag. + * + * The first stage of handling "--more--" is to suck the input dry, so that + * (as far as is reasonably possible) does not steal a keystroke as the + * "--more--" response which was typed before the prompt was issued. + * + * The cli_blocked flag indicates that the CLI is in this first stage. + */ + +/*------------------------------------------------------------------------------ + * Change the CLI to the "--more--" CLI. + * + * Outputs the new prompt line. + */ +extern void +uty_cli_enter_more_wait(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; + + uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is + wiped before change the CLI state */ + + vio->cmd_out_enabled = 0 ; /* stop output pro tem */ + vio->cli_more_wait = 1 ; /* new state */ + + uty_cli_draw(vio) ; /* draw the "--more--" */ +} ; + +/*------------------------------------------------------------------------------ + * Exit the "--more--" CLI. + * + * Wipes the "--more--" prompt. + * + * This is used when the user responds to the prompt. + * + * It is also used when the vty is "half-closed". In this case, it is (just) + * possible that the '--more--' prompt is yet to be completely written away, + * so: + * + * * assert that is either: !vio->cli_blocked (most of the time it will) + * or: !vio_fifo_empty(&vio->cli_obuf) + * + * * note that can wipe the prompt even though it hasn't been fully + * written away yet. (The effect is to append the wipe action to the + * cli_obuf !) + */ +extern void +uty_cli_exit_more_wait(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf)) + && !vio->cmd_out_enabled && vio->cli_more_wait) ; + + uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--') + before changing the CLI state */ + + vio->cli_blocked = 1 ; /* back to blocked waiting for output */ + vio->cli_more_wait = 0 ; /* exit more_wait */ + vio->cmd_out_enabled = 1 ; /* re-enable output */ +} ; + +/*------------------------------------------------------------------------------ + * Handle the "--more--" state. + * + * Deals with the first stage if cli_blocked. + * + * Tries to steal a keystroke, and when succeeds wipes the "--more--" + * prompt and exits cli_more_wait -- and may cancel all outstanding output. + * + * EOF on input causes immediate exit from cli_more_state. + * + * Returns: read_ready -- waiting to steal a keystroke + * now_ready -- just left cli_more_wait + * not_ready -- otherwise + */ +static enum vty_readiness +uty_cli_more_wait(vty_io vio) +{ + struct keystroke steal ; + + VTY_ASSERT_LOCKED() ; + + /* Deal with the first stage of "--more--" */ + if (vio->cli_blocked) + { + int get ; + + /* If the CLI buffer is not yet empty, then is waiting for the + * initial prompt to clear, so nothing to be done here. + */ + if (!vio_fifo_empty(&vio->cli_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + + /* empty the input buffer into the keystroke stream */ + do + { + get = uty_read(vio, NULL) ; + } while (get > 0) ; + } ; + + /* Go through the "--more--" process, unless no longer write_open (!) */ + if (vio->sock.write_open) + { + /* The read fetches a reasonable lump from the I/O -- so if there + * is a complete keystroke available, expect to get it. + * + * If no complete keystroke available to steal, returns ks_null. + * + * If has hit EOF (or error etc), returns knull_eof. + */ + uty_read(vio, &steal) ; + + /* If nothing stolen, make sure prompt is drawn and wait for more + * input. + */ + if ((steal.type == ks_null) && (steal.value != knull_eof)) + { + if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */ + return write_ready ; + else + return read_ready ; + } ; + + /* Stolen a keystroke -- a (very) few terminate all output */ + if (steal.type == ks_char) + { + switch (steal.value) + { + case CONTROL('C'): + case 'q': + case 'Q': + uty_out_clear(vio) ; + break; + + default: /* everything else, thrown away */ + break ; + } ; + } ; + } ; + + /* End of "--more--" process + * + * Wipe out the prompt and update state. + * + * Return write_ready to tidy up the screen and, unless cleared, write + * some more. + */ + uty_cli_exit_more_wait(vio) ; + + return now_ready ; +} ; + +/*============================================================================== + * CLI VTY output + * + * This is buffered separately from the general (command) VTY output above. + * + * Has a dedicated buffer in the struct vty, which is flushed regularly during + * command processing. + * + * It is expected that can flush straight to the file, since this is running at + * CLI speed. However, if the CLI is being driven by something other than a + * keyboard, or "monitor" output has filled the buffers, then may need to + * have intermediate buffering. + * + * No actual I/O takes place here-- all "output" is to vio->cli_obuf + * and/or vio->cli_ex_obuf + * + * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. + * This is used while passwords are entered and to allow command line changes + * to be made while the line is not visible. + */ + +enum { cli_rep_count = 32 } ; + +typedef const char cli_rep_char[cli_rep_count] ; + +static const char telnet_backspaces[] = + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + } ; +CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; + +static const char telnet_spaces[] = + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' + } ; +CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; + +static const char* telnet_newline = "\r\n" ; + +static void uty_cli_write(vty_io vio, const char *this, int len) ; +static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf fprintf() + */ +static void +uty_cli_out(vty_io vio, const char *format, ...) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->sock.write_open) + { + va_list args ; + + va_start (args, format); + vio_fifo_vprintf(&vio->cli_obuf, format, args) ; + va_end(args); + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo user input + * + * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open + */ +static void +uty_cli_echo(vty_io vio, const char *this, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->sock.write_open) + return ; + + uty_cli_write(vio, this, len) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo 'n' characters using a cli_rep_char string + * + * Do nothing if echo suppressed (eg in AUTH_NODE) + */ +static void +uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->sock.write_open) + return ; + + uty_cli_write_n(vio, chars, n) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf write() + */ +inline static void +uty_cli_write(vty_io vio, const char *this, int len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->sock.write_open) + vio_fifo_put(&vio->cli_obuf, this, len) ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write 'n' characters using a cli_rep_char string + */ +static void +uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) +{ + int len ; + + len = sizeof(cli_rep_char) ; + while (n > 0) + { + if (n < len) + len = n ; + uty_cli_write(vio, chars, len) ; + n -= len ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write given string + * + * Returns length of string. + */ +static int +uty_cli_write_s(vty_io vio, const char *str) +{ + int len ; + + len = strlen(str) ; + if (len != 0) + uty_cli_write(vio, str, len) ; + + return len ; +} ; + +/*============================================================================== + * Standard Messages + */ + +/*------------------------------------------------------------------------------ + * Send newline to the console. + * + * Clears the cli_drawn and the cli_dirty flags. + */ +static void +uty_cli_out_newline(vty_io vio) +{ + uty_cli_write(vio, telnet_newline, 2) ; + vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Wipe 'n' characters. + * + * If 'n' < 0, wipes characters backwards and moves cursor back. + * 'n' > 0, wipes characters forwards, leaving cursor where it is + */ +static void +uty_cli_out_wipe_n(vty_io vio, int n) +{ + if (n < 0) + { + n = abs(n) ; + uty_cli_write_n(vio, telnet_backspaces, n); + } ; + + if (n > 0) + { + uty_cli_write_n(vio, telnet_spaces, n) ; + uty_cli_write_n(vio, telnet_backspaces, n) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send response to the given cli_do + * + * If no command is in progress, then will send newline to signal that the + * command is about to be dispatched. + * + * If command is in progress, then leaves cursor on '^' to signal that is now + * waiting for previous command to complete. + */ +static const char* cli_response [2][cli_do_count] = +{ + { /* when not waiting for previous command to complete */ + [cli_do_command] = "", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" + }, + { /* when waiting for a previous command to complete */ + [cli_do_command] = "^", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" + } +} ; + +static void +uty_cli_response(vty_io vio, enum cli_do cli_do) +{ + const char* str ; + int len ; + + if ((cli_do == cli_do_nothing) || (vio->half_closed)) + return ; + + str = (cli_do < cli_do_count) + ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; + assert(str != NULL) ; + + len = uty_cli_write_s(vio, str) ; + + if (vio->cmd_in_progress) + { + vio->cli_extra_len = len ; + uty_cli_write_n(vio, telnet_backspaces, len) ; + } + else + { + uty_cli_out_newline(vio) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send various messages with trailing newline. + */ + +static void +uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; + uty_cli_out_newline(vio) ; +} ; + +static void +uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; + uty_cli_out_newline(vio) ; +} ; + +/*============================================================================== + * Command line draw and wipe + */ + +/*------------------------------------------------------------------------------ + * Wipe the current console line -- if any. + */ +static void +uty_cli_wipe(vty_io vio, int len) +{ + int a ; + int b ; + + if (!vio->cli_drawn) + return ; /* quit if already wiped */ + + assert(vio->cl.cp <= vio->cl.len) ; + + /* Establish how much ahead and how much behind the cursor */ + a = vio->cli_extra_len ; + b = vio->cli_prompt_len ; + + if (!vio->cli_echo_suppress && !vio->cli_more_wait) + { + a += vio->cl.len - vio->cl.cp ; + b += vio->cl.cp ; + } ; + + /* Wipe anything ahead of the current position and ahead of new len */ + if ((a + b) > len) + uty_cli_out_wipe_n(vio, +a) ; + + /* Wipe anything behind current position, but ahead of new len */ + if (b > len) + { + uty_cli_out_wipe_n(vio, -(b - len)) ; + b = len ; /* moved the cursor back */ + } ; + + /* Back to the beginning of the line */ + uty_cli_write_n(vio, telnet_backspaces, b) ; + + /* Nothing there any more */ + vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * If not currently drawn, draw prompt etc according to the current state + * and node. + * + * See uty_cli_draw(). + */ +extern bool +uty_cli_draw_if_required(vty_io vio) +{ + if (vio->cli_drawn) + return false ; + + uty_cli_draw(vio) ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt etc for the current vty node. + * + * See uty_cli_draw_this() + */ +static void +uty_cli_draw(vty_io vio) +{ + uty_cli_draw_this(vio, vio->vty->node) ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt and entire command line, leaving current position where it + * should be. + * + * If command line is currently drawn, this wipes and redraws. + * + * Otherwise, assumes is positioned at start of an empty line. + * + * Draws prompt according to the given 'node', except: + * + * * if is half_closed, draw nothing -- wipes the current line + * + * * if is cli_more_wait, draw the "--more--" prompt + * + * * if is cmd_in_progress, draw the vestigial prompt. + * + * By the time the current command completes, the node may have changed, so + * the current prompt may be invalid. + * + * Sets: cli_drawn = true + * cli_dirty = false + * cli_prompt_len = length of prompt used + * cli_extra_len = 0 + * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) + */ +static void +uty_cli_draw_this(vty_io vio, enum node_type node) +{ + const char* prompt ; + size_t l_len ; + int p_len ; + + if (vio->cli_dirty) + uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */ + + /* Sort out what the prompt is. */ + if (vio->half_closed) + { + prompt = "" ; + p_len = 0 ; + l_len = 0 ; + } + else if (vio->cli_more_wait) + { + prompt = "--more--" ; + p_len = strlen(prompt) ; + l_len = 0 ; + } + else if (vio->cmd_in_progress) + { + /* If there is a queued command, the prompt is a minimal affair. */ + prompt = "~ " ; + p_len = strlen(prompt) ; + l_len = vio->cl.len ; + } + else + { + /* The prompt depends on the node, and is expected to include the + * host name. + * + * Caches the prompt so doesn't re-evaluate it every time. + * + * If the host name changes, the cli_prompt_set flag is cleared. + */ + if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) + { + const char* prompt ; + + if (vty_host_name == NULL) + uty_check_host_name() ; /* should never be required */ + + prompt = cmd_prompt(node) ; + if (prompt == NULL) + { + zlog_err("vty %s has node %d", uty_get_name(vio), node) ; + prompt = "%s ???: " ; + } ; + + qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + + vio->cli_prompt_node = node ; + vio->cli_prompt_set = 1 ; + } ; + + prompt = vio->cli_prompt_for_node.body ; + p_len = vio->cli_prompt_for_node.len ; + l_len = vio->cl.len ; + } ; + + /* Now, if line is currently drawn, time to wipe it */ + if (vio->cli_drawn) + uty_cli_wipe(vio, p_len + l_len) ; + + /* Set about writing the prompt and the line */ + vio->cli_drawn = 1 ; + vio->cli_extra_len = 0 ; + vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + vio->cli_prompt_len = p_len ; + + uty_cli_write(vio, prompt, p_len) ; + + if (l_len != 0) + { + uty_cli_write(vio, qs_chars(&vio->cl), l_len) ; + if (vio->cl.cp < l_len) + uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ; + } ; +} ; + +/*============================================================================== + * Monitor output. + * + * To prepare for monitor output, wipe as much as is necessary for the + * monitor line to appear correctly. + * + * After monitor output, may need to do two things: + * + * * if the output was incomplete, place the rump in the CLI buffer, + * so that: + * + * a. don't mess up the console with partial lines + * + * b. don't lose part of a message + * + * c. act as a brake on further monitor output -- cannot do any more + * until the last, part, line is dealt with. + * + * * restore the command line, unless it is empty ! + */ + + /*----------------------------------------------------------------------------- + * Prepare for new monitor output line. + * + * Wipe any existing command line. + */ +extern void +uty_cli_pre_monitor(vty_io vio, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_wipe(vio, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Recover from monitor line output. + * + * If monitor line failed to complete, append the rump to the CLI buffer. + * + * If have a non-empty command line, or is cli_more_wait, redraw the command + * line. + * + * Returns: 0 => rump was empty and no command line stuff written + * > 0 => rump not empty or some command line stuff written + */ +extern int +uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (len != 0) + uty_cli_write(vio, buf, len) ; + + if (vio->cli_more_wait || (vio->cl.len != 0)) + { + uty_cli_draw(vio) ; + ++len ; + } ; + + return len ; +} ; + +/*============================================================================== + * Command line processing loop + */ + +static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ; +static int uty_cli_insert (vty_io vio, const char* chars, int n) ; +static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; +static int uty_cli_word_overwrite (vty_io vio, char *str) ; +static int uty_cli_forwards(vty_io vio, int n) ; +static int uty_cli_backwards(vty_io vio, int n) ; +static int uty_cli_del_forwards(vty_io vio, int n) ; +static int uty_cli_del_backwards(vty_io vio, int n) ; +static int uty_cli_bol (vty_io vio) ; +static int uty_cli_eol (vty_io vio) ; +static int uty_cli_word_forwards_delta(vty_io vio) ; +static int uty_cli_word_forwards(vty_io vio) ; +static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ; +static int uty_cli_word_backwards_pure (vty_io vio) ; +static int uty_cli_word_backwards (vty_io vio) ; +static int uty_cli_del_word_forwards(vty_io vio) ; +static int uty_cli_del_word_backwards(vty_io vio) ; +static int uty_cli_del_to_eol (vty_io vio) ; +static int uty_cli_clear_line(vty_io vio) ; +static int uty_cli_transpose_chars(vty_io vio) ; +static void uty_cli_history_use(vty_io vio, int step) ; +static void uty_cli_next_line(vty_io vio) ; +static void uty_cli_previous_line (vty_io vio) ; +static void uty_cli_complete_command (vty_io vio, enum node_type node) ; +static void uty_cli_describe_command (vty_io vio, enum node_type node) ; + +/*------------------------------------------------------------------------------ + * Fetch next keystroke, reading from the file if required. + */ +static inline bool +uty_cli_get_keystroke(vty_io vio, keystroke stroke) +{ + if (keystroke_get(vio->key_stream, stroke)) + return 1 ; + + uty_read(vio, NULL) ; /* not stealing */ + + return keystroke_get(vio->key_stream, stroke) ; +} ; + +/*------------------------------------------------------------------------------ + * Process keystrokes until run out of input, or get something to cli_do. + * + * If required, draw the prompt and command line. + * + * Process keystrokes until run out of stuff to do, or have a "command line" + * that must now be executed. + * + * Processes the contents of the keystroke stream. If exhausts that, will set + * ready to read and return. (To give some "sharing".) + * + * Returns: cli_do_xxxx + * + * When returns the cl is '\0' terminated. + */ +static enum cli_do +uty_cli_process(vty_io vio, enum node_type node) +{ + struct keystroke stroke ; + uint8_t u ; + int auth ; + enum cli_do ret ; + + auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + /* Now process as much as possible of what there is */ + ret = cli_do_nothing ; + while (1) + { + if (!vio->cli_drawn) + uty_cli_draw_this(vio, node) ; + + if (!uty_cli_get_keystroke(vio, &stroke)) + { + ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; + break ; + } ; + + if (stroke.flags != 0) + { + /* TODO: deal with broken keystrokes */ + continue ; + } ; + + switch (stroke.type) + { + /* Straightforward character -----------------------------------*/ + /* Note: only interested in 8-bit characters ! */ + case ks_char: + u = (uint8_t)stroke.value ; + + switch (stroke.value) + { + case CONTROL('A'): + uty_cli_bol (vio); + break; + + case CONTROL('B'): + uty_cli_backwards(vio, 1); + break; + + case CONTROL('C'): + ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/ + break ; + + case CONTROL('D'): + if (vio->cl.len == 0) /* if at start of empty line */ + ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/ + else + uty_cli_del_forwards(vio, 1); + break; + + case CONTROL('E'): + uty_cli_eol (vio); + break; + + case CONTROL('F'): + uty_cli_forwards(vio, 1); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_backwards(vio, 1); + break; + + case CONTROL('K'): + uty_cli_del_to_eol (vio); + break; + + case CONTROL('N'): + uty_cli_next_line (vio); + break; + + case CONTROL('P'): + uty_cli_previous_line (vio); + break; + + case CONTROL('T'): + uty_cli_transpose_chars (vio); + break; + + case CONTROL('U'): + uty_cli_clear_line(vio); + break; + + case CONTROL('W'): + uty_cli_del_word_backwards (vio); + break; + + case CONTROL('Z'): + ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ + break; + + case '\n': + case '\r': + ret = cli_do_command ; /* Exit on CR or LF.............*/ + break ; + + case '\t': + if (auth) + break ; + else + uty_cli_complete_command (vio, node); + break; + + case '?': + if (auth) + uty_cli_insert (vio, (char*)&u, 1); + else + uty_cli_describe_command (vio, node); + break; + + default: + if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) + uty_cli_insert (vio, (char*)&u, 1) ; + break; + } + break ; + + /* ESC X -------------------------------------------------------------*/ + case ks_esc: + switch (stroke.value) + { + case 'b': + uty_cli_word_backwards (vio); + break; + + case 'f': + uty_cli_word_forwards (vio); + break; + + case 'd': + uty_cli_del_word_forwards (vio); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_word_backwards (vio); + break; + + default: + break; + } ; + break ; + + /* ESC [ X -----------------------------------------------------------*/ + case ks_csi: + if (stroke.len != 0) + break ; /* only recognise 3 byte sequences */ + + switch (stroke.value) + { + case ('A'): + uty_cli_previous_line (vio); + break; + + case ('B'): + uty_cli_next_line (vio); + break; + + case ('C'): + uty_cli_forwards(vio, 1); + break; + + case ('D'): + uty_cli_backwards(vio, 1); + break; + default: + break ; + } ; + break ; + + /* Telnet Command ----------------------------------------------------*/ + case ks_iac: + uty_telnet_command(vio, &stroke, false) ; + break ; + + /* Unknown -----------------------------------------------------------*/ + default: + zabort("unknown keystroke type") ; + } ; + + /* After each keystroke..... */ + + if (ret != cli_do_nothing) + { + uty_cli_eol (vio) ; /* go to the end of the line */ + break ; /* stop processing */ + } ; + } ; + + /* Tidy up and return where got to. */ + + qs_term(&vio->cl) ; /* add '\0' */ + + return ret ; +} ; + +/*============================================================================== + * Command line operations + */ + +/*------------------------------------------------------------------------------ + * Insert 'n' characters at current position in the command line + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_insert (vty_io vio, const char* chars, int n) +{ + int after ; + + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + + if (n <= 0) + return n ; /* avoid trouble */ + + after = qs_insert(&vio->cl, chars, n) ; + + uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + + if (after != 0) + uty_cli_echo_n(vio, telnet_backspaces, after) ; + + vio->cl.cp += n ; + + return n ; +} ; + +/*------------------------------------------------------------------------------ + * Overstrike 'n' characters at current position in the command line + * + * Move current position forwards. + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_overwrite (vty_io vio, char* chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + + if (n > 0) + { + qs_replace(&vio->cl, chars, n) ; + uty_cli_echo(vio, chars, n) ; + + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Insert a word into vty interface with overwrite mode. + * + * NB: Assumes result will then be the end of the line. + * + * Returns number of characters inserted -- ie length of string + */ +static int +uty_cli_word_overwrite (vty_io vio, char *str) +{ + int n ; + VTY_ASSERT_LOCKED() ; + + n = uty_cli_overwrite(vio, str, strlen(str)) ; + + vio->cl.len = vio->cl.cp ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Forward 'n' characters -- stop at end of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_forwards(vty_io vio, int n) +{ + int have ; + VTY_ASSERT_LOCKED() ; + + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Backwards 'n' characters -- stop at start of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_backwards(vty_io vio, int n) +{ + VTY_ASSERT_LOCKED() ; + + if ((int)vio->cl.cp < n) + n = vio->cl.cp ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cli_echo_n(vio, telnet_backspaces, n) ; + vio->cl.cp -= n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters -- forwards -- stop at end of line. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_forwards(vty_io vio, int n) +{ + int after ; + int have ; + + VTY_ASSERT_LOCKED() ; + + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; /* cannot delete more than have */ + + assert(n >= 0) ; + + if (n <= 0) + return 0 ; + + after = qs_delete(&vio->cl, n) ; + + if (after > 0) + uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + + uty_cli_echo_n(vio, telnet_spaces, n) ; + uty_cli_echo_n(vio, telnet_backspaces, after + n) ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters before the point. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_backwards(vty_io vio, int n) +{ + return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; +} + +/*------------------------------------------------------------------------------ + * Move to the beginning of the line. + * + * Returns number of characters moved over. + */ +static int +uty_cli_bol (vty_io vio) +{ + return uty_cli_backwards(vio, vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Move to the end of the line. + * + * Returns number of characters moved over + */ +static int +uty_cli_eol (vty_io vio) +{ + return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word delta -- distance to start of next word. + * + * Return number of characters to step over to reach next word. + * + * Steps over non-space characters and then any spaces. + */ +static int +uty_cli_word_forwards_delta(vty_io vio) +{ + char* cp ; + char* tp ; + char* ep ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + ep = qs_ep_char(&vio->cl) ; + + tp = cp ; + + while ((tp < ep) && (*tp != ' ')) + ++tp ; + + while ((tp < ep) && (*tp == ' ')) + ++tp ; + + return tp - cp ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word -- move to start of next word. + * + * Moves past any non-spaces, then past any spaces. + */ +static int +uty_cli_word_forwards(vty_io vio) +{ + return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word delta -- distance to start of next word, back. + * + * Return number of characters to step over to reach next word. + * + * If "eat_spaces", starts by stepping over spaces. + * Steps back until next (backwards) character is space, or hits start of line. + */ +static int +uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) +{ + char* cp ; + char* tp ; + char* sp ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + sp = qs_chars(&vio->cl) ; + + tp = cp ; + + if (eat_spaces) + while ((tp > sp) && (*(tp - 1) == ' ')) + --tp ; + + while ((tp > sp) && (*(tp - 1) != ' ')) + --tp ; + + return cp - tp ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word, but not trailing spaces. + * + * Move back until next (backwards) character is space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards_pure (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word -- move to start of previous word. + * + * Moves past any spaces, then move back until next (backwards) character is + * space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Delete to end of word -- forwards. + * + * Deletes any leading spaces, then deletes upto next space or end of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_forwards(vty_io vio) +{ + return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} + +/*------------------------------------------------------------------------------ + * Delete to start of word -- backwards. + * + * Deletes any trailing spaces, then deletes upto next space or start of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_backwards(vty_io vio) +{ + return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill rest of line from current point. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_to_eol (vty_io vio) +{ + return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill line from the beginning. + * + * Returns number of characters deleted. + */ +static int +uty_cli_clear_line(vty_io vio) +{ + uty_cli_bol(vio) ; + return uty_cli_del_to_eol(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Transpose chars before or at the point. + * + * Return number of characters affected. + */ +static int +uty_cli_transpose_chars(vty_io vio) +{ + char chars[2] ; + char* cp ; + + VTY_ASSERT_LOCKED() ; + + /* Give up if < 2 characters or at start of line. */ + if ((vio->cl.len < 2) || (vio->cl.cp < 1)) + return 0 ; + + /* Move back to first of characters to exchange */ + if (vio->cl.cp == vio->cl.len) + uty_cli_backwards(vio, 2) ; + else + uty_cli_backwards(vio, 1) ; + + /* Pick up in the new order */ + cp = qs_cp_char(&vio->cl) ; + chars[1] = *cp++ ; + chars[0] = *cp ; + + /* And overwrite */ + return uty_cli_overwrite(vio, chars, 2) ; +} ; + +/*============================================================================== + * Command line history handling + */ + +/*------------------------------------------------------------------------------ + * Add given command line to the history buffer. + * + * This is inserting the vty->buf line into the history. + */ +extern void +uty_cli_hist_add (vty_io vio, const char* cmd_line) +{ + qstring prev_line ; + qstring_t line ; + int prev_index ; + + VTY_ASSERT_LOCKED() ; + + /* Construct a dummy qstring for the given command line */ + qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ + + /* make sure have a suitable history vector */ + vector_set_min_length(vio->hist, VTY_MAXHIST) ; + + /* find the previous command line in the history */ + prev_index = vio->hindex - 1 ; + if (prev_index < 0) + prev_index = VTY_MAXHIST - 1 ; + + prev_line = vector_get_item(vio->hist, prev_index) ; + + /* If the previous line is NULL, that means the history is empty. + * + * If the previous line is essentially the same as the current line, + * replace it with the current line -- so that the latest whitespace + * version is saved. + * + * Either way, replace the the previous line entry by moving hindex + * back ! + */ + if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) + vio->hindex = prev_index ; + else + prev_line = vector_get_item(vio->hist, vio->hindex) ; + + /* Now replace the hindex entry */ + vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + + /* Advance to the near future and reset the history pointer */ + vio->hindex++; + if (vio->hindex == VTY_MAXHIST) + vio->hindex = 0; + + vio->hp = vio->hindex; +} ; + +/*------------------------------------------------------------------------------ + * Replace command line by current history. + * + * This function is called from vty_next_line and vty_previous_line. + * + * Step +1 is towards the present + * -1 is into the past + */ +static void +uty_cli_history_use(vty_io vio, int step) +{ + int index ; + unsigned old_len ; + unsigned after ; + unsigned back ; + qstring hist ; + + VTY_ASSERT_LOCKED() ; + + assert((step == +1) || (step == -1)) ; + + index = vio->hp ; + + /* Special case of being at the insertion point */ + if (index == vio->hindex) + { + if (step > 0) + return ; /* already in the present */ + + /* before stepping back from the present, take a copy of the + * current command line -- so can get back to it. + */ + hist = vector_get_item(&vio->hist, vio->hindex) ; + vector_set_item(vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + } ; + + /* Advance or retreat */ + index += step ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + else if (index >= VTY_MAXHIST) + index = 0 ; + + hist = vector_get_item(vio->hist, index) ; + + /* If moving backwards in time, may not move back to the insertion + * point (that would be wrapping round to the present) and may not + * move back to a NULL entry (that would be going back before '.'). + */ + if (step < 0) + if ((hist == NULL) || (index == vio->hindex)) + return ; + + /* Now, if arrived at the insertion point, this is returning to the + * present, which is fine. + */ + vio->hp = index; + + /* Move back to the start of the current line */ + uty_cli_bol(vio) ; + + /* Get previous line from history buffer and echo that */ + old_len = vio->cl.len ; + qs_copy(&vio->cl, hist) ; + + /* Sort out wiping out any excess and setting the cursor position */ + if (old_len > vio->cl.len) + after = old_len - vio->cl.len ; + else + after = 0 ; + + back = after ; + if (vio->cl.len > vio->cl.cp) + back += (vio->cl.len - vio->cl.cp) ; + + if (vio->cl.len > 0) + uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; + + if (after > 0) + uty_cli_echo_n(vio, telnet_spaces, after) ; + + if (back > 0) + uty_cli_echo_n(vio, telnet_backspaces, back) ; + + return ; +} ; + +/*------------------------------------------------------------------------------ + * Use next history line, if any. + */ +static void +uty_cli_next_line(vty_io vio) +{ + uty_cli_history_use(vio, +1) ; +} + +/*------------------------------------------------------------------------------ + * Use previous history line, if any. + */ +static void +uty_cli_previous_line (vty_io vio) +{ + uty_cli_history_use(vio, -1) ; +} + +/*============================================================================== + * Command Completion and Command Description + * + */ +static void uty_cli_describe_show(vty_io vio, vector describe) ; +static void uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) ; +static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) ; + +static vector uty_cli_cmd_prepare(vty_io vio, int help) ; + +/*------------------------------------------------------------------------------ + * Command completion + */ +static void +uty_cli_complete_command (vty_io vio, enum node_type node) +{ + unsigned i ; + int ret ; + int len ; + int n ; + vector matched ; + vector vline ; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + matched = cmd_complete_command (vline, node, &ret); + cmd_free_strvec (vline); + + /* Show the result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + case CMD_COMPLETE_FULL_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + uty_cli_insert(vio, " ", 1); + break ; + + case CMD_COMPLETE_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + break ; + + case CMD_COMPLETE_LIST_MATCH: + len = 6 ; + for (i = 0; i < vector_end(matched); i++) + { + int sl = strlen((char*)vector_get_item(matched, i)) ; + if (len < sl) + len = sl ; + } ; + + n = vio->width ; + if (n == 0) + n = 60 ; + n = n / (len + 2) ; + if (n == 0) + n = 1 ; + + for (i = 0; i < vector_end(matched); i++) + { + if ((i % n) == 0) + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i)); + } + uty_cli_out_newline(vio) ; + + break; + + case CMD_COMPLETE_ALREADY: + default: + break; + } ; + + cmd_free_strvec(matched); +} ; + +/*------------------------------------------------------------------------------ + * Command Description + */ +static void +uty_cli_describe_command (vty_io vio, enum node_type node) +{ + int ret; + vector vline; + vector describe; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + describe = cmd_describe_command (vline, node, &ret); + cmd_free_strvec (vline); + + uty_cli_out_newline(vio); /* clears cli_drawn */ + + /* Deal with result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + default: + uty_cli_describe_show(vio, describe) ; + break ; + } ; + + if (describe != NULL) + vector_free (describe); +} + +/*------------------------------------------------------------------------------ + * Show the command description. + * + * Generates lines of the form: + * + * word description text + * + * Where the word field is adjusted to suit the longest word, and the + * description text is wrapped if required (if the width of the console is + * known) so that get: + * + * word description .................................. + * .............text + * + * If one of the options is '<cr>', that is always shown last. + */ +static void +uty_cli_describe_show(vty_io vio, vector describe) +{ + unsigned int i, cmd_width, desc_width; + struct desc *desc, *desc_cr ; + + /* Get width of the longest "word" */ + cmd_width = 0; + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + unsigned int len; + + if (desc->cmd[0] == '\0') + continue; + + len = strlen (desc->cmd); + if (desc->cmd[0] == '.') + len--; + + if (cmd_width < len) + cmd_width = len; + } + + /* Set width of description string. */ + desc_width = vio->width - (cmd_width + 6); + + /* Print out description. */ + desc_cr = NULL ; /* put <cr> last if it appears */ + + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + if (desc->cmd[0] == '\0') + continue; + + if (strcmp (desc->cmd, command_cr) == 0) + { + desc_cr = desc; + continue; + } + + uty_cli_describe_fold (vio, cmd_width, desc_width, desc); + } + + if (desc_cr != NULL) + uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); +} ; + +/*------------------------------------------------------------------------------ + * Show one word and the description, folding the description as required. + */ +static void +uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) +{ + char *buf; + const char *cmd, *p; + int pos; + + VTY_ASSERT_LOCKED() ; + + cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; + p = desc->str ; + + /* If have a sensible description width */ + if (desc_width > 20) + { + buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + + while (strlen (p) > desc_width) + { + /* move back to first space */ + for (pos = desc_width; pos > 0; pos--) + if (*(p + pos) == ' ') + break; + + /* if did not find a space, break at width */ + if (pos == 0) + pos = desc_width ; + + strncpy (buf, p, pos); + buf[pos] = '\0'; + uty_cli_describe_line(vio, cmd_width, cmd, buf) ; + + cmd = ""; /* for 2nd and subsequent lines */ + + p += pos ; /* step past what just wrote */ + while (*p == ' ') + ++p ; /* skip spaces */ + } ; + + XFREE (MTYPE_TMP, buf); + } ; + + uty_cli_describe_line(vio, cmd_width, cmd, p) ; +} ; + +/*------------------------------------------------------------------------------ + * Show one description line. + */ +static void +uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) +{ + if (str != NULL) + uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ; + else + uty_cli_out (vio, " %-s", cmd) ; + uty_cli_out_newline(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Prepare "vline" token array for command handler. + * + * For "help" (command completion/description), if the command line is empty, + * or ends in ' ', adds an empty token to the end of the token array. + */ +static vector +uty_cli_cmd_prepare(vty_io vio, int help) +{ + vector vline ; + + vline = cmd_make_strvec(qs_term(&vio->cl)) ; + + /* Note that if there is a vector of tokens, then there is at least one + * token, so can guarantee that vio->cl.len >= 1 ! + */ + if (help) + if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1))) + vline = cmd_add_to_strvec(vline, "") ; + + return vline ; +} ; + +/*============================================================================== + * VTY telnet stuff + */ + +#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ + +static const char* telnet_commands[256] = +{ + [tn_IAC ] = "IAC", + [tn_DONT ] = "DONT", + [tn_DO ] = "DO", + [tn_WONT ] = "WONT", + [tn_WILL ] = "WILL", + [tn_SB ] = "SB", + [tn_GA ] = "GA", + [tn_EL ] = "EL", + [tn_EC ] = "EC", + [tn_AYT ] = "AYT", + [tn_AO ] = "AO", + [tn_IP ] = "IP", + [tn_BREAK] = "BREAK", + [tn_DM ] = "DM", + [tn_NOP ] = "NOP", + [tn_SE ] = "SE", + [tn_EOR ] = "EOR", + [tn_ABORT] = "ABORT", + [tn_SUSP ] = "SUSP", + [tn_EOF ] = "EOF", +} ; + +static const char* telnet_options[256] = +{ + [to_BINARY] = "BINARY", /* 8-bit data path */ + [to_ECHO] = "ECHO", /* echo */ + [to_RCP] = "RCP", /* prepare to reconnect */ + [to_SGA] = "SGA", /* suppress go ahead */ + [to_NAMS] = "NAMS", /* approximate message size */ + [to_STATUS] = "STATUS", /* give status */ + [to_TM] = "TM", /* timing mark */ + [to_RCTE] = "RCTE", /* remote controlled tx and echo */ + [to_NAOL] = "NAOL", /* neg. about output line width */ + [to_NAOP] = "NAOP", /* neg. about output page size */ + [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ + [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ + [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ + [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ + [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ + [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ + [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ + [to_XASCII] = "XASCII", /* extended ascii character set */ + [to_LOGOUT] = "LOGOUT", /* force logout */ + [to_BM] = "BM", /* byte macro */ + [to_DET] = "DET", /* data entry terminal */ + [to_SUPDUP] = "SUPDUP", /* supdup protocol */ + [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ + [to_SNDLOC] = "SNDLOC", /* send location */ + [to_TTYPE] = "TTYPE", /* terminal type */ + [to_EOR] = "EOR", /* end or record */ + [to_TUID] = "TUID", /* TACACS user identification */ + [to_OUTMRK] = "OUTMRK", /* output marking */ + [to_TTYLOC] = "TTYLOC", /* terminal location number */ + [to_3270REGIME] = "3270REGIME", /* 3270 regime */ + [to_X3PAD] = "X3PAD", /* X.3 PAD */ + [to_NAWS] = "NAWS", /* window size */ + [to_TSPEED] = "TSPEED", /* terminal speed */ + [to_LFLOW] = "LFLOW", /* remote flow control */ + [to_LINEMODE] = "LINEMODE", /* Linemode option */ + [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ + [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ + [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ + [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ + [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ + [to_EXOPL] = "EXOPL", /* extended-options-list */ +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as decimal. + */ +static void +uty_cli_out_dec(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "%d ", (int)u) ; +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as hex. + */ +static void +uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "0x%02x ", (unsigned)u) ; +} ; + +/*------------------------------------------------------------------------------ + * Send telnet: "WILL TELOPT_ECHO" + */ +static void +uty_will_echo (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "suppress Go-Ahead" + */ +static void +uty_will_suppress_go_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use linemode" + */ +static void +uty_dont_linemode (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "Use window size" + */ +static void +uty_do_window_size (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use lflow" -- not currently used + */ +static void +uty_dont_lflow_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * The keystroke iac callback function. + * + * This deals with IAC sequences that should be dealt with as soon as they + * are read -- not stored in the keystroke stream for later processing. + */ +extern bool +uty_cli_iac_callback(keystroke_iac_callback_args) +{ + return uty_telnet_command((vty_io)context, stroke, true) ; +} ; + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * May be called during keystroke iac callback, or when processing CLI + * keystrokes. + * + * In particular: get telnet window size. + * + * Returns: true <=> dealt with, for: + * + * * telnet window size. + */ +static bool +uty_telnet_command(vty_io vio, keystroke stroke, bool callback) +{ + uint8_t* p ; + uint8_t o ; + int left ; + bool dealt_with ; + + /* Echo to the other end if required */ + if (TELNET_OPTION_DEBUG) + { + uty_cli_wipe(vio, 0) ; + + p = stroke->buf ; + left = stroke->len ; + + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_commands[*p], *p) ; + ++p ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_options[*p], *p) ; + ++p ; + + if (left > 0) + { + while(left-- > 0) + uty_cli_out_hex(vio, NULL, *p++) ; + + if (stroke->flags & kf_truncated) + uty_cli_out(vio, "... ") ; + + if (!(stroke->flags & kf_broken)) + { + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; + } + } ; + + if (stroke->flags & kf_broken) + uty_cli_out (vio, "BROKEN") ; + + uty_cli_out (vio, "\r\n") ; + } ; + + /* Process the telnet command */ + dealt_with = false ; + + if (stroke->flags != 0) + return dealt_with ; /* go no further if broken */ + + p = stroke->buf ; + left = stroke->len ; + + passert(left >= 1) ; /* must be if not broken ! */ + passert(stroke->value == *p) ; /* or something is wrong */ + + ++p ; /* step past X of IAC X */ + --left ; + + /* Decode the one command that is interesting -- "NAWS" */ + switch (stroke->value) + { + case tn_SB: + passert(left > 0) ; /* or parser failed */ + + o = *p++ ; /* the option byte */ + --left ; + switch(o) + { + case to_NAWS: + if (left != 4) + { + uzlog(NULL, LOG_WARNING, + "RFC 1073 violation detected: telnet NAWS option " + "should send %d characters, but we received %d", + (3 + 4 + 2), (3 + left + 2)) ; + } + else + { + vio->width = *p++ << 8 ; + vio->width += *p++ ; + vio->height = *p++ << 8 ; + vio->height += *p ; + + if (TELNET_OPTION_DEBUG) + uty_cli_out(vio, "TELNET NAWS window size received: " + "width %d, height %d%s", + vio->width, vio->height, telnet_newline) ; + uty_set_height(vio) ; + + dealt_with = true ; + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; + + return dealt_with ; +} ; diff --git a/lib/workqueue.h b/lib/workqueue.h index 9ff7cdb5..34e68cd8 100644 --- a/lib/workqueue.h +++ b/lib/workqueue.h @@ -24,9 +24,7 @@ #ifndef _QUAGGA_WORK_QUEUE_H #define _QUAGGA_WORK_QUEUE_H -#ifndef Inline -#define Inline static inline -#endif +#include "misc.h" /* Hold time for the initial schedule of a queue run, in millisec */ #define WORK_QUEUE_DEFAULT_HOLD 50 diff --git a/lib/zassert.h b/lib/zassert.h index 8ca2203f..9f2465f7 100644 --- a/lib/zassert.h +++ b/lib/zassert.h @@ -33,7 +33,7 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file, #define zassert(EX) ((void)((EX) ? 0 : \ (_zlog_assert_failed(#EX, __FILE__, __LINE__, \ - __ASSERT_FUNCTION), 0))) + __ASSERT_FUNCTION), 0))) /* Implicitly *permanent* assert() -- irrespective of NDEBUG */ #undef assert diff --git a/tests/test-list_util.c b/tests/test-list_util.c index fc81a562..a3c6ad59 100644 --- a/tests/test-list_util.c +++ b/tests/test-list_util.c @@ -20,10 +20,10 @@ static void test_ddl(void); } while (0) static void -test_assert_fail(const char* true, const char* message, const char* func, +test_assert_fail(const char* truth, const char* message, const char* func, int line) { - printf("*** %s %d: (%s) not true: %s\n", func, line, true, message) ; + printf("*** %s %d: (%s) not true: %s\n", func, line, truth, message) ; } ; diff --git a/tests/test-symtab.c b/tests/test-symtab.c index ed83e607..a4b0bcf2 100644 --- a/tests/test-symtab.c +++ b/tests/test-symtab.c @@ -56,24 +56,25 @@ test_symbol_table_init_new(void) assert_true(table != NULL, "table == NULL"); /* expect to not find */ - sym = symbol_lookup(table, name, 0); + sym = symbol_lookup(table, name, no_add); assert_true(sym == NULL, "sym != NULL"); /* add */ - sym = symbol_lookup(table, name, 1); + sym = symbol_lookup(table, name, add); symbol_set_value(sym, value); assert_true(sym != NULL, "sym == NULL"); - assert_true(strcmp(symbol_get_name(sym), name) == 0, "strcmp(symbol_get_name(sym), name) != 0"); + assert_true(strcmp(symbol_get_name(sym), name) == 0, + "strcmp(symbol_get_name(sym), name) != 0"); /* find */ - sym2 = symbol_lookup(table, name, 0); + sym2 = symbol_lookup(table, name, no_add); assert_true(sym == sym2, "sym != sym2"); assert_true(symbol_get_value(sym) == value, "symbol_get_value(sym) != value"); old_value = symbol_delete(sym); assert_true(value == old_value, "value != old_value"); - while ((old_value = symbol_table_ream(table, 1)) != NULL) + while ((old_value = symbol_table_ream(table, keep_it)) != NULL) { } @@ -98,7 +99,7 @@ test_symbol_table_lookup(void) for (i = 0; i < len; ++i) { sprintf(buf, "%d-name", i); - sym = symbol_lookup(table, buf, 1); + sym = symbol_lookup(table, buf, add); assert_true(sym != NULL, "add: sym == NULL"); assert_true(strcmp(symbol_get_name(sym), buf) == 0, "strcmp(symbol_get_name(sym), buf) != 0"); @@ -114,7 +115,7 @@ test_symbol_table_lookup(void) for (i = 0; i < len; ++i) { sprintf(buf, "%d-name", i); - sym = symbol_lookup(table, buf, 0); + sym = symbol_lookup(table, buf, no_add); assert_true(sym != NULL, "find: sym == NULL"); assert_true(strcmp(symbol_get_name(sym), buf) == 0, "strcmp(symbol_get_name(sym), buf) != 0"); @@ -162,12 +163,12 @@ test_call_back(void) /* add */ symbol_table_set_value_call_back(table, call_back_function_set); - sym = symbol_lookup(table, name, 1); + sym = symbol_lookup(table, name, add); symbol_set_value(sym, value); /* change */ symbol_table_set_value_call_back(table, call_back_function_change); - sym = symbol_lookup(table, name, 1); + sym = symbol_lookup(table, name, add); symbol_set_value(sym, new_value); /* delete */ @@ -216,7 +217,7 @@ test_ref(void) table = symbol_table_init_new(table, NULL, 0, 0, NULL, NULL); /* add */ - sym = symbol_lookup(table, name, 1); + sym = symbol_lookup(table, name, add); symbol_set_value(sym, value); /* create references, in reverse order so that walk in order */ @@ -272,7 +273,7 @@ test_ref_heavy(void) table = symbol_table_init_new(table, NULL, 0, 0, NULL, NULL); /* add */ - sym = symbol_lookup(table, name, 1); + sym = symbol_lookup(table, name, add); symbol_set_value(sym, value); /* create references, in reverse order so that walk in order */ diff --git a/tests/test-vector.c b/tests/test-vector.c index b54ae9f9..a73e5637 100644 --- a/tests/test-vector.c +++ b/tests/test-vector.c @@ -424,12 +424,12 @@ void do_test_insert(const int rider) { vector v = NULL; - const vector_index len = 100; - const vector_index ins = 50; - vector_index i; + const vector_length_t len = 100; + const vector_index_t ins = 50; + vector_index_t i; char buf[10]; - vector_index check_end = len + 1; - vector_index check_ins = ins; + vector_length_t check_end = len + 1; + vector_index_t check_ins = ins; int check_shift = 1; switch(rider) @@ -563,9 +563,9 @@ test_vector_bsearch(void) const int len = 2000; char buf[20]; char target[20]; - vector_index target_index = 0; + vector_index_t target_index = 0; int result; - vector_index index; + vector_index_t index; printf("test_vector_bsearch\n"); @@ -614,14 +614,14 @@ void do_test_move_item_here(const int rider) { vector v = NULL; - const vector_index len = 100; - const vector_index ins = 50; - const vector_index src = 70; - vector_index i; + const vector_length_t len = 100; + const vector_index_t ins = 50; + const vector_index_t src = 70; + vector_index_t i; char buf[10]; - vector_index check_dest = 0; - vector_index check_src = 0; - vector_index check_end = 0; + vector_index_t check_dest = 0; + vector_index_t check_src = 0; + vector_index_t check_end = 0; int check_shift = 0; p_vector_item dest_item = NULL; @@ -711,10 +711,10 @@ void test_vector_part_reverse(void) { vector v = NULL; - const vector_index len = 100; - const vector_index rstart = 50; - const vector_index rstop = 70; - vector_index i; + const vector_length_t len = 100; + const vector_index_t rstart = 50; + const vector_index_t rstop = 70; + vector_index_t i; char buf[10]; printf("test_vector_part_reverse\n"); @@ -770,8 +770,8 @@ test_vector_copy_here(void) { vector v1 = NULL; vector v2 = NULL; - vector_index i; - const vector_index len = 100; + vector_index_t i; + const vector_length_t len = 100; char buf[10]; printf("test_vector_copy_here\n"); @@ -809,8 +809,8 @@ test_vector_move_here(void) { vector v1 = NULL; vector v2 = NULL; - vector_index i; - const vector_index len = 100; + vector_index_t i; + const vector_length_t len = 100; char buf[10]; printf("test_vector_move_here\n"); @@ -851,8 +851,8 @@ test_vector_copy_append(void) { vector v1 = NULL; vector v2 = NULL; - vector_index i; - const vector_index len = 100; + vector_index_t i; + const vector_length_t len = 100; char buf[10]; printf("test_vector_copy_append\n"); @@ -900,8 +900,8 @@ test_vector_move_append(void) { vector v1 = NULL; vector v2 = NULL; - vector_index i; - const vector_index len = 100; + vector_index_t i; + const vector_length_t len = 100; char buf[10]; printf("test_vector_move_append\n"); @@ -949,10 +949,10 @@ void test_vector_insert(void) { vector v = NULL; - vector_index i; - const vector_index len = 100; - const vector_index istart = 50; - const vector_index istop = 70; + vector_index_t i; + const vector_length_t len = 100; + const vector_index_t istart = 50; + const vector_index_t istop = 70; char buf[10]; printf("test_vector_insert\n"); @@ -1004,10 +1004,10 @@ void test_vector_delete(void) { vector v = NULL; - vector_index i; - const vector_index len = 100; - const vector_index dstart = 50; - const vector_index dstop = 70; + vector_index_t i; + const vector_length_t len = 100; + const vector_index_t dstart = 50; + const vector_index_t dstop = 70; char buf[10]; printf("test_vector_delete\n"); @@ -1060,9 +1060,9 @@ void test_vector_discard(void) { vector v = NULL; - vector_index i; - const vector_index len = 100; - const vector_index dstart = 50; + vector_index_t i; + const vector_length_t len = 100; + const vector_index_t dstart = 50; char buf[10]; printf("test_vector_discard\n"); @@ -1110,12 +1110,12 @@ test_vector_sak(void) vector v1 = NULL; vector v2 = NULL; vector v3 = NULL; - vector_index i; - const vector_index len = 100; - const vector_index sstart = 60; - const vector_index sstop = 70; - const vector_index dstart = 40; - const vector_index dstop = 50; + vector_index_t i; + const vector_length_t len = 100; + const vector_index_t sstart = 60; + const vector_index_t sstop = 70; + const vector_index_t dstart = 40; + const vector_index_t dstop = 50; char buf[10]; printf("test_vector_sak\n"); @@ -1132,7 +1132,7 @@ test_vector_sak(void) } v1 = vector_sak(1, v1, v2, dstart, dstop - dstart, - v3, sstart, sstop - sstart, 0); + v3, sstart, sstop - sstart, 0); assert_true(v1 != NULL, "v1 == NULL"); assert_true(vector_end(v1) == (dstop - dstart), diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 64c1b549..3692a953 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -31,13 +31,14 @@ #include <readline/history.h> #include "command.h" +#include "command_execute.h" #include "memory.h" #include "vtysh/vtysh.h" #include "log.h" #include "bgpd/bgp_vty.h" /* Struct VTY. */ -struct vty *vty; +static struct vty* vtysh_vty; /* VTY shell pager name. */ char *vtysh_pager_name = NULL; @@ -51,13 +52,13 @@ struct vtysh_client const char *path; } vtysh_client[] = { - { .fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .path = ZEBRA_VTYSH_PATH}, - { .fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .path = RIP_VTYSH_PATH}, + { .fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .path = ZEBRA_VTYSH_PATH}, + { .fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .path = RIP_VTYSH_PATH}, { .fd = -1, .name = "ripngd", .flag = VTYSH_RIPNGD, .path = RIPNG_VTYSH_PATH}, - { .fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .path = OSPF_VTYSH_PATH}, + { .fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .path = OSPF_VTYSH_PATH}, { .fd = -1, .name = "ospf6d", .flag = VTYSH_OSPF6D, .path = OSPF6_VTYSH_PATH}, - { .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH}, - { .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH}, + { .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH}, + { .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH}, }; #define VTYSH_INDEX_MAX (sizeof(vtysh_client)/sizeof(vtysh_client[0])) @@ -250,7 +251,7 @@ vtysh_client_execute (struct vtysh_client *vclient, const char *line, FILE *fp) } } -void +static void vtysh_exit_ripd_only (void) { if (ripd_client) @@ -284,27 +285,27 @@ vtysh_execute_func (const char *line, int pager) int saved_ret, saved_node; /* TODO: how well does vtysh_execute_func work ?? -- esp. qpthreads_enabled */ - vty->buf = line ; + vtysh_vty->buf = line ; - saved_ret = ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); + saved_ret = ret = cmd_execute_command (vtysh_vty, cmd_parse_completion, &cmd); if ((ret == CMD_SUCCESS) && (cmd == NULL)) return ret ; /* quit if nothing to do ??? */ - saved_node = vty->node; + saved_node = vtysh_vty->node; /* If command doesn't succeeded in current node, try to walk up in node tree. - * Changing vty->node is enough to try it just out without actual walkup in - * the vtysh. */ + * Changing vtysh_vty->node is enough to try it just out without actual + * walkup in the vtysh. */ while (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING - && vty->node > CONFIG_NODE) + && vtysh_vty->node > CONFIG_NODE) { - vty->node = node_parent(vty->node); - ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); + vtysh_vty->node = node_parent(vtysh_vty->node); + ret = cmd_execute_command (vtysh_vty, cmd_parse_completion, &cmd); tried++; } - vty->node = saved_node; + vtysh_vty->node = saved_node; /* If command succeeded in any other node than current (tried > 0) we have * to move into node in the vtysh where it succeeded. */ @@ -338,7 +339,7 @@ vtysh_execute_func (const char *line, int pager) switch (ret) { case CMD_WARNING: - if (vty->type == VTY_FILE) + if (vtysh_vty->type == VTY_FILE) fprintf (stdout,"Warning...\n"); break; case CMD_ERR_AMBIGUOUS: @@ -382,16 +383,16 @@ vtysh_execute_func (const char *line, int pager) { line = "end"; - vty->buf = line ; + vtysh_vty->buf = line ; - ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); + ret = cmd_execute_command (vtysh_vty, cmd_parse_completion, &cmd); if (ret != CMD_SUCCESS_DAEMON) break; } else if (cmd->func) { - (*cmd->func) (cmd, vty, 0, NULL); + (*cmd->func) (cmd, vtysh_vty, 0, NULL); break; } } @@ -410,7 +411,7 @@ vtysh_execute_func (const char *line, int pager) break; if (cmd->func) - (*cmd->func) (cmd, vty, 0, NULL); + (*cmd->func) (cmd, vtysh_vty, 0, NULL); } } if (pager && vtysh_pager_name && fp && closepager) @@ -524,7 +525,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) } /* We don't care about the point of the cursor when '?' is typed. */ -int +static int vtysh_rl_describe (void) { int ret; @@ -546,7 +547,7 @@ vtysh_rl_describe (void) if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1])) vector_set (vline, '\0'); - describe = cmd_describe_command (vline, vty->node, &ret); + describe = cmd_describe_command (vline, vtysh_vty->node, &ret); fprintf (stdout,"\n"); @@ -626,7 +627,7 @@ command_generator (const char *text, int state) { index = 0; - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) + if (vtysh_vty->node == AUTH_NODE || vtysh_vty->node == AUTH_ENABLE_NODE) return NULL; vline = cmd_make_strvec (rl_line_buffer); @@ -636,7 +637,7 @@ command_generator (const char *text, int state) if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1])) vector_set (vline, '\0'); - matched = cmd_complete_command (vline, vty->node, &complete_status); + matched = cmd_complete_command (vline, vtysh_vty->node, &complete_status); } if (matched && matched[index]) @@ -671,7 +672,7 @@ vtysh_completion (char *text, int start, int end) vector vline; char **matched = NULL; - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) + if (vtysh_vty->node == AUTH_NODE || vtysh_vty->node == AUTH_ENABLE_NODE) return NULL; vline = cmd_make_strvec (rl_line_buffer); @@ -682,7 +683,7 @@ vtysh_completion (char *text, int start, int end) if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1])) vector_set (vline, '\0'); - matched = cmd_complete_command (vline, vty, &ret); + matched = cmd_complete_command (vline, vtysh_vty, &ret); cmd_free_strvec (vline); @@ -791,17 +792,17 @@ static struct cmd_node keychain_key_node = extern struct cmd_node vty_node; /* When '^Z' is received from vty, move down to the enable mode. */ -int +static int vtysh_end (void) { - switch (vty->node) + switch (vtysh_vty->node) { case VIEW_NODE: case ENABLE_NODE: /* Nothing to do. */ break; default: - vty->node = ENABLE_NODE; + vtysh_vty->node = ENABLE_NODE; break; } return CMD_SUCCESS; @@ -2196,17 +2197,17 @@ vtysh_prompt (void) hostname = names.nodename; } - snprintf (buf, sizeof buf, cmd_prompt (vty->node), hostname); + snprintf (buf, sizeof buf, cmd_prompt (vtysh_vty->node), hostname); return buf; } -void +struct vty* vtysh_init_vty (void) { - /* Make vty structure. */ - vty = vty_open(VTY_SHELL); - vty->node = VIEW_NODE; + /* Make vtysh_vty structure. */ + vtysh_vty = vty_open(VTY_SHELL); + vtysh_vty->node = VIEW_NODE; /* Initialize commands. */ cmd_init (0); @@ -2437,4 +2438,5 @@ vtysh_init_vty (void) install_element (CONFIG_NODE, &vtysh_enable_password_text_cmd); install_element (CONFIG_NODE, &no_vtysh_enable_password_cmd); + return vtysh_vty ; } diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index e711d593..820e5506 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -36,7 +36,7 @@ /* vtysh local configuration file. */ #define VTYSH_DEFAULT_CONFIG "vtysh.conf" -void vtysh_init_vty (void); +struct vty* vtysh_init_vty (void); void vtysh_init_cmd (void); extern int vtysh_connect_all (const char *optional_daemon_name); void vtysh_readline_init (void); @@ -64,6 +64,4 @@ void vtysh_pager_init (void); /* Child process execution flag. */ extern int execute_flag; -extern struct vty *vty; - #endif /* VTYSH_H */ diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index 15938f43..dfdc020a 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -47,19 +47,19 @@ struct config struct list *config_top; -int -line_cmp (char *c1, char *c2) +static int +line_cmp (const char *c1, const char *c2) { return strcmp (c1, c2); } -void +static void line_del (char *line) { XFREE (MTYPE_VTYSH_CONFIG_LINE, line); } -struct config * +static struct config * config_new () { struct config *config; @@ -67,13 +67,13 @@ config_new () return config; } -int +static int config_cmp (struct config *c1, struct config *c2) { return strcmp (c1->name, c2->name); } -void +static void config_del (struct config* config) { list_delete (config->line); @@ -82,7 +82,7 @@ config_del (struct config* config) XFREE (MTYPE_VTYSH_CONFIG, config); } -struct config * +static struct config * config_get (int index, const char *line) { struct config *config; @@ -121,13 +121,13 @@ config_get (int index, const char *line) return config; } -void +static void config_add_line (struct list *config, const char *line) { listnode_add (config, XSTRDUP (MTYPE_VTYSH_CONFIG_LINE, line)); } -void +static void config_add_line_uniq (struct list *config, const char *line) { struct listnode *node, *nnode; @@ -141,7 +141,7 @@ config_add_line_uniq (struct list *config, const char *line) listnode_add_sort (config, XSTRDUP (MTYPE_VTYSH_CONFIG_LINE, line)); } -void +static void vtysh_config_parse_line (const char *line) { char c; @@ -365,7 +365,7 @@ vtysh_read_file (FILE *confp) vtysh_execute_no_pager ("end"); vtysh_execute_no_pager ("disable"); - vty_close (vty); + vty_close_final(vty); if (ret != CMD_SUCCESS) { diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c index 4a315a5c..c576d3e0 100644 --- a/vtysh/vtysh_main.c +++ b/vtysh/vtysh_main.c @@ -63,7 +63,7 @@ struct thread_master *master; FILE *logfile; /* SIGTSTP handler. This function care user's ^Z input. */ -void +static void sigtstp (int sig) { /* Execute "end" command. */ @@ -84,7 +84,7 @@ sigtstp (int sig) } /* SIGINT handler. This function care user's ^Z input. */ -void +static void sigint (int sig) { /* Check this process is not child process. */ @@ -98,7 +98,7 @@ sigint (int sig) /* Signale wrapper for vtysh. We don't use sigevent because * vtysh doesn't use threads. TODO */ -RETSIGTYPE * +static RETSIGTYPE * vtysh_signal_set (int signo, void (*func)(int)) { int ret; @@ -121,7 +121,7 @@ vtysh_signal_set (int signo, void (*func)(int)) } /* Initialization of signal handles. */ -void +static void vtysh_signal_init () { vtysh_signal_set (SIGINT, sigint); @@ -168,7 +168,7 @@ struct option longopts[] = }; /* Read a string, and return a pointer to it. Returns NULL on EOF. */ -char * +static char * vtysh_rl_gets () { HIST_ENTRY *last; @@ -202,7 +202,7 @@ static void log_it(const char *line) { time_t t = time(NULL); struct tm *tmp = localtime(&t); - char *user = getenv("USER") ? : "boot"; + const char *user = getenv("USER") ? : "boot"; char tod[64]; strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp); @@ -214,6 +214,7 @@ static void log_it(const char *line) int main (int argc, char **argv, char **env) { + struct vty* vty ; char *p; int opt; int dryrun = 0; @@ -292,7 +293,7 @@ main (int argc, char **argv, char **env) vtysh_signal_init (); /* Make vty structure and register commands. */ - vtysh_init_vty (); + vty = vtysh_init_vty (); vtysh_init_cmd (); vtysh_user_init (); vtysh_config_init (); diff --git a/vtysh/vtysh_user.c b/vtysh/vtysh_user.c index 58676c10..5dd50bb7 100644 --- a/vtysh/vtysh_user.c +++ b/vtysh/vtysh_user.c @@ -21,6 +21,7 @@ #include <zebra.h> #include <lib/version.h> +#include <vtysh_user.h> #include <pwd.h> @@ -98,19 +99,21 @@ struct vtysh_user struct list *userlist; -struct vtysh_user * +static struct vtysh_user * user_new () { return XCALLOC (0, sizeof (struct vtysh_user)); } -void +static void user_free (struct vtysh_user *user) __attribute__((unused)) ; + +static void user_free (struct vtysh_user *user) { XFREE (0, user); } -struct vtysh_user * +static struct vtysh_user * user_lookup (const char *name) { struct listnode *node, *nnode; @@ -124,8 +127,10 @@ user_lookup (const char *name) return NULL; } -void -user_config_write () +static void user_config_write (void) __attribute__((unused)) ; + +static void +user_config_write (void) { struct listnode *node, *nnode; struct vtysh_user *user; @@ -137,7 +142,7 @@ user_config_write () } } -struct vtysh_user * +static struct vtysh_user * user_get (const char *name) { struct vtysh_user *user; @@ -165,8 +170,8 @@ DEFUN (username_nopassword, return CMD_SUCCESS; } -int -vtysh_auth () +extern int +vtysh_auth(void) { struct vtysh_user *user; struct passwd *passwd; @@ -186,8 +191,8 @@ vtysh_auth () return 0; } -void -vtysh_user_init () +extern void +vtysh_user_init(void) { userlist = list_new (); install_element (CONFIG_NODE, &username_nopassword_cmd); diff --git a/vtysh/vtysh_user.h b/vtysh/vtysh_user.h index 8d0a4cf6..c02cfede 100644 --- a/vtysh/vtysh_user.h +++ b/vtysh/vtysh_user.h @@ -22,6 +22,7 @@ #ifndef _VTYSH_USER_H #define _VTYSH_USER_H -int vtysh_auth (); +extern int vtysh_auth(void) ; +extern void vtysh_user_init(void) ; #endif /* _VTYSH_USER_H */ |