diff options
author | Chris Hall <GMCH@hestia.halldom.com> | 2010-03-16 01:35:19 +0000 |
---|---|---|
committer | Chris Hall <GMCH@hestia.halldom.com> | 2010-03-16 01:35:19 +0000 |
commit | d87a9d74eab06082ea49313083ffa0aa41f666f9 (patch) | |
tree | 7c6f7ae0be39683b7c90ea298454ec28d49406cb | |
parent | 05fb7fd0421b395c089bb08dd0e0d78d3746b8cf (diff) | |
download | quagga-d87a9d74eab06082ea49313083ffa0aa41f666f9.tar.bz2 quagga-d87a9d74eab06082ea49313083ffa0aa41f666f9.tar.xz |
Major update
bgpd/bgp_advertise.c
bgpd/bgp_advertise.h
The adj_in and adj_out objects are now put on a list based
on the peer to whom the route belongs.
The adj_in and adj_out objects also now point to the bgp_node
which they are routes for.
This substantially reduces the work needed to shut down a peer.
bgpd/bgp_damp.c
Changes to adj_in and adj_out forced small change to macros
used in bgp_damp.c to manage its lists.
bgpd/bgp_debug.c
Replaced direct access to vty->node by the required vty_get_node().
bgpd/bgp_dump.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_engine.h
Modified the debug and trace functions.
bgpd/bgp_fsm.c
Make use of sockunion2str() consistent with common usage.
Improved some documentation.
bgpd/bgp_main.c
Use the newly extended qpn_add_hook_function() facility.
bgpd/bgp_mplsvpn.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_msg_read.c
Bug fix: correct handling of capability code length.
Improvement: better casting in calculation of message length.
bgpd/bgp_msg_write.c
Bug fix: correct byte ordering of bgp_id in open message.
bgpd/bgp_network.c
Bug fix: correct handling of incoming connections.
Takes advantage of improvements in sockunion.c.
bgpd/bgp_nexthop.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_open_state.c
Remove mistaken #include of memtypes.h
bgpd/bgp_packet.c
Improvements to handling of withdrawing routes for peers.
bgpd/bgp_peer.c
Tidying up the state of peers as they are enabled and disabled.
Improvements to handling of withdrawing routes for peers.
bgpd/bgp_peer.h
Adding list bases for lists of routes originated by the peer.
bgpd/bgp_peer_index.c
Bug fix: correct freeing of peer indexes.
bgpd/bgp_route.c
Implement lists of bgp_info based in the owning peer. Adjust
for name changes to bgp_info fields.
Reimplemented all the clearing functions to use the lists of
items that belong to the peer -- rather than searching route
tables for stuff to withdraw.
Changed work queue handling for added/changed routes, so
that queues run through existing items, rather than having
queues of auxiliary items -- lower memory overhead.
bgpd/bgp_route.h
Added fields to bgp_info to allow all bgp_info originated by
each peer to live on lists based in the peer. And changed the
name of existing fields to avoid confusion.
bgpd/bgp_routemap.c
Removing redundant code and fixing a memory leak.
bgpd/bgp_table.h
Based work queue for added/changed routes directly in the
table, rather than having auxiliary structures.
bgpd/bgp_vty.c
Use vty_get_node() and vty_set_node() rather than direct
access to the vty field.
bgpd/bgpd.c
Implement changes to route clearing.
bgpd/bgpd.h
Changes to work queue handling.
lib/buffer.c
Changes to allow embedded buffer structures.
lib/buffer.h
Moved struct buffer here so that could have embedded buffer
structurs.
lib/command.c
Substantial tidy up and document exercise.
Restructured the top level command processing and finding
of descriptions and command completion.
Removal of unpleasant messing around with the insides of
vector structures.
Movement of some command actions to vty.c.
Uses uty.h to pick up the "private" functions from vty.c
et al.
lib/command.h
Moved the "node" values to node_type.h, so that can use an
enum node_type in places where cannot include command.h.
lib/command_queue.c
Updated to cope with the called command changing the node
value.
Improved handling of revoked commands, so the the command
line handler does not get stuck waiting for a command to
complete which has been revoked !
lib/command_queue.h
Improved message format.
lib/if.c
Use vty_set_node().
lib/keychain.c
Use vty_set_node().
new lib/keystroke.c
new lib/keystroke.h
New code to implement a keystroke FIFO.
This moves some complexity out of the command handler.
The handling of mixtures of escapes and Telnet IACs is
tightened up.
It would be possible to extend this to, say, UTF-8.
Regularises the "stealing" of keystrokes for the "--more--"
output handling... which was a bit hit and miss.
new lib/list_util.c
new lib/list_util.h
New code to implement various forms of linked list,
where the list pointers are embedded in structures.
lib/log.c
Changed the handling of log messages, so that all types
of log output (except syslog) use the same message buffer
scheme, and the message is constructed once and once
only.
Changes to the handling of VTY_LOCK() etc.
Uses uty.h to pick up the "private" functions from vty.c
et al.
lib/log.h
Changes to the buffering of log messages.
new lib/mem_tracker.c
New code to track memory allocation/deallocation, for
debug purposes.
lib/memory.c
lib/memory.h
Updated to allow the use of the mem_tracker.
lib/memtypes.awk
Made the memtypes into a named enum MTYPE.
lib/memtypes.c
Various new memory types.
lib/mqueue.c
lib/mqueue.h
Add mqueue_finish function for close-down.
lib/network.c
lib/network.h
Added non-blocking read_nb() and write_nb().
new lib/node_type.h
As above.
lib/plist.c
Remove vty_puts() which wasn't a good idea.
lib/qlib_init.c
Added qps_init() to first stage and mqueue_finish to
finish.
lib/qpnexus.c
lib/qpnexus.h
More flexible hooks for in_thread_init and in_thread_final.
lib/qpselect.c
lib/qpselect.h
Added qps_start_up() to build the required maps once and
for all.
Added qdebug to control the debug checks and validation.
Improved validation and test functions.
new lib/qstring.c
new lib/qstring.h
New code for limited flexible string handling.
lib/qtimers.c
Added qdebug to control the debug checks and validation.
lib/routemap.c
Use vty_set_node().
lib/sockunion.c
lib/sockunion.h
Tidied up and regularised the handling of sin_len and
sin6_len.
Created common function for setting port into socket.
Created common function for initialisation/allocation of
new sockunion.
Reduced various functions by using common sub-functions.
Rationalised some code.
Added sockunion_listen() and sockunion_new_sockaddr().
Renamed sockunion_new() to sockunion_new_prefix().
Improved some logging messages.
Added documentation.
new lib/uty.h
Functions etc. used only by vty/command/log/vty_io and
vty_cli.
lib/vector.c
lib/vector.h
Added vector_t type.
Removed VECTOR_INDEX, vector_only_wrapper_free() and
vector_only_index_free() -- following improvement of
code in command.c.
Added vector_set_min_length(), vector_set_new_min_length()
and vector_length() functions.
new lib/vio_fifo.c
new lib/vio_fifo.h
New code to manage simple FIFO of indefinite length.
lib/vty.c
lib/vty.h
Reworked. Broken into vty.c, vty_io.c and vty_cli.c.
new lib/vty_cli.c
new lib/vty_cli.h
CLI handling parts of the vty family.
new lib/vty_io.c
new lib/vty_io.h
I/O parts of the vty family.
lib/workqueue.h
Introduced tyedefs for the various call-back entries.
new tests/test-list_util.c
Tests for the list-util stuff.
vtysh/vtysh.c
Small change to interface for cmd_execute_command()
78 files changed, 20595 insertions, 5382 deletions
diff --git a/bgpd/bgp_advertise.c b/bgpd/bgp_advertise.c index 04f1f847..552b2291 100644 --- a/bgpd/bgp_advertise.c +++ b/bgpd/bgp_advertise.c @@ -153,12 +153,6 @@ bgp_advertise_unintern (struct hash *hash, struct bgp_advertise_attr *baa) } /* BGP adjacency keeps minimal advertisement information. */ -static void -bgp_adj_out_free (struct bgp_adj_out *adj) -{ - peer_unlock (adj->peer); /* adj_out peer reference */ - XFREE (MTYPE_BGP_ADJ_OUT, adj); -} int bgp_adj_out_lookup (struct peer *peer, struct prefix *p, @@ -166,7 +160,7 @@ bgp_adj_out_lookup (struct peer *peer, struct prefix *p, { struct bgp_adj_out *adj; - for (adj = rn->adj_out; adj; adj = adj->next) + for (adj = rn->adj_out; adj; adj = adj->adj_next) if (adj->peer == peer) break; @@ -217,31 +211,45 @@ bgp_adj_out_set (struct bgp_node *rn, struct peer *peer, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi, struct bgp_info *binfo) { - struct bgp_adj_out *adj = NULL; + struct bgp_adj_out* adj = NULL; + struct bgp_adj_out** adj_out_head ; struct bgp_advertise *adv; if (DISABLE_BGP_ANNOUNCE) return; + assert(rn != NULL) ; + assert((afi == rn->table->afi) && (safi == rn->table->safi)) ; + /* Look for adjacency information. */ - if (rn) - { - for (adj = rn->adj_out; adj; adj = adj->next) - if (adj->peer == peer) + for (adj = rn->adj_out; adj; adj = adj->adj_next) + if (adj->peer == peer) break; - } - if (! adj) + if (adj == NULL) { adj = XCALLOC (MTYPE_BGP_ADJ_OUT, sizeof (struct bgp_adj_out)); - adj->peer = peer_lock (peer); /* adj_out peer reference */ - if (rn) - { - BGP_ADJ_OUT_ADD (rn, adj); - bgp_lock_node (rn); - } - } + /* Add to list of adj_out stuff for the peer */ + adj->peer = peer_lock (peer); + + adj_out_head = &(peer->adj_out_head[afi][safi]) ; + + adj->route_next = *adj_out_head ; + adj->route_prev = NULL ; + if (*adj_out_head != NULL) + (*adj_out_head)->route_prev = adj ; + *adj_out_head = adj ; + + /* Add to list of adj out stuff for the bgp_node */ + adj->rn = bgp_lock_node (rn); + + adj->adj_next = rn->adj_out ; + adj->adj_prev = NULL ; + if (rn->adj_out != NULL) + rn->adj_out->adj_prev = adj ; + rn->adj_out = adj ; + } ; if (adj->adv) bgp_advertise_clean (peer, adj, afi, safi); @@ -277,63 +285,80 @@ bgp_adj_out_unset (struct bgp_node *rn, struct peer *peer, struct prefix *p, return; /* Lookup existing adjacency, if it is not there return immediately. */ - for (adj = rn->adj_out; adj; adj = adj->next) + for (adj = rn->adj_out; adj; adj = adj->adj_next) if (adj->peer == peer) break; - if (! adj) + if (adj == NULL) return; - /* Clearn up previous advertisement. */ + assert(rn == adj->rn) ; + + /* Clear up previous advertisement. */ if (adj->adv) bgp_advertise_clean (peer, adj, afi, safi); if (adj->attr) { - /* We need advertisement structure. */ + /* We need advertisement structure. */ adj->adv = bgp_advertise_new (); adv = adj->adv; adv->rn = rn; adv->adj = adj; - /* Add to synchronization entry for withdraw announcement. */ + /* Add to synchronization entry for withdraw announcement */ bgp_advertise_fifo_add(&peer->sync[afi][safi]->withdraw, adv); /* Schedule packet write. */ bgp_write(peer, NULL) ; } else - { - /* Remove myself from adjacency. */ - BGP_ADJ_OUT_DEL (rn, adj); - - /* Free allocated information. */ - bgp_adj_out_free (adj); - - bgp_unlock_node (rn); - } + bgp_adj_out_remove(rn, adj, peer, afi, safi) ; } void bgp_adj_out_remove (struct bgp_node *rn, struct bgp_adj_out *adj, struct peer *peer, afi_t afi, safi_t safi) { + assert((rn == adj->rn) && (peer == adj->peer)) ; + if (adj->attr) bgp_attr_unintern (adj->attr); if (adj->adv) bgp_advertise_clean (peer, adj, afi, safi); - BGP_ADJ_OUT_DEL (rn, adj); - bgp_adj_out_free (adj); + /* Unhook from peer */ + if (adj->route_next != NULL) + adj->route_next->route_prev = adj->route_prev ; + if (adj->route_prev != NULL) + adj->route_prev->route_next = adj->route_next ; + else + peer->adj_out_head[afi][safi] = adj->route_next ; + + peer_unlock (peer); + + /* Unhook from bgp_node */ + if (adj->adj_next) + adj->adj_next->adj_prev = adj->adj_prev; + if (adj->adj_prev) + adj->adj_prev->adj_next = adj->adj_next; + else + rn->adj_out = adj->adj_next; + + bgp_unlock_node (rn); + + /* now can release memory. */ + XFREE (MTYPE_BGP_ADJ_OUT, adj); } void bgp_adj_in_set (struct bgp_node *rn, struct peer *peer, struct attr *attr) { struct bgp_adj_in *adj; + struct bgp_adj_in** adj_in_head ; - for (adj = rn->adj_in; adj; adj = adj->next) + for (adj = rn->adj_in; adj; adj = adj->adj_next) { if (adj->peer == peer) { @@ -345,19 +370,69 @@ bgp_adj_in_set (struct bgp_node *rn, struct peer *peer, struct attr *attr) return; } } + + /* Need to create a brand new bgp_adj_in */ + adj = XCALLOC (MTYPE_BGP_ADJ_IN, sizeof (struct bgp_adj_in)); - adj->peer = peer_lock (peer); /* adj_in peer reference */ + + /* Set the interned attributes */ adj->attr = bgp_attr_intern (attr); - BGP_ADJ_IN_ADD (rn, adj); - bgp_lock_node (rn); + + /* Add to list of adj in stuff for the peer */ + adj->peer = peer_lock (peer); + + adj_in_head = &(peer->adj_in_head[rn->table->afi][rn->table->safi]) ; + + adj->route_next = *adj_in_head ; + adj->route_prev = NULL ; + if (*adj_in_head != NULL) + (*adj_in_head)->route_prev = adj ; + *adj_in_head = adj ; + + /* Add to list of adj in stuff for the bgp_node */ + adj->rn = bgp_lock_node (rn); + + adj->adj_next = rn->adj_in ; + adj->adj_prev = NULL ; + if (rn->adj_in != NULL) + rn->adj_in->adj_prev = adj ; + rn->adj_in = adj ; } void bgp_adj_in_remove (struct bgp_node *rn, struct bgp_adj_in *bai) { + bgp_peer peer = bai->peer ; + struct bgp_adj_in** adj_in_head ; + + adj_in_head = &(peer->adj_in_head[rn->table->afi][rn->table->safi]) ; + + assert(rn == bai->rn) ; + + /* Done with this copy of attributes */ bgp_attr_unintern (bai->attr); - BGP_ADJ_IN_DEL (rn, bai); - peer_unlock (bai->peer); /* adj_in peer reference */ + + /* Unhook from peer */ + if (bai->route_next != NULL) + bai->route_next->route_prev = bai->route_prev ; + if (bai->route_prev != NULL) + bai->route_prev->route_next = bai->route_next ; + else + *adj_in_head = bai->route_next ; + + peer_unlock (peer); + + /* Unhook from bgp_node */ + if (bai->adj_next) + bai->adj_next->adj_prev = bai->adj_prev; + if (bai->adj_prev) + bai->adj_prev->adj_next = bai->adj_next; + else + rn->adj_in = bai->adj_next; + + bgp_unlock_node (rn); + + /* now can release memory. */ XFREE (MTYPE_BGP_ADJ_IN, bai); } @@ -366,7 +441,7 @@ bgp_adj_in_unset (struct bgp_node *rn, struct peer *peer) { struct bgp_adj_in *adj; - for (adj = rn->adj_in; adj; adj = adj->next) + for (adj = rn->adj_in; adj; adj = adj->adj_next) if (adj->peer == peer) break; @@ -374,7 +449,6 @@ bgp_adj_in_unset (struct bgp_node *rn, struct peer *peer) return; bgp_adj_in_remove (rn, adj); - bgp_unlock_node (rn); } void diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h index 918e1216..ca92238a 100644 --- a/bgpd/bgp_advertise.h +++ b/bgpd/bgp_advertise.h @@ -81,31 +81,37 @@ struct bgp_advertise /* BGP adjacency out. */ struct bgp_adj_out { - /* Lined list pointer. */ - struct bgp_adj_out *next; - struct bgp_adj_out *prev; + /* Linked list pointer. */ + struct bgp_node* rn ; + struct bgp_adj_out *adj_next; + struct bgp_adj_out *adj_prev; - /* Advertised peer. */ + /* Advertised peer. */ struct peer *peer; + struct bgp_adj_out* route_next ; + struct bgp_adj_out* route_prev ; - /* Advertised attribute. */ + /* Advertised attribute. */ struct attr *attr; - /* Advertisement information. */ + /* Advertisement information. */ struct bgp_advertise *adv; }; /* BGP adjacency in. */ struct bgp_adj_in { - /* Linked list pointer. */ - struct bgp_adj_in *next; - struct bgp_adj_in *prev; + /* Linked list pointer. */ + struct bgp_node* rn ; + struct bgp_adj_in *adj_next; + struct bgp_adj_in *adj_prev; - /* Received peer. */ + /* Received peer. */ struct peer *peer; + struct bgp_adj_in* route_next ; + struct bgp_adj_in* route_prev ; - /* Received attribute. */ + /* Received attribute. */ struct attr *attr; }; @@ -183,21 +189,21 @@ bgp_advertise_fifo_del(bgp_advertise adv) /* BGP adjacency linked list. */ #define BGP_INFO_ADD(N,A,TYPE) \ do { \ - (A)->prev = NULL; \ - (A)->next = (N)->TYPE; \ + (A)->adj_prev = NULL; \ + (A)->adj_next = (N)->TYPE; \ if ((N)->TYPE) \ - (N)->TYPE->prev = (A); \ + (N)->TYPE->adj_prev = (A); \ (N)->TYPE = (A); \ } while (0) #define BGP_INFO_DEL(N,A,TYPE) \ do { \ - if ((A)->next) \ - (A)->next->prev = (A)->prev; \ - if ((A)->prev) \ - (A)->prev->next = (A)->next; \ + if ((A)->adj_next) \ + (A)->adj_next->adj_prev = (A)->adj_prev; \ + if ((A)->adj_prev) \ + (A)->adj_prev->adj_next = (A)->adj_next; \ else \ - (N)->TYPE = (A)->next; \ + (N)->TYPE = (A)->adj_next; \ } while (0) #define BGP_ADJ_IN_ADD(N,A) BGP_INFO_ADD(N,A,adj_in) diff --git a/bgpd/bgp_damp.c b/bgpd/bgp_damp.c index e21131ef..52a93eb7 100644 --- a/bgpd/bgp_damp.c +++ b/bgpd/bgp_damp.c @@ -31,7 +31,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "bgpd/bgp_damp.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" -#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_attr.h" #include "bgpd/bgp_advertise.h" /* Global variable to access damping configuration */ @@ -40,9 +40,25 @@ static struct bgp_damp_config *damp = &bgp_damp_cfg; /* Utility macro to add and delete BGP dampening information to no used list. */ -#define BGP_DAMP_LIST_ADD(N,A) BGP_INFO_ADD(N,A,no_reuse_list) -#define BGP_DAMP_LIST_DEL(N,A) BGP_INFO_DEL(N,A,no_reuse_list) - +#define BGP_DAMP_LIST_ADD(N,A) \ + do { \ + (A)->prev = NULL; \ + (A)->next = (N)->no_reuse_list; \ + if ((N)->no_reuse_list) \ + (N)->no_reuse_list->prev = (A); \ + (N)->no_reuse_list = (A); \ + } while (0) + +#define BGP_DAMP_LIST_DEL(N,A) \ + do { \ + if ((A)->next) \ + (A)->next->prev = (A)->prev; \ + if ((A)->prev) \ + (A)->prev->next = (A)->next; \ + else \ + (N)->no_reuse_list = (A)->next; \ + } while (0) + /* Calculate reuse list index by penalty value. */ static int bgp_reuse_index (int penalty) @@ -51,17 +67,17 @@ bgp_reuse_index (int penalty) int index; i = (int)(((double) penalty / damp->reuse_limit - 1.0) * damp->scale_factor); - + if ( i >= damp->reuse_index_size ) i = damp->reuse_index_size - 1; index = damp->reuse_index[i] - damp->reuse_index[0]; - return (damp->reuse_offset + index) % damp->reuse_list_size; + return (damp->reuse_offset + index) % damp->reuse_list_size; } /* Add BGP dampening information to reuse list. */ -static void +static void bgp_reuse_list_add (struct bgp_damp_info *bdi) { int index; @@ -85,10 +101,10 @@ bgp_reuse_list_delete (struct bgp_damp_info *bdi) bdi->prev->next = bdi->next; else damp->reuse_list[bdi->index] = bdi->next; -} - +} + /* Return decayed penalty value. */ -int +int bgp_damp_decay (time_t tdiff, int penalty) { unsigned int i; @@ -96,8 +112,8 @@ bgp_damp_decay (time_t tdiff, int penalty) i = (int) ((double) tdiff / DELTA_T); if (i == 0) - return penalty; - + return penalty; + if (i >= damp->decay_array_size) return 0; @@ -112,7 +128,7 @@ bgp_reuse_timer (struct thread *t) struct bgp_damp_info *bdi; struct bgp_damp_info *next; time_t t_now, t_diff; - + damp->t_reuse = NULL; damp->t_reuse = thread_add_timer (master, bgp_reuse_timer, NULL, DELTA_REUSE); @@ -132,14 +148,14 @@ bgp_reuse_timer (struct thread *t) for (; bdi; bdi = next) { struct bgp *bgp = bdi->binfo->peer->bgp; - + next = bdi->next; /* Set t-diff = t-now - t-updated. */ t_diff = t_now - bdi->t_updated; /* Set figure-of-merit = figure-of-merit * decay-array-ok [t-diff] */ - bdi->penalty = bgp_damp_decay (t_diff, bdi->penalty); + bdi->penalty = bgp_damp_decay (t_diff, bdi->penalty); /* Set t-updated = t-now. */ bdi->t_updated = t_now; @@ -155,7 +171,7 @@ bgp_reuse_timer (struct thread *t) { bgp_info_unset_flag (bdi->rn, bdi->binfo, BGP_INFO_HISTORY); bgp_aggregate_increment (bgp, &bdi->rn->p, bdi->binfo, - bdi->afi, bdi->safi); + bdi->afi, bdi->safi); bgp_process (bgp, bdi->rn, bdi->afi, bdi->safi); } @@ -180,13 +196,13 @@ bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn, time_t t_now; struct bgp_damp_info *bdi = NULL; double last_penalty = 0; - + t_now = time (NULL); /* Processing Unreachable Messages. */ if (binfo->extra) bdi = binfo->extra->damp_info; - + if (bdi == NULL) { /* If there is no previous stability history. */ @@ -214,8 +230,8 @@ bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn, last_penalty = bdi->penalty; /* 1. Set t-diff = t-now - t-updated. */ - bdi->penalty = - (bgp_damp_decay (t_now - bdi->t_updated, bdi->penalty) + bdi->penalty = + (bgp_damp_decay (t_now - bdi->t_updated, bdi->penalty) + (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY)); if (bdi->penalty > damp->ceiling) @@ -223,9 +239,9 @@ bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn, bdi->flap++; } - + assert ((rn == bdi->rn) && (binfo == bdi->binfo)); - + bdi->lastrecord = BGP_RECORD_WITHDRAW; bdi->t_updated = t_now; @@ -235,13 +251,13 @@ bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn, /* Remove the route from a reuse list if it is on one. */ if (CHECK_FLAG (bdi->binfo->flags, BGP_INFO_DAMPED)) { - /* If decay rate isn't equal to 0, reinsert brn. */ + /* If decay rate isn't equal to 0, reinsert brn. */ if (bdi->penalty != last_penalty) { bgp_reuse_list_delete (bdi); - bgp_reuse_list_add (bdi); + bgp_reuse_list_add (bdi); } - return BGP_DAMP_SUPPRESSED; + return BGP_DAMP_SUPPRESSED; } /* If not suppressed before, do annonunce this withdraw and @@ -258,7 +274,7 @@ bgp_damp_withdraw (struct bgp_info *binfo, struct bgp_node *rn, } int -bgp_damp_update (struct bgp_info *binfo, struct bgp_node *rn, +bgp_damp_update (struct bgp_info *binfo, struct bgp_node *rn, afi_t afi, safi_t safi) { time_t t_now; @@ -287,28 +303,28 @@ bgp_damp_update (struct bgp_info *binfo, struct bgp_node *rn, status = BGP_DAMP_USED; } else - status = BGP_DAMP_SUPPRESSED; + status = BGP_DAMP_SUPPRESSED; if (bdi->penalty > damp->reuse_limit / 2.0) bdi->t_updated = t_now; else bgp_damp_info_free (bdi, 0); - + return status; } /* Remove dampening information and history route. */ -int +int bgp_damp_scan (struct bgp_info *binfo, afi_t afi, safi_t safi) { time_t t_now, t_diff; struct bgp_damp_info *bdi; - + assert (binfo->extra && binfo->extra->damp_info); - + t_now = time (NULL); bdi = binfo->extra->damp_info; - + if (CHECK_FLAG (binfo->flags, BGP_INFO_DAMPED)) { t_diff = t_now - bdi->suppress_time; @@ -321,7 +337,7 @@ bgp_damp_scan (struct bgp_info *binfo, afi_t afi, safi_t safi) bdi->penalty = damp->reuse_limit; bdi->suppress_time = 0; bdi->t_updated = t_now; - + /* Need to announce UPDATE once this binfo is usable again. */ if (bdi->lastrecord == BGP_RECORD_UPDATE) return 1; @@ -336,13 +352,13 @@ bgp_damp_scan (struct bgp_info *binfo, afi_t afi, safi_t safi) if (bdi->penalty <= damp->reuse_limit / 2.0) { - /* release the bdi, bdi->binfo. */ + /* release the bdi, bdi->binfo. */ bgp_damp_info_free (bdi, 1); return 0; - } + } else bdi->t_updated = t_now; - } + } return 0; } @@ -366,7 +382,7 @@ bgp_damp_info_free (struct bgp_damp_info *bdi, int withdraw) if (bdi->lastrecord == BGP_RECORD_WITHDRAW && withdraw) bgp_info_delete (bdi->rn, binfo); - + XFREE (MTYPE_BGP_DAMP_INFO, bdi); } @@ -376,7 +392,7 @@ bgp_damp_parameter_set (int hlife, int reuse, int sup, int maxsup) double reuse_max_ratio; unsigned int i; double j; - + damp->suppress_value = sup; damp->half_life = hlife; damp->reuse_limit = reuse; @@ -385,7 +401,7 @@ bgp_damp_parameter_set (int hlife, int reuse, int sup, int maxsup) /* Initialize params per bgp_damp_config. */ damp->reuse_index_size = REUSE_ARRAY_SIZE; - damp->ceiling = (int)(damp->reuse_limit * (pow(2, (double)damp->max_suppress_time/damp->half_life))); + damp->ceiling = (int)(damp->reuse_limit * (pow(2, (double)damp->max_suppress_time/damp->half_life))); /* Decay-array computations */ damp->decay_array_size = ceil ((double) damp->max_suppress_time / DELTA_T); @@ -397,21 +413,21 @@ bgp_damp_parameter_set (int hlife, int reuse, int sup, int maxsup) /* Calculate decay values for all possible times */ for (i = 2; i < damp->decay_array_size; i++) damp->decay_array[i] = damp->decay_array[i-1] * damp->decay_array[1]; - + /* Reuse-list computations */ i = ceil ((double)damp->max_suppress_time / DELTA_REUSE) + 1; if (i > REUSE_LIST_SIZE || i == 0) i = REUSE_LIST_SIZE; - damp->reuse_list_size = i; + damp->reuse_list_size = i; - damp->reuse_list = XCALLOC (MTYPE_BGP_DAMP_ARRAY, - damp->reuse_list_size + damp->reuse_list = XCALLOC (MTYPE_BGP_DAMP_ARRAY, + damp->reuse_list_size * sizeof (struct bgp_reuse_node *)); - memset (damp->reuse_list, 0x00, - damp->reuse_list_size * sizeof (struct bgp_reuse_node *)); + memset (damp->reuse_list, 0x00, + damp->reuse_list_size * sizeof (struct bgp_reuse_node *)); /* Reuse-array computations */ - damp->reuse_index = XMALLOC (MTYPE_BGP_DAMP_ARRAY, + damp->reuse_index = XMALLOC (MTYPE_BGP_DAMP_ARRAY, sizeof(int) * damp->reuse_index_size); memset (damp->reuse_index, 0x00, damp->reuse_list_size * sizeof (int)); @@ -425,7 +441,7 @@ bgp_damp_parameter_set (int hlife, int reuse, int sup, int maxsup) for (i = 0; i < damp->reuse_index_size; i++) { - damp->reuse_index[i] = + damp->reuse_index[i] = (int)(((double)damp->half_life / DELTA_REUSE) * log10 (1.0 / (damp->reuse_limit * ( 1.0 + ((double)i/damp->scale_factor)))) / log10(0.5)); } @@ -450,7 +466,7 @@ bgp_damp_enable (struct bgp *bgp, afi_t afi, safi_t safi, time_t half, /* Register reuse timer. */ if (! damp->t_reuse) - damp->t_reuse = + damp->t_reuse = thread_add_timer (master, bgp_reuse_timer, NULL, DELTA_REUSE); return 0; @@ -549,14 +565,14 @@ bgp_get_reuse_time (unsigned int penalty, char *buf, size_t len) if (penalty > damp->reuse_limit) { - reuse_time = (int) (DELTA_T * ((log((double)damp->reuse_limit/penalty))/(log(damp->decay_array[1])))); + reuse_time = (int) (DELTA_T * ((log((double)damp->reuse_limit/penalty))/(log(damp->decay_array[1])))); if (reuse_time > damp->max_suppress_time) reuse_time = damp->max_suppress_time; tm = gmtime (&reuse_time); } - else + else reuse_time = 0; /* Making formatted timer strings. */ @@ -565,20 +581,20 @@ bgp_get_reuse_time (unsigned int penalty, char *buf, size_t len) if (reuse_time == 0) snprintf (buf, len, "00:00:00"); else if (reuse_time < ONE_DAY_SECOND) - snprintf (buf, len, "%02d:%02d:%02d", + snprintf (buf, len, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (reuse_time < ONE_WEEK_SECOND) - snprintf (buf, len, "%dd%02dh%02dm", + snprintf (buf, len, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else - snprintf (buf, len, "%02dw%dd%02dh", - tm->tm_yday/7, tm->tm_yday - ((tm->tm_yday/7) * 7), tm->tm_hour); + snprintf (buf, len, "%02dw%dd%02dh", + tm->tm_yday/7, tm->tm_yday - ((tm->tm_yday/7) * 7), tm->tm_hour); return buf; } - + void -bgp_damp_info_vty (struct vty *vty, struct bgp_info *binfo) +bgp_damp_info_vty (struct vty *vty, struct bgp_info *binfo) { struct bgp_damp_info *bdi; time_t t_now, t_diff; @@ -587,7 +603,7 @@ bgp_damp_info_vty (struct vty *vty, struct bgp_info *binfo) if (!binfo->extra) return; - + /* BGP dampening information. */ bdi = binfo->extra->damp_info; @@ -620,10 +636,10 @@ bgp_damp_reuse_time_vty (struct vty *vty, struct bgp_info *binfo, struct bgp_damp_info *bdi; time_t t_now, t_diff; int penalty; - + if (!binfo->extra) return NULL; - + /* BGP dampening information. */ bdi = binfo->extra->damp_info; diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index 6ebd24ca..31074e5a 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -315,7 +315,7 @@ DEFUN (debug_bgp_as4, BGP_STR "BGP AS4 actions\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (as4, AS4); else { @@ -333,7 +333,7 @@ DEFUN (no_debug_bgp_as4, BGP_STR "BGP AS4 actions\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (as4, AS4); else { @@ -358,7 +358,7 @@ DEFUN (debug_bgp_as4_segment, BGP_STR "BGP AS4 aspath segment handling\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (as4, AS4_SEGMENT); else { @@ -376,7 +376,7 @@ DEFUN (no_debug_bgp_as4_segment, BGP_STR "BGP AS4 aspath segment handling\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (as4, AS4_SEGMENT); else { @@ -401,7 +401,7 @@ DEFUN (debug_bgp_fsm, BGP_STR "BGP Finite State Machine\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (fsm, FSM); else { @@ -419,7 +419,7 @@ DEFUN (no_debug_bgp_fsm, BGP_STR "Finite State Machine\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (fsm, FSM); else { @@ -444,7 +444,7 @@ DEFUN (debug_bgp_events, BGP_STR "BGP events\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (events, EVENTS); else { @@ -462,7 +462,7 @@ DEFUN (no_debug_bgp_events, BGP_STR "BGP events\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (events, EVENTS); else { @@ -486,7 +486,7 @@ DEFUN (debug_bgp_filter, BGP_STR "BGP filters\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (filter, FILTER); else { @@ -504,7 +504,7 @@ DEFUN (no_debug_bgp_filter, BGP_STR "BGP filters\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (filter, FILTER); else { @@ -528,7 +528,7 @@ DEFUN (debug_bgp_keepalive, BGP_STR "BGP keepalives\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (keepalive, KEEPALIVE); else { @@ -546,7 +546,7 @@ DEFUN (no_debug_bgp_keepalive, BGP_STR "BGP keepalives\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (keepalive, KEEPALIVE); else { @@ -570,7 +570,7 @@ DEFUN (debug_bgp_update, BGP_STR "BGP updates\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) { DEBUG_ON (update, UPDATE_IN); DEBUG_ON (update, UPDATE_OUT); @@ -593,7 +593,7 @@ DEFUN (debug_bgp_update_direct, "Inbound updates\n" "Outbound updates\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) { if (strncmp ("i", argv[0], 1) == 0) { @@ -632,7 +632,7 @@ DEFUN (no_debug_bgp_update, BGP_STR "BGP updates\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) { DEBUG_OFF (update, UPDATE_IN); DEBUG_OFF (update, UPDATE_OUT); @@ -659,7 +659,7 @@ DEFUN (debug_bgp_normal, DEBUG_STR BGP_STR) { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (normal, NORMAL); else { @@ -676,7 +676,7 @@ DEFUN (no_debug_bgp_normal, DEBUG_STR BGP_STR) { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (normal, NORMAL); else { @@ -699,7 +699,7 @@ DEFUN (debug_bgp_zebra, BGP_STR "BGP Zebra messages\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_ON (zebra, ZEBRA); else { @@ -717,7 +717,7 @@ DEFUN (no_debug_bgp_zebra, BGP_STR "BGP Zebra messages\n") { - if (vty->node == CONFIG_NODE) + if (vty_get_node(vty) == CONFIG_NODE) DEBUG_OFF (zebra, ZEBRA); else { diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c index b44b57ce..4bd48e4e 100644 --- a/bgpd/bgp_dump.c +++ b/bgpd/bgp_dump.c @@ -33,7 +33,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_dump.h" - + enum bgp_dump_type { BGP_DUMP_ALL, @@ -89,7 +89,7 @@ struct bgp_dump bgp_dump_routes; /* Dump whole BGP table is very heavy process. */ struct thread *t_bgp_dump_routes; - + /* Some define for BGP packet dump. */ static FILE * bgp_dump_open_file (struct bgp_dump *bgp_dump) @@ -131,7 +131,7 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump) umask(oldumask); return NULL; } - umask(oldumask); + umask(oldumask); return bgp_dump->fp; } @@ -156,7 +156,7 @@ bgp_dump_interval_add (struct bgp_dump *bgp_dump, int interval) secs_into_day = tm.tm_sec + 60*tm.tm_min + 60*60*tm.tm_hour; interval = interval - secs_into_day % interval; /* always > 0 */ } - bgp_dump->t_interval = thread_add_timer (master, bgp_dump_interval_func, + bgp_dump->t_interval = thread_add_timer (master, bgp_dump_interval_func, bgp_dump, interval); } else @@ -179,7 +179,7 @@ bgp_dump_header (struct stream *obuf, int type, int subtype) time (&now); /* Put dump packet header. */ - stream_putl (obuf, now); + stream_putl (obuf, now); stream_putw (obuf, type); stream_putw (obuf, subtype); @@ -348,7 +348,7 @@ bgp_dump_routes_func (int afi, int first_run, unsigned int seq) /* Entry count, note that this is overwritten later */ stream_putw(obuf, 0); - for (info = rn->info; info; info = info->next) + for (info = rn->info; info; info = info->info_next) { entry_count++; @@ -500,7 +500,7 @@ bgp_dump_packet_func (struct bgp_dump *bgp_dump, struct peer *peer, /* Dump header and common part. */ if (CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV) ) - { + { bgp_dump_header (obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE_AS4); } else @@ -511,7 +511,7 @@ bgp_dump_packet_func (struct bgp_dump *bgp_dump, struct peer *peer, /* Packet contents. */ stream_put (obuf, STREAM_DATA (packet), stream_get_endp (packet)); - + /* Set length. */ bgp_dump_set_size (obuf, MSG_PROTOCOL_BGP4MP); @@ -531,7 +531,7 @@ bgp_dump_packet (struct peer *peer, int type, struct stream *packet) if (type == BGP_MSG_UPDATE) bgp_dump_packet_func (&bgp_dump_updates, peer, packet); } - + static unsigned int bgp_dump_parse_time (const char *str) { @@ -585,10 +585,10 @@ bgp_dump_set (struct vty *vty, struct bgp_dump *bgp_dump, const char *interval_str) { unsigned int interval; - + if (interval_str) { - + /* Check interval string. */ interval = bgp_dump_parse_time (interval_str); if (interval == 0) @@ -610,13 +610,13 @@ bgp_dump_set (struct vty *vty, struct bgp_dump *bgp_dump, if (bgp_dump->interval_str) free (bgp_dump->interval_str); bgp_dump->interval_str = strdup (interval_str); - + } else { interval = 0; } - + /* Create interval thread. */ bgp_dump_interval_add (bgp_dump, interval); @@ -665,7 +665,7 @@ bgp_dump_unset (struct vty *vty, struct bgp_dump *bgp_dump) free (bgp_dump->interval_str); bgp_dump->interval_str = NULL; } - + return CMD_SUCCESS; } @@ -812,36 +812,36 @@ config_write_bgp_dump (struct vty *vty) if (bgp_dump_all.filename) { if (bgp_dump_all.interval_str) - vty_out (vty, "dump bgp all %s %s%s", + vty_out (vty, "dump bgp all %s %s%s", bgp_dump_all.filename, bgp_dump_all.interval_str, VTY_NEWLINE); else - vty_out (vty, "dump bgp all %s%s", + vty_out (vty, "dump bgp all %s%s", bgp_dump_all.filename, VTY_NEWLINE); } if (bgp_dump_updates.filename) { if (bgp_dump_updates.interval_str) - vty_out (vty, "dump bgp updates %s %s%s", + vty_out (vty, "dump bgp updates %s %s%s", bgp_dump_updates.filename, bgp_dump_updates.interval_str, VTY_NEWLINE); else - vty_out (vty, "dump bgp updates %s%s", + vty_out (vty, "dump bgp updates %s%s", bgp_dump_updates.filename, VTY_NEWLINE); } if (bgp_dump_routes.filename) { if (bgp_dump_routes.interval_str) - vty_out (vty, "dump bgp routes-mrt %s %s%s", + vty_out (vty, "dump bgp routes-mrt %s %s%s", bgp_dump_routes.filename, bgp_dump_routes.interval_str, VTY_NEWLINE); else - vty_out (vty, "dump bgp routes-mrt %s%s", + vty_out (vty, "dump bgp routes-mrt %s%s", bgp_dump_routes.filename, VTY_NEWLINE); } return 0; } - + /* Initialize BGP packet dump functionality. */ void bgp_dump_init (void) diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h index 3a751885..6137226e 100644 --- a/bgpd/bgp_engine.h +++ b/bgpd/bgp_engine.h @@ -32,7 +32,13 @@ #define Inline static inline #endif - +enum { qdebug = +#ifdef QDEBUG + 1 +#else + 0 +#endif +}; /*============================================================================== * @@ -112,7 +118,8 @@ Inline void bgp_to_bgp_engine(mqueue_block mqb) { mqueue_enqueue(bgp_nexus->queue, mqb, 0) ; - bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ; + if (qdebug) + bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ; } ; /* Send given message to the BGP Engine -- priority @@ -121,7 +128,8 @@ Inline void bgp_to_bgp_engine_priority(mqueue_block mqb) { mqueue_enqueue(bgp_nexus->queue, mqb, 1) ; - bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ; + if (qdebug) + bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ; } ; /*============================================================================== @@ -134,7 +142,8 @@ Inline void bgp_to_routing_engine(mqueue_block mqb) { mqueue_enqueue(routing_nexus->queue, mqb, 0) ; - bgp_queue_logging("Routing Engine", routing_nexus->queue, + if (qdebug) + bgp_queue_logging("Routing Engine", routing_nexus->queue, &routing_engine_queue_stats) ; } ; @@ -144,7 +153,8 @@ Inline void bgp_to_routing_engine_priority(mqueue_block mqb) { mqueue_enqueue(routing_nexus->queue, mqb, 1) ; - bgp_queue_logging("Routing Engine", routing_nexus->queue, + if (qdebug) + bgp_queue_logging("Routing Engine", routing_nexus->queue, &routing_engine_queue_stats) ; } ; diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index 77afa12f..d51ab09b 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -1657,9 +1657,9 @@ static bgp_fsm_action(bgp_fsm_send_open) how = "accept" ; zlog_debug("%s open %s(), local address %s", - sockunion2str(connection->su_remote, buf_r, SU_ADDRSTRLEN), + sockunion2str(connection->su_remote, buf_r, sizeof(buf_r)), how, - sockunion2str(connection->su_local, buf_l, SU_ADDRSTRLEN)) ; + sockunion2str(connection->su_local, buf_l, sizeof(buf_l))) ; } ; bgp_connection_read_enable(connection) ; @@ -1817,7 +1817,7 @@ static bgp_fsm_action(bgp_fsm_recv_open) bgp_msg_noms_o_bad_id(NULL, connection->open_recv->bgp_id)) ; } ; - /* NB: bgp_id in open_state is in *host* order */ + /* NB: bgp_id in open_state is in *network* order */ loser = (ntohl(session->open_send->bgp_id) < ntohl(sibling->open_recv->bgp_id)) ? connection diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 233d487a..366949af 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -85,6 +85,7 @@ void sigusr2 (void); static void bgp_exit (int); static void init_second_stage(int pthreads); static void bgp_in_thread_init(void); +static void routing_start(void) ; static int routing_foreground(void); static int routing_background(void); static void sighup_action(mqueue_block mqb, mqb_flag_t flag); @@ -407,8 +408,10 @@ init_second_stage(int pthreads) * Beware if !qpthreads_enabled then there is only 1 nexus object * with all nexus pointers being aliases for it. */ - bgp_nexus->in_thread_init = bgp_in_thread_init ; - bgp_nexus->in_thread_final = bgp_close_listeners ; + qpn_add_hook_function(&routing_nexus->in_thread_init, routing_start) ; + qpn_add_hook_function(&bgp_nexus->in_thread_init, bgp_in_thread_init) ; + + qpn_add_hook_function(&bgp_nexus->in_thread_final, bgp_close_listeners) ; qpn_add_hook_function(&routing_nexus->foreground, routing_foreground) ; qpn_add_hook_function(&bgp_nexus->foreground, bgp_connection_queue_process) ; @@ -617,14 +620,24 @@ main (int argc, char **argv) bgp_exit(0); } -/* bgp_nexus in-thread initialization */ +/* bgp_nexus in-thread initialization */ static void bgp_in_thread_init(void) { bgp_open_listeners(bm->port, bm->address); } -/* legacy threads in routing engine */ +/* routing_nexus in-thread initialization -- for gdb ! */ + +static int routing_started = 0 ; + +static void +routing_start(void) +{ + routing_started = 1 ; +} + +/* legacy threads in routing engine */ static int routing_foreground(void) { diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 8682424c..0489cf0f 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -36,7 +36,7 @@ static u_int16_t decode_rd_type (u_char *pnt) { u_int16_t v; - + v = ((u_int16_t) *pnt++ << 8); v |= (u_int16_t) *pnt; return v; @@ -58,7 +58,7 @@ decode_rd_as (u_char *pnt, struct rd_as *rd_as) { rd_as->as = (u_int16_t) *pnt++ << 8; rd_as->as |= (u_int16_t) *pnt++; - + rd_as->val = ((u_int32_t) *pnt++ << 24); rd_as->val |= ((u_int32_t) *pnt++ << 16); rd_as->val |= ((u_int32_t) *pnt++ << 8); @@ -70,13 +70,13 @@ decode_rd_ip (u_char *pnt, struct rd_ip *rd_ip) { memcpy (&rd_ip->ip, pnt, 4); pnt += 4; - + rd_ip->val = ((u_int16_t) *pnt++ << 8); rd_ip->val |= (u_int16_t) *pnt; } int -bgp_nlri_parse_vpnv4 (struct peer *peer, struct attr *attr, +bgp_nlri_parse_vpnv4 (struct peer *peer, struct attr *attr, struct bgp_nlri *packet) { u_char *pnt; @@ -94,7 +94,7 @@ bgp_nlri_parse_vpnv4 (struct peer *peer, struct attr *attr, /* Check peer status. */ if (peer->state != bgp_peer_sEstablished) return 0; - + /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; @@ -234,12 +234,12 @@ str2tag (const char *str, u_char *tag) u_int32_t t; l = strtoul (str, &endptr, 10); - + if (*endptr == '\0' || l == ULONG_MAX || l > UINT32_MAX) return 0; t = (u_int32_t) l; - + tag[0] = (u_char)(t >> 12); tag[1] = (u_char)(t >> 4); tag[2] = (u_char)(t << 4); @@ -421,7 +421,7 @@ bgp_show_mpls_vpn (struct vty *vty, struct prefix_rd *prd, enum bgp_show_type ty vty_out (vty, "No BGP process is configured%s", VTY_NEWLINE); return CMD_WARNING; } - + for (rn = bgp_table_top (bgp->rib[AFI_IP][SAFI_MPLS_VPN]); rn; rn = bgp_route_next (rn)) { if (prd && memcmp (rn->p.u.val, prd->val, 8) != 0) @@ -432,7 +432,7 @@ bgp_show_mpls_vpn (struct vty *vty, struct prefix_rd *prd, enum bgp_show_type ty rd_header = 1; for (rm = bgp_table_top (table); rm; rm = bgp_route_next (rm)) - for (ri = rm->info; ri; ri = ri->next) + for (ri = rm->info; ri; ri = ri->info_next) { if (type == bgp_show_type_neighbor) { @@ -481,8 +481,8 @@ bgp_show_mpls_vpn (struct vty *vty, struct prefix_rd *prd, enum bgp_show_type ty vty_out (vty, "%u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_IP) vty_out (vty, "%s:%d", safe_inet_ntoa (rd_ip.ip), rd_ip.val); - - vty_out (vty, "%s", VTY_NEWLINE); + + vty_out (vty, "%s", VTY_NEWLINE); rd_header = 0; } if (tags) @@ -579,7 +579,7 @@ DEFUN (show_ip_bgp_vpnv4_all_neighbor_routes, { union sockunion *su; struct peer *peer; - + su = sockunion_str2su (argv[0]); if (su == NULL) { diff --git a/bgpd/bgp_msg_read.c b/bgpd/bgp_msg_read.c index baf967ec..425038fd 100644 --- a/bgpd/bgp_msg_read.c +++ b/bgpd/bgp_msg_read.c @@ -63,7 +63,8 @@ bgp_msg_get_mlen(uint8_t* p, uint8_t* limit) uint16_t mlen ; passert((p + BGP_MH_HEAD_L) <= limit) ; - mlen = (*(p + BGP_MH_MARKER_L) << 8) + (*(p + BGP_MH_MARKER_L + 1)) ; + mlen = ((bgp_size_t)(*(p + BGP_MH_MARKER_L)) << 8) + + (*(p + BGP_MH_MARKER_L + 1)) ; passert((p + mlen) <= limit) ; @@ -840,7 +841,7 @@ bgp_msg_capability_option_parse (bgp_connection connection, unsigned cap_length ; /* We need at least capability code and capability length. */ - if ((left -= 2) > 0) + if ((left -= 2) >= 0) { cap_code = suck_b(sr); cap_length = suck_b(sr); diff --git a/bgpd/bgp_msg_write.c b/bgpd/bgp_msg_write.c index d61ba642..6ba76545 100644 --- a/bgpd/bgp_msg_write.c +++ b/bgpd/bgp_msg_write.c @@ -237,7 +237,7 @@ bgp_msg_send_open(bgp_connection connection, bgp_open_state open_state) stream_putw(s, (open_state->my_as <= BGP_AS_MAX) ? (u_int16_t) open_state->my_as : BGP_AS_TRANS) ; stream_putw(s, open_state->holdtime) ; - stream_putl(s, open_state->bgp_id) ; + stream_put_ipv4(s, open_state->bgp_id) ; /* Set OPEN message options */ bgp_open_options(s, open_state) ; diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c index fa1dbd37..856ac879 100644 --- a/bgpd/bgp_network.c +++ b/bgpd/bgp_network.c @@ -148,7 +148,8 @@ bgp_open_listeners(unsigned short port, const char *address) if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) continue; - sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); + sock = sockunion_socket(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol); if (sock < 0) { zlog_err ("socket: %s", safe_strerror (errno)); @@ -456,20 +457,20 @@ bgp_accept_action(qps_file qf, void* file_info) if (BGP_DEBUG(events, EVENTS)) zlog_debug("[Event] BGP connection from host %s", - inet_sutop(&su_remote, buf)) ; + sockunion2str(&su_remote, buf, sizeof(buf))) ; /* See if we are ready to accept connections from the connecting party */ connection = bgp_peer_index_seek_accept(&su_remote, &exists) ; - if (connection != NULL) + if (connection == NULL) { if (BGP_DEBUG(events, EVENTS)) zlog_debug(exists ? "[Event] BGP accept IP address %s is not accepting" : "[Event] BGP accept IP address %s is not configured", - inet_sutop(&su_remote, buf)) ; + sockunion2str(&su_remote, buf, sizeof(buf))) ; close(fd) ; return ; /* quietly reject connection */ - /* RFC recommends sending a NOTIFICATION... */ +/* TODO: RFC recommends sending a NOTIFICATION when refusing accept() */ } ; /* Will accept the connection. @@ -537,7 +538,7 @@ bgp_open_connect(bgp_connection connection) union sockunion* su = connection->session->su_peer ; /* Make socket for the connect connection. */ - fd = sockunion_socket(su) ; + fd = sockunion_socket(sockunion_family(su), SOCK_STREAM, 0) ; ret = (fd >= 0) ? 0 : errno ; /* Set the common options. */ diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c index faf4e7a6..7e8c82ee 100644 --- a/bgpd/bgp_nexthop.c +++ b/bgpd/bgp_nexthop.c @@ -450,7 +450,7 @@ bgp_scan (afi_t afi, safi_t safi) { for (bi = rn->info; bi; bi = next) { - next = bi->next; + next = bi->info_next; if (bi->type == ZEBRA_ROUTE_BGP && bi->sub_type == BGP_ROUTE_NORMAL) { diff --git a/bgpd/bgp_open_state.c b/bgpd/bgp_open_state.c index 4eb328e7..dda00aa3 100644 --- a/bgpd/bgp_open_state.c +++ b/bgpd/bgp_open_state.c @@ -27,9 +27,6 @@ #include "bgpd/bgp_open_state.h" #include "lib/memory.h" -#include "lib/memtypes.h" - - /*============================================================================== * BGP Open State. diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 8955be3b..ce70bee7 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -334,7 +334,6 @@ bgp_withdraw_packet (struct peer *peer, afi_t afi, safi_t safi) peer->scount[afi][safi]--; bgp_adj_out_remove (rn, adj, peer, afi, safi); - bgp_unlock_node (rn); if (! (afi == AFI_IP && safi == SAFI_UNICAST)) break; @@ -2210,7 +2209,7 @@ bgp_capability_msg_parse (struct peer *peer, u_char *pnt, bgp_size_t length) peer->afc_nego[afi][safi] = 0; if (peer_active_nego (peer)) - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_NORMAL); + bgp_clear_route_normal (peer, afi, safi); else { /* TODO: only used for unit tests. Test will need fixing */ diff --git a/bgpd/bgp_peer.c b/bgpd/bgp_peer.c index 16ec8bd9..99430c1d 100644 --- a/bgpd/bgp_peer.c +++ b/bgpd/bgp_peer.c @@ -337,8 +337,8 @@ bgp_session_has_disabled(bgp_peer peer) { /* disable the peer */ bgp_peer_stop(peer); - bgp_clear_route_all(peer); peer_change_status(peer, bgp_peer_sClearing); + bgp_clear_route_all(peer); } /* if the program is terminating then see if this was the last session @@ -470,6 +470,25 @@ bgp_peer_stop (struct peer *peer) return 0; } +/*------------------------------------------------------------------------------ + * When a bgp_clear_route_all completes, this is called to move the peer state + * on, if required. + */ +extern void +bgp_peer_clearing_completed(struct peer *peer) +{ + /* Flush the event queue and ensure the peer is shut down */ + bgp_peer_stop(peer); + BGP_EVENT_FLUSH (peer); + + if (peer->state == bgp_peer_sClearing) + { + peer_change_status (peer, bgp_peer_sIdle); + /* enable peer if required */ + bgp_peer_enable(peer); + } +} ; + #if 0 /* Stop all timers for the given peer */ @@ -749,7 +768,6 @@ peer_create (union sockunion *su, struct bgp *bgp, as_t local_as, { int active; struct peer *peer; - char buf[SU_ADDRSTRLEN]; peer = peer_new (bgp); peer->su = *su; @@ -766,8 +784,8 @@ peer_create (union sockunion *su, struct bgp *bgp, as_t local_as, peer = peer_lock (peer); /* bgp peer list reference */ listnode_add_sort (bgp->peer, peer); + /* If "default ipv4-unicast", then this is implicit "activate" */ active = peer_active (peer); - if (afi && safi) peer->afc[afi][safi] = 1; @@ -781,12 +799,7 @@ peer_create (union sockunion *su, struct bgp *bgp, as_t local_as, peer->ttl = (peer_sort (peer) == BGP_PEER_IBGP ? 255 : 1); /* Make peer's address string. */ - sockunion2str (su, buf, SU_ADDRSTRLEN); - peer->host = XSTRDUP (MTYPE_BGP_PEER_HOST, buf); - - /* Set up peer's events and timers. */ - if (! active && peer_active (peer)) - bgp_timer_set (peer); + peer->host = sockunion_su2str (su, MTYPE_BGP_PEER_HOST) ; /* register */ bgp_peer_index_register(peer, &peer->su); @@ -794,6 +807,13 @@ peer_create (union sockunion *su, struct bgp *bgp, as_t local_as, /* session */ peer->session = bgp_session_init_new(peer->session, peer); + /* If implicit activate, Set up peer's events and timers. */ + if ((! active) && peer_active (peer)) + { + bgp_timer_set (peer); + bgp_peer_enable (peer); + } ; + return peer; } @@ -843,8 +863,8 @@ peer_delete (struct peer *peer) */ peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; bgp_peer_stop (peer); - bgp_clear_route_all(peer); peer_change_status (peer, bgp_peer_sDeleted); + bgp_clear_route_all(peer); /* Password configuration */ if (peer->password) @@ -874,7 +894,7 @@ peer_delete (struct peer *peer) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_MY_RSCLIENT); + bgp_clear_route_rsclient(peer, afi, safi); } /* Free RIB for any family in which peer is RSERVER_CLIENT, and is not @@ -1021,6 +1041,9 @@ bgp_peer_enable(bgp_peer peer) * 2) Shutdown * 3) Dealing with prefix overflow, its timer will enable peer when ready */ + + zlog_err ("%s: enabling peer %s:", __func__, peer->host) ; + if ((peer->state == bgp_peer_sIdle) && !CHECK_FLAG (peer->flags, PEER_FLAG_SHUTDOWN) && !CHECK_FLAG (peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) @@ -1116,7 +1139,7 @@ bgp_peer_get_ifaddress(bgp_peer peer, const char* ifname, pAF_t paf) prefix_free(peer_prefix) ; if (best_prefix != NULL) - return sockunion_new(best_prefix) ; + return sockunion_new_prefix(NULL, best_prefix) ; zlog_err("Peer %s interface %s has no suitable address", peer->host, ifname); diff --git a/bgpd/bgp_peer.h b/bgpd/bgp_peer.h index b4b0c7d3..7e8a7e74 100644 --- a/bgpd/bgp_peer.h +++ b/bgpd/bgp_peer.h @@ -137,9 +137,18 @@ struct peer /* Local router ID. */ struct in_addr local_id; - /* Peer specific RIB when configured as route-server-client. */ + /* Peer specific RIB when configured as route-server-client. */ struct bgp_table *rib[AFI_MAX][SAFI_MAX]; + /* Collection of routes originated by peer */ + struct bgp_info* routes_head[AFI_MAX][SAFI_MAX] ; + + /* Collection of adj_in routes */ + struct bgp_adj_in* adj_in_head[AFI_MAX][SAFI_MAX] ; + + /* Collection of adj_out routes */ + struct bgp_adj_out* adj_out_head[AFI_MAX][SAFI_MAX] ; + /* Packet receive buffer. */ struct stream *ibuf; @@ -422,7 +431,11 @@ bgp_peer_enable(bgp_peer peer); extern void bgp_peer_disable(bgp_peer peer, bgp_notify notification); -extern int bgp_peer_stop (struct peer *peer) ; +extern int +bgp_peer_stop (struct peer *peer) ; + +extern void +bgp_peer_clearing_completed(struct peer *peer) ; extern void peer_change_status (bgp_peer peer, int status); diff --git a/bgpd/bgp_peer_index.c b/bgpd/bgp_peer_index.c index 72d3b17d..5bb7f3d0 100644 --- a/bgpd/bgp_peer_index.c +++ b/bgpd/bgp_peer_index.c @@ -383,6 +383,7 @@ bgp_peer_id_table_make_ids(void) bgp_peer_id_table_free_entry(entry) ; ++id_new ; + ++entry ; } ; } ; diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index c5191b18..f8c4c74e 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -144,8 +144,6 @@ bgp_info_free (struct bgp_info *binfo) bgp_info_extra_free (&binfo->extra); - peer_unlock (binfo->peer); /* bgp_info peer reference */ - XFREE (MTYPE_BGP_ROUTE, binfo); } @@ -186,19 +184,28 @@ bgp_info_unlock (struct bgp_info *binfo) void bgp_info_add (struct bgp_node *rn, struct bgp_info *ri) { - struct bgp_info *top; - - top = rn->info; + bgp_peer peer = ri->peer ; + struct bgp_info** routes_head ; - ri->next = rn->info; - ri->prev = NULL; - if (top) - top->prev = ri; + /* add to list of routes for this bgp_node */ + ri->rn = rn ; + ri->info_next = rn->info; + ri->info_prev = NULL; + if (rn->info != NULL) + ((struct bgp_info*)rn->info)->info_prev = ri; rn->info = ri; + /* add to list of routes for this peer */ + routes_head = &(peer->routes_head[rn->table->afi][rn->table->safi]) ; + ri->routes_next = *routes_head ; + ri->routes_prev = NULL ; + if (*routes_head != NULL) + (*routes_head)->routes_prev = ri ; + *routes_head = ri ; + bgp_info_lock (ri); bgp_lock_node (rn); - peer_lock (ri->peer); /* bgp_info peer reference */ + peer_lock (peer); /* bgp_info peer reference */ } /* Do the actual removal of info from RIB, for use by bgp_process @@ -206,15 +213,31 @@ bgp_info_add (struct bgp_node *rn, struct bgp_info *ri) static void bgp_info_reap (struct bgp_node *rn, struct bgp_info *ri) { - if (ri->next) - ri->next->prev = ri->prev; - if (ri->prev) - ri->prev->next = ri->next; + bgp_peer peer = ri->peer ; + struct bgp_info** routes_head ; + + assert(ri->rn == rn) ; + + /* remove from list of routes for the bgp_node */ + if (ri->info_next) + ri->info_next->info_prev = ri->info_prev; + if (ri->info_prev) + ri->info_prev->info_next = ri->info_next; + else + rn->info = ri->info_next; + + /* remove from list of routes for the peer */ + routes_head = &(peer->routes_head[rn->table->afi][rn->table->safi]) ; + if (ri->routes_next != NULL) + ri->routes_next->routes_prev = ri->routes_prev ; + if (ri->routes_prev != NULL) + ri->routes_prev->routes_next = ri->routes_next ; else - rn->info = ri->next; + *routes_head = ri->routes_next ; - bgp_info_unlock (ri); - bgp_unlock_node (rn); + bgp_info_unlock (ri); /* fewer references to bgp_info */ + bgp_unlock_node (rn); /* fewer references to bgp_node */ + peer_unlock (peer); /* fewer references to peer */ } void @@ -1329,7 +1352,7 @@ bgp_best_selection (struct bgp *bgp, struct bgp_node *rn, struct bgp_info_pair * /* bgp deterministic-med */ new_select = NULL; if (bgp_flag_check (bgp, BGP_FLAG_DETERMINISTIC_MED)) - for (ri1 = rn->info; ri1; ri1 = ri1->next) + for (ri1 = rn->info; ri1; ri1 = ri1->info_next) { if (CHECK_FLAG (ri1->flags, BGP_INFO_DMED_CHECK)) continue; @@ -1337,8 +1360,8 @@ bgp_best_selection (struct bgp *bgp, struct bgp_node *rn, struct bgp_info_pair * continue; new_select = ri1; - if (ri1->next) - for (ri2 = ri1->next; ri2; ri2 = ri2->next) + if (ri1->info_next) + for (ri2 = ri1->info_next; ri2; ri2 = ri2->info_next) { if (CHECK_FLAG (ri2->flags, BGP_INFO_DMED_CHECK)) continue; @@ -1365,7 +1388,7 @@ bgp_best_selection (struct bgp *bgp, struct bgp_node *rn, struct bgp_info_pair * /* Check old selected route and new selected route. */ old_select = NULL; new_select = NULL; - for (ri = rn->info; (ri != NULL) && (nextri = ri->next, 1); ri = nextri) + for (ri = rn->info; (ri != NULL) && (nextri = ri->info_next, 1); ri = nextri) { if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED)) old_select = ri; @@ -1451,10 +1474,9 @@ bgp_process_announce_selected (struct peer *peer, struct bgp_info *selected, struct bgp_process_queue { - struct bgp *bgp; - struct bgp_node *rn; - afi_t afi; - safi_t safi; + struct bgp* bgp ; + struct bgp_node* head ; + struct bgp_node* tail ; }; WQ_ARGS_SIZE_OK(bgp_process_queue) ; @@ -1463,15 +1485,30 @@ static wq_item_status bgp_process_rsclient (struct work_queue *wq, work_queue_item item) { struct bgp_process_queue *pq = work_queue_item_args(item) ; - struct bgp *bgp = pq->bgp; - struct bgp_node *rn = pq->rn; - afi_t afi = pq->afi; - safi_t safi = pq->safi; + struct bgp *bgp = pq->bgp ; + struct bgp_node *rn ; + afi_t afi ; + safi_t safi ; struct bgp_info *new_select; struct bgp_info *old_select; struct bgp_info_pair old_and_new; struct listnode *node, *nnode; - struct peer *rsclient = rn->table->owner; + struct peer *rsclient ; + + assert(wq->spec.data == item) ; + + /* Is there anything left on the queue ? */ + rn = pq->head ; + if (rn == NULL) + return WQ_SUCCESS ; + + /* hack off queue and prepare to process */ + pq->head = rn->wq_next ; + rn->on_wq = 0 ; + + rsclient = rn->table->owner; + afi = rn->table->afi; + safi = rn->table->safi; /* Best path selection. */ bgp_best_selection (bgp, rn, &old_and_new); @@ -1515,18 +1552,24 @@ bgp_process_rsclient (struct work_queue *wq, work_queue_item item) if (old_select && CHECK_FLAG (old_select->flags, BGP_INFO_REMOVED)) bgp_info_reap (rn, old_select); - UNSET_FLAG (rn->flags, BGP_NODE_PROCESS_SCHEDULED); - return WQ_SUCCESS; + bgp_table_unlock (rn->table); + bgp_unlock_node (rn); + bgp_unlock (bgp); + + if (pq->head == NULL) + return WQ_SUCCESS ; + else + return WQ_REQUEUE ; } static wq_item_status bgp_process_main (struct work_queue *wq, work_queue_item item) { struct bgp_process_queue *pq = work_queue_item_args(item) ; - struct bgp *bgp = pq->bgp; - struct bgp_node *rn = pq->rn; - afi_t afi = pq->afi; - safi_t safi = pq->safi; + struct bgp *bgp = pq->bgp ; + struct bgp_node *rn ; + afi_t afi ; + safi_t safi ; struct prefix *p = &rn->p; struct bgp_info *new_select; struct bgp_info *old_select; @@ -1534,6 +1577,20 @@ bgp_process_main (struct work_queue *wq, work_queue_item item) struct listnode *node, *nnode; struct peer *peer; + assert(wq->spec.data == item) ; + + /* Is there anything left on the queue ? */ + rn = pq->head ; + if (rn == NULL) + return WQ_SUCCESS ; + + /* hack off queue and prepare to process */ + pq->head = rn->wq_next ; + rn->on_wq = 0 ; + + afi = rn->table->afi; + safi = rn->table->safi; + /* Best path selection. */ bgp_best_selection (bgp, rn, &old_and_new); old_select = old_and_new.old; @@ -1547,8 +1604,7 @@ bgp_process_main (struct work_queue *wq, work_queue_item item) if (CHECK_FLAG (old_select->flags, BGP_INFO_IGP_CHANGED)) bgp_zebra_announce (p, old_select, bgp); - UNSET_FLAG (rn->flags, BGP_NODE_PROCESS_SCHEDULED); - return WQ_SUCCESS; + goto finish ; /* was return ! */ } } @@ -1585,89 +1641,139 @@ bgp_process_main (struct work_queue *wq, work_queue_item item) } } - /* Reap old select bgp_info, it it has been removed */ + /* Reap old select bgp_info, it it has been removed */ if (old_select && CHECK_FLAG (old_select->flags, BGP_INFO_REMOVED)) bgp_info_reap (rn, old_select); - UNSET_FLAG (rn->flags, BGP_NODE_PROCESS_SCHEDULED); - return WQ_SUCCESS; + /* Finish up */ + +finish: + bgp_table_unlock (rn->table); + bgp_unlock_node (rn); + bgp_unlock (bgp); + + if (pq->head == NULL) + return WQ_SUCCESS ; + else + return WQ_REQUEUE ; } static void bgp_processq_del (struct work_queue *wq, work_queue_item item) { - struct bgp_process_queue *pq = work_queue_item_args(item); - struct bgp_table *table = pq->rn->table; + struct bgp_process_queue *pq = work_queue_item_args(item) ; + struct bgp_node *rn ; - bgp_unlock (pq->bgp); - bgp_unlock_node (pq->rn); - bgp_table_unlock (table); -} + assert(wq->spec.data == item) ; -static void -bgp_process_queue_init (void) + while ((rn = pq->head) != NULL) + { + pq->head = rn->wq_next ; + rn->on_wq = 0 ; + + bgp_table_unlock (rn->table); + bgp_unlock_node (rn); + bgp_unlock (pq->bgp); + } ; + + wq->spec.data = NULL ; +} ; + +static work_queue +bgp_process_queue_init (struct bgp* bgp, bgp_table_t type) { - bm->process_main_queue - = work_queue_new (bm->master, "process_main_queue"); - bm->process_rsclient_queue - = work_queue_new (bm->master, "process_rsclient_queue"); + work_queue wq ; + const char* name ; + wq_workfunc* workfunc ; + work_queue* p_wq ; - if ( !(bm->process_main_queue && bm->process_rsclient_queue) ) + switch (type) { - zlog_err ("%s: Failed to allocate work queue", __func__); - exit (1); - } + case BGP_TABLE_MAIN: + p_wq = &bgp->process_main_queue ; + name = "process_main_queue" ; + workfunc = &bgp_process_main ; + break ; + case BGP_TABLE_RSCLIENT: + p_wq = &bgp->process_rsclient_queue ; + name = "process_rsclient_queue" ; + workfunc = &bgp_process_rsclient ; + break ; + default: + zabort("invalid BGP table type") ; + } ; + + wq = work_queue_new (bm->master, name) ; - bm->process_main_queue->spec.data = bm->master ; - bm->process_main_queue->spec.errorfunc = NULL ; - bm->process_main_queue->spec.workfunc = &bgp_process_main; - bm->process_main_queue->spec.del_item_data = &bgp_processq_del; - bm->process_main_queue->spec.completion_func = NULL ; - bm->process_main_queue->spec.max_retries = 0; - bm->process_main_queue->spec.hold = 50; + wq->spec.data = NULL ; + wq->spec.errorfunc = NULL ; + wq->spec.workfunc = workfunc ; + wq->spec.del_item_data = &bgp_processq_del ; + wq->spec.completion_func = NULL ; + wq->spec.max_retries = 0 ; + wq->spec.hold = 50 ; - bm->process_rsclient_queue->spec = bm->process_main_queue->spec ; - bm->process_rsclient_queue->spec.workfunc = &bgp_process_rsclient; + return *p_wq = wq ; } void bgp_process (struct bgp *bgp, struct bgp_node *rn, afi_t afi, safi_t safi) { - struct bgp_process_queue *pqnode; + work_queue_item item ; + struct bgp_process_queue *pq ; struct work_queue* wq ; - /* already scheduled for processing? */ - if (CHECK_FLAG (rn->flags, BGP_NODE_PROCESS_SCHEDULED)) + /* already scheduled for processing? */ + if (rn->on_wq) return; - if ( (bm->process_main_queue == NULL) || - (bm->process_rsclient_queue == NULL) ) - bgp_process_queue_init (); - + /* get the required work queue -- making it if necessary */ switch (rn->table->type) { case BGP_TABLE_MAIN: - wq = bm->process_main_queue ; + wq = bgp->process_main_queue ; + if (wq == NULL) + wq = bgp_process_queue_init(bgp, BGP_TABLE_MAIN) ; break; case BGP_TABLE_RSCLIENT: - wq = bm->process_rsclient_queue ; + wq = bgp->process_rsclient_queue ; + if (wq == NULL) + wq = bgp_process_queue_init(bgp, BGP_TABLE_RSCLIENT) ; break; default: zabort("invalid rn->table->type") ; } - pqnode = work_queue_item_add(wq); + /* get the work queue item -- making it if necessary */ + item = wq->spec.data ; + if (item == NULL) + { + /* TODO: sort out assumption that item == args */ + item = wq->spec.data = work_queue_item_add(wq) ; + pq = work_queue_item_args(item) ; - if (!pqnode) - return; + pq->bgp = bgp ; + pq->head = NULL ; + pq->tail = NULL ; + } + else + pq = work_queue_item_args(item) ; - /* all unlocked in bgp_processq_del */ + /* all unlocked when processed or deleted */ bgp_table_lock (rn->table); - pqnode->rn = bgp_lock_node (rn); - pqnode->bgp = bgp; + bgp_lock_node (rn); bgp_lock (bgp); - pqnode->afi = afi; - pqnode->safi = safi; + + /* add to the queue */ + if (pq->head == NULL) + pq->head = rn ; + else + pq->tail->wq_next = rn ; + + pq->tail = rn ; + rn->wq_next = NULL ; + + rn->on_wq = 1 ; return; } @@ -1825,7 +1931,7 @@ bgp_update_rsclient (struct peer *rsclient, afi_t afi, safi_t safi, rn = bgp_afi_node_get (rsclient->rib[afi][safi], afi, safi, p, prd); /* Check previously received route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer && ri->type == type && ri->sub_type == sub_type) break; @@ -2013,7 +2119,7 @@ bgp_withdraw_rsclient (struct peer *rsclient, afi_t afi, safi_t safi, rn = bgp_afi_node_get (rsclient->rib[afi][safi], afi, safi, p, prd); /* Lookup withdrawn route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer && ri->type == type && ri->sub_type == sub_type) break; @@ -2056,7 +2162,7 @@ bgp_update_main (struct peer *peer, struct prefix *p, struct attr *attr, bgp_adj_in_set (rn, peer, attr); /* Check previously received route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer && ri->type == type && ri->sub_type == sub_type) break; @@ -2421,7 +2527,7 @@ bgp_withdraw (struct peer *peer, struct prefix *p, struct attr *attr, bgp_adj_in_unset (rn, peer); /* Lookup withdrawn route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer && ri->type == type && ri->sub_type == sub_type) break; @@ -2543,7 +2649,7 @@ bgp_announce_table (struct peer *peer, afi_t afi, safi_t safi, bgp_default_originate (peer, afi, safi, 0); for (rn = bgp_table_top (table); rn; rn = bgp_route_next(rn)) - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED) && ri->peer != peer) { if ( (rsclient) ? @@ -2607,7 +2713,7 @@ bgp_soft_reconfig_table_rsclient (struct peer *rsclient, afi_t afi, table = rsclient->bgp->rib[afi][safi]; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) - for (ain = rn->adj_in; ain; ain = ain->next) + for (ain = rn->adj_in; ain; ain = ain->adj_next) { bgp_update_rsclient (rsclient, afi, safi, ain->attr, ain->peer, &rn->p, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, NULL); @@ -2642,7 +2748,7 @@ bgp_soft_reconfig_table (struct peer *peer, afi_t afi, safi_t safi, table = peer->bgp->rib[afi][safi]; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) - for (ain = rn->adj_in; ain; ain = ain->next) + for (ain = rn->adj_in; ain; ain = ain->adj_next) { if (ain->peer == peer) { @@ -2677,6 +2783,118 @@ bgp_soft_reconfig_in (struct peer *peer, afi_t afi, safi_t safi) bgp_soft_reconfig_table (peer, afi, safi, table); } +/*============================================================================== + * Clearing. + * + * There are two (quite different) forms of clearing: + * + * 1. Normal clearing -- mass withdraw of given client's routes for all + * or individual AFI/SAFI. + * + * Note that normal clearing deals with the main RIB and any RS Client + * RIBs that may also contain routes. + * + * 2. RS Client clearing -- dismantling of RS Client RIB for an AFI/SAFI. + * + *------------------------------------------------------------------------------ + * Normal clearing + * + * This is used in two ways: + * + * 1. when a peer falls out of Established state. + * + * See: bgp_clear_route_all(). + * + * All the peer's routes in all AFI/SAFI are withdrawn, but may be subject + * to NSF. + * + * 2. when an individual AFI/SAFI is disabled. + * + * See: bgp_clear_route(). + * + * [This appears to be for Dynamic Capabilities only.] + * TODO: discover whether NSF affects Dynamic Capability route clear. + * + * All the peer's routes in the AFI/SAFI are withdrawn. (NSF ??). + * + * Normal clearing affects: + * + * 1. the main RIB in all relevant AFI/SAFI. + * + * 2. all RS Client RIBs in all relevant AFI/SAFI + * + * Any routes (ie bgp_info objects) in the affected tables are either marked + * stale or are removed all together. + * + * Any adj_in (soft reconfig) and adj_out (announcement state) objects are + * removed. + * + * The peer's: + * + * struct bgp_info* routes_head[AFI_MAX][SAFI_MAX] ; + * struct bgp_adj_in* adj_in_head[AFI_MAX][SAFI_MAX] ; + * struct bgp_adj_out* adj_out_head[AFI_MAX][SAFI_MAX] ; + * + * Are maintained for exactly this purpose. + * + * NB: this is now a linear process, because the lists identify the stuff to + * be processed. + * + * Not much work is required to remove a route -- the consequences are + * dealt with by the relevant processing work queue. + * + * In theory it would be better to break up the work. A peer who announces + * 500,000 prefixes has a fair amount to do here. A peer who announces + * 10,000 prefixes to 1,000 RS Clients has 10,000,000 routes to withdraw. + * + * Nevertheless, a really hard case looks like less than 10secs work... + * For the time being, the simplicity of living without a clearing work + * queue task is preferred -- and the + * + * [The old code walked the main RIB, and then every RS Client RIB, searching + * for bgp_node objects which had bgp_info from the given peer. It then issued + * a work queue task to do the actual change (which was probably more work than + * doing the change straight away).] + * + * [The MPLS VPN stuff has a two level RIB, which the above probably doesn't + * work for... more work required, here.] + * TODO: fix bgp_clear_route() and MPLS VPN !! + * + *------------------------------------------------------------------------------ + * RS Client Clearing + * + * This is done when a given RS Client RIB is about to be dismantled. + * + * This walks the RS Client RIB and discards all bgp_info, adj_in and adj_out. + * (This is unconditional -- no NSF gets in the way.) + * + */ + +/*------------------------------------------------------------------------------ + * Clear given route -- respecting NSF for peer whose route this is. + * + * Will mark the bgp_info as stale, or will remove altogether. If removes the + * route will set the bgp_node to be reprocessed. + */ +static void +bgp_clear_this_route(bgp_peer peer, struct bgp_node* rn, struct bgp_info* ri, + afi_t afi, safi_t safi) +{ + assert (rn && peer && ri) ; + assert ((rn == ri->rn) && (peer == ri->peer)) ; + + /* graceful restart STALE flag set. */ + if (CHECK_FLAG (peer->sflags, PEER_STATUS_NSF_WAIT) + && peer->nsf[afi][safi] + && ! CHECK_FLAG (ri->flags, BGP_INFO_STALE) + && ! CHECK_FLAG (ri->flags, BGP_INFO_UNUSEABLE)) + bgp_info_set_flag (rn, ri, BGP_INFO_STALE); + else + bgp_rib_remove (rn, ri, peer, afi, safi); +} ; + +#if 0 + struct bgp_clear_node_queue { struct bgp_node *rn; @@ -2697,7 +2915,7 @@ bgp_clear_route_node (struct work_queue *wq, work_queue_item item) assert (rn && peer); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer || cnq->purpose == BGP_CLEAR_ROUTE_MY_RSCLIENT) { /* graceful restart STALE flag set. */ @@ -2765,104 +2983,130 @@ bgp_clear_node_queue_init (struct peer *peer) peer->clear_node_queue->spec.data = peer; } +#endif + +/*------------------------------------------------------------------------------ + * Completely empty the given table which belongs to the given peer. + * + * Used for RS Client RIB clearing. + * + * Walks the table, *unconditionally* deleting all routes. + * + * Deletes any and all adj_in and adj_out. + * + * TODO: fix bgp_clear_route_table() so that will clear an MPLS VPN table.... + */ static void -bgp_clear_route_table (struct peer *peer, afi_t afi, safi_t safi, - struct bgp_table *table, struct peer *rsclient, - enum bgp_clear_route_type purpose) +bgp_clear_route_table (bgp_peer peer, struct bgp_table* table) { - struct bgp_node *rn; - + struct bgp_node *rn ; - if (! table) - table = (rsclient) ? rsclient->rib[afi][safi] : peer->bgp->rib[afi][safi]; + if (table == NULL) + return ; /* Ignore unconfigured afi/safi or similar */ - /* If still no table => afi/safi isn't configured at all or smth. */ - if (! table) - return; + passert(table->safi != SAFI_MPLS_VPN) ; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) { - struct bgp_info *ri; - struct bgp_adj_in *ain; + struct bgp_info *ri; + struct bgp_info *next_ri ; + struct bgp_adj_in *ain; struct bgp_adj_out *aout; - if (rn->info == NULL) - continue; + next_ri = rn->info ; + while(next_ri != NULL) + { + ri = next_ri ; + next_ri = ri->info_next ; /* bank this */ - /* XXX:TODO: This is suboptimal, every non-empty route_node is - * queued for every clearing peer, regardless of whether it is - * relevant to the peer at hand. - * - * Overview: There are 3 different indices which need to be - * scrubbed, potentially, when a peer is removed: - * - * 1 peer's routes visible via the RIB (ie accepted routes) - * 2 peer's routes visible by the (optional) peer's adj-in index - * 3 other routes visible by the peer's adj-out index - * - * 3 there is no hurry in scrubbing, once the struct peer is - * removed from bgp->peer, we could just GC such deleted peer's - * adj-outs at our leisure. - * - * 1 and 2 must be 'scrubbed' in some way, at least made - * invisible via RIB index before peer session is allowed to be - * brought back up. So one needs to know when such a 'search' is - * complete. - * - * Ideally: - * - * - there'd be a single global queue or a single RIB walker - * - rather than tracking which route_nodes still need to be - * examined on a peer basis, we'd track which peers still - * aren't cleared - * - * Given that our per-peer prefix-counts now should be reliable, - * this may actually be achievable. It doesn't seem to be a huge - * problem at this time, - */ - for (ri = rn->info; ri; ri = ri->next) - if (ri->peer == peer || purpose == BGP_CLEAR_ROUTE_MY_RSCLIENT) - { - struct bgp_clear_node_queue *cnq; - - /* both unlocked in bgp_clear_node_queue_del */ - bgp_table_lock (rn->table); - bgp_lock_node (rn); - cnq = work_queue_item_add(peer->clear_node_queue) ; - cnq->rn = rn; - cnq->purpose = purpose; - break; - } + bgp_rib_remove (rn, ri, peer, table->afi, table->safi); + } ; - for (ain = rn->adj_in; ain; ain = ain->next) - if (ain->peer == peer || purpose == BGP_CLEAR_ROUTE_MY_RSCLIENT) - { - bgp_adj_in_remove (rn, ain); - bgp_unlock_node (rn); - break; - } - for (aout = rn->adj_out; aout; aout = aout->next) - if (aout->peer == peer || purpose == BGP_CLEAR_ROUTE_MY_RSCLIENT) - { - bgp_adj_out_remove (rn, aout, peer, afi, safi); - bgp_unlock_node (rn); - break; - } - } - return; -} + while ((ain = rn->adj_in) != NULL) + { + assert(ain->adj_prev == NULL) ; + bgp_adj_in_remove (rn, ain); + assert(ain != rn->adj_in) ; + } ; + + while ((aout = rn->adj_out) != NULL) + { + assert(aout->adj_prev == NULL) ; + bgp_adj_out_remove (rn, aout, aout->peer, table->afi, table->safi) ; + assert(aout != rn->adj_out) ; + } ; + } + return ; +} + +/*------------------------------------------------------------------------------ + * Normal clearing of a a given peer's routes. + * + * The following lists are processed: + * + * * struct bgp_info* routes_head + * + * Walks this and clears each route. + * + * * struct bgp_adj_in* adj_in_head + * * struct bgp_adj_out* adj_out_head + * + * These two are simply emptied out. + * + * TODO: fix bgp_clear_route_normal() so that will clear an MPLS VPN table.... + */ +extern void +bgp_clear_route_normal(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_info* ri ; + struct bgp_info* next_ri ; + struct bgp_adj_in* adj_in ; + struct bgp_adj_out* adj_out ; + struct bgp_adj_in** adj_in_head ; + struct bgp_adj_out** adj_out_head ; + + next_ri = peer->routes_head[afi][safi] ; + + assert((safi != SAFI_MPLS_VPN) || (next_ri == NULL)) ; + while (next_ri != NULL) + { + /* The current bgp_info object may vanish, so bank the next */ + ri = next_ri ; + next_ri = ri->routes_next ; + + bgp_clear_this_route(peer, ri->rn, ri, afi, safi) ; + } ; + + /* Empty out all adjacencies */ + adj_in_head = &(peer->adj_in_head[afi][safi]) ; + while ((adj_in = *adj_in_head) != NULL) + { + assert(adj_in->route_prev == NULL) ; + bgp_adj_in_remove (adj_in->rn, adj_in) ; + assert(adj_in != *adj_in_head) ; + } ; + + adj_out_head = &(peer->adj_out_head[afi][safi]) ; + while ((adj_out = *adj_out_head) != NULL) + { + assert(adj_out->route_prev == NULL) ; + bgp_adj_out_remove (adj_out->rn, adj_out, peer, afi, safi) ; + assert(adj_out != *adj_out_head) ; + } ; +} ; + +#if 0 void -bgp_clear_route (struct peer *peer, afi_t afi, safi_t safi, - enum bgp_clear_route_type purpose) +bgp_clear_route (struct peer *peer, afi_t afi, safi_t safi) { - struct bgp_node *rn; - struct bgp_table *table; - struct peer *rsclient; - struct listnode *node, *nnode; +//struct bgp_node *rn; +//struct bgp_table *table; +//struct peer *rsclient; +//struct listnode *node, *nnode; - if (peer->clear_node_queue == NULL) - bgp_clear_node_queue_init (peer); +//if (peer->clear_node_queue == NULL) +// bgp_clear_node_queue_init (peer); /* bgp_fsm.c keeps sessions in state Clearing, not transitioning to * Idle until it receives a Clearing_Completed event. This protects @@ -2876,28 +3120,36 @@ bgp_clear_route (struct peer *peer, afi_t afi, safi_t safi, * on the process_main queue. Fast-flapping could cause that queue * to grow and grow. */ - if (!peer->clear_node_queue->thread) - peer_lock (peer); /* bgp_clear_node_complete */ +//if (!peer->clear_node_queue->thread) + peer_lock (peer); /* bgp_clear_node_complete */ switch (purpose) { case BGP_CLEAR_ROUTE_NORMAL: + if (peer->routes_head[afi][safi] == NULL) + break ; + if (safi != SAFI_MPLS_VPN) - bgp_clear_route_table (peer, afi, safi, NULL, NULL, purpose); + bgp_clear_route_normal(peer, afi, safi) ; else +/* TODO: how to deal with SAFI_MPLS_VPN in bgp_clear_route ?? */ + passert(0) ; +#if 0 for (rn = bgp_table_top (peer->bgp->rib[afi][safi]); rn; rn = bgp_route_next (rn)) if ((table = rn->info) != NULL) bgp_clear_route_table (peer, afi, safi, table, NULL, purpose); - +#endif +#if 0 for (ALL_LIST_ELEMENTS (peer->bgp->rsclient, node, nnode, rsclient)) if (CHECK_FLAG(rsclient->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) bgp_clear_route_table (peer, afi, safi, NULL, rsclient, purpose); +#endif break; case BGP_CLEAR_ROUTE_MY_RSCLIENT: - bgp_clear_route_table (peer, afi, safi, NULL, peer, purpose); + bgp_clear_route_table (peer, peer->rib[afi][safi]) ; break; default: @@ -2924,11 +3176,35 @@ bgp_clear_route (struct peer *peer, afi_t afi, safi_t safi, * pre-Established, avoiding above list and table scans. Once we're * sure it is safe.. */ - if (!peer->clear_node_queue->thread) - bgp_clear_node_complete (peer->clear_node_queue); + + /* The following was in bgp_clear_node_complete */ + + peer_unlock (peer); /* bgp_clear_route */ } +#endif -void +/*------------------------------------------------------------------------------ + * Clear Route Server RIB for given AFI/SAFI -- unconditionally + * + * Does nothing if there is no RIB for that AFI/SAFI. + */ +extern void +bgp_clear_route_rsclient(struct peer* rsclient, afi_t afi, safi_t safi) +{ + bgp_clear_route_table (rsclient, rsclient->rib[afi][safi]) ; +} ; + +/*------------------------------------------------------------------------------ + * Normal clearing of given peer for all AFI/SAFI -- respecting NSF. + * + * NB: in the latest scheme of things this is completed immediately... + * + * ...however, retain the ability to run this in the background with the + * peer in bgp_peer_sClearing. + * + * Caller should set state of peer *before* calling this. + */ +extern void bgp_clear_route_all (struct peer *peer) { afi_t afi; @@ -2936,9 +3212,14 @@ bgp_clear_route_all (struct peer *peer) for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_NORMAL); -} + bgp_clear_route_normal(peer, afi, safi); + bgp_peer_clearing_completed(peer) ; +} ; + +/*------------------------------------------------------------------------------ + * Walk main RIB and remove all adj_in for given peer. + */ void bgp_clear_adj_in (struct peer *peer, afi_t afi, safi_t safi) { @@ -2949,15 +3230,17 @@ bgp_clear_adj_in (struct peer *peer, afi_t afi, safi_t safi) table = peer->bgp->rib[afi][safi]; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) - for (ain = rn->adj_in; ain ; ain = ain->next) + for (ain = rn->adj_in; ain ; ain = ain->adj_next) if (ain->peer == peer) { bgp_adj_in_remove (rn, ain); - bgp_unlock_node (rn); break; } } +/*------------------------------------------------------------------------------ + * Walk main RIB and remove all stale routes for the given peer. + */ void bgp_clear_stale_route (struct peer *peer, afi_t afi, safi_t safi) { @@ -2969,7 +3252,7 @@ bgp_clear_stale_route (struct peer *peer, afi_t afi, safi_t safi) for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) { - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == peer) { if (CHECK_FLAG (ri->flags, BGP_INFO_STALE)) @@ -2978,6 +3261,7 @@ bgp_clear_stale_route (struct peer *peer, afi_t afi, safi_t safi) } } } +/*============================================================================*/ /* Delete all kernel routes. */ void @@ -2994,7 +3278,7 @@ bgp_cleanup_routes (void) table = bgp->rib[AFI_IP][SAFI_UNICAST]; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED) && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_NORMAL) @@ -3003,7 +3287,7 @@ bgp_cleanup_routes (void) table = bgp->rib[AFI_IP6][SAFI_UNICAST]; for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED) && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_NORMAL) @@ -3206,7 +3490,7 @@ bgp_static_withdraw_rsclient (struct bgp *bgp, struct peer *rsclient, rn = bgp_afi_node_get (rsclient->rib[afi][safi], afi, safi, p, NULL); /* Check selected route and self inserted route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_STATIC) @@ -3325,7 +3609,7 @@ bgp_static_update_rsclient (struct peer *rsclient, struct prefix *p, bgp_attr_unintern (attr_new); attr_new = bgp_attr_intern (&new_attr); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_STATIC) break; @@ -3449,7 +3733,7 @@ bgp_static_update_main (struct bgp *bgp, struct prefix *p, else attr_new = bgp_attr_intern (&attr); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_STATIC) break; @@ -3574,7 +3858,7 @@ bgp_static_withdraw (struct bgp *bgp, struct prefix *p, afi_t afi, rn = bgp_afi_node_get (bgp->rib[afi][safi], afi, safi, p, NULL); /* Check selected route and self inserted route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_STATIC) @@ -3622,7 +3906,7 @@ bgp_static_withdraw_vpnv4 (struct bgp *bgp, struct prefix *p, u_int16_t afi, rn = bgp_afi_node_get (bgp->rib[afi][safi], afi, safi, p, prd); /* Check selected route and self inserted route. */ - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_STATIC) @@ -4624,7 +4908,7 @@ bgp_aggregate_route (struct bgp *bgp, struct prefix *p, struct bgp_info *rinew, { match = 0; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { if (BGP_INFO_HOLDDOWN (ri)) continue; @@ -4849,7 +5133,7 @@ bgp_aggregate_add (struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, { match = 0; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { if (BGP_INFO_HOLDDOWN (ri)) continue; @@ -4948,7 +5232,7 @@ bgp_aggregate_delete (struct bgp *bgp, struct prefix *p, afi_t afi, { match = 0; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { if (BGP_INFO_HOLDDOWN (ri)) continue; @@ -4978,7 +5262,7 @@ bgp_aggregate_delete (struct bgp *bgp, struct prefix *p, afi_t afi, /* Delete aggregate route from BGP table. */ rn = bgp_node_get (table, p); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == ZEBRA_ROUTE_BGP && ri->sub_type == BGP_ROUTE_AGGREGATE) @@ -5500,7 +5784,7 @@ bgp_redistribute_add (struct prefix *p, struct in_addr *nexthop, new_attr = bgp_attr_intern (&attr_new); bgp_attr_extra_free (&attr_new); - for (bi = bn->info; bi; bi = bi->next) + for (bi = bn->info; bi; bi = bi->info_next) if (bi->peer == bgp->peer_self && bi->sub_type == BGP_ROUTE_REDISTRIBUTE) break; @@ -5577,7 +5861,7 @@ bgp_redistribute_delete (struct prefix *p, u_char type) { rn = bgp_afi_node_get (bgp->rib[afi][SAFI_UNICAST], afi, SAFI_UNICAST, p, NULL); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == type) break; @@ -5605,7 +5889,7 @@ bgp_redistribute_withdraw (struct bgp *bgp, afi_t afi, int type) for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) { - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) if (ri->peer == bgp->peer_self && ri->type == type) break; @@ -5989,8 +6273,7 @@ static void route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p, struct bgp_info *binfo, afi_t afi, safi_t safi) { - char buf[INET6_ADDRSTRLEN]; - char buf1[BUFSIZ]; + char buf[SU_ADDRSTRLEN]; struct attr *attr; int sockunion_vty_out (struct vty *, union sockunion *); @@ -6016,9 +6299,11 @@ route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p, vty_out (vty, ", (aggregated by %u %s)", attr->extra->aggregator_as, safe_inet_ntoa (attr->extra->aggregator_addr)); - if (CHECK_FLAG (binfo->peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) + if (CHECK_FLAG (binfo->peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) vty_out (vty, ", (Received from a RR-client)"); - if (CHECK_FLAG (binfo->peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) + if (CHECK_FLAG (binfo->peer->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT)) vty_out (vty, ", (Received from a RS-client)"); if (CHECK_FLAG (binfo->flags, BGP_INFO_HISTORY)) vty_out (vty, ", (history entry)"); @@ -6055,11 +6340,13 @@ route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p, vty_out (vty, " (inaccessible)"); else if (binfo->extra && binfo->extra->igpmetric) vty_out (vty, " (metric %d)", binfo->extra->igpmetric); - vty_out (vty, " from %s", sockunion2str (&binfo->peer->su, buf, SU_ADDRSTRLEN)); + vty_out (vty, " from %s", + sockunion2str (&binfo->peer->su, buf, sizeof(buf))); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) vty_out (vty, " (%s)", safe_inet_ntoa (attr->extra->originator_id)); else - vty_out (vty, " (%s)", inet_ntop (AF_INET, &binfo->peer->remote_id, buf1, BUFSIZ)); + vty_out (vty, " (%s)", + inet_ntop (AF_INET, &binfo->peer->remote_id, buf, sizeof(buf))); } vty_out (vty, "%s", VTY_NEWLINE); @@ -6069,12 +6356,13 @@ route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p, { vty_out (vty, " (%s)%s", inet_ntop (AF_INET6, &attr->extra->mp_nexthop_local, - buf, INET6_ADDRSTRLEN), + buf, sizeof(buf)), VTY_NEWLINE); } #endif /* HAVE_IPV6 */ - /* Line 3 display Origin, Med, Locpref, Weight, valid, Int/Ext/Local, Atomic, best */ + /* Line 3 display: + * Origin, Med, Locpref, Weight, valid, Int/Ext/Local, Atomic, best */ vty_out (vty, " Origin %s", bgp_origin_long_str[attr->origin]); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) @@ -6097,7 +6385,8 @@ route_vty_out_detail (struct vty *vty, struct bgp *bgp, struct prefix *p, vty_out (vty, ", internal"); else vty_out (vty, ", %s", - (bgp_confederation_peers_check(bgp, binfo->peer->as) ? "confed-external" : "external")); + (bgp_confederation_peers_check(bgp, binfo->peer->as) + ? "confed-external" : "external")); } else if (binfo->sub_type == BGP_ROUTE_AGGREGATE) vty_out (vty, ", aggregated, local"); @@ -6218,7 +6507,7 @@ bgp_show_table (struct vty *vty, struct bgp_table *table, struct in_addr *router { display = 0; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { if (type == bgp_show_type_flap_statistics || type == bgp_show_type_flap_address @@ -6455,8 +6744,8 @@ route_vty_out_detail_header (struct vty *vty, struct bgp *bgp, struct prefix *p; struct peer *peer; struct listnode *node, *nnode; - char buf1[INET6_ADDRSTRLEN]; - char buf2[INET6_ADDRSTRLEN]; + char buf[SU_ADDRSTRLEN]; + char buf_rd[RD_ADDRSTRLEN]; int count = 0; int best = 0; int suppress = 0; @@ -6468,12 +6757,12 @@ route_vty_out_detail_header (struct vty *vty, struct bgp *bgp, p = &rn->p; vty_out (vty, "BGP routing table entry for %s%s%s/%d%s", (safi == SAFI_MPLS_VPN ? - prefix_rd2str (prd, buf1, RD_ADDRSTRLEN) : ""), + prefix_rd2str (prd, buf_rd, sizeof(buf_rd)) : ""), safi == SAFI_MPLS_VPN ? ":" : "", - inet_ntop (p->family, &p->u.prefix, buf2, INET6_ADDRSTRLEN), + inet_ntop (p->family, &p->u.prefix, buf, sizeof(buf)), p->prefixlen, VTY_NEWLINE); - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { count++; if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED)) @@ -6518,8 +6807,9 @@ route_vty_out_detail_header (struct vty *vty, struct bgp *bgp, if (bgp_adj_out_lookup (peer, p, afi, safi, rn)) { if (! first) - vty_out (vty, " Advertised to non peer-group peers:%s ", VTY_NEWLINE); - vty_out (vty, " %s", sockunion2str (&peer->su, buf1, SU_ADDRSTRLEN)); + vty_out (vty, " Advertised to non peer-group peers:%s ", + VTY_NEWLINE); + vty_out (vty, " %s", sockunion2str (&peer->su, buf, sizeof(buf))); first = 1; } } @@ -6570,7 +6860,7 @@ bgp_show_route_in_table (struct vty *vty, struct bgp *bgp, if (prefix_check && rm->p.prefixlen != match.prefixlen) continue; - for (ri = rm->info; ri; ri = ri->next) + for (ri = rm->info; ri; ri = ri->info_next) { if (header) { @@ -6594,7 +6884,7 @@ bgp_show_route_in_table (struct vty *vty, struct bgp *bgp, { if (! prefix_check || rn->p.prefixlen == match.prefixlen) { - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { if (header) { @@ -9131,7 +9421,7 @@ bgp_table_stats_walker (struct thread *t) else if (prn->info) ts->counts[BGP_STATS_MAX_AGGREGATEABLE]++; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { rinum++; ts->counts[BGP_STATS_RIB]++; @@ -9417,11 +9707,11 @@ bgp_peer_count_walker (struct thread *t) struct bgp_adj_in *ain; struct bgp_info *ri; - for (ain = rn->adj_in; ain; ain = ain->next) + for (ain = rn->adj_in; ain; ain = ain->adj_next) if (ain->peer == peer) pc->count[PCOUNT_ADJ_IN]++; - for (ri = rn->info; ri; ri = ri->next) + for (ri = rn->info; ri; ri = ri->info_next) { char buf[SU_ADDRSTRLEN]; @@ -9642,7 +9932,7 @@ show_adj_route (struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn)) if (in) { - for (ain = rn->adj_in; ain; ain = ain->next) + for (ain = rn->adj_in; ain; ain = ain->adj_next) if (ain->peer == peer) { if (header1) @@ -9666,7 +9956,7 @@ show_adj_route (struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, } else { - for (adj = rn->adj_out; adj; adj = adj->next) + for (adj = rn->adj_out; adj; adj = adj->adj_next) if (adj->peer == peer) { if (header1) @@ -11369,12 +11659,12 @@ bgp_clear_damp_route (struct vty *vty, const char *view_name, { if (ri->extra && ri->extra->damp_info) { - ri_temp = ri->next; + ri_temp = ri->info_next; bgp_damp_info_free (ri->extra->damp_info, 1); ri = ri_temp; } else - ri = ri->next; + ri = ri->info_next; } } } @@ -11389,12 +11679,12 @@ bgp_clear_damp_route (struct vty *vty, const char *view_name, { if (ri->extra && ri->extra->damp_info) { - ri_temp = ri->next; + ri_temp = ri->info_next; bgp_damp_info_free (ri->extra->damp_info, 1); ri = ri_temp; } else - ri = ri->next; + ri = ri->info_next; } } } diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index c206d673..97ee39ab 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -44,26 +44,29 @@ struct bgp_info_extra struct bgp_info { - /* For linked list. */ - struct bgp_info *next; - struct bgp_info *prev; + /* For linked list. */ + struct bgp_node* rn ; + struct bgp_info *info_next; + struct bgp_info *info_prev; - /* Peer structure. */ + /* Peer structure. */ struct peer *peer; + struct bgp_info* routes_next ; + struct bgp_info* routes_prev ; - /* Attribute structure. */ + /* Attribute structure. */ struct attr *attr; - /* Extra information */ + /* Extra information */ struct bgp_info_extra *extra; - /* Uptime. */ + /* Uptime. */ time_t uptime; - /* reference count */ + /* reference count */ int lock; - /* BGP information status. */ + /* BGP information status. */ u_int16_t flags; #define BGP_INFO_IGP_CHANGED (1 << 0) #define BGP_INFO_DAMPED (1 << 1) @@ -177,9 +180,11 @@ extern void bgp_announce_route_all (struct peer *); extern void bgp_default_originate (struct peer *, afi_t, safi_t, int); extern void bgp_soft_reconfig_in (struct peer *, afi_t, safi_t); extern void bgp_soft_reconfig_rsclient (struct peer *, afi_t, safi_t); -extern void bgp_check_local_routes_rsclient (struct peer *rsclient, afi_t afi, safi_t safi); -extern void bgp_clear_route (struct peer *, afi_t, safi_t, - enum bgp_clear_route_type); +extern void bgp_check_local_routes_rsclient (struct peer *rsclient, + afi_t afi, safi_t safi); +extern void bgp_clear_route_normal(struct peer *peer, afi_t afi, safi_t safi) ; +extern void bgp_clear_route_rsclient(struct peer* rsclient, + afi_t afi, safi_t safi) ; extern void bgp_clear_route_all (struct peer *); extern void bgp_clear_adj_in (struct peer *, afi_t, safi_t); extern void bgp_clear_stale_route (struct peer *, afi_t, safi_t); @@ -197,13 +202,14 @@ extern int bgp_nlri_parse (struct peer *, struct attr *, struct bgp_nlri *); extern int bgp_maximum_prefix_overflow (struct peer *, afi_t, safi_t, int); -extern void bgp_redistribute_add (struct prefix *, struct in_addr *, u_int32_t, u_char); +extern void bgp_redistribute_add (struct prefix *, struct in_addr *, u_int32_t, + u_char); extern void bgp_redistribute_delete (struct prefix *, u_char); extern void bgp_redistribute_withdraw (struct bgp *, afi_t, int); extern void bgp_static_delete (struct bgp *); -extern void bgp_static_update (struct bgp *, struct prefix *, struct bgp_static *, - afi_t, safi_t); +extern void bgp_static_update (struct bgp *, struct prefix *, + struct bgp_static *, afi_t, safi_t); extern void bgp_static_withdraw (struct bgp *, struct prefix *, afi_t, safi_t); extern int bgp_static_set_vpnv4 (struct vty *vty, const char *, diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index aa7dbce1..64f2a90c 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -102,8 +102,8 @@ o Local extention set as-path exclude : Done match pathlimit as : Done -*/ - +*/ + /* Compiles either AS or TTL argument. It is amused the VTY code * has already range-checked the values to be suitable as TTL or ASN */ @@ -117,16 +117,16 @@ route_pathlimit_compile (const char *arg) /* TTL or AS value shoud be integer. */ if (! all_digit (arg)) return NULL; - + tmp = strtoul (arg, &endptr, 10); if (*endptr != '\0' || tmp == ULONG_MAX || tmp > UINT32_MAX) return NULL; - + if (!(val = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (u_int32_t)))) return NULL; - + *val = tmp; - + return val; } @@ -143,16 +143,16 @@ route_match_pathlimit_as (void *rule, struct prefix *prefix, route_map_object_t struct bgp_info *info = object; struct attr *attr = info->attr; uint32_t as = *(uint32_t *)rule; - + if (type != RMAP_BGP) return RMAP_NOMATCH; - + if (!attr->pathlimit.as) return RMAP_NOMATCH; - + if (as == attr->pathlimit.as) return RMAP_MATCH; - + return RMAP_NOMATCH; } @@ -173,7 +173,7 @@ route_set_pathlimit_ttl (void *rule, struct prefix *prefix, struct bgp_info *info = object; struct attr *attr = info->attr; u_char ttl = *(uint32_t *)rule; - + if (type == RMAP_BGP) { attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_AS_PATHLIMIT); @@ -185,14 +185,14 @@ route_set_pathlimit_ttl (void *rule, struct prefix *prefix, } /* Set local preference rule structure. */ -struct route_map_rule_cmd route_set_pathlimit_ttl_cmd = +struct route_map_rule_cmd route_set_pathlimit_ttl_cmd = { "pathlimit ttl", route_set_pathlimit_ttl, route_pathlimit_compile, route_pathlimit_free, }; - + /* 'match peer (A.B.C.D|X:X::X:X)' */ /* Compares the peer specified in the 'match peer' clause with the peer @@ -229,12 +229,12 @@ route_match_peer (void *rule, struct prefix *prefix, route_map_object_t type, ret = RMAP_MATCH; else ret = RMAP_NOMATCH; - + sockunion_free (su2); return ret; } sockunion_free (su2); - + if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP)) { if (sockunion_same (su, &peer->su)) @@ -294,7 +294,7 @@ struct route_map_rule_cmd route_match_peer_cmd = /* Match function should return 1 if match is success else return zero. */ static route_map_result_t -route_match_ip_address (void *rule, struct prefix *prefix, +route_match_ip_address (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; @@ -305,7 +305,7 @@ route_match_ip_address (void *rule, struct prefix *prefix, alist = access_list_lookup (AFI_IP, (char *) rule); if (alist == NULL) return RMAP_NOMATCH; - + return (access_list_apply (alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } @@ -335,12 +335,12 @@ struct route_map_rule_cmd route_match_ip_address_cmd = route_match_ip_address_compile, route_match_ip_address_free }; - + /* `match ip next-hop IP_ADDRESS' */ /* Match function return 1 if match is success else return zero. */ static route_map_result_t -route_match_ip_next_hop (void *rule, struct prefix *prefix, +route_match_ip_next_hop (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; @@ -387,12 +387,12 @@ struct route_map_rule_cmd route_match_ip_next_hop_cmd = route_match_ip_next_hop_compile, route_match_ip_next_hop_free }; - + /* `match ip route-source ACCESS-LIST' */ /* Match function return 1 if match is success else return zero. */ static route_map_result_t -route_match_ip_route_source (void *rule, struct prefix *prefix, +route_match_ip_route_source (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; @@ -445,11 +445,11 @@ struct route_map_rule_cmd route_match_ip_route_source_cmd = route_match_ip_route_source_compile, route_match_ip_route_source_free }; - + /* `match ip address prefix-list PREFIX_LIST' */ static route_map_result_t -route_match_ip_address_prefix_list (void *rule, struct prefix *prefix, +route_match_ip_address_prefix_list (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; @@ -459,7 +459,7 @@ route_match_ip_address_prefix_list (void *rule, struct prefix *prefix, plist = prefix_list_lookup (AFI_IP, (char *) rule); if (plist == NULL) return RMAP_NOMATCH; - + return (prefix_list_apply (plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } @@ -485,7 +485,7 @@ struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd = route_match_ip_address_prefix_list_compile, route_match_ip_address_prefix_list_free }; - + /* `match ip next-hop prefix-list PREFIX_LIST' */ static route_map_result_t @@ -532,7 +532,7 @@ struct route_map_rule_cmd route_match_ip_next_hop_prefix_list_cmd = route_match_ip_next_hop_prefix_list_compile, route_match_ip_next_hop_prefix_list_free }; - + /* `match ip route-source prefix-list PREFIX_LIST' */ static route_map_result_t @@ -585,12 +585,12 @@ struct route_map_rule_cmd route_match_ip_route_source_prefix_list_cmd = route_match_ip_route_source_prefix_list_compile, route_match_ip_route_source_prefix_list_free }; - + /* `match metric METRIC' */ /* Match function return 1 if match is success else return zero. */ static route_map_result_t -route_match_metric (void *rule, struct prefix *prefix, +route_match_metric (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { u_int32_t *med; @@ -600,7 +600,7 @@ route_match_metric (void *rule, struct prefix *prefix, { med = rule; bgp_info = object; - + if (bgp_info->attr->med == *med) return RMAP_MATCH; else @@ -620,12 +620,12 @@ route_match_metric_compile (const char *arg) tmpval = strtoul (arg, &endptr, 10); if (*endptr != '\0' || tmpval == ULONG_MAX || tmpval > UINT32_MAX) return NULL; - + med = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (u_int32_t)); - + if (!med) return med; - + *med = tmpval; return med; } @@ -645,15 +645,15 @@ struct route_map_rule_cmd route_match_metric_cmd = route_match_metric_compile, route_match_metric_free }; - + /* `match as-path ASPATH' */ /* Match function for as-path match. I assume given object is */ static route_map_result_t -route_match_aspath (void *rule, struct prefix *prefix, +route_match_aspath (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { - + struct as_list *as_list; struct bgp_info *bgp_info; @@ -662,9 +662,9 @@ route_match_aspath (void *rule, struct prefix *prefix, as_list = as_list_lookup ((char *) rule); if (as_list == NULL) return RMAP_NOMATCH; - + bgp_info = object; - + /* Perform match. */ return ((as_list_apply (as_list, bgp_info->attr->aspath) == AS_FILTER_DENY) ? RMAP_NOMATCH : RMAP_MATCH); } @@ -686,14 +686,14 @@ route_match_aspath_free (void *rule) } /* Route map commands for aspath matching. */ -struct route_map_rule_cmd route_match_aspath_cmd = +struct route_map_rule_cmd route_match_aspath_cmd = { "as-path", route_match_aspath, route_match_aspath_compile, route_match_aspath_free }; - + /* `match community COMMUNIY' */ struct rmap_community { @@ -703,14 +703,14 @@ struct rmap_community /* Match function for community match. */ static route_map_result_t -route_match_community (void *rule, struct prefix *prefix, +route_match_community (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct bgp_info *bgp_info; struct rmap_community *rcom; - if (type == RMAP_BGP) + if (type == RMAP_BGP) { bgp_info = object; rcom = rule; @@ -765,34 +765,34 @@ route_match_community_free (void *rule) { struct rmap_community *rcom = rule; - XFREE (MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE (MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE (MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Route map commands for community matching. */ -struct route_map_rule_cmd route_match_community_cmd = +struct route_map_rule_cmd route_match_community_cmd = { "community", route_match_community, route_match_community_compile, route_match_community_free }; - + /* Match function for extcommunity match. */ static route_map_result_t -route_match_ecommunity (void *rule, struct prefix *prefix, +route_match_ecommunity (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct bgp_info *bgp_info; - if (type == RMAP_BGP) + if (type == RMAP_BGP) { bgp_info = object; - + if (!bgp_info->attr->extra) return RMAP_NOMATCH; - + list = community_list_lookup (bgp_clist, (char *) rule, EXTCOMMUNITY_LIST_MASTER); if (! list) @@ -819,20 +819,20 @@ route_match_ecommunity_free (void *rule) } /* Route map commands for community matching. */ -struct route_map_rule_cmd route_match_ecommunity_cmd = +struct route_map_rule_cmd route_match_ecommunity_cmd = { "extcommunity", route_match_ecommunity, route_match_ecommunity_compile, route_match_ecommunity_free }; - + /* `match nlri` and `set nlri` are replaced by `address-family ipv4` and `address-family vpnv4'. */ - + /* `match origin' */ static route_map_result_t -route_match_origin (void *rule, struct prefix *prefix, +route_match_origin (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { u_char *origin; @@ -842,7 +842,7 @@ route_match_origin (void *rule, struct prefix *prefix, { origin = rule; bgp_info = object; - + if (bgp_info->attr->origin == *origin) return RMAP_MATCH; } @@ -896,7 +896,9 @@ route_set_ip_nexthop (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_ip_nexthop_set *rins = rule; +#if 0 /* see below */ struct in_addr peer_address; +#endif struct bgp_info *bgp_info; struct peer *peer; @@ -907,27 +909,35 @@ route_set_ip_nexthop (void *rule, struct prefix *prefix, if (rins->peer_address) { - if ((CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IN) || - CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) - && peer->su_remote + if ( (CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IN) || + CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IMPORT) ) + && peer->su_remote && sockunion_family (peer->su_remote) == AF_INET) { +#if 0 /* the following (a) appears redundant and (b) leaks memory */ inet_aton (sockunion_su2str (peer->su_remote), &peer_address); bgp_info->attr->nexthop = peer_address; +#else + bgp_info->attr->nexthop = peer->su_remote->sin.sin_addr ; +#endif bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_NEXT_HOP); } else if (CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_OUT) && peer->su_local && sockunion_family (peer->su_local) == AF_INET) { +#if 0 /* the following (a) appears redundant and (b) leaks memory */ inet_aton (sockunion_su2str (peer->su_local), &peer_address); bgp_info->attr->nexthop = peer_address; +#else + bgp_info->attr->nexthop = peer->su_local->sin.sin_addr ; +#endif bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_NEXT_HOP); } } else { - /* Set next hop value. */ + /* Set next hop value. */ bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_NEXT_HOP); bgp_info->attr->nexthop = *rins->address; } @@ -976,7 +986,7 @@ route_set_ip_nexthop_free (void *rule) if (rins->address) XFREE (MTYPE_ROUTE_MAP_COMPILED, rins->address); - + XFREE (MTYPE_ROUTE_MAP_COMPILED, rins); } @@ -988,7 +998,7 @@ struct route_map_rule_cmd route_set_ip_nexthop_cmd = route_set_ip_nexthop_compile, route_set_ip_nexthop_free }; - + /* `set local-preference LOCAL_PREF' */ /* Set local preference. */ @@ -1004,8 +1014,8 @@ route_set_local_pref (void *rule, struct prefix *prefix, /* Fetch routemap's rule information. */ local_pref = rule; bgp_info = object; - - /* Set local preference value. */ + + /* Set local preference value. */ bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_LOCAL_PREF); bgp_info->attr->local_pref = *local_pref; } @@ -1024,18 +1034,18 @@ route_set_local_pref_compile (const char *arg) /* Local preference value shoud be integer. */ if (! all_digit (arg)) return NULL; - + tmp = strtoul (arg, &endptr, 10); if (*endptr != '\0' || tmp == ULONG_MAX || tmp > UINT32_MAX) return NULL; - - local_pref = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (u_int32_t)); - + + local_pref = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (u_int32_t)); + if (!local_pref) return local_pref; - + *local_pref = tmp; - + return local_pref; } @@ -1047,14 +1057,14 @@ route_set_local_pref_free (void *rule) } /* Set local preference rule structure. */ -struct route_map_rule_cmd route_set_local_pref_cmd = +struct route_map_rule_cmd route_set_local_pref_cmd = { "local-preference", route_set_local_pref, route_set_local_pref_compile, route_set_local_pref_free, }; - + /* `set weight WEIGHT' */ /* Set weight. */ @@ -1070,8 +1080,8 @@ route_set_weight (void *rule, struct prefix *prefix, route_map_object_t type, /* Fetch routemap's rule information. */ weight = rule; bgp_info = object; - - /* Set weight value. */ + + /* Set weight value. */ if (*weight) (bgp_attr_extra_get (bgp_info->attr))->weight = *weight; else if (bgp_info->attr->extra) @@ -1097,14 +1107,14 @@ route_set_weight_compile (const char *arg) tmp = strtoul (arg, &endptr, 10); if (*endptr != '\0' || tmp == ULONG_MAX || tmp > UINT32_MAX) return NULL; - + weight = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (u_int32_t)); - + if (weight == NULL) return weight; - - *weight = tmp; - + + *weight = tmp; + return weight; } @@ -1116,19 +1126,19 @@ route_set_weight_free (void *rule) } /* Set local preference rule structure. */ -struct route_map_rule_cmd route_set_weight_cmd = +struct route_map_rule_cmd route_set_weight_cmd = { "weight", route_set_weight, route_set_weight_compile, route_set_weight_free, }; - + /* `set metric METRIC' */ /* Set metric to attribute. */ static route_map_result_t -route_set_metric (void *rule, struct prefix *prefix, +route_set_metric (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { char *metric; @@ -1214,14 +1224,14 @@ route_set_metric_free (void *rule) } /* Set metric rule structure. */ -struct route_map_rule_cmd route_set_metric_cmd = +struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_set_metric_compile, route_set_metric_free, }; - + /* `set as-path prepend ASPATH' */ /* For AS path prepend mechanism. */ @@ -1236,7 +1246,7 @@ route_set_aspath_prepend (void *rule, struct prefix *prefix, route_map_object_t { aspath = rule; binfo = object; - + if (binfo->attr->aspath->refcnt) new = aspath_dup (binfo->attr->aspath); else @@ -1270,14 +1280,14 @@ route_set_aspath_prepend_free (void *rule) } /* Set metric rule structure. */ -struct route_map_rule_cmd route_set_aspath_prepend_cmd = +struct route_map_rule_cmd route_set_aspath_prepend_cmd = { "as-path prepend", route_set_aspath_prepend, route_set_aspath_prepend_compile, route_set_aspath_prepend_free, }; - + /* `set as-path exclude ASn' */ /* For ASN exclude mechanism. @@ -1328,14 +1338,14 @@ route_set_aspath_exclude_free (void *rule) } /* Set ASn exlude rule structure. */ -struct route_map_rule_cmd route_set_aspath_exclude_cmd = +struct route_map_rule_cmd route_set_aspath_exclude_cmd = { "as-path exclude", route_set_aspath_exclude, route_set_aspath_exclude_compile, route_set_aspath_exclude_free, }; - + /* `set community COMMUNITY' */ struct rmap_com_set { @@ -1355,7 +1365,7 @@ route_set_community (void *rule, struct prefix *prefix, struct community *new = NULL; struct community *old; struct community *merge; - + if (type == RMAP_BGP) { rcs = rule; @@ -1375,8 +1385,8 @@ route_set_community (void *rule, struct prefix *prefix, if (rcs->additive && old) { merge = community_merge (community_dup (old), rcs->com); - - /* HACK: if the old community is not intern'd, + + /* HACK: if the old community is not intern'd, * we should free it here, or all reference to it may be lost. * Really need to cleanup attribute caching sometime. */ @@ -1387,7 +1397,7 @@ route_set_community (void *rule, struct prefix *prefix, } else new = community_dup (rcs->com); - + /* will be interned by caller if required */ attr->community = new; @@ -1406,7 +1416,7 @@ route_set_community_compile (const char *arg) char *sp; int additive = 0; int none = 0; - + if (strcmp (arg, "none") == 0) none = 1; else @@ -1428,12 +1438,12 @@ route_set_community_compile (const char *arg) if (! com) return NULL; } - + rcs = XCALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (struct rmap_com_set)); rcs->com = com; rcs->additive = additive; rcs->none = none; - + return rcs; } @@ -1449,14 +1459,14 @@ route_set_community_free (void *rule) } /* Set community rule structure. */ -struct route_map_rule_cmd route_set_community_cmd = +struct route_map_rule_cmd route_set_community_cmd = { "community", route_set_community, route_set_community_compile, route_set_community_free, }; - + /* `set comm-list (<1-99>|<100-500>|WORD) delete' */ /* For community set mechanism. */ @@ -1538,12 +1548,12 @@ struct route_map_rule_cmd route_set_community_delete_cmd = route_set_community_delete_compile, route_set_community_delete_free, }; - + /* `set extcommunity rt COMMUNITY' */ /* For community set mechanism. */ static route_map_result_t -route_set_ecommunity_rt (void *rule, struct prefix *prefix, +route_set_ecommunity_rt (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct ecommunity *ecom; @@ -1555,10 +1565,10 @@ route_set_ecommunity_rt (void *rule, struct prefix *prefix, { ecom = rule; bgp_info = object; - + if (! ecom) return RMAP_OKAY; - + /* We assume additive for Extended Community. */ old_ecom = (bgp_attr_extra_get (bgp_info->attr))->ecommunity; @@ -1598,7 +1608,7 @@ route_set_ecommunity_rt_free (void *rule) } /* Set community rule structure. */ -struct route_map_rule_cmd route_set_ecommunity_rt_cmd = +struct route_map_rule_cmd route_set_ecommunity_rt_cmd = { "extcommunity rt", route_set_ecommunity_rt, @@ -1610,7 +1620,7 @@ struct route_map_rule_cmd route_set_ecommunity_rt_cmd = /* For community set mechanism. */ static route_map_result_t -route_set_ecommunity_soo (void *rule, struct prefix *prefix, +route_set_ecommunity_soo (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct ecommunity *ecom; @@ -1620,10 +1630,10 @@ route_set_ecommunity_soo (void *rule, struct prefix *prefix, { ecom = rule; bgp_info = object; - + if (! ecom) return RMAP_OKAY; - + bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_EXT_COMMUNITIES); (bgp_attr_extra_get (bgp_info->attr))->ecommunity = ecommunity_dup (ecom); } @@ -1639,7 +1649,7 @@ route_set_ecommunity_soo_compile (const char *arg) ecom = ecommunity_str2com (arg, ECOMMUNITY_SITE_ORIGIN, 0); if (! ecom) return NULL; - + return ecom; } @@ -1652,14 +1662,14 @@ route_set_ecommunity_soo_free (void *rule) } /* Set community rule structure. */ -struct route_map_rule_cmd route_set_ecommunity_soo_cmd = +struct route_map_rule_cmd route_set_ecommunity_soo_cmd = { "extcommunity soo", route_set_ecommunity_soo, route_set_ecommunity_soo_compile, route_set_ecommunity_soo_free, }; - + /* `set origin ORIGIN' */ /* For origin set. */ @@ -1673,7 +1683,7 @@ route_set_origin (void *rule, struct prefix *prefix, route_map_object_t type, vo { origin = rule; bgp_info = object; - + bgp_info->attr->origin = *origin; } @@ -1706,14 +1716,14 @@ route_set_origin_free (void *rule) } /* Set metric rule structure. */ -struct route_map_rule_cmd route_set_origin_cmd = +struct route_map_rule_cmd route_set_origin_cmd = { "origin", route_set_origin, route_set_origin_compile, route_set_origin_free, }; - + /* `set atomic-aggregate' */ /* For atomic aggregate set. */ @@ -1747,14 +1757,14 @@ route_set_atomic_aggregate_free (void *rule) } /* Set atomic aggregate rule structure. */ -struct route_map_rule_cmd route_set_atomic_aggregate_cmd = +struct route_map_rule_cmd route_set_atomic_aggregate_cmd = { "atomic-aggregate", route_set_atomic_aggregate, route_set_atomic_aggregate_compile, route_set_atomic_aggregate_free, }; - + /* `set aggregator as AS A.B.C.D' */ struct aggregator { @@ -1763,7 +1773,7 @@ struct aggregator }; static route_map_result_t -route_set_aggregator_as (void *rule, struct prefix *prefix, +route_set_aggregator_as (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_info *bgp_info; @@ -1775,7 +1785,7 @@ route_set_aggregator_as (void *rule, struct prefix *prefix, bgp_info = object; aggregator = rule; ae = bgp_attr_extra_get (bgp_info->attr); - + ae->aggregator_as = aggregator->as; ae->aggregator_addr = aggregator->address; bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_AGGREGATOR); @@ -1806,19 +1816,19 @@ route_set_aggregator_as_free (void *rule) XFREE (MTYPE_ROUTE_MAP_COMPILED, rule); } -struct route_map_rule_cmd route_set_aggregator_as_cmd = +struct route_map_rule_cmd route_set_aggregator_as_cmd = { "aggregator as", route_set_aggregator_as, route_set_aggregator_as_compile, route_set_aggregator_as_free, }; - + #ifdef HAVE_IPV6 /* `match ipv6 address IP_ACCESS_LIST' */ static route_map_result_t -route_match_ipv6_address (void *rule, struct prefix *prefix, +route_match_ipv6_address (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; @@ -1828,7 +1838,7 @@ route_match_ipv6_address (void *rule, struct prefix *prefix, alist = access_list_lookup (AFI_IP6, (char *) rule); if (alist == NULL) return RMAP_NOMATCH; - + return (access_list_apply (alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } @@ -1855,11 +1865,11 @@ struct route_map_rule_cmd route_match_ipv6_address_cmd = route_match_ipv6_address_compile, route_match_ipv6_address_free }; - + /* `match ipv6 next-hop IP_ADDRESS' */ static route_map_result_t -route_match_ipv6_next_hop (void *rule, struct prefix *prefix, +route_match_ipv6_next_hop (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct in6_addr *addr; @@ -1869,10 +1879,10 @@ route_match_ipv6_next_hop (void *rule, struct prefix *prefix, { addr = rule; bgp_info = object; - + if (!bgp_info->attr->extra) return RMAP_NOMATCH; - + if (IPV6_ADDR_SAME (&bgp_info->attr->extra->mp_nexthop_global, rule)) return RMAP_MATCH; @@ -1917,11 +1927,11 @@ struct route_map_rule_cmd route_match_ipv6_next_hop_cmd = route_match_ipv6_next_hop_compile, route_match_ipv6_next_hop_free }; - + /* `match ipv6 address prefix-list PREFIX_LIST' */ static route_map_result_t -route_match_ipv6_address_prefix_list (void *rule, struct prefix *prefix, +route_match_ipv6_address_prefix_list (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; @@ -1931,7 +1941,7 @@ route_match_ipv6_address_prefix_list (void *rule, struct prefix *prefix, plist = prefix_list_lookup (AFI_IP6, (char *) rule); if (plist == NULL) return RMAP_NOMATCH; - + return (prefix_list_apply (plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } @@ -1957,12 +1967,12 @@ struct route_map_rule_cmd route_match_ipv6_address_prefix_list_cmd = route_match_ipv6_address_prefix_list_compile, route_match_ipv6_address_prefix_list_free }; - + /* `set ipv6 nexthop global IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static route_map_result_t -route_set_ipv6_nexthop_global (void *rule, struct prefix *prefix, +route_set_ipv6_nexthop_global (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct in6_addr *address; @@ -1973,10 +1983,10 @@ route_set_ipv6_nexthop_global (void *rule, struct prefix *prefix, /* Fetch routemap's rule information. */ address = rule; bgp_info = object; - - /* Set next hop value. */ + + /* Set next hop value. */ (bgp_attr_extra_get (bgp_info->attr))->mp_nexthop_global = *address; - + /* Set nexthop length. */ if (bgp_info->attr->extra->mp_nexthop_len == 0) bgp_info->attr->extra->mp_nexthop_len = 16; @@ -2021,12 +2031,12 @@ struct route_map_rule_cmd route_set_ipv6_nexthop_global_cmd = route_set_ipv6_nexthop_global_compile, route_set_ipv6_nexthop_global_free }; - + /* `set ipv6 nexthop local IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static route_map_result_t -route_set_ipv6_nexthop_local (void *rule, struct prefix *prefix, +route_set_ipv6_nexthop_local (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct in6_addr *address; @@ -2037,10 +2047,10 @@ route_set_ipv6_nexthop_local (void *rule, struct prefix *prefix, /* Fetch routemap's rule information. */ address = rule; bgp_info = object; - - /* Set next hop value. */ + + /* Set next hop value. */ (bgp_attr_extra_get (bgp_info->attr))->mp_nexthop_local = *address; - + /* Set nexthop length. */ if (bgp_info->attr->extra->mp_nexthop_len != 32) bgp_info->attr->extra->mp_nexthop_len = 32; @@ -2086,11 +2096,11 @@ struct route_map_rule_cmd route_set_ipv6_nexthop_local_cmd = route_set_ipv6_nexthop_local_free }; #endif /* HAVE_IPV6 */ - + /* `set vpnv4 nexthop A.B.C.D' */ static route_map_result_t -route_set_vpnv4_nexthop (void *rule, struct prefix *prefix, +route_set_vpnv4_nexthop (void *rule, struct prefix *prefix, route_map_object_t type, void *object) { struct in_addr *address; @@ -2101,8 +2111,8 @@ route_set_vpnv4_nexthop (void *rule, struct prefix *prefix, /* Fetch routemap's rule information. */ address = rule; bgp_info = object; - - /* Set next hop value. */ + + /* Set next hop value. */ (bgp_attr_extra_get (bgp_info->attr))->mp_nexthop_global_in = *address; } @@ -2142,7 +2152,7 @@ struct route_map_rule_cmd route_set_vpnv4_nexthop_cmd = route_set_vpnv4_nexthop_compile, route_set_vpnv4_nexthop_free }; - + /* `set originator-id' */ /* For origin set. */ @@ -2152,11 +2162,11 @@ route_set_originator_id (void *rule, struct prefix *prefix, route_map_object_t t struct in_addr *address; struct bgp_info *bgp_info; - if (type == RMAP_BGP) + if (type == RMAP_BGP) { address = rule; bgp_info = object; - + bgp_info->attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_ORIGINATOR_ID); (bgp_attr_extra_get (bgp_info->attr))->originator_id = *address; } @@ -2192,14 +2202,14 @@ route_set_originator_id_free (void *rule) } /* Set metric rule structure. */ -struct route_map_rule_cmd route_set_originator_id_cmd = +struct route_map_rule_cmd route_set_originator_id_cmd = { "originator-id", route_set_originator_id, route_set_originator_id_compile, route_set_originator_id_free, }; - + /* Add bgp route map rule. */ static int bgp_route_match_add (struct vty *vty, struct route_map_index *index, @@ -2318,11 +2328,11 @@ bgp_route_map_update (const char *unused) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { filter = &peer->filter[afi][safi]; - + for (direct = RMAP_IN; direct < RMAP_MAX; direct++) { if (filter->map[direct].name) - filter->map[direct].map = + filter->map[direct].map = route_map_lookup_by_name (filter->map[direct].name); else filter->map[direct].map = NULL; @@ -2340,11 +2350,11 @@ bgp_route_map_update (const char *unused) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { filter = &group->conf->filter[afi][safi]; - + for (direct = RMAP_IN; direct < RMAP_MAX; direct++) { if (filter->map[direct].name) - filter->map[direct].map = + filter->map[direct].map = route_map_lookup_by_name (filter->map[direct].name); else filter->map[direct].map = NULL; @@ -2398,7 +2408,7 @@ bgp_route_map_update (const char *unused) for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (bgp->rmap[ZEBRA_FAMILY_IPV4][i].name) - bgp->rmap[ZEBRA_FAMILY_IPV4][i].map = + bgp->rmap[ZEBRA_FAMILY_IPV4][i].map = route_map_lookup_by_name (bgp->rmap[ZEBRA_FAMILY_IPV4][i].name); #ifdef HAVE_IPV6 if (bgp->rmap[ZEBRA_FAMILY_IPV6][i].name) @@ -2408,7 +2418,7 @@ bgp_route_map_update (const char *unused) } } } - + DEFUN (match_peer, match_peer_cmd, "match peer (A.B.C.D|X:X::X:X)", @@ -2460,7 +2470,7 @@ ALIAS (no_match_peer, "Match peer address\n" "Static or Redistributed routes\n") -DEFUN (match_ip_address, +DEFUN (match_ip_address, match_ip_address_cmd, "match ip address (<1-199>|<1300-2699>|WORD)", MATCH_STR @@ -2473,7 +2483,7 @@ DEFUN (match_ip_address, return bgp_route_match_add (vty, vty->index, "ip address", argv[0]); } -DEFUN (no_match_ip_address, +DEFUN (no_match_ip_address, no_match_ip_address_cmd, "no match ip address", NO_STR @@ -2487,7 +2497,7 @@ DEFUN (no_match_ip_address, return bgp_route_match_delete (vty, vty->index, "ip address", argv[0]); } -ALIAS (no_match_ip_address, +ALIAS (no_match_ip_address, no_match_ip_address_val_cmd, "no match ip address (<1-199>|<1300-2699>|WORD)", NO_STR @@ -2498,7 +2508,7 @@ ALIAS (no_match_ip_address, "IP access-list number (expanded range)\n" "IP Access-list name\n") -DEFUN (match_ip_next_hop, +DEFUN (match_ip_next_hop, match_ip_next_hop_cmd, "match ip next-hop (<1-199>|<1300-2699>|WORD)", MATCH_STR @@ -2536,7 +2546,7 @@ ALIAS (no_match_ip_next_hop, "IP access-list number (expanded range)\n" "IP Access-list name\n") -DEFUN (match_ip_route_source, +DEFUN (match_ip_route_source, match_ip_route_source_cmd, "match ip route-source (<1-199>|<1300-2699>|WORD)", MATCH_STR @@ -2574,7 +2584,7 @@ ALIAS (no_match_ip_route_source, "IP access-list number (expanded range)\n" "IP standard access-list name\n") -DEFUN (match_ip_address_prefix_list, +DEFUN (match_ip_address_prefix_list, match_ip_address_prefix_list_cmd, "match ip address prefix-list WORD", MATCH_STR @@ -2611,7 +2621,7 @@ ALIAS (no_match_ip_address_prefix_list, "Match entries of prefix-lists\n" "IP prefix-list name\n") -DEFUN (match_ip_next_hop_prefix_list, +DEFUN (match_ip_next_hop_prefix_list, match_ip_next_hop_prefix_list_cmd, "match ip next-hop prefix-list WORD", MATCH_STR @@ -2648,7 +2658,7 @@ ALIAS (no_match_ip_next_hop_prefix_list, "Match entries of prefix-lists\n" "IP prefix-list name\n") -DEFUN (match_ip_route_source_prefix_list, +DEFUN (match_ip_route_source_prefix_list, match_ip_route_source_prefix_list_cmd, "match ip route-source prefix-list WORD", MATCH_STR @@ -2685,7 +2695,7 @@ ALIAS (no_match_ip_route_source_prefix_list, "Match entries of prefix-lists\n" "IP prefix-list name\n") -DEFUN (match_metric, +DEFUN (match_metric, match_metric_cmd, "match metric <0-4294967295>", MATCH_STR @@ -2716,7 +2726,7 @@ ALIAS (no_match_metric, "Match metric of route\n" "Metric value\n") -DEFUN (match_community, +DEFUN (match_community, match_community_cmd, "match community (<1-99>|<100-500>|WORD)", MATCH_STR @@ -2728,7 +2738,7 @@ DEFUN (match_community, return bgp_route_match_add (vty, vty->index, "community", argv[0]); } -DEFUN (match_community_exact, +DEFUN (match_community_exact, match_community_exact_cmd, "match community (<1-99>|<100-500>|WORD) exact-match", MATCH_STR @@ -2784,7 +2794,7 @@ ALIAS (no_match_community, "Community-list name\n" "Do exact matching of communities\n") -DEFUN (match_ecommunity, +DEFUN (match_ecommunity, match_ecommunity_cmd, "match extcommunity (<1-99>|<100-500>|WORD)", MATCH_STR @@ -2900,7 +2910,7 @@ DEFUN (set_ip_nexthop, vty_out (vty, "%% Malformed Next-hop address%s", VTY_NEWLINE); return CMD_WARNING; } - + return bgp_route_set_add (vty, vty->index, "ip next-hop", argv[0]); } @@ -3038,7 +3048,7 @@ DEFUN (no_set_weight, { if (argc == 0) return bgp_route_set_delete (vty, vty->index, "weight", NULL); - + return bgp_route_set_delete (vty, vty->index, "weight", argv[0]); } @@ -3463,7 +3473,7 @@ DEFUN (set_aggregator_as, char *argstr; VTY_GET_INTEGER_RANGE ("AS", as, argv[0], 1, BGP_AS4_MAX); - + ret = inet_aton (argv[1], &address); if (ret == 0) { @@ -3498,7 +3508,7 @@ DEFUN (no_set_aggregator_as, if (argv == 0) return bgp_route_set_delete (vty, vty->index, "aggregator as", NULL); - + VTY_GET_INTEGER_RANGE ("AS", as, argv[0], 1, BGP_AS4_MAX); ret = inet_aton (argv[1], &address); @@ -3530,9 +3540,9 @@ ALIAS (no_set_aggregator_as, "AS number\n" "IP address of aggregator\n") - + #ifdef HAVE_IPV6 -DEFUN (match_ipv6_address, +DEFUN (match_ipv6_address, match_ipv6_address_cmd, "match ipv6 address WORD", MATCH_STR @@ -3543,7 +3553,7 @@ DEFUN (match_ipv6_address, return bgp_route_match_add (vty, vty->index, "ipv6 address", argv[0]); } -DEFUN (no_match_ipv6_address, +DEFUN (no_match_ipv6_address, no_match_ipv6_address_cmd, "no match ipv6 address WORD", NO_STR @@ -3555,7 +3565,7 @@ DEFUN (no_match_ipv6_address, return bgp_route_match_delete (vty, vty->index, "ipv6 address", argv[0]); } -DEFUN (match_ipv6_next_hop, +DEFUN (match_ipv6_next_hop, match_ipv6_next_hop_cmd, "match ipv6 next-hop X:X::X:X", MATCH_STR @@ -3578,7 +3588,7 @@ DEFUN (no_match_ipv6_next_hop, return bgp_route_match_delete (vty, vty->index, "ipv6 next-hop", argv[0]); } -DEFUN (match_ipv6_address_prefix_list, +DEFUN (match_ipv6_address_prefix_list, match_ipv6_address_prefix_list_cmd, "match ipv6 address prefix-list WORD", MATCH_STR @@ -3663,7 +3673,7 @@ DEFUN (no_set_ipv6_nexthop_local, { if (argc == 0) return bgp_route_set_delete (vty, vty->index, "ipv6 next-hop local", NULL); - + return bgp_route_set_delete (vty, vty->index, "ipv6 next-hop local", argv[0]); } @@ -3731,7 +3741,7 @@ DEFUN (no_set_originator_id, { if (argc == 0) return bgp_route_set_delete (vty, vty->index, "originator-id", NULL); - + return bgp_route_set_delete (vty, vty->index, "originator-id", argv[0]); } @@ -3763,7 +3773,7 @@ DEFUN (no_set_pathlimit_ttl, { if (argc == 0) return bgp_route_set_delete (vty, vty->index, "pathlimit ttl", NULL); - + return bgp_route_set_delete (vty, vty->index, "pathlimit ttl", argv[0]); } @@ -3795,7 +3805,7 @@ DEFUN (no_match_pathlimit_as, { if (argc == 0) return bgp_route_match_delete (vty, vty->index, "pathlimit as", NULL); - + return bgp_route_match_delete (vty, vty->index, "pathlimit as", argv[0]); } @@ -3807,7 +3817,7 @@ ALIAS (no_match_pathlimit_as, "BGP AS-Pathlimit attribute\n" "Match Pathlimit ASN\n") - + /* Initialization of route map. */ void bgp_route_map_init (void) @@ -3944,7 +3954,7 @@ bgp_route_map_init (void) route_map_install_match (&route_match_ipv6_address_prefix_list_cmd); route_map_install_set (&route_set_ipv6_nexthop_global_cmd); route_map_install_set (&route_set_ipv6_nexthop_local_cmd); - + install_element (RMAP_NODE, &match_ipv6_address_cmd); install_element (RMAP_NODE, &no_match_ipv6_address_cmd); install_element (RMAP_NODE, &match_ipv6_next_hop_cmd); diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h index 53df0bc6..8cb1a9ed 100644 --- a/bgpd/bgp_table.h +++ b/bgpd/bgp_table.h @@ -30,18 +30,18 @@ typedef enum struct bgp_table { bgp_table_t type; - + /* afi/safi of this table */ afi_t afi; safi_t safi; - + int lock; /* The owner of this 'bgp_table' structure. */ struct peer *owner; struct bgp_node *top; - + unsigned long count; }; @@ -65,8 +65,8 @@ struct bgp_node int lock; - u_char flags; -#define BGP_NODE_PROCESS_SCHEDULED (1 << 0) + struct bgp_node* wq_next ; + uint8_t on_wq ; }; extern struct bgp_table *bgp_table_init (afi_t, safi_t); diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 272edfd9..0ff30632 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -57,7 +57,9 @@ extern struct in_addr router_id_zebra; afi_t bgp_node_afi (struct vty *vty) { - if (vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE) + enum node_type node = vty_get_node(vty) ; + + if (node == BGP_IPV6_NODE || node == BGP_IPV6M_NODE) return AFI_IP6; return AFI_IP; } @@ -67,9 +69,11 @@ bgp_node_afi (struct vty *vty) safi_t bgp_node_safi (struct vty *vty) { - if (vty->node == BGP_VPNV4_NODE) + enum node_type node = vty_get_node(vty) ; + + if (node == BGP_VPNV4_NODE) return SAFI_MPLS_VPN; - if (vty->node == BGP_IPV4M_NODE || vty->node == BGP_IPV6M_NODE) + if (node == BGP_IPV4M_NODE || node == BGP_IPV6M_NODE) return SAFI_MULTICAST; return SAFI_UNICAST; } @@ -345,7 +349,7 @@ DEFUN (router_bgp, return CMD_WARNING; } - vty->node = BGP_NODE; + vty_set_node(vty, BGP_NODE) ; vty->index = bgp; return CMD_SUCCESS; @@ -2203,7 +2207,7 @@ peer_rsclient_unset_vty (struct vty *vty, const char *peer_str, if ( ! peer_rsclient_active (peer) ) { - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_MY_RSCLIENT); + bgp_clear_route_rsclient (peer, afi, safi); listnode_delete (bgp->rsclient, peer); peer_unlock (peer); /* peer bgp rsclient reference */ } @@ -3963,7 +3967,7 @@ DEFUN (address_family_ipv4, "Enter Address Family command mode\n" "Address family\n") { - vty->node = BGP_IPV4_NODE; + vty_set_node(vty, BGP_IPV4_NODE) ; return CMD_SUCCESS; } @@ -3976,9 +3980,9 @@ DEFUN (address_family_ipv4_safi, "Address Family modifier\n") { if (strncmp (argv[0], "m", 1) == 0) - vty->node = BGP_IPV4M_NODE; + vty_set_node(vty, BGP_IPV4M_NODE) ; else - vty->node = BGP_IPV4_NODE; + vty_set_node(vty, BGP_IPV4_NODE) ; return CMD_SUCCESS; } @@ -3989,7 +3993,7 @@ DEFUN (address_family_ipv6, "Enter Address Family command mode\n" "Address family\n") { - vty->node = BGP_IPV6_NODE; + vty_set_node(vty, BGP_IPV6_NODE) ; return CMD_SUCCESS; } @@ -4002,9 +4006,9 @@ DEFUN (address_family_ipv6_safi, "Address Family modifier\n") { if (strncmp (argv[0], "m", 1) == 0) - vty->node = BGP_IPV6M_NODE; + vty_set_node(vty, BGP_IPV6M_NODE) ; else - vty->node = BGP_IPV6_NODE; + vty_set_node(vty, BGP_IPV6_NODE) ; return CMD_SUCCESS; } @@ -4015,7 +4019,7 @@ DEFUN (address_family_vpnv4, "Enter Address Family command mode\n" "Address family\n") { - vty->node = BGP_VPNV4_NODE; + vty_set_node(vty, BGP_VPNV4_NODE) ; return CMD_SUCCESS; } @@ -4031,12 +4035,14 @@ DEFUN (exit_address_family, "exit-address-family", "Exit from Address Family configuration mode\n") { - if (vty->node == BGP_IPV4_NODE - || vty->node == BGP_IPV4M_NODE - || vty->node == BGP_VPNV4_NODE - || vty->node == BGP_IPV6_NODE - || vty->node == BGP_IPV6M_NODE) - vty->node = BGP_NODE; + enum node_type node = vty_get_node(vty) ; + + if (node == BGP_IPV4_NODE + || node == BGP_IPV4M_NODE + || node == BGP_VPNV4_NODE + || node == BGP_IPV6_NODE + || node == BGP_IPV6M_NODE) + node = BGP_NODE; return CMD_SUCCESS; } @@ -7261,7 +7267,7 @@ static void bgp_show_peer (struct vty *vty, struct peer *p) { struct bgp *bgp; - char buf1[BUFSIZ]; + char buf[SU_ADDRSTRLEN]; char timebuf[BGP_UPTIME_LEN]; afi_t afi; safi_t safi; @@ -7298,7 +7304,7 @@ bgp_show_peer (struct vty *vty, struct peer *p) /* BGP Version. */ vty_out (vty, " BGP version 4"); vty_out (vty, ", remote router ID %s%s", - inet_ntop (AF_INET, &p->remote_id, buf1, BUFSIZ), + inet_ntop (AF_INET, &p->remote_id, buf, sizeof(buf)), VTY_NEWLINE); /* Confederation */ @@ -7522,7 +7528,7 @@ bgp_show_peer (struct vty *vty, struct peer *p) vty_out (vty, "%s", p->update_if); else if (p->update_source) vty_out (vty, "%s", - sockunion2str (p->update_source, buf1, SU_ADDRSTRLEN)); + sockunion2str (p->update_source, buf, sizeof(buf))); vty_out (vty, "%s", VTY_NEWLINE); } @@ -7572,7 +7578,7 @@ bgp_show_peer (struct vty *vty, struct peer *p) if (p->su_local) { vty_out (vty, "Local host: %s, Local port: %d%s", - sockunion2str (p->su_local, buf1, SU_ADDRSTRLEN), + sockunion2str (p->su_local, buf, sizeof(buf)), ntohs (p->su_local->sin.sin_port), VTY_NEWLINE); } @@ -7581,7 +7587,7 @@ bgp_show_peer (struct vty *vty, struct peer *p) if (p->su_remote) { vty_out (vty, "Foreign host: %s, Foreign port: %d%s", - sockunion2str (p->su_remote, buf1, SU_ADDRSTRLEN), + sockunion2str (p->su_remote, buf, sizeof(buf)), ntohs (p->su_remote->sin.sin_port), VTY_NEWLINE); } @@ -7590,14 +7596,14 @@ bgp_show_peer (struct vty *vty, struct peer *p) if (p->su_local) { vty_out (vty, "Nexthop: %s%s", - inet_ntop (AF_INET, &p->nexthop.v4, buf1, BUFSIZ), + inet_ntop (AF_INET, &p->nexthop.v4, buf, sizeof(buf)), VTY_NEWLINE); #ifdef HAVE_IPV6 vty_out (vty, "Nexthop global: %s%s", - inet_ntop (AF_INET6, &p->nexthop.v6_global, buf1, BUFSIZ), + inet_ntop (AF_INET6, &p->nexthop.v6_global, buf, sizeof(buf)), VTY_NEWLINE); vty_out (vty, "Nexthop local: %s%s", - inet_ntop (AF_INET6, &p->nexthop.v6_local, buf1, BUFSIZ), + inet_ntop (AF_INET6, &p->nexthop.v6_local, buf, sizeof(buf)), VTY_NEWLINE); vty_out (vty, "BGP connection: %s%s", p->shared_network ? "shared network" : "non shared network", diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 238bd01c..b8846609 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -946,7 +946,7 @@ peer_deactivate (struct peer *peer, afi_t afi, safi_t safi) bgp_capability_send (peer, afi, safi, CAPABILITY_CODE_MP, CAPABILITY_ACTION_UNSET); - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_NORMAL); + bgp_clear_route_normal(peer, afi, safi); peer->pcount[afi][safi] = 0; } else @@ -1470,7 +1470,7 @@ peer_group_bind (struct bgp *bgp, union sockunion *su, list_delete_node (bgp->rsclient, pn); /* Clear our own rsclient rib for this afi/safi. */ - bgp_clear_route (peer, afi, safi, BGP_CLEAR_ROUTE_MY_RSCLIENT); + bgp_clear_route_rsclient (peer, afi, safi); } bgp_table_finish (&peer->rib[afi][safi]); @@ -2004,10 +2004,10 @@ peer_flag_modify_action (struct peer *peer, u_int32_t flag) zlog_debug ("%s Maximum-prefix restart timer cancelled", peer->host); } - +#if 0 /* TODO: surely no need to peer_nsf_stop() twice ? */ if (CHECK_FLAG (peer->sflags, PEER_STATUS_NSF_WAIT)) peer_nsf_stop (peer); - +#endif bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); } @@ -4155,7 +4155,7 @@ bgp_config_write_peer (struct vty *vty, struct bgp *bgp, || sockunion_cmp (g_peer->update_source, peer->update_source) != 0) vty_out (vty, " neighbor %s update-source %s%s", addr, - sockunion2str (peer->update_source, buf, SU_ADDRSTRLEN), + sockunion2str (peer->update_source, buf, sizeof(buf)), VTY_NEWLINE); /* advertisement-interval */ @@ -4727,18 +4727,19 @@ bgp_terminate (int terminating, int retain_mode) } if (!retain_mode) - { - bgp_cleanup_routes (); + bgp_cleanup_routes (); - if (bm->process_main_queue) + for (ALL_LIST_ELEMENTS (bm->bgp, mnode, mnnode, bgp)) + { + if (bgp->process_main_queue) { - work_queue_free (bm->process_main_queue); - bm->process_main_queue = NULL; + work_queue_free (bgp->process_main_queue); + bgp->process_main_queue = NULL; } - if (bm->process_rsclient_queue) + if (bgp->process_rsclient_queue) { - work_queue_free (bm->process_rsclient_queue); - bm->process_rsclient_queue = NULL; + work_queue_free (bgp->process_rsclient_queue); + bgp->process_rsclient_queue = NULL; } } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 04121b74..d6ce07e3 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -39,10 +39,6 @@ struct bgp_master /* BGP thread master. */ struct thread_master *master; - /* work queues */ - struct work_queue *process_main_queue; - struct work_queue *process_rsclient_queue; - /* Listening sockets */ struct list *listen_sockets; @@ -86,6 +82,10 @@ struct bgp /* BGP route-server-clients. */ struct list *rsclient; + /* work queues */ + struct work_queue *process_main_queue; + struct work_queue *process_rsclient_queue; + /* BGP configuration. */ u_int16_t config; #define BGP_CONFIG_ROUTER_ID (1 << 0) diff --git a/lib/Makefile.am b/lib/Makefile.am index 9f21a377..17940f8d 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -14,7 +14,8 @@ 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 + 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 BUILT_SOURCES = memtypes.h route_types.h @@ -32,7 +33,8 @@ pkginclude_HEADERS = \ 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 + 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 EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt diff --git a/lib/buffer.c b/lib/buffer.c index f19a9e0c..b81924ec 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -1,5 +1,5 @@ /* - * Buffering of output and input. + * Buffering of output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -8,7 +8,7 @@ * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. - * + * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Boston, MA 02111-1307, USA. */ #include <zebra.h> @@ -29,18 +29,6 @@ #include <stddef.h> - -/* Buffer master. */ -struct buffer -{ - /* Data list. */ - struct buffer_data *head; - struct buffer_data *tail; - - /* Size of each buffer_data chunk. */ - size_t size; -}; - /* Data container. */ struct buffer_data { @@ -67,11 +55,12 @@ struct buffer_data /* Make new buffer. */ struct buffer * -buffer_new (size_t size) +buffer_init_new (struct buffer* b, size_t size) { - struct buffer *b; - - b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer)); + if (b == NULL) + b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer)); + else + memset(b, 0, sizeof (struct buffer)) ; if (size) b->size = size; @@ -89,6 +78,13 @@ buffer_new (size_t size) return b; } +/* Make new buffer. */ +struct buffer * +buffer_new (size_t size) +{ + return buffer_init_new(NULL, size); +} + /* Free buffer. */ void buffer_free (struct buffer *b) @@ -133,7 +129,7 @@ buffer_reset (struct buffer *b) { struct buffer_data *data; struct buffer_data *next; - + for (data = b->head; data; data = next) { next = data->next; @@ -148,7 +144,8 @@ buffer_add (struct buffer *b) { struct buffer_data *d; - d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(struct buffer_data, data[b->size])); + typedef struct buffer_data buffer_data_t ; /* stop Eclipse whinging */ + d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(buffer_data_t, data[b->size])); d->cp = d->sp = 0; d->next = NULL; @@ -169,7 +166,7 @@ buffer_put(struct buffer *b, const void *p, size_t size) const char *ptr = p; /* We use even last one byte of data buffer. */ - while (size) + while (size) { size_t chunk; @@ -226,7 +223,7 @@ buffer_flush_all (struct buffer *b, int fd) /* Flush enough data to fill a terminal window of the given scene (used only by vty telnet interface). */ buffer_status_t -buffer_flush_window (struct buffer *b, int fd, int width, int height, +buffer_flush_window (struct buffer *b, int fd, int width, int height, int erase_flag, int no_more_flag) { int nbytes; diff --git a/lib/buffer.h b/lib/buffer.h index 6c3dc76a..132fe155 100644 --- a/lib/buffer.h +++ b/lib/buffer.h @@ -1,5 +1,5 @@ /* - * Buffering to output and input. + * Buffering to output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. @@ -23,11 +23,22 @@ #ifndef _ZEBRA_BUFFER_H #define _ZEBRA_BUFFER_H +/* Buffer master. */ +struct buffer +{ + /* Data list. */ + struct buffer_data *head; + struct buffer_data *tail; + + /* Size of each buffer_data chunk. */ + size_t size; +}; /* Create a new buffer. Memory will be allocated in chunks of the given size. If the argument is 0, the library will supply a reasonable default size suitable for buffering socket I/O. */ extern struct buffer *buffer_new (size_t); +struct buffer *buffer_init_new (struct buffer* b, size_t size) ; /* Free all data in the buffer. */ extern void buffer_reset (struct buffer *); @@ -73,7 +84,7 @@ typedef enum extern buffer_status_t buffer_write(struct buffer *, int fd, const void *, size_t); -/* This function attempts to flush some (but perhaps not all) of +/* This function attempts to flush some (but perhaps not all) of the queued data to the given file descriptor. */ extern buffer_status_t buffer_flush_available(struct buffer *, int fd); @@ -88,7 +99,7 @@ extern buffer_status_t buffer_flush_all (struct buffer *, int fd); /* Attempt to write enough data to the given fd to fill a window of the given width and height (and remove the data written from the buffer). - If !no_more, then a message saying " --More-- " is appended. + If !no_more, then a message saying " --More-- " is appended. If erase is true, then first overwrite the previous " --More-- " message with spaces. diff --git a/lib/command.c b/lib/command.c index 2c9ca64a..251c8963 100644 --- a/lib/command.c +++ b/lib/command.c @@ -30,6 +30,8 @@ Boston, MA 02111-1307, USA. */ #include "thread.h" #include "vector.h" #include "vty.h" +#include "uty.h" +#include "qstring.h" #include "command.h" #include "workqueue.h" #include "command_queue.h" @@ -242,10 +244,29 @@ sort_node () } } -/* Breaking up string into each command piece. I assume given - character is separated by a space character. Return value is a - vector which includes char ** data element. */ -vector +/*------------------------------------------------------------------------------ + * Take string and break it into tokens. + * + * Discards leading and trailing white-space. + * + * Treats lines that start with '!' or '#' (after any leading white-space) + * as empty -- these are comment lines. + * + * Tokens are non-whitespace separated by one or more white-space. + * + * White-space is anything that isspace() thinks is a space. (Which in the + * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) + * + * Returns: NULL => empty line (after white-space trimming) or comment line. + * otherwise: is vector containing one or more tokens. + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the caller's responsibility to release the vector and its contents, + * see cmd_free_strvec(). + */ +extern vector cmd_make_strvec (const char *string) { const char *cp, *start; @@ -258,58 +279,69 @@ cmd_make_strvec (const char *string) cp = string; - /* Skip white spaces. */ - while (isspace ((int) *cp) && *cp != '\0') + /* Skip white spaces. */ + while (isspace((int) *cp)) cp++; - /* Return if there is only white spaces */ - if (*cp == '\0') + /* Return if line is empty or effectively empty */ + if ((*cp == '\0') || (*cp == '!') || (*cp == '#')) return NULL; - if (*cp == '!' || *cp == '#') - return NULL; + /* Prepare return vector -- expect some reasonable number of tokens. */ + strvec = vector_init (10) ; - /* Prepare return vector. */ - strvec = vector_init (0); - - /* Copy each command piece and set into vector. */ + /* Copy each command piece and set into vector. */ while (1) { start = cp; - while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') && - *cp != '\0') - cp++; + while (!isspace((int) *cp) && (*cp != '\0')) + cp++ ; /* eat token characters */ strlen = cp - start; token = XMALLOC (MTYPE_STRVEC, strlen + 1); memcpy (token, start, strlen); *(token + strlen) = '\0'; - vector_set (strvec, token); + vector_push_item(strvec, token); - while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') && - *cp != '\0') - cp++; + while (isspace((int) *cp)) + cp++ ; /* skip white-space */ if (*cp == '\0') return strvec; } } -/* Free allocated string vector. */ -void -cmd_free_strvec (vector v) +/*------------------------------------------------------------------------------ + * Add given string to vector of strings. + * + * Create vector if required. + */ +extern vector +cmd_add_to_strvec (vector strvec, const char* str) { - unsigned int i; - char *cp; + if (strvec == NULL) + strvec = vector_init(1) ; - if (!v) - return; + vector_push_item(strvec, XSTRDUP(MTYPE_STRVEC, str)); - for (i = 0; i < vector_active (v); i++) - if ((cp = vector_slot (v, i)) != NULL) - XFREE (MTYPE_STRVEC, cp); + return strvec ; +} ; - vector_free (v); -} +/*------------------------------------------------------------------------------ + * Free allocated string vector (if any) and all its contents. + * + * Note that this is perfectly happy with strvec == NULL. + */ +extern void +cmd_free_strvec (vector strvec) +{ + char *cp; + + /* Note that vector_ream_free() returns NULL if strvec == NULL */ + while((cp = vector_ream_free(strvec)) != NULL) + XFREE (MTYPE_STRVEC, cp); +} ; + +/*----------------------------------------------------------------------------*/ /* Fetch next description. Used in cmd_make_descvec(). */ static char * @@ -472,7 +504,19 @@ cmd_prompt (enum node_type node) { struct cmd_node *cnode; - cnode = vector_slot (cmdvec, node); + assert(cmdvec != NULL) ; + assert(cmdvec->p_items != NULL) ; + + cnode = NULL ; + if (node < cmdvec->limit) + cnode = vector_slot (cmdvec, node); + + if (cnode == NULL) + { + zlog_err("Could not find prompt for node %d for", node) ; + return NULL ; + } ; + return cnode->prompt; } @@ -683,21 +727,41 @@ cmd_filter_by_symbol (char *command, char *symbol) } #endif +/*============================================================================== + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? + */ + /* Completion match types. */ enum match_type { - no_match, + no_match, /* nope */ extend_match, + ipv4_prefix_match, ipv4_match, ipv6_prefix_match, ipv6_match, range_match, vararg_match, - partly_match, - exact_match + + partly_match, /* OK as far as it went */ + exact_match /* Syntactically complete */ }; +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address: + * + * 999.999.999.999 -- where no part may be > 255 + * + * TODO: cmd_ipv4_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ static enum match_type cmd_ipv4_match (const char *str) { @@ -755,6 +819,22 @@ cmd_ipv4_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv4 Prefix: + * + * 999.999.999.999/99 -- where no part may be > 255, + * and prefix length may not be > 32 + * + * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? + * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ static enum match_type cmd_ipv4_prefix_match (const char *str) { @@ -834,6 +914,16 @@ cmd_ipv4_prefix_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address: + * + * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + */ + #define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" #define STATE_START 1 @@ -952,6 +1042,19 @@ cmd_ipv6_match (const char *str) return exact_match; } +/*------------------------------------------------------------------------------ + * Is this an IPv6 Prefix: + * + * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? + * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? + * + * Returns: no_match -- improperly formed + * partly_match -- accepts empty string + * exact_match -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ static enum match_type cmd_ipv6_prefix_match (const char *str) { @@ -1085,6 +1188,14 @@ cmd_ipv6_prefix_match (const char *str) #endif /* HAVE_IPV6 */ +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: 1 => OK -- *including* empty string + * 0 => not a valid number, or not in required range + * (or invalid range !!) + */ + #define DECIMAL_STRLEN_MAX 10 static int @@ -1132,124 +1243,182 @@ cmd_range_match (const char *range, const char *str) return 1; } -/* Make completion match and return match type flag. */ +/*============================================================================== + * Command "filtering". + * + * The command parsing process starts with a (shallow) copy of the cmd_vector + * entry for the current "node". + * + * So cmd_v contains pointers to struct cmd_element values. When match fails, + * the pointer is set NULL -- so parsing is a process of reducing the cmd_v + * down to just the entries that match. + * + * Each cmd_element has a vector "strvec", which contains an entry for each + * "token" position. That entry is a vector containing the possible values at + * that position. + * + * + */ + +/*------------------------------------------------------------------------------ + * Make completion match and return match type flag. + * + * Takes: command -- address of candidate token + * cmd_v -- vector of commands that is being reduced/filtered + * index -- index of token (position in line -- 0 == first) + * + * Returns: any of the enum match_type values: + * + * no_match => no match of any kind + * + * extend_match => saw an optional token + * ipv4_prefix_match ) + * ipv4_match ) + * ipv6_prefix_match ) saw full or partial match for this + * ipv6_match ) + * range_match ) + * vararg_match ) + * + * partly_match => saw partial match for a keyword + * exact_match => saw exact match for a keyword + * + * Note that these return values are in ascending order of preference. So, + * if there are multiple possibilities at this position, will return the one + * furthest down this list. + */ static enum match_type -cmd_filter_by_completion (char *command, vector v, unsigned int index) +cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) { unsigned int i; - const char *str; - struct cmd_element *cmd_element; enum match_type match_type; - vector descvec; - struct desc *desc; match_type = no_match; /* If command and cmd_element string does not match set NULL to vector */ - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) - { - if (index >= vector_active (cmd_element->strvec)) - vector_slot (v, i) = NULL; - else - { - unsigned int j; - int matched = 0; + for (i = 0; i < vector_active (cmd_v); i++) + { + const char *str; + struct cmd_element *cmd_element; + vector descvec; + struct desc *desc; + unsigned int j; + int matched ; - descvec = vector_slot (cmd_element->strvec, index); + /* Skip past cmd_v entries that have already been set NULL */ + if ((cmd_element = vector_slot (cmd_v, i)) == NULL) + continue ; - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - str = desc->cmd; + /* Discard cmd_v entry that has no token at the current position */ + if (index >= vector_active (cmd_element->strvec)) + { + vector_slot (cmd_v, i) = NULL; + continue ; + } ; - 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; + /* See if get any sort of match at current position */ + matched = 0 ; + descvec = vector_slot (cmd_element->strvec, index); - matched++; - } - } + for (j = 0; j < vector_active (descvec); j++) + if ((desc = vector_slot (descvec, j))) + { + str = desc->cmd; + + if (CMD_VARARG (str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE (str)) + { + if (cmd_range_match (str, command)) + { + if (match_type < range_match) + match_type = range_match; + + matched++; + } + } #ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command)) - { - if (match_type < ipv6_match) - match_type = ipv6_match; - - matched++; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command)) - { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; - - matched++; - } - } + else if (CMD_IPV6 (str)) + { + if (cmd_ipv6_match (command)) + { + if (match_type < ipv6_match) + match_type = ipv6_match; + + matched++; + } + } + else if (CMD_IPV6_PREFIX (str)) + { + if (cmd_ipv6_prefix_match (command)) + { + if (match_type < ipv6_prefix_match) + match_type = ipv6_prefix_match; + + matched++; + } + } #endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command)) - { - if (match_type < ipv4_match) - match_type = ipv4_match; + else if (CMD_IPV4 (str)) + { + if (cmd_ipv4_match (command)) + { + if (match_type < ipv4_match) + match_type = ipv4_match; + + matched++; + } + } + else if (CMD_IPV4_PREFIX (str)) + { + if (cmd_ipv4_prefix_match (command)) + { + if (match_type < ipv4_prefix_match) + match_type = ipv4_prefix_match; + matched++; + } + } + else if (CMD_OPTION (str) || CMD_VARIABLE (str)) + /* Check is this point's argument optional ? */ + { + if (match_type < extend_match) + match_type = extend_match; + matched++; + } + else if (strncmp (command, str, strlen (command)) == 0) + { + if (strcmp (command, str) == 0) + match_type = exact_match; + else + { + if (match_type < partly_match) + match_type = partly_match; + } + matched++; + } ; + } ; + + /* Discard cmd_v entry that has no match at this position */ + if (!matched) + vector_slot (cmd_v, i) = NULL; + } - matched++; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command)) - { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; - } - } - else - /* Check is this point's argument optional ? */ - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (match_type < extend_match) - match_type = extend_match; - matched++; - } - else if (strncmp (command, str, strlen (command)) == 0) - { - if (strcmp (command, str) == 0) - match_type = exact_match; - else - { - if (match_type < partly_match) - match_type = partly_match; - } - matched++; - } - } - if (!matched) - vector_slot (v, i) = NULL; - } - } return match_type; } -/* Filter vector by command character with index. */ +/*------------------------------------------------------------------------------ + * Filter vector by command character with index. + * + * This appears to be identical to cmd_filter_by_completion(), except that + * when matching keywords, requires an exact match. + * + * TODO: see if can merge cmd_filter_by_completion() & cmd_filter_by_string() + */ static enum match_type -cmd_filter_by_string (char *command, vector v, unsigned int index) +cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) { unsigned int i; const char *str; @@ -1261,13 +1430,13 @@ cmd_filter_by_string (char *command, vector v, unsigned int index) match_type = no_match; /* If command and cmd_element string does not match set NULL to vector */ - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_active (cmd_v); i++) + if ((cmd_element = vector_slot (cmd_v, i)) != NULL) { /* If given index is bigger than max string vector of command, set NULL */ if (index >= vector_active (cmd_element->strvec)) - vector_slot (v, i) = NULL; + vector_slot (cmd_v, i) = NULL; else { unsigned int j; @@ -1349,15 +1518,49 @@ cmd_filter_by_string (char *command, vector v, unsigned int index) } } if (!matched) - vector_slot (v, i) = NULL; + vector_slot (cmd_v, i) = NULL; } } return match_type; } -/* Check ambiguous match */ +/*------------------------------------------------------------------------------ + * Check for ambiguous match + * + * Given the best that cmd_filter_by_completion() or cmd_filter_by_string() + * found, check as follows: + * + * 1. discard all commands for which do not have the type of match selected. + * + * See above for the ranking of matches. + * + * 2. for "partial match", look out for matching more than one keyword, and + * return 1 if finds that. + * + * 3. for "range match", look out for matching more than one range, and + * return 1 if finds that. + * + * 4. for ipv4_prefix_match and ipv6_prefix_match, if get a "partial match", + * return 2. + * + * This appears to catch things which are supposed to be prefixes, but + * do not have a '/' or do not have any digits after the '/'. + * + * Takes: command -- address of candidate token + * cmd_v -- vector of commands that is being reduced/filtered + * index -- index of token (position in line -- 0 == first) + * type -- as returned by cmd_filter_by_completion() + * or cmd_filter_by_string() + * + * Returns: 0 => not ambiguous + * 1 => ambiguous -- the candidate token matches more than one + * keyword, or the candidate number matches more + * than one number range. + * 2 => partial match for ipv4_prefix or ipv6_prefix + * (missing '/' or no digits after '/'). + */ static int -is_cmd_ambiguous (char *command, vector v, int index, enum match_type type) +is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) { unsigned int i; unsigned int j; @@ -1367,8 +1570,8 @@ is_cmd_ambiguous (char *command, vector v, int index, enum match_type type) vector descvec; struct desc *desc; - for (i = 0; i < vector_active (v); i++) - if ((cmd_element = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_active (cmd_v); i++) + if ((cmd_element = vector_slot (cmd_v, i)) != NULL) { int match = 0; @@ -1447,25 +1650,27 @@ is_cmd_ambiguous (char *command, vector v, int index, enum match_type type) } } if (!match) - vector_slot (v, i) = NULL; + vector_slot (cmd_v, i) = NULL; } return 0; } -/* If src matches dst return dst string, otherwise return NULL */ +/*------------------------------------------------------------------------------ + * If src matches dst return dst string, otherwise return NULL + * + * Returns NULL if dst is an option, variable of vararg. + * + * NULL or empty src are deemed to match. + */ static const char * cmd_entry_function (const char *src, const char *dst) { - /* Skip variable arguments. */ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst) || - CMD_IPV4 (dst) || CMD_IPV4_PREFIX (dst) || CMD_RANGE (dst)) + if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) return NULL; - /* In case of 'command \t', given src is NULL string. */ - if (src == NULL) + if ((src == NULL) || (*src == '\0')) return dst; - /* Matched with input string. */ if (strncmp (src, dst, strlen (src)) == 0) return dst; @@ -1537,8 +1742,12 @@ cmd_entry_function_desc (const char *src, const char *dst) return NULL; } -/* Check same string element existence. If it isn't there return - 1. */ +/*------------------------------------------------------------------------------ + * Check same string element existence. + * + * Returns: 0 => found same string in the vector + * 1 => NOT found same string in the vector + */ static int cmd_unique_string (vector v, const char *str) { @@ -1569,15 +1778,9 @@ desc_unique_string (vector v, const char *str) static int cmd_try_do_shortcut (enum node_type node, char* first_word) { - if ( first_word != NULL && - node != AUTH_NODE && - node != VIEW_NODE && - node != AUTH_ENABLE_NODE && - node != ENABLE_NODE && - node != RESTRICTED_NODE && - 0 == strcmp( "do", first_word ) ) - return 1; - return 0; + return (node >= MIN_DO_SHORTCUT_NODE) + && (first_word != NULL) + && (strcmp( "do", first_word) == 0) ? 1 : 0 ; } /* '?' describe command support. */ @@ -1717,6 +1920,19 @@ cmd_describe_command_real (vector vline, int node, int *status) return matchvec; } +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct desc" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ vector cmd_describe_command (vector vline, int node, int *status) { @@ -1745,134 +1961,166 @@ cmd_describe_command (vector vline, int node, int *status) return cmd_describe_command_real (vline, node, status); } - -/* Check LCD of matched command. */ +/*------------------------------------------------------------------------------ + * Check LCD of matched command. + * + * Scan list of matched keywords, and by comparing them pair-wise, find the + * longest common leading substring. + * + * Returns: 0 if zero or one matched keywords + * length of longest common leading substring, otherwise. + */ static int -cmd_lcd (char **matched) +cmd_lcd (vector matchvec) { - int i; - int j; - int lcd = -1; - char *s1, *s2; - char c1, c2; + int n ; + int i ; + int lcd ; + char *sp, *sq, *ss ; - if (matched[0] == NULL || matched[1] == NULL) - return 0; + n = vector_end(matchvec) ; + if (n < 2) + return 0 ; - for (i = 1; matched[i] != NULL; i++) + ss = vector_get_item(matchvec, 0) ; + lcd = strlen(ss) ; + + for (i = 1 ; i < n ; i++) { - s1 = matched[i - 1]; - s2 = matched[i]; + sq = ss ; + ss = vector_get_item(matchvec, i) ; + sp = ss ; - for (j = 0; (c1 = s1[j]) && (c2 = s2[j]); j++) - if (c1 != c2) - break; + while ((*sp == *sq) && (*sp != '\0')) + { + ++sp ; + ++sq ; + } ; - if (lcd < 0) - lcd = j; - else - { - if (lcd > j) - lcd = j; - } + if (lcd > (sp - ss)) + lcd = (sp - ss) ; } return lcd; } -/* Command line completion support. */ -static char ** +/*------------------------------------------------------------------------------ + * Command line completion support. + */ +static vector cmd_complete_command_real (vector vline, int node, int *status) { unsigned int i; - vector cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); + unsigned int ivl ; + unsigned int last_ivl ; + vector cmd_v ; #define INIT_MATCHVEC_SIZE 10 vector matchvec; struct cmd_element *cmd_element; unsigned int index; - char **match_str; struct desc *desc; vector descvec; - char *command; - int lcd; + char *token; + int n ; + /* Stop immediately if the vline is empty. */ if (vector_active (vline) == 0) { - vector_free (cmd_vector); *status = CMD_ERR_NO_MATCH; return NULL; } - else - index = vector_active (vline) - 1; - /* First, filter by preceeding command string */ - for (i = 0; i < index; i++) - if ((command = vector_slot (vline, i))) - { - enum match_type match; - int ret; + /* Take (shallow) copy of cmdvec for given node. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); - /* First try completion match, if there is exactly match return 1 */ - match = cmd_filter_by_completion (command, cmd_vector, i); + /* First, filter upto, but excluding last token */ + last_ivl = vector_active (vline) - 1; - /* If there is exact match then filter ambiguous match else check - ambiguousness. */ - if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1) - { - vector_free (cmd_vector); - *status = CMD_ERR_AMBIGUOUS; - return NULL; - } - /* - else if (ret == 2) - { - vector_free (cmd_vector); + for (ivl = 0; ivl < last_ivl; ivl++) + { + enum match_type match; + int ret; + + /* TODO: does this test make any sense ? */ + if ((token = vector_slot (vline, ivl)) == NULL) + continue ; + + /* First try completion match, return best kind of match */ + index = ivl ; + match = cmd_filter_by_completion (token, cmd_v, index) ; + + /* Eliminate all but the selected kind of match */ + ret = is_cmd_ambiguous (token, cmd_v, index, match) ; + + if (ret == 1) + { + /* ret == 1 => either token matches more than one keyword + * or token matches more than one number range + */ + vector_free (cmd_v); + *status = CMD_ERR_AMBIGUOUS; + return NULL; + } +#if 0 + /* For command completion purposes do not appear to care about + * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). + */ + else if (ret == 2) + { + vector_free (cmd_v); *status = CMD_ERR_NO_MATCH; return NULL; - } - */ + } +#endif } - /* Prepare match vector. */ + /* Prepare match vector. */ matchvec = vector_init (INIT_MATCHVEC_SIZE); - /* Now we got into completion */ - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i))) - { - const char *string; - vector strvec = cmd_element->strvec; + /* Now we got into completion */ + index = last_ivl ; + token = vector_slot(vline, last_ivl) ; /* is now the last token */ - /* Check field length */ - if (index >= vector_active (strvec)) - vector_slot (cmd_vector, i) = NULL; - else - { - unsigned int j; + for (i = 0; i < vector_active (cmd_v); i++) + { + unsigned int j; + const char *string; - descvec = vector_slot (strvec, index); - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - if ((string = - cmd_entry_function (vector_slot (vline, index), - desc->cmd))) - if (cmd_unique_string (matchvec, string)) - vector_set (matchvec, XSTRDUP (MTYPE_TMP, string)); - } - } - } + if ((cmd_element = vector_slot (cmd_v, i)) == NULL) + continue ; - /* We don't need cmd_vector any more. */ - vector_free (cmd_vector); + vector strvec = cmd_element->strvec; + + /* Check field length */ + if (index >= vector_active (strvec)) + { + vector_slot (cmd_v, i) = NULL; + continue ; + } + + descvec = vector_slot (strvec, index); + + for (j = 0; j < vector_active (descvec); j++) + if ((desc = vector_slot (descvec, j)) != NULL) + { + string = cmd_entry_function(token, desc->cmd) ; + if ((string != NULL) && cmd_unique_string(matchvec, string)) + cmd_add_to_strvec (matchvec, string) ; + } + } ; + + n = vector_end(matchvec) ; /* number of entries in the matchvec */ + + /* We don't need cmd_v any more. */ + vector_free (cmd_v); /* No matched command */ - if (vector_slot (matchvec, 0) == NULL) + if (n == 0) { vector_free (matchvec); /* In case of 'command \t' pattern. Do you need '?' command at the end of the line. */ - if (vector_slot (vline, index) == '\0') + if (*token == '\0') *status = CMD_ERR_NOTHING_TODO; else *status = CMD_ERR_NO_MATCH; @@ -1880,66 +2128,50 @@ cmd_complete_command_real (vector vline, int node, int *status) } /* XXX: TODO: stop poking around inside vector */ - /* Only one matched */ - if (vector_slot (matchvec, 1) == NULL) + /* Only one matched */ + if (n == 1) { - match_str = (char **) matchvec->VECTOR_INDEX; - vector_only_wrapper_free (matchvec); *status = CMD_COMPLETE_FULL_MATCH; - return match_str; + return matchvec ; } - /* Make it sure last element is NULL. */ - vector_set (matchvec, NULL); - /* Check LCD of matched strings. */ - if (vector_slot (vline, index) != NULL) + /* Check LCD of matched strings. */ + if (token != NULL) { - lcd = cmd_lcd ((char **) matchvec->VECTOR_INDEX); + unsigned lcd = cmd_lcd (matchvec) ; - if (lcd) + if (lcd != 0) { - int len = strlen (vector_slot (vline, index)); - - if (len < lcd) + if (strlen(token) < lcd) { char *lcdstr; lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); - memcpy (lcdstr, matchvec->VECTOR_INDEX[0], lcd); + memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; lcdstr[lcd] = '\0'; - /* match_str = (char **) &lcdstr; */ + cmd_free_strvec(matchvec) ; /* discard the match vector */ - /* Free matchvec. */ - for (i = 0; i < vector_active (matchvec); i++) - { - if (vector_slot (matchvec, i)) - XFREE (MTYPE_STRVEC, vector_slot (matchvec, i)); - } - vector_free (matchvec); - - /* Make new matchvec. */ matchvec = vector_init (INIT_MATCHVEC_SIZE); - vector_set (matchvec, lcdstr); - match_str = (char **) matchvec->VECTOR_INDEX; - vector_only_wrapper_free (matchvec); + vector_push_item(matchvec, lcdstr) ; *status = CMD_COMPLETE_MATCH; - return match_str; + return matchvec ; } } } - match_str = (char **) matchvec->VECTOR_INDEX; - vector_only_wrapper_free (matchvec); *status = CMD_COMPLETE_LIST_MATCH; - return match_str; + return matchvec ; } -char ** +/*------------------------------------------------------------------------------ + * Can the current command be completed ? + */ +extern vector cmd_complete_command (vector vline, int node, int *status) { - char **ret; + vector ret; if ( cmd_try_do_shortcut(node, vector_slot(vline, 0) ) ) { @@ -1964,13 +2196,14 @@ cmd_complete_command (vector vline, int node, int *status) return cmd_complete_command_real (vline, node, status); } -/* return parent node */ -/* MUST eventually converge on CONFIG_NODE */ +/*------------------------------------------------------------------------------ + * Return parent node + * + * All nodes > CONFIG_NODE are descended from CONFIG_NODE + */ enum node_type node_parent ( enum node_type node ) { - enum node_type ret; - assert (node > CONFIG_NODE); switch (node) @@ -1980,69 +2213,101 @@ node_parent ( enum node_type node ) case BGP_IPV4M_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: - ret = BGP_NODE; - break; + return BGP_NODE; + case KEYCHAIN_KEY_NODE: - ret = KEYCHAIN_NODE; - break; - default: - ret = CONFIG_NODE; - } + return KEYCHAIN_NODE; - return ret; -} + default: + return CONFIG_NODE; + } +} + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", if possible, ready for execution. + * + * If "strict" use: cmd_filter_by_string() + * otherwise use: cmd_filter_by_completion() + * + * Takes the node from parsed->cnode. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * CMD_SUCCESS_DAEMON => as CMD_SUCCESS, and the command has a + * "daemon" value. + * + * otherwise => some failure to parse + * + * NB: the argv[] in the parsed structure contains *copies* of the char* + * pointers in the given vline. + * + * The vline may not be released until the parsed structure is. + * + * The parsed structure may be released without worrying about the contents + * of the argv[]. + */ +enum cmd_parse_type +{ + cmd_parse_completion = 0, + cmd_parse_strict = 1, +}; -/* Execute command by argument vline vector. */ static int -cmd_execute_command_real (vector vline, struct vty *vty, - struct cmd_element **cmd, qpn_nexus dest_nexus) +cmd_parse_command(vector vline, int first, struct cmd_parsed* parsed, + enum cmd_parse_type type) { - unsigned int i; - unsigned int index; - vector cmd_vector; + unsigned int i ; + unsigned int ivl ; + unsigned index ; + vector cmd_v; struct cmd_element *cmd_element; struct cmd_element *matched_element; unsigned int matched_count, incomplete_count; int argc; - const char *argv[CMD_ARGC_MAX]; enum match_type match = 0; int varflag; char *command; - /* Make copy of command elements. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty_get_node(vty))); + int strict = (type == cmd_parse_strict) ; - for (index = 0; index < vector_active (vline); index++) - if ((command = vector_slot (vline, index))) + parsed->cmd = NULL ; /* return this if parsing fails */ + parsed->argc = 0 ; + + /* Make copy of command elements. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, parsed->cnode)); + + /* Look for an unambiguous result */ + index = 0 ; + for (ivl = first; ivl < vector_active (vline); ivl++) + if ((command = vector_slot (vline, ivl)) != NULL) { - int ret; + int ret ; - match = cmd_filter_by_completion (command, cmd_vector, index); + index = ivl - first ; + match = strict ? cmd_filter_by_string(command, cmd_v, index) + : cmd_filter_by_completion(command, cmd_v, index) ; if (match == vararg_match) break; - ret = is_cmd_ambiguous (command, cmd_vector, index, match); + ret = is_cmd_ambiguous (command, cmd_v, index, match); - if (ret == 1) + if (ret != 0) { - vector_free (cmd_vector); - return CMD_ERR_AMBIGUOUS; - } - else if (ret == 2) - { - vector_free (cmd_vector); - return CMD_ERR_NO_MATCH; + assert((ret == 1) || (ret == 2)) ; + vector_free (cmd_v); + return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; } } - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; + /* Check matched count. */ + matched_element = NULL; + matched_count = 0; incomplete_count = 0; - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i))) + for (i = 0; i < vector_active (cmd_v); i++) + if ((cmd_element = vector_slot (cmd_v, i)) != NULL) { if (match == vararg_match || index >= cmd_element->cmdsize) { @@ -2058,133 +2323,255 @@ cmd_execute_command_real (vector vline, struct vty *vty, } } - /* Finish of using cmd_vector. */ - vector_free (cmd_vector); + /* Finished with cmd_v. */ + vector_free (cmd_v); - /* To execute command, matched_count must be 1. */ - if (matched_count == 0) + /* To execute command, matched_count must be 1. */ + if (matched_count != 1) { - if (incomplete_count) - return CMD_ERR_INCOMPLETE; + if (matched_count == 0) + return (incomplete_count) ? CMD_ERR_INCOMPLETE : CMD_ERR_NO_MATCH ; else - return CMD_ERR_NO_MATCH; - } - - if (matched_count > 1) - return CMD_ERR_AMBIGUOUS; + return CMD_ERR_AMBIGUOUS ; + } ; - /* Argument treatment */ - varflag = 0; - argc = 0; + /* Found command -- process the arguments ready for execution */ + varflag = 0 ; + argc = 0 ; - for (i = 0; i < vector_active (vline); i++) + for (ivl = first; ivl < vector_active (vline); ivl++) { - if (varflag) - argv[argc++] = vector_slot (vline, i); - else + int take = varflag ; + + if (!varflag) { - vector descvec = vector_slot (matched_element->strvec, i); + int index = ivl - first ; + vector descvec = vector_slot (matched_element->strvec, index); if (vector_active (descvec) == 1) { struct desc *desc = vector_slot (descvec, 0); if (CMD_VARARG (desc->cmd)) - varflag = 1; - - if (varflag || CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) - argv[argc++] = vector_slot (vline, i); + take = varflag = 1 ; + else + take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; } else - argv[argc++] = vector_slot (vline, i); + take = 1 ; } - if (argc >= CMD_ARGC_MAX) - return CMD_ERR_EXEED_ARGC_MAX; - } + if (take) + { + if (argc >= CMD_ARGC_MAX) + return CMD_ERR_EXCEED_ARGC_MAX ; + parsed->argv[argc++] = vector_slot (vline, ivl); + } ; + } ; - /* For vtysh execution. */ - if (cmd) - *cmd = matched_element; + /* Everything checks out... ready to execute command */ + parsed->cmd = matched_element ; + parsed->argc = argc ; - if (matched_element->daemon) + if (parsed->cmd->daemon) return CMD_SUCCESS_DAEMON; - /* Execute matched command. */ - if (qpthreads_enabled && !(matched_element->attr & CMD_ATTR_CALL)) - { - /* Don't do it now, but send to bgp qpthread */ - cq_enqueue(matched_element, vty, argc, argv, dest_nexus); - return CMD_QUEUED; - } - else - { - return (*matched_element->func) (matched_element, vty, argc, argv); - } -} + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", or any of its ancestors. + * + * Takes the node to start in from parsed->cnode. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * NB: parsed->cnode may have changed. + * + * CMD_SUCCESS_DAEMON => as CMD_SUCCESS, and the command has a + * "daemon" value. + * + * otherwise => some failure to parse + * + * NB: returns error from attempt to parse in the + * starting parsed->cnode (which is returned + * unchanged). + * + * See cmd_parse_command() for more detail. + */ +static int +cmd_parse_command_tree(vector vline, int first, struct cmd_parsed* parsed, + enum cmd_parse_type type) +{ + int ret ; + int first_ret ; + enum node_type first_node ; -int -cmd_execute_command (vector vline, struct vty *vty, struct cmd_element **cmd, - qpn_nexus dest_nexus, int vtysh) { - int ret, saved_ret, tried = 0; - enum node_type onode, try_node; + /* Try in the current node */ + ret = cmd_parse_command(vline, first, parsed, type) ; - onode = try_node = vty_get_node(vty); + if ((ret == CMD_SUCCESS) || (ret == CMD_SUCCESS_DAEMON)) + return ret ; /* done if found command */ - if ( cmd_try_do_shortcut(vty_get_node(vty), vector_slot(vline, 0) ) ) - { - vector shifted_vline; - unsigned int index; + /* Try in parent node(s) */ + first_node = parsed->cnode ; + first_ret = ret ; - vty_set_node(vty, ENABLE_NODE); - /* We can try it on enable node, cos' the vty is authenticated */ + while (parsed->cnode <= CONFIG_NODE) + { + parsed->cnode = node_parent(parsed->cnode) ; + ret = cmd_parse_command(vline, first, parsed, type) ; - shifted_vline = vector_init (vector_count(vline)); - /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) - { - vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); - } + if ((ret == CMD_SUCCESS) || (ret == CMD_SUCCESS_DAEMON)) + return ret ; /* done if found command */ + } ; - ret = cmd_execute_command_real (shifted_vline, vty, cmd, dest_nexus); + parsed->cnode = first_node ; /* restore node state */ + return first_ret ; /* return original result */ +} - vector_free(shifted_vline); - vty_set_node(vty, onode); - return ret; - } +/*------------------------------------------------------------------------------ + * Prepare to parse command -- deal with possible "do" shortcut. + * + * Initialises parsed structure, setting cnode and onode to vty->node. + * + * Checks to see if this is a valid "do" shortcut, and adjusts cnode if so. + * + * Returns: 0 => not "do" -- first word of actual command is first word + * 1 => is "do" -- first word of actual command is second word + */ +static int +cmd_pre_command(struct vty* vty, struct cmd_parsed* parsed, vector vline) +{ + int first ; + parsed->onode = parsed->cnode = vty_get_node(vty) ; - saved_ret = ret = cmd_execute_command_real (vline, vty, cmd, dest_nexus); + parsed->cmd = NULL ; + parsed->argc = 0 ; + /* 'do' forces command to be parsed in ENABLE_NODE (if allowed) */ + if (cmd_try_do_shortcut(parsed->cnode, vector_slot(vline, 0))) + { + parsed->cnode = ENABLE_NODE ; + parsed->do_shortcut = 1 ; + first = 1 ; + } + else + { + parsed->do_shortcut = 0 ; + first = 0 ; + } ; + + return first ; +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after executing command. + * + * This is separated out so that can be called when queued command completes. + * + * If have just processed a "do" shortcut command, and it has not set the + * vty->node to something other than ENABLE_NODE, then restore to the original + * state. + * + * Arguments: ret = CMD_XXXX -- NB: CMD_QUEUED => command revoked + * action = true unless command return is being revoked + */ +extern void +cmd_post_command(struct vty* vty, struct cmd_parsed* parsed, int ret, + int action) +{ + if (parsed->do_shortcut) + { + if (vty_get_node(vty) == ENABLE_NODE) + vty_set_node(vty, parsed->onode) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Execute command: + * + * -- use cmd_parse_completion type parsing. + * + * -- unless vtysh: if does not parse in current node, try ancestors + * + * If does not find in any ancestor, return error from current node. + * + * -- implement the "do" shortcut + * + * If qpthreads_enabled, then may queue the command rather than execute it + * here. + * + * The vty->node may be changed during the execution of the command, and may + * be returned changed once the command has completed. + */ +extern int +cmd_execute_command (vector vline, struct vty *vty, + struct cmd_element **cmd, qpn_nexus to_nexus, + qpn_nexus from_nexus, int vtysh) +{ + int ret ; + int first ; + struct cmd_parsed parsed ; + + /* Set up parsed structure & deal with "do" shortcut prefix */ + first = cmd_pre_command(vty, &parsed, vline) ; + + /* Try to parse in parsed.cnode node or, if required, ancestors thereof. */ if (vtysh) - return saved_ret; + ret = cmd_parse_command(vline, first, &parsed, cmd_parse_completion) ; + else + ret = cmd_parse_command_tree(vline, first, &parsed, cmd_parse_completion) ; - /* This assumes all nodes above CONFIG_NODE are childs of CONFIG_NODE */ - while ( ret != CMD_SUCCESS && ret != CMD_WARNING - && vty_get_node(vty) > CONFIG_NODE ) + if (cmd) + *cmd = parsed.cmd ; /* for vtysh */ + + if (ret != CMD_SUCCESS) + return ret ; /* NB: CMD_SUCCESS_DAEMON exits here */ + + /* Execute the parsed command */ + + vty_set_node(vty, parsed.cnode) ; + + if (qpthreads_enabled && !(parsed.cmd->attr & CMD_ATTR_CALL)) { - try_node = node_parent(try_node); - vty_set_node(vty, try_node); - ret = cmd_execute_command_real (vline, vty, cmd, dest_nexus); - tried = 1; - if (ret == CMD_SUCCESS || ret == CMD_WARNING) - { - /* succesfull command, leave the node as is */ - return ret; - } + /* Don't do it now, but send to bgp qpthread */ + cq_enqueue(vty, &parsed, to_nexus, from_nexus) ; + ret = CMD_QUEUED ; } - /* no command succeeded, reset the vty to the original node and - return the error for this node */ - if ( tried ) - vty_set_node(vty, onode); - return saved_ret; -} - -/* Execute command by argument readline. */ -int + else + { + ret = (*(parsed.cmd->func))(parsed.cmd, vty, parsed.argc, parsed.argv) ; + + cmd_post_command(vty, &parsed, ret, 1) ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Execute command by argument readline. + * + * -- use cmd_parse_strict type parsing. + * + * -- unless vtysh: if does not parse in current node, try ancestors + * + * If does not find in any ancestor, return error from current node. + * + * -- does NOT implement the "do" shortcut + * + * At all times executes the command immediately (no queueing, even if + * qpthreads_enabled). + * + * The vty->node may be changed either when parsing or executing the command. + */ +extern int cmd_execute_command_strict (vector vline, struct vty *vty, - struct cmd_element **cmd) + struct cmd_element **cmd, int vtysh) { +#if 0 /* replaced by cmd_parse_command() */ unsigned int i; unsigned int index; vector cmd_vector; @@ -2285,62 +2672,74 @@ cmd_execute_command_strict (vector vline, struct vty *vty, } if (argc >= CMD_ARGC_MAX) - return CMD_ERR_EXEED_ARGC_MAX; + return CMD_ERR_EXCEED_ARGC_MAX; } +#endif + + struct cmd_parsed parsed ; + int ret ; + + /* Try to parse in parsed.cnode node or, if required, ancestors thereof. */ + if (vtysh) + ret = cmd_parse_command(vline, 0, &parsed, cmd_parse_strict) ; + else + ret = cmd_parse_command_tree(vline, 0, &parsed, cmd_parse_strict) ; - /* For vtysh execution. */ if (cmd) - *cmd = matched_element; + *cmd = parsed.cmd ; /* for vtysh */ - if (matched_element->daemon) - return CMD_SUCCESS_DAEMON; + if (ret != CMD_SUCCESS) + return ret ; /* NB: CMD_SUCCESS_DAEMON exits here */ - /* Now execute matched command */ - return (*matched_element->func) (matched_element, vty, argc, argv); + /* Now execute matched command */ + vty_set_node(vty, parsed.cnode) ; + + return (*(parsed.cmd->func)) (parsed.cmd, vty, parsed.argc, parsed.argv); } -/* Configration make from file. */ +/*------------------------------------------------------------------------------ + * Read configuration from file. + * + */ int -config_from_file (struct vty *vty, FILE *fp, void (*after_first_cmd)(void)) +config_from_file (struct vty *vty, FILE *fp, void (*after_first_cmd)(void), + qstring buf) { int ret; vector vline; int first_cmd = 1; - while (fgets (vty->buf, VTY_BUFSIZ, fp)) + while (fgets (buf->body, buf->size, fp)) { - vline = cmd_make_strvec (vty->buf); + vline = cmd_make_strvec (buf->body); - /* In case of comment line */ + /* In case of comment line */ if (vline == NULL) continue; - /* Execute configuration command : this is strict match */ - ret = cmd_execute_command_strict (vline, vty, NULL); - /* special handling for after the first command */ + /* Execute configuration command : this is strict match */ + ret = cmd_execute_command_strict (vline, vty, NULL, 0); + + /* special handling for after the first command */ if (first_cmd && after_first_cmd) { after_first_cmd(); first_cmd = 0; } - /* Try again with setting node to CONFIG_NODE */ - while (ret != CMD_SUCCESS && ret != CMD_WARNING - && ret != CMD_ERR_NOTHING_TODO && vty_get_node(vty) != CONFIG_NODE) - { - vty_set_node(vty, node_parent(vty_get_node(vty))); - ret = cmd_execute_command_strict (vline, vty, NULL); - } - cmd_free_strvec (vline); + /* TODO: why does config file not stop on CMD_WARNING ?? */ if (ret != CMD_SUCCESS && ret != CMD_WARNING - && ret != CMD_ERR_NOTHING_TODO) + && ret != CMD_ERR_NOTHING_TODO) return ret; - } + } ; + return CMD_SUCCESS; } +/*----------------------------------------------------------------------------*/ + /* Configration from terminal */ DEFUN_CALL (config_terminal, config_terminal_cmd, @@ -2348,14 +2747,11 @@ DEFUN_CALL (config_terminal, "Configuration from vty interface\n" "Configuration terminal\n") { - if (vty_config_lock (vty)) - vty_set_node(vty, CONFIG_NODE); - else - { - vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); - return CMD_WARNING; - } - return CMD_SUCCESS; + if (vty_config_lock (vty, CONFIG_NODE)) + return CMD_SUCCESS; + + vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); + return CMD_WARNING; } /* Enable command */ @@ -2391,47 +2787,7 @@ DEFUN_CALL (config_exit, "exit", "Exit current mode and down to previous mode\n") { - switch (vty_get_node(vty)) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - if (vty_shell (vty)) - exit (0); - else - vty_set_status(vty, VTY_CLOSE); - break; - case CONFIG_NODE: - vty_set_node(vty, ENABLE_NODE); - vty_config_unlock (vty); - break; - case INTERFACE_NODE: - case ZEBRA_NODE: - case BGP_NODE: - case RIP_NODE: - case RIPNG_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case MASC_NODE: - case RMAP_NODE: - case VTY_NODE: - vty_set_node(vty, CONFIG_NODE); - break; - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - vty_set_node(vty, BGP_NODE); - break; - case KEYCHAIN_KEY_NODE: - vty_set_node(vty, KEYCHAIN_NODE); - break; - default: - break; - } + vty_cmd_exit(vty) ; return CMD_SUCCESS; } @@ -2447,38 +2803,7 @@ DEFUN_CALL (config_end, "end", "End current mode and change to enable mode.") { - switch (vty_get_node(vty)) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - vty_config_unlock (vty); - vty_set_node(vty, ENABLE_NODE); - break; - default: - break; - } + vty_cmd_end(vty) ; return CMD_SUCCESS; } @@ -2539,7 +2864,7 @@ DEFUN_CALL (config_list, } /* Write current configuration into file. */ -DEFUN_CALL (config_write_file, +DEFUN (config_write_file, config_write_file_cmd, "write file", "Write running configuration to memory, network, or terminal\n" @@ -2645,18 +2970,18 @@ finished: return ret; } -ALIAS_CALL (config_write_file, +ALIAS (config_write_file, config_write_cmd, "write", "Write running configuration to memory, network, or terminal\n") -ALIAS_CALL (config_write_file, +ALIAS (config_write_file, config_write_memory_cmd, "write memory", "Write running configuration to memory, network, or terminal\n" "Write configuration to the file (same as write file)\n") -ALIAS_CALL (config_write_file, +ALIAS (config_write_file, copy_runningconfig_startupconfig_cmd, "copy running-config startup-config", "Copy configuration\n" @@ -2664,7 +2989,7 @@ ALIAS_CALL (config_write_file, "Copy running config to startup config (same as write file)\n") /* Write current configuration into the terminal. */ -DEFUN_CALL (config_write_terminal, +DEFUN (config_write_terminal, config_write_terminal_cmd, "write terminal", "Write running configuration to memory, network, or terminal\n" @@ -2700,14 +3025,14 @@ DEFUN_CALL (config_write_terminal, } /* Write current configuration into the terminal. */ -ALIAS_CALL (config_write_terminal, +ALIAS (config_write_terminal, show_running_config_cmd, "show running-config", SHOW_STR "running configuration\n") /* Write startup configuration into the terminal. */ -DEFUN_CALL (show_startup_config, +DEFUN (show_startup_config, show_startup_config_cmd, "show startup-config", SHOW_STR @@ -2753,10 +3078,16 @@ DEFUN_CALL (config_hostname, return CMD_WARNING; } + VTY_LOCK() ; + if (host.name) XFREE (MTYPE_HOST, host.name); host.name = XSTRDUP (MTYPE_HOST, argv[0]); + uty_set_host_name(host.name) ; + + VTY_UNLOCK() ; + return CMD_SUCCESS; } @@ -2767,9 +3098,15 @@ DEFUN_CALL (config_no_hostname, "Reset system's network name\n" "Host name of this router\n") { + VTY_LOCK() ; + if (host.name) XFREE (MTYPE_HOST, host.name); host.name = NULL; + uty_set_host_name(host.name) ; + + VTY_UNLOCK() ; + return CMD_SUCCESS; } diff --git a/lib/command.h b/lib/command.h index b1cf18a4..0e8b3630 100644 --- a/lib/command.h +++ b/lib/command.h @@ -8,7 +8,7 @@ * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. - * + * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -23,8 +23,11 @@ #ifndef _ZEBRA_COMMAND_H #define _ZEBRA_COMMAND_H +#include "node_type.h" #include "vector.h" +#include "uty.h" #include "vty.h" +#include "qstring.h" #include "lib/route_types.h" /* Host configuration variable */ @@ -59,69 +62,24 @@ struct host char *motdfile; }; -/* There are some command levels which called from command node. */ -enum node_type -{ - AUTH_NODE, /* Authentication mode of vty interface. */ - RESTRICTED_NODE, /* Restricted view mode */ - VIEW_NODE, /* View node. Default mode of vty interface. */ - AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ - ENABLE_NODE, /* Enable node. */ - CONFIG_NODE, /* Config node. Default mode of config file. */ - SERVICE_NODE, /* Service node. */ - DEBUG_NODE, /* Debug node. */ - AAA_NODE, /* AAA node. */ - KEYCHAIN_NODE, /* Key-chain node. */ - KEYCHAIN_KEY_NODE, /* Key-chain key node. */ - INTERFACE_NODE, /* Interface mode node. */ - ZEBRA_NODE, /* zebra connection node. */ - TABLE_NODE, /* rtm_table selection node. */ - RIP_NODE, /* RIP protocol mode node. */ - RIPNG_NODE, /* RIPng protocol mode node. */ - BGP_NODE, /* BGP protocol mode which includes BGP4+ */ - BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ - BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ - BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ - BGP_IPV6_NODE, /* BGP IPv6 address family */ - BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ - OSPF_NODE, /* OSPF protocol mode */ - OSPF6_NODE, /* OSPF protocol for IPv6 mode */ - ISIS_NODE, /* ISIS protocol mode */ - MASC_NODE, /* MASC for multicast. */ - IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ - IP_NODE, /* Static ip route node. */ - ACCESS_NODE, /* Access list node. */ - PREFIX_NODE, /* Prefix list node. */ - ACCESS_IPV6_NODE, /* Access list node. */ - PREFIX_IPV6_NODE, /* Prefix list node. */ - AS_LIST_NODE, /* AS list node. */ - COMMUNITY_LIST_NODE, /* Community list node. */ - RMAP_NODE, /* Route map node. */ - SMUX_NODE, /* SNMP configuration node. */ - DUMP_NODE, /* Packet dump node. */ - FORWARDING_NODE, /* IP forwarding node. */ - PROTOCOL_NODE, /* protocol filtering node */ - VTY_NODE, /* Vty node. */ -}; - /* Node which has some commands and prompt string and configuration function pointer . */ -struct cmd_node +struct cmd_node { /* Node index. */ - enum node_type node; + enum node_type node; /* Prompt character at vty interface. */ - const char *prompt; + const char *prompt; /* Is this node's configuration goes to vtysh ? */ int vtysh; - + /* Node's configuration write function */ int (*func) (struct vty *); /* Vector of this node's command list. */ - vector cmd_vector; + vector cmd_vector; }; enum @@ -132,8 +90,8 @@ enum CMD_ATTR_CALL = 0x04, }; -/* Structure of command element. */ -struct cmd_element +/* Structure of command element. */ +struct cmd_element { const char *string; /* Command specification by string. */ int (*func) (struct cmd_element *, struct vty *, int, const char *[]); @@ -146,32 +104,64 @@ struct cmd_element u_char attr; /* Command attributes */ }; -/* Command description structure. */ +/* Command description structure. */ struct desc { char *cmd; /* Command string. */ char *str; /* Command's description. */ }; -/* Return value of the commands. */ -#define CMD_SUCCESS 0 -#define CMD_WARNING 1 -#define CMD_ERR_NO_MATCH 2 -#define CMD_ERR_AMBIGUOUS 3 -#define CMD_ERR_INCOMPLETE 4 -#define CMD_ERR_EXEED_ARGC_MAX 5 -#define CMD_ERR_NOTHING_TODO 6 -#define CMD_COMPLETE_FULL_MATCH 7 -#define CMD_COMPLETE_MATCH 8 -#define CMD_COMPLETE_LIST_MATCH 9 -#define CMD_SUCCESS_DAEMON 10 -#define CMD_QUEUED 11 - /* Argc max counts. */ #define CMD_ARGC_MAX 25 +/* Parsed command */ +struct cmd_parsed +{ + struct cmd_element *cmd ; + + enum node_type cnode ; /* node command is in */ + + int do_shortcut ; /* true => is "do" command */ + enum node_type onode ; /* vty->node before "do" */ + + int argc ; + const char* argv[CMD_ARGC_MAX] ; +} ; + +/* Return values for command handling. + * + * NB: when a command is executed it may return CMD_SUCCESS or CMD_WARNING. + * + * In both cases any output required (including any warning or error + * messages) must already have been output. + * + * All other return codes are for use within the command handler. + */ +enum cmd_return_code +{ + CMD_SUCCESS = 0, + CMD_WARNING = 1, + + CMD_ERR_NO_MATCH = 2, + CMD_ERR_AMBIGUOUS = 3, + CMD_ERR_INCOMPLETE = 4, + CMD_ERR_EXCEED_ARGC_MAX = 5, + CMD_ERR_NOTHING_TODO = 6, + CMD_COMPLETE_FULL_MATCH = 7, + CMD_COMPLETE_MATCH = 8, + CMD_COMPLETE_LIST_MATCH = 9, + + CMD_SUCCESS_DAEMON = 10, + CMD_QUEUED = 11 +} ; + +#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" +#define MSG_CMD_ERR_EXEED_ARGC_MAX "Exceeds maximum word count" + /* Turn off these macros when uisng cpp with extract.pl */ -#ifndef VTYSH_EXTRACT_PL +#ifndef VTYSH_EXTRACT_PL /* helper defines for end-user DEFUN* macros */ #define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ @@ -352,15 +342,21 @@ extern void sort_node (void); extern char *argv_concat (const char **argv, int argc, int shift); 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 char **cmd_complete_command (vector, int, int *status); +extern vector cmd_complete_command (vector, int, int *status); extern const char *cmd_prompt (enum node_type); -extern int config_from_file (struct vty *, FILE *, void (*)(void)); +extern int config_from_file (struct vty *, FILE *, void (*)(void), qstring buf); extern enum node_type node_parent (enum node_type); -extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, - qpn_nexus, int); -extern int cmd_execute_command_strict (vector, struct vty *, struct cmd_element **); +extern int cmd_execute_command (vector, struct vty* vty, + struct cmd_element **cmd, qpn_nexus to_nexus, + qpn_nexus from_nexus, + int vtysh) ; +extern void cmd_post_command(struct vty* vty, struct cmd_parsed* parsed, + int ret, int action) ; +extern int cmd_execute_command_strict (vector vline, struct vty *vty, + struct cmd_element **cmd, int vtysh) ; extern void config_replace_string (struct cmd_element *, char *, ...); extern void cmd_init (int); extern void cmd_terminate (void); @@ -377,7 +373,7 @@ extern void host_config_set (char *); extern void print_version (const char *); /* struct host global, ick */ -extern struct host host; +extern struct host host; /* "<cr>" global */ extern char *command_cr; diff --git a/lib/command_queue.c b/lib/command_queue.c index b14bcd20..b6aab580 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -26,56 +26,130 @@ #include "memory.h" #include "command_queue.h" -/* Prototypes */ +/*------------------------------------------------------------------------------ + * Form of message passed with command to be executed + */ + +struct cq_command_args +{ + qpn_nexus ret_nexus ; + + struct cmd_element *cmd ; + + enum node_type cnode ; /* vty->node before execution */ + enum node_type onode ; /* vty->node before "do" */ + + short int do_shortcut ; /* true => is "do" command */ + + short int argc ; /* count of arguments */ + short int ret ; /* return code */ +} ; +MQB_ARGS_SIZE_OK(cq_command_args) ; + +/*------------------------------------------------------------------------------ + * Prototypes + */ static void cq_action(mqueue_block mqb, mqb_flag_t flag); +static void cq_return(mqueue_block mqb, mqb_flag_t flag); +/*------------------------------------------------------------------------------ + * Enqueue vty and argv[] for execution in given nexus. + */ void -cq_enqueue(struct cmd_element *matched_element, struct vty *vty, - int argc, const char *argv[], qpn_nexus bgp_nexus) +cq_enqueue(struct vty *vty, struct cmd_parsed* parsed, qpn_nexus to_nexus, + qpn_nexus from_nexus) { int i; + struct cq_command_args* args ; + mqueue_block mqb = mqb_init_new(NULL, cq_action, vty) ; + args = mqb_get_args(mqb) ; + + args->cmd = parsed->cmd ; + args->cnode = parsed->cnode ; + args->onode = parsed->onode ; + args->do_shortcut = parsed->do_shortcut ; + args->argc = parsed->argc ; - /* all parameters are pointers so use the queue's argv */ - mqb_push_argv_p(mqb, matched_element); - for (i = 0; i < argc; ++i) - mqb_push_argv_p(mqb, XSTRDUP(MTYPE_MARSHAL, argv[i])); + args->ret_nexus = from_nexus ; + args->ret = CMD_SUCCESS ; - mqueue_enqueue(bgp_nexus->queue, mqb, 0) ; + for (i = 0; i < parsed->argc; ++i) + mqb_push_argv_p(mqb, XSTRDUP(MTYPE_MARSHAL, parsed->argv[i])); + + mqueue_enqueue(to_nexus->queue, mqb, 0) ; } -/* dispatch a command from the message queue block */ +/*------------------------------------------------------------------------------ + * Dispatch a command from the message queue block + * + * When done (or revoked/deleted) return the message, so that the sender knows + * that the command has been dealt with (one way or another). + */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) { - int result; - int i; - struct cmd_element *matched_element; struct vty *vty; - void **argv; - int argc; + struct cq_command_args* args ; - vty = mqb_get_arg0(mqb); - argc = mqb_get_argv_count(mqb); - argv = mqb_get_argv(mqb) ; - - matched_element = argv[0]; - argv++; - argc--; + vty = mqb_get_arg0(mqb); + args = mqb_get_args(mqb) ; if (flag == mqb_action) { - /* Execute matched command. */ - result = (matched_element->func) - (matched_element, vty, argc, (const char **)argv); + const char** argv = mqb_get_argv(mqb) ; - /* report */ - vty_queued_result(vty, result); + args->ret = (args->cmd->func)(args->cmd, vty, args->argc, argv) ; } + else + args->ret = CMD_QUEUED ; + + mqb_set_action(mqb, cq_return) ; + mqueue_enqueue(args->ret_nexus->queue, mqb, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept return from command executed in another thread. + * + * The command line processing for the vty may be stalled (with read mode + * disabled) waiting for the return from the command. + * + * If the message is being revoked/deleted the state of the vty is still + * updated (to show that the command has completed) BUT nothing is kicked. + * It is up to the revoke/delete function to deal with any possibility of the + * vty remaining stalled. + */ +static void +cq_return(mqueue_block mqb, mqb_flag_t flag) +{ + struct vty *vty ; + struct cq_command_args* args ; + int i ; + void** argv ; + struct cmd_parsed parsed ; + + vty = mqb_get_arg0(mqb) ; + args = mqb_get_args(mqb) ; + + /* clean up */ + argv = mqb_get_argv(mqb) ; + + for (i = 0; i < args->argc; ++i) + XFREE(MTYPE_MARSHAL, argv[i]); + + /* signal end of command -- passing the action state */ + parsed.cmd = args->cmd ; + parsed.cnode = args->cnode ; + parsed.onode = args->onode ; + parsed.do_shortcut = args->do_shortcut ; + parsed.argc = 0 ; + cmd_post_command(vty, &parsed, args->ret, (flag == mqb_action)) ; + + /* update the state of the vty -- passing the "action" state */ + vty_queued_result(vty, args->ret, (flag == mqb_action)); - /* clean up */ - for (i = 0; i < argc; ++i) - XFREE(MTYPE_MARSHAL, argv[i]); + if (qpthreads_enabled) + qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); mqb_free(mqb); } diff --git a/lib/command_queue.h b/lib/command_queue.h index 90a4f7b6..257c9552 100644 --- a/lib/command_queue.h +++ b/lib/command_queue.h @@ -25,7 +25,8 @@ #include "command.h" #include "qpnexus.h" -extern void cq_enqueue(struct cmd_element *matched_element, struct vty *vty, - int argc, const char *argv[], qpn_nexus); +extern void +cq_enqueue(struct vty *vty, struct cmd_parsed* parsed, qpn_nexus to_nexus, + qpn_nexus from_nexus) ; #endif /* COMMAND_QUEUE_H_ */ @@ -1,10 +1,10 @@ -/* +/* * Interface functions. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. - * + * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your @@ -35,7 +35,7 @@ #include "buffer.h" #include "str.h" #include "log.h" - + /* Master list of interfaces. */ struct list *iflist; @@ -45,7 +45,7 @@ struct if_master int (*if_new_hook) (struct interface *); int (*if_delete_hook) (struct interface *); } if_master; - + /* Compare interface names, returning an integer greater than, equal to, or * less than 0, (following the strcmp convention), according to the * relationship between ifp1 and ifp2. Interface names consist of an @@ -53,7 +53,7 @@ struct if_master * lexicographic by name, and then numeric by number. No number sorts * before all numbers. Examples: de0 < de1, de100 < fxp0 < xl0, devpty < * devpty0, de0 < del0 - */ + */ int if_cmp_func (struct interface *ifp1, struct interface *ifp2) { @@ -87,9 +87,9 @@ if_cmp_func (struct interface *ifp1, struct interface *ifp2) p1 += l1; p2 += l1; - if (!*p1) + if (!*p1) return -1; - if (!*p2) + if (!*p2) return 1; x1 = strtol(p1, &p1, 10); @@ -119,7 +119,7 @@ if_create (const char *name, int namelen) ifp = XCALLOC (MTYPE_IF, sizeof (struct interface)); ifp->ifindex = IFINDEX_INTERNAL; - + assert (name); assert (namelen <= INTERFACE_NAMSIZ); /* Need space for '\0' at end. */ strncpy (ifp->name, name, namelen); @@ -215,7 +215,7 @@ if_lookup_by_name (const char *name) { struct listnode *node; struct interface *ifp; - + if (name) for (ALL_LIST_ELEMENTS_RO (iflist, node, ifp)) { @@ -262,7 +262,7 @@ if_lookup_exact_address (struct in_addr src) { if (IPV4_ADDR_SAME (&p->u.prefix4, &src)) return ifp; - } + } } } return NULL; @@ -434,12 +434,12 @@ if_dump (const struct interface *ifp) "mtu6 %d " #endif /* HAVE_IPV6 */ "%s", - ifp->name, ifp->ifindex, ifp->metric, ifp->mtu, + ifp->name, ifp->ifindex, ifp->metric, ifp->mtu, #ifdef HAVE_IPV6 ifp->mtu6, #endif /* HAVE_IPV6 */ if_flag_dump (ifp->flags)); - + for (ALL_LIST_ELEMENTS_RO (ifp->connected, node, c)) ; } @@ -455,7 +455,7 @@ if_dump_all (void) if_dump (p); } -DEFUN (interface_desc, +DEFUN (interface_desc, interface_desc_cmd, "description .LINE", "Interface specific description\n" @@ -474,7 +474,7 @@ DEFUN (interface_desc, return CMD_SUCCESS; } -DEFUN (no_interface_desc, +DEFUN (no_interface_desc, no_interface_desc_cmd, "no description", NO_STR @@ -489,7 +489,7 @@ DEFUN (no_interface_desc, return CMD_SUCCESS; } - + #ifdef SUNOS_5 /* Need to handle upgrade from SUNWzebra to Quagga. SUNWzebra created * a seperate struct interface for each logical interface, so config @@ -519,11 +519,11 @@ if_sunwzebra_get (const char *name, size_t nlen) if ( (ifp = if_lookup_by_name_len(name, nlen)) != NULL) return ifp; - + /* hunt the primary interface name... */ while (seppos < nlen && name[seppos] != ':') seppos++; - + /* Wont catch seperator as last char, e.g. 'foo0:' but thats invalid */ if (seppos < nlen) return if_get_by_name_len (name, seppos); @@ -531,7 +531,7 @@ if_sunwzebra_get (const char *name, size_t nlen) return if_get_by_name_len (name, nlen); } #endif /* SUNOS_5 */ - + DEFUN (interface, interface_cmd, "interface IFNAME", @@ -556,7 +556,7 @@ DEFUN (interface, #endif /* SUNOS_5 */ vty->index = ifp; - vty->node = INTERFACE_NODE; + vty_set_node(vty, INTERFACE_NODE) ; return CMD_SUCCESS; } @@ -579,7 +579,7 @@ DEFUN_NOSH (no_interface, return CMD_WARNING; } - if (CHECK_FLAG (ifp->status, ZEBRA_INTERFACE_ACTIVE)) + if (CHECK_FLAG (ifp->status, ZEBRA_INTERFACE_ACTIVE)) { vty_out (vty, "%% Only inactive interfaces can be deleted%s", VTY_NEWLINE); @@ -649,11 +649,11 @@ connected_log (struct connected *connected, char *str) struct interface *ifp; char logbuf[BUFSIZ]; char buf[BUFSIZ]; - + ifp = connected->ifp; p = connected->address; - snprintf (logbuf, BUFSIZ, "%s interface %s %s %s/%d ", + snprintf (logbuf, BUFSIZ, "%s interface %s %s %s/%d ", str, ifp->name, prefix_family_str (p), inet_ntop (p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); @@ -734,7 +734,7 @@ connected_lookup_address (struct interface *ifp, struct in_addr dst) } struct connected * -connected_add_by_prefix (struct interface *ifp, struct prefix *p, +connected_add_by_prefix (struct interface *ifp, struct prefix *p, struct prefix *destination) { struct connected *ifc; @@ -782,7 +782,7 @@ if_indextoname (unsigned int ifindex, char *name) return ifp->name; } #endif - + #if 0 /* this route_table of struct connected's is unused * however, it would be good to use a route_table rather than * a list.. @@ -852,7 +852,7 @@ ifaddr_ipv4_lookup (struct in_addr *addr, unsigned int ifindex) rn = route_node_lookup (ifaddr_ipv4_table, (struct prefix *) &p); if (! rn) return NULL; - + ifp = rn->info; route_unlock_node (rn); return ifp; diff --git a/lib/keychain.c b/lib/keychain.c index 41c463f9..2f8a0b77 100644 --- a/lib/keychain.c +++ b/lib/keychain.c @@ -74,7 +74,7 @@ key_cmp_func (void *arg1, void *arg2) { const struct key *k1 = arg1; const struct key *k2 = arg2; - + if (k1->index > k2->index) return 1; if (k1->index < k2->index) @@ -226,7 +226,7 @@ key_delete (struct keychain *keychain, struct key *key) free (key->string); key_free (key); } - + DEFUN (key_chain, key_chain_cmd, "key chain WORD", @@ -238,7 +238,7 @@ DEFUN (key_chain, keychain = keychain_get (argv[0]); vty->index = keychain; - vty->node = KEYCHAIN_NODE; + vty_set_node(vty, KEYCHAIN_NODE) ; return CMD_SUCCESS; } @@ -281,8 +281,8 @@ DEFUN (key, VTY_GET_INTEGER ("key identifier", index, argv[0]); key = key_get (keychain, index); vty->index_sub = key; - vty->node = KEYCHAIN_KEY_NODE; - + vty_set_node(vty, KEYCHAIN_KEY_NODE) ; + return CMD_SUCCESS; } @@ -296,7 +296,7 @@ DEFUN (no_key, struct keychain *keychain; struct key *key; u_int32_t index; - + keychain = vty->index; VTY_GET_INTEGER ("key identifier", index, argv[0]); @@ -309,7 +309,7 @@ DEFUN (no_key, key_delete (keychain, key); - vty->node = KEYCHAIN_NODE; + vty_set_node(vty, KEYCHAIN_NODE) ; return CMD_SUCCESS; } @@ -353,7 +353,7 @@ DEFUN (no_key_string, /* Convert HH:MM:SS MON DAY YEAR to time_t value. -1 is returned when given string is malformed. */ -static time_t +static time_t key_str2time (const char *time_str, const char *day_str, const char *month_str, const char *year_str) { @@ -364,7 +364,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, unsigned int sec, min, hour; unsigned int day, month, year; - const char *month_name[] = + const char *month_name[] = { "January", "February", @@ -392,7 +392,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, return -1; \ (V) = tmpl; \ } - + /* Check hour field of time_str. */ colon = strchr (time_str, ':'); if (colon == NULL) @@ -416,10 +416,10 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, time_str = colon + 1; if (*time_str == '\0') return -1; - + /* Sec must be between 0 and 59. */ GET_LONG_RANGE (sec, time_str, 0, 59); - + /* Check day_str. Day must be <1-31>. */ GET_LONG_RANGE (day, day_str, 1, 31); @@ -437,7 +437,7 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, /* Check year_str. Year must be <1993-2035>. */ GET_LONG_RANGE (year, year_str, 1993, 2035); - + memset (&tm, 0, sizeof (struct tm)); tm.tm_sec = sec; tm.tm_min = min; @@ -445,9 +445,9 @@ key_str2time (const char *time_str, const char *day_str, const char *month_str, tm.tm_mon = month; tm.tm_mday = day; tm.tm_year = year - 1900; - + time = mktime (&tm); - + return time; #undef GET_LONG_RANGE } @@ -461,7 +461,7 @@ key_lifetime_set (struct vty *vty, struct key_range *krange, { time_t time_start; time_t time_end; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -496,7 +496,7 @@ key_lifetime_duration_set (struct vty *vty, struct key_range *krange, { time_t time_start; u_int32_t duration; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -518,7 +518,7 @@ key_lifetime_infinite_set (struct vty *vty, struct key_range *krange, const char *smonth_str, const char *syear_str) { time_t time_start; - + time_start = key_str2time (stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { @@ -531,7 +531,7 @@ key_lifetime_infinite_set (struct vty *vty, struct key_range *krange, return CMD_SUCCESS; } - + DEFUN (accept_lifetime_day_month_day_month, accept_lifetime_day_month_day_month_cmd, "accept-lifetime HH:MM:SS <1-31> MONTH <1993-2035> HH:MM:SS <1-31> MONTH <1993-2035>", @@ -689,7 +689,7 @@ DEFUN (accept_lifetime_duration_month_day, return key_lifetime_duration_set (vty, &key->accept, argv[0], argv[2], argv[1], argv[3], argv[4]); } - + DEFUN (send_lifetime_day_month_day_month, send_lifetime_day_month_day_month_cmd, "send-lifetime HH:MM:SS <1-31> MONTH <1993-2035> HH:MM:SS <1-31> MONTH <1993-2035>", @@ -847,7 +847,7 @@ DEFUN (send_lifetime_duration_month_day, return key_lifetime_duration_set (vty, &key->send, argv[0], argv[2], argv[1], argv[3], argv[4]); } - + static struct cmd_node keychain_node = { KEYCHAIN_NODE, @@ -887,7 +887,7 @@ keychain_config_write (struct vty *vty) for (ALL_LIST_ELEMENTS_RO (keychain_list, node, keychain)) { vty_out (vty, "key chain %s%s", keychain->name, VTY_NEWLINE); - + for (ALL_LIST_ELEMENTS_RO (keychain->key, knode, key)) { vty_out (vty, " key %d%s", key->index, VTY_NEWLINE); diff --git a/lib/keystroke.c b/lib/keystroke.c new file mode 100644 index 00000000..09f0fed0 --- /dev/null +++ b/lib/keystroke.c @@ -0,0 +1,1007 @@ +/* Keystroke Buffering + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "string.h" + +#include "keystroke.h" + +#include "list_util.h" + +#include "memory.h" +#include "mqueue.h" +#include "zassert.h" + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Parsing of incoming keystrokes. + * + * At present this code only handles 8-bit characters, which one assumes are + * ISO8859-1 (or similar). The encoding of keystrokes allows for characters + * of up to 32-bits -- so could parse UTF-8 (or similar). + * + * Handles: + * + * 0. Null, returned as: + * + * type = ks_null + * value = knull_eof + * knull_not_eof + * + * len = 0 + * truncated = false + * broken = false + * buf -- not used + * + * This is returned when there is nothing else available. + * + * 1. Characters, returned as: + * + * type = ks_char + * value = 0x0.. -- the character value + * '\0' if truncated, malformed or EOF met + * + * len = 1..n => length of character representation, or + * number of bytes in raw form if no good. + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- if OK, the representation for the character (UTF-8 ?) + * if truncated or broken, the raw bytes + * + * 2. ESC X -- where X is single character, other than '['. + * + * Returned as: + * + * type = ks_esc + * value = 0x0.. -- the character value X + * ('\0' if hit EOF) + * + * len = 1 (or 0 if EOF met) + * truncated = false + * broken => EOF met + * buf = copy of X (unless EOF met) + * + * 3. ESC [ ... X or CSI ... X -- ANSI escape + * + * Returned as: + * + * type = ks_csi + * value = 0x0.. -- the character value X + * ('\0' if hit EOF or malformed) + * + * len = number of bytes in buf (excluding '\0' terminator) + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- bytes between ESC [ or CSI and terminating X + * NB: '\0' terminated. + * + * Note: an ANSI escape is malformed if a byte outside the range + * 0x20..0x7F is found before the terminating byte. The illegal + * byte is deemed not to be part of the sequence. + * + * 4. Telnet command -- IAC X ... + * + * IAC IAC is treated as character 0xFF, so appears as a ks_char, or + * possibly as a component of an ESC or other sequence. + * + * Returned as: + * + * type = ks_iac + * value = 0x0.. -- the value of X + * ('\0' if hit EOF) + * + * len = number of bytes in buf (0 if hit EOF after IAC) + * truncated => too long for buffers + * broken => malformed or EOF met + * buf -- the X and any further bytes, + * but *excluding* any terminating IAC SE + * + * Note: a Telnet command may appear at any time, including in the + * middle of any of the above "real" keystrokes. + * + * This is made invisible to the "real" keystrokes, and the Telnet + * command(s) will appear before the incomplete real keystroke in + * the keystroke stream. + * + * Note: there are three forms of Telnet command, and one escape. + * + * 1) IAC IAC is an escape, and maps simply to the value 0xFF, + * wherever it appears (except when reading an <option>). + * + * 2) IAC X -- two bytes, where X < 250 (SB) + * + * 3) IAC X O -- three bytes, where X is 251..254 (WILL/WONT/DO/DONT) + * + * the O may be 0x00..0xFF (where 0xFF is *NOT* escaped) + * + * 4) IAC SB O ... IAC SE -- many bytes. + * + * the O may be 0x00..0xFF (where 0xFF is *NOT* escaped) + * + * the data ... is subject to IAC IAC escaping (so that + * the terminating IAC SE is unambiguous). + * + * This implementation treats IAC X (where X is not IAC + * and not SE) as an error -- terminating the command + * before the IAC, and marking it malformed. The IAC X + * will then be taken as a new command. + * + * Extended Option objects (O = 0xFF) are exotic, but the above will + * parse them. + */ + +/*------------------------------------------------------------------------------ + * Encoding of keystrokes in the keystroke FIFO. + * + * The first byte of a keystroke is as follows: + * + * 1. 0x00..0x7F -- simple character -- complete keystroke + * + * 2. 0x80..0xFF -- compound keystroke -- further bytes (may) follow + * + * A compound keystroke comprises: + * + * 1. type of keystroke -- see enum keystroke_type + * + * 2. number of bytes that make up the keystroke + * + * This is limited to keystroke_max_len. Any keystroke longer than that + * is marked as truncated. + * + * 3. the bytes (0..keystroke_max_len of them) + * + * This is encoded as <first> <length> [ <bytes> ] + * + * Where: + * + * <first> is 0x80..0xFF (as above): + * + * b7 = 1 + * b6 = 0 : "reserved" + * b5 : 0 => OK + * 1 => broken object + * b4 : 0 => OK + * 1 => truncated + * + * b3..0 : type of keystroke -- so 16 types of keystroke + * + * <length> is 0..keystroke_max_len + * + * <bytes> (if present) are the bytes: + * + * ks_null -- should not appear in the FIFO + * + * ks_char -- character value, in network order, length 1..4 + * + * BUT, if broken or truncated, the raw bytes received (must + * be at least one). + * + * ks_esc -- byte that followed the ESC, length = 0..1 + * + * Length 0 => EOF met => broken + * + * ks_csi -- bytes that followed the ESC [ or CSI, length 1..n + * + * The last byte is *always* the byte that terminated the + * sequence, or '\0' if is badly formed, or hit EOF. + * + * If the sequence is truncated, then the last byte of the + * sequence is written over the last buffered byte. + * + * ks_iac -- bytes of the telnet command, excluding the leading + * IAC and the trailing IAC SE, length 1..n. + * + * IAC IAC pairs are not telnet commands. + * + * IAC IAC pairs between SB X O and IAC SE are reduced to + * 0xFF. + * + * Telnet commands are broken if EOF is met before the end + * of the command, or get IAC X between SB X O and IAC SE + * (where X is not IAC or SE). + */ + +enum stream_state +{ + kst_null, /* nothing special (but see iac) */ + + kst_char, /* collecting a multi-byte character */ + kst_esc, /* collecting an ESC sequence */ + kst_csi, /* collecting an ESC '[' or CSI sequence */ + + kst_iac_option, /* waiting for option (just seen IAC X) */ + kst_iac_sub, /* waiting for IAC SE */ +} ; + +struct keystroke_state +{ + enum stream_state state ; + unsigned len ; + uint8_t raw[keystroke_max_len] ; +} ; + +struct keystroke_stream +{ + vio_fifo_t fifo ; /* the keystrokes */ + + uint8_t CSI ; /* CSI character value (if any) */ + + bool eof_met ; /* nothing more to come */ + + bool steal_this ; /* steal current keystroke when complete */ + + bool iac ; /* last character was an IAC */ + + struct keystroke_state in ; /* current keystroke being collected */ + + struct keystroke_state pushed_in ; + /* keystroke interrupted by IAC */ +} ; + +/* Buffering of keystrokes */ + +enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */ + +/*------------------------------------------------------------------------------ + * Prototypes + */ +inline static int keystroke_set_null(keystroke_stream stream, keystroke stroke); +inline static uint8_t keystroke_get_byte(keystroke_stream stream) ; +static inline void keystroke_add_raw(keystroke_stream stream, uint8_t u) ; +static void keystroke_put_char(keystroke_stream stream, uint32_t u) ; +inline static void keystroke_put_esc(keystroke_stream stream, uint8_t u, + int len) ; +inline static void keystroke_put_csi(keystroke_stream stream, uint8_t u) ; +inline static void keystroke_put_iac(keystroke_stream stream, uint8_t u, + int len) ; +inline static void keystroke_put_iac_long(keystroke_stream stream, + int broken) ; +static void keystroke_steal_char(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_steal_esc(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_steal_csi(keystroke steal, keystroke_stream stream, + uint8_t u) ; +static void keystroke_put(keystroke_stream stream, enum keystroke_type type, + int broken, uint8_t* p, int len) ; + +/*============================================================================== + * Creating and freeing keystroke streams and keystroke stream buffers. + */ + +/*------------------------------------------------------------------------------ + * Create and initialise a keystroke stream. + * + * Can set CSI character value. '\0' => none. (As does '\x1B' !) + */ +extern keystroke_stream +keystroke_stream_new(uint8_t csi_char) +{ + keystroke_stream stream ; + + stream = XCALLOC(MTYPE_KEY_STREAM, sizeof(struct keystroke_stream)) ; + + /* Zeroising the structure sets: + * + * eof_met = false -- no EOF yet + * steal = false -- no stealing set + * iac = false -- last character was not an IAC + * + * in.state = kst_null + * in.len = 0 -- nothing in the buffer + * + * pushed_in.state ) ditto + * pushed_in.len ) + */ + confirm(kst_null == 0) ; + + vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ; + + stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ; + + return stream ; +} ; + +/*------------------------------------------------------------------------------ + * Free keystroke stream and all associated buffers. + */ +extern void +keystroke_stream_free(keystroke_stream stream) +{ + if (stream == NULL) + return ; + + vio_fifo_reset_keep(&stream->fifo) ; + + XFREE(MTYPE_KEY_STREAM, stream) ; +} ; + +/*============================================================================== + * Keystroke stream state + */ + +/*------------------------------------------------------------------------------ + * See if given keystroke stream is empty + * + * May or may not be at "EOF", see below. + * + * Returns: true <=> is empty + */ +extern bool +keystroke_stream_empty(keystroke_stream stream) +{ + return vio_fifo_empty(&stream->fifo) ; +} ; + +/*------------------------------------------------------------------------------ + * See if given keystroke stream is at "EOF", that is: + * + * * keystroke stream is empty + * + * * there is no partial keystroke in construction + * + * * EOF has been signalled by a suitable call of keystroke_input(). + * + * Returns: true <=> is at EOF + */ +extern bool +keystroke_stream_eof(keystroke_stream stream) +{ + /* Note that when EOF is signalled, any partial keystroke in construction + * is converted to a broken keystroke and placed in the stream. + * (So eof_met => no partial keystroke.) + */ + return vio_fifo_empty(&stream->fifo) && stream->eof_met ; +} ; + +/*------------------------------------------------------------------------------ + * Set keystroke stream to "EOF", that is: + * + * * discard contents of the stream, including any partial keystroke. + * + * * set the stream "eof_met". + */ +extern void +keystroke_stream_set_eof(keystroke_stream stream) +{ + vio_fifo_reset_keep(&stream->fifo) ; + + stream->eof_met = 1 ; /* essential information */ + + stream->steal_this = 0 ; /* keep tidy */ + stream->iac = 0 ; + stream->in.state = kst_null ; + stream->pushed_in.state = kst_null ; +} ; + + +/*============================================================================== + * Input raw bytes to given keyboard stream. + * + * To steal the next keystroke, pass 'steal' = address of a keystroke structure. + * Otherwise, pass NULL. + * + * Note: when trying to steal, will complete any partial keystroke before + * stealing the next one. May exit from here: + * + * a. without having completed the partial keystroke. + * + * b. without having completed the keystroke to be stolen. + * + * State (b) is remembered by the keystroke_stream. + * + * Caller may have to call several times with steal != NULL to get a + * keystroke. + * + * If steal != NULL the keystroke will be set to the stolen keystroke. That + * will be type ks_null if nothing was available, and may be knull_eof. + * + * Note that never steals broken or truncated keystrokes. + * + * Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream. + * + * Updates the stream and returns updated raw + */ +extern void +keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, + keystroke steal) +{ + uint8_t* end ; + + /* Deal with EOF if required + * + * Any partial keystroke is converted into a broken keystroke and placed + * at the end of the stream. + * + * Note that this occurs before any attempt to steal a keystroke -- so can + * never steal a broken keystroke. + */ + if ((len == 0) && (ptr == NULL)) + { + stream->eof_met = 1 ; + stream->steal_this = 0 ; + + if (stream->iac && (stream->in.state == kst_null)) + keystroke_put_iac(stream, '\0', 0) ; + + /* Uses a while loop here to deal with partial IAC which has interrupted + * a partial escape or other sequence. + */ + while (stream->in.state != kst_null) + { + switch (stream->in.state) + { + case kst_esc: /* expecting rest of escape */ + keystroke_put_esc(stream, '\0', 0) ; + stream->in.state = kst_null ; + break ; + + case kst_csi: + keystroke_put_csi(stream, '\0') ; + stream->in.state = kst_null ; + break ; + + case kst_iac_option: /* expecting rest of IAC */ + case kst_iac_sub: + keystroke_put_iac_long(stream, 1) ; + /* pops the stream->pushed_in */ + break ; + + case kst_char: /* TBD */ + zabort("impossible keystroke stream state") ; + + default: + zabort("unknown keystroke stream state") ; + } ; + } ; + } ; + + /* Update the stealing state + * + * steal != NULL => want to steal a keystroke + * + * If do not wish to steal now, must clear any + * remembered steal_this state. + * + * stream->steal_this => steal the next keystroke to complete. + * + * If want to steal a keystroke, this is set if + * currently "between" keystrokes, or later when + * reach that condition. + * + * Once set, this is remembered across calls to + * keystroke_input(), while still wish to steal. + */ + if (steal == NULL) + stream->steal_this = 0 ; /* clear as not now required */ + else + stream->steal_this = (stream->in.state == kst_null) ; + /* want to and can can steal the next + keystroke that completes */ + + /* Once EOF has been signalled, do not expect to receive any further input. + * + * However, keystroke_stream_set_eof() can set the stream artificially at + * EOF, and that is honoured here. + */ + if (stream->eof_met) + len = 0 ; + + /* Normal processing + * + * Note that when manages to steal a keystroke sets steal == NULL, and + * proceeds to collect any following keystrokes. + */ + end = ptr + len ; + while (ptr < end) + { + uint8_t u = *ptr++ ; + + /* IAC handling takes precedence over everything, except the <option> + * byte, which may be EXOPL, which happens to be 255 as well ! + */ + if ((u == tn_IAC) && (stream->in.state != kst_iac_option)) + { + if (stream->iac) + stream->iac = 0 ; /* IAC IAC => single IAC byte value */ + else + { + stream->iac = 1 ; /* seen an IAC */ + continue ; /* wait for next character */ + } ; + } ; + + /* If stream->iac, then need to worry about IAC XX + * + * Note that IAC sequences are entirely invisible to the general + * stream of keystrokes. So... IAC sequences may appear in the middle + * of multi-byte general keystroke objects. + * + * If this is not a simple 2 byte IAC, then must put whatever was + * collecting to one side, and deal with the IAC. + * + * Note: not interested in stealing an IAC object. + */ + if (stream->iac) + { + stream->iac = 0 ; /* assume will eat the IAC XX */ + + switch (stream->in.state) + { + case kst_null: + case kst_esc: + case kst_csi: + if (u < tn_SB) + keystroke_put_iac(stream, u, 1) ; + else + { + stream->pushed_in = stream->in ; + + stream->in.len = 1 ; + stream->in.raw[0] = u ; + + stream->in.state = kst_iac_option ; + } + break ; + + case kst_iac_sub: + assert(stream->in.raw[0] == tn_SB) ; + + if (u != tn_SE) + { + --ptr ; /* put back the XX */ + stream->iac = 1 ; /* put back the IAC */ + } ; + + keystroke_put_iac_long(stream, (u != tn_SE)) ; + /* pops the stream->pushed_in */ + break ; + + case kst_char: /* TBD */ + case kst_iac_option: + zabort("impossible keystroke stream state") ; + + default: + zabort("unknown keystroke stream state") ; + } ; + + continue ; + } ; + + /* No IAC complications... proceed per current state */ + switch (stream->in.state) + { + case kst_null: /* Expecting anything */ + stream->steal_this = (steal != NULL) ; + + if (u == 0x1B) + stream->in.state = kst_esc ; + else if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */ + { + stream->in.len = 0 ; + stream->in.state = kst_csi ; + } + else + { + if (!stream->steal_this) + keystroke_put_char(stream, u) ; + else + { + keystroke_steal_char(steal, stream, u) ; + stream->steal_this = 0 ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + } ; + break ; + + case kst_char: /* TBD */ + zabort("impossible keystroke stream state") ; + + case kst_esc: /* Expecting XX after ESC */ + if (u == '[') + { + stream->in.len = 0 ; + stream->in.state = kst_csi ; + } + else + { + if (!stream->steal_this) + keystroke_put_esc(stream, u, 1) ; + else + { + keystroke_steal_esc(steal, stream, u) ; + stream->steal_this = 0 ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + } ; + break ; + + case kst_csi: /* Expecting ... after ESC [ or CSI */ + if ((u >= 0x20) && (u <= 0x3F)) + keystroke_add_raw(stream, u) ; + else + { + int ok = 1 ; + int l ; + + if ((u < 0x40) || (u > 0x7F)) + { + --ptr ; /* put back the duff XX */ + stream->iac = (u == tn_IAC) ; + /* re=escape if is IAC */ + u = '\0' ; + ok = 0 ; /* broken */ + } ; + + l = stream->in.len++ ; + if (l >= keystroke_max_len) + { + l = keystroke_max_len - 1 ; + ok = 0 ; /* truncated */ + } ; + stream->in.raw[l] = u ; /* plant terminator */ + + if (!stream->steal_this || !ok) + keystroke_put_csi(stream, u) ; + else + { + keystroke_steal_csi(steal, stream, u) ; + stream->steal_this = 0 ; + steal = NULL ; + } ; + stream->in.state = kst_null ; + } ; + break ; + + case kst_iac_option: /* Expecting <option> after IAC XX */ + assert(stream->in.len == 1) ; + keystroke_add_raw(stream, u) ; + + if (stream->in.raw[0]== tn_SB) + stream->in.state = kst_iac_sub ; + else + keystroke_put_iac_long(stream, 0) ; + /* pops the stream->pushed_in */ + break ; + + case kst_iac_sub: /* Expecting sub stuff */ + assert(stream->in.raw[0]== tn_SB) ; + + keystroke_add_raw(stream, u) ; + break ; + + default: + zabort("unknown keystroke stream state") ; + } + } ; + assert(ptr == end) ; + + /* If did not steal a keystroke, return a ks_null -- which may be + * a knull_eof. + */ + if (steal != NULL) + keystroke_set_null(stream, steal) ; +} ; + +/*============================================================================== + * Fetch next keystroke from keystroke stream + * + * Returns: 1 => have a stroke type != ks_null + * 0 => stroke type is ks_null (may be EOF). + */ +extern int +keystroke_get(keystroke_stream stream, keystroke stroke) +{ + int b ; + uint8_t* p ; + uint8_t* e ; + + /* Get first byte and deal with FIFO empty response */ + b = vio_fifo_get_byte(&stream->fifo) ; + + if (b < 0) + return keystroke_set_null(stream, stroke) ; + + /* Fetch first byte and deal with the simple character case */ + if ((b & kf_compound) == 0) /* Simple character ? */ + { + stroke->type = ks_char ; + stroke->value = b ; + + stroke->flags = 0 ; + stroke->len = 1 ; + stroke->buf[0] = b ; + + return 1 ; + } ; + + /* Sex the compound keystroke */ + + stroke->type = b & kf_type_mask ; + stroke->value = 0 ; + stroke->flags = b & (kf_broken | kf_truncated) ; + stroke->len = keystroke_get_byte(stream) ; + + /* Fetch what we need to the stroke buffer */ + p = stroke->buf ; + e = p + stroke->len ; + + while (p < e) + *p++ = keystroke_get_byte(stream) ; + + p = stroke->buf ; + + /* Complete the process, depending on the type */ + switch (stroke->type) + { + case ks_null: + zabort("ks_null found in FIFO") ; + + case ks_char: + /* If character is well formed, set its value */ + if (stroke->flags == 0) + { + assert((stroke->len > 0) && (stroke->len <= 4)) ; + while (p < e) + stroke->value = (stroke->value << 8) + *p++ ; + + /* NB: to do UTF-8 would need to create UTF form here */ + + } ; + break ; + + case ks_esc: + /* If have ESC X, set value = X */ + if (stroke->len == 1) + stroke->value = *p ; + else + assert(stroke->len == 0) ; + break ; + + case ks_csi: + /* If have the final X, set value = X */ + /* Null terminate the parameters */ + if (stroke->len != 0) + { + --e ; + stroke->value = *e ; + --stroke->len ; + } ; + *e = '\0' ; + break ; + + case ks_iac: + /* If have the command byte after IAC, set value */ + if (stroke->len > 0) + stroke->value = *p ; + break ; + + default: + zabort("unknown keystroke type") ; + } ; + + return 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Set given keystroke to ks_null -- and set to knull_eof if stream->eof_met + * + * Returns: 0 + */ +inline static int +keystroke_set_null(keystroke_stream stream, keystroke stroke) +{ + stroke->type = ks_null ; + stroke->value = stream->eof_met ? knull_eof : knull_not_eof ; + + stroke->flags = 0 ; + stroke->len = 0 ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Fetch 2nd or subsequent byte of keystroke. + * + * NB: it is impossible for partial keystrokes to be written, so this treats + * buffer empty as a FATAL error. + */ +inline static uint8_t +keystroke_get_byte(keystroke_stream stream) +{ + int b = vio_fifo_get_byte(&stream->fifo) ; + + passert(b >= 0) ; + + return b ; +} ; + +/*============================================================================== + * Functions to support keystroke_input. + */ + +/*------------------------------------------------------------------------------ + * If possible, add character to the stream->in.raw[] buffer + */ +static inline void +keystroke_add_raw(keystroke_stream stream, uint8_t u) +{ + if (stream->in.len < keystroke_max_len) + stream->in.raw[stream->in.len] = u ; + + ++stream->in.len ; +} ; + +/*------------------------------------------------------------------------------ + * Store simple character value + */ +static void +keystroke_put_char(keystroke_stream stream, uint32_t u) +{ + if (u < 0x80) + vio_fifo_put_byte(&stream->fifo, (uint8_t)u) ; + else + { + uint8_t buf[4] ; + uint8_t* p ; + + p = buf + 4 ; + + do + { + *(--p) = u & 0xFF ; + u >>= 8 ; + } + while (u != 0) ; + + keystroke_put(stream, ks_char, 0, p, (buf + 4) - p) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Store simple ESC. Is broken if length (after ESC) == 0 ! + */ +inline static void +keystroke_put_esc(keystroke_stream stream, uint8_t u, int len) +{ + keystroke_put(stream, ks_esc, (len == 0), &u, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store CSI. + * + * Plants the last character of the CSI in the buffer, even if has to overwrite + * the existing last character -- the sequence is broken in any case, but this + * way at least we have the end of the sequence. + * + * Is broken if u == '\0'. May also be truncated ! + */ +inline static void +keystroke_put_csi(keystroke_stream stream, uint8_t u) +{ + keystroke_put(stream, ks_csi, (u == '\0'), stream->in.raw, stream->in.len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store simple IAC. Is broken (EOF met) if length (after IAC) == 0 + */ +inline static void +keystroke_put_iac(keystroke_stream stream, uint8_t u, int len) +{ + keystroke_put(stream, ks_iac, (len == 0), &u, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store long IAC. Is broken if says it is. + * + * Pops the stream->pushed_in + */ +inline static void +keystroke_put_iac_long(keystroke_stream stream, int broken) +{ + keystroke_put(stream, ks_iac, broken, stream->in.raw, stream->in.len) ; + + stream->in = stream->pushed_in ; + stream->pushed_in.state = kst_null ; +} ; + +/*------------------------------------------------------------------------------ + * Store <first> <len> [<bytes>] + */ +static void +keystroke_put(keystroke_stream stream, enum keystroke_type type, int broken, + uint8_t* p, int len) +{ + if (len > keystroke_max_len) + { + len = keystroke_max_len ; + type |= kf_truncated ; + } ; + + vio_fifo_put_byte(&stream->fifo, + kf_compound | (broken ? kf_broken : 0) | type) ; + vio_fifo_put_byte(&stream->fifo, len) ; + + if (len > 0) + vio_fifo_put(&stream->fifo, (void*)p, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Steal character value -- cannot be broken + */ +static void +keystroke_steal_char(keystroke steal, keystroke_stream stream, uint8_t u) +{ + steal->type = ks_char ; + steal->value = u ; + steal->flags = 0 ; + steal->len = 1 ; + steal->buf[0] = u ; +} ; + +/*------------------------------------------------------------------------------ + * Steal simple escape -- cannot be broken + */ +static void +keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u) +{ + steal->type = ks_esc ; + steal->value = u ; + steal->flags = 0 ; + steal->len = 1 ; + steal->buf[0] = u ; +} ; + +/*------------------------------------------------------------------------------ + * Steal CSI escape. + * + * In the stream-in.raw buffer the last character is the escape terminator, + * after the escape parameters. + * + * In keystroke buffer the escape parameters are '\0' terminated, and the + * escape terminator is the keystroke value. + * + * Does not steal broken or truncated stuff. + */ +static void +keystroke_steal_csi(keystroke steal, keystroke_stream stream, uint8_t u) +{ + int len ; + + len = stream->in.len ; /* includes the escape terminator */ + assert(len <= keystroke_max_len) ; + + steal->type = ks_esc ; + steal->value = u ; + steal->flags = 0 ; + steal->len = len - 1 ; + + memcpy(steal->buf, stream->in.raw, len - 1) ; + steal->buf[len] = '\0' ; +} ; + +/*============================================================================== + */ diff --git a/lib/keystroke.h b/lib/keystroke.h new file mode 100644 index 00000000..4dc94d12 --- /dev/null +++ b/lib/keystroke.h @@ -0,0 +1,187 @@ +/* Keystroke Buffering -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_KEYSTROKE_H +#define _ZEBRA_KEYSTROKE_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include <arpa/telnet.h> + +#include "zassert.h" +#include "vio_fifo.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Keystroke buffering + */ + +enum { keystroke_max_len = 100 } ; + +enum keystroke_type +{ + ks_null = 0, /* nothing, nada, bupkis... */ + ks_char, /* character -- uint32_t */ + ks_esc, /* ESC xx */ + ks_csi, /* ESC [ ... or CSI ... */ + ks_iac, /* Telnet command */ + + ks_type_count, + + ks_type_reserved = 0x0F, +} ; +CONFIRM(ks_type_count <= ks_type_reserved) ; + +enum keystroke_null +{ + knull_not_eof, + knull_eof +}; + +enum keystroke_flags +{ + kf_compound = 0x80, /* marker on all compound characters */ + kf_reserved = 0x40, + kf_broken = 0x20, /* badly formed in some way */ + kf_truncated = 0x10, /* too big for buffer ! */ + /* for ks_null => EOF */ + + kf_type_mask = 0x0F, /* extraction of type */ +} ; + +CONFIRM(ks_type_reserved == kf_type_mask) ; + +typedef struct keystroke* keystroke ; +typedef struct keystroke_stream* keystroke_stream ; + +struct keystroke +{ + enum keystroke_type type ; + uint8_t flags ; + + uint32_t value ; + + unsigned len ; + uint8_t buf[keystroke_max_len] ; +} ; + +/* Telnet commands/options */ +enum tn_Command +{ + tn_IAC = IAC, /* IAC IAC interpret as command: */ + tn_DONT = DONT, /* IAC DONT <opt> you are not to use option */ + tn_DO = DO, /* IAC DO <opt> please, you use option */ + tn_WONT = WONT, /* IAC WONT <opt> I won't use option */ + tn_WILL = WILL, /* IAC WILL <opt> I will use option */ + tn_SB = SB, /* IAC SB <opt> interpret as subnegotiation */ + tn_GA = GA, /* IAC GA you may reverse the line */ + tn_EL = EL, /* IAC EL erase the current line */ + tn_EC = EC, /* IAC EC erase the current character */ + tn_AYT = AYT, /* IAC AYT are you there */ + tn_AO = AO, /* IAC AO abort output--but let prog finish */ + tn_IP = IP, /* IAC IP interrupt process--permanently */ + tn_BREAK = BREAK, /* IAC BREAK break */ + tn_DM = DM, /* IAC DM data mark--for connect. cleaning */ + tn_NOP = NOP, /* IAC NOP nop */ + tn_SE = SE, /* IAC SE end sub negotiation */ + tn_EOR = EOR, /* IAC EOR end of record (transparent mode) */ + tn_ABORT = ABORT, /* IAC ABORT Abort process */ + tn_SUSP = SUSP, /* IAC SUSP Suspend process */ + tn_EOF = xEOF, /* IAC xEOF End of file: EOF is already used... */ + + tn_SYNCH = SYNCH, /* IAC SYNCH for telfunc calls */ +} ; + +enum tn_Option +{ + to_BINARY = TELOPT_BINARY, /* 8-bit data path */ + to_ECHO = TELOPT_ECHO, /* echo */ + to_RCP = TELOPT_RCP, /* prepare to reconnect */ + to_SGA = TELOPT_SGA, /* suppress go ahead */ + to_NAMS = TELOPT_NAMS, /* approximate message size */ + to_STATUS = TELOPT_STATUS, /* give status */ + to_TM = TELOPT_TM, /* timing mark */ + to_RCTE = TELOPT_RCTE, /* remote controlled tx and echo */ + to_NAOL = TELOPT_NAOL, /* neg. about output line width */ + to_NAOP = TELOPT_NAOP, /* neg. about output page size */ + to_NAOCRD = TELOPT_NAOCRD, /* neg. about CR disposition */ + to_NAOHTS = TELOPT_NAOHTS, /* neg. about horizontal tabstops */ + to_NAOHTD = TELOPT_NAOHTD, /* neg. about horizontal tab disp. */ + to_NAOFFD = TELOPT_NAOFFD, /* neg. about formfeed disposition */ + to_NAOVTS = TELOPT_NAOVTS, /* neg. about vertical tab stops */ + to_NAOVTD = TELOPT_NAOVTD, /* neg. about vertical tab disp. */ + to_NAOLFD = TELOPT_NAOLFD, /* neg. about output LF disposition */ + to_XASCII = TELOPT_XASCII, /* extended ascii character set */ + to_LOGOUT = TELOPT_LOGOUT, /* force logout */ + to_BM = TELOPT_BM, /* byte macro */ + to_DET = TELOPT_DET, /* data entry terminal */ + to_SUPDUP = TELOPT_SUPDUP, /* supdup protocol */ + to_SUPDUPOUTPUT = TELOPT_SUPDUPOUTPUT,/* supdup output */ + to_SNDLOC = TELOPT_SNDLOC, /* send location */ + to_TTYPE = TELOPT_TTYPE, /* terminal type */ + to_EOR = TELOPT_EOR, /* end or record */ + to_TUID = TELOPT_TUID, /* TACACS user identification */ + to_OUTMRK = TELOPT_OUTMRK, /* output marking */ + to_TTYLOC = TELOPT_TTYLOC, /* terminal location number */ + to_3270REGIME = TELOPT_3270REGIME, /* 3270 regime */ + to_X3PAD = TELOPT_X3PAD, /* X.3 PAD */ + to_NAWS = TELOPT_NAWS, /* window size */ + to_TSPEED = TELOPT_TSPEED, /* terminal speed */ + to_LFLOW = TELOPT_LFLOW, /* remote flow control */ + to_LINEMODE = TELOPT_LINEMODE, /* Linemode option */ + to_XDISPLOC = TELOPT_XDISPLOC, /* X Display Location */ + to_OLD_ENVIRON = TELOPT_OLD_ENVIRON, /* Old - Environment variables */ + to_AUTHENTICATION = TELOPT_AUTHENTICATION, /* Authenticate */ + to_ENCRYPT = TELOPT_ENCRYPT, /* Encryption option */ + to_NEW_ENVIRON = TELOPT_NEW_ENVIRON, /* New - Environment variables */ + to_EXOPL = TELOPT_EXOPL, /* extended-options-list */ +} ; + + +/*============================================================================== + * Functions + */ +extern keystroke_stream +keystroke_stream_new(uint8_t csi_char) ; + +extern void +keystroke_stream_set_eof(keystroke_stream stream) ; + +extern void +keystroke_stream_free(keystroke_stream stream) ; + +extern bool +keystroke_stream_empty(keystroke_stream stream) ; +extern bool +keystroke_stream_eof(keystroke_stream stream) ; + +extern void +keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, + keystroke steal) ; +extern int +keystroke_get(keystroke_stream stream, keystroke stroke) ; + +#endif /* _ZEBRA_KEYSTROKE_H */ diff --git a/lib/list_util.c b/lib/list_util.c new file mode 100644 index 00000000..0d61a67a --- /dev/null +++ b/lib/list_util.c @@ -0,0 +1,59 @@ +/* List Utilities + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <list_util.h> + + +/*============================================================================== + * Single Base, Single Link + */ + +/*------------------------------------------------------------------------------ + * Deleting item + * + * Have to chase down list to find item. + * + * Returns: 0 => OK -- removed item from list (OR item == NULL) + * -1 => item not found on list + */ +extern int +ssl_del_func(void** p_this, void* item, size_t link_offset) +{ + if (item == NULL) + return 0 ; + + while (*p_this != item) + { + if (*p_this == NULL) + return -1 ; + + p_this = _sl_p_next(*p_this, link_offset) ; + } ; + + *p_this = _sl_next(item, link_offset) ; + + return 0 ; +} ; + +/*============================================================================== + * Single Base, Double Link + */ + diff --git a/lib/list_util.h b/lib/list_util.h new file mode 100644 index 00000000..b658c7ce --- /dev/null +++ b/lib/list_util.h @@ -0,0 +1,729 @@ +/* List Utilities -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_LIST_UTIL_H +#define _ZEBRA_LIST_UTIL_H + +#include <stddef.h> + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*------------------------------------------------------------------------------ + * Note that the following fell foul of "strict-aliasing": + * + * #define ssl_del_head(base, next) \ + * ssl_del_head_func_i((void**)&(base), _lu_off(base, next)) + * + * Inline void* + * ssl_del_head_func_i(void** p_base, size_t link_offset) + * { + * void* item = *p_base ; + * + * if (item != NULL) + * *p_base = _sl_next(item, link_offset) ; + * + * return item ; + * } ; + * + * the assignment to *p_base is, apparently, unacceptable. This works + * perfectly well as am ordinary function. Using a GNUC extension it is + * possible to avoid the function call... hence the ugly skips. + */ +#ifdef __GNUC__ +#define __GNUC__LIST_UTIL +#endif + +/*============================================================================== + * These utilities provide for linked lists of items, where the list pointers + * are fields in the items. + * + * This is a little less general that the linklist stuff, but carries less + * overhead. + * + * The items will be structures of some sort, and are described here as being + * of type "struct item". Pointers to those items will be of type + * "struct item*". + * + * Most of these utilities are implemented as macros. + * + *------------------------------------------------------------------------------ + * Links and Bases. + * + * For a singly linked list, the item declaration is straightforward: + * + * struct item + * { + * .... + * struct item* foo_next ; + * .... + * } + * + * The item can live on more than one list, all that is required is that each + * list has its next pointer. + * + * For double linked lists, the item may be declared: + * + * struct item + * { + * .... + * struct list_pair(struct item*) foo_list ; + * .... + * } ; + * + * A single base is straighforward: + * + * struct item* foo_base ; + * + * and that may be a variable or a structure field. + * + * A double base may be declared: + * + * struct base_pair(struct item*) foo_base ; + * + * Various ways to construct structures or structure types: + * + * typedef struct list_pair(struct foo*) foo_list ; + * + * struct foo_list list_pair(struct foo*) ; + * + * struct foo_base base_pair(struct foo*) ; + */ + +#define dl_list_pair(ptr_t) { ptr_t next ; ptr_t prev ; } + +#define dl_base_pair(ptr_t) { ptr_t head ; ptr_t tail ; } + +struct dl_void_list_pair list_pair(void*) ; +struct dl_void_base_pair base_pair(void*) ; + +#define _lu_off(obj, field) ((char*)&((obj)->field) - (char*)(obj)) + +/*============================================================================== + * Single Base, Single Link + * + * To delete entry must chase down list to find it. + * + * Supports: + * + * ssl_init(base) -- initialise base + * + * An empty list has a NULL base. + * + * ssl_push(base, item, next) -- add at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ssl_del(base, item, next) -- delete from list + * + * Treat as function returning int. Does nothing if the item is NULL. + * + * Returns: 0 => OK -- removed item from list (OR item == NULL) + * -1 => item not found on list + * + * ssl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ssl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ssl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * ssl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if item is NULL. + * + * Note that ssl_del() and ssl_pop() do NOT affect the item->next pointer. + * + * Where: + * + * "base" to be an r-value of type struct item* + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "next" to be the name of a field in struct item, with type struct item* + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct item* bar_next ; + * ... + * } ; + * + * static struct item* bar_base ; // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * ssl_push(bar_base, q, bar_next) ; + * + * // remove item from list + * ssl_del(bar_base, q, bar_next) ; + * + * // walk a list + * struct item* t = ssl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = ssl_next(t, bar_next) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (ssl_pop(&t, bar_base, bar_next) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = ssl_head(bar_base) != NULL) + * { + * .... + * ssl_del_head(bar_base, bar_next) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct item* bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * ssl_push(parent->bar_base, item, bar_next) ; + * .... + * } + */ + +#define ssl_init(base) \ + ((base) = NULL) + +#define ssl_push(base, item, next) \ + do { confirm(_lu_off(base, next) == _lu_off(item, next)) ; \ + (item)->next = (base) ; \ + (base) = item ; \ + } while (0) + +extern int ssl_del_func(void** p_this, void* obj, size_t link_offset) ; + +#define ssl_del(base, item, next) \ + ssl_del_func((void**)&(base), item, _lu_off(base, next)) + +#define ssl_del_head(base, next) \ + do { if ((base) != NULL) \ + (base) = (base)->next ; \ + } while (0) + +#define ssl_pop(dst, base, next) \ + ((*(dst) = (base)) != NULL ? ((base) = (base)->next, *(dst)) : NULL) + +#define ssl_head(base) (base) + +#define ssl_next(item, next) \ + ((item) != NULL ? (item)->next : NULL) + +/* _sl_p_next(item, off) -- pointer to next pointer at given offset + * _sl_next(item, off) -- contents of next pointer at given offset + */ + +#define _sl_p_next(item, off) \ + ( (void**)( (char*)(item) + (off) ) ) + +#define _sl_next(item, off) \ + *_sl_p_next(item, off) + +/*============================================================================== + * Single Base, Double Link + * + * Can delete entry directly. + * + * Supports: + * + * sdl_init(base) -- initialise base + * + * An empty list has a NULL base. + * + * sdl_push(base, item, list) -- add at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * sdl_del(base, item, list) -- delete from list + * + * Treat as void function. Does nothing if the item is NULL. + * + * Undefined if item is not on the list. + * + * sdl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * sdl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * sdl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * sdl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * sdl_prev(item, next) -- step to prev item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * Note that sdl_del() and sdl_pop() do NOT affect the item->list.next + * or item->list.prev pointers. + * + * Where: + * + * "base" to be an r-value of type: struct base_pair(struct item*)* + * + * That is... a variable or field which is a pointer to + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "list" to be the name of a field in struct item + * of type: struct list_pair(struct item*) + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct list_pair(struct item*) bar_list ; + * ... + * } ; + * + * static struct base_pair(struct item*) bar_base ; + * // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * sdl_push(bar_base, q, bar_list) ; + * + * // remove item from list + * sdl_del(bar_base, q, bar_list) ; + * + * // walk a list + * struct item* t = sdl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = sdl_next(t, bar_list) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (sdl_pop(&t, bar_base, bar_list) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = sdl_head(bar_base) != NULL) + * { + * .... + * sdl_del_head(bar_base, bar_list) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct base_pair(struct item*) bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * sdl_push(parent->bar_base, item, bar_list) ; + * .... + * } + */ + +#define sdl_init(base) \ + ((base) = NULL) + +#define sdl_push(base, item, list) \ + do { confirm(_lu_off(base, list.next) == _lu_off(item, list.next)) ; \ + confirm(_lu_off(base, list.prev) == _lu_off(item, list.prev)) ; \ + (item)->list.next = (base) ; \ + (item)->list.prev = NULL ; \ + if ((base) != NULL) \ + (base)->list.prev = (item) ; \ + (base) = (item) ; \ + } while (0) + +#define sdl_del(base, item, list) \ + do { confirm(_lu_off(base, list.next) == _lu_off(item, list.next)) ; \ + confirm(_lu_off(base, list.prev) == _lu_off(item, list.prev)) ; \ + if ((item) != NULL) \ + { \ + if ((item)->list.next != NULL) \ + (item)->list.next->list.prev = (item)->list.prev ; \ + if ((item)->list.prev != NULL) \ + (item)->list.prev->list.next = (item)->list.next ; \ + else \ + (base) = (item)->list.next ; \ + } ; \ + } while (0) + +#define sdl_del_head(base, list) \ + do { if ((base) != NULL) \ + { \ + (base) = (base)->list.next ; \ + if ((base) != NULL) \ + (base)->list.prev = NULL ; \ + } \ + } while (0) + +#define sdl_pop(dst, base, list) \ + ((*(dst) = (base)) != NULL \ + ? ( ((base) = (base)->list.next) != NULL \ + ? ( (base)->list.prev = NULL, *(dst) ) : *(dst) ) : NULL) + +#define sdl_head(base) (base) + +#define sdl_next(item, list) \ + ((item) != NULL ? (item)->list.next : NULL) + +#define sdl_prev(item, list) \ + ((item) != NULL ? (item)->list.prev : NULL) + +/* _dl_p_next(obj, off) -- pointer to next pointer at given offset + * _dl_next(obj, off) -- contents of next pointer at given offset + * _dl_p_prev(obj, off) -- pointer to prev pointer at given offset + * _dl_prev(obj, off) -- contents of prev pointer at given offset + */ +#define _dl_p_next(obj, off) \ + ( (void**)( (char*)(obj) + (off) + 0 ) ) + +#define _dl_next(obj, off) \ + *_dl_p_next(obj, off) + +#define _dl_p_prev(obj, off) \ + ( (void**)( (char*)(obj) + (off) _ sizeof(void*) ) ) + +#define _dl_prev(obj, off) \ + *_dl_p_next(obj, off) + +/*============================================================================== + * Double Base, Double Link + * + * Can delete entry directly. Can insert and remove at tail. + * + * Supports: + * + * ddl_init(base) -- initialise base + * + * An empty list has *both* head and tail pointers NULL. + * + * NB: confusion will arise if only one of these pointers is NULL. + * + * ddl_push(base, item, list) -- insert at head of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ddl_append(base, item, list) -- insert at tail of list + * + * Treat as void function. The item may *not* be NULL. + * + * Undefined if item is already on any list (including this one). + * + * ddl_in_after(after, base, item, list) -- insert after + * + * Treat as void function. The after & item may *not* be NULL. + * + * Undefined if item is already on any list (including this one), or if + * after is not on the list. + * + * ddl_in_before(before, base, item, list) -- insert before + * + * Treat as void function. The before & item may *not* be NULL. + * + * Undefined if item is already on any list (including this one), or if + * before is not on the list. + * + * ddl_pop(&dst, base, next) -- pop head of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ddl_crop(&dst, base, next) -- crop tail of list, if any + * + * Treat as function returning void*. + * + * Returns old head in dst and as return from "function". + * + * Returns NULL and sets dst == NULL if list is empty. + * + * ddl_del(base, item, list) -- delete from list + * + * Treat as void function. Does nothing if the item is NULL. + * + * Undefined if item is not on the list. + * + * ddl_del_head(base, next) -- delete head of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ddl_del_tail(base, next) -- delete tail of list + * + * Treat as void function. Does nothing if the list is empty. + * + * ddl_head(base) -- return head of list + * + * Treat as function returning void*. + * + * ddl_tail(base) -- return tail of list + * + * Treat as function returning void*. + * + * ddl_next(item, next) -- step to next item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * ddl_prev(item, next) -- step to prev item, if any + * + * Treat as function returning void*. Returns NULL if the item is NULL. + * + * Note that ddl_del() and ddl_pop() do NOT affect the item->list.next + * or item->list.prev pointers. + * + * Where: + * + * "base" to be an r-value of type: struct base_pair(struct item*)* + * + * That is... a variable or field which is a pointer to + * + * "item" to be an l-value of type struct item* + * + * "dst" to be an r-value of type struct item* + * + * "list" to be the name of a field in struct item + * of type: struct list_pair(struct item*) + * + * + * + *------------------------------------------------------------------------------ + * For example: + * + * struct item // definition for list items + * { + * ... + * struct list_pair(struct item*) bar_list ; + * ... + * } ; + * + * static struct base_pair(struct item*) bar_base ; + * // declaration of the list base + * + * // create item and add to list (adds at front) + * struct item* q = calloc(1, sizeof(struct item)) ; + * ddl_push(bar_base, q, bar_list) ; + * + * // remove item from list + * ddl_del(bar_base, q, bar_list) ; + * + * // walk a list + * struct item* t = ddl_head(bar_base) ; + * while (t != NULL) + * { + * .... + * t = ddl_next(t, bar_list) ; + * } + * + * // walk and empty out a list -- removing item before processing + * struct item* t ; + * while (ddl_pop(&t, bar_base, bar_list) != NULL) + * { + * .... // t points to old head of list + * } + * + * // walk and empty out a list -- removing after processing + * struct item* t ; + * while ((t = ddl_head(bar_base) != NULL) + * { + * .... + * ddl_del_head(bar_base, bar_list) ; + * } + * + * And for example: + * + * struct parent_item // parent structure containing list + * { + * .... + * struct base_pair(struct item*) bar_base ; + * .... + * } + * + * void footle(struct parent_item* parent, struct item* item) + * { + * .... + * ddl_push(parent->bar_base, item, bar_list) ; + * .... + * } + */ + +#define ddl_init(base) \ + ((base).head = (base).tail = NULL) + +#define ddl_push(base, item, list) \ + do { (item)->list.next = (base).head ; \ + (item)->list.prev = NULL ; \ + if ((base).head != NULL) \ + (base).head->list.prev = (item) ; \ + else \ + (base).tail = (item) ; \ + (base).head = (item) ; \ + } while (0) + +#define ddl_append(base, item, list) \ + do { (item)->list.next = NULL ; \ + (item)->list.prev = (base).tail ; \ + if ((base).tail != NULL) \ + (base).tail->list.next = (item) ; \ + else \ + (base).head = (item) ; \ + (base).tail = (item) ; \ + } while (0) + +#define ddl_in_after(after, base, item, list) \ + do { (item)->list.next = (after)->list.next ; \ + (item)->list.prev = (after) ; \ + if ((after)->list.next != NULL) \ + (after)->list.next->list.prev = (item) ; \ + else \ + (base).tail = (item) ; \ + (after)->list.next = (item) ; \ + } while (0) + +#define ddl_in_before(before, base, item, list) \ + do { (item)->list.next = (before) ; \ + (item)->list.prev = (before)->list.prev ; \ + if ((before)->list.prev != NULL) \ + (before)->list.prev->list.next = (item) ; \ + else \ + (base).head = (item) ; \ + (before)->list.prev = (item) ; \ + } while (0) + +#define ddl_del(base, item, list) \ + do { if ((item) != NULL) \ + { \ + if ((item)->list.next != NULL) \ + (item)->list.next->list.prev = (item)->list.prev ; \ + else \ + (base).tail = (item)->list.prev ; \ + if ((item)->list.prev != NULL) \ + (item)->list.prev->list.next = (item)->list.next ; \ + else \ + (base).head = (item)->list.next ; \ + } ; \ + } while (0) + +#define ddl_del_head(base, list) \ + do { if ((base).head != NULL) \ + { \ + (base).head = (base).head->list.next ; \ + if ((base).head != NULL) \ + (base).head->list.prev = NULL ; \ + else \ + (base).tail = NULL ; \ + } \ + } while (0) + +#define ddl_del_tail(base, list) \ + do { if ((base).tail != NULL) \ + { \ + (base).tail = (base).tail->list.prev ; \ + if ((base).tail != NULL) \ + (base).tail->list.next = NULL ; \ + else \ + (base).head = NULL ; \ + } \ + } while (0) + +#define ddl_pop(dst, base, list) \ + ((*(dst) = (base).head) != NULL \ + ? ( ((base).head = (base).head->list.next) != NULL \ + ? ( (base).head->list.prev = NULL, *(dst) ) \ + : ( (base).tail = NULL, *(dst) ) ) \ + : NULL) + +#define ddl_crop(dst, base, list) \ + ((*(dst) = (base).tail) != NULL \ + ? ( ((base).tail = (base).tail->list.prev) != NULL \ + ? ( (base).tail->list.next = NULL, *(dst) ) \ + : ( (base).head = NULL, *(dst) ) ) \ + : NULL) + +#define ddl_head(base) ((base).head) + +#define ddl_tail(base) ((base).tail) + +#define ddl_next(item, list) \ + ((item) != NULL ? (item)->list.next : NULL) + +#define ddl_prev(item, list) \ + ((item) != NULL ? (item)->list.prev : NULL) + +#endif /* _ZEBRA_LIST_UTIL_H */ @@ -25,6 +25,8 @@ #include <zebra.h> #include "log.h" +#include "vty.h" +#include "uty.h" #include "memory.h" #include "command.h" #ifndef SUNOS_5 @@ -36,15 +38,6 @@ #endif #include "qpthreads.h" -#ifdef NDEBUG -#define LOCK qpt_mutex_lock(&vty_mutex); -#define UNLOCK qpt_mutex_unlock(&vty_mutex); -#else -#define LOCK qpt_mutex_lock(&vty_mutex);++vty_lock_count; -#define UNLOCK --vty_lock_count;qpt_mutex_unlock(&vty_mutex); -#define ASSERTLOCKED if(vty_lock_count==0 && !vty_lock_asserted){vty_lock_asserted=1;assert(0);} -#endif - /* log is protected by the same mutext as vty, see comments in vty.c */ /* prototypes */ @@ -86,30 +79,55 @@ const char *zlog_priority[] = NULL, }; -/* For time string format. */ +/*============================================================================== + * Time stamp handling -- gettimeofday(), localtime() and strftime(). + * + * Maintains a cached form of the current time (under the vty/log mutex), so + * that can avoid multiple calls of localtime() and strftime() per second. + * + * The value from gettimeofday() is in micro-seconds. Can provide timestamp + * with any number of decimal digits, but at most 6 will be significant. + */ +/*------------------------------------------------------------------------------ + * Fill buffer with current time, to given number of decimal digits. + * + * If given buffer is too small, provides as many characters as possible. + * + * Returns: number of characters in buffer, not including trailing '\0'. + * + * NB: does no rounding. + * + * NB: buflen MUST be > 1 and buf MUST NOT be NULL. + */ size_t quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { size_t result; - LOCK + VTY_LOCK() ; result = uquagga_timestamp(timestamp_precision, buf, buflen); - UNLOCK + VTY_UNLOCK() ; return result; } -/* unprotected version for when mutex already held */ +/*------------------------------------------------------------------------------ + * unprotected version for when mutex already held + */ size_t uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { static struct { time_t last; size_t len; - char buf[28]; + char buf[timestamp_buffer_len]; } cache; + struct timeval clock; - size_t result = 0; + size_t len ; + int left ; + + assert((buflen > 1) && (buf != NULL)) ; /* would it be sufficient to use global 'recent_time' here? I fear not... */ gettimeofday(&clock, NULL); @@ -121,149 +139,233 @@ uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) cache.last = clock.tv_sec; localtime_r(&cache.last, &tm); cache.len = strftime(cache.buf, sizeof(cache.buf), - "%Y/%m/%d %H:%M:%S", &tm); + "%Y/%m/%d %H:%M:%S", &tm) ; + assert(cache.len > 0) ; } /* note: it's not worth caching the subsecond part, because chances are that back-to-back calls are not sufficiently close together for the clock not to have ticked forward */ - if (buflen > cache.len) + len = cache.len ; /* NB: asserted cache.len > 0 */ + + left = (buflen - (len + 1)) ; /* what would be left */ + if (left < 0) + len = buflen - 1 ; /* NB: asserted buflen > 1 */ + + memcpy(buf, cache.buf, len) ; + + /* Can do decimal part if there is room for the '.' character */ + if ((timestamp_precision > 0) && (left > 0)) { - memcpy(buf, cache.buf, cache.len); - if ((timestamp_precision > 0) && - (buflen > cache.len+1+timestamp_precision)) - { - /* should we worry about locale issues? */ - static const int divisor[] = {0, 100000, 10000, 1000, 100, 10, 1}; - int prec; - char *p = buf+cache.len+1+(prec = timestamp_precision); - *p-- = '\0'; - while (prec > 6) - /* this is unlikely to happen, but protect anyway */ - { - *p-- = '0'; - prec--; - } - clock.tv_usec /= divisor[prec]; - do - { - *p-- = '0'+(clock.tv_usec % 10); - clock.tv_usec /= 10; - } - while (--prec > 0); - *p = '.'; - result = cache.len+1+timestamp_precision; - } - else + /* should we worry about locale issues? */ + static const int divisor[] = { 1, /* 0 */ + 100000, 10000, 1000, /* 1, 2, 3 */ + 100, 10, 1}; /* 4, 5, 6 */ + int prec; + char *p ; + + prec = timestamp_precision ; + if ((1 + prec) > left) + prec = left - 1 ; /* NB: left > 0 */ + len += 1 + prec ; + + p = buf + prec ; /* point at last decimal digit */ + + while (prec > 6) + /* this is unlikely to happen, but protect anyway */ { - buf[cache.len] = '\0'; - result = cache.len; - } - } else { - if (buflen > 0) - buf[0] = '\0'; - } + *p-- = '0'; + --prec ; + } ; - return result; -} + clock.tv_usec /= divisor[prec]; -/* Utility routine for current time printing. */ -static void -time_print(FILE *fp, struct timestamp_control *ctl) -{ - if (!ctl->already_rendered) - { - ctl->len = uquagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); - ctl->already_rendered = 1; - } - fprintf(fp, "%s ", ctl->buf); -} + while (prec > 0) /* could have been reduced to 0 */ + { + *p-- = '0'+(clock.tv_usec % 10); + clock.tv_usec /= 10; + --prec ; + } ; + + *p = '.'; + } ; -/* va_list version of zlog. */ + buf[len] = '\0'; + + return len ; +} ; + +/*============================================================================== + * va_list version of zlog + */ static void vzlog (struct zlog *zl, int priority, const char *format, va_list args) { - LOCK + VTY_LOCK() ; uvzlog(zl, priority, format, args); - UNLOCK + VTY_UNLOCK() ; } /* va_list version of zlog. Unprotected assumes mutex already held*/ static void -uvzlog (struct zlog *zl, int priority, const char *format, va_list args) +uvzlog (struct zlog *zl, int priority, const char *format, va_list va) { - struct timestamp_control tsctl; - tsctl.already_rendered = 0; + struct logline ll ; /* prepares line for output, here */ - ASSERTLOCKED + VTY_ASSERT_LOCKED() ; - /* If zlog is not specified, use default one. */ + ll.p_nl = NULL ; /* Nothing generated, yet */ + + /* If zlog is not specified, use default one. */ if (zl == NULL) - zl = zlog_default; + zl = zlog_default ; - /* When zlog_default is also NULL, use stderr for logging. */ + /* When zlog_default is also NULL, use stderr for logging. */ if (zl == NULL) { - tsctl.precision = 0; - time_print(stderr, &tsctl); - fprintf (stderr, "%s: ", "unknown"); - vfprintf (stderr, format, args); - fprintf (stderr, "\n"); - fflush (stderr); + uvzlog_line(&ll, zl, priority, format, va, 0) ; + write(fileno(stderr), ll.line, ll.len) ; } else { - tsctl.precision = zl->timestamp_precision; - - /* Syslog output */ + /* Syslog output */ if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) { va_list ac; - va_copy(ac, args); + va_copy(ac, va); vsyslog (priority|zlog_default->facility, format, ac); va_end(ac); } - /* File output. */ + /* File output. */ if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) { - va_list ac; - time_print (zl->fp, &tsctl); - if (zl->record_priority) - fprintf (zl->fp, "%s: ", zlog_priority[priority]); - fprintf (zl->fp, "%s: ", zlog_proto_names[zl->protocol]); - va_copy(ac, args); - vfprintf (zl->fp, format, ac); - va_end(ac); - fprintf (zl->fp, "\n"); - fflush (zl->fp); + uvzlog_line(&ll, zl, priority, format, va, 0) ; + write(fileno(zl->fp), ll.line, ll.len) ; } /* stdout output. */ if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) { - va_list ac; - time_print (stdout, &tsctl); - if (zl->record_priority) - fprintf (stdout, "%s: ", zlog_priority[priority]); - fprintf (stdout, "%s: ", zlog_proto_names[zl->protocol]); - va_copy(ac, args); - vfprintf (stdout, format, ac); - va_end(ac); - fprintf (stdout, "\n"); - fflush (stdout); + uvzlog_line(&ll, zl, priority, format, va, 0) ; + write(fileno(zl->fp), ll.line, ll.len) ; } /* Terminal monitor. */ if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) - { - const char *priority_name = (zl->record_priority ? zlog_priority[priority] : NULL); - const char *proto_name = zlog_proto_names[zl->protocol]; - vty_log (priority_name, proto_name, format, &tsctl, args); - } + uty_log(&ll, zl, priority, format, va) ; } } +/*------------------------------------------------------------------------------ + * Preparation of line to send to logging: file, stdout or "monitor" terminals. + * + * Takes copy of va_list before using it, so the va_list is unchanged. + */ +extern void +uvzlog_line(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va, int crlf) +{ + char* p ; + + p = ll->p_nl ; + + if (p != NULL) + { + /* we have the line -- just need to worry about the crlf state */ + if ((crlf && ll->crlf) || (!crlf && !ll->crlf)) + return ; /* exit here if all set */ + } + else + { + /* must construct the line */ + const char* q ; + char* e ; + size_t len ; + va_list vac ; + + p = ll->line = ll->buf ; + e = p + sizeof(ll->buf) - 3 ; /* leave space for '\r', '\n' and '\0' */ + + /* "<time stamp> " */ + len = uquagga_timestamp((zl != NULL) ? zl->timestamp_precision : 0, + p, e - p) ; + p += len ; /* len guaranteed to be <= e - p */ + + if (p < e) + *p++ = ' ' ; + + /* "<priority>: " if required */ + if ((zl != NULL) && (zl->record_priority)) + { + q = zlog_priority[priority] ; + len = strlen(q) ; + + if ((p + len) > e) + len = e - p ; + + if (len > 0) + memcpy(p, q, len) ; + p += len ; + + if (p < e) + *p++ = ':' ; + if (p < e) + *p++ = ' ' ; + } ; + + /* "<protocol>: " or "unknown: " */ + if (zl != NULL) + q = zlog_proto_names[zl->protocol] ; + else + q = "unknown" ; + + len = strlen(q) ; + + if ((p + len) > e) + len = e - p ; + + if (len > 0) + memcpy(p, q, len) ; + p += len ; + + if (p < e) + *p++ = ':' ; + if (p < e) + *p++ = ' ' ; + + /* Now the log line itself */ + /* Have reserved space for '\n', so have (e - p + 1) of buffer */ + if (p < e) + { + va_copy(vac, va); + len = vsnprintf(p, (e - p + 1), format, vac) ; + va_end(vac); + + p += len ; /* len returned is *required* length */ + + if (p > e) + p = e ; /* actual end */ + } ; + + ll->p_nl = p ; /* set end pointer */ + + assert(p <= e) ; + } ; + + /* finish off with '\r''\n''\0' or '\n''\0' as required */ + if (crlf) + *p++ = '\r' ; + + *p++ = '\n' ; + *p = '\0' ; + + ll->len = p - ll->line ; + ll->crlf = crlf ; +} ; + +/*============================================================================*/ + static char * str_append(char *dst, int len, const char *src) { @@ -566,9 +668,9 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) void zlog_backtrace(int priority) { - LOCK + VTY_LOCK() ; uzlog_backtrace(priority); - UNLOCK + VTY_UNLOCK() ; } static void @@ -720,9 +822,9 @@ _zlog_abort_err (const char *mess, int err, const char *file, static void zlog_abort (const char *mess) { -#ifndef NDEBUG - /* don't work about being unlocked */ - vty_lock_asserted = 1; +#if VTY_DEBUG + /* May not be locked -- but that doesn't matter any more */ + ++vty_lock_count ; #endif /* Force fallback file logging? */ @@ -778,7 +880,7 @@ closezlog (struct zlog *zl) void zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level) { - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -788,7 +890,7 @@ zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level) zl->maxlvl[dest] = log_level; } - UNLOCK + VTY_UNLOCK() ; } int @@ -798,7 +900,7 @@ zlog_set_file (struct zlog *zl, const char *filename, int log_level) mode_t oldumask; int result = 1; - LOCK + VTY_LOCK() ; /* There is opend file. */ uzlog_reset_file (zl); @@ -825,7 +927,7 @@ zlog_set_file (struct zlog *zl, const char *filename, int log_level) } } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -834,9 +936,9 @@ int zlog_reset_file (struct zlog *zl) { int result; - LOCK + VTY_LOCK() ; result = uzlog_reset_file(zl); - UNLOCK + VTY_UNLOCK() ; return result; } @@ -869,7 +971,7 @@ zlog_rotate (struct zlog *zl) int level; int result = 1; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -908,7 +1010,7 @@ zlog_rotate (struct zlog *zl) } } } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -917,7 +1019,7 @@ zlog_get_default_lvl (struct zlog *zl) { int result = LOG_DEBUG; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -927,14 +1029,14 @@ zlog_get_default_lvl (struct zlog *zl) result = zl->default_lvl; } - UNLOCK + VTY_UNLOCK() ; return result; } void zlog_set_default_lvl (struct zlog *zl, int level) { - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -944,7 +1046,7 @@ zlog_set_default_lvl (struct zlog *zl, int level) zl->default_lvl = level; } - UNLOCK + VTY_UNLOCK() ; } /* Set logging level and default for all destinations */ @@ -953,7 +1055,7 @@ zlog_set_default_lvl_dest (struct zlog *zl, int level) { int i; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -967,7 +1069,7 @@ zlog_set_default_lvl_dest (struct zlog *zl, int level) zl->maxlvl[i] = level; } - UNLOCK + VTY_UNLOCK() ; } int @@ -975,7 +1077,7 @@ zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest) { int result = ZLOG_DISABLED; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -985,7 +1087,7 @@ zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest) result = zl->maxlvl[dest]; } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -994,7 +1096,7 @@ zlog_get_facility (struct zlog *zl) { int result = LOG_DAEMON; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1004,14 +1106,14 @@ zlog_get_facility (struct zlog *zl) result = zl->facility; } - UNLOCK + VTY_UNLOCK() ; return result; } void zlog_set_facility (struct zlog *zl, int facility) { - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1021,7 +1123,7 @@ zlog_set_facility (struct zlog *zl, int facility) zl->facility = facility; } - UNLOCK + VTY_UNLOCK() ; } int @@ -1029,7 +1131,7 @@ zlog_get_record_priority (struct zlog *zl) { int result = 0; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1039,14 +1141,14 @@ zlog_get_record_priority (struct zlog *zl) result = zl->record_priority; } - UNLOCK + VTY_UNLOCK() ; return result; } void zlog_set_record_priority (struct zlog *zl, int record_priority) { - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1055,7 +1157,7 @@ zlog_set_record_priority (struct zlog *zl, int record_priority) { zl->record_priority = record_priority; } - UNLOCK + VTY_UNLOCK() ; } int @@ -1063,7 +1165,7 @@ zlog_get_timestamp_precision (struct zlog *zl) { int result = 0; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1072,14 +1174,14 @@ zlog_get_timestamp_precision (struct zlog *zl) { result = zl->timestamp_precision; } - UNLOCK + VTY_UNLOCK() ; return result; } void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision) { - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1089,7 +1191,7 @@ zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision) zl->timestamp_precision = timestamp_precision; } - UNLOCK + VTY_UNLOCK() ; } /* returns name of ZLOG_NONE if no zlog given and no default set */ @@ -1097,9 +1199,9 @@ const char * zlog_get_proto_name (struct zlog *zl) { const char * result; - LOCK + VTY_LOCK() ; result = uzlog_get_proto_name(zl); - UNLOCK + VTY_UNLOCK() ; return result; } @@ -1126,7 +1228,7 @@ zlog_get_filename (struct zlog *zl) { char * result = NULL; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1136,7 +1238,7 @@ zlog_get_filename (struct zlog *zl) result = strdup(zl->filename); } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -1145,7 +1247,7 @@ zlog_get_ident (struct zlog *zl) { const char * result = NULL; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1155,7 +1257,7 @@ zlog_get_ident (struct zlog *zl) result = zl->ident; } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -1165,7 +1267,7 @@ zlog_is_file (struct zlog *zl) { int result = 0; - LOCK + VTY_LOCK() ; if (zl == NULL) zl = zlog_default; @@ -1175,7 +1277,7 @@ zlog_is_file (struct zlog *zl) result = (zl->fp != NULL); } - UNLOCK; + VTY_UNLOCK() ;; return result; } @@ -1346,7 +1448,3 @@ proto_name2num(const char *s) return -1; } #undef RTSIZE - -#undef LOCK -#undef UNLOCK -#undef ASSERTLOCKED @@ -19,7 +19,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_LOG_H @@ -44,7 +44,7 @@ * please use LOG_ERR instead. */ -typedef enum +typedef enum { ZLOG_NONE, ZLOG_DEFAULT, @@ -52,7 +52,7 @@ typedef enum ZLOG_RIP, ZLOG_BGP, ZLOG_OSPF, - ZLOG_RIPNG, + ZLOG_RIPNG, ZLOG_OSPF6, ZLOG_ISIS, ZLOG_MASC @@ -71,7 +71,7 @@ typedef enum } zlog_dest_t; #define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1) -struct zlog +struct zlog { const char *ident; /* daemon name (first arg to openlog) */ zlog_proto_t protocol; @@ -176,7 +176,7 @@ extern const char * uzlog_get_proto_name (struct zlog *zl); #define LOOKUP(x, y) mes_lookup(x, x ## _max, y, "(no item found)") extern const char *lookup (const struct message *, int); -extern const char *mes_lookup (const struct message *meslist, +extern const char *mes_lookup (const struct message *meslist, int max, int index, const char *no_item); @@ -203,23 +203,58 @@ extern void zlog_backtrace(int priority); extern void zlog_backtrace_sigsafe(int priority, void *program_counter); /* Puts a current timestamp in buf and returns the number of characters - written (not including the terminating NUL). The purpose of - this function is to avoid calls to localtime appearing all over the code. - It caches the most recent localtime result and can therefore - avoid multiple calls within the same second. If buflen is too small, - *buf will be set to '\0', and 0 will be returned. */ + * written (not including the terminating NUL). The purpose of + * this function is to avoid calls to localtime appearing all over the code. + * It caches the most recent localtime result and can therefore + * avoid multiple calls within the same second. + * + * The buflen MUST be > 1 and the buffer address MUST NOT be NULL. + * + * If buflen is too small, writes buflen-1 characters followed by '\0'. + * + * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S + * + * This has a fixed length (leading zeros are included) of 19 characters + * (unless this code is still in use beyond the year 9999 !) + * + * Which may be followed by "." and a number of decimal digits, usually 1..6. + * + * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and + * rounding up for good measure -- buffer size = 32. + */ +#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S" + +enum { timestamp_buffer_len = 32 } ; + extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, char *buf, size_t buflen); -/* unprotected version for when mutex already held */ +/* unprotected version for when mutex already held */ extern size_t uquagga_timestamp(int timestamp_precision /* # subsecond digits */, char *buf, size_t buflen); -/* structure useful for avoiding repeated rendering of the same timestamp */ -struct timestamp_control { - size_t len; /* length of rendered timestamp */ - int precision; /* configuration parameter */ - int already_rendered; /* should be initialized to 0 */ - char buf[40]; /* will contain the rendered timestamp */ -}; + +/* Generate line to be logged + * + * Structure used to hold line for log output -- so that need be generated + * just once even if output to multiple destinations. + * + * Note that the buffer length is a hard limit (including terminating '\n''\0' + * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging. + */ +enum { logline_buffer_len = 1008 } ; +struct logline { + char* p_nl ; /* address of the first byte of "\n" or "\r\n" */ + /* NULL => not filled in yet */ + + char* line ; /* address of the buffered line */ + size_t len ; /* length including either '\r''\n' or '\n' */ + int crlf ; /* true if terminated by "\r\n" */ + + char buf[logline_buffer_len]; /* buffer */ +} ; + +extern void +uvzlog_line(struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va, int crlf) ; /* Defines for use in command construction: */ diff --git a/lib/mem_tracker.c b/lib/mem_tracker.c new file mode 100644 index 00000000..a7ee430f --- /dev/null +++ b/lib/mem_tracker.c @@ -0,0 +1,583 @@ +/* Memory Allocation Tracker + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "vty.h" + +/*============================================================================== + * Memory Tracker + */ +typedef struct mem_descriptor* mem_descriptor ; +struct mem_descriptor +{ + void* addr ; + const char* name ; + + uint32_t next ; /* MS Type is encoded as MS 4 bits */ + uint32_t size ; /* LS Type is encoded as MS 4 bits */ +} ; + +typedef uint32_t md_index ; + +enum +{ + md_next_bits = 28, /* up to 256M allocated objects */ + md_next_mask = (1 << md_next_bits) - 1, + + md_index_max = md_next_mask + 1, + + md_size_bits = 28, /* up to 256M individual item */ + md_size_mask = (1 << md_size_bits) - 1, + + md_size_max = md_size_mask, + + md_next_type_bits = 32 - md_next_bits, + md_next_type_mask = (1 << md_next_type_bits) - 1, + md_size_type_bits = 32 - md_size_bits, + md_size_type_mask = (1 << md_size_type_bits) - 1, + + md_i_index_bits = 16, + md_i_index_count = 1 << md_i_index_bits, + md_i_index_mask = md_i_index_count - 1, + + md_page_bits = md_next_bits - md_i_index_bits, + md_page_count = 1 << md_page_bits, + md_page_mask = md_page_count - 1, +} ; + +CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ; + +static struct mem_type_tracker +{ + struct mem_tracker mt[MTYPE_MAX] ; +} mem_type_tracker ; + +static mem_descriptor mem_page_table[md_page_count] ; + +static mem_descriptor mem_free_descriptors ; +static md_index mem_next_index ; + +static struct mem_tracker mem ; + +uint32_t mem_base_count ; + +md_index* mem_bases ; + +inline static void +mem_md_set_type(mem_descriptor md, enum MTYPE mtype) +{ + uint32_t t_ms ; + uint32_t t_ls ; + + t_ms = mtype >> md_size_type_bits ; + t_ls = mtype ; + + t_ms = (t_ms & md_next_type_mask) << md_next_bits ; + t_ls = (t_ls & md_size_type_mask) << md_size_bits ; + + md->next = (md->next & md_next_mask) | t_ms ; + md->size = (md->size & md_size_mask) | t_ls ; +} ; + +inline static void +mem_md_set_next(mem_descriptor md, md_index next) +{ + md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ; +} ; + +inline static void +mem_md_set_size(mem_descriptor md, size_t size) +{ + md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ; +} ; + +inline static uint8_t +mem_md_type(mem_descriptor md) +{ + return ( (md->next >> (md_next_bits - md_size_type_bits)) + & (md_next_type_mask << md_size_type_bits) ) + | ( (md->size >> md_size_bits) & md_size_type_mask ) ; +} ; + +inline static md_index +mem_md_next(mem_descriptor md) +{ + return md->next & md_next_mask ; +} ; + +inline static size_t +mem_md_size(mem_descriptor md) +{ + return md->size & md_size_mask ; +} ; + +inline static mem_descriptor +mem_md_ptr(md_index mdi) +{ + mem_descriptor page ; + + if (mdi == 0) + return NULL ; + + page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ; + passert(page != NULL) ; + return page + (mdi & md_i_index_mask) ; +} ; + +static void mem_md_make_bases(void) ; + +inline static md_index* +mem_md_base(void* address) +{ + if (mem_bases == NULL) + mem_md_make_bases() ; + + return mem_bases + ((uintptr_t)address % mem_base_count) ; +} ; + +static void +mem_md_make_bases(void) +{ + md_index* bases_was = mem_bases ; + uint32_t count_was = mem_base_count ; + + mem_base_count += 256 * 1024 ; + mem_base_count |= 1 ; + mem_bases = calloc(mem_base_count, sizeof(md_index)) ; + + if (bases_was == NULL) + passert(count_was == 0) ; + else + { + md_index* base = bases_was ; + md_index* new_base ; + md_index this ; + md_index next ; + mem_descriptor md ; + + while (count_was) + { + next = *base++ ; + while (next != 0) + { + this = next ; + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + new_base = mem_md_base(md->addr) ; + mem_md_set_next(md, *new_base) ; + *new_base = this ; + } ; + --count_was ; + } ; + + free(bases_was) ; + } ; +} ; + +static void +mem_md_make_descriptors(void) +{ + mem_descriptor md ; + md_index mdi ; + + mdi = mem_next_index ; + passert(mdi < md_index_max) ; + + mem_free_descriptors + = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] + = calloc(md_i_index_count, sizeof(struct mem_descriptor)) ; + + mem_next_index += md_i_index_count ; + + if (mdi == 0) + { + ++mem_free_descriptors ; /* don't use index == 0 */ + ++mdi ; + } ; + + md = mem_free_descriptors ; + while (mdi < mem_next_index) + { + md->addr = md + 1 ; /* point at next entry */ + md->next = mdi ; /* set to point at self */ + ++md ; + ++mdi ; + } ; + (md-1)->addr = NULL ; /* set end of list */ +} ; + +inline static void +mem_md_malloc(enum MTYPE mtype, void* address, size_t size, const char* name) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md ; + md_index mdi ; + + passert(size <= md_size_max) ; + + if (mem_free_descriptors == NULL) + mem_md_make_descriptors() ; + + md = mem_free_descriptors ; + mem_free_descriptors = md->addr ; + mdi = md->next ; + + if (mem.tracked_count >= (mem_base_count * 4)) + mem_md_make_bases() ; + + base = mem_md_base(address) ; + + md->addr = address ; + md->name = name ; + md->size = size ; + md->next = *base ; + mem_md_set_type(md, mtype) ; + + *base = mdi ; + + ++mem.malloc_count ; + ++mem.tracked_count ; + + mem.tracked_size += size ; + + if (mem.tracked_max_count < mem.tracked_count) + mem.tracked_max_count = mem.tracked_count ; + + if (mem.tracked_max_size < mem.tracked_size) + mem.tracked_max_size = mem.tracked_size ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->malloc_count) ; + ++(mtt->tracked_count) ; + mtt->tracked_size += size ; + + if (mtt->tracked_max_count < mtt->tracked_count) + mtt->tracked_max_count = mtt->tracked_count ; + + if (mtt->tracked_max_size < mtt->tracked_size) + mtt->tracked_max_size = mtt->tracked_size ; +} ; + +inline static void +mem_md_free(enum MTYPE mtype, void* address) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md, prev_md ; + md_index this, next ; + + base = mem_md_base(address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + if (md->addr == address) + { + if (mem_md_type(md) != mtype) + zabort("memory type mismatch in free") ; + + ++mem.free_count ; + --mem.tracked_count ; + + mem.tracked_size -= mem_md_size(md) ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->free_count) ; + --(mtt->tracked_count) ; + mtt->tracked_size -= mem_md_size(md) ; + + if (prev_md == NULL) + *base = next ; + else + mem_md_set_next(prev_md, next) ; + + md->addr = mem_free_descriptors ; + mem_free_descriptors = md ; + md->next = this ; + + return ; + } + else + { + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being freed") ; +} ; + +inline static void +mem_md_realloc(enum MTYPE mtype, void* old_address, void* new_address, + size_t size, const char* name) +{ + mem_tracker mtt ; + md_index* base ; + mem_descriptor md, prev_md ; + md_index this, next ; + + if (old_address == NULL) + { + mem_md_malloc(mtype, new_address, size, name) ; + return ; + } ; + + passert(size <= md_size_max) ; + + base = mem_md_base(old_address) ; + + prev_md = NULL ; + this = *base ; + while (this != 0) + { + md = mem_md_ptr(this) ; + next = mem_md_next(md) ; + + if (md->addr == old_address) + { + if (mem_md_type(md) != mtype) + zabort("memory type mismatch in realloc") ; + + ++mem.realloc_count ; + + mem.tracked_size += size - mem_md_size(md) ; + + if (mem.tracked_max_size < mem.tracked_size) + mem.tracked_max_size = mem.tracked_size ; + + mtt = &(mem_type_tracker.mt[mtype]) ; + + ++(mtt->realloc_count) ; + mtt->tracked_size += size - mem_md_size(md) ; + + if (mtt->tracked_max_size < mtt->tracked_size) + mtt->tracked_max_size = mtt->tracked_size ; + + md->name = name ; + mem_md_set_size(md, size) ; + + if (old_address == new_address) + return ; + + if (prev_md == NULL) + *base = next ; + else + mem_md_set_next(prev_md, next) ; + + base = mem_md_base(new_address) ; + mem_md_set_next(md, *base) ; + *base = this ; + + md->addr = new_address ; + + return ; + } + else + { + prev_md = md ; + this = next ; + } ; + } ; + + zabort("Failed to find memory being realloced") ; +} ; + +/*============================================================================== + * Memory Tracker Display + */ + +static const char* scale_d_tags [] = +{ + [0] = " " , + [1] = "k", + [2] = "m", + [3] = "g", +} ; + +static const char* scale_b_tags [] = +{ + [0] = " " , + [1] = "KiB", + [2] = "MiB", + [3] = "GiB", +} ; + +static char* +mem_show_commas(char* buff, size_t size, uint64_t val, const char* tag) +{ + char* p ; + const char* q ; + int n ; + + passert(size > 10) ; + + p = buff + size ; + *(--p) = '\0' ; + + q = tag + strlen(tag) ; + while ((p > buff) && (q > tag)) + *(--p) = *(--q) ; + + n = 3 ; + while (p > buff) + { + *(--p) = '0' + (val % 10) ; + val /= 10 ; + if (val == 0) + break ; + + if ((--n == 0) && (p > buff)) + { + *(--p) = ',' ; + n = 3 ; + } ; + } ; + + return p ; +} ; + +static char* +mem_show_count(char* buff, size_t size, uint64_t val, int scale) +{ + int i, r ; + + i = 0 ; + if (scale) + { + r = 0 ; + while ((i < 3) && (val >= 10000)) + { + r = (val % 1000) ; + val /= 1000 ; + ++i ; + } ; + if (r >= 500) { + val += 1 ; + if ((val == 10000) && (i < 3)) + { + val /= 1000 ; + ++i ; + } ; + } ; + } ; + + return mem_show_commas(buff, size, val, scale_d_tags[i]) ; +} ; + +static char* +mem_show_byte_count(char* buff, size_t size, uint64_t val, int scale) +{ + int i, r ; + + i = 0 ; + if (scale) + { + r = 0 ; + while ((i < 3) && (val >= 10000)) + { + r = (val % 1024) ; + val /= 1024 ; + ++i ; + } ; + if (r >= 512) { + val += 1 ; + if ((val == 10000) && (i < 3)) + { + val /= 1024 ; + ++i ; + } ; + } ; + } ; + + return mem_show_commas(buff, size, val, scale_b_tags[i]) ; +} ; + +static int +show_memory_tracker_summary(struct vty *vty) +{ + struct mem_tracker mt ; + enum { sbs = 100 } ; + char buf[sbs]; + size_t overhead ; + + LOCK ; + overhead = (sizeof(struct mem_descriptor) * mem_next_index) + + (sizeof(md_index) * mem_base_count) + + (sizeof(mem_descriptor) * md_page_count) ; + + mt = mem ; /* copy the overall memory information */ + UNLOCK ; + + vty_out (vty, "Memory Tracker Statistics:%s", VTY_NEWLINE); + vty_out (vty, " Current memory allocated: %10s%s", + mem_show_byte_count(buf, sbs, mt.tracked_size, 1), + VTY_NEWLINE); + vty_out (vty, " Current allocated objects: %8s%s", + mem_show_count (buf, sbs, mt.tracked_count, 1), + VTY_NEWLINE); + vty_out (vty, " Maximum memory allocated: %10s%s", + mem_show_byte_count(buf, sbs, mt.tracked_max_size, 1), + VTY_NEWLINE); + vty_out (vty, " Maximum allocated objects: %8s%s", + mem_show_count (buf, sbs, mt.tracked_max_count, 1), + VTY_NEWLINE); + vty_out (vty, " malloc/calloc call count: %8s%s", + mem_show_count (buf, sbs, mt.malloc_count, 1), + VTY_NEWLINE); + vty_out (vty, " realloc_call_count: %8s%s", + mem_show_count (buf, sbs, mt.realloc_count, 1), + VTY_NEWLINE); + vty_out (vty, " free call count: %8s%s", + mem_show_count (buf, sbs, mt.free_count, 1), + VTY_NEWLINE); + vty_out (vty, " Memory Tracker overhead: %10s%s", + mem_show_byte_count(buf, sbs, overhead, 1), + VTY_NEWLINE); + return 1; +} ; + +static int +show_memory_tracker_detail(struct vty *vty, struct mem_tracker* mt, + unsigned long alloc) +{ + enum { sbs = 100 } ; + char buf[sbs]; + + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_count, 1)) ; + vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_size, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_max_count, 1)) ; + vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_max_size, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->malloc_count, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->realloc_count, 1)) ; + vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->free_count, 1)) ; + + if (alloc != mt->tracked_count) + vty_out(vty, " %8s!!", mem_show_count(buf, sbs, alloc, 1)) ; + + return 1; +} ; diff --git a/lib/memory.c b/lib/memory.c index e11a5e4a..f68dd298 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -31,13 +31,13 @@ #include "qpthreads.h" /* Needs to be qpthread safe. The system malloc etc are already - * thread safe, but we need to protect the stats */ + * thread safe, but we need to protect the stats + */ static qpt_mutex_t memory_mutex; -#define LOCK qpt_mutex_lock(&memory_mutex); + +#define LOCK qpt_mutex_lock(&memory_mutex); #define UNLOCK qpt_mutex_unlock(&memory_mutex); -static void alloc_inc (int); -static void alloc_dec (int); static void log_memstats(int log_priority); static const struct message mstr [] = @@ -50,12 +50,57 @@ static const struct message mstr [] = { 0, NULL }, }; +/* If using the mem_tracker, include it now. */ + +typedef struct mem_tracker* mem_tracker ; +struct mem_tracker +{ + uint64_t malloc_count ; + uint64_t realloc_count ; + uint64_t free_count ; + + uint32_t tracked_count ; + size_t tracked_size ; + + uint32_t tracked_max_count ; + size_t tracked_max_size ; +} ; + +static void +mem_tracker_zeroise(struct mem_tracker* mem) +{ + memset(mem, 0, sizeof(struct mem_tracker)) ; +} ; + +#ifdef MEMORY_TRACKER +#include "mem_tracker.c" +#endif + +/*============================================================================== + * Keeping track of number of allocated objects of given type + */ + +static struct mstat +{ + struct + { + char *name ; + long alloc ; + } mt[MTYPE_MAX] ; +} mstat ; + +/*============================================================================== + * Memory allocation functions. + * + * NB: failure to allocate is FATAL -- so no need to test return value. + */ + /* Fatal memory allocation error occured. */ static void __attribute__ ((noreturn)) zerror (const char *fname, int type, size_t size) { zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n", - fname, lookup (mstr, type), (int) size, safe_strerror(errno)); + fname, lookup (mstr, type), (int) size, safe_strerror(errno)); log_memstats(LOG_WARNING); /* N.B. It might be preferable to call zlog_backtrace_sigsafe here, since that function should definitely be safe in an OOM condition. But @@ -65,72 +110,145 @@ zerror (const char *fname, int type, size_t size) abort(); } -/* Memory allocation. */ +/*------------------------------------------------------------------------------ + * Memory allocation. + */ void * -zmalloc (int type, size_t size) +zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = malloc (size); if (memory == NULL) - zerror ("malloc", type, size); - - alloc_inc (type); + { + UNLOCK ; + zerror ("malloc", mtype, size); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, memory, size, name) ; +#endif + UNLOCK ; + } ; return memory; } -/* Memory allocation with num * size with cleared. */ +/*------------------------------------------------------------------------------ + * Memory allocation zeroising the allocated area. + */ void * -zcalloc (int type, size_t size) +zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = calloc (1, size); if (memory == NULL) - zerror ("calloc", type, size); - - alloc_inc (type); + { + UNLOCK ; + zerror ("calloc", mtype, size); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, memory, size, name) ; +#endif + UNLOCK ; + } ; return memory; } -/* Memory reallocation. */ +/*------------------------------------------------------------------------------ + * Memory reallocation. + */ void * -zrealloc (int type, void *ptr, size_t size) +zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) { void *memory; + LOCK ; + memory = realloc (ptr, size); if (memory == NULL) - zerror ("realloc", type, size); + { + UNLOCK ; + zerror ("realloc", mtype, size); /* NO RETURN ! */ + } + else + { + if (ptr == NULL) + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_realloc(mtype, ptr, memory, size, name) ; +#endif + UNLOCK ; + } ; + return memory; -} +} ; -/* Memory free. */ +/*------------------------------------------------------------------------------ + * Memory free. + */ void -zfree (int type, void *ptr) +zfree (enum MTYPE mtype, void *ptr) { - alloc_dec (type); + LOCK ; + free (ptr); -} -/* String duplication. */ + mstat.mt[mtype].alloc--; +#ifdef MEMORY_TRACKER + mem_md_free(mtype, ptr) ; +#endif + + UNLOCK ; +} ; + +/*------------------------------------------------------------------------------ + * String duplication. + */ char * -zstrdup (int type, const char *str) +zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) { void *dup; + LOCK ; + dup = strdup (str); if (dup == NULL) - zerror ("strdup", type, strlen (str)); - alloc_inc (type); + { + UNLOCK ; + zerror ("strdup", mtype, strlen (str)); /* NO RETURN ! */ + } + else + { + mstat.mt[mtype].alloc++; +#ifdef MEMORY_TRACKER + mem_md_malloc(mtype, dup, strlen(str)+1, name) ; +#endif + UNLOCK ; + } ; + return dup; } +/*============================================================================== + * Memory allocation with built in logging + */ + #ifdef MEMORY_LOG + static struct { const char *name; @@ -142,10 +260,11 @@ static struct unsigned long t_realloc; unsigned long t_free; unsigned long c_strdup; -} mstat [MTYPE_MAX]; +} mlog_stat [MTYPE_MAX]; static void -mtype_log (char *func, void *memory, const char *file, int line, int type) +mtype_log (char *func, void *memory, const char *file, int line, + enum MTYPE type) { zlog_debug ("%s: %s %p %s %d", func, lookup (mstr, type), memory, file, line); } @@ -156,8 +275,8 @@ mtype_zmalloc (const char *file, int line, int type, size_t size) void *memory; LOCK - mstat[type].c_malloc++; - mstat[type].t_malloc++; + mlog_stat[type].c_malloc++; + mlog_stat[type].t_malloc++; UNLOCK memory = zmalloc (type, size); @@ -167,13 +286,13 @@ mtype_zmalloc (const char *file, int line, int type, size_t size) } void * -mtype_zcalloc (const char *file, int line, int type, size_t size) +mtype_zcalloc (const char *file, int line, enum MTYPE type, size_t size) { void *memory; LOCK - mstat[type].c_calloc++; - mstat[type].t_calloc++; + mlog_stat[type].c_calloc++; + mlog_stat[type].t_calloc++; UNLOCK memory = zcalloc (type, size); @@ -183,13 +302,14 @@ mtype_zcalloc (const char *file, int line, int type, size_t size) } void * -mtype_zrealloc (const char *file, int line, int type, void *ptr, size_t size) +mtype_zrealloc (const char *file, int line, enum MTYPE type, void *ptr, + size_t size) { void *memory; /* Realloc need before allocated pointer. */ LOCK - mstat[type].t_realloc++; + mlog_stat[type].t_realloc++; UNLOCK memory = zrealloc (type, ptr, size); @@ -201,10 +321,10 @@ mtype_zrealloc (const char *file, int line, int type, void *ptr, size_t size) /* Important function. */ void -mtype_zfree (const char *file, int line, int type, void *ptr) +mtype_zfree (const char *file, int line, enum MTYPE type, void *ptr) { LOCK - mstat[type].t_free++; + mlog_stat[type].t_free++; UNLOCK mtype_log ("xfree", ptr, file, line, type); @@ -213,12 +333,12 @@ mtype_zfree (const char *file, int line, int type, void *ptr) } char * -mtype_zstrdup (const char *file, int line, int type, const char *str) +mtype_zstrdup (const char *file, int line, enum MTYPE type, const char *str) { char *memory; LOCK - mstat[type].c_strdup++; + mlog_stat[type].c_strdup++; UNLOCK memory = zstrdup (type, str); @@ -227,31 +347,11 @@ mtype_zstrdup (const char *file, int line, int type, const char *str) return memory; } -#else -static struct -{ - char *name; - long alloc; -} mstat [MTYPE_MAX]; -#endif /* MEMORY_LOG */ - -/* Increment allocation counter. */ -static void -alloc_inc (int type) -{ - LOCK - mstat[type].alloc++; - UNLOCK -} +#endif -/* Decrement allocation counter. */ -static void -alloc_dec (int type) -{ - LOCK - mstat[type].alloc--; - UNLOCK -} +/*============================================================================== + * Showing memory allocation + */ /* Looking up memory status from vty interface. */ #include "vector.h" @@ -261,8 +361,13 @@ alloc_dec (int type) static void log_memstats(int pri) { + struct mstat mst ; struct mlist *ml; + LOCK ; + mst = mstat ; + UNLOCK ; + for (ml = mlists; ml->list; ml++) { struct memory_list *m; @@ -270,7 +375,7 @@ log_memstats(int pri) zlog (NULL, pri, "Memory utilization in module %s:", ml->name); for (m = ml->list; m->index >= 0; m++) { - unsigned long alloc = mtype_stats_alloc(m->index); + unsigned long alloc = mst.mt[m->index].alloc ; if (m->index && alloc) zlog (NULL, pri, " %-30s: %10ld", m->format, alloc); } @@ -280,17 +385,22 @@ log_memstats(int pri) void log_memstats_stderr (const char *prefix) { + struct mstat mst ; struct mlist *ml; struct memory_list *m; int i; int j = 0; + LOCK ; + mst = mstat ; + UNLOCK ; + for (ml = mlists; ml->list; ml++) { i = 0; for (m = ml->list; m->index >= 0; m++) { - unsigned long alloc = mtype_stats_alloc(m->index); + unsigned long alloc = mst.mt[m->index].alloc ; if (m->index && alloc) { if (!i) @@ -321,37 +431,99 @@ log_memstats_stderr (const char *prefix) } static void -show_separator(struct vty *vty) +show_memory_type_vty (struct vty *vty, const char* name, + struct mem_tracker* mt, long int alloc, int sep) { - vty_out (vty, "-----------------------------\r\n"); -} + if (sep) + vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; + + vty_out (vty, "%-30s:", name) ; +#ifdef MEMORY_TRACKER + show_memory_tracker_detail(vty, mt, alloc) ; +#else + vty_out (vty, " %10ld", alloc) ; +#endif + vty_out (vty, "%s", VTY_NEWLINE); +} ; static int -show_memory_vty (struct vty *vty, struct memory_list *list) +show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, + int needsep) { - struct memory_list *m; - int needsep = 0; + int notempty = 0 ; - for (m = list; m->index >= 0; m++) - if (m->index == 0) - { - if (needsep) - { - show_separator (vty); - needsep = 0; - } - } - else - { - unsigned long alloc = mtype_stats_alloc(m->index); - if (alloc) - { - vty_out (vty, "%-30s: %10ld\r\n", m->format, alloc); - needsep = 1; - } - } - return needsep; -} + long int alloc ; + + struct mstat mst ; + struct mem_tracker mem_tot ; + struct mem_tracker mem_one ; + struct mem_tracker* mt ; + +#ifdef MEMORY_TRACKER + struct mem_type_tracker mem_tt ; +#endif + + LOCK ; + mst = mstat ; +#ifdef MEMORY_TRACKER + mem_tt = mem_type_tracker ; +#endif + UNLOCK ; + + mem_tracker_zeroise(&mem_tot) ; + mem_tracker_zeroise(&mem_one) ; + + if ((m == NULL) && (ml != NULL)) + m = (ml++)->list ; + + while (m != NULL) + { + if (m->index <= 0) + { + needsep = notempty ; + if (m->index < 0) + { + if (ml == NULL) + m = NULL ; + else + m = (ml++)->list ; + } + else + ++m ; + } + else + { + alloc = mst.mt[m->index].alloc ; +#ifdef MEMORY_TRACKER + mt = &(mem_tt.mt[m->index]) ; +#else + mt = &mem_one ; + mt->tracked_count = alloc ; +#endif + + mem_tot.malloc_count += mt->malloc_count ; + mem_tot.free_count += mt->free_count ; + mem_tot.realloc_count += mt->realloc_count ; + mem_tot.tracked_count += mt->tracked_count ; + mem_tot.tracked_max_count += mt->tracked_max_count ; + mem_tot.tracked_size += mt->tracked_size ; + mem_tot.tracked_max_size += mt->tracked_max_size ; + + if (alloc || mt->tracked_count) + { + show_memory_type_vty(vty, m->format, mt, alloc, needsep) ; + needsep = 0 ; + notempty = 1 ; + } ; + + ++m ; + } ; + } ; + + show_memory_type_vty(vty, "Total", &mem_tot, mem_tot.tracked_count, notempty); + + return 1 ; +} ; #ifdef HAVE_MALLINFO static int @@ -390,10 +562,40 @@ show_memory_mallinfo (struct vty *vty) VTY_NEWLINE); vty_out (vty, "(see system documentation for 'mallinfo' for meaning)%s", VTY_NEWLINE); + return 1; } #endif /* HAVE_MALLINFO */ + +DEFUN_CALL (show_memory_summary, + show_memory_summary_cmd, + "show memory summary", + "Show running system information\n" + "Memory statistics\n" + "Summary memory statistics\n") +{ +#ifdef MEMORY_TRACKER + show_memory_tracker_summary(vty) ; +#else + long alloc = 0 ; + int mtype ; + +# ifdef HAVE_MALLINFO + show_memory_mallinfo (vty); +# endif /* HAVE_MALLINFO */ + + LOCK ; + for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype) + alloc += mstat[mtype] ; + UNLOCK + vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ; + +#endif /* MEMORY_TRACKER */ + + return CMD_SUCCESS; +} + DEFUN_CALL (show_memory_all, show_memory_all_cmd, "show memory all", @@ -401,19 +603,16 @@ DEFUN_CALL (show_memory_all, "Memory statistics\n" "All memory statistics\n") { - struct mlist *ml; int needsep = 0; #ifdef HAVE_MALLINFO - needsep = show_memory_mallinfo (vty); + needsep |= show_memory_mallinfo (vty); #endif /* HAVE_MALLINFO */ +#ifdef MEMORY_TRACKER + needsep |= show_memory_tracker_summary(vty) ; +#endif - for (ml = mlists; ml->list; ml++) - { - if (needsep) - show_separator (vty); - needsep = show_memory_vty (vty, ml->list); - } + show_memory_vty (vty, NULL, mlists, needsep); return CMD_SUCCESS; } @@ -431,7 +630,7 @@ DEFUN_CALL (show_memory_lib, "Memory statistics\n" "Library memory\n") { - show_memory_vty (vty, memory_list_lib); + show_memory_vty (vty, memory_list_lib, NULL, 0); return CMD_SUCCESS; } @@ -442,7 +641,7 @@ DEFUN_CALL (show_memory_zebra, "Memory statistics\n" "Zebra memory\n") { - show_memory_vty (vty, memory_list_zebra); + show_memory_vty (vty, memory_list_zebra, NULL, 0); return CMD_SUCCESS; } @@ -453,7 +652,7 @@ DEFUN_CALL (show_memory_rip, "Memory statistics\n" "RIP memory\n") { - show_memory_vty (vty, memory_list_rip); + show_memory_vty (vty, memory_list_rip, NULL, 0); return CMD_SUCCESS; } @@ -464,7 +663,7 @@ DEFUN_CALL (show_memory_ripng, "Memory statistics\n" "RIPng memory\n") { - show_memory_vty (vty, memory_list_ripng); + show_memory_vty (vty, memory_list_ripng, NULL, 0); return CMD_SUCCESS; } @@ -475,7 +674,7 @@ DEFUN_CALL (show_memory_bgp, "Memory statistics\n" "BGP memory\n") { - show_memory_vty (vty, memory_list_bgp); + show_memory_vty (vty, memory_list_bgp, NULL, 0); return CMD_SUCCESS; } @@ -486,7 +685,7 @@ DEFUN_CALL (show_memory_ospf, "Memory statistics\n" "OSPF memory\n") { - show_memory_vty (vty, memory_list_ospf); + show_memory_vty (vty, memory_list_ospf, NULL, 0); return CMD_SUCCESS; } @@ -497,7 +696,7 @@ DEFUN_CALL (show_memory_ospf6, "Memory statistics\n" "OSPF6 memory\n") { - show_memory_vty (vty, memory_list_ospf6); + show_memory_vty (vty, memory_list_ospf6, NULL, 0); return CMD_SUCCESS; } @@ -508,7 +707,7 @@ DEFUN_CALL (show_memory_isis, "Memory statistics\n" "ISIS memory\n") { - show_memory_vty (vty, memory_list_isis); + show_memory_vty (vty, memory_list_isis, NULL, 0); return CMD_SUCCESS; } @@ -529,6 +728,7 @@ memory_finish (void) void memory_init (void) { + install_element (RESTRICTED_NODE, &show_memory_summary_cmd); install_element (RESTRICTED_NODE, &show_memory_cmd); install_element (RESTRICTED_NODE, &show_memory_all_cmd); install_element (RESTRICTED_NODE, &show_memory_lib_cmd); @@ -539,6 +739,7 @@ memory_init (void) install_element (RESTRICTED_NODE, &show_memory_ospf6_cmd); install_element (RESTRICTED_NODE, &show_memory_isis_cmd); + install_element (VIEW_NODE, &show_memory_summary_cmd); install_element (VIEW_NODE, &show_memory_cmd); install_element (VIEW_NODE, &show_memory_all_cmd); install_element (VIEW_NODE, &show_memory_lib_cmd); @@ -549,6 +750,7 @@ memory_init (void) install_element (VIEW_NODE, &show_memory_ospf6_cmd); install_element (VIEW_NODE, &show_memory_isis_cmd); + install_element (ENABLE_NODE, &show_memory_summary_cmd); install_element (ENABLE_NODE, &show_memory_cmd); install_element (ENABLE_NODE, &show_memory_all_cmd); install_element (ENABLE_NODE, &show_memory_lib_cmd); @@ -625,11 +827,11 @@ mtype_memstr (char *buf, size_t len, unsigned long bytes) } unsigned long -mtype_stats_alloc (int type) +mtype_stats_alloc (enum MTYPE type) { unsigned long result; LOCK - result = mstat[type].alloc; + result = mstat.mt[type].alloc; UNLOCK return result; } diff --git a/lib/memory.h b/lib/memory.h index 09fddf85..5fa5c5ac 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -52,45 +52,63 @@ extern struct mlist mlists[]; #define XSTRDUP(mtype, str) \ mtype_zstrdup (__FILE__, __LINE__, (mtype), (str)) #else -#define XMALLOC(mtype, size) zmalloc ((mtype), (size)) -#define XCALLOC(mtype, size) zcalloc ((mtype), (size)) -#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size)) + +#define MEMORY_TRACKER 1 + +#ifdef MEMORY_TRACKER +#define MEMORY_TRACKER_NAME , const char* name +#define MEMORY_TRACKER_FUNC , __func__ +#else +#define MEMORY_TRACKER_NAME +#define MEMORY_TRACKER_FUNC +#endif + +#define XMALLOC(mtype, size) zmalloc ((mtype), (size) \ + MEMORY_TRACKER_FUNC) +#define XCALLOC(mtype, size) zcalloc ((mtype), (size) \ + MEMORY_TRACKER_FUNC) +#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \ + MEMORY_TRACKER_FUNC) #define XFREE(mtype, ptr) do { \ zfree ((mtype), (ptr)); \ ptr = NULL; } \ while (0) -#define XSTRDUP(mtype, str) zstrdup ((mtype), (str)) +#define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \ + MEMORY_TRACKER_FUNC) + #endif /* MEMORY_LOG */ #define SIZE(t,n) (sizeof(t) * (n)) /* Prototypes of memory function. */ -extern void *zmalloc (int type, size_t size); -extern void *zcalloc (int type, size_t size); -extern void *zrealloc (int type, void *ptr, size_t size); -extern void zfree (int type, void *ptr); -extern char *zstrdup (int type, const char *str); +extern void *zmalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME); +extern void *zcalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME); +extern void *zrealloc (enum MTYPE type, void *ptr, size_t size + MEMORY_TRACKER_NAME); +extern void zfree (enum MTYPE type, void *ptr); +extern char *zstrdup (enum MTYPE type, const char *str MEMORY_TRACKER_NAME); -extern void *mtype_zmalloc (const char *file, int line, int type, size_t size); +extern void *mtype_zmalloc (const char *file, int line, enum MTYPE type, + size_t size); -extern void *mtype_zcalloc (const char *file, int line, int type, - size_t num, size_t size); +extern void *mtype_zcalloc (const char *file, int line, enum MTYPE type, + size_t num, size_t size); -extern void *mtype_zrealloc (const char *file, int line, int type, void *ptr, - size_t size); +extern void *mtype_zrealloc (const char *file, int line, enum MTYPE type, + void *ptr, size_t size); -extern void mtype_zfree (const char *file, int line, int type, - void *ptr); +extern void mtype_zfree (const char *file, int line, enum MTYPE type, + void *ptr); -extern char *mtype_zstrdup (const char *file, int line, int type, - const char *str); +extern char *mtype_zstrdup (const char *file, int line, enum MTYPE type, + const char *str); extern void memory_init (void); extern void memory_init_r (void); extern void memory_finish (void); extern void log_memstats_stderr (const char *); /* return number of allocations outstanding for the type */ -extern unsigned long mtype_stats_alloc (int); +extern unsigned long mtype_stats_alloc (enum MTYPE); /* Human friendly string for given byte count */ #define MTYPE_MEMSTR_LEN 20 diff --git a/lib/memtypes.awk b/lib/memtypes.awk index 5429f6e8..a8004977 100644 --- a/lib/memtypes.awk +++ b/lib/memtypes.awk @@ -54,7 +54,7 @@ BEGIN { } END { - printf("enum\n{\n MTYPE_TMP = 1,\n"); + printf("enum MTYPE\n{\n MTYPE_TMP = 1,\n"); for (i = 0; i < tcount; i++) { if (mtype[i] != "" && mtype[i] != "MTYPE_TMP") printf (" %s,\n", mtype[i]); diff --git a/lib/memtypes.c b/lib/memtypes.c index 2f2ac239..4ed5cbc0 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -45,7 +45,13 @@ struct memory_list memory_list_lib[] = { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, - { MTYPE_VTY_HIST, "VTY history" }, + { MTYPE_VTY_HIST, "VTY history" }, + { MTYPE_VTY_NAME, "VTY name" }, + { MTYPE_KEY_STREAM, "Keystroke Stream" }, + { MTYPE_VIO_FIFO, "VTY IO FIFO" }, + { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO LUMP" }, + { MTYPE_QSTRING, "qstring structure" }, + { MTYPE_QSTRING_BODY, "qstring body" }, { MTYPE_IF, "Interface" }, { MTYPE_CONNECTED, "Connected" }, { MTYPE_CONNECTED_LABEL, "Connected interface label" }, diff --git a/lib/mqueue.c b/lib/mqueue.c index a6dca32f..8b557dfe 100644 --- a/lib/mqueue.c +++ b/lib/mqueue.c @@ -129,9 +129,67 @@ */ /*============================================================================== - * Initialisation etc. for Message Queues. + * Message Block allocation statics + * + * Once a message block is allocated it is not deallocated, but kept ready + * for future use. + * + * Keeps a count of free message blocks. (Could at some later date reduce the + * number of free message blocks if it is known that some burst of messages has + * now passed.) + */ + +static pthread_mutex_t mqb_mutex ; /* for allocation of mqueue blocks */ + +static mqueue_block mqb_free_list = NULL ; +static unsigned mqb_free_count = 0 ; + +/*============================================================================== + * Initialise and shut down Message Queue and Message Block handling + */ + +/*------------------------------------------------------------------------------ + * Initialise Message Queue handling. + * + * Must be called before any qpt_threads are started. + * + * Freezes qpthreads_enabled. + */ +extern void +mqueue_initialise(void) +{ + if (qpthreads_enabled_freeze) + qpt_mutex_init_new(&mqb_mutex, qpt_mutex_quagga) ; +} ; + +/*------------------------------------------------------------------------------ + * Shut down Message Queue handling. + * + * Release all resources used. + * + * NB: all pthreads must have stopped -- mutex must be free and no further + * uses may be made. + */ +extern void +mqueue_finish(void) +{ + mqueue_block mqb ; + + while ((mqb = mqb_free_list) != NULL) + { + assert(mqb_free_count != 0) ; + mqb_free_list = mqb->next ; + XFREE(MTYPE_MQUEUE_BLOCK, mqb) ; + } ; + + assert(mqb_free_count == 0) ; + + qpt_mutex_destroy_keep(&mqb_mutex) ; +} ; + +/*============================================================================== + * Initialisation etc. for Message Queue * - * TODO: how to shut down a message queue... for reset/exit ? */ /*------------------------------------------------------------------------------ @@ -328,11 +386,6 @@ mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) * mqueue_initialise MUST be called before the first message block is allocated. */ -static pthread_mutex_t mqb_mutex ; - -static mqueue_block mqb_free_list = NULL ; -static unsigned mqb_free_count = 0 ; - inline static size_t mqb_argv_size(mqb_index_t alloc) { return alloc * sizeof(mqb_arg_t) ; @@ -476,7 +529,7 @@ static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ; * NB: this works perfectly well if !qpthreads enabled. Of course, there can * never be any waiters... so no kicking is ever done. */ -void +extern void mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) { qpt_mutex_lock(&mq->mutex) ; @@ -582,7 +635,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) * * Returns a message block if one is available. (And not otherwise.) */ -mqueue_block +extern mqueue_block mqueue_dequeue(mqueue_queue mq, int wait, void* arg) { mqueue_block mqb ; @@ -750,7 +803,7 @@ mqueue_revoke(mqueue_queue mq, void* arg0) * * (Signal will never be kicked if !qpthreads_enabled.) */ -int +extern int mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) { int kicked ; @@ -834,7 +887,7 @@ mqueue_local_dequeue(mqueue_local_queue lmq) * * Returns address of the structure. */ -mqueue_thread_signal +extern mqueue_thread_signal mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, int signum) { @@ -860,7 +913,7 @@ mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, * Frees the structure if required, and returns NULL. * Otherwise zeroises the structure, and returns address of same. */ -mqueue_thread_signal +extern mqueue_thread_signal mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) { passert(mqt->prev == NULL) ; @@ -1248,19 +1301,3 @@ mqb_argv_extend(mqueue_block mqb, mqb_index_t iv) mqb->argv_alloc = need ; } ; - -/*============================================================================== - * Initialise Message Queue handling - * - * Must be called before any qpt_threads are started. - * - * Freezes qpthreads_enabled. - * - * TODO: how do we shut down message queue handling ? - */ -void -mqueue_initialise(void) -{ - if (qpthreads_enabled_freeze) - qpt_mutex_init_new(&mqb_mutex, qpt_mutex_quagga) ; -} ; diff --git a/lib/mqueue.h b/lib/mqueue.h index 355aec23..f22ea022 100644 --- a/lib/mqueue.h +++ b/lib/mqueue.h @@ -198,6 +198,9 @@ struct mqueue_local_queue extern void mqueue_initialise(void) ; +extern void +mqueue_finish(void) ; + extern mqueue_queue mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ; diff --git a/lib/network.c b/lib/network.c index 3373983b..61d98717 100644 --- a/lib/network.c +++ b/lib/network.c @@ -17,14 +17,23 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> #include "log.h" #include "network.h" -/* Read nbytes from fd and store into ptr. */ +/*------------------------------------------------------------------------------ + * Read nbytes from fd and store into ptr -- BLOCKING + * + * Loops internally if gets EINTR. + * + * Returns: >= 0 -- number of bytes read + * < 0 => error + * + * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK + */ int readn (int fd, u_char *ptr, int nbytes) { @@ -33,24 +42,86 @@ readn (int fd, u_char *ptr, int nbytes) nleft = nbytes; - while (nleft > 0) + while (nleft > 0) { nread = read (fd, ptr, nleft); - if (nread < 0) - return (nread); + if (nread > 0) + { + nleft -= nread; + ptr += nread; + } + else if (nread == 0) + break; else - if (nread == 0) - break; - - nleft -= nread; - ptr += nread; + { + if (errno != EINTR) + return (nread); + } } return nbytes - nleft; -} +} + +/*------------------------------------------------------------------------------ + * Read up to nbyte bytes into buf -- assuming NON-BLOCKING. + * + * Loops internally if gets EINTR -- so if does not read everything asked for, + * that must be because the read would otherwise block. + * + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately + * + * NB: if asked to write zero bytes, does nothing and will return 0. + * + * Reading zero bytes is defined for all types of files, and may be used + * to probe for error state. + */ +int +read_nb(int fd, void* buf, size_t nbyte) +{ + size_t nleft = nbyte ; + + do + { + int ret = read(fd, buf, nleft); + + if (ret > 0) + { + buf = (char*)buf + ret ; + nleft -= ret ; + } + else if (ret == 0) + { + if (nleft < nbyte) + break ; /* if read something before EOF */ + + return -2 ; /* hit EOF immediately */ + } + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; /* failed */ + } ; + } while (nleft > 0) ; + + return (nbyte - nleft) ; +} ; -/* Write nbytes from ptr to fd. */ +/*------------------------------------------------------------------------------ + * Write nbytes to fd from ptr -- BLOCKING + * + * Loops internally if gets EINTR. + * + * Returns: >= 0 -- number of bytes written + * < 0 => error + * + * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK + */ int writen(int fd, const u_char *ptr, int nbytes) { @@ -59,19 +130,75 @@ writen(int fd, const u_char *ptr, int nbytes) nleft = nbytes; - while (nleft > 0) + while (nleft > 0) { nwritten = write(fd, ptr, nleft); - - if (nwritten <= 0) - return (nwritten); - nleft -= nwritten; - ptr += nwritten; + if (nwritten > 0) + { + nleft -= nwritten; + ptr += nwritten; + } + else if (nwritten == 0) + break ; + else + { + if (errno != EINTR) + return (nwritten); + } } return nbytes - nleft; } +/*------------------------------------------------------------------------------ + * Write up to nbyte bytes from buf -- assuming NON-BLOCKING. + * + * Loops internally if gets EINTR. + * + * Returns: 0..n -- number of bytes written + * -1 => failed -- see errno + * + * NB: if asked to write zero bytes, does nothing and will return 0. + * + * Writing zero bytes is defined for "regular files", but not for anything + * else. + */ +int +write_nb(int fd, void* buf, size_t nbyte) +{ + size_t nleft = nbyte ; + + while (nleft > 0) + { + int ret = write(fd, buf, nleft); + + if (ret > 0) + { + buf = (char*)buf + ret ; + nleft -= ret ; + } + else if (ret == 0) + break ; /* not sure can happen... but + cannot assume will go away */ + else + { + int err = errno ; + if ((err == EAGAIN) || (err == EWOULDBLOCK)) + break ; + if (err != EINTR) + return -1 ; /* failed */ + } ; + } ; + + return (nbyte - nleft) ; +} ; + +/*------------------------------------------------------------------------------ + * Set fd to non-blocking + * + * Returns: 0 => OK + * -1 => failed + */ int set_nonblocking(int fd) { diff --git a/lib/network.h b/lib/network.h index 4d9c2284..72d38b52 100644 --- a/lib/network.h +++ b/lib/network.h @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #ifndef _ZEBRA_NETWORK_H @@ -33,6 +33,10 @@ extern int writen (int, const u_char *, int); -1 on error. */ extern int set_nonblocking(int fd); +/* Non-Blocking versions of read/write */ +int read_nb(int fd, void* buf, size_t nbyte) ; +int write_nb(int fd, void* buf, size_t nbyte) ; + /* Does the I/O error indicate that the operation should be retried later? */ #define ERRNO_IO_RETRY(EN) \ (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) diff --git a/lib/node_type.h b/lib/node_type.h new file mode 100644 index 00000000..7ec1107d --- /dev/null +++ b/lib/node_type.h @@ -0,0 +1,81 @@ +/* Command handler node_type stuff -- header + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_NODE_TYPE_H +#define _ZEBRA_NODE_TYPE_H + +/* There are some command levels which called from command node. */ +enum node_type +{ + AUTH_NODE, /* Authentication mode of vty interface. */ + RESTRICTED_NODE, /* Restricted view mode */ + VIEW_NODE, /* View node. Default mode of vty interface. */ + AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ + ENABLE_NODE, /* Enable node. */ + + MIN_DO_SHORTCUT_NODE = ENABLE_NODE, + /* May not "do xxx" at any node lower */ + MAX_NON_CONFIG_NODE = ENABLE_NODE, + /* May not be higher than this without owning + * the configuration symbol of power */ + + CONFIG_NODE, /* Config node. Default mode of config file. */ + + MIN_CONTEXT_NODE = CONFIG_NODE, + /* May not change context to any node lower */ + + SERVICE_NODE, /* Service node. */ + DEBUG_NODE, /* Debug node. */ + AAA_NODE, /* AAA node. */ + KEYCHAIN_NODE, /* Key-chain node. */ + KEYCHAIN_KEY_NODE, /* Key-chain key node. */ + INTERFACE_NODE, /* Interface mode node. */ + ZEBRA_NODE, /* zebra connection node. */ + TABLE_NODE, /* rtm_table selection node. */ + RIP_NODE, /* RIP protocol mode node. */ + RIPNG_NODE, /* RIPng protocol mode node. */ + BGP_NODE, /* BGP protocol mode which includes BGP4+ */ + BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ + BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ + BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ + BGP_IPV6_NODE, /* BGP IPv6 address family */ + BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ + OSPF_NODE, /* OSPF protocol mode */ + OSPF6_NODE, /* OSPF protocol for IPv6 mode */ + ISIS_NODE, /* ISIS protocol mode */ + MASC_NODE, /* MASC for multicast. */ + IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ + IP_NODE, /* Static ip route node. */ + ACCESS_NODE, /* Access list node. */ + PREFIX_NODE, /* Prefix list node. */ + ACCESS_IPV6_NODE, /* Access list node. */ + PREFIX_IPV6_NODE, /* Prefix list node. */ + AS_LIST_NODE, /* AS list node. */ + COMMUNITY_LIST_NODE, /* Community list node. */ + RMAP_NODE, /* Route map node. */ + SMUX_NODE, /* SNMP configuration node. */ + DUMP_NODE, /* Packet dump node. */ + FORWARDING_NODE, /* IP forwarding node. */ + PROTOCOL_NODE, /* protocol filtering node */ + VTY_NODE, /* Vty node. */ +}; + +#endif /* _ZEBRA_NODE_TYPE_H */ diff --git a/lib/plist.c b/lib/plist.c index 9cf099dd..41868e96 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -1074,7 +1074,7 @@ vty_prefix_list_value_print(struct vty* vty, struct prefix_list_entry* pe, vty_out(vty, "%s ", prefix_list_type_str(pe)) ; if (pe->flags & PREFIX_ANY) - vty_puts(vty, "any"); + vty_out(vty, "any"); else { struct prefix *p = &pe->prefix ; @@ -1092,7 +1092,7 @@ vty_prefix_list_value_print(struct vty* vty, struct prefix_list_entry* pe, if (with_stats) vty_out (vty, " (hit count: %lu, refcount: %lu)", pe->hitcnt, pe->refcnt); - vty_puts(vty, post) ; + vty_out(vty, post) ; } static void __attribute__ ((unused)) @@ -1391,7 +1391,7 @@ vty_show_prefix_entry (struct vty *vty, struct prefix_list *plist, struct prefix_list_entry* p_l = vector_get_last_item(&plist->list) ; vty_prefix_list_name_print(vty, plist, ":") ; - vty_out_newline(vty) ; + vty_out(vty, VTY_NEWLINE) ; vty_prefix_list_desc_print(vty, plist, 3, VTY_NEWLINE) ; @@ -2798,7 +2798,7 @@ config_write_prefix_afi (afi_t afi, struct vty *vty) } else { - vty_puts(vty, "!! ") ; + vty_out(vty, "!! ") ; vty_prefix_list_undefined_print(vty, afi, sym, VTY_NEWLINE) ; write++ ; } ; diff --git a/lib/qlib_init.c b/lib/qlib_init.c index 400b34fc..c44de575 100644 --- a/lib/qlib_init.c +++ b/lib/qlib_init.c @@ -23,6 +23,7 @@ #include "zassert.h" #include "memory.h" #include "qpthreads.h" +#include "qpselect.h" #include "thread.h" #include "privs.h" #include "mqueue.h" @@ -68,6 +69,7 @@ void qlib_init_first_stage(void) { + qps_start_up() ; } void @@ -86,6 +88,7 @@ void qexit(int exit_code) { safe_finish(); + mqueue_finish(); zprivs_finish(); thread_finish(); memory_finish(); diff --git a/lib/qpnexus.c b/lib/qpnexus.c index cb0bd12c..6fc9129d 100644 --- a/lib/qpnexus.c +++ b/lib/qpnexus.c @@ -172,6 +172,10 @@ qpn_start(void* arg) /* now in our thread, complete initialisation */ qpn_in_thread_init(qpn); + /* custom in-thread initialization */ + for (i = 0; i < qpn->in_thread_init.count ; ++i) + ((qpn_init_function*)(qpn->in_thread_init.hooks[i]))() ; + /* Until required to terminate, loop */ done = 1 ; while (!qpn->terminate) @@ -226,11 +230,11 @@ qpn_start(void* arg) if (wait) mqueue_done_waiting(qpn->queue, qpn->mts); - /* process I/O actions */ + /* process I/O actions */ while (actions) actions = qps_dispatch_next(qpn->selection) ; - /* process timers */ + /* process timers */ now = qt_get_monotonic() ; while (qtimer_pile_dispatch_next(qpn->pile, now)) done = 1 ; @@ -241,14 +245,16 @@ qpn_start(void* arg) done |= ((qpn_hook_function*)(qpn->background.hooks[i]))() ; } ; - /* last bit of code to run in this thread */ - if (qpn->in_thread_final != NULL) - qpn->in_thread_final(); + /* custom in-thread finalization */ + for (i = qpn->in_thread_final.count - 1; i > 0 ; --i) + ((qpn_init_function*)(qpn->in_thread_final.hooks[i]))() ; return NULL; } -/* Now running in our thread, complete initialisation */ +/*------------------------------------------------------------------------------ + * Now running in our thread, do common initialisation + */ static void qpn_in_thread_init(qpn_nexus qpn) { @@ -295,13 +301,11 @@ qpn_in_thread_init(qpn_nexus qpn) qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, SIGMQUEUE); if (qpn->selection != NULL) qps_set_signal(qpn->selection, SIGMQUEUE, newmask); - - /* custom in-thread initialization */ - if (qpn->in_thread_init != NULL) - qpn->in_thread_init(); } -/* Ask the thread to terminate itself quickly and cleanly */ +/*------------------------------------------------------------------------------ + * Ask the thread to terminate itself quickly and cleanly + */ void qpn_terminate(qpn_nexus qpn) { diff --git a/lib/qpnexus.h b/lib/qpnexus.h index d5b7c5a6..c2cc6463 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -47,20 +47,21 @@ * */ -/* maximum time in seconds to sit in a pselect */ +/* maximum time in seconds to sit in a pselect */ #define MAX_PSELECT_WAIT 10 -/* signal for message queues */ +/* signal for message queues */ #define SIGMQUEUE SIGUSR2 -/* number of event hooks */ +/* number of hooks per hook list */ enum { qpn_hooks_max = 4 } ; /*============================================================================== * Data Structures. */ -typedef int qpn_hook_function(void) ; +typedef int qpn_hook_function(void) ; /* dispatch of tasks */ +typedef int qpn_init_function(void) ; /* start/stop work */ typedef struct qpn_hook_list* qpn_hook_list ; struct qpn_hook_list @@ -95,14 +96,24 @@ struct qpn_nexus /* qpthread routine, can override */ void* (*start)(void*); - /* in-thread initialise, can override. Called within the thread - * after all other initialisation just before thread loop */ - void (*in_thread_init)(void); + /* in-thread initialise, can override. Called within the thread after all + * other initialisation just before thread loop + * + * These are typedef int qpn_init_function(void). + * + * These are executed in the order given. + */ + struct qpn_hook_list in_thread_init ; - /* in-thread finalise, can override. Called within thread - * just before thread dies. Nexus components all exist but - * thread loop is no longer executed */ - void (*in_thread_final)(void); + /* in-thread finalise, can override. Called within thread just before + * thread dies. Nexus components all exist but thread loop is no longer + * executed + * + * These are typedef int qpn_init_function(void). + * + * These are executed in the reverse of the order given. + */ + struct qpn_hook_list in_thread_final ; /* in-thread queue(s) of events or other work. * @@ -110,6 +121,8 @@ struct qpn_nexus * loop. So in addition to the mqueue, I/O, timers and any background stuff, * the thread may have other queue(s) of things to be done. * + * These are typedef int qpn_hook_function(void). + * * Hook function can process some queue(s) of things to be done. It does not * have to empty its queues, but it MUST only return 0 if all queues are now * empty. @@ -121,6 +134,8 @@ struct qpn_nexus * The hook functions are called at the bottom of the qpnexus loop, but only * when there is absolutely nothing else to do. * + * These are typedef int qpn_hook_function(void). + * * The hook function should do some unit of background work (if there is any) * and return. MUST return 0 iff there is no more work to do. */ diff --git a/lib/qpselect.c b/lib/qpselect.c index d3f8e5ad..882f4173 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -29,6 +29,14 @@ #include "memory.h" #include "vector.h" +enum { qdebug = +#ifdef QDEBUG + 1 +#else + 0 +#endif +}; + /*============================================================================== * Quagga pselect -- qps_xxxx * @@ -92,38 +100,65 @@ * the file removed from the selection... there are no restrictions. */ -static int qps_super_set_map_made = 0 ; - -static void qps_make_super_set_map(void) ; - /*============================================================================== * qps_selection handling */ +/* See qps_make_super_set_map() below. */ +static short fd_byte_count[FD_SETSIZE] ; /* number of bytes for fds 0..fd */ + /* Forward references */ +static void qps_make_super_set_map(void) ; +static void qps_selection_re_init(qps_selection qps) ; static qps_file qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert) ; static void qps_file_remove(qps_selection qps, qps_file qf) ; static void qps_super_set_zero(fd_super_set* p_set, int n) ; +static int qps_super_set_cmp(fd_super_set* p_a, fd_super_set* p_b, int n) ; static int qps_next_fd_pending(fd_super_set* pending, int fd, int fd_last) ; static void qps_selection_validate(qps_selection qps) ; -/* See qps_make_super_set_map() and qps_pselect() below. */ -static short fd_byte_count[FD_SETSIZE] ; /* number of bytes for fds 0..fd */ +/*------------------------------------------------------------------------------ + * Initialise a selection -- allocating it if required. + * + * Returns the qps_selection. + */ + +extern void +qps_start_up(void) +{ + qps_make_super_set_map() ; /* map the fd_super_set */ +} ; -/* Initialise a selection -- allocating it if required. +/*------------------------------------------------------------------------------ + * Initialise a selection -- allocating it if required. * * Returns the qps_selection. + * + * NB: when initialising an existing selection which has been used before, it + * is the caller's responsibility to have dealt with its contents before + * calling this. */ qps_selection qps_selection_init_new(qps_selection qps) { - if (!qps_super_set_map_made) - qps_make_super_set_map() ; /* map the fd_super_set */ - if (qps == NULL) qps = XCALLOC(MTYPE_QPS_SELECTION, sizeof(struct qps_selection)) ; - else - memset(qps, 0, sizeof(struct qps_selection)) ; + + qps_selection_re_init(qps) ; + + return qps ; +} ; + +/*------------------------------------------------------------------------------ + * Re-initialise a selection. + * + * It is the caller's responsibility to have dealt with any active files before + * calling this. + */ +static void +qps_selection_re_init(qps_selection qps) +{ + memset(qps, 0, sizeof(struct qps_selection)) ; /* Zeroising initialises: * @@ -146,24 +181,13 @@ qps_selection_init_new(qps_selection qps) * signum -- no signal to be enabled * sigmask -- unset * - * So nothing much else to do -- see also qps_selection_re_init(), below. + * So nothing much else to do: */ qps->fd_last = -1 ; /* not an fd in sight. */ - - return qps ; -} ; - -/* Re-initialise a selection. - */ -static void -qps_selection_re_init(qps_selection qps) -{ - memset(qps, 0, sizeof(struct qps_selection)) ; - - qps->fd_last = -1 ; /* not an fd in sight. */ } ; -/* Add given file to the selection, setting its fd and pointer to further +/*------------------------------------------------------------------------------ + * Add given file to the selection, setting its fd and pointer to further * file information. All modes are disabled. * * This initialises most of the qps_file structure, but not the actions. @@ -187,7 +211,8 @@ qps_add_file(qps_selection qps, qps_file qf, int fd, void* file_info) qps_file_lookup_fd(qps, fd, qf) ; /* Add. */ } ; -/* Remove given file from its selection, if any. +/*------------------------------------------------------------------------------ + * Remove given file from its selection, if any. * * It is the callers responsibility to ensure that the file is in a suitable * state to be removed from the selection. @@ -201,7 +226,8 @@ qps_remove_file(qps_file qf) qps_file_remove(qf->selection, qf) ; } ; -/* Ream (another) file out of the selection. +/*------------------------------------------------------------------------------ + * Ream (another) file out of the selection. * * If selection is empty, release the qps_selection structure, if required. * @@ -243,7 +269,8 @@ qps_selection_ream(qps_selection qps, int free_structure) return qf ; } ; -/* Set the signal mask for the selection. +/*------------------------------------------------------------------------------ + * Set the signal mask for the selection. * * This supports the unmasking of a single signal for the duration of the * pselect operation. @@ -270,7 +297,8 @@ qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) } ; } ; -/* Execute a pselect for the given selection -- subject to the given maximum +/*------------------------------------------------------------------------------ + * Execute a pselect for the given selection -- subject to the given maximum * time to wait. * * There is no support for an infinite timeout. @@ -291,8 +319,8 @@ qps_pselect(qps_selection qps, qtime_t max_wait) fd_set* p_fds[qps_mnum_count] ; int n ; - /* TODO: put this under a debug skip */ - qps_selection_validate(qps) ; + if (qdebug) + qps_selection_validate(qps) ; /* If there is stuff still pending, tidy up by zeroising the result */ /* vectors. This is to make sure that when bits are copied from */ @@ -361,7 +389,8 @@ qps_pselect(qps_selection qps, qtime_t max_wait) zabort_errno("Failed in pselect") ; } ; -/* Dispatch the next errored/readable/writeable file, as returned by the +/*------------------------------------------------------------------------------ + * Dispatch the next errored/readable/writeable file, as returned by the * most recent qps_pselect(). * * Processes the errored files, then the readable and lastly the writeable. @@ -386,8 +415,8 @@ qps_dispatch_next(qps_selection qps) qps_file qf ; qps_mnum_t mnum ; - /* TODO: put this under a debug skip */ - qps_selection_validate(qps) ; + if (qdebug) + qps_selection_validate(qps) ; if (qps->pend_count == 0) return 0 ; /* quit immediately of nothing to do. */ @@ -435,7 +464,8 @@ qps_dispatch_next(qps_selection qps) * qps_file structure handling */ -/* Initialise qps_file structure -- allocating one if required. +/*------------------------------------------------------------------------------ + * Initialise qps_file structure -- allocating one if required. * * If a template is given, then the action functions are copied from there to * the new structure. See above for discussion of action functions. @@ -472,7 +502,8 @@ qps_file_init_new(qps_file qf, qps_file template) return qf ; } ; -/* Free dynamically allocated qps_file structure. +/*------------------------------------------------------------------------------ + * Free dynamically allocated qps_file structure. * * It is the caller's responsibility to have removed it from any selection it * may have been in. @@ -485,7 +516,8 @@ qps_file_free(qps_file qf) XFREE(MTYPE_QPS_FILE, qf) ; } ; -/* Enable (or re-enable) file for the given mode. +/*------------------------------------------------------------------------------ + * Enable (or re-enable) file for the given mode. * * If the action argument is not NULL, set the action for the mode. * @@ -519,7 +551,8 @@ qps_enable_mode(qps_file qf, qps_mnum_t mnum, qps_action* action) } ; } ; -/* Set action for given mode -- does not enable/disable. +/*------------------------------------------------------------------------------ + * Set action for given mode -- does not enable/disable. * * May unset an action by setting it NULL ! * @@ -538,7 +571,8 @@ qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) qf->actions[mnum] = action ; } ; -/* Disable file for one or more modes. +/*------------------------------------------------------------------------------ + * Disable file for one or more modes. * * If there are any pending pending results for the modes, those are discarded. * @@ -608,7 +642,9 @@ qps_disable_modes(qps_file qf, qps_mbit_t mbits) * fd. */ -/* Comparison function for binary chop */ +/*------------------------------------------------------------------------------ + * Comparison function for binary chop + */ static int qps_fd_cmp(const int** pp_fd, const qps_file* p_qf) { @@ -619,7 +655,8 @@ qps_fd_cmp(const int** pp_fd, const qps_file* p_qf) return 0 ; } -/* Lookup/Insert file by file-descriptor. +/*------------------------------------------------------------------------------ + * Lookup/Insert file by file-descriptor. * * Inserts if insert argument is not NULL. * @@ -696,7 +733,8 @@ qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert) return qf ; } ; -/* Remove file from selection. +/*------------------------------------------------------------------------------ + * Remove file from selection. * * NB: FATAL error if file is not in the selection, or the file-descriptor * is invalid (or refers to some other file !). @@ -751,7 +789,7 @@ qps_file_remove(qps_selection qps, qps_file qf) qf->selection = NULL ; } ; - /*============================================================================== +/*============================================================================== * fd_super_set support. * * For large sets of file descriptors something faster than testing for all @@ -787,65 +825,100 @@ static uint8_t fd_bit_map [FD_SETSIZE] ; /* maps fd to bit in byte */ static int8_t fd_first_map[256] ; /* maps byte value to 0..7, where that */ /* is the lowest fd bit set in byte. */ -#define QPS_TESTING 0 /* true => testing */ - -#if !QPS_TESTING - -/* Not testing, so map to the standard FD_SET etc. functions. */ -# define qFD_SET FD_SET -# define qFD_CLR FD_CLR -# define qFD_ISSET FD_ISSET -# define qFD_ZERO FD_ZERO - -#else - -/* Set up the testing */ +/*------------------------------------------------------------------------------ + * Cross Check + * + * Where the shape of the bit map is known, this will test that the correct + * bit map has been deduced. + * + * Requires the following to be defined: + * + * QPS_CROSS_CHECK -- weebb + * + * where: w -- number of bytes per word, 1.. + * ee -- 10 => big-endian bytes in word + * 01 => little-endian + * bb -- 70 => b7 is MS bit, b0 is LS bit + * 07 => b0 is MS bit, b7 is LS bit + * + * So: + * + * 10170 => a bit map handled as bytes + * + * 40170 => a bit map handled as little-endian 32-bit words + * + * ...though this is actually no different to handling the bit map + * as bytes. + * + * 41070 => a bit map handled as big-endian 32-bit words + * + * 10107 => a bit map handled as bytes, where the "leftmost" bit is the first + * bit in the bitmap: + * + * ...a big-endian machine, where the bit map is handled as n-bit + * words, with the "leftmost" bit being the first would be like + * this too. + */ -# define QPS_TEST_WORD 4 /* Wordsize */ -# define QPS_TEST_BE 1 /* true => big-endian */ -# define QPS_TEST_B_ORD 07 /* 07 => bits 0..7, 70 => bits 7..0 */ +#define QPS_CROSS_CHECK 40170 -# define QPS_TEST_WORD_BITS (QPS_TEST_WORD * 8) -# if QPS_TEST_BE -# define QPS_BYTE(fd) ( ((fd / QPS_TEST_WORD_BITS) * QPS_TEST_WORD) \ - + (QPS_TEST_WORD - 1) - ((fd % QPS_TEST_WORD_BITS) / 8) ) +enum { +#ifdef QPS_CROSS_CHECK + qps_cross_check = 1, + qps_cc_word_bytes = QPS_CROSS_CHECK / 10000, + qps_cc_byte_ord = (QPS_CROSS_CHECK / 100) % 100, + qps_cc_bit_ord = QPS_CROSS_CHECK % 100, #else -# define QPS_BYTE(fd) ( fd / 8 ) + qps_cross_check = 0, /* no cross check */ + qps_cc_word_bytes = 1, /* byte_wise */ + qps_cc_byte_ord = 1, /* little-endian */ + qps_cc_bit_ord = 70, /* standard bit order */ #endif + qps_cc_word_bits = qps_cc_word_bytes * 8 +} ; -# if QPS_TEST_B_ORD == 07 -# define QPS_BIT(fd) (0x01 << (fd & 0x7)) -# else -# define QPS_BIT(fd) (0x80 >> (fd & 0x7)) -# endif +CONFIRM((qps_cc_word_bytes == 16) || (qps_cc_word_bytes == 8) + || (qps_cc_word_bytes == 4) + || (qps_cc_word_bytes == 2) + || (qps_cc_word_bytes == 1)) ; +CONFIRM((qps_cc_byte_ord == 10) || (qps_cc_byte_ord == 1)) ; +CONFIRM((qps_cc_bit_ord == 70) || (qps_cc_bit_ord == 7)) ; - static void - qFD_SET(int fd, fd_set* set) - { - *((uint8_t*)set + QPS_BYTE(fd)) |= QPS_BIT(fd) ; - } ; +/* Functions required for the cross check. */ - static void - qFD_CLR(int fd, fd_set* set) - { - *((uint8_t*)set + QPS_BYTE(fd)) &= ~QPS_BIT(fd) ; - } ; +static inline int +qpd_cc_word(int fd) +{ + return fd / qps_cc_word_bits ; +} ; - static int - qFD_ISSET(int fd, fd_set* set) - { - return (*((uint8_t*)set + QPS_BYTE(fd)) & QPS_BIT(fd)) != 0 ; - } ; +static inline int +qps_cc_byte(int fd) +{ + if (qps_cc_byte_ord == 10) + return (qpd_cc_word(fd) * qps_cc_word_bytes) + + qps_cc_word_bytes - 1 - ((fd % qps_cc_word_bits) / 8) ; + else + return fd / 8 ; +} ; - static void - qFD_ZERO(fd_set* set) - { - memset(set, 0, sizeof(fd_set)) ; - } ; +static inline uint8_t +qps_cc_bit(int fd) +{ + if (qps_cc_bit_ord == 70) + return 0x01 << (fd & 0x7) ; + else + return 0x80 >> (fd & 0x7) ; +} ; -#endif +static int +ccFD_ISSET(int fd, fd_set* set) +{ + return (*((uint8_t*)set + qps_cc_byte(fd)) & qps_cc_bit(fd)) != 0 ; +} ; -/* Scan for next fd in given fd set, and clear it. +/*------------------------------------------------------------------------------ + * Scan for next fd in given fd set, and clear it. * * Starts at the given fd, will not consider anything above fd_last. * @@ -886,7 +959,8 @@ qps_next_fd_pending(fd_super_set* pending, int fd, int fd_last) return fd ; } ; -/* Make a map of the fd_super_set. +/*------------------------------------------------------------------------------ + * Make a map of the fd_super_set. * * The form of an fd_set is not defined. This code verifies that it is, in * fact a bit vector, and hence that the fd_super_set works here ! @@ -903,11 +977,11 @@ qps_make_super_set_map(void) qps_super_set_zero(&test, 1) ; for (fd = 0 ; fd < FD_SETSIZE ; ++fd) - if (qFD_ISSET(fd, &test.fdset)) + if (FD_ISSET(fd, &test.fdset)) zabort("Zeroised fd_super_set is not empty") ; /* (2) check that zeroising the fd_set doesn't change things */ - qFD_ZERO(&test.fdset) ; + FD_ZERO(&test.fdset) ; for (iw = 0 ; iw < FD_SUPER_SET_WORD_SIZE ; ++iw) if (test.words[iw] != 0) zabort("Zeroised fd_super_set is not all zero words") ; @@ -918,7 +992,7 @@ qps_make_super_set_map(void) { fd_word_t w ; - qFD_SET(fd, &test.fdset) ; + FD_SET(fd, &test.fdset) ; w = 0 ; for (iw = 0 ; iw < FD_SUPER_SET_WORD_SIZE ; ++iw) @@ -949,7 +1023,7 @@ qps_make_super_set_map(void) if (w == 0) zabort("FD_SET did not set any bit in any word") ; - qFD_CLR(fd, &test.fdset) ; + FD_CLR(fd, &test.fdset) ; for (iw = 0 ; iw < FD_SUPER_SET_WORD_SIZE ; ++iw) if (test.words[iw] != 0) @@ -1006,6 +1080,9 @@ qps_make_super_set_map(void) fd_first_map[i] = fd ; } ; + if (fd_first_map[0] != -1) + zabort("Broken fd_first_map -- invalid result for 0") ; + for (i = 1 ; i < 256 ; ++i) if (fd_first_map[i] == -1) zabort("Broken fd_first_map -- missing bits") ; @@ -1026,31 +1103,31 @@ qps_make_super_set_map(void) fd_byte_count[fd] = c ; } ; -#if QPS_TESTING - - /* Checking that the maps have been correctly deduced */ + if (!qps_cross_check) + return ; + /*---------------------------------------------------------------------------- + * Checking that the maps have been correctly deduced -- where know what + * the mapping really is ! + */ for (fd = 0 ; fd < FD_SETSIZE ; ++fd) { uint8_t b ; short c ; - iw = fd / QPS_TEST_WORD_BITS ; - if (QPS_TEST_BE) - ib = ( ((fd / QPS_TEST_WORD_BITS) * QPS_TEST_WORD) + - (QPS_TEST_WORD - 1) - ((fd % QPS_TEST_WORD_BITS) / 8) ) ; - else - ib = ( fd / 8 ) ; + FD_ZERO(&test.fdset) ; + FD_SET(fd, &test.fdset) ; + if (!ccFD_ISSET(fd, &test.fdset)) + zabort("FD_SET and ccFD_ISSET differ") ; - if (QPS_TEST_B_ORD == 07) - b = 0x01 << (fd % 8) ; - else - b = 0x80 >> (fd % 8) ; + iw = qpd_cc_word(fd) ; + ib = qps_cc_byte(fd) ; + b = qps_cc_bit(fd) ; - if (QPS_TEST_BE) - c = (iw + 1) * QPS_TEST_WORD ; + if (qps_cc_byte_ord == 10) + c = (iw + 1) * 4 ; else - c = (ib + 1) ; + c = ib + 1 ; if (fd_word_map[fd] != iw) zabort("Broken fd_word_map") ; @@ -1066,7 +1143,7 @@ qps_make_super_set_map(void) { uint8_t b = i ; fd = 0 ; - if (QPS_TEST_B_ORD == 07) + if (qps_cc_bit_ord == 70) { while ((b & 1) == 0) { @@ -1087,14 +1164,11 @@ qps_make_super_set_map(void) zabort("Broken fd_first_map") ; } ; - zabort("OK fd mapping") ; -#endif - - /* Phew -- we're all set now */ - qps_super_set_map_made = 1 ; + return ; } ; -/* Zeroise 'n' contiguous fd_super_sets +/*------------------------------------------------------------------------------ + * Zeroise 'n' contiguous fd_super_sets * * NB: this MUST be used in place of FD_ZERO because the fd_set may be shorter * than the overlayed words/bytes vectors. @@ -1107,17 +1181,10 @@ qps_super_set_zero(fd_super_set* p_set, int n) memset(p_set, 0, SIZE(fd_super_set, n)) ; } ; -#if 0 /* Mask unused function */ -/* Copy 'n' contiguous fd_super_sets - */ -static void -qps_super_set_copy(fd_super_set* p_dst, fd_super_set* p_src, int n) -{ - memcpy(p_dst, p_src, SIZE(fd_super_set, n)) ; -} ; -#endif - -/* Compare 'n' contiguous fd_super_sets +/*------------------------------------------------------------------------------ + * Compare 'n' contiguous fd_super_sets + * + * Returns 0 <=> equal */ static int qps_super_set_cmp(fd_super_set* p_a, fd_super_set* p_b, int n) @@ -1125,7 +1192,8 @@ qps_super_set_cmp(fd_super_set* p_a, fd_super_set* p_b, int n) return memcmp(p_a, p_b, SIZE(fd_super_set, n)) ; } ; -/* Count the number of bits set in 'n' contiguous fd_super_sets. +/*------------------------------------------------------------------------------ + * Count the number of bits set in 'n' contiguous fd_super_sets. */ static int qps_super_set_count(fd_super_set* p_set, int n) diff --git a/lib/qpselect.h b/lib/qpselect.h index 538ebf68..561eebb2 100644 --- a/lib/qpselect.h +++ b/lib/qpselect.h @@ -163,16 +163,19 @@ struct qps_file * qps_selection handling */ -qps_selection +extern void +qps_start_up(void) ; + +extern qps_selection qps_selection_init_new(qps_selection qps) ; -void +extern void qps_add_file(qps_selection qps, qps_file qf, int fd, void* file_info) ; -void +extern void qps_remove_file(qps_file qf) ; -qps_file +extern qps_file qps_selection_ream(qps_selection qps, int free_structure) ; /* Ream out selection and free the selection structure. */ @@ -180,32 +183,32 @@ qps_selection_ream(qps_selection qps, int free_structure) ; /* Ream out selection but keep the selection structure. */ #define qps_selection_ream_keep(qps) qps_selection_ream(qps, 0) -void +extern void qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ; -int +extern int qps_pselect(qps_selection qps, qtime_mono_t timeout) ; -int +extern int qps_dispatch_next(qps_selection qps) ; /*============================================================================== * qps_file structure handling */ -qps_file +extern qps_file qps_file_init_new(qps_file qf, qps_file template) ; -void +extern void qps_file_free(qps_file qf) ; -void +extern void qps_enable_mode(qps_file qf, qps_mnum_t mnum, qps_action* action) ; -void +extern void qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) ; -void +extern void qps_disable_modes(qps_file qf, qps_mbit_t mbits) ; Inline void* diff --git a/lib/qstring.c b/lib/qstring.c new file mode 100644 index 00000000..f847e0b0 --- /dev/null +++ b/lib/qstring.c @@ -0,0 +1,227 @@ +/* Some string handling + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qstring.h" + +#include "memory.h" +#include "zassert.h" + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Initialise qstring -- allocate if required. + * + * If non-zero len is given, a body is allocated (for at least len + 1). + * + * Returns: address of qstring + * + * NB: assumes initialising a new structure. If not, then caller should + * use qs_reset() or qs_set_empty(). + */ +extern qstring +qs_init_new(qstring qs, size_t len) +{ + if (qs == NULL) + qs = XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; + else + memset(qs, 0, sizeof(qstring_t)) ; + + /* Zeroising has set: + * + * body = NULL -- no body + * size = 0 -- no body + * + * len = 0 + * cp = 0 + * + * Nothing more to do unless initial size != 0 + */ + + if (len != 0) + qs_alloc(qs, len) ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Allocate or reallocate so that string is big enough for the given length. + * + * Allocates to 16 byte boundaries. + * + * Returns: the number of bytes *allocated*, which includes the byte for + * possible trailing '\0'. + * + * NB: allocates EXTRA space for trailing '\0' beyond given length. + */ +extern size_t +qs_alloc(qstring qs, size_t len) +{ + len = (len + 0x10) & ~(size_t)(0x10 - 1) ; + + if (qs->body == NULL) + { + assert(qs->size == 0) ; + qs->size = len ; + qs->body = XMALLOC(MTYPE_QSTRING_BODY, qs->size) ; + } + else + { + assert(qs->size > 0) ; + qs->size *= 2 ; + if (qs->size < len) + qs->size = len ; + qs->body = XREALLOC(MTYPE_QSTRING_BODY, qs->body, qs->size) ; + } ; + + return qs->size ; +} ; + +/*------------------------------------------------------------------------------ + * Free body of qstring -- zeroise size, len and cp + */ +extern void +qs_free_body(qstring qs) +{ + if (qs->body != NULL) + XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + + qs->size = 0 ; + qs->len = 0 ; + qs->cp = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Reset qstring -- free body and, if required, free the structure. + * + * If not freeing the structure, zeroise size, len and cp -- qs_free_body() + * + * Returns: NULL if freed the structure + * address of structure, otherwise + */ +extern qstring +qs_reset(qstring qs, int free_structure) +{ + if (qs->body != NULL) + XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + + if (free_structure) + XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ + else + { + qs->size = 0 ; + qs->len = 0 ; + qs->cp = 0 ; + } ; + + return qs ; +} ; + +/*============================================================================== + * printf(0 and vprintf() type functions + */ + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf printf() + */ +extern int +qs_printf(qstring qs, const char* format, ...) +{ + va_list args; + int result ; + + va_start (args, format); + result = qs_vprintf(qs, format, args); + va_end (args); + + return result; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf vprintf() + * + * 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. + */ +extern int +qs_vprintf(qstring qs, const char *format, va_list args) +{ + va_list ac ; + int len ; + + while (1) + { + va_copy(ac, args); + qs->len = len = vsnprintf (qs->body, qs->size, format, ac) ; + va_end(ac); + + if (len < (int)qs->size) + return len ; /* quit if done (or error) */ + + qs_alloc(qs, len) ; + } ; +} ; + +/*============================================================================== + * Other operations + */ + +/*------------------------------------------------------------------------------ + * Set qstring to be copy of the given string. + * + * Sets qs->len to the length of the string (excluding trailing '\0') + * + * NB: if stc == NULL, sets qstring to be zero length string. + */ +extern size_t +qs_set(qstring qs, const char* src) +{ + 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->len ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be leading 'n' bytes of given string. + * + * NB: src string MUST be at least that long. + * + * NB: src may not be NULL unless len == 0. + */ +extern size_t +qs_set_n(qstring qs, const char* src, size_t n) +{ + qs_need(qs, n) ; /* sets qs->len */ + if (n != 0) + memcpy(qs->body, src, n) ; + + *((char*)qs->body + n) = '\0' ; + + return n ; +} ; diff --git a/lib/qstring.h b/lib/qstring.h new file mode 100644 index 00000000..1841657e --- /dev/null +++ b/lib/qstring.h @@ -0,0 +1,365 @@ +/* Some string handling -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_QSTRING_H +#define _ZEBRA_QSTRING_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#ifndef Inline +#define Inline static inline +#endif + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +/*============================================================================== + * These "qstrings" address the ... + * + * + * + */ + +typedef struct qstring qstring_t ; +typedef struct qstring* qstring ; + +struct qstring +{ + void* body ; + size_t size ; + + size_t len ; + size_t cp ; +} ; + +/*------------------------------------------------------------------------------ + * Access functions for body of qstring -- to take care of casting pointers + * + * NB: if the body has not yet been allocated, these functions will return + * NULL or NULL + the offset. + */ +Inline char* /* pointer to body of qstring */ +qs_chars(qstring qs) +{ + return (char*)qs->body ; +} ; + +Inline unsigned char* /* pointer to body of qstring */ +qs_bytes(qstring qs) +{ + return (unsigned char*)qs->body ; +} ; + +Inline char* /* pointer to given offset in qstring */ +qs_chars_at(qstring qs, size_t off) +{ + return qs_chars(qs) + off ; +} ; + +Inline unsigned char* /* pointer to given offset in qstring */ +qs_bytes_at(qstring qs, size_t off) +{ + return qs_bytes(qs) + off ; +} ; + +Inline char* /* pointer to 'cp' offset in qstring */ +qs_cp_char(qstring qs) +{ + return qs_chars_at(qs, qs->cp) ; +} ; + +Inline unsigned char* /* pointer to 'cp' offset in qstring */ +qs_cp_byte(qstring qs) +{ + return qs_bytes_at(qs, qs->cp) ; +} ; + +Inline char* /* pointer to 'len' offset in qstring */ +qs_ep_char(qstring qs) +{ + return qs_chars_at(qs, qs->len) ; +} ; + +Inline unsigned char* /* pointer to 'len' offset in qstring */ +qs_ep_byte(qstring qs) +{ + return qs_bytes_at(qs, qs->len) ; +} ; + +/*============================================================================== + * Functions + */ + +extern qstring +qs_init_new(qstring qs, size_t len) ; + +extern size_t +qs_alloc(qstring qs, size_t len) ; + +extern void +qs_free_body(qstring qs) ; + +extern qstring +qs_reset(qstring qs, int free_structure) ; + +#define qs_reset_keep(qs) qs_reset(qs, 0) +#define qs_reset_free(qs) qs_reset(qs, 1) + +extern int +qs_printf(qstring qs, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; + +extern int +qs_vprintf(qstring qs, const char *format, va_list args) ; + +extern size_t +qs_set(qstring qs, const char* s) ; + +extern size_t +qs_set_n(qstring qs, const char* s, size_t len) ; + +Inline size_t +qs_need(qstring qs, size_t len) ; + +Inline size_t +qs_set_len(qstring qs, size_t len) ; + +Inline void +qs_set_empty(qstring qs) ; + +Inline size_t +qs_len(qstring qs) ; + +Inline size_t +qs_size(qstring qs) ; + +Inline void* +qs_term(qstring qs) ; + +Inline size_t +qs_insert(qstring qs, const void* src, size_t n) ; + +Inline void +qs_replace(qstring qs, const void* src, size_t n) ; + +Inline size_t +qs_delete(qstring qs, size_t n) ; + +/*============================================================================== + * The Inline functions. + */ + +/*------------------------------------------------------------------------------ + * Need space for a string of 'len' characters (plus possible '\0'). + * + * Returns: size of the qstring body + * (which includes the extra space allowed for '\0') + * + * NB: asking for 0 bytes will cause a body to be allocated, ready for any + * '\0' ! + * + * NB: has no effect on 'cp' or 'len'. + */ +Inline size_t +qs_need(qstring qs, size_t len) +{ + if (len < qs->size) + { + assert(qs->body != NULL) ; + return qs->size ; + } + else + return qs_alloc(qs, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Set 'len' -- allocate or extend body as required. + * + * Returns: size of the qstring body + * (which includes the extra space allowed for '\0') + * + * 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'. + */ +Inline size_t +qs_set_len(qstring qs, size_t len) +{ + qs->len = len ; + return qs_need(qs, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Reset contents of qstring. + * + * Sets 'cp' = 'len' = 0. Sets first byte of body (if any) to NULL. + */ +Inline void +qs_set_empty(qstring qs) +{ + qs->len = 0 ; + qs->cp = 0 ; + if (qs->body != NULL) + *((char*)qs->body) = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Get length of qstring -- by doing strlen() -- and record it in qs->len. + * + * Returns: the string length + * + * NB: if no body has been allocated, length = 0 + */ +Inline size_t +qs_len(qstring qs) +{ + return qs->len = (qs->body != NULL) ? strlen(qs_chars(qs)) : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Get size of qstring body. + * + * NB: if no body has been allocated, size = 0 + */ +Inline size_t +qs_size(qstring qs) +{ + return qs->size ; +} ; + +/*------------------------------------------------------------------------------ + * Set '\0' at qs->len -- allocate or extend body as required. + * + * Returns address of body. + */ +Inline void* +qs_term(qstring qs) +{ + size_t len ; + if ((len = qs->len) >= qs->size) + qs_alloc(qs, len) ; + + *qs_chars_at(qs, len) = '\0' ; + + return qs->body ; +} ; + +/*------------------------------------------------------------------------------ + * Insert 'n' bytes at 'cp' -- moves anything cp..len up. + * + * Increases 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + */ +Inline size_t +qs_insert(qstring qs, const void* src, size_t n) +{ + size_t after ; + char* p ; + + if (qs->len < qs->cp) + qs->len = qs->cp ; + after = qs->len - qs->cp ; + + qs_set_len(qs, qs->len + n) ; /* set len and ensure have space */ + + p = qs_cp_char(qs) ; + if (after > 0) + memmove (p + n, p, after) ; + + if (n > 0) + memmove(p, src, n) ; + + return after ; +} ; + +/*------------------------------------------------------------------------------ + * Replace 'n' bytes at 'cp' -- extending if required. + * + * May increase 'len'. but does not affect 'cp'. + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + */ +Inline void +qs_replace(qstring qs, const void* src, size_t n) +{ + if (qs->len < qs->cp + n) + qs_set_len(qs, qs->cp + n) ; /* set len and ensure have space */ + + if (n > 0) + memmove(qs_cp_char(qs), src, n) ; +} ; + +/*------------------------------------------------------------------------------ + * Remove 'n' bytes at 'cp' -- extending if required. + * + * May change 'len'. but does not affect 'cp'. + * + * Returns: number of bytes beyond 'cp' that were moved before insert. + * + * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. + * + * NB: the string is NOT re-terminated. + */ +Inline size_t +qs_delete(qstring qs, size_t n) +{ + size_t after ; + char* p ; + + /* If deleting up to or beyond len, then simply set len == cp */ + if ((qs->cp + n) >= qs->len) + { + qs_set_len(qs, qs->cp) ; /* set len, looks after cp > len */ + return 0 ; /* nothing after */ + } + + /* There is at least one byte after cp (so body must exist) */ + after = qs->len - (qs->cp + n) ; + + if (n > 0) + { + p = qs_cp_char(qs) ; + memmove (p, p + n, after) ; + + qs->len -= n ; + } ; + + return after ; +} ; + +#endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtimers.c b/lib/qtimers.c index 0aef52a4..508fc7d7 100644 --- a/lib/qtimers.c +++ b/lib/qtimers.c @@ -27,6 +27,14 @@ #include "memory.h" #include "heap.h" +enum { qdebug = +#ifdef QDEBUG + 1 +#else + 0 +#endif +}; + /*============================================================================== * Quagga Timers -- qtimer_xxxx * @@ -153,7 +161,8 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) { qtimer qtr ; - qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */ + if (qdebug) + qtimer_pile_verify(qtp) ; qtr = heap_top_item(&qtp->timers) ; if ((qtr != NULL) && (qtr->time <= upto)) @@ -320,7 +329,8 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) qtp = qtr->pile ; dassert(qtp != NULL) ; - qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */ + if (qdebug) + qtimer_pile_verify(qtp) ; qtr->time = when ; @@ -336,9 +346,10 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) if (action != NULL) qtr->action = action ; else - dassert(qtr->action != NULL) ; + assert(qtr->action != NULL) ; - qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */ + if (qdebug) + qtimer_pile_verify(qtp) ; } ; /* Unset given timer @@ -353,12 +364,13 @@ qtimer_unset(qtimer qtr) qtimer_pile qtp = qtr->pile ; dassert(qtp != NULL) ; - qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */ + if (qdebug) + qtimer_pile_verify(qtp) ; heap_delete_item(&qtp->timers, qtr) ; - assert(qtp == qtr->pile); - qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */ + if (qdebug) + qtimer_pile_verify(qtp) ; qtr->state = qtr_state_inactive ; /* overrides any unset pending */ } ; diff --git a/lib/routemap.c b/lib/routemap.c index 2dfa5a46..b142e99c 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -28,7 +28,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "command.h" #include "vty.h" #include "log.h" - + /* Vector for route match rules. */ static vector route_match_vec; @@ -60,7 +60,7 @@ struct route_map_list void (*add_hook) (const char *); void (*delete_hook) (const char *); - void (*event_hook) (route_map_event_t, const char *); + void (*event_hook) (route_map_event_t, const char *); }; /* Master list of route map. */ @@ -72,7 +72,7 @@ route_map_rule_delete (struct route_map_rule_list *, static void route_map_index_delete (struct route_map_index *, int); - + /* New route map allocation. Please note route map's name must be specified. */ static struct route_map * @@ -94,7 +94,7 @@ route_map_add (const char *name) map = route_map_new (name); list = &route_map_master; - + map->next = NULL; map->prev = list->tail; if (list->tail) @@ -117,7 +117,7 @@ route_map_delete (struct route_map *map) struct route_map_list *list; struct route_map_index *index; char *name; - + while ((index = map->head) != NULL) route_map_index_delete (index, 0); @@ -220,23 +220,23 @@ vty_show_route_map_entry (struct vty *vty, struct route_map *map) if (index->description) vty_out (vty, " Description:%s %s%s", VTY_NEWLINE, index->description, VTY_NEWLINE); - + /* Match clauses */ vty_out (vty, " Match clauses:%s", VTY_NEWLINE); for (rule = index->match_list.head; rule; rule = rule->next) - vty_out (vty, " %s %s%s", + vty_out (vty, " %s %s%s", rule->cmd->str, rule->rule_str, VTY_NEWLINE); - + vty_out (vty, " Set clauses:%s", VTY_NEWLINE); for (rule = index->set_list.head; rule; rule = rule->next) vty_out (vty, " %s %s%s", rule->cmd->str, rule->rule_str, VTY_NEWLINE); - + /* Call clause */ vty_out (vty, " Call clause:%s", VTY_NEWLINE); if (index->nextrm) vty_out (vty, " Call %s%s", index->nextrm, VTY_NEWLINE); - + /* Exit Policy */ vty_out (vty, " Action:%s", VTY_NEWLINE); if (index->exitpolicy == RMAP_GOTO) @@ -353,7 +353,7 @@ route_map_index_add (struct route_map *map, enum route_map_type type, index->map = map; index->type = type; index->pref = pref; - + /* Compare preference. */ for (point = map->head; point; point = point->next) if (point->pref >= pref) @@ -394,7 +394,7 @@ route_map_index_add (struct route_map *map, enum route_map_type type, /* Get route map index. */ static struct route_map_index * -route_map_index_get (struct route_map *map, enum route_map_type type, +route_map_index_get (struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; @@ -420,7 +420,7 @@ route_map_rule_new (void) new = XCALLOC (MTYPE_ROUTE_MAP_RULE, sizeof (struct route_map_rule)); return new; } - + /* Install rule command to the match list. */ void route_map_install_match (struct route_map_rule_cmd *cmd) @@ -552,7 +552,7 @@ route_map_add_match (struct route_map_index *index, const char *match_name, { next = rule->next; if (rule->cmd == cmd) - { + { route_map_rule_delete (&index->match_list, rule); replaced = 1; } @@ -591,9 +591,9 @@ route_map_delete_match (struct route_map_index *index, const char *match_name, cmd = route_map_lookup_match (match_name); if (cmd == NULL) return 1; - + for (rule = index->match_list.head; rule; rule = rule->next) - if (rule->cmd == cmd && + if (rule->cmd == cmd && (rulecmp (rule->rule_str, match_arg) == 0 || match_arg == NULL)) { route_map_rule_delete (&index->match_list, rule); @@ -677,7 +677,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, cmd = route_map_lookup_set (set_name); if (cmd == NULL) return 1; - + for (rule = index->set_list.head; rule; rule = rule->next) if ((rule->cmd == cmd) && (rulecmp (rule->rule_str, set_arg) == 0 || set_arg == NULL)) @@ -698,7 +698,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, The matrix for a route-map looks like this: (note, this includes the description for the "NEXT" and "GOTO" frobs now - + Match | No Match | permit action | cont @@ -707,7 +707,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, | deny deny | cont | - + action) -Apply Set statements, accept route -If Call statement is present jump to the specified route-map, if it @@ -719,10 +719,10 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, -Route is denied by route-map. cont) -Goto Next index - + If we get no matches after we've processed all updates, then the route is dropped too. - + Some notes on the new "CALL", "NEXT" and "GOTO" call WORD - If this clause is matched, then the set statements are executed and then we jump to route-map 'WORD'. If @@ -735,7 +735,7 @@ route_map_delete_set (struct route_map_index *index, const char *set_name, first clause greater than this. In order to ensure route-maps *always* exit, you cannot jump backwards. Sorry ;) - + We need to make sure our route-map processing matches the above */ @@ -757,7 +757,7 @@ route_map_apply_match (struct route_map_rule_list *match_list, for (match = match_list->head; match; match = match->next) { /* Try each match statement in turn, If any do not return - RMAP_MATCH, return, otherwise continue on to next match + RMAP_MATCH, return, otherwise continue on to next match statement. All match statements must match for end-result to be a match. */ ret = (*match->cmd->func_apply) (match->value, prefix, @@ -827,7 +827,7 @@ route_map_apply (struct route_map *map, struct prefix *prefix, if (ret == RMAP_DENYMATCH) return ret; } - + switch (index->exitpolicy) { case RMAP_EXIT: @@ -898,7 +898,7 @@ route_map_finish (void) vector_free (route_set_vec); route_set_vec = NULL; } - + /* VTY related functions. */ DEFUN (route_map, route_map_cmd, @@ -945,7 +945,7 @@ DEFUN (route_map, index = route_map_index_get (map, permit, pref); vty->index = index; - vty->node = RMAP_NODE; + vty_set_node(vty, RMAP_NODE) ; return CMD_SUCCESS; } @@ -1025,7 +1025,7 @@ DEFUN (no_route_map, index = route_map_index_lookup (map, permit, pref); if (index == NULL) { - vty_out (vty, "%% Could not find route-map entry %s %s%s", + vty_out (vty, "%% Could not find route-map entry %s %s%s", argv[0], argv[2], VTY_NEWLINE); return CMD_WARNING; } @@ -1066,7 +1066,7 @@ DEFUN (no_rmap_onmatch_next, struct route_map_index *index; index = vty->index; - + if (index) index->exitpolicy = RMAP_EXIT; @@ -1089,11 +1089,11 @@ DEFUN (rmap_onmatch_goto, VTY_GET_INTEGER_RANGE("route-map index", d, argv[0], 1, 65536); else d = index->pref + 1; - + if (d <= index->pref) { /* Can't allow you to do that, Dave */ - vty_out (vty, "can't jump backwards in route-maps%s", + vty_out (vty, "can't jump backwards in route-maps%s", VTY_NEWLINE); return CMD_WARNING; } @@ -1119,7 +1119,7 @@ DEFUN (no_rmap_onmatch_goto, if (index) index->exitpolicy = RMAP_EXIT; - + return CMD_SUCCESS; } @@ -1259,7 +1259,7 @@ route_map_config_write (struct vty *vty) else first = 0; - vty_out (vty, "route-map %s %s %d%s", + vty_out (vty, "route-map %s %s %d%s", map->name, route_map_type_str (index->type), index->pref, VTY_NEWLINE); @@ -1268,7 +1268,7 @@ route_map_config_write (struct vty *vty) vty_out (vty, " description %s%s", index->description, VTY_NEWLINE); for (rule = index->match_list.head; rule; rule = rule->next) - vty_out (vty, " match %s %s%s", rule->cmd->str, + vty_out (vty, " match %s %s%s", rule->cmd->str, rule->rule_str ? rule->rule_str : "", VTY_NEWLINE); @@ -1282,7 +1282,7 @@ route_map_config_write (struct vty *vty) vty_out (vty, " on-match goto %d%s", index->nextpref, VTY_NEWLINE); if (index->exitpolicy == RMAP_NEXT) vty_out (vty," on-match next%s", VTY_NEWLINE); - + write++; } return write; @@ -1315,12 +1315,12 @@ route_map_init_vty (void) install_element (RMAP_NODE, &no_rmap_onmatch_next_cmd); install_element (RMAP_NODE, &rmap_onmatch_goto_cmd); install_element (RMAP_NODE, &no_rmap_onmatch_goto_cmd); - + /* Install the continue stuff (ALIAS of on-match). */ install_element (RMAP_NODE, &rmap_continue_cmd); install_element (RMAP_NODE, &no_rmap_continue_cmd); install_element (RMAP_NODE, &rmap_continue_index_cmd); - + /* Install the call stuff. */ install_element (RMAP_NODE, &rmap_call_cmd); install_element (RMAP_NODE, &no_rmap_call_cmd); @@ -1328,7 +1328,7 @@ route_map_init_vty (void) /* Install description commands. */ install_element (RMAP_NODE, &rmap_description_cmd); install_element (RMAP_NODE, &no_rmap_description_cmd); - + /* Install show command */ install_element (ENABLE_NODE, &rmap_show_name_cmd); } diff --git a/lib/sockunion.c b/lib/sockunion.c index 479adc3e..4043783c 100644 --- a/lib/sockunion.c +++ b/lib/sockunion.c @@ -117,50 +117,164 @@ inet_ntop (int family, const void *addrptr, char *strptr, size_t len) } #endif /* ! HAVE_INET_NTOP */ -const char * -inet_sutop (union sockunion *su, char *str) +/*------------------------------------------------------------------------------ + * Set the sockunion size (sin_len or sin6_len), if required. + * + * NB: POSIX does not require this and Stevens et al say that even where it + * is supported, the application need not worry about it. + * + * However... the code as found does this. + * + * TODO: is it *really* necessary to set sin_len or sin6_len ?? + * + * Returns: the sockunion size + */ +inline static int +sockunion_sin_len(sockunion su) +{ + return +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = +#endif + sizeof(struct sockaddr_in); +} ; + +#if HAVE_IPV6 +inline static int +sockunion_sin6_len(sockunion su) +{ + return +#ifdef SIN6_LEN + su->sin6.sin6_len = +#endif + sizeof(struct sockaddr_in6); +} ; +#endif + +/*------------------------------------------------------------------------------ + * Set the address family for the given sockunion. + * + * If sin_len or sin6_len entry is present, fill that in too. + * + * Assumes the address family is valid ! + * + * Returns: 0 + */ +inline static int +sockunion_set_family(sockunion su, sa_family_t family) +{ + su->sa.sa_family = family ; + +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + if (family == AF_INET) + su->sin.sin_len = sizeof(struct sockaddr_in); +#endif +#if defined(HAVE_IPV6) && defined(SIN6_LEN) + if (family == AF_INET6) + su->sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Set the given sockunion address to "any" + */ +static void +sockunion_set_addr_any(sockunion su) { switch (su->sa.sa_family) - { + { case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN); - break; + su->sin.sin_addr.s_addr = htonl (INADDR_ANY); + return ; + #ifdef HAVE_IPV6 case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN); - break; -#endif /* HAVE_IPV6 */ - } - return str; -} +# if defined(LINUX_IPV6) || defined(NRL) + memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); +# else + su->sin6.sin6_addr = in6addr_any; +# endif /* LINUX_IPV6 || defined(NRL) */ + return ; +#endif + + default: + return ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set the port number in the given sockunion. + * + * For good measure, set the size (if that's required) and return same. + */ +static int +sockunion_set_port(sockunion su, in_port_t port) +{ + switch (su->sa.sa_family) + { + case AF_INET: + su->sin.sin_port = htons(port) ; + return sockunion_sin_len(su) ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6.sin6_port = htons(port) ; + return sockunion_sin6_len(su) ; +#endif + + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise a new sockunion -- for the given address family (if any) + * + * Allocates a sockunion if required. + * + * Advice is to zeroize sockaddr_in6, in particular. + */ +extern sockunion +sockunion_init_new(sockunion su, sa_family_t family) +{ + if (su == NULL) + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)) ; + else + memset(su, 0, sizeof(union sockunion)) ; + + if (family != 0) + sockunion_set_family(su, family) ; + return su ; +} ; + +/*------------------------------------------------------------------------------ + * From the given string, fill in the given sockunion. + * + * Returns: 0 => OK -- sockunion filled in + * -1 => not a valid address (or not a known address family) + */ int str2sockunion (const char *str, union sockunion *su) { int ret; - memset (su, 0, sizeof (union sockunion)); + assert(su != NULL) ; + + sockunion_init_new(su, 0) ; ret = inet_pton (AF_INET, str, &su->sin.sin_addr); if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return 0; - } + return sockunion_set_family(su, AF_INET) ; + #ifdef HAVE_IPV6 ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return 0; - } + return sockunion_set_family(su, AF_INET6) ; #endif /* HAVE_IPV6 */ + return -1; } @@ -170,72 +284,65 @@ str2sockunion (const char *str, union sockunion *su) * Requires buffer of at least SU_ADDRSTRLEN characters. */ const char * -sockunion2str (union sockunion *su, char *buf, size_t len) +sockunion2str (union sockunion *su, char *buf, size_t size) { - assert(len >= SU_ADDRSTRLEN) ; + assert(size >= SU_ADDRSTRLEN) ; - if (su->sa.sa_family == AF_INET) - return inet_ntop (AF_INET, &su->sin.sin_addr, buf, len); + switch (su->sa.sa_family) + { + case AF_INET: + inet_ntop (AF_INET, &su->sin.sin_addr, buf, size); + break; #ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) - return inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, len); + case AF_INET6: + inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, size); + break; #endif /* HAVE_IPV6 */ - return NULL; + default: + snprintf (buf, size, "?af=%d?", (int)su->sa.sa_family) ; + } ; + + return buf; } +/*------------------------------------------------------------------------------ + * From the given string, construct and fill in a sockunion. + * + * Returns: NULL => not a valid address (or not a known address family) + * otherwise is address of new sockunion. + * + * NB: the caller is responsible for freeing the sockunion created. + */ union sockunion * sockunion_str2su (const char *str) { - int ret; union sockunion *su; - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); + su = XMALLOC (MTYPE_SOCKUNION, sizeof(union sockunion)); - ret = inet_pton (AF_INET, str, &su->sin.sin_addr); - if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return su; - } -#ifdef HAVE_IPV6 - ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); - if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return su; - } -#endif /* HAVE_IPV6 */ + if (str2sockunion (str, su) != 0) + XFREE (MTYPE_SOCKUNION, su); /* sets su = NULL */ - XFREE (MTYPE_SOCKUNION, su); - return NULL; + return su ; } -char * -sockunion_su2str (union sockunion *su) +/*------------------------------------------------------------------------------ + * Convert given sockunion to string, and return a new piece of memory + * containing same. + * + * It is the callers responsibility to free the memory in due course. + */ +extern char * +sockunion_su2str (union sockunion *su, enum MTYPE type) { - char str[SU_ADDRSTRLEN]; + char buf[SU_ADDRSTRLEN]; - switch (su->sa.sa_family) - { - case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, sizeof (str)); - break; -#ifdef HAVE_IPV6 - case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, sizeof (str)); - break; -#endif /* HAVE_IPV6 */ - } - return XSTRDUP (MTYPE_TMP, str); + return XSTRDUP (type, sockunion2str(su, buf, sizeof(buf))) ; } -/* Convert IPv4 compatible IPv6 address to IPv4 address. */ +/*------------------------------------------------------------------------------ + * Convert IPv4 compatible IPv6 address to IPv4 address. + */ static void sockunion_normalise_mapped (union sockunion *su) { @@ -277,7 +384,7 @@ sockunion_accept (int sock, union sockunion *su) len = sizeof(union sockunion); memset(su, 0, len) ; - ret = accept(sock, (struct sockaddr *)su, &len) ; + ret = accept(sock, &su->sa, &len) ; if (ret >= 0) { @@ -292,188 +399,175 @@ sockunion_accept (int sock, union sockunion *su) || (ret == EINTR) ) ? -2 : -1 ; } ; -/* Return sizeof union sockunion. */ -static int -sockunion_sizeof (union sockunion *su) +/*------------------------------------------------------------------------------ + * Make socket for given family, type and protocol + * + * Returns: -1 : failed -- see errno + * otherwise : socket + * + * Logs a LOG_WARNING message if fails. + */ +extern int +sockunion_socket(sa_family_t family, int type, int protocol) { - int ret; + int sockfd ; - ret = 0; - switch (su->sa.sa_family) + sockfd = socket(family, type, protocol); + if (sockfd < 0) { - case AF_INET: - ret = sizeof (struct sockaddr_in); - break; -#ifdef HAVE_IPV6 - case AF_INET6: - ret = sizeof (struct sockaddr_in6); - break; -#endif /* AF_INET6 */ + zlog (NULL, LOG_WARNING, + "Can't make socket family=%d, type=%d, protocol=%d : %s", + (int)family, type, protocol, safe_strerror(errno)) ; + return -1; } - return ret; -} - -/* return sockunion structure : this function should be revised. */ -static char * -sockunion_log (union sockunion *su) -{ - static char buf[SU_ADDRSTRLEN]; - switch (su->sa.sa_family) - { - case AF_INET: - snprintf (buf, SU_ADDRSTRLEN, "%s", safe_inet_ntoa (su->sin.sin_addr)); - break; -#ifdef HAVE_IPV6 - case AF_INET6: - snprintf (buf, SU_ADDRSTRLEN, "%s", - inet_ntop (AF_INET6, &(su->sin6.sin6_addr), buf, SU_ADDRSTRLEN)); - break; -#endif /* HAVE_IPV6 */ - default: - snprintf (buf, SU_ADDRSTRLEN, "af_unknown %d ", su->sa.sa_family); - break; - } - return (XSTRDUP (MTYPE_TMP, buf)); + return sockfd ; } -/*============================================================================== - * Return socket of sockunion. (only used in bgpd) +/*------------------------------------------------------------------------------ + * Make socket for family from given sockunion, type=SOCK_STREAM, protocol=0. * * Returns: -1 : failed -- see errno * otherwise : socket + * + * Logs a LOG_WARNING message if fails. */ int -sockunion_socket (union sockunion *su) +sockunion_stream_socket (union sockunion *su) { - int sockfd ; - - sockfd = socket(su->sa.sa_family, SOCK_STREAM, 0); - if (sockfd < 0) - { - zlog (NULL, LOG_WARNING, "Can't make socket : %s", safe_strerror(errno)) ; - return -1; - } + if (su->sa.sa_family == 0) + su->sa.sa_family = AF_INET_UNION; - return sockfd ; + return sockunion_socket (su->sa.sa_family, SOCK_STREAM, 0); } -/*============================================================================== - * Initiate a connection (only used in bgpd) +/*------------------------------------------------------------------------------ + * Initiate a connection * * Reports EINPROGRESS as success. * + * TODO: discover how the ifindex thing is supposed to work !! + * * Returns: 0 : OK (so far so good) - * != 0 : error number (from errno or otherwise) + * < 0 : failed -- see errno + * + * Logs a LOG_INFO message if fails. */ extern int sockunion_connect(int fd, union sockunion* peer_su, unsigned short port, unsigned int ifindex) { + char buf[SU_ADDRSTRLEN] ; union sockunion su ; int ret ; + int sa_len ; memcpy(&su, peer_su, sizeof(union sockunion)) ; - switch (su.sa.sa_family) - { - case AF_INET: - su.sin.sin_port = htons(port) ; - break; + sa_len = sockunion_set_port(&su, port) ; + #ifdef HAVE_IPV6 - case AF_INET6: - su.sin6.sin6_port = htons(port) ; -#ifdef KAME +# ifdef KAME + if (su.sa.sa_family == AF_INET6) + { if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) { -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID +# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID /* su.sin6.sin6_scope_id = ifindex; */ -#ifdef MUSICA +# ifdef MUSICA su.sin6.sin6_scope_id = ifindex; -#endif -#endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ -#ifndef MUSICA +# endif +# endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ +# ifndef MUSICA SET_IN6_LINKLOCAL_IFINDEX (su.sin6.sin6_addr, ifindex); -#endif +# endif } -#endif /* KAME */ - break; + } ; +# endif /* KAME */ #endif /* HAVE_IPV6 */ - } - ret = connect(fd, (struct sockaddr *)&su, sockunion_sizeof(&su)) ; + ret = connect(fd, &su.sa, sa_len) ; - if ((ret == 0) || ((ret = errno) == EINPROGRESS)) + if ((ret == 0) || (errno == EINPROGRESS)) return 0 ; /* instant success or EINPROGRESS as expected */ - zlog_info("can't connect to %s fd %d : %s", - sockunion_log (&su), fd, safe_strerror(ret)) ; + zlog_info("can't connect to %s port %d fd %d : %s", + sockunion2str(&su, buf, sizeof(buf)), port, fd, safe_strerror(errno)) ; return ret ; } ; -/* Make socket from sockunion union. */ -int -sockunion_stream_socket (union sockunion *su) +/*------------------------------------------------------------------------------ + * Start listening on given socket + * + * Reports EINPROGRESS as success. + * + * TODO: discover how the ifindex thing is supposed to work !! + * + * Returns: 0 : OK (so far so good) + * < 0 : failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ +extern int +sockunion_listen(int fd, int backlog) { - int sock; - - if (su->sa.sa_family == 0) - su->sa.sa_family = AF_INET_UNION; + int ret ; - sock = socket (su->sa.sa_family, SOCK_STREAM, 0); + ret = listen(fd, backlog) ; - if (sock < 0) - zlog (NULL, LOG_WARNING, "can't make socket sockunion_stream_socket"); + if (ret == 0) + return 0 ; - return sock; -} + zlog (NULL, LOG_WARNING, "can't listen on fd %d : %s", + fd, safe_strerror(errno)) ; + return ret ; +} ; -/* Bind socket to specified address. */ +/*------------------------------------------------------------------------------ + * Bind socket to address/port. + * + * Sets the given port into the sockunion su. + * + * If the 'any' parameter is NULL, set the address part of sockunion to + * INADDR_ANY or the family equivalent. Note that for IPv6 this does not + * affect the flow/scope in the su. + * + * For good measure, sets sin_len or family equivalent if required. + * + * Performs bind() and logs a LOG_WARNING message if fails. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + */ int -sockunion_bind (int sock, union sockunion *su, unsigned short port, - union sockunion *su_addr) +sockunion_bind (int sock, union sockunion *su, unsigned short port, void* any) { - int size = 0; + int sa_len ; int ret; + char buf[SU_ADDRSTRLEN] ; - if (su->sa.sa_family == AF_INET) - { - size = sizeof (struct sockaddr_in); - su->sin.sin_port = htons (port); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = size; -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - if (su_addr == NULL) - su->sin.sin_addr.s_addr = htonl (INADDR_ANY); - } -#ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) - { - size = sizeof (struct sockaddr_in6); - su->sin6.sin6_port = htons (port); -#ifdef SIN6_LEN - su->sin6.sin6_len = size; -#endif /* SIN6_LEN */ - if (su_addr == NULL) - { -#if defined(LINUX_IPV6) || defined(NRL) - memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); -#else - su->sin6.sin6_addr = in6addr_any; -#endif /* LINUX_IPV6 */ - } - } -#endif /* HAVE_IPV6 */ + if (any == NULL) + sockunion_set_addr_any(su) ; + sa_len = sockunion_set_port(su, port) ; - ret = bind (sock, (struct sockaddr *)su, size); + ret = bind (sock, &su->sa, sa_len); if (ret < 0) - zlog (NULL, LOG_WARNING, "can't bind socket : %s", safe_strerror (errno)); + zlog (NULL, LOG_WARNING, "can't bind to %s port %d fd %d : %s", + sockunion2str(su, buf, sizeof(buf)), port, sock, safe_strerror(ret)) ; return ret; } +/*------------------------------------------------------------------------------ + * Set socket SO_REUSEADDR option + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ int sockopt_reuseaddr (int sock) { @@ -484,16 +578,25 @@ sockopt_reuseaddr (int sock) (void *) &on, sizeof (on)); if (ret < 0) { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEADDR to socket %d", sock); + zlog (NULL, LOG_WARNING, + "can't set sockopt SO_REUSEADDR to socket %d", sock); return -1; } return 0; } -#ifdef SO_REUSEPORT +/*------------------------------------------------------------------------------ + * Set socket SO_REUSEPORT option -- if it is locally supported. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + * + * Logs a LOG_WARNING message if fails. + */ int sockopt_reuseport (int sock) { +#ifdef SO_REUSEPORT int ret; int on = 1; @@ -501,20 +604,18 @@ sockopt_reuseport (int sock) (void *) &on, sizeof (on)); if (ret < 0) { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEPORT to socket %d", sock); + zlog (NULL, LOG_WARNING, + "can't set sockopt SO_REUSEPORT to socket %d", sock); return -1; } +#endif + return 0; -} -#else -int -sockopt_reuseport (int sock) -{ - return 0; -} -#endif /* 0 */ +} ; -/* If same family and same prefix return 1. */ +/*------------------------------------------------------------------------------ + * If same family and same prefix return 1. + */ int sockunion_same (union sockunion *su1, union sockunion *su2) { @@ -526,9 +627,8 @@ sockunion_same (union sockunion *su1, union sockunion *su2) switch (su1->sa.sa_family) { case AF_INET: - ret = memcmp (&su1->sin.sin_addr, &su2->sin.sin_addr, - sizeof (struct in_addr)); - break; + return (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) ; + #ifdef HAVE_IPV6 case AF_INET6: ret = memcmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, @@ -631,8 +731,9 @@ sockunion_getpeername (int fd, union sockunion* su_remote) return sockunion_get_name(fd, su_remote, 0) ; } ; - -/* Print sockunion structure */ +/*------------------------------------------------------------------------------ + * Print sockunion structure to stdout + */ static void __attribute__ ((unused)) sockunion_print (union sockunion *su) { @@ -671,27 +772,9 @@ sockunion_print (union sockunion *su) } } -#ifdef HAVE_IPV6 -static int -in6addr_cmp (struct in6_addr *addr1, struct in6_addr *addr2) -{ - unsigned int i; - u_char *p1, *p2; - - p1 = (u_char *)addr1; - p2 = (u_char *)addr2; - - for (i = 0; i < sizeof (struct in6_addr); i++) - { - if (p1[i] > p2[i]) - return 1; - else if (p1[i] < p2[i]) - return -1; - } - return 0; -} -#endif /* HAVE_IPV6 */ - +/*------------------------------------------------------------------------------ + * Compare two sockunion values + */ int sockunion_cmp (union sockunion *su1, union sockunion *su2) { @@ -700,23 +783,33 @@ sockunion_cmp (union sockunion *su1, union sockunion *su2) if (su1->sa.sa_family < su2->sa.sa_family) return -1; - if (su1->sa.sa_family == AF_INET) - { - if (ntohl (su1->sin.sin_addr.s_addr) == ntohl (su2->sin.sin_addr.s_addr)) - return 0; - if (ntohl (su1->sin.sin_addr.s_addr) > ntohl (su2->sin.sin_addr.s_addr)) - return 1; + switch (su1->sa.sa_family) + { + case AF_INET: + if (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) + return 0; + if (ntohl(su1->sin.sin_addr.s_addr) > ntohl(su2->sin.sin_addr.s_addr)) + return +1; else return -1; - } + #ifdef HAVE_IPV6 - if (su1->sa.sa_family == AF_INET6) - return in6addr_cmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr); + case AF_INET6: + return memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, + sizeof(struct in6_addr)) ; #endif /* HAVE_IPV6 */ - return 0; + + default: + return 0 ; + } ; } -/* Duplicate sockunion. */ +/*------------------------------------------------------------------------------ + * Create copy of existing sockunion. + * + * It is the caller's responsibility to free the sockunion at some point --see + * sockunion_free() + */ union sockunion * sockunion_dup (union sockunion *su) { @@ -725,37 +818,47 @@ sockunion_dup (union sockunion *su) return dup; } +/*------------------------------------------------------------------------------ + * Free given sockunion (if any). + */ void sockunion_free (union sockunion *su) { - XFREE (MTYPE_SOCKUNION, su); + if (su != NULL) + XFREE (MTYPE_SOCKUNION, su); } /*============================================================================== * Sockunion reference utilities */ +/*------------------------------------------------------------------------------ + * Set sockunion from given prefix -- allocate new sockunion, if required. + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + */ extern sockunion -sockunion_new(struct prefix* p) +sockunion_new_prefix(sockunion su, struct prefix* p) { - sockunion nsu = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)) ; + sa_family_t family ; - if (p == NULL) - return NULL ; + family = (p != NULL) ? p->family : 0 ; - switch (p->family) + su = sockunion_init_new(su, family) ; + + switch (family) { + case 0: + break ; + case AF_INET: - nsu->sin.sin_family = AF_INET ; - nsu->sin.sin_port = 0 ; - nsu->sin.sin_addr = p->u.prefix4 ; + su->sin.sin_addr = p->u.prefix4 ; break ; #ifdef HAVE_IPV6 case AF_INET6: - nsu->sin6.sin6_family = AF_INET ; - nsu->sin6.sin6_port = 0 ; - nsu->sin6.sin6_addr = p->u.prefix6 ; + su->sin6.sin6_addr = p->u.prefix6 ; break ; #endif @@ -763,7 +866,44 @@ sockunion_new(struct prefix* p) break ; } ; - return nsu ; + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Create new sockunion from given sockaddr. + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + */ +extern sockunion +sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) +{ + sa_family_t family ; + + family = (sa != NULL) ? sa->sa_family : 0 ; + + su = sockunion_init_new(su, family) ; + + switch (family) + { + case 0: + break ; + + case AF_INET: + su->sin = *(struct sockaddr_in*)sa ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6 = *(struct sockaddr_in6*)sa ; + break ; +#endif + + default: + break ; + } ; + + return su ; } ; /*------------------------------------------------------------------------------ @@ -825,7 +965,6 @@ sockunion_set_mov(sockunion* p_dst, sockunion* p_src) /*============================================================================== * Symbol Table Hash function -- for symbols whose name is an address. */ - extern void sockunion_symbol_hash(symbol_hash p_hash, const void* name) { diff --git a/lib/sockunion.h b/lib/sockunion.h index ea76a955..d1f29b13 100644 --- a/lib/sockunion.h +++ b/lib/sockunion.h @@ -26,6 +26,7 @@ #include "zebra.h" #include "symtab.h" #include "prefix.h" +#include "memory.h" #if 0 union sockunion { @@ -99,12 +100,13 @@ CONFIRM(SU_ADDRSTRLEN >= INET6_ADDRSTRLEN) ; #define sockunion_family(X) (X)->sa.sa_family /* Prototypes. */ +extern sockunion sockunion_init_new(sockunion su, sa_family_t family) ; extern int str2sockunion (const char *, union sockunion *); extern const char *sockunion2str (union sockunion *, char *, size_t); extern int sockunion_cmp (union sockunion *, union sockunion *); extern int sockunion_same (union sockunion *, union sockunion *); -extern char *sockunion_su2str (union sockunion *su); +extern char* sockunion_su2str (union sockunion* su, enum MTYPE type) ; extern union sockunion *sockunion_str2su (const char *str); extern struct in_addr sockunion_get_in_addr (union sockunion *su); extern int sockunion_accept (int sock, union sockunion *); @@ -112,18 +114,19 @@ extern int sockunion_stream_socket (union sockunion *); extern int sockopt_reuseaddr (int); extern int sockopt_reuseport (int); extern int sockunion_bind (int sock, union sockunion *, - unsigned short, union sockunion *); + unsigned short, void* any); extern int sockopt_ttl (int family, int sock, int ttl); -extern int sockunion_socket (union sockunion *su); -extern const char *inet_sutop (union sockunion *su, char *str); +extern int sockunion_socket (sa_family_t family, int type, int protocol) ; extern int sockunion_connect (int fd, union sockunion *su, unsigned short port, unsigned int) ; +extern int sockunion_listen(int fd, int backlog) ; extern int sockunion_getsockname (int, union sockunion*); extern int sockunion_getpeername (int, union sockunion*); extern union sockunion *sockunion_dup (union sockunion *); extern void sockunion_free (union sockunion *); -extern sockunion sockunion_new(prefix p) ; +extern sockunion sockunion_new_prefix(sockunion su, prefix p) ; +extern sockunion sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) ; extern void sockunion_unset(sockunion* p_su) ; extern void sockunion_set(sockunion* p_dst, sockunion su) ; extern void sockunion_set_dup(sockunion* p_dst, sockunion su) ; diff --git a/lib/uty.h b/lib/uty.h new file mode 100644 index 00000000..80a7cae3 --- /dev/null +++ b/lib/uty.h @@ -0,0 +1,205 @@ +/* VTY internal stuff -- header + * Copyright (C) 1997 Kunihiro Ishiguro + * + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_UTY_H +#define _ZEBRA_UTY_H + +#include <stdbool.h> + +#include "qpthreads.h" +#include "qpnexus.h" +#include "thread.h" +#include "list_util.h" +#include "vty.h" +#include "node_type.h" + +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif + +/*============================================================================== + * This is stuff which is used by the close family of: + * + * vty + * command + * log + * + * and which is not for use elsewhere. + * + * The two: vty_io and vty_cli are treated as private to vty. So anything + * there that is used in command or log is published here. (Nobody other + * than vty.c/vty_io.c/vty_cli.c will include either vty_io.h or vty_cli.h.) + */ + +/*============================================================================== + * To make vty qpthread safe we use a single mutex. + * + * vty and log recurse through each other, so the same mutex is used + * for both, i.e. they are treated as being part of the same monitor. + * + * A recursive mutex is used. This simplifies the calling from log to vty and + * back again. It also allows for the vty internals to call each other. + * + * There are some "uty" functions which assume the mutex is locked. + * + * vty is closely bound to the command handling -- the main vty structure + * contains the context in which commands are parsed and executed. + */ + +extern qpt_mutex_t vty_mutex ; + +#ifdef NDEBUG +# define VTY_DEBUG 0 /* NDEBUG override */ +#else +# ifndef VTY_DEBUG +# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */ +# endif +#endif + +#if VTY_DEBUG + +extern int vty_lock_count ; +extern int vty_lock_assert_fail ; + +#endif + +Inline void +VTY_LOCK(void) +{ + qpt_mutex_lock(&vty_mutex) ; + if (VTY_DEBUG) + ++vty_lock_count ; +} ; + +Inline void +VTY_UNLOCK(void) +{ + if (VTY_DEBUG) + --vty_lock_count ; + qpt_mutex_lock(&vty_mutex) ; +} ; + +#if VTY_DEBUG + +Inline void +VTY_ASSERT_LOCKED(void) +{ + if (vty_lock_count == 0 && !vty_lock_assert_fail) + { + vty_lock_assert_fail = 1; + assert(0); + } +} ; + +#else + +#define VTY_ASSERT_LOCKED() + +#endif + +/*============================================================================== + * Shared definitions + */ + +enum cli_do +{ + cli_do_nothing = 0, /* no action required */ + + cli_do_command, /* dispatch the current command line */ + cli_do_ctrl_c, /* received ^c */ + cli_do_ctrl_d, /* received ^d on empty line */ + cli_do_ctrl_z, /* received ^z */ + + cli_do_count /* number of different cli_do_xxx */ +} ; + +/*============================================================================== + * Variables in vty.c -- used in any of the family + */ +extern vty_io vio_list_base ; +extern vty_io vio_monitors_base ; +extern vty_io vio_death_watch ; + +union vty_watch_dog +{ + qtimer qnexus ; /* when running qnexus */ + struct thread* thread; /* when running threads */ + void* anon ; +}; + +extern union vty_watch_dog vty_watch_dog ; + +extern struct thread_master* vty_master ; + +extern unsigned long vty_timeout_val ; + +extern bool vty_config ; + +extern bool no_password_check ; +extern const bool restricted_mode_default ; +extern bool restricted_mode ; + +char *vty_accesslist_name ; +char *vty_ipv6_accesslist_name ; + +extern qpn_nexus vty_cli_nexus ; +extern qpn_nexus vty_cmd_nexus ; + +/*============================================================================== + * Functions in vty.c -- used in any of the family + */ + +extern int uty_command(struct vty *vty, const char *buf) ; +extern int uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) ; +extern int vty_cmd_exit(struct vty* vty) ; +extern int vty_cmd_end(struct vty* vty) ; +extern int uty_stop_input(struct vty *vty) ; +extern int uty_end_config (struct vty *vty) ; +extern int uty_down_level (struct vty *vty) ; + +extern bool vty_config_lock (struct vty *, enum node_type node); +extern void vty_config_unlock (struct vty *, enum node_type node); +extern void uty_config_unlock (struct vty *vty, enum node_type node) ; + +/*============================================================================== + * Functions in vty_cli + */ +extern void +vty_queued_result(struct vty* vty, int ret, int action); + +extern void +uty_set_host_name(const char* name) ; + +/*============================================================================== + * Functions in vty_io + * + * Send a fixed-size message to all vty terminal monitors; this should be + * an async-signal-safe function. + */ +extern void vty_log_fixed (const char *buf, size_t len); + +extern void uty_log (struct logline* ll, struct zlog *zl, int priority, + const char *format, va_list va); + +#endif /* _ZEBRA_UTY_H */ diff --git a/lib/vector.c b/lib/vector.c index b1ec160d..3fb4cbd9 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -124,20 +124,6 @@ vector_init (unsigned int size) return vector_init_new(NULL, size ? size : 1) ; /* at least 1 entry */ } ; -/* Basic: free the given vector structure. NB: Orphans any existing body !! */ -void -vector_only_wrapper_free (vector v) -{ - XFREE (MTYPE_VECTOR, v); -} - -/* Basic: free the vector body. */ -void -vector_only_index_free (void *body) -{ - XFREE (MTYPE_VECTOR_BODY, body); -} - /* Basic: free the vector body and the vector structure. */ void vector_free (vector v) @@ -201,6 +187,38 @@ vector_reset(vector v, int free_structure) return vector_init_new(v, 0) ; } ; +/* Set vector length to be (at least) the given fixed length. + * + * There must be a vector. + * + * Does nothing if the vector is already as long or longer than the given + * length. + * + * If the body is not big enough for the new length, allocates or extends to + * exactly the new length. Otherwise, leaves body as it is. + * + * Appends NULLs as required to extend to the required length. + * + * Note that the existing contents of the vector are preserved in all cases. + */ +extern void +vector_set_new_min_length(vector v, unsigned int len) +{ + assert (v != NULL) ; + + if (len > v->limit) + { + if (v->p_items == NULL) + v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ; + else + v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(len)); + v->limit = len ; + } ; + + if (v->end < len) + vector_extend(v, len) ; +} ; + /* Pop item from vector, stepping past any NULLs. * * If vector is empty, free the body and, if required, the vector structure. diff --git a/lib/vector.h b/lib/vector.h index 08abdcf7..727d2626 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -30,22 +30,24 @@ #define Inline static inline #endif -/* types and struct for vector */ -/* */ -/* NB: an entirely zero structure represents an entirely empty vector. */ -/* */ -/* TODO: could force vector_index to be 32 bits ? */ - +/*------------------------------------------------------------------------------ + * types and struct for vector + * + * NB: an entirely zero structure represents an entirely empty vector. + * + * TODO: could force vector_index to be 32 bits ? + */ typedef void* p_vector_item ; typedef unsigned int vector_index ; +typedef struct vector* vector ; /* pointer to vector structure */ +typedef struct vector vector_t ; /* embedded vector structure */ struct vector { p_vector_item *p_items ; /* pointer to array of vector item pointers */ vector_index end ; /* number of "active" item entries */ vector_index limit ; /* number of allocated item entries */ }; -typedef struct vector *vector; /* Values that control the allocation of the vector body. */ /* NB: these must all be powers of 2. */ @@ -76,9 +78,6 @@ typedef struct vector *vector; /* include any NULL items. */ #define vector_active(V) ((V)->end) -/* TODO: fix where this is used to poke around inside a vector */ -#define VECTOR_INDEX p_items - /* To walk all items in a vector: * * vector_index i ; @@ -109,8 +108,6 @@ extern int vector_set (vector v, void *val); extern int vector_set_index (vector v, vector_index i, void *val); #define vector_unset(v, i) (void)vector_unset_item(v, i) extern vector_index vector_count (vector v); -extern void vector_only_wrapper_free (vector v); -extern void vector_only_index_free (void *index); extern void vector_free (vector v); extern vector vector_copy (vector v); @@ -131,7 +128,11 @@ extern p_vector_item vector_ream(vector v, int free_structure) ; /* Ream out vector but keep the vector structure. */ #define vector_ream_keep(v) vector_ream(v, 0) -Inline vector_index vector_end(vector v) ; +Inline void vector_set_min_length(vector v, unsigned int len) ; +extern void vector_set_new_min_length(vector v, unsigned int len) ; + +Inline vector_index vector_length(vector v) ; +#define vector_end(v) vector_length(v) Inline int vector_is_empty(vector v) ; Inline p_vector_item vector_get_item(vector v, vector_index i) ; @@ -226,9 +227,20 @@ vector_ensure(vector v, vector_index i) vector_extend(v, i + 1) ; /* do it the hard way */ } ; +/* Want vector to be at least the given length. */ +/* Adjusts logical and physical end of the vector as required, filling */ +/* with NULLs upto any new logical end -- does not allocate any more */ +/* than is exactly necessary. */ +Inline void +vector_set_min_length(vector v, unsigned int len) +{ + if (len > v->end) /* trivial if within vector */ + vector_set_new_min_length(v, len) ; +} ; + /* Return index of end of vector (index of last item + 1) */ Inline vector_index -vector_end(vector v) +vector_length(vector v) { return v->end ; } ; diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c new file mode 100644 index 00000000..fb62f192 --- /dev/null +++ b/lib/vio_fifo.c @@ -0,0 +1,572 @@ +/* VTY I/O FIFO + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <stddef.h> +#include <string.h> + +#include "vio_fifo.h" +#include "list_util.h" + +#include "memory.h" +#include "zassert.h" + +/*============================================================================== + * VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer. + * + * The FIFO is arranged as lumps of some given size. Lumps are allocated + * as and when necessary, and released once emptied. + * + * The last lump is never released. So, it may be that only one lump is + * ever needed. + * + *------------------------------------------------------------------------------ + * Implementation notes: + * + * The FIFO is initialised with all pointers NULL -- so with no lumps at all. + * + * Once a lump has been allocated there is always one lump in the FIFO. + * + * The following are expected to be true: + * + * * put_ptr == get_ptr => FIFO empty + * + * * put_ptr == tail->end -- at all times (NULL when no lumps) + * + * put_ptr >= tail->data ) otherwise something is broken + * put_ptr <= tail->end ) + * + * * get_ptr == head->end -- when there is more than one lump + * get_ptr <= put_ptr -- when there is only one lump + * + * get_ptr >= head->data ) otherwise something is broken + * get_ptr <= head->end ) + * + * * put_ptr == put_end => tail lump is full + * put_ptr < put_end => space exists in the tail lump + * put_ptr > put_end => broken + * + * * get_ptr == get_end => head lump is empty + * BUT if there is only one lump, make sure that + * get_end == put_ptr. + * get_ptr < get_end => data exists in the head lump + * get_ptr > get_end => broken + * + * Note that: + * + * * when the get_ptr reaches the put_ptr the pointers are reset to the + * start of the one and only lump. + * + * Everywhere that the get_ptr is moved, must check for meeting the + * put_ptr and reset pointers. At the same time, when reaches the end of + * a lump, gets rid of it. + * + * * when advancing the put_ptr does not check for advancing the get_end. + * + * The one exception to this, is that when the put_ptr advances to a new + * block, if there was one lump, sets the get_end to the end of that block. + * + * Everywhere that the get_end is used, must check for there being one + * lump and the possibility that put_ptr has changed. + */ + +/*============================================================================== + * Initialisation, allocation and freeing of FIFO and lumps thereof. + */ + +/* Return default size, or given size rounded up to 16 byte boundary */ +static size_t +vio_fifo_size(size_t size) +{ + if (size == 0) + return 4096 ; + else + return ((size + 16 - 1) / 16) * 16 ; +} ; + +/*============================================================================== + * Initialise VTY I/O FIFO -- allocating if required. + */ +extern vio_fifo +vio_fifo_init_new(vio_fifo vf, size_t size) +{ + if (vf == NULL) + vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; + else + memset(vf, 0, sizeof(vio_fifo_t)) ; + + /* Zeroising the the vio_fifo_t has set: + * + * lump -- base pair, both pointers NULL => list is empty + * + * put_ptr -- NULL ) no lump to put anything into + * put_end -- NULL ) put_ptr == put_end => no room in current lump + * + * get_ptr -- NULL ) no lump to get anything from + * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump + * + * ALSO put_ptr == get_ptr => FIFO is empty ! + */ + + vf->size = vio_fifo_size(size) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return vf ; +} + +/*------------------------------------------------------------------------------ + * Free contents of given FIFO, and free FIFO structure as well, if required. + * + * Does nothing if given a NULL pointer -- must already have been freed ! + * + * If does not free the FIFO structure, resets it all empty. + * + * See also: vio_fifo_reset_keep(vio_fifo) + * vio_fifo_reset_free(vio_fifo) + */ +extern vio_fifo +vio_fifo_reset(vio_fifo vf, int free_structure) +{ + vio_fifo_lump lump ; + + if (vf == NULL) + return NULL ; + + while (ddl_pop(&lump, vf->base, list) != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + + if (free_structure) + XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + else + vio_fifo_init_new(vf, vf->size) ; + + return vf ; +} ; + +/*------------------------------------------------------------------------------ + * Set FIFO empty, discarding current contents -- will continue to use the FIFO. + */ +extern void +vio_fifo_set_empty(vio_fifo vf) +{ + vio_fifo_lump lump ; + + assert(vf != NULL) ; + + while (ddl_head(vf->base) != ddl_tail(vf->base)) + { + ddl_pop(&lump, vf->base, list) ; + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + } ; + + lump = ddl_head(vf->base) ; + if (lump != NULL) + { + vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; + vf->put_end = lump->end ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Allocate another lump for putting into. + * + * Call when (vf->put_ptr >= vf->put_end) -- asserts that the pointers are equal. + * + * Set the put_ptr/put_end pointers to point at the new lump. + * + * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * + * If have just filled the first lump on the list, update the get_end pointer + * to reflect the fact that the out lump is now full. + */ +extern void +vio_fifo_lump_new(vio_fifo vf) +{ + vio_fifo_lump lump ; + size_t size ; + int first_alloc ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + + lump = ddl_tail(vf->base) ; + + /* When there is only one lump, the get_end tracks the put_ptr. + * But when there is more than one lump, it must be the end of that lump. + */ + if (vf->one) + vf->get_end = lump->end ; + + first_alloc = (lump == NULL) ; /* extra initialisation needed */ + + if (first_alloc) + assert(vf->put_ptr == NULL) ; /* must all be NULL together */ + else + assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ + + size = vio_fifo_size(vf->size) ; + lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, offsetof(vio_fifo_lump_t, data[size])) ; + lump->end = (char*)lump->data + vf->size ; + + ddl_append(vf->base, lump, list) ; + + vf->one = first_alloc ; + + vf->put_ptr = lump->data ; + vf->put_end = lump->end ; + + if (first_alloc) + { + vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ + vf->get_end = vf->put_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*============================================================================== + * Put data to the FIFO. + */ + +/*------------------------------------------------------------------------------ + * Store 'n' bytes -- allocate new lump if current is exhausted. + */ +extern void +vio_fifo_put(vio_fifo vf, const char* src, size_t n) +{ + size_t take ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + while (n > 0) + { + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf) ; /* traps broken vf->put_ptr > vf->put_end */ + + take = (vf->put_end - vf->put_ptr) ; + if (take > n) + take = n ; + + memcpy(vf->put_ptr, src, take) ; + vf->put_ptr += take ; + + src += take ; + n -= take ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*============================================================================== + * Get data from the FIFO. + */ + +static bool vio_fifo_get_next_lump(vio_fifo vf) ; + +/*------------------------------------------------------------------------------ + * Get ready to read something out of the FIFO. + * + * Makes sure vf->get_end is up to date (if required) and if the FIFO is not + * empty, makes sure vf->get_ptr points at the next byte to be read. + * + * Returns: true <=> there is something in the FIFO. + */ +static inline bool +vio_fifo_get_ready(vio_fifo vf) +{ + if (vf->one) + vf->get_end = vf->put_ptr ; /* make sure have everything */ + + if (vf->get_ptr >= vf->get_end) + if (!vio_fifo_get_next_lump(vf)) + return 0 ; /* quit now if nothing there */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Get upto 'n' bytes. + * + * Returns: number of bytes got -- may be zero. + */ +extern size_t +vio_fifo_get(vio_fifo vf, void* dst, size_t n) +{ + size_t have ; + void* dst_in ; + + if (!vio_fifo_get_ready(vf)) + return 0 ; /* quit now if nothing there */ + + dst_in = dst ; + while (n > 0) + { + have = vf->get_end - vf->get_ptr ; + + if (have > n) + have = n ; + + memcpy(dst, vf->get_ptr, have) ; + vf->get_ptr += have ; + dst = (char*)dst + have ; + + if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ + if (!vio_fifo_get_next_lump(vf)) + break ; /* quit if nothing more to come */ + + n -= have ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return (char*)dst - (char*)dst_in ; +} ; + +/*------------------------------------------------------------------------------ + * Get byte -- the long winded way. + * + * See the inline vio_fifo_get_byte(). + * + * The version is used when the get_ptr is at or just before the end of the + * current lump. Looks after all the necessary pointer updates associated with + * hitting end of lump, or hitting end of FIFO. + * + * Returns: 0x00..0xFF -- byte value (as an int) + * -1 => FIFO is empty. + */ + +extern int +vio_fifo_get_next_byte(vio_fifo vf) +{ + unsigned char u ; + + if (!vio_fifo_get_ready(vf)) + return -1 ; /* quit now if nothing there */ + + u = *vf->get_ptr++ ; + + /* As soon as reach the end want either to discard empty lump, or reset + * the pointers. + */ + if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ + vio_fifo_get_next_lump(vf) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return u ; +} ; + +/*------------------------------------------------------------------------------ + * Get pointer to a lump of bytes. + * + * Returns: address of next byte to get, *have = number of bytes available + * or: NULL => FIFO is empty, *have = 0 + * + * If the FIFO is not empty, will return pointer to at least one byte. + * + * Returns number of bytes to the end of the current lump. There may be + * further lumps beyond the current one. + */ +extern void* +vio_fifo_get_lump(vio_fifo vf, size_t* have) +{ + if (!vio_fifo_get_ready(vf)) + { + *have = 0 ; + return NULL ; + } ; + + *have = (vf->get_end - vf->get_ptr) ; + return vf->get_ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Advance FIFO to position reached. + * + * Having done vio_fifo_get_lump(), can take any number of bytes (up to the + * number that "have"), then call this function to advance the pointers. + * + * The "here" argument must the the address returned by vio_fifo_get_lump() + * plus the number of bytes taken. + */ +extern void +vio_fifo_got_upto(vio_fifo vf, void* here) +{ + vf->get_ptr = here ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + if (vf->get_ptr >= vf->get_end) + vio_fifo_get_next_lump(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Move on to next lump to get stuff from. + * + * Advance pointers etc. so that have at least one byte available, unless + * the FIFO is entirely empty. + * + * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that + * these are equal ! + * + * NB: when there is only one block, it may be that get_end is out of date, + * and should be advanced to the current put_ptr position. + * + * That is done here, but may be worth updating get_end before testing + * against get_ptr. + * + * Returns: true <=> at least one byte in FIFO. + * + * NB: if finds that the FIFO is empty, resets the pointers to the start + * of the last lump. + */ +static bool +vio_fifo_get_next_lump(vio_fifo vf) +{ + vio_fifo_lump head ; + vio_fifo_lump tail ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + assert(vf->get_ptr == vf->get_end) ; + + head = ddl_head(vf->base) ; /* current lump for put */ + tail = ddl_tail(vf->base) ; /* current lump for get */ + + /* Deal with case of one lump only */ + if (vf->one) + { + assert( (head != NULL) + && (head == tail) ) ; + + if (vf->get_ptr == vf->put_ptr) + { + /* FIFO is empty -- reset pointers and exit */ + vf->get_ptr = vf->get_end = vf->put_ptr = head->data ; + assert(vf->put_end == head->end) ; + + return 0 ; /* FIFO empty */ + } ; + + /* Had an out of date vf->get_end */ + assert(vf->get_end < vf->put_ptr) ; + vf->get_end = vf->put_ptr ; + + return 1 ; /* FIFO not empty after all */ + } ; + + /* Deal with case of not yet allocated */ + if (head == NULL) + { + assert( (tail == NULL) + && (vf->put_ptr == vf->get_ptr) ); + + return 0 ; /* FIFO empty */ + } ; + + /* Deal with (remaining) case of two or more lumps */ + assert(vf->get_ptr == head->end) ; + + ddl_del_head(vf->base, list) ; + XFREE(MTYPE_VIO_FIFO_LUMP, head) ; + + head = ddl_head(vf->base) ; + assert(head != NULL) ; + + vf->one = (head == tail) ; + + vf->get_ptr = head->data ; /* at start of next lump */ + + if (vf->one) + vf->get_end = vf->put_ptr ; /* up to current put */ + else + vf->get_end = head->end ; /* up to end of lump */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return (vf->get_ptr < vf->get_end) ; +} ; + +/*============================================================================== + * For debug purposes -- verify the state of the given FIFO + */ +Private void +vio_fifo_verify(vio_fifo vf) +{ + vio_fifo_lump head ; + vio_fifo_lump tail ; + + head = ddl_head(vf->base) ; + tail = ddl_tail(vf->base) ; + + /* If nothing allocated, should all be NULL & !vf->one */ + /* If something allocated, tail must not be NULL */ + if (head == NULL) + { + if ( (tail != NULL) + || (vf->put_ptr != NULL) + || (vf->put_end != NULL) + || (vf->get_ptr != NULL) + || (vf->get_end != NULL) + || (vf->one) ) + zabort("nothing allocated, but not all NULL") ; + return ; + } + else + { + if (tail == NULL) + zabort("head pointer not NULL, but tail pointer is") ; + } ; + + /* Check that all the pointers are within respective lumps + * + * Know that put_end is always tail->end, but get_end need not be. + * */ + if ( (tail->data > vf->put_ptr) + || (vf->put_ptr > vf->put_end) + || (vf->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; + + if ( (head->data > vf->get_ptr) + || (vf->get_ptr > vf->get_end) + || (vf->get_end > head->end) ) + zabort("get pointers outside the head lump") ; + + /* If head == tail, should be vf->one, etc. */ + if (head == tail) + { + if (!vf->one) + zabort("have one lump, but !vf->one") ; + + if (vf->get_end > vf->put_ptr) + zabort("get_end is greater than put_ptr when vf->one") ; + } + else + { + if (vf->one) + zabort("have two or more lumps, but vf->one is true") ; + + if (vf->get_end != head->end) + zabort("get_end is not head->end when !vf->one") ; + } +} ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h new file mode 100644 index 00000000..6d99afe5 --- /dev/null +++ b/lib/vio_fifo.h @@ -0,0 +1,185 @@ +/* VTY I/O FIFO -- header + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _ZEBRA_VIO_FIFO_H +#define _ZEBRA_VIO_FIFO_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "list_util.h" +#include "zassert.h" + +#ifndef Inline /* in case of compiler issues */ +#define Inline static inline +#endif + +#ifndef Private /* extern, but for "friends" only */ +#define Private extern +#endif + +/*============================================================================== + * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. + */ + +#ifdef NDEBUG +# define VIO_FIFO_DEBUG 0 /* NDEBUG override */ +#else +# ifndef VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG 1 /* Set to 1 to turn on debug checks */ +# endif +#endif + +/*============================================================================== + * Data Structures + */ +typedef struct vio_fifo vio_fifo_t ; +typedef struct vio_fifo* vio_fifo ; + +typedef struct vio_fifo_lump vio_fifo_lump_t ; +typedef struct vio_fifo_lump* vio_fifo_lump ; + +struct vio_fifo +{ + struct dl_base_pair(vio_fifo_lump) base ; + + bool one ; + + char* put_ptr ; + char* put_end ; + + char* get_ptr ; + char* get_end ; + + size_t size ; +} ; + +struct vio_fifo_lump +{ + struct dl_list_pair(vio_fifo_lump) list ; + + char* end ; /* end of this particular lump */ + char data[] ; +} ; + +/*============================================================================== + * Functions + */ + +extern vio_fifo +vio_fifo_init_new(vio_fifo vf, size_t size) ; + +extern vio_fifo +vio_fifo_reset(vio_fifo vf, int free_structure) ; + +#define vio_fifo_reset_keep(vf) vio_fifo_reset(vf, 0) +#define vio_fifo_reset_free(vf) vio_fifo_reset(vf, 1) + +extern void +vio_fifo_set_empty(vio_fifo vf) ; + +Inline bool +vio_fifo_empty(vio_fifo vf) ; + +extern void +vio_fifo_put(vio_fifo vf, const char* src, size_t n) ; + +Inline void +vio_fifo_put_byte(vio_fifo vf, char b) ; + +extern size_t +vio_fifo_get(vio_fifo vf, void* dst, size_t n) ; + +Inline int +vio_fifo_get_byte(vio_fifo vf) ; + +extern void* +vio_fifo_get_lump(vio_fifo vf, size_t* have) ; + +extern void +vio_fifo_got_upto(vio_fifo vf, void* here) ; + + +Private void +vio_fifo_lump_new(vio_fifo vf) ; + +Private int +vio_fifo_get_next_byte(vio_fifo vf) ; + +/*============================================================================== + * Debug -- verification function + */ + +Private void +vio_fifo_verify(vio_fifo vf) ; + +#if VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf) +#else +# define VIO_FIFO_DEBUG_VERIFY(vf) +#endif + +/*============================================================================== + * Inline Functions + */ + +/*------------------------------------------------------------------------------ + * Returns true <=> FIFO is empty + */ +Inline bool +vio_fifo_empty(vio_fifo vf) +{ + return (vf->get_ptr == vf->put_ptr) ; +} + +/*------------------------------------------------------------------------------ + * Put one byte to the FIFO + */ +Inline void +vio_fifo_put_byte(vio_fifo vf, char b) +{ + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf) ; /* traps broken vf->put_ptr > vf->put_end */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *vf->put_ptr++ = b ; +} ; + +/*------------------------------------------------------------------------------ + * Get one byte from the FIFO. + * + * Returns: 0x00..0xFF -- byte value (as an int) + * -1 => FIFO is empty. + */ +Inline int +vio_fifo_get_byte(vio_fifo vf) +{ + if (vf->get_end <= (vf->get_ptr + 1)) + return vio_fifo_get_next_byte(vf) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return (unsigned char)*vf->get_ptr++ ; +} ; + +#endif /* _ZEBRA_VIO_FIFO_H */ @@ -1,7 +1,8 @@ -/* - * Virtual terminal [aka TeletYpe] interface routine. +/* VTY top level * Copyright (C) 1997, 98 Kunihiro Ishiguro * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it @@ -20,327 +21,348 @@ * 02111-1307, USA. */ -#include <zebra.h> -#include "miyagi.h" +#include "zebra.h" +#include <stdbool.h> + +#include "vty_io.h" +#include "vty.h" +#include "uty.h" +#include "vty_cli.h" + +#include "list_util.h" -#include "linklist.h" -#include "thread.h" -#include "buffer.h" -#include <lib/version.h> #include "command.h" -#include "sockunion.h" #include "memory.h" -#include "str.h" #include "log.h" -#include "prefix.h" -#include "filter.h" -#include "vty.h" -#include "privs.h" -#include "network.h" - -#include <arpa/telnet.h> -#include "qpthreads.h" -#include "qpnexus.h" - -/* Needs to be qpthread safe */ -qpt_mutex_t vty_mutex; -#ifdef NDEBUG -#define LOCK qpt_mutex_lock(&vty_mutex); -#define UNLOCK qpt_mutex_unlock(&vty_mutex); -#else -int vty_lock_count = 0; -int vty_lock_asserted = 0; -#define LOCK qpt_mutex_lock(&vty_mutex);++vty_lock_count; -#define UNLOCK --vty_lock_count;qpt_mutex_unlock(&vty_mutex); -#define ASSERTLOCKED if(vty_lock_count==0 && !vty_lock_asserted){vty_lock_asserted=1;assert(0);} + +/*============================================================================== + * Variables etc. + */ + +/*------------------------------------------------------------------------------ + * Static and Global (see uty.h) Variables + */ + +/* The mutex and related debug counters */ +qpt_mutex_t vty_mutex ; + +#if VTY_DEBUG + +int vty_lock_count = 0 ; +int vty_lock_assert_fail = 0 ; + #endif -/* - * To make vty qpthread safe we use a single mutex. In general external - * routines have explicit locks, static routines assume that they are being - * called with the mutex already locked. There are a few exceptions, e.g. - * callbacks where static routines are being invoked from outside the module. - * - * There are a few cases where both external and static versions of a - * routine exist. The former for use outside, the latter for use inside - * the module (and lock). In these cases the internal static versions - * starts uty_. This is not strictly necessary as we are using a recursive - * mutex but it avoids unnecessary recursive calls. The recursive mutex - * is used so that we can call zlog and friends from anywhere. - * - * vty and log recurse through each other, so the same mutex is used - * for both, i.e. they are treated as being part of the same monitor. +/* For thread handling -- initialised in vty_init */ +struct thread_master* vty_master = NULL ; + +/* In the qpthreads world, have nexus for the CLI and one for the Routeing + * Engine. Some commands are processed directly in the CLI, most have to + * be sent to the Routeing Engine. */ +qpn_nexus vty_cli_nexus = NULL ; +qpn_nexus vty_cmd_nexus = NULL ; -/* Vty events */ -enum event -{ - VTY_SERV, - VTY_READ, - VTY_WRITE, - VTY_TIMEOUT_RESET, -#ifdef VTYSH - VTYSH_SERV, - VTYSH_READ, - VTYSH_WRITE -#endif /* VTYSH */ -}; +/* List of all known vio */ +vty_io vio_list_base = NULL ; + +/* List of all vty which are in monitor state. */ +vty_io vio_monitors_base = NULL ; + +/* List of all vty which are on death watch */ +vty_io vio_death_watch = NULL ; + +/* Vty timeout value -- see "exec timeout" command */ +unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; + +/* Vty access-class command */ +char *vty_accesslist_name = NULL; -/* Prototypes */ -static int uty_out (struct vty *vty, const char *format, ...); -static int uty_vout(struct vty *vty, const char *format, va_list args); -static void vty_event (enum event, int, struct vty *); -static void uty_hello (struct vty *vty); -static void uty_close (struct vty *vty); -static int uty_config_unlock (struct vty *vty); -static int uty_shell (struct vty *vty); -static int uty_read (struct vty *vty, int vty_sock); -static int uty_flush (struct vty *vty, int vty_sock); -static void vty_event_t (enum event event, int sock, struct vty *vty); -static void vty_event_r (enum event event, int sock, struct vty *vty); -static int uty_accept (int accept_sock); -static int uty_timeout (struct vty *vty); -static void vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when); -static void vty_read_r (qps_file qf, void* file_info); -static void vty_flush_r (qps_file qf, void* file_info); -void uty_reset (void); - -/* Extern host structure from command.c */ -extern struct host host; - -/* Vector which store each vty structure. */ -static vector vtyvec; - -/* Vty timeout value. */ -static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; - -/* Vty access-class command */ -static char *vty_accesslist_name = NULL; - -/* Vty access-calss for IPv6. */ -static char *vty_ipv6_accesslist_name = NULL; - -/* VTY server thread. */ -static vector Vvty_serv_thread; - -/* Current directory. */ +/* Vty access-class for IPv6. */ +char *vty_ipv6_accesslist_name = NULL; + +/* Current directory -- initialised in vty_init() */ static char *vty_cwd = NULL; -/* Configure lock. */ -static int vty_config; +/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */ +bool vty_config = 0 ; -/* Login password check. */ -static int no_password_check = 0; +/* Login password check override. */ +bool no_password_check = 0; -/* Restrict unauthenticated logins? */ -static const u_char restricted_mode_default = 0; -static u_char restricted_mode = 0; +/* Restrict unauthenticated logins? */ +const bool restricted_mode_default = 0 ; + bool restricted_mode = 0 ; -/* Integrated configuration file path */ -char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; +/* Watch-dog timer. */ +union vty_watch_dog vty_watch_dog = { NULL } ; -/* Master of the threads. */ -static struct thread_master *master = NULL; -static qpn_nexus cli_nexus = NULL; -static qpn_nexus routing_nexus = NULL; +/*------------------------------------------------------------------------------ + * VTYSH stuff + */ -/* VTY standard output function. vty == NULL or VTY_SHELL => stdout */ -int -vty_out (struct vty *vty, const char *format, ...) -{ - int result; +/* Integrated configuration file path -- for VTYSH */ +char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; - LOCK - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); - UNLOCK - return result; -} +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void uty_reset (bool final) ; +static void uty_init_commands (void) ; +static void vty_save_cwd (void) ; -/* internal VTY standard output function. vty == NULL or VTY_SHELL => stdout */ -static int -uty_out (struct vty *vty, const char *format, ...) -{ - int result; - ASSERTLOCKED - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); - return result; -} +/*============================================================================== + * Public Interface + */ -/* internal VTY standard output function. vty == NULL or VTY_SHELL => stdout */ -static int -uty_vout(struct vty *vty, const char *format, va_list args) +/*------------------------------------------------------------------------------ + * Initialise vty handling (threads and pthreads) + * + * Install vty's own commands like `who' command. + */ +extern void +vty_init (struct thread_master *master_thread) { - int len = 0; - int size = 1024; - char buf[1024]; - char *p = NULL; - va_list ac; + VTY_LOCK() ; - ASSERTLOCKED + vty_master = master_thread; /* Local pointer to the master thread */ - if (uty_shell (vty)) - { - vprintf (format, args); - } - else - { - /* Try to write to initial buffer. */ - va_copy(ac, args); - len = vsnprintf (buf, sizeof buf, format, ac); - va_end(ac); + vty_save_cwd (); /* need cwd for config reading */ - /* Initial buffer is not enough. */ - if (len < 0 || len >= size) - { - while (1) - { - if (len > -1) - size = len + 1; - else - size = size * 2; + vio_list_base = NULL ; /* no VTYs yet */ + vio_monitors_base = NULL ; + vio_death_watch = NULL ; - p = XREALLOC (MTYPE_VTY_OUT_BUF, p, size); - if (! p) - return -1; + vty_cli_nexus = NULL ; /* not running qnexus-wise */ + vty_cmd_nexus = NULL ; - va_copy(ac, args); - len = vsnprintf (p, size, format, ac); - va_end(ac); + vty_watch_dog.anon = NULL ; /* no watch dog */ - if (len > -1 && len < size) - break; - } - } + uty_init_commands() ; /* install nodes */ - /* When initial buffer is enough to store all output. */ - if (! p) - p = buf; + VTY_UNLOCK() ; +} - /* Pointer p must point out buffer. */ - buffer_put (vty->obuf, (u_char *) p, len); +/*------------------------------------------------------------------------------ + * Further initialisation for qpthreads. + * + * This is done during "second stage" initialisation, when all nexuses have + * been set up and the qpthread_enabled state established. + * + * Need to know where the CLI nexus and the Routeing Engine nexus are. + * + * Initialise mutex. + */ +extern void +vty_init_r (qpn_nexus cli, qpn_nexus cmd) +{ + vty_cli_nexus = cli ; + vty_cmd_nexus = cmd ; - /* If p is not different with buf, it is allocated buffer. */ - if (p != buf) - XFREE (MTYPE_VTY_OUT_BUF, p); - } + qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); +} ; - return len; -} +/*------------------------------------------------------------------------------ + * Initialise the listeners for VTY_TERM and VTY_SHELL_SERV VTY + * + * This is done after the configuration file has been read. + */ +extern void +vty_serv_sock(const char *addr, unsigned short port, const char *path) +{ + VTY_LOCK() ; + uty_open_listeners(addr, port, path) ; + VTY_UNLOCK() ; +} ; -int -vty_puts(struct vty *vty, const char* str) +/*------------------------------------------------------------------------------ + * Initialisation for vtysh application. + * + * TODO: work out what this needs to do ! (If anything.) + */ +extern void +vty_init_vtysh (void) { - return vty_out(vty, "%s", str) ; -} + VTY_LOCK() ; -int -vty_out_newline(struct vty *vty) + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new VTY of the given type + */ +extern struct vty * +vty_new (int fd, enum vty_type type) +{ + struct vty* vty ; + + VTY_LOCK() ; + vty = uty_new(fd, type); + VTY_UNLOCK() ; + + return vty ; +} ; + +/*------------------------------------------------------------------------------ + * Close the given VTY completely + */ +extern void +vty_close (struct vty *vty) { - return vty_out(vty, "%s", VTY_NEWLINE) ; + VTY_LOCK() ; + uty_close(vty->vio); + VTY_UNLOCK() ; } -/* 123456789012345678901234 */ -const char* vty_spaces_string = " " ; +/*------------------------------------------------------------------------------ + * Reset all VTY status + * + * This is done just before the configuration file is re-read (SIGHUP). + * + * Half closes all VTY, leaving the death watch to tidy up once all output has + * completed. + * + * NB: old code discarded all output and hard closed all the VTY... + * + * TODO: ...SIGHUP while a command is queued ? + * + * Closes all listening sockets. + */ +extern void +vty_reset(void) +{ + VTY_LOCK() ; + uty_reset(0) ; /* not final ! */ + VTY_UNLOCK() ; +} -int -vty_out_indent(struct vty *vty, int indent) +/*------------------------------------------------------------------------------ + * System shut-down + * + * Reset all known vty and release all memory. + */ +extern void +vty_terminate (void) { - return vty_puts(vty, VTY_SPACES(indent)) ; + VTY_LOCK() ; + uty_reset(1) ; /* final reset */ + VTY_UNLOCK() ; + + qpt_mutex_destroy(&vty_mutex, 0); } -static int -vty_log_out (struct vty *vty, const char *level, const char *proto_str, - const char *format, struct timestamp_control *ctl, va_list va) +/*------------------------------------------------------------------------------ + * Reset -- final or for SIGHUP + */ +static void +uty_reset (bool curtains) { - int ret; - int len; - char buf[1024]; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; - ASSERTLOCKED + uty_close_listeners() ; - if (!ctl->already_rendered) + while ((vio = sdl_pop(&vio, vio_list_base, vio_list)) != NULL) { - ctl->len = uquagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); - ctl->already_rendered = 1; - } - if (ctl->len+1 >= sizeof(buf)) - return -1; - memcpy(buf, ctl->buf, len = ctl->len); - buf[len++] = ' '; - buf[len] = '\0'; - - if (level) - ret = snprintf(buf+len, sizeof(buf)-len, "%s: %s: ", level, proto_str); - else - ret = snprintf(buf+len, sizeof(buf)-len, "%s: ", proto_str); - if ((ret < 0) || ((size_t)(len += ret) >= sizeof(buf))) - return -1; + uty_half_close(vio) ; /* TODO: reason for close */ - if (((ret = vsnprintf(buf+len, sizeof(buf)-len, format, va)) < 0) || - ((size_t)((len += ret)+2) > sizeof(buf))) - return -1; + if (curtains) + uty_full_close(vio) ; + } ; - buf[len++] = '\r'; - buf[len++] = '\n'; + vty_timeout_val = VTY_TIMEOUT_DEFAULT; - if (write(vty->fd, buf, len) < 0) + if (vty_accesslist_name) { - if (ERRNO_IO_RETRY(errno)) - /* Kernel buffer is full, probably too much debugging output, so just - drop the data and ignore. */ - return -1; - /* Fatal I/O error. */ - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - uzlog(NULL, LOG_WARNING, "%s: write failed to vty client fd %d, closing: %s", - __func__, vty->fd, safe_strerror(errno)); - buffer_reset(vty->obuf); - /* cannot call vty_close, because a parent routine may still try - to access the vty struct */ - vty->status = VTY_CLOSE; - shutdown(vty->fd, SHUT_RDWR); - return -1; + XFREE(MTYPE_VTY, vty_accesslist_name); + vty_accesslist_name = NULL; } - return 0; -} -/* Output current time to the vty. */ -void -vty_time_print (struct vty *vty, int cr) + if (vty_ipv6_accesslist_name) + { + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + vty_ipv6_accesslist_name = NULL; + } + + if (curtains && vty_cwd) + XFREE (MTYPE_TMP, vty_cwd); +} ; + +/*============================================================================== + * General VTY output. + * + * This is mostly used during command execution, to output the results of the + * command. + * + * All these end up in uty_vout -- see vty_io. + */ + +/*------------------------------------------------------------------------------ + * VTY output -- cf fprintf ! + */ +extern int +vty_out (struct vty *vty, const char *format, ...) { - char buf [25]; + int result; - if (quagga_timestamp(0, buf, sizeof(buf)) == 0) + VTY_LOCK() ; + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + VTY_UNLOCK() ; + return result; +} + +/*------------------------------------------------------------------------------ + * VTY output -- output a given numnber of spaces + */ + +/* 1 2 3 4 */ +/* 1234567890123456789012345678901234567890 */ +const char vty_spaces_string[] = " " ; +CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ; + +extern int +vty_out_indent(struct vty *vty, int indent) +{ + while (indent > VTY_MAX_SPACES) { - zlog (NULL, LOG_INFO, "quagga_timestamp error"); - return; + int ret = vty_out(vty, VTY_SPACES(indent)) ; + if (ret < 0) + return ret ; + indent -= VTY_MAX_SPACES ; } + return vty_out(vty, VTY_SPACES(indent)) ; +} ; + +/*------------------------------------------------------------------------------ + * VTY output -- output the current time in standard form, to the second. + */ +extern void +vty_time_print (struct vty *vty, int cr) +{ + char buf [timestamp_buffer_len]; + + quagga_timestamp(0, buf, sizeof(buf)) ; + if (cr) - vty_out (vty, "%s\n", buf); + vty_out (vty, "%s%s", buf, VTY_NEWLINE); else vty_out (vty, "%s ", buf); return; } -/* Say hello to vty interface. */ +/*------------------------------------------------------------------------------ + * Say hello to vty interface. + */ void vty_hello (struct vty *vty) { - LOCK - uty_hello(vty); - UNLOCK -} + VTY_LOCK() ; -static void -uty_hello (struct vty *vty) -{ - ASSERTLOCKED #ifdef QDEBUG uty_out (vty, "%s%s", debug_banner, VTY_NEWLINE); #endif @@ -351,126 +373,147 @@ uty_hello (struct vty *vty) f = fopen (host.motdfile, "r"); if (f) - { - while (fgets (buf, sizeof (buf), f)) - { - char *s; - /* work backwards to ignore trailling isspace() */ - for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); - s--); - *s = '\0'; - uty_out (vty, "%s%s", buf, VTY_NEWLINE); - } - fclose (f); - } + { + while (fgets (buf, sizeof (buf), f)) + { + char *s; + /* work backwards to ignore trailing isspace() */ + for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); + s--); + *s = '\0'; + uty_out (vty, "%s%s", buf, VTY_NEWLINE); + } + fclose (f); + } else - uty_out (vty, "MOTD file %s not found%s", host.motdfile, VTY_NEWLINE); + uty_out (vty, "MOTD file %s not found%s", host.motdfile, VTY_NEWLINE); } else if (host.motd) uty_out (vty, "%s", host.motd); + + VTY_UNLOCK() ; } -/* Put out prompt and wait input from user. */ -static void -vty_prompt (struct vty *vty) +/*============================================================================== + * Command Execution + */ + +/*------------------------------------------------------------------------------ + * Execute command, adding it to the history if not empty or comment + * + * Outputs diagnostics if fails to parse. + * + * Returns: CMD_xxxx result. + */ +extern int +uty_command(struct vty *vty, const char *buf) { - struct utsname names; - const char*hostname; + int ret; + vector vline; + const char *protocolname; - ASSERTLOCKED + VTY_ASSERT_LOCKED() ; - if (vty->type == VTY_TERM) - { - hostname = host.name; - if (!hostname) - { - uname (&names); - hostname = names.nodename; - } - uty_out (vty, cmd_prompt (vty->node), hostname); - } -} + /* Split readline string up into the vector */ + vline = cmd_make_strvec (buf); -/* Send WILL TELOPT_ECHO to remote server. */ -static void -vty_will_echo (struct vty *vty) -{ - unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' }; - ASSERTLOCKED - uty_out (vty, "%s", cmd); -} + if (vline == NULL) + return CMD_SUCCESS; /* quit if empty or comment */ -/* Make suppress Go-Ahead telnet option. */ -static void -vty_will_suppress_go_ahead (struct vty *vty) -{ - unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' }; - ASSERTLOCKED - uty_out (vty, "%s", cmd); -} + uty_cli_hist_add (vty->vio, buf) ; -/* Make don't use linemode over telnet. */ -static void -vty_dont_linemode (struct vty *vty) -{ - unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' }; - ASSERTLOCKED - uty_out (vty, "%s", cmd); -} +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; -/* Use window size. */ -static void -vty_do_window_size (struct vty *vty) -{ - unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' }; - ASSERTLOCKED - uty_out (vty, "%s", cmd); -} + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ -#if 0 /* Currently not used. */ -/* Make don't use lflow vty interface. */ -static void -vty_dont_lflow_ahead (struct vty *vty) -{ - unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW, '\0' }; - ASSERTLOCKED - uty_out (vty, "%s", cmd); -} -#endif /* 0 */ +//VTY_UNLOCK() ; + ret = cmd_execute_command (vline, vty, NULL, vty_cmd_nexus, vty_cli_nexus, 0); +//VTY_LOCK() ; -/* Allocate new vty struct. */ -struct vty * -vty_new (int fd, int type) -{ - struct vty *vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + /* Get the name of the protocol if any */ + protocolname = uzlog_get_proto_name(NULL); - vty->obuf = buffer_new(0); /* Use default buffer size. */ - vty->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); - vty->max = VTY_BUFSIZ; - vty->fd = fd; - vty->type = type; +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + if ((realtime = thread_consumed_time(&after, &before, &cputime)) > + CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, buf); + } +#endif /* CONSUMED_TIME_CHECK */ - if (cli_nexus) - { - vty->qf = qps_file_init_new(vty->qf, NULL); - qps_add_file(cli_nexus->selection, vty->qf, vty->fd, vty); - vty->qtr = qtimer_init_new(vty->qtr, cli_nexus->pile, vty_timeout_r, vty); - } + if (ret != CMD_SUCCESS) + switch (ret) + { + case CMD_WARNING: + if (vty->vio->type == VTY_FILE) + uty_out (vty, "Warning...%s", VTY_NEWLINE); + break; + case CMD_ERR_AMBIGUOUS: + uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + break; + case CMD_ERR_NO_MATCH: + uty_out (vty, "%% [%s] Unknown command: %s%s", protocolname, buf, VTY_NEWLINE); + break; + case CMD_ERR_INCOMPLETE: + uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); + break; + } + cmd_free_strvec (vline); - return vty; + return ret; } -/* Authentication of vty */ -static void -vty_auth (struct vty *vty, char *buf) +/*------------------------------------------------------------------------------ + * Authentication of vty + * + * During AUTH_NODE and AUTH_ENABLE_NODE, when a command line is dispatched by + * any means this function is called. + * + * Note that if the AUTH_NODE password fails too many times, the terminal is + * closed. + * + * Returns: 0 <=> not queued. + */ +extern int +uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) { char *passwd = NULL; enum node_type next_node = 0; int fail; char *crypt (const char *, const char *); - ASSERTLOCKED + vty_io vio = vty->vio ; + + VTY_ASSERT_LOCKED() ; + + /* What to do ? + * + * In fact, all the exotic command terminators simply discard any input + * and return. + */ + switch (cli_do) + { + case cli_do_nothing: + case cli_do_ctrl_c: + case cli_do_ctrl_d: + case cli_do_ctrl_z: + return 0 ; + + case cli_do_command: + break ; + + default: + zabort("unknown or invalid cli_do") ; + } ; + /* Ordinary command dispatch -- see if password is OK. */ switch (vty->node) { case AUTH_NODE: @@ -504,368 +547,102 @@ vty_auth (struct vty *vty, char *buf) if (! fail) { - vty->fail = 0; + vio->fail = 0; vty->node = next_node; /* Success ! */ } else { - vty->fail++; - if (vty->fail >= 3) + vio->fail++; + if (vio->fail >= 3) { if (vty->node == AUTH_NODE) { - uty_out (vty, "%% Bad passwords, too many failures!%s", VTY_NEWLINE); - vty->status = VTY_CLOSE; + uty_out (vty, "%% Bad passwords, too many failures!%s", + VTY_NEWLINE); + uty_half_close(vio) ; } else { /* AUTH_ENABLE_NODE */ - vty->fail = 0; - uty_out (vty, "%% Bad enable passwords, too many failures!%s", VTY_NEWLINE); + vio->fail = 0; + uty_out (vty, "%% Bad enable passwords, too many failures!%s", + VTY_NEWLINE); vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; } } } -} - -/* Command execution over the vty interface. */ -static int -vty_command (struct vty *vty, char *buf) -{ - int ret; - vector vline; - const char *protocolname; - - ASSERTLOCKED - - /* Split readline string up into the vector */ - vline = cmd_make_strvec (buf); - - if (vline == NULL) - return CMD_SUCCESS; - -#ifdef CONSUMED_TIME_CHECK - { - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; - - GETRUSAGE(&before); -#endif /* CONSUMED_TIME_CHECK */ - - UNLOCK - ret = cmd_execute_command (vline, vty, NULL, routing_nexus, 0); - LOCK - - /* Get the name of the protocol if any */ - protocolname = uzlog_get_proto_name(NULL); - -#ifdef CONSUMED_TIME_CHECK - GETRUSAGE(&after); - if ((realtime = thread_consumed_time(&after, &before, &cputime)) > - CONSUMED_TIME_CHECK) - /* Warn about CPU hog that must be fixed. */ - uzlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", - realtime/1000, cputime/1000, buf); - } -#endif /* CONSUMED_TIME_CHECK */ - - if (ret != CMD_SUCCESS) - switch (ret) - { - case CMD_WARNING: - if (vty->type == VTY_FILE) - uty_out (vty, "Warning...%s", VTY_NEWLINE); - break; - case CMD_ERR_AMBIGUOUS: - uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - break; - case CMD_ERR_NO_MATCH: - uty_out (vty, "%% [%s] Unknown command: %s%s", protocolname, buf, VTY_NEWLINE); - break; - case CMD_ERR_INCOMPLETE: - uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); - break; - } - cmd_free_strvec (vline); - - return ret; -} - -/* queued command has completed */ -void -vty_queued_result(struct vty *vty, int result) -{ - LOCK - - vty_prompt(vty); - - /* Wake up */ - if (cli_nexus) - { - vty_event (VTY_WRITE, vty->fd, vty); - if (qpthreads_enabled) - qpt_thread_signal(cli_nexus->thread_id, SIGMQUEUE); - } - - UNLOCK -} - -static const char telnet_backward_char = 0x08; -static const char telnet_space_char = ' '; - -/* Basic function to write buffer to vty. */ -static void -vty_write (struct vty *vty, const char *buf, size_t nbytes) -{ - ASSERTLOCKED - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - return; - - /* Should we do buffering here ? And make vty_flush (vty) ? */ - buffer_put (vty->obuf, buf, nbytes); -} - -/* Ensure length of input buffer. Is buffer is short, double it. */ -static void -vty_ensure (struct vty *vty, int length) -{ - ASSERTLOCKED - if (vty->max <= length) - { - vty->max *= 2; - vty->buf = XREALLOC (MTYPE_VTY, vty->buf, vty->max); - } -} - -/* Basic function to insert character into vty. */ -static void -vty_self_insert (struct vty *vty, char c) -{ - int i; - int length; - - ASSERTLOCKED - - vty_ensure (vty, vty->length + 1); - length = vty->length - vty->cp; - memmove (&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); - vty->buf[vty->cp] = c; - vty_write (vty, &vty->buf[vty->cp], length + 1); - for (i = 0; i < length; i++) - vty_write (vty, &telnet_backward_char, 1); + return 0 ; +} ; - vty->cp++; - vty->length++; -} - -/* Self insert character 'c' in overwrite mode. */ -static void -vty_self_insert_overwrite (struct vty *vty, char c) -{ - ASSERTLOCKED - vty_ensure (vty, vty->length + 1); - vty->buf[vty->cp++] = c; - - if (vty->cp > vty->length) - vty->length++; - - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - return; - - vty_write (vty, &c, 1); -} - -/* Insert a word into vty interface with overwrite mode. */ -static void -vty_insert_word_overwrite (struct vty *vty, char *str) +/*------------------------------------------------------------------------------ + * Command line "exit" command -- aka "quit" + * + * Falls back one NODE level. + * + * Returns: 0 <=> not queued. + */ +extern int +vty_cmd_exit(struct vty* vty) { + VTY_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ - int len = strlen (str); - - ASSERTLOCKED - - vty_write (vty, str, len); - strcpy (&vty->buf[vty->cp], str); - vty->cp += len; - vty->length = vty->cp; -} - -/* Forward character. */ -static void -vty_forward_char (struct vty *vty) -{ - ASSERTLOCKED - if (vty->cp < vty->length) - { - vty_write (vty, &vty->buf[vty->cp], 1); - vty->cp++; - } -} - -/* Backward character. */ -static void -vty_backward_char (struct vty *vty) -{ - ASSERTLOCKED - if (vty->cp > 0) + switch (vty->node) { - vty->cp--; - vty_write (vty, &telnet_backward_char, 1); + case VIEW_NODE: + case ENABLE_NODE: + case RESTRICTED_NODE: + if (vty_shell (vty)) + exit (0); +// else +// vty_set_status(vty, VTY_CLOSE); + break; + case CONFIG_NODE: + uty_config_unlock (vty, ENABLE_NODE); + break; + case INTERFACE_NODE: + case ZEBRA_NODE: + case BGP_NODE: + case RIP_NODE: + case RIPNG_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case MASC_NODE: + case RMAP_NODE: + case VTY_NODE: + vty->node = CONFIG_NODE ; + break; + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: + vty->node = BGP_NODE ; + break; + case KEYCHAIN_KEY_NODE: + vty->node = KEYCHAIN_NODE ; + break; + default: + break; } -} - -/* Move to the beginning of the line. */ -static void -vty_beginning_of_line (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp) - vty_backward_char (vty); -} - -/* Move to the end of the line. */ -static void -vty_end_of_line (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp < vty->length) - vty_forward_char (vty); -} - -static void vty_kill_line_from_beginning (struct vty *); -static void vty_redraw_line (struct vty *); -/* Print command line history. This function is called from - vty_next_line and vty_previous_line. */ -static void -vty_history_print (struct vty *vty) -{ - int length; - - ASSERTLOCKED - - vty_kill_line_from_beginning (vty); - - /* Get previous line from history buffer */ - length = strlen (vty->hist[vty->hp]); - memcpy (vty->buf, vty->hist[vty->hp], length); - vty->cp = vty->length = length; - - /* Redraw current line */ - vty_redraw_line (vty); -} - -/* Show next command line history. */ -static void -vty_next_line (struct vty *vty) -{ - int try_index; - - ASSERTLOCKED - - if (vty->hp == vty->hindex) - return; - - /* Try is there history exist or not. */ - try_index = vty->hp; - if (try_index == (VTY_MAXHIST - 1)) - try_index = 0; - else - try_index++; - - /* If there is not history return. */ - if (vty->hist[try_index] == NULL) - return; - else - vty->hp = try_index; - - vty_history_print (vty); + VTY_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + return 0 ; } -/* Show previous command line history. */ -static void -vty_previous_line (struct vty *vty) -{ - int try_index; - - ASSERTLOCKED - - try_index = vty->hp; - if (try_index == 0) - try_index = VTY_MAXHIST - 1; - else - try_index--; - - if (vty->hist[try_index] == NULL) - return; - else - vty->hp = try_index; - - vty_history_print (vty); -} - -/* This function redraw all of the command line character. */ -static void -vty_redraw_line (struct vty *vty) -{ - ASSERTLOCKED - vty_write (vty, vty->buf, vty->length); - vty->cp = vty->length; -} - -/* Forward word. */ -static void -vty_forward_word (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') - vty_forward_char (vty); - - while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') - vty_forward_char (vty); -} - -/* Backward word without skipping training space. */ -static void -vty_backward_pure_word (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_backward_char (vty); -} - -/* Backward word. */ -static void -vty_backward_word (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') - vty_backward_char (vty); - - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_backward_char (vty); -} - -/* When '^D' is typed at the beginning of the line we move to the down - level. */ -static void -vty_down_level (struct vty *vty) -{ - ASSERTLOCKED - uty_out (vty, "%s", VTY_NEWLINE); - (*config_exit_cmd.func)(NULL, vty, 0, NULL); - vty_prompt (vty); - vty->cp = 0; -} - -/* When '^Z' is received from vty, move down to the enable mode. */ -static void -vty_end_config (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command line "end" command + * + * Falls back to ENABLE_NODE. + * + * Returns: 0 <=> not queued. + */ +extern int +vty_cmd_end(struct vty* vty) { - ASSERTLOCKED - uty_out (vty, "%s", VTY_NEWLINE); + VTY_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ switch (vty->node) { @@ -893,406 +670,76 @@ vty_end_config (struct vty *vty) case KEYCHAIN_KEY_NODE: case MASC_NODE: case VTY_NODE: - uty_config_unlock (vty); - vty->node = ENABLE_NODE; + uty_config_unlock (vty, ENABLE_NODE); break; default: - /* Unknown node, we have to ignore it. */ break; } - vty_prompt (vty); - vty->cp = 0; -} - -/* Delete a charcter at the current point. */ -static void -vty_delete_char (struct vty *vty) -{ - int i; - int size; - - ASSERTLOCKED - - if (vty->length == 0) - { - vty_down_level (vty); - return; - } - - if (vty->cp == vty->length) - return; /* completion need here? */ - - size = vty->length - vty->cp; - - vty->length--; - memmove (&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); - vty->buf[vty->length] = '\0'; - - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - return; - - vty_write (vty, &vty->buf[vty->cp], size - 1); - vty_write (vty, &telnet_space_char, 1); - - for (i = 0; i < size; i++) - vty_write (vty, &telnet_backward_char, 1); -} - -/* Delete a character before the point. */ -static void -vty_delete_backward_char (struct vty *vty) -{ - ASSERTLOCKED - if (vty->cp == 0) - return; - - vty_backward_char (vty); - vty_delete_char (vty); -} - -/* Kill rest of line from current point. */ -static void -vty_kill_line (struct vty *vty) -{ - int i; - int size; - - ASSERTLOCKED - - size = vty->length - vty->cp; - - if (size == 0) - return; - - for (i = 0; i < size; i++) - vty_write (vty, &telnet_space_char, 1); - for (i = 0; i < size; i++) - vty_write (vty, &telnet_backward_char, 1); - - memset (&vty->buf[vty->cp], 0, size); - vty->length = vty->cp; -} - -/* Kill line from the beginning. */ -static void -vty_kill_line_from_beginning (struct vty *vty) -{ - ASSERTLOCKED - vty_beginning_of_line (vty); - vty_kill_line (vty); -} - -/* Delete a word before the point. */ -static void -vty_forward_kill_word (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') - vty_delete_char (vty); - while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') - vty_delete_char (vty); -} - -/* Delete a word before the point. */ -static void -vty_backward_kill_word (struct vty *vty) -{ - ASSERTLOCKED - while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') - vty_delete_backward_char (vty); - while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') - vty_delete_backward_char (vty); -} - -/* Transpose chars before or at the point. */ -static void -vty_transpose_chars (struct vty *vty) -{ - char c1, c2; - - ASSERTLOCKED - - /* If length is short or point is near by the beginning of line then - return. */ - if (vty->length < 2 || vty->cp < 1) - return; - - /* In case of point is located at the end of the line. */ - if (vty->cp == vty->length) - { - c1 = vty->buf[vty->cp - 1]; - c2 = vty->buf[vty->cp - 2]; - - vty_backward_char (vty); - vty_backward_char (vty); - vty_self_insert_overwrite (vty, c1); - vty_self_insert_overwrite (vty, c2); - } - else - { - c1 = vty->buf[vty->cp]; - c2 = vty->buf[vty->cp - 1]; - - vty_backward_char (vty); - vty_self_insert_overwrite (vty, c1); - vty_self_insert_overwrite (vty, c2); - } -} + VTY_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + return 0 ; +} ; -/* Do completion at vty interface. */ -static void -vty_complete_command (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command line ^C action. + * + * Ignores contents of command line (including not adding to history). + * + * Fall back to ENABLE_NODE if in any one of a number of nodes. + * + * Resets the history pointer. + * + * Returns: 0 <=> not queued. + */ +extern int +uty_stop_input(struct vty *vty) { + vty_io vio = vty->vio ; - int i; - int ret; - char **matched = NULL; - vector vline; - - ASSERTLOCKED - - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - return; - - vline = cmd_make_strvec (vty->buf); - if (vline == NULL) - return; - - /* In case of 'help \t'. */ - if (isspace ((int) vty->buf[vty->length - 1])) - vector_set (vline, '\0'); - - matched = cmd_complete_command (vline, vty->node, &ret); + VTY_ASSERT_LOCKED() ; - cmd_free_strvec (vline); - - uty_out (vty, "%s", VTY_NEWLINE); - switch (ret) + switch (vty->node) { - case CMD_ERR_AMBIGUOUS: - uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - vty_prompt (vty); - vty_redraw_line (vty); - break; - case CMD_ERR_NO_MATCH: - /* vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); */ - vty_prompt (vty); - vty_redraw_line (vty); - break; - case CMD_COMPLETE_FULL_MATCH: - vty_prompt (vty); - vty_redraw_line (vty); - vty_backward_pure_word (vty); - vty_insert_word_overwrite (vty, matched[0]); - vty_self_insert (vty, ' '); - XFREE (MTYPE_TMP, matched[0]); - break; - case CMD_COMPLETE_MATCH: - vty_prompt (vty); - vty_redraw_line (vty); - vty_backward_pure_word (vty); - vty_insert_word_overwrite (vty, matched[0]); - XFREE (MTYPE_TMP, matched[0]); - vector_only_index_free (matched); - return; - break; - case CMD_COMPLETE_LIST_MATCH: - for (i = 0; matched[i] != NULL; i++) - { - if (i != 0 && ((i % 6) == 0)) - uty_out (vty, "%s", VTY_NEWLINE); - uty_out (vty, "%-10s ", matched[i]); - XFREE (MTYPE_TMP, matched[i]); - } - uty_out (vty, "%s", VTY_NEWLINE); - - vty_prompt (vty); - vty_redraw_line (vty); - break; - case CMD_ERR_NOTHING_TODO: - vty_prompt (vty); - vty_redraw_line (vty); + case CONFIG_NODE: + case INTERFACE_NODE: + case ZEBRA_NODE: + case RIP_NODE: + case RIPNG_NODE: + case BGP_NODE: + case RMAP_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case KEYCHAIN_KEY_NODE: + case MASC_NODE: + case VTY_NODE: + uty_config_unlock (vty, ENABLE_NODE) ; break; default: + /* Unknown node, we have to ignore it. */ break; } - if (matched) - vector_only_index_free (matched); -} - -static void -vty_describe_fold (struct vty *vty, int cmd_width, - unsigned int desc_width, struct desc *desc) -{ - char *buf; - const char *cmd, *p; - int pos; - - ASSERTLOCKED - - cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; - - if (desc_width <= 0) - { - uty_out (vty, " %-*s %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE); - return; - } - - buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); - - for (p = desc->str; strlen (p) > desc_width; p += pos + 1) - { - for (pos = desc_width; pos > 0; pos--) - if (*(p + pos) == ' ') - break; - - if (pos == 0) - break; - - strncpy (buf, p, pos); - buf[pos] = '\0'; - uty_out (vty, " %-*s %s%s", cmd_width, cmd, buf, VTY_NEWLINE); - - cmd = ""; - } - - uty_out (vty, " %-*s %s%s", cmd_width, cmd, p, VTY_NEWLINE); - - XFREE (MTYPE_TMP, buf); -} - -/* Describe matched command function. */ -static void -vty_describe_command (struct vty *vty) -{ - int ret; - vector vline; - vector describe; - unsigned int i, width, desc_width; - struct desc *desc, *desc_cr = NULL; - - ASSERTLOCKED - - vline = cmd_make_strvec (vty->buf); - - /* In case of '> ?'. */ - if (vline == NULL) - { - vline = vector_init (1); - vector_set (vline, '\0'); - } - else - if (isspace ((int) vty->buf[vty->length - 1])) - vector_set (vline, '\0'); - - describe = cmd_describe_command (vline, vty->node, &ret); - - uty_out (vty, "%s", VTY_NEWLINE); - - /* Ambiguous error. */ - switch (ret) - { - case CMD_ERR_AMBIGUOUS: - uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); - goto out; - break; - case CMD_ERR_NO_MATCH: - uty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); - goto out; - break; - } - - /* Get width of command string. */ - width = 0; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - unsigned int len; - - if (desc->cmd[0] == '\0') - continue; - - len = strlen (desc->cmd); - if (desc->cmd[0] == '.') - len--; - - if (width < len) - width = len; - } - - /* Get width of description string. */ - desc_width = vty->width - (width + 6); - /* Print out description. */ - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - if (desc->cmd[0] == '\0') - continue; - - if (strcmp (desc->cmd, command_cr) == 0) - { - desc_cr = desc; - continue; - } - - if (!desc->str) - uty_out (vty, " %-s%s", - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - VTY_NEWLINE); - else if (desc_width >= strlen (desc->str)) - uty_out (vty, " %-*s %s%s", width, - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str, VTY_NEWLINE); - else - vty_describe_fold (vty, width, desc_width, desc); - -#if 0 - uty_out (vty, " %-*s %s%s", width - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str ? desc->str : "", VTY_NEWLINE); -#endif /* 0 */ - } - - if ((desc = desc_cr)) - { - if (!desc->str) - uty_out (vty, " %-s%s", - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - VTY_NEWLINE); - else if (desc_width >= strlen (desc->str)) - uty_out (vty, " %-*s %s%s", width, - desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, - desc->str, VTY_NEWLINE); - else - vty_describe_fold (vty, width, desc_width, desc); - } - -out: - cmd_free_strvec (vline); - if (describe) - vector_free (describe); - - vty_prompt (vty); - vty_redraw_line (vty); -} + /* Set history pointer to the latest one. */ + vio->hp = vio->hindex; -static void -vty_clear_buf (struct vty *vty) -{ - ASSERTLOCKED - memset (vty->buf, 0, vty->max); -} + return 0 ; +} ; -/* ^C stop current input and do not add command line to the history. */ -static void -vty_stop_input (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command ^Z action. + * + * Ignores contents of command line (including not adding to history). + * + * Fall back to ENABLE_NODE if in any one of a number of nodes. + * + * Returns: 0 <=> not queued. + */ +extern int +uty_end_config (struct vty *vty) { - ASSERTLOCKED - vty->cp = vty->length = 0; - vty_clear_buf (vty); - uty_out (vty, "%s", VTY_NEWLINE); + VTY_ASSERT_LOCKED() ; switch (vty->node) { @@ -1307,6 +754,11 @@ vty_stop_input (struct vty *vty) case RIP_NODE: case RIPNG_NODE: case BGP_NODE: + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: case RMAP_NODE: case OSPF_NODE: case OSPF6_NODE: @@ -1315,1354 +767,258 @@ vty_stop_input (struct vty *vty) case KEYCHAIN_KEY_NODE: case MASC_NODE: case VTY_NODE: - uty_config_unlock (vty); - vty->node = ENABLE_NODE; + uty_config_unlock (vty, ENABLE_NODE) ; break; default: /* Unknown node, we have to ignore it. */ break; } - vty_prompt (vty); - /* Set history pointer to the latest one. */ - vty->hp = vty->hindex; + return 0 ; } -/* Add current command line to the history buffer. */ -static void -vty_hist_add (struct vty *vty) +/*------------------------------------------------------------------------------ + * Command ^D action -- when nothing else on command line. + * + * Same as "exit" command. + * + * Returns: 0 <=> not queued. + */ +extern int +uty_down_level (struct vty *vty) { - int index; - - ASSERTLOCKED + return vty_cmd_exit(vty) ; +} ; - if (vty->length == 0) - return; - - index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; - - /* Ignore the same string as previous one. */ - if (vty->hist[index]) - if (strcmp (vty->buf, vty->hist[index]) == 0) - { - vty->hp = vty->hindex; - return; - } - - /* Insert history entry. */ - if (vty->hist[vty->hindex]) - XFREE (MTYPE_VTY_HIST, vty->hist[vty->hindex]); - vty->hist[vty->hindex] = XSTRDUP (MTYPE_VTY_HIST, vty->buf); - - /* History index rotation. */ - vty->hindex++; - if (vty->hindex == VTY_MAXHIST) - vty->hindex = 0; - - vty->hp = vty->hindex; -} +/*============================================================================== + * Reading of configuration file + */ -/* #define TELNET_OPTION_DEBUG */ +static FILE * vty_use_backup_config (char *fullpath) ; +static void vty_read_file (FILE *confp, void (*after_first_cmd)(void)) ; -/* Get telnet window size. */ -static int -vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) +/*------------------------------------------------------------------------------ + * Read the given configuration file. + */ +extern void +vty_read_config (char *config_file, + char *config_default) { -#ifdef TELNET_OPTION_DEBUG - int i; - - ASSERTLOCKED - - for (i = 0; i < nbytes; i++) - { - switch (buf[i]) - { - case IAC: - uty_out (vty, "IAC "); - break; - case WILL: - uty_out (vty, "WILL "); - break; - case WONT: - uty_out (vty, "WONT "); - break; - case DO: - uty_out (vty, "DO "); - break; - case DONT: - uty_out (vty, "DONT "); - break; - case SB: - uty_out (vty, "SB "); - break; - case SE: - uty_out (vty, "SE "); - break; - case TELOPT_ECHO: - uty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE); - break; - case TELOPT_SGA: - uty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE); - break; - case TELOPT_NAWS: - uty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE); - break; - default: - uty_out (vty, "%x ", buf[i]); - break; - } - } - uty_out (vty, "%s", VTY_NEWLINE); - -#endif /* TELNET_OPTION_DEBUG */ - - switch (buf[0]) - { - case SB: - vty->sb_len = 0; - vty->iac_sb_in_progress = 1; - return 0; - break; - case SE: - { - if (!vty->iac_sb_in_progress) - return 0; - - if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) - { - vty->iac_sb_in_progress = 0; - return 0; - } - switch (vty->sb_buf[0]) - { - case TELOPT_NAWS: - if (vty->sb_len != TELNET_NAWS_SB_LEN) - uzlog(NULL, LOG_WARNING, "RFC 1073 violation detected: telnet NAWS option " - "should send %d characters, but we received %lu", - TELNET_NAWS_SB_LEN, (u_long)vty->sb_len); - else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) - uzlog(NULL, LOG_ERR, "Bug detected: sizeof(vty->sb_buf) %lu < %d, " - "too small to handle the telnet NAWS option", - (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); - else - { - vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]); - vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]); -#ifdef TELNET_OPTION_DEBUG - uty_out(vty, "TELNET NAWS window size negotiation completed: " - "width %d, height %d%s", - vty->width, vty->height, VTY_NEWLINE); -#endif - } - break; - } - vty->iac_sb_in_progress = 0; - return 0; - break; - } - default: - break; - } - return 1; + vty_read_config_first_cmd_special(config_file, config_default, NULL); } -/* Execute current command line. */ -static int -vty_execute (struct vty *vty) +/*------------------------------------------------------------------------------ + * Read the given configuration file. + * + * The config_file (-f argument) is used if specified. + * + * If config_file is NULL, use the config_default. + * + * If using the config_default, if VTYSH_ENABLED, look for "vtysh" in the name. + * If find "vtysh" and find the "integrate_default" file, then do nothing + * now -- expect vtysh to connect in due course and provide the configuration. + * + * The config_file or config_default may be relative file names. + * + * May have a function to call after the first actual command is processed. + * This mechanism supports the setting of qpthreads-ness by configuration file + * command. + */ +extern void +vty_read_config_first_cmd_special(char *config_file, + char *config_default, + void (*after_first_cmd)(void)) { - int ret; - - ret = CMD_SUCCESS; - - switch (vty->node) - { - case AUTH_NODE: - case AUTH_ENABLE_NODE: - vty_auth (vty, vty->buf); - break; - default: - ret = vty_command (vty, vty->buf); - if (vty->type == VTY_TERM) - vty_hist_add (vty); - break; - } - - /* Clear command line buffer. */ - vty->cp = vty->length = 0; - vty_clear_buf (vty); - - if (vty->status != VTY_CLOSE && ret != CMD_QUEUED) - vty_prompt (vty); - - return ret; -} - -#define CONTROL(X) ((X) - '@') -#define VTY_NORMAL 0 -#define VTY_PRE_ESCAPE 1 -#define VTY_ESCAPE 2 + char cwd[MAXPATHLEN]; + FILE *confp = NULL; + char *fullpath; + char *tmp = NULL; -/* Escape character command map. */ -static void -vty_escape_map (unsigned char c, struct vty *vty) -{ - ASSERTLOCKED - switch (c) + /* Deal with VTYSH_ENABLED magic */ + if (VTYSH_ENABLED && (config_file == NULL)) { - case ('A'): - vty_previous_line (vty); - break; - case ('B'): - vty_next_line (vty); - break; - case ('C'): - vty_forward_char (vty); - break; - case ('D'): - vty_backward_char (vty); - break; - default: - break; - } - - /* Go back to normal mode. */ - vty->escape = VTY_NORMAL; -} - -/* Quit print out to the buffer. */ -static void -vty_buffer_reset (struct vty *vty) -{ - ASSERTLOCKED - buffer_reset (vty->obuf); - vty_prompt (vty); - vty_redraw_line (vty); -} - -/* Callback: qpthreads., Read data via vty socket. */ -static void -vty_read_r (qps_file qf, void* file_info) -{ - int vty_sock = qf->fd; - struct vty *vty = (struct vty *)file_info; - - LOCK - - /* is this necessary? */ - qps_disable_modes(qf, qps_read_mbit); - uty_read(vty, vty_sock); - - UNLOCK -} - -/* Callback: threads. Read data via vty socket. */ -static int -vty_read (struct thread *thread) -{ - int vty_sock = THREAD_FD (thread); - struct vty *vty = THREAD_ARG (thread); - int result ; - - LOCK + int ret; + struct stat conf_stat; - vty->t_read = NULL; - result = uty_read(vty, vty_sock); + /* !!!!PLEASE LEAVE!!!! + * This is NEEDED for use with vtysh -b, or else you can get + * a real configuration food fight with a lot garbage in the + * merged configuration file it creates coming from the per + * daemon configuration files. This also allows the daemons + * to start if there default configuration file is not + * present or ignore them, as needed when using vtysh -b to + * configure the daemons at boot - MAG + */ - UNLOCK - return result; -} + /* Stat for vtysh Zebra.conf, if found startup and wait for + * boot configuration + */ -static int -uty_read (struct vty *vty, int vty_sock) -{ - int i; - int nbytes; - unsigned char buf[VTY_READ_BUFSIZ]; + if ( strstr(config_default, "vtysh") == NULL) + { + ret = stat (integrate_default, &conf_stat); + if (ret >= 0) + return; + } + } ; - /* Read raw data from socket */ - if ((nbytes = read (vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) - { - if (nbytes < 0) - { - if (ERRNO_IO_RETRY(errno)) - { - vty_event (VTY_READ, vty_sock, vty); - return 0; - } - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - uzlog(NULL, LOG_WARNING, "%s: read error on vty client fd %d, closing: %s", - __func__, vty->fd, safe_strerror(errno)); - } - buffer_reset(vty->obuf); - vty->status = VTY_CLOSE; - } + /* Use default if necessary, and deal with constructing full path */ + if (config_file == NULL) + config_file = config_default ; - for (i = 0; i < nbytes; i++) + if (! IS_DIRECTORY_SEP (config_file[0])) { - if (buf[i] == IAC) - { - if (!vty->iac) - { - vty->iac = 1; - continue; - } - else - { - vty->iac = 0; - } - } - - if (vty->iac_sb_in_progress && !vty->iac) - { - if (vty->sb_len < sizeof(vty->sb_buf)) - vty->sb_buf[vty->sb_len] = buf[i]; - vty->sb_len++; - continue; - } - - if (vty->iac) - { - /* In case of telnet command */ - int ret = 0; - ret = vty_telnet_option (vty, buf + i, nbytes - i); - vty->iac = 0; - i += ret; - continue; - } - - - if (vty->status == VTY_MORE) - { - switch (buf[i]) - { - case CONTROL('C'): - case 'q': - case 'Q': - vty_buffer_reset (vty); - break; -#if 0 /* More line does not work for "show ip bgp". */ - case '\n': - case '\r': - vty->status = VTY_MORELINE; - break; -#endif - default: - break; - } - continue; - } - - /* Escape character. */ - if (vty->escape == VTY_ESCAPE) - { - vty_escape_map (buf[i], vty); - continue; - } - - /* Pre-escape status. */ - if (vty->escape == VTY_PRE_ESCAPE) - { - switch (buf[i]) - { - case '[': - vty->escape = VTY_ESCAPE; - break; - case 'b': - vty_backward_word (vty); - vty->escape = VTY_NORMAL; - break; - case 'f': - vty_forward_word (vty); - vty->escape = VTY_NORMAL; - break; - case 'd': - vty_forward_kill_word (vty); - vty->escape = VTY_NORMAL; - break; - case CONTROL('H'): - case 0x7f: - vty_backward_kill_word (vty); - vty->escape = VTY_NORMAL; - break; - default: - vty->escape = VTY_NORMAL; - break; - } - continue; - } - - switch (buf[i]) - { - case CONTROL('A'): - vty_beginning_of_line (vty); - break; - case CONTROL('B'): - vty_backward_char (vty); - break; - case CONTROL('C'): - vty_stop_input (vty); - break; - case CONTROL('D'): - vty_delete_char (vty); - break; - case CONTROL('E'): - vty_end_of_line (vty); - break; - case CONTROL('F'): - vty_forward_char (vty); - break; - case CONTROL('H'): - case 0x7f: - vty_delete_backward_char (vty); - break; - case CONTROL('K'): - vty_kill_line (vty); - break; - case CONTROL('N'): - vty_next_line (vty); - break; - case CONTROL('P'): - vty_previous_line (vty); - break; - case CONTROL('T'): - vty_transpose_chars (vty); - break; - case CONTROL('U'): - vty_kill_line_from_beginning (vty); - break; - case CONTROL('W'): - vty_backward_kill_word (vty); - break; - case CONTROL('Z'): - vty_end_config (vty); - break; - case '\n': - case '\r': - uty_out (vty, "%s", VTY_NEWLINE); - vty_execute (vty); - break; - case '\t': - vty_complete_command (vty); - break; - case '?': - if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) - vty_self_insert (vty, buf[i]); - else - vty_describe_command (vty); - break; - case '\033': - if (i + 1 < nbytes && buf[i + 1] == '[') - { - vty->escape = VTY_ESCAPE; - i++; - } - else - vty->escape = VTY_PRE_ESCAPE; - break; - default: - if (buf[i] > 31 && buf[i] < 127) - vty_self_insert (vty, buf[i]); - break; - } + getcwd (cwd, sizeof(cwd)) ; + tmp = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (config_file) + 2) ; + sprintf (tmp, "%s/%s", cwd, config_file); + fullpath = tmp; } - - /* Check status. */ - if (vty->status == VTY_CLOSE) - uty_close (vty); else { - vty_event (VTY_WRITE, vty_sock, vty); - vty_event (VTY_READ, vty_sock, vty); - } + tmp = NULL ; + fullpath = config_file; + } ; - return 0; -} - -/* Callback: qpthreads. Flush buffer to the vty. */ -static void -vty_flush_r (qps_file qf, void* file_info) -{ - int vty_sock = qf->fd; - struct vty *vty = (struct vty *)file_info; - - LOCK - - qps_disable_modes(qf, qps_write_mbit); + /* try to open the configuration file */ + confp = fopen (fullpath, "r"); - /* Temporary disable read thread. */ - if ((vty->lines == 0)) + if (confp == NULL) { - qps_disable_modes(qf, qps_read_mbit); - } - - uty_flush(vty, vty_sock); - - UNLOCK -} - -/* Callback: threads. Flush buffer to the vty. */ -static int -vty_flush (struct thread *thread) -{ - int vty_sock = THREAD_FD (thread); - struct vty *vty = THREAD_ARG (thread); - int result; - - LOCK - vty->t_write = NULL; - - /* Temporary disable read thread. */ - if ((vty->lines == 0) && vty->t_read) - { - thread_cancel (vty->t_read); - vty->t_read = NULL; - } - result = uty_flush(vty, vty_sock); - - UNLOCK - return result; -} - -static int -uty_flush (struct vty *vty, int vty_sock) -{ - int erase; - buffer_status_t flushrc; - - /* Function execution continue. */ - erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); - - /* N.B. if width is 0, that means we don't know the window size. */ - if ((vty->lines == 0) || (vty->width == 0)) - flushrc = buffer_flush_available(vty->obuf, vty->fd); - else if (vty->status == VTY_MORELINE) - flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, - 1, erase, 0); - else - flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, - vty->lines >= 0 ? vty->lines : - vty->height, - erase, 0); - switch (flushrc) - { - case BUFFER_ERROR: - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - uzlog(NULL, LOG_WARNING, "buffer_flush failed on vty client fd %d, closing", - vty->fd); - buffer_reset(vty->obuf); - uty_close(vty); - break; - case BUFFER_EMPTY: - if (vty->status == VTY_CLOSE) - uty_close (vty); - else - { - vty->status = VTY_NORMAL; - if (vty->lines == 0) - vty_event (VTY_READ, vty_sock, vty); - } - break; - case BUFFER_PENDING: - /* There is more data waiting to be written. */ - vty->status = VTY_MORE; - if (vty->lines == 0) - vty_event (VTY_WRITE, vty_sock, vty); - break; - } - - return 0; -} - -/* Create new vty structure. */ -static struct vty * -vty_create (int vty_sock, union sockunion *su) -{ - struct vty *vty; + fprintf (stderr, "%s: failed to open configuration file %s: %s\n", + __func__, fullpath, safe_strerror (errno)); - ASSERTLOCKED - - /* Allocate new vty structure and set up default values. */ - vty = vty_new (vty_sock, VTY_TERM); - vty->address = sockunion_su2str (su); - if (no_password_check) - { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; + confp = vty_use_backup_config (fullpath); + if (confp) + fprintf (stderr, "WARNING: using backup configuration file!\n"); else - vty->node = VIEW_NODE; - } - else - vty->node = AUTH_NODE; - vty->fail = 0; - vty->cp = 0; - vty_clear_buf (vty); - vty->length = 0; - memset (vty->hist, 0, sizeof (vty->hist)); - vty->hp = 0; - vty->hindex = 0; - vector_set_index (vtyvec, vty_sock, vty); - vty->status = VTY_NORMAL; - vty->v_timeout = vty_timeout_val; - if (host.lines >= 0) - vty->lines = host.lines; - else - vty->lines = -1; - vty->iac = 0; - vty->iac_sb_in_progress = 0; - vty->sb_len = 0; - - if (! no_password_check) - { - /* Vty is not available if password isn't set. */ - if (host.password == NULL && host.password_encrypt == NULL) - { - uty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); - vty->status = VTY_CLOSE; - uty_close (vty); - return NULL; - } - } - - /* Say hello to the world. */ - uty_hello (vty); - if (! no_password_check) - uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); - - /* Setting up terminal. */ - vty_will_echo (vty); - vty_will_suppress_go_ahead (vty); - - vty_dont_linemode (vty); - vty_do_window_size (vty); - /* vty_dont_lflow_ahead (vty); */ - - vty_prompt (vty); - - /* Add read/write thread. */ - vty_event (VTY_WRITE, vty_sock, vty); - vty_event (VTY_READ, vty_sock, vty); - - return vty; -} - -/* Callback: qpthreads. Accept connection from the network. */ -static void -vty_accept_r (qps_file qf, void* file_info) -{ - LOCK - - int accept_sock = qf->fd; - uty_accept(accept_sock); - - UNLOCK -} - -/* Callback: threads. Accept connection from the network. */ -static int -vty_accept (struct thread *thread) -{ - int result; - - LOCK - - int accept_sock = THREAD_FD (thread); - result = uty_accept(accept_sock); - - UNLOCK - return result; -} - -static int -uty_accept (int accept_sock) -{ - int vty_sock; - struct vty *vty; - union sockunion su; - int ret; - unsigned int on; - struct prefix *p = NULL; - struct access_list *acl = NULL; - char *bufp; - - ASSERTLOCKED - - /* We continue hearing vty socket. */ - vty_event (VTY_SERV, accept_sock, NULL); - - memset (&su, 0, sizeof (union sockunion)); - - /* We can handle IPv4 or IPv6 socket. */ - vty_sock = sockunion_accept (accept_sock, &su); - if (vty_sock < 0) - { - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", safe_strerror (errno)); - return -1; - } - set_nonblocking(vty_sock); - - p = sockunion2hostprefix (&su); - - /* VTY's accesslist apply. */ - if (p->family == AF_INET && vty_accesslist_name) - { - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - { - char *buf; - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", - (buf = sockunion_su2str (&su))); - free (buf); - close (vty_sock); - - /* continue accepting connections */ - vty_event (VTY_SERV, accept_sock, NULL); - - prefix_free (p); - return 0; - } - } - -#ifdef HAVE_IPV6 - /* VTY's ipv6 accesslist apply. */ - if (p->family == AF_INET6 && vty_ipv6_accesslist_name) - { - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - { - char *buf; - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", - (buf = sockunion_su2str (&su))); - free (buf); - close (vty_sock); - - /* continue accepting connections */ - vty_event (VTY_SERV, accept_sock, NULL); - - prefix_free (p); - return 0; - } - } -#endif /* HAVE_IPV6 */ - - prefix_free (p); - - on = 1; - ret = setsockopt (vty_sock, IPPROTO_TCP, TCP_NODELAY, - (char *) &on, sizeof (on)); - if (ret < 0) - uzlog (NULL, LOG_INFO, "can't set sockopt to vty_sock : %s", - safe_strerror (errno)); - - uzlog (NULL, LOG_INFO, "Vty connection from %s", - (bufp = sockunion_su2str (&su))); - if (bufp) - XFREE (MTYPE_TMP, bufp); - - vty = vty_create (vty_sock, &su); - - return 0; -} - -#if defined(HAVE_IPV6) && !defined(NRL) -static void -vty_serv_sock_addrinfo (const char *hostname, unsigned short port) -{ - int ret; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - int sock; - char port_str[BUFSIZ]; - - ASSERTLOCKED - - memset (&req, 0, sizeof (struct addrinfo)); - req.ai_flags = AI_PASSIVE; - req.ai_family = AF_UNSPEC; - req.ai_socktype = SOCK_STREAM; - sprintf (port_str, "%d", port); - port_str[sizeof (port_str) - 1] = '\0'; - - ret = getaddrinfo (hostname, port_str, &req, &ainfo); - - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (ret)); - exit (1); - } - - ainfo_save = ainfo; - - do - { - if (ainfo->ai_family != AF_INET -#ifdef HAVE_IPV6 - && ainfo->ai_family != AF_INET6 -#endif /* HAVE_IPV6 */ - ) - continue; - - sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); - if (sock < 0) - continue; - - sockopt_reuseaddr (sock); - sockopt_reuseport (sock); - - /* set non-blocking */ - ret = set_nonblocking(sock); - if (ret < 0) { - close (sock); /* Avoid sd leak. */ - continue; + fprintf (stderr, "can't open backup configuration file [%s%s]\n", + fullpath, CONF_BACKUP_EXT); + exit(1); } + } ; - ret = bind (sock, ainfo->ai_addr, ainfo->ai_addrlen); - if (ret < 0) - { - close (sock); /* Avoid sd leak. */ - continue; - } - - ret = listen (sock, 3); - if (ret < 0) - { - close (sock); /* Avoid sd leak. */ - continue; - } - - vty_event (VTY_SERV, sock, NULL); - } - while ((ainfo = ainfo->ai_next) != NULL); - - freeaddrinfo (ainfo_save); -} -#endif /* HAVE_IPV6 && ! NRL */ - -#if defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) -/* Make vty server socket. */ -static void -vty_serv_sock_family (const char* addr, unsigned short port, int family) -{ - int ret; - union sockunion su; - int accept_sock; - void* naddr=NULL; - - ASSERTLOCKED - - memset (&su, 0, sizeof (union sockunion)); - su.sa.sa_family = family; - if(addr) - switch(family) - { - case AF_INET: - naddr=&su.sin.sin_addr; -#ifdef HAVE_IPV6 - case AF_INET6: - naddr=&su.sin6.sin6_addr; +#ifdef QDEBUG + fprintf(stderr, "Reading config file: %s\n", fullpath); #endif - } - - if(naddr) - switch(inet_pton(family,addr,naddr)) - { - case -1: - uzlog(NULL, LOG_ERR, "bad address %s",addr); - naddr=NULL; - break; - case 0: - uzlog(NULL, LOG_ERR, "error translating address %s: %s",addr,safe_strerror(errno)); - naddr=NULL; - } - - /* Make new socket. */ - accept_sock = sockunion_stream_socket (&su); - if (accept_sock < 0) - return; - - /* This is server, so reuse address. */ - sockopt_reuseaddr (accept_sock); - sockopt_reuseport (accept_sock); - - /* set non-blocking */ - ret = set_nonblocking(accept_sock); - if (ret < 0) - { - close (accept_sock); /* Avoid sd leak. */ - return; - } - - /* Bind socket to universal address and given port. */ - ret = sockunion_bind (accept_sock, &su, port, naddr); - if (ret < 0) - { - uzlog(NULL, LOG_WARNING, "can't bind socket"); - close (accept_sock); /* Avoid sd leak. */ - return; - } - - /* Listen socket under queue 3. */ - ret = listen (accept_sock, 3); - if (ret < 0) - { - uzlog (NULL, LOG_WARNING, "can't listen socket"); - close (accept_sock); /* Avoid sd leak. */ - return; - } - - /* Add vty server event. */ - vty_event (VTY_SERV, accept_sock, NULL); -} -#endif /* defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) */ - -#ifdef VTYSH -/* For sockaddr_un. */ -#include <sys/un.h> - -/* VTY shell UNIX domain socket. */ -static void -vty_serv_un (const char *path) -{ - int ret; - int sock, len; - struct sockaddr_un serv; - mode_t old_mask; - struct zprivs_ids_t ids; - - ASSERTLOCKED - - /* First of all, unlink existing socket */ - unlink (path); - - /* Set umask */ - old_mask = umask (0007); - - /* Make UNIX domain socket. */ - sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", safe_strerror(errno)); - return; - } - - /* Make server socket. */ - memset (&serv, 0, sizeof (struct sockaddr_un)); - serv.sun_family = AF_UNIX; - strncpy (serv.sun_path, path, strlen (path)); -#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN - len = serv.sun_len = SUN_LEN(&serv); -#else - len = sizeof (serv.sun_family) + strlen (serv.sun_path); -#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ - - ret = bind (sock, (struct sockaddr *) &serv, len); - if (ret < 0) - { - uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, safe_strerror(errno)); - close (sock); /* Avoid sd leak. */ - return; - } - - ret = listen (sock, 5); - if (ret < 0) - { - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, safe_strerror(errno)); - close (sock); /* Avoid sd leak. */ - return; - } - - umask (old_mask); - - zprivs_get_ids(&ids); - - if (ids.gid_vty > 0) - { - /* set group of socket */ - if ( chown (path, -1, ids.gid_vty) ) - { - uzlog (NULL, LOG_ERR, "vty_serv_un: could chown socket, %s", - safe_strerror (errno) ); - } - } - - vty_event (VTYSH_SERV, sock, NULL); -} - -/* #define VTYSH_DEBUG 1 */ - -/* Callback: qpthreads. Accept connection */ -void int -vtysh_accept_r (qps_file qf, void* file_info) -{ - int accept_sock = qf->fd; - LOCK - utysh_accept (accept_sock); - UNLOCK -} - -/* Callback: threads. Accept connection */ -static int -vtysh_accept (struct thread *thread) -{ - int accept_sock = THREAD_FD (thread); - LOCK - result = utysh_accept (accept_sock); - UNLOCK - return result; -} - -static int -utysh_accept (int accept_sock) -{ - int sock; - int client_len; - struct sockaddr_un client; - struct vty *vty; - - ASSERTLOCKED - - vty_event (VTYSH_SERV, accept_sock, NULL); - - memset (&client, 0, sizeof (struct sockaddr_un)); - client_len = sizeof (struct sockaddr_un); - - sock = accept (accept_sock, (struct sockaddr *) &client, - (socklen_t *) &client_len); - if (sock < 0) - { - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", safe_strerror (errno)); - return -1; - } - - if (set_nonblocking(sock) < 0) - { - uzlog (NULL, LOG_WARNING, "vtysh_accept: could not set vty socket %d to non-blocking," - " %s, closing", sock, safe_strerror (errno)); - close (sock); - return -1; - } - -#ifdef VTYSH_DEBUG - printf ("VTY shell accept\n"); -#endif /* VTYSH_DEBUG */ - - vty = vty_new (); - vty->fd = sock; - vty->type = VTY_SHELL_SERV; - vty->node = VIEW_NODE; - - vty_event (VTYSH_READ, sock, vty); - - return 0; -} - -static int -vtysh_flush(struct vty *vty) -{ - ASSERTLOCKED - - switch (buffer_flush_available(vty->obuf, vty->fd)) - { - case BUFFER_PENDING: - vty_event(VTYSH_WRITE, vty->fd, vty); - break; - case BUFFER_ERROR: - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - uzlog(NULL, LOG_WARNING, "%s: write error to fd %d, closing", __func__, vty->fd); - buffer_reset(vty->obuf); - uty_close(vty); - return -1; - break; - case BUFFER_EMPTY: - break; - } - return 0; -} - -/* Callback: qpthreads., Read data via vty socket. */ -static void -vtysh_read_r (qps_file qf, void* file_info) -{ - int vty_sock = qf->fd; - struct vty *vty = (struct vty *)file_info; + vty_read_file (confp, after_first_cmd); + fclose (confp); - LOCK + host_config_set (fullpath); - /* is this necessary? */ - qps_disable_modes(qf, qps_read_mbit); - utysh_read(vty, vty_soc); +#ifdef QDEBUG + fprintf(stderr, "Finished reading config file\n"); +#endif - UNLOCK + if (tmp) + XFREE (MTYPE_TMP, tmp); } -/* Callback: threads. Read data via vty socket. */ -static int -vtysh_read (struct thread *thread) +/*------------------------------------------------------------------------------ + * Try to use a backup configuration file. + * + * Having failed to open the file "<fullpath>", if there is a file called + * "<fullpath>.sav" that can be opened for reading, then: + * + * - make a copy of that file + * - call it "<fullpath>" + * - return an open FILE + * + * Returns: NULL => no "<fullpath>.sav", or faild doing any of the above + * otherwise, returns FILE* for open file. + */ +static FILE * +vty_use_backup_config (char *fullpath) { - int vty_sock = THREAD_FD (thread); - struct vty *vty = THREAD_ARG (thread); - int result; - - LOCK - - vty->t_read = NULL; - result = uty_read(vty, vty_soc); - - UNLOCK - return result; -} + char *tmp_path ; + struct stat buf; + int ret, tmp, sav; + int c; + char buffer[4096] ; -static int -utysh_read (struct vty *vty, int sock) -{ - int ret; - int nbytes; - unsigned char buf[VTY_READ_BUFSIZ]; - unsigned char *p; - u_char header[4] = {0, 0, 0, 0}; + enum { xl = 32 } ; + tmp_path = malloc(strlen(fullpath) + xl) ; - if ((nbytes = read (sock, buf, VTY_READ_BUFSIZ)) <= 0) - { - if (nbytes < 0) - { - if (ERRNO_IO_RETRY(errno)) - { - vty_event (VTYSH_READ, sock, vty); - return 0; - } - vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ - uzlog(NULL, LOG_WARNING, "%s: read failed on vtysh client fd %d, closing: %s", - __func__, sock, safe_strerror(errno)); - } - buffer_reset(vty->obuf); - uty_close (vty); -#ifdef VTYSH_DEBUG - printf ("close vtysh\n"); -#endif /* VTYSH_DEBUG */ - return 0; - } + /* construct the name "<fullname>.sav", and try to open it. */ + confirm(xl > sizeof(CONF_BACKUP_EXT)) ; + sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ; -#ifdef VTYSH_DEBUG - printf ("line: %.*s\n", nbytes, buf); -#endif /* VTYSH_DEBUG */ + sav = -1 ; + if (stat (tmp_path, &buf) != -1) + sav = open (tmp_path, O_RDONLY); - for (p = buf; p < buf+nbytes; p++) + if (sav < 0) { - vty_ensure(vty, vty->length+1); - vty->buf[vty->length++] = *p; - if (*p == '\0') - { - /* Pass this line to parser. */ - ret = vty_execute (vty); - /* Note that vty_execute clears the command buffer and resets - vty->length to 0. */ - - /* Return result. */ -#ifdef VTYSH_DEBUG - printf ("result: %d\n", ret); - printf ("vtysh node: %d\n", vty->node); -#endif /* VTYSH_DEBUG */ - - header[3] = ret; - buffer_put(vty->obuf, header, 4); - - if (!vty->t_write && (vtysh_flush(vty) < 0)) - /* Try to flush results; exit if a write error occurs. */ - return 0; - } - } - - vty_event (VTYSH_READ, sock, vty); - - return 0; -} - -/* Callback: qpthraeds. Write */ -static void -vtysh_write_r (qps_file qf, void* file_info) -{ - struct vty *vty = (struct vty *)file_info; - - LOCK - - qps_disable_modes(qf, qps_write_mbit); - vtysh_flush(vty); - - UNLOCK -} - -//* Callback: thraeds. Write */ -static int -vtysh_write (struct thread *thread) -{ - struct vty *vty = THREAD_ARG (thread); - - LOCK - - vty->t_write = NULL; - vtysh_flush(vty); - - UNLOCK - return 0; -} + free (tmp_path); + return NULL; + } ; -#endif /* VTYSH */ + /* construct a temporary file and copy "<fullpath.sav>" to it. */ + confirm(xl > sizeof(".XXXXXX")) + sprintf (tmp_path, "%s%s", fullpath, ".XXXXXX") ; -/* Determine address family to bind. */ -void -vty_serv_sock (const char *addr, unsigned short port, const char *path) -{ - LOCK - - /* If port is set to 0, do not listen on TCP/IP at all! */ - if (port) + /* Open file to configuration write. */ + tmp = mkstemp (tmp_path); + if (tmp < 0) { - -#ifdef HAVE_IPV6 -#ifdef NRL - vty_serv_sock_family (addr, port, AF_INET); - vty_serv_sock_family (addr, port, AF_INET6); -#else /* ! NRL */ - vty_serv_sock_addrinfo (addr, port); -#endif /* NRL*/ -#else /* ! HAVE_IPV6 */ - vty_serv_sock_family (addr,port, AF_INET); -#endif /* HAVE_IPV6 */ + free (tmp_path); + close(sav); + return NULL; } -#ifdef VTYSH - vty_serv_un (path); -#endif /* VTYSH */ - - UNLOCK -} + while((c = read (sav, buffer, sizeof(buffer))) > 0) + write (tmp, buffer, c); -/* Close vty interface. Warning: call this only from functions that - will be careful not to access the vty afterwards (since it has - now been freed). This is safest from top-level functions (called - directly by the thread dispatcher). */ -void -vty_close (struct vty *vty) -{ - LOCK - uty_close(vty); - UNLOCK -} + close (sav); + close (tmp); -static void -uty_close (struct vty *vty) -{ - int i; - - ASSERTLOCKED - - /* Cancel threads.*/ - if (vty->t_read) - thread_cancel (vty->t_read); - if (vty->t_write) - thread_cancel (vty->t_write); - if (vty->t_timeout) - thread_cancel (vty->t_timeout); - if (vty->qf) - { - qps_remove_file(vty->qf); - qps_file_free(vty->qf); - vty->qf = NULL; - } - if (vty->qtr) + /* Make sure that have the required file status */ + if (chmod(tmp_path, CONFIGFILE_MASK) != 0) { - qtimer_free(vty->qtr); - vty->qtr = NULL; + unlink (tmp_path); + free (tmp_path); + return NULL; } - /* Flush buffer. */ - buffer_flush_all (vty->obuf, vty->fd); - - /* Free input buffer. */ - buffer_free (vty->obuf); - - /* Free command history. */ - for (i = 0; i < VTY_MAXHIST; i++) - if (vty->hist[i]) - XFREE (MTYPE_VTY_HIST, vty->hist[i]); - - /* Unset vector. */ - vector_unset (vtyvec, vty->fd); - - /* Close socket. */ - if (vty->fd > 0) - close (vty->fd); - - if (vty->address) - XFREE (MTYPE_TMP, vty->address); - if (vty->buf) - XFREE (MTYPE_VTY, vty->buf); - - /* Check configure. */ - uty_config_unlock (vty); - - /* OK free vty. */ - XFREE (MTYPE_VTY, vty); -} - -/* Callback: qpthreads. When time out occur output message then close connection. */ -static void -vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when) -{ - struct vty *vty = (struct vty *)timer_info; - LOCK - qtimer_unset(qtr); - uty_timeout(vty); - UNLOCK -} - -/* Callback: threads. When time out occur output message then close connection. */ -static int -vty_timeout (struct thread *thread) -{ - int result; - struct vty *vty = THREAD_ARG (thread); - LOCK - vty->t_timeout = NULL; - result = uty_timeout(vty); - UNLOCK - return result; -} - -static int -uty_timeout (struct vty *vty) -{ - vty->v_timeout = 0; - - /* Clear buffer*/ - buffer_reset (vty->obuf); - uty_out (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + /* Make <fullpath> be a name for the new file just created. */ + ret = link (tmp_path, fullpath) ; - /* Close connection. */ - vty->status = VTY_CLOSE; - uty_close (vty); + /* Discard the temporary, now */ + unlink (tmp_path) ; + free (tmp_path) ; - return 0; -} + /* If link was successful, try to open -- otherwise, failed. */ + return (ret == 0) ? fopen (fullpath, "r") : NULL ; +} ; -/* Read up configuration file from file_name. */ +/*------------------------------------------------------------------------------ + * Read the given configuration file. + * + * May have a function to call after the first actual command is processed. + * This mechanism supports the setting of qpthreads-ness by configuration file + * command. + */ static void vty_read_file (FILE *confp, void (*after_first_cmd)(void)) { int ret; struct vty *vty; + /* TODO: sort out what VTY Type should use for reading config file */ vty = vty_new (0, VTY_TERM); /* stdout */ vty->node = CONFIG_NODE; - /* Execute configuration file */ - ret = config_from_file (vty, confp, after_first_cmd); + /* Make sure we have a suitable buffer, and set vty->buf to point at + * it -- same like other command execution. + */ + qs_need(&vty->vio->clx, VTY_BUFSIZ) ; + vty->buf = qs_chars(&vty->vio->clx) ; + + /* Execute configuration file */ + ret = config_from_file (vty, confp, after_first_cmd, &vty->vio->clx) ; - LOCK + VTY_LOCK() ; if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) ) { @@ -2675,219 +1031,13 @@ vty_read_file (FILE *confp, void (*after_first_cmd)(void)) fprintf (stderr, "There is no such command.\n"); break; } - fprintf (stderr, "Error occured during reading below line.\n%s\n", - vty->buf); - uty_close (vty); - exit (1); - } - - uty_close (vty); - UNLOCK -} - -static FILE * -vty_use_backup_config (char *fullpath) -{ - char *fullpath_sav, *fullpath_tmp; - FILE *ret = NULL; - struct stat buf; - int tmp, sav; - int c; - char buffer[512]; - - fullpath_sav = malloc (strlen (fullpath) + strlen (CONF_BACKUP_EXT) + 1); - strcpy (fullpath_sav, fullpath); - strcat (fullpath_sav, CONF_BACKUP_EXT); - if (stat (fullpath_sav, &buf) == -1) - { - free (fullpath_sav); - return NULL; - } - - fullpath_tmp = malloc (strlen (fullpath) + 8); - sprintf (fullpath_tmp, "%s.XXXXXX", fullpath); - - /* Open file to configuration write. */ - tmp = mkstemp (fullpath_tmp); - if (tmp < 0) - { - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - sav = open (fullpath_sav, O_RDONLY); - if (sav < 0) - { - unlink (fullpath_tmp); - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - while((c = read (sav, buffer, 512)) > 0) - write (tmp, buffer, c); - - close (sav); - close (tmp); - - if (chmod(fullpath_tmp, CONFIGFILE_MASK) != 0) - { - unlink (fullpath_tmp); - free (fullpath_sav); - free (fullpath_tmp); - return NULL; - } - - if (link (fullpath_tmp, fullpath) == 0) - ret = fopen (fullpath, "r"); - - unlink (fullpath_tmp); - - free (fullpath_sav); - free (fullpath_tmp); - return ret; -} - -/* Read up configuration file from file_name. */ -void -vty_read_config (char *config_file, - char *config_default_dir) -{ - vty_read_config_first_cmd_special(config_file, config_default_dir, NULL); -} - -/* Read up configuration file from file_name. - * callback after first command */ -void -vty_read_config_first_cmd_special(char *config_file, - char *config_default_dir, void (*after_first_cmd)(void)) -{ - char cwd[MAXPATHLEN]; - FILE *confp = NULL; - char *fullpath; - char *tmp = NULL; - - /* If -f flag specified. */ - if (config_file != NULL) - { - if (! IS_DIRECTORY_SEP (config_file[0])) - { - getcwd (cwd, MAXPATHLEN); - tmp = XMALLOC (MTYPE_TMP, - strlen (cwd) + strlen (config_file) + 2); - sprintf (tmp, "%s/%s", cwd, config_file); - fullpath = tmp; - } - else - fullpath = config_file; - - confp = fopen (fullpath, "r"); - - if (confp == NULL) - { - fprintf (stderr, "%s: failed to open configuration file %s: %s\n", - __func__, fullpath, safe_strerror (errno)); - - confp = vty_use_backup_config (fullpath); - if (confp) - fprintf (stderr, "WARNING: using backup configuration file!\n"); - else - { - fprintf (stderr, "can't open configuration file [%s]\n", - config_file); - exit(1); - } - } - } - else - { -#ifdef VTYSH - int ret; - struct stat conf_stat; - - /* !!!!PLEASE LEAVE!!!! - * This is NEEDED for use with vtysh -b, or else you can get - * a real configuration food fight with a lot garbage in the - * merged configuration file it creates coming from the per - * daemon configuration files. This also allows the daemons - * to start if there default configuration file is not - * present or ignore them, as needed when using vtysh -b to - * configure the daemons at boot - MAG - */ - - /* Stat for vtysh Zebra.conf, if found startup and wait for - * boot configuration - */ + fprintf (stderr, "Error occurred while processing:\n%s\n", vty->buf); - if ( strstr(config_default_dir, "vtysh") == NULL) - { - ret = stat (integrate_default, &conf_stat); - if (ret >= 0) - return; - } -#endif /* VTYSH */ - - confp = fopen (config_default_dir, "r"); - if (confp == NULL) - { - fprintf (stderr, "%s: failed to open configuration file %s: %s\n", - __func__, config_default_dir, safe_strerror (errno)); - - confp = vty_use_backup_config (config_default_dir); - if (confp) - { - fprintf (stderr, "WARNING: using backup configuration file!\n"); - fullpath = config_default_dir; - } - else - { - fprintf (stderr, "can't open configuration file [%s]\n", - config_default_dir); - exit (1); - } - } - else - fullpath = config_default_dir; + exit (1); } - vty_read_file (confp, after_first_cmd); - - fclose (confp); - -#ifdef QDEBUG - fprintf(stderr, "Reading config file: %s\n", fullpath); -#endif - host_config_set (fullpath); -#ifdef QDEBUG - fprintf(stderr, "Finished reading config file\n"); -#endif - - if (tmp) - XFREE (MTYPE_TMP, fullpath); -} - -/* Small utility function which output log to the VTY. */ -void -vty_log (const char *level, const char *proto_str, - const char *format, struct timestamp_control *ctl, va_list va) -{ - unsigned int i; - struct vty *vty; - - ASSERTLOCKED - - if (!vtyvec) - return; - - for (i = 0; i < vector_active (vtyvec); i++) - if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor) - { - va_list ac; - va_copy(ac, va); - vty_log_out (vty, level, proto_str, format, ctl, ac); - va_end(ac); - } + uty_half_close (vty->vio); + VTY_UNLOCK() ; } #ifdef QDEBUG @@ -2898,238 +1048,120 @@ vty_goodbye (void) unsigned int i; struct vty *vty; - LOCK + VTY_LOCK() ; if (vtyvec) { for (i = 0; i < vector_active (vtyvec); i++) { - if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->type == VTY_TERM) + if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->vio->type == VTY_TERM) { - uty_out(vty, QUAGGA_PROGNAME " is shutting down%s", VTY_NEWLINE); + uty_cout(vty, QUAGGA_PROGNAME " is shutting down%s", VTY_NEWLINE); /* Wake up */ - if (cli_nexus) - vty_event (VTY_WRITE, vty->fd, vty); + if (vty_cli_nexus) + vty_event (VTY_WRITE, vty->vio->fd, vty); } } if (qpthreads_enabled) - qpt_thread_signal(cli_nexus->thread_id, SIGMQUEUE); + qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); } - UNLOCK + VTY_UNLOCK() ; } #endif -/* Async-signal-safe version of vty_log for fixed strings. */ -void -vty_log_fixed (const char *buf, size_t len) -{ - unsigned int i; - struct iovec iov[2]; - - /* vty may not have been initialised */ - if (!vtyvec) - return; +/*============================================================================== + * Configuration node/state handling + * + * At most one VTY may hold the configuration symbol of power at any time. + */ - iov[0].iov_base = miyagi(buf) ; - iov[0].iov_len = len; - iov[1].iov_base = miyagi("\r\n") ; - iov[1].iov_len = 2; +/*------------------------------------------------------------------------------ + * Attempt to gain the configuration symbol of power + * + * If succeeds, set the given node. + * + * Returns: true <=> now own the symbol of power. + */ +extern bool +vty_config_lock (struct vty *vty, enum node_type node) +{ + bool result; - for (i = 0; i < vector_active (vtyvec); i++) - { - struct vty *vty; - if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor) - /* N.B. We don't care about the return code, since process is - most likely just about to die anyway. */ - writev(vty->fd, iov, 2); - } -} + VTY_LOCK() ; -int -vty_config_lock (struct vty *vty) -{ - int result; - LOCK if (vty_config == 0) { - vty->config = 1; - vty_config = 1; - } - result = vty->config; - UNLOCK - return result; -} + vty->vio->config = 1 ; + vty_config = 1 ; + } ; + + result = vty->vio->config; + + if (result) + vty->node = node ; + + VTY_UNLOCK() ; -int -vty_config_unlock (struct vty *vty) -{ - int result; - LOCK - result = uty_config_unlock(vty); - UNLOCK return result; } -static int -uty_config_unlock (struct vty *vty) +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +vty_config_unlock (struct vty *vty, enum node_type node) { - ASSERTLOCKED - if (vty_config == 1 && vty->config == 1) - { - vty->config = 0; - vty_config = 0; - } - return vty->config; + VTY_LOCK() ; + uty_config_unlock(vty, node); + VTY_UNLOCK() ; } -static void -vty_event (enum event event, int sock, struct vty *vty) +/*------------------------------------------------------------------------------ + * Give back the configuration symbol of power -- if own it. + * + * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + */ +extern void +uty_config_unlock (struct vty *vty, enum node_type node) { - if (cli_nexus) - vty_event_r(event, sock, vty); - else - vty_event_t(event, sock, vty); -} - -/* thread event setter */ -static void -vty_event_t (enum event event, int sock, struct vty *vty) - { - struct thread *vty_serv_thread; - - ASSERTLOCKED - - switch (event) + VTY_ASSERT_LOCKED() ; + if ((vty_config == 1) && (vty->vio->config == 1)) { - case VTY_SERV: - vty_serv_thread = thread_add_read (master, vty_accept, vty, sock); - vector_set_index (Vvty_serv_thread, sock, vty_serv_thread); - break; -#ifdef VTYSH - case VTYSH_SERV: - thread_add_read (master, vtysh_accept, vty, sock); - break; - case VTYSH_READ: - vty->t_read = thread_add_read (master, vtysh_read, vty, sock); - break; - case VTYSH_WRITE: - vty->t_write = thread_add_write (master, vtysh_write, vty, sock); - break; -#endif /* VTYSH */ - case VTY_READ: - vty->t_read = thread_add_read (master, vty_read, vty, sock); - - /* Time out treatment. */ - if (vty->v_timeout) - { - if (vty->t_timeout) - thread_cancel (vty->t_timeout); - vty->t_timeout = - thread_add_timer (master, vty_timeout, vty, vty->v_timeout); - } - break; - case VTY_WRITE: - if (! vty->t_write) - vty->t_write = thread_add_write (master, vty_flush, vty, sock); - break; - case VTY_TIMEOUT_RESET: - if (vty->t_timeout) - { - thread_cancel (vty->t_timeout); - vty->t_timeout = NULL; - } - if (vty->v_timeout) - { - vty->t_timeout = - thread_add_timer (master, vty_timeout, vty, vty->v_timeout); - } - break; + vty->vio->config = 0; + vty_config = 0; } -} - -/* qpthreads event setter */ -static void -vty_event_r (enum event event, int sock, struct vty *vty) - { - qps_file accept_file = NULL; - - ASSERTLOCKED - - switch (event) - { - case VTY_SERV: - accept_file = vector_get_item(Vvty_serv_thread, sock); - if (accept_file == NULL) - { - accept_file = qps_file_init_new(accept_file, NULL); - qps_add_file(cli_nexus->selection, accept_file, sock, NULL); - vector_set_index(Vvty_serv_thread, sock, accept_file); - } - qps_enable_mode(accept_file, qps_read_mnum, vty_accept_r) ; - break; -#ifdef VTYSH - case VTYSH_SERV: - accept_file = vector_get_item(Vvty_serv_thread, sock); - if (accept_file == NULL) - { - accept_file = qps_file_init_new(accept_file, NULL); - qps_add_file(master, accept_file, sock, NULL); - vector_set_index(Vvty_serv_thread, sock, accept_file); - } - qps_enable_mode(accept_file, qps_read_mnum, vtysh_accept_r) ; - break; - case VTYSH_READ: - qps_enable_mode(vty->file, qps_read_mnum, vtysh_read_r) ; - break; - case VTYSH_WRITE: - qps_enable_mode(vty->file, qps_write_mnum, vtysh_write_r) ; - break; -#endif /* VTYSH */ - case VTY_READ: - qps_enable_mode(vty->qf, qps_read_mnum, vty_read_r) ; - - /* Time out treatment. */ - if (vty->v_timeout) - { - qtimer_set(vty->qtr, qt_add_monotonic(QTIME(vty->v_timeout)), NULL) ; - } - break; - case VTY_WRITE: - qps_enable_mode(vty->qf, qps_write_mnum, vty_flush_r) ; - break; - case VTY_TIMEOUT_RESET: - if (vty->qtr == NULL) - break; - if (vty->v_timeout) - { - qtimer_set(vty->qtr, qt_add_monotonic(QTIME(vty->v_timeout)), NULL) ; - } - else - { - qtimer_unset(vty->qtr); - } - break; - } -} + assert(node <= MAX_NON_CONFIG_NODE) ; + vty->node = node ; +} ; +/*============================================================================== + * Commands + * + */ DEFUN_CALL (config_who, config_who_cmd, "who", "Display who is on vty\n") { - unsigned int i; - struct vty *v; + unsigned int i = 0; + vty_io vio ; - LOCK - for (i = 0; i < vector_active (vtyvec); i++) - if ((v = vector_slot (vtyvec, i)) != NULL) + VTY_LOCK() ; + + vio = vio_list_base ; + while (vio != NULL) /* TODO: show only VTY_TERM ??? */ + { uty_out (vty, "%svty[%d] connected from %s.%s", - v->config ? "*" : " ", - i, v->address, VTY_NEWLINE); - UNLOCK + vio->config ? "*" : " ", + i, uty_get_name(vio), VTY_NEWLINE); + vio = sdl_next(vio, vio_list) ; + } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3140,9 +1172,9 @@ DEFUN_CALL (line_vty, "Configure a terminal line\n" "Virtual terminal\n") { - LOCK + VTY_LOCK() ; vty->node = VTY_NODE; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3152,7 +1184,7 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { unsigned long timeout = 0; - LOCK + VTY_LOCK() ; /* min_str and sec_str are already checked by parser. So it must be all digit string. */ @@ -3165,10 +1197,10 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) timeout += strtol (sec_str, NULL, 10); vty_timeout_val = timeout; - vty->v_timeout = timeout; - vty_event (VTY_TIMEOUT_RESET, 0, vty); + vty->vio->file.v_timeout = timeout; +// vty_event (VTY_TIMEOUT_RESET, 0, vty); - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3207,14 +1239,14 @@ DEFUN_CALL (vty_access_class, "Filter connections based on an IP access list\n" "IP access list\n") { - LOCK + VTY_LOCK() ; if (vty_accesslist_name) XFREE(MTYPE_VTY, vty_accesslist_name); vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3228,7 +1260,7 @@ DEFUN_CALL (no_vty_access_class, { int result = CMD_SUCCESS; - LOCK + 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", @@ -3241,7 +1273,7 @@ DEFUN_CALL (no_vty_access_class, vty_accesslist_name = NULL; } - UNLOCK + VTY_UNLOCK() ; return result; } @@ -3254,13 +1286,13 @@ DEFUN_CALL (vty_ipv6_access_class, "Filter connections based on an IP access list\n" "IPv6 access list\n") { - LOCK + VTY_LOCK() ; if (vty_ipv6_accesslist_name) XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3275,7 +1307,7 @@ DEFUN_CALL (no_vty_ipv6_access_class, { int result = CMD_SUCCESS; - LOCK + VTY_LOCK() ; if (! vty_ipv6_accesslist_name || (argc && strcmp(vty_ipv6_accesslist_name, argv[0]))) @@ -3291,7 +1323,7 @@ DEFUN_CALL (no_vty_ipv6_access_class, vty_ipv6_accesslist_name = NULL; } - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } #endif /* HAVE_IPV6 */ @@ -3302,9 +1334,9 @@ DEFUN_CALL (vty_login, "login", "Enable password checking\n") { - LOCK + VTY_LOCK() ; no_password_check = 0; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3314,9 +1346,9 @@ DEFUN_CALL (no_vty_login, NO_STR "Enable password checking\n") { - LOCK + VTY_LOCK() ; no_password_check = 1; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3326,9 +1358,9 @@ DEFUN_CALL (vty_restricted_mode, "anonymous restricted", "Restrict view commands available in anonymous, unauthenticated vty\n") { - LOCK + VTY_LOCK() ; restricted_mode = 1; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3338,9 +1370,9 @@ DEFUN_CALL (vty_no_restricted_mode, NO_STR "Enable password checking\n") { - LOCK + VTY_LOCK() ; restricted_mode = 0; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3350,9 +1382,9 @@ DEFUN_CALL (service_advanced_vty, "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { - LOCK + VTY_LOCK() ; host.advanced = 1; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3363,9 +1395,9 @@ DEFUN_CALL (no_service_advanced_vty, "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { - LOCK + VTY_LOCK() ; host.advanced = 0; - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3375,9 +1407,9 @@ DEFUN_CALL (terminal_monitor, "Set terminal line parameters\n" "Copy debug output to the current terminal line\n") { - LOCK - vty->monitor = 1; - UNLOCK + VTY_LOCK() ; + uty_set_monitor(vty->vio, true); + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3388,9 +1420,9 @@ DEFUN_CALL (terminal_no_monitor, NO_STR "Copy debug output to the current terminal line\n") { - LOCK - vty->monitor = 0; - UNLOCK + VTY_LOCK() ; + uty_set_monitor(vty->vio, false); + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -3409,27 +1441,34 @@ DEFUN_CALL (show_history, { int index; - LOCK + VTY_LOCK() ; - for (index = vty->hindex + 1; index != vty->hindex;) + for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) { + const char* line ; + if (index == VTY_MAXHIST) { index = 0; continue; } - if (vty->hist[index] != NULL) - uty_out (vty, " %s%s", vty->hist[index], VTY_NEWLINE); + line = vector_get_item(&vty->vio->hist, index) ; + if (line != NULL) + uty_out (vty, " %s%s", line, VTY_NEWLINE); index++; } - UNLOCK + VTY_UNLOCK() ; return CMD_SUCCESS; } -/* Display current configuration. */ +/*============================================================================== + * Output the current configuration + * + * Returns: CMD_SUCCESS + */ static int vty_config_write (struct vty *vty) { @@ -3466,76 +1505,16 @@ vty_config_write (struct vty *vty) return CMD_SUCCESS; } -struct cmd_node vty_node = -{ - VTY_NODE, - "%s(config-line)# ", - 1, -}; - -/* Reset all VTY status. */ -void -vty_reset () -{ - LOCK - uty_reset(); - UNLOCK -} - -void -uty_reset () -{ - unsigned int i; - struct vty *vty; - struct thread *vty_serv_thread; - qps_file qf; - - for (i = 0; i < vector_active (vtyvec); i++) - if ((vty = vector_slot (vtyvec, i)) != NULL) - { - buffer_reset (vty->obuf); - vty->status = VTY_CLOSE; - uty_close (vty); - } - - if (cli_nexus) - { - for (i = 0; i < vector_active (Vvty_serv_thread); i++) - if ((qf = vector_slot (Vvty_serv_thread, i)) != NULL) - { - qps_remove_file(qf); - qps_file_free(qf); - vector_slot (Vvty_serv_thread, i) = NULL; - close (i); - } - } - else - { - assert(master); - for (i = 0; i < vector_active (Vvty_serv_thread); i++) - if ((vty_serv_thread = vector_slot (Vvty_serv_thread, i)) != NULL) - { - thread_cancel (vty_serv_thread); - vector_slot (Vvty_serv_thread, i) = NULL; - close (i); - } - } - - vty_timeout_val = VTY_TIMEOUT_DEFAULT; - - if (vty_accesslist_name) - { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } - - if (vty_ipv6_accesslist_name) - { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = NULL; - } -} +/*============================================================================== + * The cwd at start-up. + */ +/*------------------------------------------------------------------------------ + * Save cwd + * + * This is done early in the morning so that any future operations on files + * can use the original cwd if required. + */ static void vty_save_cwd (void) { @@ -3550,140 +1529,107 @@ vty_save_cwd (void) getcwd (cwd, MAXPATHLEN); } - vty_cwd = XMALLOC (MTYPE_TMP, strlen (cwd) + 1); - strcpy (vty_cwd, cwd); -} + vty_cwd = XSTRDUP(MTYPE_TMP, cwd) ; +} ; +/*------------------------------------------------------------------------------ + * Get cwd as at start-up. Never changed -- so no locking required. + */ char * vty_get_cwd () { return vty_cwd; } +/*============================================================================== + * Access functions for VTY values, where locking is or might be required. + */ + int vty_shell (struct vty *vty) { - LOCK + VTY_LOCK() ; int result; - result = uty_shell (vty); - UNLOCK + result = (vty->vio->type == VTY_SHELL) ? 1 : 0 ; + VTY_UNLOCK() ; return result; } -static int -uty_shell (struct vty *vty) -{ - return ((vty == NULL) || (vty->type == VTY_SHELL)) ? 1 : 0; -} - int vty_shell_serv (struct vty *vty) { - LOCK + VTY_LOCK() ; int result; - result = ((vty->type == VTY_SHELL_SERV) ? 1 : 0); - UNLOCK + result = ((vty->vio->type == VTY_SHELL_SERV) ? 1 : 0); + VTY_UNLOCK() ; return result; } -void -vty_init_vtysh () -{ - LOCK - vtyvec = vector_init (0); - UNLOCK -} - int vty_get_node(struct vty *vty) { int result; - LOCK + VTY_LOCK() ; result = vty->node; - UNLOCK + VTY_UNLOCK() ; return result; } void vty_set_node(struct vty *vty, int node) { - LOCK + VTY_LOCK() ; vty->node = node; - UNLOCK + VTY_UNLOCK() ; } int vty_get_type(struct vty *vty) { int result; - LOCK - result = vty->type; - UNLOCK + VTY_LOCK() ; + result = vty->vio->type; + VTY_UNLOCK() ; return result; } int -vty_get_status(struct vty *vty) -{ - int result; - LOCK - result = vty->status; - UNLOCK - return result; -} - -void -vty_set_status(struct vty *vty, int status) -{ - LOCK - vty->status = status; - UNLOCK -} - -int vty_get_lines(struct vty *vty) { int result; - LOCK - result = vty->lines; - UNLOCK + VTY_LOCK() ; + result = vty->vio->lines; + VTY_UNLOCK() ; return result; } void vty_set_lines(struct vty *vty, int lines) { - LOCK - vty->lines = lines; - UNLOCK + VTY_LOCK() ; + vty->vio->lines = lines; + VTY_UNLOCK() ; } -/* qpthreads: Install vty's own commands like `who' command. */ -void -vty_init_r (qpn_nexus cli_n, qpn_nexus routing_n) -{ - cli_nexus = cli_n; - routing_nexus = routing_n; - qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); -} +/*============================================================================== + * The VTY command nodes + */ -/* threads: Install vty's own commands like `who' command. */ -void -vty_init (struct thread_master *master_thread) +struct cmd_node vty_node = { - LOCK - - /* For further configuration read, preserve current directory. */ - vty_save_cwd (); - - vtyvec = vector_init (0); - - master = master_thread; + VTY_NODE, + "%s(config-line)# ", + 1, +}; - /* Initilize server thread vector. */ - Vvty_serv_thread = vector_init (0); +/*------------------------------------------------------------------------------ + * Install vty's own commands like `who' command. + */ +static void +uty_init_commands (void) +{ + VTY_ASSERT_LOCKED() ; - /* Install bgp top node. */ install_node (&vty_node, vty_config_write); install_element (RESTRICTED_NODE, &config_who_cmd); @@ -3714,29 +1660,4 @@ vty_init (struct thread_master *master_thread) install_element (VTY_NODE, &vty_ipv6_access_class_cmd); install_element (VTY_NODE, &no_vty_ipv6_access_class_cmd); #endif /* HAVE_IPV6 */ - - UNLOCK -} - -void -vty_terminate (void) -{ - LOCK - - if (vty_cwd) - XFREE (MTYPE_TMP, vty_cwd); - - if (vtyvec && Vvty_serv_thread) - { - uty_reset (); - vector_free (vtyvec); - vector_free (Vvty_serv_thread); - } - UNLOCK - - qpt_mutex_destroy(&vty_mutex, 0); -} - -#undef LOCK -#undef UNLOCK -#undef ASSERTLOCKED +} ; diff --git a/lib/vty.c.x b/lib/vty.c.x new file mode 100644 index 00000000..bed6fc28 --- /dev/null +++ b/lib/vty.c.x @@ -0,0 +1,4414 @@ +/* + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> +#include "miyagi.h" + +#include "keystroke.h" +#include "vty_io.h" +#include "vty_cli.h" + +#include "linklist.h" +#include "thread.h" +#include "buffer.h" +#include <lib/version.h> +#include "command.h" +#include "sockunion.h" +#include "memory.h" +#include "str.h" +#include "log.h" +#include "prefix.h" +#include "filter.h" +#include "vty.h" +#include "privs.h" +#include "network.h" + +#include <arpa/telnet.h> +#include "qpthreads.h" +#include "qpnexus.h" + + + + +/* Needs to be qpthread safe */ +qpt_mutex_t vty_mutex; +#ifdef NDEBUG +#define LOCK qpt_mutex_lock(&vty_mutex); +#define UNLOCK qpt_mutex_unlock(&vty_mutex); +#else +int vty_lock_count = 0; +int vty_lock_asserted = 0; +#define LOCK qpt_mutex_lock(&vty_mutex);++vty_lock_count; +#define UNLOCK --vty_lock_count;qpt_mutex_unlock(&vty_mutex); +#define ASSERTLOCKED if(vty_lock_count==0 && !vty_lock_asserted){vty_lock_asserted=1;assert(0);} +#endif + +/*============================================================================== + * To make vty qpthread safe we use a single mutex. + * + * vty and log recurse through each other, so the same mutex is used + * for both, i.e. they are treated as being part of the same monitor. + * + * A recursive mutex is used. This simplifies the calling from log to vty and + * back again. It also allows for the vty internals to call each other. + * + * There are some "uty" functions which assume the mutex is locked. + * + * vty is closely bound to the command handling -- the main vty structure + * contains the context in which commands are parsed and executed. + */ + +/*------------------------------------------------------------------------------ + * + */ + +/*------------------------------------------------------------------------------ + * Prototypes + */ +static int uty_out (struct vty *vty, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +static int uty_vout(struct vty *vty, const char *format, va_list args); + +static int uty_vbuf(vty_io vio, const char *format, va_list args) ; +static void uty_cout (vty_io vio, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +static void uty_cwrite(vty_io vio, const char *this, int len) ; + +static void uty_cecho(vty_io vio, const char *this, size_t len) ; +static void uty_cecho_n(vty_io vio, const char *this, size_t len, int n) ; + +static void vty_event (enum vty_event, int, struct vty *); +static void uty_close (struct vty *vty); +static int uty_config_unlock (struct vty *vty); +static int uty_read (struct vty *vty); +static int uty_flush (struct vty *vty, int vty_sock); +static void vty_event_t (enum vty_event event, int sock, struct vty *vty); +static void vty_event_r (enum vty_event event, int sock, struct vty *vty); +static int uty_accept (int accept_sock); +static int uty_timeout (struct vty *vty); +static void vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when); +static void vty_read_r (qps_file qf, void* file_info); +static void vty_flush_r (qps_file qf, void* file_info); +void uty_reset (void); + +/* Extern host structure from command.c */ +extern struct host host; + +/*------------------------------------------------------------------------------ + * Static Variables + */ + +/* For thread handling need the thread_master -- initialised in vty_init */ +static struct thread_master *master = NULL ; + +/* In the qpthreads world, have nexus for the CLI and one for the Routeing + * Engine. Some commands are processed directly in the CLI, most have to be + * sent to the Routeing Engine. + */ +static qpn_nexus cli_nexus = NULL ; +static qpn_nexus routing_nexus = NULL ; + +/* List of all known vty */ +static struct vty* vty_known = NULL ; + +/* List of all vty which are in monitor state. */ +static struct vty* vty_monitors = NULL ; + +/* Vty timeout value -- see "exec timeout" command */ +static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; + +/* Vty access-class command */ +static char *vty_accesslist_name = NULL; + +/* Vty access-class for IPv6. */ +static char *vty_ipv6_accesslist_name = NULL; + +/* VTY server thread. */ +//static vector Vvty_serv_thread; + +/* Current directory -- initialised in vty_init() */ +static char *vty_cwd = NULL; + +/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */ +static int vty_config; + +/* Login password check override. */ +static int no_password_check = 0; + +/* Restrict unauthenticated logins? */ +static const u_char restricted_mode_default = 0; +static u_char restricted_mode = 0; + +/*------------------------------------------------------------------------------ + * Global Variables + */ + +/* Integrated configuration file path -- for VTYSH */ +char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; + +/*============================================================================== + * Interlock mechanism for command execution -- qpthreads version. + * + * The meaning and validity of a given command line depends on all previous + * command lines having been processed. + * + * Most commands are not executed in the CLI thread. So, an interlock is + * required so that a command is not dispatched until the previous one has + * been processed to completion. + * + * In the struct vty is the "node" value -- this is the current command context. + * There are a few other values which provide further context, but those are + * not required by the command line handling. + * + * The processing of commands proceeds as follows: + * + * (1) no command queued and no output buffered + * + * The command processor is idle, and waiting for the user to complete + * a new command. The screen will look like: + * + * <prompt>: user ..... input [] + * + * (where [] is the cursor). + * + * When the user hits ENTER, a newline is output, and the command is parsed + * according to the current "node". If there is a match, the command can be + * dispatched. Otherwise, may need to work up the "node" tree until get a + * match or run out of tree. + * + * If command parses successfully, proceed to: + * + * (2) if the command can be executed in the CLI thread (or if not + * running qpthreads-wise). + * + * (3) if the command must be sent to the routeing engine for execution. + * + * If fails to parse the command, then an error message will be output and + * buffered. There is now output pending -- proceed to (4). + * + * (2) command can be executed in the CLI thread. + * + * This is always the case if not running qpthreads-wise. + * + * The command is executed immediately. + * + * During execution the command may generate some output. All that output + * is buffered until the command completes. + * + * No input will be processed while the command is being executed. So there + * will be no partial command in hand. + * + * (3) command must be sent to the routing engine. + * + * The command is dispatched to the routing engine, and there is now a + * "command queued". + * + * While the command is executing, any output will be buffered. So the + * command line processor is free to use the console and the user + * may enter a further command. + * + * NB: If any context sensitive help is requested, it will be given in the + * current known context -- which may NOT be the actual context when the + * queued command completes. + * + * If the queued command completes before the user hits ENTER, the command + * line will be wiped out, and proceeds to (4) with the partial command + * in hand. + * + * If the queued command does not complete before the user hits enter, + * then stops processing input until the queued command does complete, and + * then proceeds to (4) with the command in hand, and the ENTER pending + * + * (4) command completes (possibly because did not parse !). + * + * Any buffered output is now actually output. No input processing is + * done until all output has been sent. + * + * TODO: The " --More-- " handling ???? + * + * Once the output completes, the current prompt is shown, followed by + * any partial command line. + * + * Loop back to (1). + * + * + */ + +/*============================================================================== + * General VTY output. + * + * This is mostly used during command execution, to output the results of the + * command. + * + * All these end up in uty_vout -- see below. + * + * For VTY_TERM and VTY_SHELL_SERV, all output is to the vty obuf. So, all + * output generated by a command is collected in the obuf, which is flushed + * to the terminal or to the shell when the command completes. This means + * that: + * + * TODO: what does this all mean ?? + * + * * the breaking up of ****** + * + * * other output (in particular "monitor" output) can go directly to the + * terminal (or shell ?). + * + * * + * + */ + +/*------------------------------------------------------------------------------ + * VTY output -- cf fprintf ! + */ +extern int +vty_out (struct vty *vty, const char *format, ...) +{ + int result; + + LOCK + va_list args; + va_start (args, format); + result = uty_vout(vty, format, args); + va_end (args); + UNLOCK + return result; +} + +/*------------------------------------------------------------------------------ + * VTY output -- output a given numnber of spaces + */ + +/* 1 2 3 4 */ +/* 1234567890123456789012345678901234567890 */ +const char vty_spaces_string[] = " " ; +CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ; + +extern int +vty_out_indent(struct vty *vty, int indent) +{ + while (indent > VTY_MAX_SPACES) + { + int ret = vty_out(vty, VTY_SPACES(indent)) ; + if (ret < 0) + return ret ; + indent -= VTY_MAX_SPACES ; + } + return vty_out(vty, VTY_SPACES(indent)) ; +} ; + +/*------------------------------------------------------------------------------ + * VTY output -- output the current time in standard form, to the second. + */ +extern void +vty_time_print (struct vty *vty, int cr) +{ + char buf [timestamp_buffer_len]; + + quagga_timestamp(0, buf, sizeof(buf)) ; + + if (cr) + vty_out (vty, "%s\n", buf); + else + vty_out (vty, "%s ", buf); + + return; +} + +/*------------------------------------------------------------------------------ + * Say hello to vty interface. + */ +void +vty_hello (struct vty *vty) +{ + LOCK + +#ifdef QDEBUG + uty_out (vty, "%s%s", debug_banner, VTY_NEWLINE); +#endif + if (host.motdfile) + { + FILE *f; + char buf[4096]; + + f = fopen (host.motdfile, "r"); + if (f) + { + while (fgets (buf, sizeof (buf), f)) + { + char *s; + /* work backwards to ignore trailing isspace() */ + for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); + s--); + *s = '\0'; + uty_out (vty, "%s%s", buf, VTY_NEWLINE); + } + fclose (f); + } + else + uty_out (vty, "MOTD file %s not found%s", host.motdfile, VTY_NEWLINE); + } + else if (host.motd) + uty_out (vty, "%s", host.motd); + + UNLOCK +} + + +/*============================================================================== + * 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. + * + * + */ + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf fprintf() + * + * No actual I/O takes place -- all "output" is to vio->cbuf and/or vio->cxbuf + */ +static void +uty_cout (vty_io vio, const char *format, ...) +{ + va_list args; + int len ; + + ASSERTLOCKED + + va_start (args, format); + len = uty_vbuf(vio, format, args) ; + va_end(args); + + if (len > 0) + uty_cwrite(vio, vio->vbuf, len) ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf write() + * + * No actual I/O takes place -- all "output" is to vio->cbuf and/or vio->cxbuf + */ +static void +uty_cwrite(vty_io vio, const char *this, int len) +{ + ASSERTLOCKED + + while (len > 0) + { + int take ; + take = vio->cbuf_end - vio->cbuf_ptr ; + + if (take == 0) + { + take = vty_cout_buffer_size ; + if (vio->cbuf == NULL) + { + vio->cbuf = XMALLOC(MTYPE_VTY_OUT_BUF, take) ; + vio->cbuf_end = vio->cbuf + vty_cout_buffer_size ; + } + else + { + assert((vio->cbuf_ptr - vio->cbuf) == take) ; + buffer_put(&vio->cxbuf, (u_char*)vio->cbuf, take) ; + } ; + vio->cbuf_ptr = vio->cbuf ; + } ; + + if (take > len) + take = len ; + + memcpy(vio->cbuf_ptr, this, take) ; + vio->cbuf_ptr += take ; + this += take ; + len -= take ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo user input + * + * Do nothing if echo suppressed (eg in AUTH_NODE) + * + * No actual I/O takes place -- all "output" is to vio->cbuf and/or vio->cxbuf + */ +static void +uty_cecho (vty_io vio, const char *this, size_t len) +{ + ASSERTLOCKED + + if (vio->cecho_suppress) + return ; + + uty_cwrite(vio, this, len) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo given stuff 'n' times + * + * Do nothing if echo suppressed (eg in AUTH_NODE) + * + * No actual I/O takes place -- all "output" is to vio->cbuf and/or vio->cxbuf + */ +static void +uty_cecho_n(vty_io vio, const char *this, size_t len, int n) +{ + ASSERTLOCKED + + if (vio->cecho_suppress) + return ; + + while (n-- > 0) + uty_cwrite(vio, this, len) ; +} + +/*============================================================================== + * Output to vty which are set to "monitor". + * + * + * + */ + +/*------------------------------------------------------------------------------ + * 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) +{ + struct vty *vty; + + ASSERTLOCKED + + vty = sdl_head(vty_monitors) ; + + if (vty == NULL) + return ; /* go no further if no "monitor" vtys */ + + /* Prepare line for output. */ + uvzlog_line(ll, zl, priority, format, va, 1) ; /* with crlf */ + + /* write to all known "monitor" vty */ + while (vty != NULL) + { + vty_io vio = vty->vio ; + + if ((vio != NULL) && vio->monitor) + { + vio->monitor = 0 ; + + if (write(vio->fd, ll->line, ll->len) < 0) + { +#if 0 // TODO: deal with error in write in uty_log() + + if (ERRNO_IO_RETRY(errno)) + /* Kernel buffer is full, probably too much debugging output, so just + drop the data and ignore. */ + return -1; + /* Fatal I/O error. */ + vty->vio->monitor = 0; /* disable monitoring to avoid infinite recursion */ + uzlog(NULL, LOG_WARNING, "%s: write failed to vty client fd %d, closing: %s", + __func__, vty->vio->fd, safe_strerror(errno)); + buffer_reset(vty->vio->obuf); + /* cannot call vty_close, because a parent routine may still try + to access the vty struct */ + vty->vio->status = VTY_CLOSE; + shutdown(vty->vio->fd, SHUT_RDWR); + return -1; +#endif + } + + vio->monitor = 1 ; + } ; + vty = sdl_next(vty, monitor_list) ; + } ; +} ; + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Allocate new vty struct + * + * Allocates and initialises vty_io structure, complete with: + * + * Output buffer + * Input buffer + * qpselect file -- added to CLI nexus ) if running CLI nexus + * qtimer ) + * + * Adds to the known vty's -- which locks/unlocks momentarily. + * + */ +struct vty * +vty_new (int fd, int type) +{ + struct vty *vty ; + struct vty_io* vio ; + + vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; + + vty->vio = vio ; + vio->vty = vty ; + + vio->obuf = buffer_new(0); /* Use default buffer size. */ + + // vio->cbuf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); + // vio->max = VTY_BUFSIZ; + // vio->fd = fd; + // vio->type = type; + + if (cli_nexus) + { + vio->qf = qps_file_init_new(vio->qf, NULL); + qps_add_file(cli_nexus->selection, vio->qf, vio->fd, vio); + vio->qtr = qtimer_init_new(vio->qtr, cli_nexus->pile, vty_timeout_r, vio); + } + + LOCK + sdl_push(vty_known, vty, vty_list) ; + UNLOCK + + return vty; +} + + + +/*============================================================================== + * VTY telnet stuff + */ + +#define TELNET_OPTION_DEBUG + +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_BRK ] = "BRK", + [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_Transmit_Binary] = "Binary", + [to_Echo ] = "Echo", + [to_Suppress_GA ] = "Suppress_GA", + [to_Status ] = "Status", + [to_Timing_Mark ] = "Timing_Mark", + [to_Terminal_Type ] = "Terminal_Type", + [to_Window_Size ] = "Window_Size", /* NAWS */ + [to_Terminal_Speed ] = "Terminal_Speed", + [to_Line_Mode ] = "Line_Mode", +} ; + +static const char* telnet_actions[2] = +{ + [ta_Ask ] = "Ask", + [ta_Value] = "Value", +} ; + +static void +uty_cout_dec(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cout(vio, "%s ", str) ; + else + uty_cout(vio, "%d ", (int)u) ; +} ; + +static void +uty_cout_hex(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cout(vio, "%s ", str) ; + else + uty_cout(vio, "0x%02x ", (unsigned)u) ; +} ; + +/*------------------------------------------------------------------------------ + * Send telnet: "WILL TELOPT_ECHO" + */ +static void +vty_will_echo (struct vty *vty) +{ + unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO }; + ASSERTLOCKED + uty_cwrite (vty->vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "suppress Go-Ahead" + */ +static void +vty_will_suppress_go_ahead (struct vty *vty) +{ + unsigned char cmd[] = { IAC, WILL, TELOPT_SGA }; + ASSERTLOCKED + uty_cwrite (vty->vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use linemode" + */ +static void +vty_dont_linemode (struct vty *vty) +{ + unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE }; + ASSERTLOCKED + uty_cwrite (vty->vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "Use window size" + */ +static void +vty_do_window_size (struct vty *vty) +{ + unsigned char cmd[] = { IAC, DO, TELOPT_NAWS }; + ASSERTLOCKED + uty_cwrite (vty->vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use lflow" + */ +#if 1 /* Currently not used. */ +static void +vty_dont_lflow_ahead (struct vty *vty) +{ + unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW }; + ASSERTLOCKED + uty_cwrite (vty->vio, (char*)cmd, (int)sizeof(cmd)); +} +#endif /* 0 */ + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * In particular: get telnet window size. + */ +static void +uty_telnet_command(vty_io vio, keystroke stroke) +{ + uint8_t* p ; + uint8_t o ; + int left ; + +#ifdef TELNET_OPTION_DEBUG + /* Echo to the other end if required */ + + p = stroke->buf ; + left = stroke->len ; + + uty_cout_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + + if (left-- > 0) + uty_cout_dec(vio, telnet_commands[*p], *p) ; + ++p ; + + if (left-- > 0) + uty_cout_dec(vio, telnet_options[*p], *p) ; + ++p ; + + if (left > 0) + { + while(left-- > 0) + uty_cout_hex(vio, NULL, *p++) ; + + if (stroke->flags & kf_truncated) + uty_cout(vio, "... ") ; + + if (!(stroke->flags & kf_broken)) + { + uty_cout_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + uty_cout_hex(vio, telnet_commands[tn_SE], tn_SE) ; + } + } ; + + if (!(stroke->flags & kf_broken)) + uty_cout (vio, "BROKEN") ; + + uty_cout (vio, "\r\n") ; + +#endif + + if (stroke->flags != 0) + return ; /* 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_Window_Size: + 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 ; + +#ifdef TELNET_OPTION_DEBUG + uty_cout(vio, "TELNET NAWS window size negotiation completed: " + "width %d, height %d%s", + vio->width, vio->height, TERM_NEWLINE) ; +#endif + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; + +} ; + +/******************************************************************************/ + + +static void uty_cl_ensure (vty_io vio, unsigned length) ; +static char* uty_cl_terminate(vty_io vio) ; +static int uty_cl_insert (vty_io vio, const char* chars, int n) ; +static int uty_cl_overwrite (vty_io vio, char* chars, int n) ; +static int uty_cl_word_overwrite (vty_io vio, char *str) ; +static int uty_cl_forwards(vty_io vio, int n) ; +static int uty_cl_backwards(vty_io vio, int n) ; +static int uty_cl_del_forwards(vty_io vio, int n) ; +static int uty_cl_del_backwards(vty_io vio, int n) ; +static int uty_cl_bol (vty_io vio) ; +static int uty_cl_eol (vty_io vio) ; +static int uty_cl_word_forwards_delta(vty_io vio) ; +static int uty_cl_word_forwards(vty_io vio) ; +static int uty_cl_word_backwards_delta(vty_io vio, int eat_spaces) ; +static int uty_cl_word_backwards_pure (vty_io vio) ; +static int uty_cl_word_backwards (vty_io vio) ; +static int uty_cl_del_word_forwards(vty_io vio) ; +static int uty_cl_del_word_backwards(vty_io vio) ; +static int uty_cl_del_to_eol (vty_io vio) ; +static int uty_cl_clear_line(vty_io vio) ; +static int uty_cl_transpose_chars(vty_io vio) ; +static void uty_cl_prompt (vty_io vio) ; +static int uty_cl_redraw_line (vty_io vio) ; +static int uty_cl_redraw(vty_io vio) ; +static void uty_cl_hist_add (vty_io vio) ; +static void uty_cl_history_use(vty_io vio, int step) ; +static void uty_cl_next_line(vty_io vio) ; +static void uty_cl_previous_line (vty_io vio) ; +static void uty_cl_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) ; +static void uty_cl_describe_command (vty_io vio) ; +static void uty_cl_complete_command (vty_io vio) ; + + +/*============================================================================== + * Command line processing loop + * + * 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_null -- no action required + * cli_dispatch -- CR or LF received + * cli_ctrl_c -- ^C received + * cli_ctrl_d -- ^D received, on empty line + * cli_ctrl_z -- ^Z received + * + * When returns the vio->cl is the state of the current command line. + * + */ + +#define CONTROL(X) ((X) - '@') + +static enum cli_returns +uty_cl_process(vty_io vio) +{ + struct keystroke stroke ; + uint8_t u ; + + /* Now process as much as possible of what there is */ + while (keystroke_get(vio->key_stream, &stroke)) + { + 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_cl_bol (vio); + break; + + case CONTROL('B'): + uty_cl_backwards(vio, 1); + break; + + case CONTROL('C'): + return cli_ctrl_c ; /* Exit on ^C ..................*/ + + case CONTROL('D'): + if (vio->cl.ep == 0) /* if at start of empty line */ + return cli_ctrl_d ; /* Exit on ^D ..................*/ + + uty_cl_del_forwards(vio, 1); + break; + + case CONTROL('E'): + uty_cl_eol (vio); + break; + + case CONTROL('F'): + uty_cl_forwards(vio, 1); + break; + + case CONTROL('H'): + case 0x7f: + uty_cl_del_backwards(vio, 1); + break; + + case CONTROL('K'): + uty_cl_del_to_eol (vio); + break; + + case CONTROL('N'): + uty_cl_next_line (vio); + break; + + case CONTROL('P'): + uty_cl_previous_line (vio); + break; + + case CONTROL('T'): + uty_cl_transpose_chars (vio); + break; + + case CONTROL('U'): + uty_cl_clear_line(vio); + break; + + case CONTROL('W'): + uty_cl_del_word_backwards (vio); + break; + + case CONTROL('Z'): + return cli_ctrl_z ; /* Exit on ^Z ..................*/ + break; + + case '\n': + case '\r': + return cli_dispatch ; /* Exit on CR or LF.............*/ + + case '\t': + uty_cl_complete_command (vio); + break; + + case '?': + if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) + uty_cl_insert (vio, (char*)&u, 1); + else + uty_cl_describe_command (vio); + break; + + default: + if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) + uty_cl_insert (vio, (char*)&u, 1) ; + break; + } + break ; + + /* ESC X -------------------------------------------------------------*/ + case ks_esc: + switch (stroke.value) + { + case 'b': + uty_cl_word_backwards (vio); + break; + + case 'f': + uty_cl_word_forwards (vio); + break; + + case 'd': + uty_cl_del_word_forwards (vio); + break; + + case CONTROL('H'): + case 0x7f: + uty_cl_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_cl_previous_line (vio); + break; + + case ('B'): + uty_cl_next_line (vio); + break; + + case ('C'): + uty_cl_forwards(vio, 1); + break; + + case ('D'): + uty_cl_backwards(vio, 1); + break; + default: + break ; + } ; + break ; + + /* Telnet Command ----------------------------------------------------*/ + case ks_iac: + uty_telnet_command(vio, &stroke) ; + break ; + + /* Single byte escape ------------------------------------------------*/ + default: + zabort("unknown keystroke type") ; + } ; + + /* After each keystroke..... */ + + + } ; + + + + /* Check status. */ + if (vio->status == VTY_CLOSE) + uty_close (vty); + else + { + vty_event (VTY_WRITE, vio->fd, vty); + vty_event (VTY_READ, vio->fd, vty); + } + + return 0; +} + + + + + + + + + + + +/*============================================================================== + * Command line operations + */ + +static const char telnet_backward_char = 0x08; +static const char telnet_space_char = ' '; + +/*------------------------------------------------------------------------------ + * Ensure length of command line buffer. + * + * Allocate or reallocate buffer so is large enough for the given length, PLUS + * one extra for '\0'. + */ +static void +uty_cl_ensure (vty_io vio, unsigned length) +{ + ASSERTLOCKED + + if (vio->cl.size <= length) /* allows for trailing '\n' */ + { + if (vio->cl.size == 0) + { + vio->cl.size = 200 ; + vio->cl.buf = XMALLOC(MTYPE_VTY, vio->cl.size) ; + } + else + { + vio->cl.size *= 2 ; + vio->cl.buf = XREALLOC (MTYPE_VTY, vio->cl.buf, vio->cl.size) ; + } ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Terminate the command line. + * + * Allocate or reallocate buffer so is large enough for the given length, plus + * one extra for '\0'. + */ +static char* +uty_cl_terminate(vty_io vio) +{ + ASSERTLOCKED + + uty_cl_ensure (vio, vio->cl.ep) ; + + vio->cl.buf[vio->cl.ep] = '\0' ; + + return vio->cl.buf ; +} ; + +/*------------------------------------------------------------------------------ + * Insert 'n' characters at current position in the command line + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cl_insert (vty_io vio, const char* chars, int n) +{ + int after ; + + ASSERTLOCKED + + assert((vio->cl.cp <= vio->cl.ep) && (n >= 0)) ; + + if (n == 0) + return n ; + + uty_cl_ensure (vio, vio->cl.ep + n) ; + + after = vio->cl.ep - vio->cl.cp ; + if (after != 0) + memmove (&vio->cl.buf[vio->cl.cp + n], &vio->cl.buf[vio->cl.cp], after) ; + + memmove(&vio->cl.buf[vio->cl.cp], chars, n) ; + + uty_cecho(vio, (char*)&vio->cl.buf[vio->cl.cp], after + n) ; + + if (after != 0) + uty_cecho_n(vio, &telnet_backward_char, 1, after) ; + + vio->cl.cp += n ; + vio->cl.ep += 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_cl_overwrite (vty_io vio, char* chars, int n) +{ + ASSERTLOCKED + + assert((vio->cl.cp <= vio->cl.ep) && (n >= 0)) ; + + if (n > 0) + { + if ((vio->cl.cp + n) > vio->cl.ep) + { + vio->cl.ep = vio->cl.cp + n ; + uty_cl_ensure (vio, vio->cl.ep) ; + } ; + + memmove(&vio->cl.buf[vio->cl.cp], chars, n) ; + uty_cecho(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_cl_word_overwrite (vty_io vio, char *str) +{ + int n ; + ASSERTLOCKED + + n = uty_cl_overwrite(vio, str, strlen(str)) ; + + vio->cl.ep = vio->cl.cp ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Forward 'n' characters -- stop at end of line. + * + * Returns number of characters actually moved + */ +static int +uty_cl_forwards(vty_io vio, int n) +{ + int c ; + ASSERTLOCKED + + c = vio->cl.ep - vio->cl.cp ; + if (n > c) + n = c ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cecho(vio, (char*)&vio->cl.buf[vio->cl.cp], n) ; + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Backwards 'n' characters -- stop at start of line. + * + * Returns number of characters actually moved + */ +static int +uty_cl_backwards(vty_io vio, int n) +{ + int c ; + ASSERTLOCKED + + c = vio->cl.ep - vio->cl.cp ; + if (n > c) + n = c ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cecho(vio, &telnet_backward_char, 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_cl_del_forwards(vty_io vio, int n) +{ + int after ; + + ASSERTLOCKED + + after = (vio->cl.ep - vio->cl.cp) ; + assert((n >= 0) && (after >= 0)) ; + + if ((n == 0) || (after == 0)) + return 0 ; /* completion need here? */ + + if (n > after) + n = after ; + + after -= n ; + + if (after > 0) + { + memmove (&vio->cl.buf[vio->cl.cp], &vio->cl.buf[vio->cl.cp + n], after) ; + uty_cecho(vio, (char*)&vio->cl.buf[vio->cl.cp], after) ; + } ; + + uty_cecho_n(vio, &telnet_space_char, 1, n) ; + uty_cecho_n(vio, &telnet_backward_char, 1, after + n) ; + + vio->cl.ep -= n ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters before the point. + * + * Returns number of characters actually deleted. + */ +static int +uty_cl_del_backwards(vty_io vio, int n) +{ + return uty_cl_del_forwards(vio, uty_cl_backwards(vio, n)) ; +} + +/*------------------------------------------------------------------------------ + * Move to the beginning of the line. + * + * Returns number of characters moved over. + */ +static int +uty_cl_bol (vty_io vio) +{ + return uty_cl_backwards(vio, vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Move to the end of the line. + * + * Returns number of characters moved over + */ +static int +uty_cl_eol (vty_io vio) +{ + return uty_cl_forwards(vio, vio->cl.ep - 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_cl_word_forwards_delta(vty_io vio) +{ + unsigned tp = vio->cl.cp ; + + ASSERTLOCKED ; + + assert(vio->cl.cp <= vio->cl.ep) ; + + while ((tp < vio->cl.ep) && (vio->cl.buf[tp] != ' ')) + ++tp ; + + while ((tp < vio->cl.ep) && (vio->cl.buf[tp] == ' ')) + ++tp ; + + return tp - vio->cl.cp ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word -- move to start of next word. + * + * Moves past any non-spaces, then past any spaces. + */ +static int +uty_cl_word_forwards(vty_io vio) +{ + return uty_cl_forwards(vio, uty_cl_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_cl_word_backwards_delta(vty_io vio, int eat_spaces) +{ + int tp = vio->cl.cp ; + + ASSERTLOCKED ; + + assert(vio->cl.cp <= vio->cl.ep) ; + + if (eat_spaces) + while ((tp > 0) && (vio->cl.buf[tp - 1] == ' ')) + --tp ; + + while ((tp > 0) && (vio->cl.buf[tp - 1] != ' ')) + --tp ; + + return vio->cl.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_cl_word_backwards_pure (vty_io vio) +{ + return uty_cl_backwards(vio, uty_cl_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_cl_word_backwards (vty_io vio) +{ + return uty_cl_backwards(vio, uty_cl_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_cl_del_word_forwards(vty_io vio) +{ + return uty_cl_del_forwards(vio, uty_cl_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_cl_del_word_backwards(vty_io vio) +{ + return uty_cl_del_backwards(vio, uty_cl_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill rest of line from current point. + * + * Returns number of characters deleted. + */ +static int +uty_cl_del_to_eol (vty_io vio) +{ + return uty_cl_del_forwards(vio, vio->cl.ep - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill line from the beginning. + * + * Returns number of characters deleted. + */ +static int +uty_cl_clear_line(vty_io vio) +{ + uty_cl_bol(vio) ; + return uty_cl_del_to_eol(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Transpose chars before or at the point. + * + * Return number of characters affected. + */ +static int +uty_cl_transpose_chars(vty_io vio) +{ + char chars[2] ; + + ASSERTLOCKED + + /* Give up if < 2 characters or at start of line. */ + if ((vio->cl.ep < 2) || (vio->cl.cp < 1)) + return 0 ; + + /* Move back to first of characters to exchange */ + if (vio->cl.cp == vio->cl.ep) + uty_cl_backwards(vio, 2) ; + else + uty_cl_backwards(vio, 1) ; + + /* Pick up in the new order */ + chars[0] = vio->cl.buf[vio->cl.cp + 1] ; + chars[1] = vio->cl.buf[vio->cl.cp + 0] ; + + /* And overwrite */ + return uty_cl_overwrite(vio, chars, 2) ; +} ; + +/*============================================================================== + * + */ + +/*------------------------------------------------------------------------------ + * If this is a VTY of type VTY_TERM, output the prompt. + * + * + * + * TODO: fix prompting.... + */ +static void +uty_cl_prompt (vty_io vio) +{ + struct utsname names; + const char* hostname; + const char* prompt ; + + ASSERTLOCKED + + if (vio->type == VTY_TERM) + { + hostname = host.name; + if (!hostname) + { + uname (&names); + hostname = names.nodename; + } + + prompt = cmd_prompt(vty->node) ; + if (prompt == NULL) + { + zlog_err("vty %s has node %d", vio->cbuf, vty->node) ; + prompt = "%s ???: " ; + } ; + + uty_cout (vio, prompt, hostname); + } +} + +/*------------------------------------------------------------------------------ + * Redraw entire command line, leaving current position at the end of the line + * + * Assumes is positioned at start of the command line (just after the prompt), + * and there is nothing that needs to be wiped out, first. + * + * Returns number of characters in line. + */ +static int +uty_cl_redraw_line (vty_io vio) +{ + ASSERTLOCKED + + if (vio->cl.ep != 0) + uty_cecho(vio, (char*)vio->cl.buf, vio->cl.ep) ; + + return (vio->cl.cp = vio->cl.ep) ; +} ; + +/*------------------------------------------------------------------------------ + * Redraw prompt and entire command line, leaving current position at the end + * of the line. + * + * Assumes is positioned at start of line, and there is nothing that needs to + * be wiped out, first. + * + * Returns number of characters in line. + */ +static int +uty_cl_redraw(vty_io vio) +{ + uty_cl_prompt(vio) ; + return uty_cl_redraw_line(vio) ; +} ; + +/*============================================================================== + * Command line history handling + */ + +/*------------------------------------------------------------------------------ + * Add current command line to the history buffer. + */ +static void +uty_cl_hist_add (vty_io vio) +{ + int index; + + ASSERTLOCKED + + /* Do nothing if current command line is empty */ + if (vio->cl.ep == 0) + return; + + /* Make sure line is terminated */ + uty_cl_terminate(vio) ; + + index = vio->hindex - 1 ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + + /* Ignore the same string as previous one. */ + if ((vio->hist[index] != NULL) && + (strcmp(vio->cl.buf, vio->hist[index]) == 0)) + { + vio->hp = vio->hindex ; + return; + } ; + + /* Insert history entry. */ + if (vio->hist[vio->hindex]) + XFREE (MTYPE_VTY_HIST, vio->hist[vio->hindex]); + vio->hist[vio->hindex] = XSTRDUP (MTYPE_VTY_HIST, vio->cl.buf) ; + + /* History index rotation. */ + 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. + */ +static void +uty_cl_history_use(vty_io vio, int step) +{ + int index ; + unsigned length ; + char* hist ; + + ASSERTLOCKED + + /* See if have anything usable */ + index = vio->hp ; + + if ((step > 0) && (index == vio->hindex)) + return ; /* cannot step forward past the insertion point */ + + index += step ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + else if (index >= VTY_MAXHIST) ; + index = 0 ; + + if ((step < 0) && (index == vio->hindex)) + return ; /* cannot step back to the insertion point */ + + hist = vio->hist[index] ; + if (hist == NULL) + return ; /* cannot step to unused entry */ + + vio->hp = index; + + /* Move back to the start of the current line */ + uty_cl_bol(vio) ; + + /* Get previous line from history buffer and echo that */ + length = strlen(hist) ; + uty_cl_ensure(vio, length) ; + + if (length != 0) + { + memcpy(vio->cl.buf, hist, length) ; + uty_cecho(vio, hist, length) ; + } ; + + /* Move "cursor" to end of the new line */ + vio->cl.cp = length; + + /* Set new end of of line -- clearing stuff if old line was longer */ + if (length < vio->cl.ep) + uty_cl_del_to_eol(vio) ; + else + vio->cl.ep = length ; + + return ; +} ; + +/*------------------------------------------------------------------------------ + * Use next history line, if any. + */ +static void +uty_cl_next_line(vty_io vio) +{ + uty_cl_history_use(vio, +1) ; +} + +/*------------------------------------------------------------------------------ + * Use previous history line, if any. + */ +static void +uty_cl_previous_line (vty_io vio) +{ + uty_cl_history_use(vio, -1) ; +} + +/*============================================================================== + * Command Description + * + */ + +/* NB: this is a console level operation */ +static void +uty_cl_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) +{ + char *buf; + const char *cmd, *p; + int pos; + + ASSERTLOCKED + + cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; + + if (desc_width <= 0) + { + uty_cout (vio, " %-*s %s%s", cmd_width, cmd, desc->str, TERM_NEWLINE); + return; + } + + buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + + for (p = desc->str; strlen (p) > desc_width; p += pos + 1) + { + for (pos = desc_width; pos > 0; pos--) + if (*(p + pos) == ' ') + break; + + if (pos == 0) + break; + + strncpy (buf, p, pos); + buf[pos] = '\0'; + uty_cout (vio, " %-*s %s%s", cmd_width, cmd, buf, TERM_NEWLINE); + + cmd = ""; + } + + uty_cout (vio, " %-*s %s%s", cmd_width, cmd, p, TERM_NEWLINE); + + XFREE (MTYPE_TMP, buf); +} + +/* Describe matched command function. */ +/* NB: this is a console level command */ +static void +uty_cl_describe_command (vty_io vio) +{ + int ret; + vector vline; + vector describe; + unsigned int i, width, desc_width; + struct desc *desc, *desc_cr = NULL; + + ASSERTLOCKED + + /* Construct vector of tokens from current command line */ + uty_cl_terminate(vio) ; + vline = cmd_make_strvec (vio->cl.buf); + + /* In case of '> ?' or line ending in ' ' + * + * Note that if there is a vector of tokens, then there is at least one + * token, so can guarantee that vio->cl.ep >= 1 ! + */ + if ((vline == NULL) || (isspace ((int) vio->cbuf[vio->cl.ep - 1]))) + vline = cmd_add_to_strvec(vline, "") ; + + describe = cmd_describe_command (vline, vio->vty->node, &ret); + + uty_cout (vio, TERM_NEWLINE); + + /* Ambiguous error. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cout (vio, "%% Ambiguous command.%s", TERM_NEWLINE); + goto out; + break; + case CMD_ERR_NO_MATCH: + uty_cout (vio, "%% There is no matched command.%s", TERM_NEWLINE); + goto out; + break; + } + + /* Get width of command string. */ + width = 0; + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + unsigned int len; + + if (desc->cmd[0] == '\0') + continue; + + len = strlen (desc->cmd); + if (desc->cmd[0] == '.') + len--; + + if (width < len) + width = len; + } + + /* Get width of description string. */ + desc_width = vio->width - (width + 6); + + /* Print out description. */ + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + if (desc->cmd[0] == '\0') + continue; + + if (strcmp (desc->cmd, command_cr) == 0) + { + desc_cr = desc; + continue; + } + + if (!desc->str) + uty_cout (vio, " %-s%s", + desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, + TERM_NEWLINE); + else if (desc_width >= strlen (desc->str)) + uty_cout (vio, " %-*s %s%s", width, + desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, + desc->str, TERM_NEWLINE); + else + uty_cl_describe_fold (vio, width, desc_width, desc); + +#if 0 + uty_cout (vio, " %-*s %s%s", width + desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, + desc->str ? desc->str : "", TERM_NEWLINE); +#endif /* 0 */ + } + + if ((desc = desc_cr)) + { + if (!desc->str) + uty_cout (vio, " %-s%s", + desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, + TERM_NEWLINE); + else if (desc_width >= strlen (desc->str)) + uty_cout (vio, " %-*s %s%s", width, + desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, + desc->str, TERM_NEWLINE); + else + uty_cl_describe_fold (vio, width, desc_width, desc); + } + +out: + cmd_free_strvec (vline); + if (describe) + vector_free (describe); + + uty_cl_redraw(vio); +} + +/*============================================================================== + * Command completion + * + * + */ +static void +uty_cl_complete_command (vty_io vio) +{ + enum node_type node ; + unsigned i ; + int ret ; + vector matched ; + vector vline ; + + ASSERTLOCKED + + node = vio->vty->node ; + + if (node == AUTH_NODE || node == AUTH_ENABLE_NODE) + return; + + /* Construct vector of tokens from current command line */ + uty_cl_terminate(vio) ; + vline = cmd_make_strvec (vio->cl.buf); + + /* In case of '> ?' or line ending in ' ' + * + * Note that if there is a vector of tokens, then there is at least one + * token, so can guarantee that vio->cl.ep >= 1 ! + */ + if ((vline == NULL) || (isspace ((int) vio->cl.buf[vio->cl.ep - 1]))) + vline = cmd_add_to_strvec(vline, "") ; + + /* Try and match the tokenised command line */ + matched = cmd_complete_command (vline, node, &ret); + + cmd_free_strvec (vline); + + uty_cout (vio, TERM_NEWLINE); + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cout (vio, "%% Ambiguous command.%s", TERM_NEWLINE); + uty_cl_redraw(vio); + break; + case CMD_ERR_NO_MATCH: + uty_cout (vio, "%% There is no matched command.%s", TERM_NEWLINE); + uty_cl_redraw(vio); + break; + case CMD_COMPLETE_FULL_MATCH: + uty_cl_redraw(vio); + uty_cl_word_backwards_pure (vio); + uty_cl_word_overwrite (vio, vector_get_item(matched, 0)); + uty_cl_insert(vio, " ", 1); + break; + case CMD_COMPLETE_MATCH: + uty_cl_redraw(vio); + uty_cl_word_backwards_pure (vio); + uty_cl_word_overwrite (vio, vector_get_item(matched, 0)); + break; + case CMD_COMPLETE_LIST_MATCH: + for (i = 0; i < vector_end(matched); i++) + { + if (i != 0 && ((i % 6) == 0)) + uty_cout (vio, "%s", TERM_NEWLINE); + uty_cout (vio, "%-10s ", (char*)vector_get_item(matched, i)); + } + uty_cout (vio, "%s", TERM_NEWLINE); + + uty_cl_redraw(vio); + break; + case CMD_ERR_NOTHING_TODO: + uty_cl_redraw(vio); + break; + default: + break; + } + + cmd_free_strvec(matched); +} ; + +/*============================================================================== + * + */ + +/* Authentication of vty */ +static void +vty_auth (struct vty *vty, char *buf) +{ + char *passwd = NULL; + enum node_type next_node = 0; + int fail; + char *crypt (const char *, const char *); + + ASSERTLOCKED + + switch (vty->node) + { + case AUTH_NODE: + if (host.encrypt) + passwd = host.password_encrypt; + else + passwd = host.password; + if (host.advanced) + next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + else + next_node = VIEW_NODE; + break; + case AUTH_ENABLE_NODE: + if (host.encrypt) + passwd = host.enable_encrypt; + else + passwd = host.enable; + next_node = ENABLE_NODE; + break; + } + + if (passwd) + { + if (host.encrypt) + fail = strcmp (crypt(buf, passwd), passwd); + else + fail = strcmp (buf, passwd); + } + else + fail = 1; + + if (! fail) + { + vty->vio->fail = 0; + vty->node = next_node; /* Success ! */ + } + else + { + vty->vio->fail++; + if (vty->vio->fail >= 3) + { + if (vty->node == AUTH_NODE) + { + uty_out (vty, "%% Bad passwords, too many failures!%s", VTY_NEWLINE); + vty->vio->status = VTY_CLOSE; + } + else + { + /* AUTH_ENABLE_NODE */ + vty->vio->fail = 0; + uty_out (vty, "%% Bad enable passwords, too many failures!%s", VTY_NEWLINE); + vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; + } + } + } +} + + +/* ^C stop current input and do not add command line to the history. */ +static void +vty_stop_input (struct vty *vty) +{ + ASSERTLOCKED + vty->vio->cp = vty->vio->length = 0; + vty_clear_buf (vty); + uty_cout (vty, "%s", VTY_NEWLINE); + + switch (vty->node) + { + case VIEW_NODE: + case ENABLE_NODE: + case RESTRICTED_NODE: + /* Nothing to do. */ + break; + case CONFIG_NODE: + case INTERFACE_NODE: + case ZEBRA_NODE: + case RIP_NODE: + case RIPNG_NODE: + case BGP_NODE: + case RMAP_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case KEYCHAIN_KEY_NODE: + case MASC_NODE: + case VTY_NODE: + uty_config_unlock (vty); + vty->node = ENABLE_NODE; + break; + default: + /* Unknown node, we have to ignore it. */ + break; + } + vty_prompt (vty); + + /* Set history pointer to the latest one. */ + vty->vio->hp = vty->vio->hindex; +} + + + +/* When '^D' is typed at the beginning of the line we move to the down + level. */ +static void +vty_down_level (struct vty *vty) +{ + ASSERTLOCKED + uty_out (vty, "%s", VTY_NEWLINE); + (*config_exit_cmd.func)(NULL, vty, 0, NULL); + vty_prompt (vty); /* TODO: should command action issue prompt ? */ + vty->vio->cp = 0; +} + +/* When '^Z' is received from vty, move down to the enable mode. */ +static void +vty_end_config (struct vty *vty) +{ + ASSERTLOCKED + uty_out (vty, "%s", VTY_NEWLINE); + + switch (vty->node) + { + case VIEW_NODE: + case ENABLE_NODE: + case RESTRICTED_NODE: + /* Nothing to do. */ + break; + case CONFIG_NODE: + case INTERFACE_NODE: + case ZEBRA_NODE: + case RIP_NODE: + case RIPNG_NODE: + case BGP_NODE: + case BGP_VPNV4_NODE: + case BGP_IPV4_NODE: + case BGP_IPV4M_NODE: + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: + case RMAP_NODE: + case OSPF_NODE: + case OSPF6_NODE: + case ISIS_NODE: + case KEYCHAIN_NODE: + case KEYCHAIN_KEY_NODE: + case MASC_NODE: + case VTY_NODE: + uty_config_unlock (vty); + vty->node = ENABLE_NODE; + break; + default: + /* Unknown node, we have to ignore it. */ + break; + } + + vty_prompt (vty); /* TODO: command action and prompt */ + vty->vio->cp = 0; +} + + +/*------------------------------------------------------------------------------ + * + */ +/* Command execution over the vty interface. */ +static int +vty_command (struct vty *vty, char *buf) +{ + int ret; + vector vline; + const char *protocolname; + + ASSERTLOCKED + + /* Split readline string up into the vector */ + vline = cmd_make_strvec (buf); + + if (vline == NULL) + return CMD_SUCCESS; + +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; + + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ + + UNLOCK + ret = cmd_execute_command (vline, vty, NULL, routing_nexus, cli_nexus, 0); + LOCK + + /* Get the name of the protocol if any */ + protocolname = uzlog_get_proto_name(NULL); + +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + if ((realtime = thread_consumed_time(&after, &before, &cputime)) > + CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, buf); + } +#endif /* CONSUMED_TIME_CHECK */ + + if (ret != CMD_SUCCESS) + switch (ret) + { + case CMD_WARNING: + if (vty->vio->type == VTY_FILE) + uty_out (vty, "Warning...%s", VTY_NEWLINE); + break; + case CMD_ERR_AMBIGUOUS: + uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + break; + case CMD_ERR_NO_MATCH: + uty_out (vty, "%% [%s] Unknown command: %s%s", protocolname, buf, VTY_NEWLINE); + break; + case CMD_ERR_INCOMPLETE: + uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); + break; + } + cmd_free_strvec (vline); + + return ret; +} + +/*------------------------------------------------------------------------------ + * + */ +/* queued command has completed */ +extern void +vty_queued_result(struct vty *vty, int result, int action) +{ + LOCK + + vty_prompt(vty); + + /* Wake up */ + if (cli_nexus) + { + vty_event (VTY_WRITE, vty->vio->fd, vty); + if (qpthreads_enabled) + qpt_thread_signal(cli_nexus->thread_id, SIGMQUEUE); + } + + UNLOCK +} + +/*------------------------------------------------------------------------------ + * Execute current command line. + * + * For qpthreads: this is called *only* if there is no command outstanding. So + * can execute the command, which may be dispatched to another thread, and + * there will then be a command outstanding. + */ +static int +uty_execute (struct vty *vty) +{ + int ret; + + ret = CMD_SUCCESS; + + switch (vty->node) + { + case AUTH_NODE: + case AUTH_ENABLE_NODE: + vty_auth (vty, vty->vio->cbuf); + break; + default: + ret = vty_command (vty, vty->vio->cbuf); + if (vty->vio->type == VTY_TERM) + vty_hist_add (vty); + break; + } + + if (ret == CMD_QUEUED) + vty->vio->interlock = vty_cQueued ; + + /* Clear command line buffer. */ + vty->vio->cp = vty->vio->length = 0; + vty_clear_buf (vty); + + if (vty->vio->status != VTY_CLOSE && ret != CMD_QUEUED) + vty_prompt (vty); + + return ret; +} + +/* Quit print out to the buffer. */ +static void +vty_buffer_reset (struct vty *vty) +{ + ASSERTLOCKED + buffer_reset (vty->vio->obuf); + vty_redraw (vty); +} + +/*============================================================================*/ + +/*------------------------------------------------------------------------------ + * Callback -- qpthreads: Read data via vty socket. + * + */ +static void +vty_read_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + assert(vty->vio->fd == vty_sock) ; + + /* is this necessary? */ + qps_disable_modes(qf, qps_read_mbit); + uty_read(vty); + + UNLOCK +} + +/*------------------------------------------------------------------------------ + * Callback -- threads: Read data via vty socket. + * + */ +static int +vty_read (struct thread *thread) +{ + int vty_sock = THREAD_FD (thread); + struct vty *vty = THREAD_ARG (thread); + int result ; + + LOCK + + assert(vty->vio->fd == vty_sock) ; + + vty->vio->t_read = NULL; + result = uty_read(vty); + + UNLOCK + return result; +} + +/*------------------------------------------------------------------------------ + * Callback: Read from the vty and execute command line stuff. + */ + +#define CONTROL(X) ((X) - '@') + +enum vty_escape_state +{ + VTY_ESCAPE_0 = 0, + VTY_ESCAPE_1 = 1, + VTY_ESCAPE_2 = 2 +} ; + +static int +uty_read (struct vty *vty) +{ + vty_io vio ; + int pending ; +//int i ; + int nbytes ; + unsigned char* ptr ; + unsigned char* end ; + + vio = vty->vio ; + + /* Deal with interlock if present -- for qpthreads only */ + if (vio->interlock & vty_cCompleted) + { + /* Is either at end of current input line, or at start of fresh line */ +// vty_wipe_input(vty) ; + vty_prompt(vty) ; +// vty_redraw_input(vty) ; + + vty->vio->interlock = 0 ; + } ; + + /* Read raw data from socket */ + ptr = vio->ibuf ; + end = ptr + vio->ibuf_has ; + + nbytes = vio->ibuf_size - vio->ibuf_has ; + if (nbytes > 0) + nbytes = read(vio->fd, end, nbytes) ; + + if (nbytes < 0) + { + if (!ERRNO_IO_RETRY(errno)) + { + vty->vio->monitor = 0; /* disable monitoring to avoid infinite recursion */ + uzlog(NULL, LOG_WARNING, "%s: read error on vty client fd %d, closing: %s", + __func__, vty->vio->fd, safe_strerror(errno)); + } + } + else + { + end += nbytes ; + if (ptr == end) /* buffer empty after read ! */ + { + /* EOF ! */ + + buffer_reset(vio->obuf); + vio->status = VTY_CLOSE; + } ; + } ; + + +#if 0 + + /* Deal with VTY_MORE */ + if (vio->status == VTY_MORE) + { + switch (u) + { + case CONTROL('C'): + case 'q': + case 'Q': + vty_buffer_reset (vty); + break; + + default: + break; + } + continue; + } +#endif + + + if (pending) + { + qps_disable_modes(vio->qf, qps_read_mbit); + vio->interlock |= vty_cPending ; + } ; + + + + + + + if (pending) + { + qps_disable_modes(vio->qf, qps_read_mbit); + vio->interlock |= vty_cPending ; + } ; + + /* Check status. */ + if (vio->status == VTY_CLOSE) + uty_close (vty); + else + { + vty_event (VTY_WRITE, vio->fd, vty); + vty_event (VTY_READ, vio->fd, vty); + } + + return 0; +} + +/* Callback: qpthreads. Flush buffer to the vty. */ +static void +vty_flush_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + qps_disable_modes(qf, qps_write_mbit); + + /* Temporary disable read thread. */ + if ((vty->vio->lines == 0)) + { + qps_disable_modes(qf, qps_read_mbit); + } + + uty_flush(vty, vty_sock); + + UNLOCK +} + +/* Callback: threads. Flush buffer to the vty. */ +static int +vty_flush (struct thread *thread) +{ + int vty_sock = THREAD_FD (thread); + struct vty *vty = THREAD_ARG (thread); + int result; + + LOCK + vty->vio->t_write = NULL; + + /* Temporary disable read thread. */ + if ((vty->vio->lines == 0) && vty->vio->t_read) + { + thread_cancel (vty->vio->t_read); + vty->vio->t_read = NULL; + } + result = uty_flush(vty, vty_sock); + + UNLOCK + return result; +} + +static int +uty_flush (struct vty *vty, int vty_sock) +{ + int erase; + buffer_status_t flushrc; + + /* Function execution continue. */ + erase = ((vty->vio->status == VTY_MORE || vty->vio->status == VTY_MORELINE)); + + /* N.B. if width is 0, that means we don't know the window size. */ + if ((vty->vio->lines == 0) || (vty->vio->width == 0)) + flushrc = buffer_flush_available(vty->vio->obuf, vty->vio->fd); + else if (vty->vio->status == VTY_MORELINE) + flushrc = buffer_flush_window(vty->vio->obuf, vty->vio->fd, vty->vio->width, + 1, erase, 0); + else + flushrc = buffer_flush_window(vty->vio->obuf, vty->vio->fd, vty->vio->width, + vty->vio->lines >= 0 ? vty->vio->lines : + vty->vio->height, + erase, 0); + switch (flushrc) + { + case BUFFER_ERROR: + vty->vio->monitor = 0; /* disable monitoring to avoid infinite recursion */ + uzlog(NULL, LOG_WARNING, "buffer_flush failed on vty client fd %d, closing", + vty->vio->fd); + buffer_reset(vty->vio->obuf); + uty_close(vty); + break; + case BUFFER_EMPTY: + if (vty->vio->status == VTY_CLOSE) + uty_close (vty); + else + { + vty->vio->status = VTY_NORMAL; + if (vty->vio->lines == 0) + vty_event (VTY_READ, vty_sock, vty); + } + break; + case BUFFER_PENDING: + /* There is more data waiting to be written. */ + vty->vio->status = VTY_MORE; + if (vty->vio->lines == 0) + vty_event (VTY_WRITE, vty_sock, vty); + break; + } + + return 0; +} + +/* Create new vty structure. */ +static struct vty * +vty_create (int vty_sock, union sockunion *su) +{ + struct vty *vty; + + ASSERTLOCKED + + /* Allocate new vty structure and set up default values. */ + vty = vty_new (vty_sock, VTY_TERM); + + + + vty->vio->address = sockunion_su2str (su); + 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; + vty->vio->fail = 0; + vty->vio->cp = 0; + vty_clear_buf (vty); + vty->vio->length = 0; + memset (vty->vio->hist, 0, sizeof (vty->vio->hist)); + vty->vio->hp = 0; + vty->vio->hindex = 0; +//vector_set_index (vtyvec, vty_sock, vty); + vty->vio->status = VTY_NORMAL; + vty->vio->v_timeout = vty_timeout_val; + if (host.lines >= 0) + vty->vio->lines = host.lines; + else + vty->vio->lines = -1; + vty->vio->iac = 0; + vty->vio->iac_sb_in_progress = 0; + vty->vio->sb_len = 0; + + if (! no_password_check) + { + /* Vty is not available if password isn't set. */ + if (host.password == NULL && host.password_encrypt == NULL) + { + uty_cout (vty, "Vty password is not set.%s", VTY_NEWLINE); + vty->vio->status = VTY_CLOSE; + uty_close (vty); + return NULL; + } + } + + /* Say hello to the world. */ + vty_hello (vty); + if (! no_password_check) + uty_cout (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + + /* Setting up terminal. */ + vty_will_echo (vty); + vty_will_suppress_go_ahead (vty); + + vty_dont_linemode (vty); + vty_do_window_size (vty); + /* vty_dont_lflow_ahead (vty); */ + + vty_prompt (vty); + + /* Add read/write thread. */ + vty_event (VTY_WRITE, vty_sock, vty); + vty_event (VTY_READ, vty_sock, vty); + + return vty; +} + +/* Callback: qpthreads. Accept connection from the network. */ +static void +vty_accept_r (qps_file qf, void* file_info) +{ + LOCK + + int accept_sock = qf->fd; + uty_accept(accept_sock); + + UNLOCK +} + +/* Callback: threads. Accept connection from the network. */ +static int +vty_accept (struct thread *thread) +{ + int result; + + LOCK + + int accept_sock = THREAD_FD (thread); + result = uty_accept(accept_sock); + + UNLOCK + return result; +} + +static int +uty_accept (int accept_sock) +{ + int vty_sock; + struct vty *vty; + union sockunion su; + int ret; + unsigned int on; + struct prefix *p = NULL; + struct access_list *acl = NULL; + char *bufp; + + ASSERTLOCKED + + /* We continue hearing vty socket. */ + vty_event (VTY_SERV, accept_sock, NULL); + + memset (&su, 0, sizeof (union sockunion)); + + /* We can handle IPv4 or IPv6 socket. */ + vty_sock = sockunion_accept (accept_sock, &su); + if (vty_sock < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", safe_strerror (errno)); + return -1; + } + set_nonblocking(vty_sock); + + p = sockunion2hostprefix (&su); + + /* VTY's accesslist apply. */ + if (p->family == AF_INET && vty_accesslist_name) + { + if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + { + char *buf; + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", + (buf = sockunion_su2str (&su))); + free (buf); + close (vty_sock); + + /* continue accepting connections */ + vty_event (VTY_SERV, accept_sock, NULL); + + prefix_free (p); + return 0; + } + } + +#ifdef HAVE_IPV6 + /* VTY's ipv6 accesslist apply. */ + if (p->family == AF_INET6 && vty_ipv6_accesslist_name) + { + if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && + (access_list_apply (acl, p) == FILTER_DENY)) + { + char *buf; + uzlog (NULL, LOG_INFO, "Vty connection refused from %s", + (buf = sockunion_su2str (&su))); + free (buf); + close (vty_sock); + + /* continue accepting connections */ + vty_event (VTY_SERV, accept_sock, NULL); + + prefix_free (p); + return 0; + } + } +#endif /* HAVE_IPV6 */ + + prefix_free (p); + + on = 1; + ret = setsockopt (vty_sock, IPPROTO_TCP, TCP_NODELAY, + (char *) &on, sizeof (on)); + if (ret < 0) + uzlog (NULL, LOG_INFO, "can't set sockopt to vty_sock : %s", + safe_strerror (errno)); + + uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", + (bufp = sockunion_su2str (&su)), vty_sock); + if (bufp) + XFREE (MTYPE_TMP, bufp); + + vty = vty_create (vty_sock, &su); + + return 0; +} + +#if defined(HAVE_IPV6) && !defined(NRL) +static void +vty_serv_sock_addrinfo (const char *hostname, unsigned short port) +{ + int ret; + struct addrinfo req; + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + int sock; + char port_str[BUFSIZ]; + + ASSERTLOCKED + + memset (&req, 0, sizeof (struct addrinfo)); + req.ai_flags = AI_PASSIVE; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + sprintf (port_str, "%d", port); + port_str[sizeof (port_str) - 1] = '\0'; + + ret = getaddrinfo (hostname, port_str, &req, &ainfo); + + if (ret != 0) + { + fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (ret)); + exit (1); + } + + ainfo_save = ainfo; + + do + { + if (ainfo->ai_family != AF_INET +#ifdef HAVE_IPV6 + && ainfo->ai_family != AF_INET6 +#endif /* HAVE_IPV6 */ + ) + continue; + + sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); + if (sock < 0) + continue; + + sockopt_reuseaddr (sock); + sockopt_reuseport (sock); + + /* set non-blocking */ + ret = set_nonblocking(sock); + if (ret < 0) + { + close (sock); /* Avoid sd leak. */ + continue; + } + + ret = bind (sock, ainfo->ai_addr, ainfo->ai_addrlen); + if (ret < 0) + { + close (sock); /* Avoid sd leak. */ + continue; + } + + ret = listen (sock, 3); + if (ret < 0) + { + close (sock); /* Avoid sd leak. */ + continue; + } + + vty_event (VTY_SERV, sock, NULL); + } + while ((ainfo = ainfo->ai_next) != NULL); + + freeaddrinfo (ainfo_save); +} +#endif /* HAVE_IPV6 && ! NRL */ + +#if defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) +/* Make vty server socket. */ +static void +vty_serv_sock_family (const char* addr, unsigned short port, int family) +{ + int ret; + union sockunion su; + int accept_sock; + void* naddr=NULL; + + ASSERTLOCKED + + memset (&su, 0, sizeof (union sockunion)); + su.sa.sa_family = family; + if(addr) + switch(family) + { + case AF_INET: + naddr=&su.sin.sin_addr; +#ifdef HAVE_IPV6 + case AF_INET6: + naddr=&su.sin6.sin6_addr; +#endif + } + + if(naddr) + switch(inet_pton(family,addr,naddr)) + { + case -1: + uzlog(NULL, LOG_ERR, "bad address %s",addr); + naddr=NULL; + break; + case 0: + uzlog(NULL, LOG_ERR, "error translating address %s: %s",addr,safe_strerror(errno)); + naddr=NULL; + } + + /* Make new socket. */ + accept_sock = sockunion_stream_socket (&su); + if (accept_sock < 0) + return; + + /* This is server, so reuse address. */ + sockopt_reuseaddr (accept_sock); + sockopt_reuseport (accept_sock); + + /* set non-blocking */ + ret = set_nonblocking(accept_sock); + if (ret < 0) + { + close (accept_sock); /* Avoid sd leak. */ + return; + } + + /* Bind socket to universal address and given port. */ + ret = sockunion_bind (accept_sock, &su, port, naddr); + if (ret < 0) + { + uzlog(NULL, LOG_WARNING, "can't bind socket"); + close (accept_sock); /* Avoid sd leak. */ + return; + } + + /* Listen socket under queue 3. */ + ret = listen (accept_sock, 3); + if (ret < 0) + { + uzlog (NULL, LOG_WARNING, "can't listen socket"); + close (accept_sock); /* Avoid sd leak. */ + return; + } + + /* Add vty server event. */ + vty_event (VTY_SERV, accept_sock, NULL); +} +#endif /* defined(HAVE_IPV6) && defined(NRL) || !defined(HAVE_IPV6) */ + +#ifdef VTYSH +/* For sockaddr_un. */ +#include <sys/un.h> + +/* VTY shell UNIX domain socket. */ +static void +vty_serv_un (const char *path) +{ + int ret; + int sock, len; + struct sockaddr_un serv; + mode_t old_mask; + struct zprivs_ids_t ids; + + ASSERTLOCKED + + /* First of all, unlink existing socket */ + unlink (path); + + /* Set umask */ + old_mask = umask (0007); + + /* Make UNIX domain socket. */ + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + { + uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", safe_strerror(errno)); + return; + } + + /* Make server socket. */ + memset (&serv, 0, sizeof (struct sockaddr_un)); + serv.sun_family = AF_UNIX; + strncpy (serv.sun_path, path, strlen (path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + len = serv.sun_len = SUN_LEN(&serv); +#else + len = sizeof (serv.sun_family) + strlen (serv.sun_path); +#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ + + ret = bind (sock, (struct sockaddr *) &serv, len); + if (ret < 0) + { + uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, safe_strerror(errno)); + close (sock); /* Avoid sd leak. */ + return; + } + + ret = listen (sock, 5); + if (ret < 0) + { + uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, safe_strerror(errno)); + close (sock); /* Avoid sd leak. */ + return; + } + + umask (old_mask); + + zprivs_get_ids(&ids); + + if (ids.gid_vty > 0) + { + /* set group of socket */ + if ( chown (path, -1, ids.gid_vty) ) + { + uzlog (NULL, LOG_ERR, "vty_serv_un: could chown socket, %s", + safe_strerror (errno) ); + } + } + + vty_event (VTYSH_SERV, sock, NULL); +} + +/* #define VTYSH_DEBUG 1 */ + +/* Callback: qpthreads. Accept connection */ +void int +vtysh_accept_r (qps_file qf, void* file_info) +{ + int accept_sock = qf->fd; + LOCK + utysh_accept (accept_sock); + UNLOCK +} + +/* Callback: threads. Accept connection */ +static int +vtysh_accept (struct thread *thread) +{ + int accept_sock = THREAD_FD (thread); + LOCK + result = utysh_accept (accept_sock); + UNLOCK + return result; +} + +static int +utysh_accept (int accept_sock) +{ + int sock; + int client_len; + struct sockaddr_un client; + struct vty *vty; + + ASSERTLOCKED + + vty_event (VTYSH_SERV, accept_sock, NULL); + + memset (&client, 0, sizeof (struct sockaddr_un)); + client_len = sizeof (struct sockaddr_un); + + sock = accept (accept_sock, (struct sockaddr *) &client, + (socklen_t *) &client_len); + + if (sock < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", safe_strerror (errno)); + return -1; + } + + if (set_nonblocking(sock) < 0) + { + uzlog (NULL, LOG_WARNING, "vtysh_accept: could not set vty socket %d to non-blocking," + " %s, closing", sock, safe_strerror (errno)); + close (sock); + return -1; + } + +#ifdef VTYSH_DEBUG + printf ("VTY shell accept\n"); +#endif /* VTYSH_DEBUG */ + + vty = vty_new (); + vty->vio->fd = sock; + vty->vio->type = VTY_SHELL_SERV; + vty->node = VIEW_NODE; + + vty_event (VTYSH_READ, sock, vty); + + return 0; +} + +static int +vtysh_flush(struct vty *vty) +{ + ASSERTLOCKED + + switch (buffer_flush_available(vty->vio->obuf, vty->vio->fd)) + { + case BUFFER_PENDING: + vty_event(VTYSH_WRITE, vty->vio->fd, vty); + break; + case BUFFER_ERROR: + vty->vio->monitor = 0; /* disable monitoring to avoid infinite recursion */ + uzlog(NULL, LOG_WARNING, "%s: write error to fd %d, closing", __func__, vty->vio->fd); + buffer_reset(vty->vio->obuf); + uty_close(vty); + return -1; + break; + case BUFFER_EMPTY: + break; + } + return 0; +} + +/* Callback: qpthreads., Read data via vty socket. */ +static void +vtysh_read_r (qps_file qf, void* file_info) +{ + int vty_sock = qf->fd; + struct vty *vty = (struct vty *)file_info; + + LOCK + + /* is this necessary? */ + qps_disable_modes(qf, qps_read_mbit); + utysh_read(vty, vty_soc); + + UNLOCK +} + +/* Callback: threads. Read data via vty socket. */ +static int +vtysh_read (struct thread *thread) +{ + int vty_sock = THREAD_FD (thread); + struct vty *vty = THREAD_ARG (thread); + int result; + + LOCK + + vty->vio->t_read = NULL; + result = uty_read(vty, vty_soc); + + UNLOCK + return result; +} + +static int +utysh_read (struct vty *vty, int sock) +{ + int ret; + int nbytes; + unsigned char buf[VTY_READ_BUFSIZ]; + unsigned char *p; + u_char header[4] = {0, 0, 0, 0}; + + if ((nbytes = read (sock, buf, VTY_READ_BUFSIZ)) <= 0) + { + if (nbytes < 0) + { + if (ERRNO_IO_RETRY(errno)) + { + vty_event (VTYSH_READ, sock, vty); + return 0; + } + vty->vio->monitor = 0; /* disable monitoring to avoid infinite recursion */ + uzlog(NULL, LOG_WARNING, "%s: read failed on vtysh client fd %d, closing: %s", + __func__, sock, safe_strerror(errno)); + } + buffer_reset(vty->vio->obuf); + uty_close (vty); +#ifdef VTYSH_DEBUG + printf ("close vtysh\n"); +#endif /* VTYSH_DEBUG */ + return 0; + } + +#ifdef VTYSH_DEBUG + printf ("line: %.*s\n", nbytes, buf); +#endif /* VTYSH_DEBUG */ + + for (p = buf; p < buf+nbytes; p++) + { + vty_ensure(vty, vty->vio->length+1); + vty->vio->cbuf[vty->vio->length++] = *p; + if (*p == '\0') + { + /* Pass this line to parser. */ + ret = vty_execute (vty); + /* Note that vty_execute clears the command buffer and resets + vty->vio->length to 0. */ + + /* Return result. */ +#ifdef VTYSH_DEBUG + printf ("result: %d\n", ret); + printf ("vtysh node: %d\n", vty->node); +#endif /* VTYSH_DEBUG */ + + header[3] = ret; + buffer_put(vty->vio->obuf, header, 4); + + if (!vty->vio->t_write && (vtysh_flush(vty) < 0)) + /* Try to flush results; exit if a write error occurs. */ + return 0; + } + } + + vty_event (VTYSH_READ, sock, vty); + + return 0; +} + +/* Callback: qpthraeds. Write */ +static void +vtysh_write_r (qps_file qf, void* file_info) +{ + struct vty *vty = (struct vty *)file_info; + + LOCK + + qps_disable_modes(qf, qps_write_mbit); + vtysh_flush(vty); + + UNLOCK +} + +//* Callback: thraeds. Write */ +static int +vtysh_write (struct thread *thread) +{ + struct vty *vty = THREAD_ARG (thread); + + LOCK + + vty->vio->t_write = NULL; + vtysh_flush(vty); + + UNLOCK + return 0; +} + +#endif /* VTYSH */ + +/* Determine address family to bind. */ +void +vty_serv_sock (const char *addr, unsigned short port, const char *path) +{ + LOCK + + /* If port is set to 0, do not listen on TCP/IP at all! */ + if (port) + { + +#ifdef HAVE_IPV6 +#ifdef NRL + vty_serv_sock_family (addr, port, AF_INET); + vty_serv_sock_family (addr, port, AF_INET6); +#else /* ! NRL */ + vty_serv_sock_addrinfo (addr, port); +#endif /* NRL*/ +#else /* ! HAVE_IPV6 */ + vty_serv_sock_family (addr,port, AF_INET); +#endif /* HAVE_IPV6 */ + } + +#ifdef VTYSH + vty_serv_un (path); +#endif /* VTYSH */ + + UNLOCK +} + +/* Close vty interface. Warning: call this only from functions that + will be careful not to access the vty afterwards (since it has + now been freed). This is safest from top-level functions (called + directly by the thread dispatcher). */ +void +vty_close (struct vty *vty) +{ + LOCK + uty_close(vty); + UNLOCK +} + +static void +uty_close (struct vty *vty) +{ + int i; + + ASSERTLOCKED + + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vty->vio->fd) ; + + /* Cancel threads.*/ + if (vty->vio->t_read) + thread_cancel (vty->vio->t_read); + if (vty->vio->t_write) + thread_cancel (vty->vio->t_write); + if (vty->vio->t_timeout) + thread_cancel (vty->vio->t_timeout); + if (vty->vio->qf) + { + qps_remove_file(vty->vio->qf); + qps_file_free(vty->vio->qf); + vty->vio->qf = NULL; + } + if (vty->vio->qtr) + { + qtimer_free(vty->vio->qtr); + vty->vio->qtr = NULL; + } + + /* Flush buffer. */ + buffer_flush_all (vty->vio->obuf, vty->vio->fd); + + /* Free input buffer. */ + buffer_free (vty->vio->obuf); + + /* Free command history. */ + for (i = 0; i < VTY_MAXHIST; i++) + if (vty->vio->hist[i]) + XFREE (MTYPE_VTY_HIST, vty->vio->hist[i]); + + /* Unset vector. */ +//vector_unset (vtyvec, vty->vio->fd); + + /* Close socket. */ + if (vty->vio->fd > 0) + close (vty->vio->fd); + + if (vty->vio->address) + XFREE (MTYPE_TMP, vty->vio->address); + if (vty->vio->cbuf) + XFREE (MTYPE_VTY, vty->vio->cbuf); + + /* Check configure. */ + uty_config_unlock (vty); + + /* OK free vty. */ +//XFREE (MTYPE_VTY, vty); +} + +/* Callback: qpthreads. When time out occur output message then close connection. */ +static void +vty_timeout_r (qtimer qtr, void* timer_info, qtime_t when) +{ + struct vty *vty = (struct vty *)timer_info; + LOCK + qtimer_unset(qtr); + uty_timeout(vty); + UNLOCK +} + +/* Callback: threads. When time out occur output message then close connection. */ +static int +vty_timeout (struct thread *thread) +{ + int result; + struct vty *vty = THREAD_ARG (thread); + LOCK + vty->vio->t_timeout = NULL; + result = uty_timeout(vty); + UNLOCK + return result; +} + +static int +uty_timeout (struct vty *vty) +{ + vty->vio->v_timeout = 0; + + /* Clear buffer*/ + buffer_reset (vty->vio->obuf); + uty_cout (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + + /* Close connection. */ + vty->vio->status = VTY_CLOSE; + uty_close (vty); + + return 0; +} + +/* Read up configuration file from file_name. */ +static void +vty_read_file (FILE *confp, void (*after_first_cmd)(void)) +{ + int ret; + struct vty *vty; + + vty = vty_new (0, VTY_TERM); /* stdout */ + vty->node = CONFIG_NODE; + + /* Execute configuration file */ + ret = config_from_file (vty, confp, after_first_cmd); + + LOCK + + if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) ) + { + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + fprintf (stderr, "Ambiguous command.\n"); + break; + case CMD_ERR_NO_MATCH: + fprintf (stderr, "There is no such command.\n"); + break; + } + fprintf (stderr, "Error occurred while processing:\n%s\n", vty->vio->cbuf); + uty_close (vty); + exit (1); + } + + uty_close (vty); + UNLOCK +} + +static FILE * +vty_use_backup_config (char *fullpath) +{ + char *fullpath_sav, *fullpath_tmp; + FILE *ret = NULL; + struct stat buf; + int tmp, sav; + int c; + char buffer[512]; + + fullpath_sav = malloc (strlen (fullpath) + strlen (CONF_BACKUP_EXT) + 1); + strcpy (fullpath_sav, fullpath); + strcat (fullpath_sav, CONF_BACKUP_EXT); + if (stat (fullpath_sav, &buf) == -1) + { + free (fullpath_sav); + return NULL; + } + + fullpath_tmp = malloc (strlen (fullpath) + 8); + sprintf (fullpath_tmp, "%s.XXXXXX", fullpath); + + /* Open file to configuration write. */ + tmp = mkstemp (fullpath_tmp); + if (tmp < 0) + { + free (fullpath_sav); + free (fullpath_tmp); + return NULL; + } + + sav = open (fullpath_sav, O_RDONLY); + if (sav < 0) + { + unlink (fullpath_tmp); + free (fullpath_sav); + free (fullpath_tmp); + return NULL; + } + + while((c = read (sav, buffer, 512)) > 0) + write (tmp, buffer, c); + + close (sav); + close (tmp); + + if (chmod(fullpath_tmp, CONFIGFILE_MASK) != 0) + { + unlink (fullpath_tmp); + free (fullpath_sav); + free (fullpath_tmp); + return NULL; + } + + if (link (fullpath_tmp, fullpath) == 0) + ret = fopen (fullpath, "r"); + + unlink (fullpath_tmp); + + free (fullpath_sav); + free (fullpath_tmp); + return ret; +} + +/* Read up configuration file from file_name. */ +void +vty_read_config (char *config_file, + char *config_default_dir) +{ + vty_read_config_first_cmd_special(config_file, config_default_dir, NULL); +} + +/* Read up configuration file from file_name. + * callback after first command */ +void +vty_read_config_first_cmd_special(char *config_file, + char *config_default_dir, void (*after_first_cmd)(void)) +{ + char cwd[MAXPATHLEN]; + FILE *confp = NULL; + char *fullpath; + char *tmp = NULL; + + /* If -f flag specified. */ + if (config_file != NULL) + { + if (! IS_DIRECTORY_SEP (config_file[0])) + { + getcwd (cwd, MAXPATHLEN); + tmp = XMALLOC (MTYPE_TMP, + strlen (cwd) + strlen (config_file) + 2); + sprintf (tmp, "%s/%s", cwd, config_file); + fullpath = tmp; + } + else + fullpath = config_file; + + confp = fopen (fullpath, "r"); + + if (confp == NULL) + { + fprintf (stderr, "%s: failed to open configuration file %s: %s\n", + __func__, fullpath, safe_strerror (errno)); + + confp = vty_use_backup_config (fullpath); + if (confp) + fprintf (stderr, "WARNING: using backup configuration file!\n"); + else + { + fprintf (stderr, "can't open configuration file [%s]\n", + config_file); + exit(1); + } + } + } + else + { +#ifdef VTYSH + int ret; + struct stat conf_stat; + + /* !!!!PLEASE LEAVE!!!! + * This is NEEDED for use with vtysh -b, or else you can get + * a real configuration food fight with a lot garbage in the + * merged configuration file it creates coming from the per + * daemon configuration files. This also allows the daemons + * to start if there default configuration file is not + * present or ignore them, as needed when using vtysh -b to + * configure the daemons at boot - MAG + */ + + /* Stat for vtysh Zebra.conf, if found startup and wait for + * boot configuration + */ + + if ( strstr(config_default_dir, "vtysh") == NULL) + { + ret = stat (integrate_default, &conf_stat); + if (ret >= 0) + return; + } +#endif /* VTYSH */ + + confp = fopen (config_default_dir, "r"); + if (confp == NULL) + { + fprintf (stderr, "%s: failed to open configuration file %s: %s\n", + __func__, config_default_dir, safe_strerror (errno)); + + confp = vty_use_backup_config (config_default_dir); + if (confp) + { + fprintf (stderr, "WARNING: using backup configuration file!\n"); + fullpath = config_default_dir; + } + else + { + fprintf (stderr, "can't open configuration file [%s]\n", + config_default_dir); + exit (1); + } + } + else + fullpath = config_default_dir; + } + + vty_read_file (confp, after_first_cmd); + + fclose (confp); + +#ifdef QDEBUG + fprintf(stderr, "Reading config file: %s\n", fullpath); +#endif + host_config_set (fullpath); +#ifdef QDEBUG + fprintf(stderr, "Finished reading config file\n"); +#endif + + if (tmp) + XFREE (MTYPE_TMP, fullpath); +} + +#ifdef QDEBUG +/* Tell all terminals that we are shutting down */ +void +vty_goodbye (void) +{ + unsigned int i; + struct vty *vty; + + LOCK + + if (vtyvec) + { + for (i = 0; i < vector_active (vtyvec); i++) + { + if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->vio->type == VTY_TERM) + { + uty_cout(vty, QUAGGA_PROGNAME " is shutting down%s", VTY_NEWLINE); + + /* Wake up */ + if (cli_nexus) + vty_event (VTY_WRITE, vty->vio->fd, vty); + } + } + if (qpthreads_enabled) + qpt_thread_signal(cli_nexus->thread_id, SIGMQUEUE); + } + + UNLOCK +} +#endif + +/* Async-signal-safe version of vty_log for fixed strings. */ +void +vty_log_fixed (const char *buf, size_t len) +{ +// unsigned int i; + struct iovec iov[2]; + + /* vty may not have been initialised */ +// if (!vtyvec) +// return; + + iov[0].iov_base = miyagi(buf) ; + iov[0].iov_len = len; + iov[1].iov_base = miyagi("\r\n") ; + iov[1].iov_len = 2; + +// for (i = 0; i < vector_active (vtyvec); i++) + { + struct vty *vty; +// if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->vio->monitor) + /* N.B. We don't care about the return code, since process is + most likely just about to die anyway. */ + writev(vty->vio->fd, iov, 2); + } +} + +int +vty_config_lock (struct vty *vty) +{ + int result; + LOCK + if (vty_config == 0) + { + vty->vio->config = 1; + vty_config = 1; + } + result = vty->vio->config; + UNLOCK + return result; +} + +int +vty_config_unlock (struct vty *vty) +{ + int result; + LOCK + result = uty_config_unlock(vty); + UNLOCK + return result; +} + +static int +uty_config_unlock (struct vty *vty) +{ + ASSERTLOCKED + if (vty_config == 1 && vty->vio->config == 1) + { + vty->vio->config = 0; + vty_config = 0; + } + return vty->vio->config; +} + +static void +vty_event (enum vty_event event, int sock, struct vty *vty) +{ + if (cli_nexus) + vty_event_r(event, sock, vty); + else + vty_event_t(event, sock, vty); +} + +/* thread event setter */ +static void +vty_event_t (enum vty_event event, int sock, struct vty *vty) + { + struct thread *vty_serv_thread; + + ASSERTLOCKED + + switch (event) + { + case VTY_SERV: + vty_serv_thread = thread_add_read (master, vty_accept, vty, sock); +// vector_set_index (Vvty_serv_thread, sock, vty_serv_thread); + break; +#ifdef VTYSH + case VTYSH_SERV: + thread_add_read (master, vtysh_accept, vty, sock); + break; + case VTYSH_READ: + vty->vio->t_read = thread_add_read (master, vtysh_read, vty, sock); + break; + case VTYSH_WRITE: + vty->vio->t_write = thread_add_write (master, vtysh_write, vty, sock); + break; +#endif /* VTYSH */ + case VTY_READ: + vty->vio->t_read = thread_add_read (master, vty_read, vty, sock); + + /* Time out treatment. */ + if (vty->vio->v_timeout) + { + if (vty->vio->t_timeout) + thread_cancel (vty->vio->t_timeout); + vty->vio->t_timeout = + thread_add_timer (master, vty_timeout, vty, vty->vio->v_timeout); + } + break; + case VTY_WRITE: + if (! vty->vio->t_write) + vty->vio->t_write = thread_add_write (master, vty_flush, vty, sock); + break; + case VTY_TIMEOUT_RESET: + if (vty->vio->t_timeout) + { + thread_cancel (vty->vio->t_timeout); + vty->vio->t_timeout = NULL; + } + if (vty->vio->v_timeout) + { + vty->vio->t_timeout = + thread_add_timer (master, vty_timeout, vty, vty->vio->v_timeout); + } + break; + } +} + +/* qpthreads event setter */ +static void +vty_event_r (enum vty_event event, int sock, struct vty *vty) + { + + qps_file accept_file = NULL; + + ASSERTLOCKED + + switch (event) + { + case VTY_SERV: + accept_file = NULL ;// vector_get_item(Vvty_serv_thread, sock); + if (accept_file == NULL) + { + accept_file = qps_file_init_new(accept_file, NULL); + qps_add_file(cli_nexus->selection, accept_file, sock, NULL); +// vector_set_index(Vvty_serv_thread, sock, accept_file); + } + qps_enable_mode(accept_file, qps_read_mnum, vty_accept_r) ; + break; +#ifdef VTYSH + case VTYSH_SERV: + accept_file = vector_get_item(Vvty_serv_thread, sock); + if (accept_file == NULL) + { + accept_file = qps_file_init_new(accept_file, NULL); + qps_add_file(master, accept_file, sock, NULL); + vector_set_index(Vvty_serv_thread, sock, accept_file); + } + qps_enable_mode(accept_file, qps_read_mnum, vtysh_accept_r) ; + break; + case VTYSH_READ: + qps_enable_mode(vty->vio->file, qps_read_mnum, vtysh_read_r) ; + break; + case VTYSH_WRITE: + qps_enable_mode(vty->vio->file, qps_write_mnum, vtysh_write_r) ; + break; +#endif /* VTYSH */ + case VTY_READ: + qps_enable_mode(vty->vio->qf, qps_read_mnum, vty_read_r) ; + + /* Time out treatment. */ + if (vty->vio->v_timeout) + { + qtimer_set(vty->vio->qtr, qt_add_monotonic(QTIME(vty->vio->v_timeout)), NULL) ; + } + break; + case VTY_WRITE: + qps_enable_mode(vty->vio->qf, qps_write_mnum, vty_flush_r) ; + break; + case VTY_TIMEOUT_RESET: + if (vty->vio->qtr == NULL) + break; + if (vty->vio->v_timeout) + { + qtimer_set(vty->vio->qtr, qt_add_monotonic(QTIME(vty->vio->v_timeout)), NULL) ; + } + else + { + qtimer_unset(vty->vio->qtr); + } + break; + } +} + +/*============================================================================== + * Commands + * + */ +DEFUN_CALL (config_who, + config_who_cmd, + "who", + "Display who is on vty\n") +{ + unsigned int i; + struct vty *v; + + LOCK +// for (i = 0; i < vector_active (vtyvec); i++) +// if ((v = vector_slot (vtyvec, i)) != NULL) + uty_out (vty, "%svty[%d] connected from %s.%s", + v->vio->config ? "*" : " ", + i, v->vio->address, VTY_NEWLINE); + UNLOCK + return CMD_SUCCESS; +} + +/* Move to vty configuration mode. */ +DEFUN_CALL (line_vty, + line_vty_cmd, + "line vty", + "Configure a terminal line\n" + "Virtual terminal\n") +{ + LOCK + vty->node = VTY_NODE; + UNLOCK + return CMD_SUCCESS; +} + +/* Set time out value. */ +static int +exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) +{ + unsigned long timeout = 0; + + LOCK + + /* min_str and sec_str are already checked by parser. So it must be + all digit string. */ + if (min_str) + { + timeout = strtol (min_str, NULL, 10); + timeout *= 60; + } + if (sec_str) + timeout += strtol (sec_str, NULL, 10); + + vty_timeout_val = timeout; + vty->vio->v_timeout = timeout; + vty_event (VTY_TIMEOUT_RESET, 0, vty); + + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (exec_timeout_min, + exec_timeout_min_cmd, + "exec-timeout <0-35791>", + "Set timeout value\n" + "Timeout value in minutes\n") +{ + return exec_timeout (vty, argv[0], NULL); +} + +DEFUN_CALL (exec_timeout_sec, + exec_timeout_sec_cmd, + "exec-timeout <0-35791> <0-2147483>", + "Set the EXEC timeout\n" + "Timeout in minutes\n" + "Timeout in seconds\n") +{ + return exec_timeout (vty, argv[0], argv[1]); +} + +DEFUN_CALL (no_exec_timeout, + no_exec_timeout_cmd, + "no exec-timeout", + NO_STR + "Set the EXEC timeout\n") +{ + return exec_timeout (vty, NULL, NULL); +} + +/* Set vty access class. */ +DEFUN_CALL (vty_access_class, + vty_access_class_cmd, + "access-class WORD", + "Filter connections based on an IP access list\n" + "IP access list\n") +{ + LOCK + + if (vty_accesslist_name) + XFREE(MTYPE_VTY, vty_accesslist_name); + + vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + + UNLOCK + return CMD_SUCCESS; +} + +/* Clear vty access class. */ +DEFUN_CALL (no_vty_access_class, + no_vty_access_class_cmd, + "no access-class [WORD]", + NO_STR + "Filter connections based on an IP access list\n" + "IP access list\n") +{ + int result = CMD_SUCCESS; + + 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); + result = CMD_WARNING; + } + else + { + XFREE(MTYPE_VTY, vty_accesslist_name); + vty_accesslist_name = NULL; + } + + UNLOCK + return result; +} + +#ifdef HAVE_IPV6 +/* Set vty access class. */ +DEFUN_CALL (vty_ipv6_access_class, + vty_ipv6_access_class_cmd, + "ipv6 access-class WORD", + IPV6_STR + "Filter connections based on an IP access list\n" + "IPv6 access list\n") +{ + LOCK + if (vty_ipv6_accesslist_name) + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + + vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + + UNLOCK + return CMD_SUCCESS; +} + +/* Clear vty access class. */ +DEFUN_CALL (no_vty_ipv6_access_class, + no_vty_ipv6_access_class_cmd, + "no ipv6 access-class [WORD]", + NO_STR + IPV6_STR + "Filter connections based on an IP access list\n" + "IPv6 access list\n") +{ + int result = CMD_SUCCESS; + + LOCK + + 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); + result = CMD_WARNING; + } + else + { + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + + vty_ipv6_accesslist_name = NULL; + } + + UNLOCK + return CMD_SUCCESS; +} +#endif /* HAVE_IPV6 */ + +/* vty login. */ +DEFUN_CALL (vty_login, + vty_login_cmd, + "login", + "Enable password checking\n") +{ + LOCK + no_password_check = 0; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (no_vty_login, + no_vty_login_cmd, + "no login", + NO_STR + "Enable password checking\n") +{ + LOCK + no_password_check = 1; + UNLOCK + return CMD_SUCCESS; +} + +/* initial mode. */ +DEFUN_CALL (vty_restricted_mode, + vty_restricted_mode_cmd, + "anonymous restricted", + "Restrict view commands available in anonymous, unauthenticated vty\n") +{ + LOCK + restricted_mode = 1; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (vty_no_restricted_mode, + vty_no_restricted_mode_cmd, + "no anonymous restricted", + NO_STR + "Enable password checking\n") +{ + LOCK + restricted_mode = 0; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (service_advanced_vty, + service_advanced_vty_cmd, + "service advanced-vty", + "Set up miscellaneous service\n" + "Enable advanced mode vty interface\n") +{ + LOCK + host.advanced = 1; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (no_service_advanced_vty, + no_service_advanced_vty_cmd, + "no service advanced-vty", + NO_STR + "Set up miscellaneous service\n" + "Enable advanced mode vty interface\n") +{ + LOCK + host.advanced = 0; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (terminal_monitor, + terminal_monitor_cmd, + "terminal monitor", + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") +{ + LOCK + vty->vio->monitor = 1; + UNLOCK + return CMD_SUCCESS; +} + +DEFUN_CALL (terminal_no_monitor, + terminal_no_monitor_cmd, + "terminal no monitor", + "Set terminal line parameters\n" + NO_STR + "Copy debug output to the current terminal line\n") +{ + LOCK + vty->vio->monitor = 0; + UNLOCK + return CMD_SUCCESS; +} + +ALIAS_CALL (terminal_no_monitor, + no_terminal_monitor_cmd, + "no terminal monitor", + NO_STR + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") + +DEFUN_CALL (show_history, + show_history_cmd, + "show history", + SHOW_STR + "Display the session command history\n") +{ + int index; + + LOCK + + for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) + { + if (index == VTY_MAXHIST) + { + index = 0; + continue; + } + + if (vty->vio->hist[index] != NULL) + uty_out (vty, " %s%s", vty->vio->hist[index], VTY_NEWLINE); + + index++; + } + + UNLOCK + return CMD_SUCCESS; +} + +/* Display current configuration. */ +static int +vty_config_write (struct vty *vty) +{ + vty_out (vty, "line vty%s", VTY_NEWLINE); + + if (vty_accesslist_name) + vty_out (vty, " access-class %s%s", + vty_accesslist_name, VTY_NEWLINE); + + if (vty_ipv6_accesslist_name) + vty_out (vty, " ipv6 access-class %s%s", + vty_ipv6_accesslist_name, VTY_NEWLINE); + + /* 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); + + /* login */ + if (no_password_check) + vty_out (vty, " no login%s", VTY_NEWLINE); + + if (restricted_mode != restricted_mode_default) + { + if (restricted_mode_default) + vty_out (vty, " no anonymous restricted%s", VTY_NEWLINE); + else + vty_out (vty, " anonymous restricted%s", VTY_NEWLINE); + } + + vty_out (vty, "!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +struct cmd_node vty_node = +{ + VTY_NODE, + "%s(config-line)# ", + 1, +}; + +/*============================================================================== + * + */ + +/*------------------------------------------------------------------------------ + * Reset all VTY status + * + */ +void +vty_reset () +{ + unsigned int i; + struct vty *vty; + struct thread *vty_serv_thread; + qps_file qf; + + LOCK + +// for (i = 0; i < vector_active (vtyvec); i++) +// if ((vty = vector_slot (vtyvec, i)) != NULL) + { + buffer_reset (vty->vio->obuf); + vty->vio->status = VTY_CLOSE; + uty_close (vty); + } + + if (cli_nexus) + { +// for (i = 0; i < vector_active (Vvty_serv_thread); i++) +// if ((qf = vector_slot (Vvty_serv_thread, i)) != NULL) + { + qps_remove_file(qf); + qps_file_free(qf); +// vector_slot (Vvty_serv_thread, i) = NULL; + close (i); + } + } + else + { + assert(master); +// for (i = 0; i < vector_active (Vvty_serv_thread); i++) +// if ((vty_serv_thread = vector_slot (Vvty_serv_thread, i)) != NULL) + { + thread_cancel (vty_serv_thread); + // vector_slot (Vvty_serv_thread, i) = NULL; + close (i); + } + } + + vty_timeout_val = VTY_TIMEOUT_DEFAULT; + + if (vty_accesslist_name) + { + XFREE(MTYPE_VTY, vty_accesslist_name); + vty_accesslist_name = NULL; + } + + if (vty_ipv6_accesslist_name) + { + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + vty_ipv6_accesslist_name = NULL; + } + + UNLOCK +} + +/*------------------------------------------------------------------------------ + * Save cwd + * + * This is done early in the morning so that any future operations on files + * (in particular the configuration file) can use the original cwd. + */ +static void +vty_save_cwd (void) +{ + char cwd[MAXPATHLEN]; + char *c; + + c = getcwd (cwd, MAXPATHLEN); + + if (!c) + { + chdir (SYSCONFDIR); + getcwd (cwd, MAXPATHLEN); + } + + vty_cwd = XMALLOC (MTYPE_TMP, strlen (cwd) + 1); + strcpy (vty_cwd, cwd); +} + +/*------------------------------------------------------------------------------ + * + */ +char * +vty_get_cwd () +{ + return vty_cwd; +} + +int +vty_shell (struct vty *vty) +{ + LOCK + int result; + result = (vty->vio->type == VTY_SHELL) ? 1 : 0 ; + UNLOCK + return result; +} + +int +vty_shell_serv (struct vty *vty) +{ + LOCK + int result; + result = ((vty->vio->type == VTY_SHELL_SERV) ? 1 : 0); + UNLOCK + return result; +} + +void +vty_init_vtysh () +{ + LOCK +// vtyvec = vector_init (0); + UNLOCK +} + +int +vty_get_node(struct vty *vty) +{ + int result; + LOCK + result = vty->node; + UNLOCK + return result; +} + +void +vty_set_node(struct vty *vty, int node) +{ + LOCK + vty->node = node; + UNLOCK +} + +int +vty_get_type(struct vty *vty) +{ + int result; + LOCK + result = vty->vio->type; + UNLOCK + return result; +} + +int +vty_get_status(struct vty *vty) +{ + int result; + LOCK + result = vty->vio->status; + UNLOCK + return result; +} + +void +vty_set_status(struct vty *vty, int status) +{ + LOCK + vty->vio->status = status; + UNLOCK +} + +int +vty_get_lines(struct vty *vty) +{ + int result; + LOCK + result = vty->vio->lines; + UNLOCK + return result; +} + +void +vty_set_lines(struct vty *vty, int lines) +{ + LOCK + vty->vio->lines = lines; + UNLOCK +} + +/*============================================================================== + * System initialisation and shut down + */ + +/*------------------------------------------------------------------------------ + * Initialise vty handling (threads and pthreads) + * + * Install vty's own commands like `who' command. + */ +void +vty_init (struct thread_master *master_thread) +{ + LOCK + + /* Local pointer to the master thread */ + master = master_thread; + + /* For further configuration read, preserve current directory */ + vty_save_cwd (); + + /* No vty yet */ + vty_known = NULL ; + vty_monitors = NULL ; + + /* Initilize server thread vector. */ +// Vvty_serv_thread = vector_init (0); + + /* Install bgp top node. */ + install_node (&vty_node, vty_config_write); + + install_element (RESTRICTED_NODE, &config_who_cmd); + install_element (RESTRICTED_NODE, &show_history_cmd); + install_element (VIEW_NODE, &config_who_cmd); + install_element (VIEW_NODE, &show_history_cmd); + install_element (ENABLE_NODE, &config_who_cmd); + install_element (CONFIG_NODE, &line_vty_cmd); + install_element (CONFIG_NODE, &service_advanced_vty_cmd); + install_element (CONFIG_NODE, &no_service_advanced_vty_cmd); + install_element (CONFIG_NODE, &show_history_cmd); + install_element (ENABLE_NODE, &terminal_monitor_cmd); + install_element (ENABLE_NODE, &terminal_no_monitor_cmd); + install_element (ENABLE_NODE, &no_terminal_monitor_cmd); + install_element (ENABLE_NODE, &show_history_cmd); + + install_default (VTY_NODE); + install_element (VTY_NODE, &exec_timeout_min_cmd); + install_element (VTY_NODE, &exec_timeout_sec_cmd); + install_element (VTY_NODE, &no_exec_timeout_cmd); + install_element (VTY_NODE, &vty_access_class_cmd); + install_element (VTY_NODE, &no_vty_access_class_cmd); + install_element (VTY_NODE, &vty_login_cmd); + install_element (VTY_NODE, &no_vty_login_cmd); + install_element (VTY_NODE, &vty_restricted_mode_cmd); + install_element (VTY_NODE, &vty_no_restricted_mode_cmd); +#ifdef HAVE_IPV6 + install_element (VTY_NODE, &vty_ipv6_access_class_cmd); + install_element (VTY_NODE, &no_vty_ipv6_access_class_cmd); +#endif /* HAVE_IPV6 */ + + UNLOCK +} + +/*------------------------------------------------------------------------------ + * Further initialisation for qpthreads. + * + * This is done during "second stage" initialisation, when all nexuses have + * been set up and the qpthread_enabled state established. + * + * Need to know where the CLI nexus and the Routeing Engine nexus are. + * + * Initialise mutex. + */ +void +vty_init_r (qpn_nexus cli_n, qpn_nexus routing_n) +{ + cli_nexus = cli_n; + routing_nexus = routing_n; + qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); +} ; + +/*------------------------------------------------------------------------------ + * System shut-down + * + * Reset all known vty and release all memory. + */ +void +vty_terminate (void) +{ + LOCK + + if (vty_cwd) + XFREE (MTYPE_TMP, vty_cwd); + + // if (vtyvec && Vvty_serv_thread) + { + uty_reset (); +// vector_free (vtyvec); +// vector_free (Vvty_serv_thread); + } + UNLOCK + + qpt_mutex_destroy(&vty_mutex, 0); +} + +#undef LOCK +#undef UNLOCK +#undef ASSERTLOCKED @@ -1,141 +1,162 @@ -/* Virtual terminal [aka TeletYpe] interface routine - Copyright (C) 1997 Kunihiro Ishiguro - -This file is part of GNU Zebra. - -GNU Zebra is free software; you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the -Free Software Foundation; either version 2, or (at your option) any -later version. - -GNU Zebra is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GNU Zebra; see the file COPYING. If not, write to the Free -Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -02111-1307, USA. */ +/* VTY top level + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ #ifndef _ZEBRA_VTY_H #define _ZEBRA_VTY_H +#include <stdbool.h> + #include "thread.h" #include "log.h" #include "qpthreads.h" #include "qpselect.h" #include "qtimers.h" #include "qpnexus.h" +#include "list_util.h" +#include "node_type.h" -#define VTY_BUFSIZ 512 -#define VTY_MAXHIST 20 - -/* VTY struct. */ -struct vty -{ - /* File descriptor of this vty. */ - int fd; - - /* Is this vty connect to file or not */ - enum {VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV} type; - - /* Node status of this vty */ - int node; - - /* What address is this vty coming from. */ - char *address; - - /* Failure count */ - int fail; - - /* Output buffer. */ - struct buffer *obuf; - - /* Command input buffer */ - char *buf; - - /* Command cursor point */ - int cp; - - /* Command length */ - int length; - - /* Command max length. */ - int max; - - /* Histry of command */ - char *hist[VTY_MAXHIST]; - - /* History lookup current point */ - int hp; - - /* History insert end point */ - int hindex; - - /* For current referencing point of interface, route-map, - access-list etc... */ - void *index; - - /* For multiple level index treatment such as key chain and key. */ - void *index_sub; +/* Macro in case there are particular compiler issues. */ +#ifndef Inline + #define Inline static inline +#endif - /* For escape character. */ - unsigned char escape; +/*============================================================================== + * The VTYSH uses a unix socket to talk to the daemon. + * + * The ability to respond to a connection from VTYSH appears to be a *compile* + * time option. In the interests of keeping the code up to date, the VTYSH + * option is turned into a testable constant. + */ +#ifdef VTYSH +# define VTYSH_DEFINED 1 +#else +# define VTYSH_DEFINED 0 +#endif - /* Current vty status. */ - enum {VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE} status; +enum { VTYSH_ENABLED = VTYSH_DEFINED } ; - /* IAC handling: was the last character received the - IAC (interpret-as-command) escape character (and therefore the next - character will be the command code)? Refer to Telnet RFC 854. */ - unsigned char iac; +#undef VTYSH_DEFINED - /* IAC SB (option subnegotiation) handling */ - unsigned char iac_sb_in_progress; - /* At the moment, we care only about the NAWS (window size) negotiation, - and that requires just a 5-character buffer (RFC 1073): - <NAWS char> <16-bit width> <16-bit height> */ -#define TELNET_NAWS_SB_LEN 5 - unsigned char sb_buf[TELNET_NAWS_SB_LEN]; - /* How many subnegotiation characters have we received? We just drop - those that do not fit in the buffer. */ - size_t sb_len; +/*============================================================================== + * VTY struct. + */ - /* Window width/height. */ - int width; - int height; +typedef struct vty_io* vty_io ; /* private to vty.c */ - /* Configure lines. */ - int lines; +struct vty +{ + /*---------------------------------------------------------------------- + * The following are used outside vty.c, and represent the context + * in which commands are executed. + */ + + /* Node status of this vty + * + * NB: when using qpthreads should lock VTY to access this -- so use + * vty_get_node() and vty_set_node(). + */ + int node ; + + /* The current command line. + * + * This is set when the command is dispatched, and not touched until the + * command completes -- so may be read while the command is being executed, + * without requiring any lock. + */ + const char* buf ; + + /* For current referencing point of interface, route-map, access-list + * etc... + * + * NB: this value is private to the command execution, which is assumed + * to all be in the one thread... so no lock required. + */ + void *index ; + + /* For multiple level index treatment such as key chain and key. + * + * NB: this value is private to the command execution, which is assumed + * to all be in the one thread... so no lock required. + */ + void *index_sub ; + + /* String which is newline... read only -- no locking */ + const char* newline ; + + /*---------------------------------------------------------------------- + * The following are used inside vty.c only. + */ + + /* Pointer to related vty_io structure -- if any. */ + vty_io vio ; +}; - /* Terminal monitor. */ - int monitor; +/*------------------------------------------------------------------------------ + * VTY events + */ +enum vty_event +{ + VTY_SERV, + VTY_READ, + VTY_WRITE, + VTY_TIMEOUT_RESET, + + VTYSH_SERV, + VTYSH_READ, + VTYSH_WRITE +}; - /* In configure mode. */ - int config; +/*------------------------------------------------------------------------------ + * VTY Types + */ +enum vty_type +{ + VTY_TERM, /* a telnet session -- input and output */ + VTY_FILE, /* writing config file -- output is to file + -- no input */ - /* Read and write thread. */ + VTY_STDOUT, /* reading config file -- output is to stdout + -- no input */ - qps_file qf; - struct thread *t_read; - struct thread *t_write; + VTY_SHELL, /* vty in vtysh -- output is to stdout */ + VTY_SHELL_SERV /* vty in daemon -- input and output */ +} ; - /* Timeout seconds and thread. */ - unsigned long v_timeout; - qtimer qtr; - struct thread *t_timeout; -}; +/*------------------------------------------------------------------------------ + * + */ +#define VTY_BUFSIZ 512 +#define VTY_MAXHIST 20 /* 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->type == VTY_TERM)) ? "\r\n" : "\n") +#define VTY_NEWLINE (((vty) != NULL) ? (vty)->newline : "\n") /* For indenting, mostly. */ -extern const char* vty_spaces_string ; -#define VTY_MAX_SPACES 24 +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)) @@ -210,40 +231,37 @@ do { } \ } while (0) -/* Exported variables */ +/*------------------------------------------------------------------------------ + * Exported variables + */ extern char integrate_default[]; -extern qpt_mutex_t vty_mutex; -#ifndef NDEBUG -extern int vty_lock_count; -extern int vty_lock_asserted; -#endif -/* Prototypes. */ -extern void vty_init_r (qpn_nexus, qpn_nexus); -extern void vty_exec_r(void); +/*------------------------------------------------------------------------------ + * Prototypes. + */ extern void vty_init (struct thread_master *); +extern void vty_init_r (qpn_nexus, qpn_nexus); +extern void vty_serv_sock(const char *addr, unsigned short port, + const char *path) ; +extern struct vty* vty_new (int fd, enum vty_type type) ; +extern void vty_close (struct vty *); + extern void vty_init_vtysh (void); extern void vty_terminate (void); extern void vty_reset (void); -extern struct vty *vty_new (int, int); + extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); -extern int vty_puts(struct vty* vty, const char* str) ; -extern int vty_out_newline(struct vty *vty) ; extern int vty_out_indent(struct vty *vty, int indent) ; + extern void vty_read_config (char *, char *); extern void vty_read_config_first_cmd_special (char *, char *, void (*)(void)); extern void vty_time_print (struct vty *, int); -extern void vty_serv_sock (const char *, unsigned short, const char *); -extern void vty_close (struct vty *); + extern char *vty_get_cwd (void); -extern void vty_log (const char *level, const char *proto, - const char *fmt, struct timestamp_control *, va_list); -extern int vty_config_lock (struct vty *); -extern int vty_config_unlock (struct vty *); + extern int vty_shell (struct vty *); extern int vty_shell_serv (struct vty *); extern void vty_hello (struct vty *); -extern void vty_queued_result(struct vty *, int); extern int vty_get_node(struct vty *); extern void vty_set_node(struct vty *, int); extern int vty_get_type(struct vty *); @@ -252,10 +270,6 @@ extern void vty_set_status(struct vty *, int); extern int vty_get_lines(struct vty *); extern void vty_set_lines(struct vty *, int); -/* Send a fixed-size message to all vty terminal monitors; this should be - an async-signal-safe function. */ -extern void vty_log_fixed (const char *buf, size_t len); - #ifdef QDEBUG extern void vty_goodbye (void); #endif diff --git a/lib/vty_cli.c b/lib/vty_cli.c new file mode 100644 index 00000000..de9bb53c --- /dev/null +++ b/lib/vty_cli.c @@ -0,0 +1,2308 @@ +/* VTY Command Line Handler + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include "keystroke.h" +#include "vty.h" +#include "uty.h" +#include "vty_io.h" +#include "vty_cli.h" + +#include "command.h" + +#include "memory.h" + +/*============================================================================== + * Host name handling + * + * The host name is used in the command line prompt. The name used is either + * the name set by "hostname" command, or the current machine host name. + * + * Static variables -- under the VTY_LOCK ! + */ + +static char* vty_host_name = NULL ; +int vty_host_name_set = 0 ; + +static void uty_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Update vty_host_name as per "hostname" or "no hostname" command + */ +extern void +uty_set_host_name(const char* name) +{ + VTY_ASSERT_LOCKED() ; + + vty_host_name_set = (name != NULL) ; + + if (vty_host_name_set) + uty_new_host_name(name) ; + else + uty_check_host_name() ; +} ; + +/*------------------------------------------------------------------------------ + * If vty_host_name is set, free it. + */ +extern void +uty_free_host_name(void) +{ + if (vty_host_name != NULL) + XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ +} ; + +/*------------------------------------------------------------------------------ + * If the host name is not set by command, see if the actual host name has + * changed, and if so change it. + * + * This is done periodically in case the actual host name changes ! + */ +extern void +uty_check_host_name(void) +{ + struct utsname names ; + + VTY_ASSERT_LOCKED() ; + + if (vty_host_name_set) + return ; /* nothing to do if set by command */ + + uname (&names) ; + + if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) + uty_new_host_name(names.nodename) ; +} ; + +/*------------------------------------------------------------------------------ + * Set new vty_host_name and run along list of VTYs to mark the change. + */ +static void +uty_new_host_name(const char* name) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + uty_free_host_name() ; + vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + + vio = vio_list_base ; + while (vio != NULL) + { + vio->cli_prompt_set = 0 ; + vio = sdl_next(vio, vio_list) ; + } ; +} ; + +/*============================================================================== + * General mechanism for command execution. + * + * Command execution is driven by select/pselect -- which means that the + * processing of commands is multiplexed with all other activity. In the + * following: + * + * -- read_ready and write_ready are events signalled by select/pselect + * + * -- setting read or write on, means enabling the file for select/pselect to + * consider it for read_ready or write_ready, respectively. + * + * State of the CLI: + * + * cli_blocked -- a command has been dispatched, and CLI is now waiting + * for it and/or its output to complete. + * + * cmd_in_progress -- a command has been dispatched (and may have been + * queued). + * + * If not blocked: can continue in the CLI until another + * command is ready to be executed. + * + * 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 only emptied when !cmd_in_progress -- this means + * that all the output from a given command is collected together before being + * sent to the file. + * + * The CLI starts with cli_blocked and !cmd_in_progress, with the socket set + * write on. The CLI progresses as follows: + * + * * on read_ready, if cli_blocked: do nothing. + * + * * on write_ready: + * + * - empty out the CLI buffer + * + * - if ! cmd_in_progress: + * + * * empty out the command buffer + * + * * if the command buffer is empty, clear cli_blocked (if was set) + * + * - generate a read_ready event unless write() would block. + * + * So the CLI is kicked once all available output completes. + * + * * on read_ready, if !cli_blocked: + * + * - process keystrokes into command line under construction. + * + * - when required, dispatch the command: + * + * * set cmd_in_progress + * + * * execute the command -- which may generate output + * + * * clear cmd_in_progress + * + * * set cli_blocked + * + * - set write on (or read on) as required. + * + * 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, all output to the command output FIFO is held + * there -- no actual write() is performed. + * + * When the command completes: + * + * * cmd_in_progress is cleared + * + * * 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 cmd_wait_more flag and its handling.) + * + * When all the output has completed, cli_blocked is cleared and the CLI will + * be kicked. + * + * 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. + */ + +/*============================================================================== + * Command Line Interface + * + * State of the CLI: + * + * cli_blocked -- a command has been dispatched, and now waiting + * for it and/or its output to complete. + * + * cmd_in_progress -- a command has been dispatched (and may have been + * queued). + * + * Can continue in the CLI until another command is + * ready to be executed. + */ + +static void uty_cli_draw(vty_io vio, enum node_type node) ; +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 int uty_cli_dispatch(vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Run the CLI until: + * + * * becomes blocked + * * runs out of keystrokes + * * executes a command + * + * When exits will (in general): + * + * * set write on if there is anything to be written or have something + * in the keystroke stream to be processed. + * + * * set read on if does not set write on, if not yet hit EOF on the + * keystroke stream. + * + * 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 ! + */ +extern void +uty_cli(vty_io vio) +{ + bool won ; + + VTY_ASSERT_LOCKED() ; + + /* cli_blocked is set when is waiting for a command, or its output to + * complete. + * + * There is no good reason to arrive here in that state, and nothing to be + * done if that happens. + */ + if (vio->cli_blocked) + return ; + + /* If there is something to do, that is because a previous command has + * now completed, which may have wiped the pending command. + * + * If there is nothing pending, then can run the CLI until there is + * something to do, or runs out of input. + */ + if (vio->cli_do != cli_do_nothing) + uty_cli_draw(vio, vio->vty->node) ; + else + vio->cli_do = uty_cli_process(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 ; + } ; + + /* If there is anything in the CLI output FIFO, must set write on to clear + * it. + * + * If there is anything in the command output FIFO *and* is !cmd_in_progress, + * must set write on to clear it. + * + * Otherwise: if cmd_in_progress, then the command buffer is not to be + * output yet, and the command completion will kick things into action -- so + * nothing further is required here. + * + * Otherwise: if there is anything in the command output buffer, must set + * write on to clear it. + * + * Otherwise: if there is something in the keystroke stream, then must set + * write on... the write_ready acts as a proxy for keystroke stream is ready. + * + * Otherwise: set read on -- so if further input arrives, will get on it. + */ + do + { + won = 1 ; /* generally the case */ + + /* If have some CLI output -- do that */ + if (!vio_fifo_empty(&vio->cli_obuf)) + break ; + + /* If is blocked with command in progress -- do nothing. + * + * This state <=> that there is a queued command, and the CLI is now + * blocked until the command completes. When it does, things will + * progress. + */ + if (vio->cli_blocked && vio->cmd_in_progress) + { + won = 0 ; + break ; + } ; + + /* If have some command output which is not held because there is + * a command in progress -- do that. + */ + if (!vio_fifo_empty(&vio->cmd_obuf) && !vio->cmd_in_progress) + break ; + + /* If the keystroke buffer is not empty then write_ready is used as a + * proxy for keystroke ready. + */ + if (!keystroke_stream_empty(vio->key_stream)) + break ; + + /* Finally... if not at keystroke EOF, set read on */ + won = 0 ; + + if (!keystroke_stream_eof(vio->key_stream)) + uty_file_set_read(&vio->file, on) ; + + } while (0) ; + + if (won) + uty_file_set_write(&vio->file, on) ; +} ; + +/* TODO: dealing with EOF and timeout and closing and such + * + * uty_cout (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + * + */ +/*------------------------------------------------------------------------------ + * 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 <=> output is pending and command completed + * false => no output pending or command was queued + * + * 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 int +uty_cli_dispatch(vty_io vio) +{ + qstring_t tmp ; + enum cli_do cli_do ; + int queued ; + + 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 */ + + 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_set_empty(&vio->cl) ; /* set cl empty (with '\0') */ + + /* Reset the command output FIFO and line_control */ + + assert(vio_fifo_empty(&vio->cmd_obuf)) ; + /* TODO: reset line_control */ + + /* Dispatch command */ + if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) + { + /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ + queued = uty_auth(vty, vty->buf, cli_do) ; + } + else + { + /* All other nodes... */ + switch (cli_do) + { + case cli_do_nothing: + break ; + + case cli_do_command: + queued = uty_command(vty, vty->buf) ; + break ; + + case cli_do_ctrl_c: + queued = uty_stop_input(vty) ; + break ; + + case cli_do_ctrl_d: + queued = uty_down_level(vty) ; + break ; + + case cli_do_ctrl_z: + queued = uty_command(vty, vty->buf) ; + if (queued) + vio->cli_do = cli_do_ctrl_z ; + else + queued = uty_end_config(vty) ; + break ; + + default: + zabort("unknown cli_do_xxx value") ; + } ; + } ; + + if (!queued) + { + vio->cmd_in_progress = 0 ; /* command complete */ + vty->buf = NULL ; /* finished with command line */ + } ; + + return ! (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) ; +} ; + +/*------------------------------------------------------------------------------ + * 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, int result, int action) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + vio->cmd_in_progress = 0 ; /* command complete */ + vty->buf = NULL ; /* finished with command line */ + + vio->cli_blocked = !vio_fifo_empty(&vio->cmd_obuf) ; + /* blocked if output is now pending */ + + uty_cli_wipe(vio) ; /* wipe any partly constructed line */ + + uty_file_set_write(&vty->vio->file, on) ; + /* flush any output -- which will do a + * read_ready when all finished */ + VTY_UNLOCK() ; +} + +/*============================================================================== + * 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->file.write_open) + { + va_list args; + int len ; + + va_start (args, format); + len = qs_vprintf(&vio->cli_vbuf, format, args) ; + va_end(args); + + if (len > 0) + uty_cli_write(vio, qs_chars(&vio->cli_vbuf), len) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * 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->file.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->file.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->file.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 flag. + */ +static void +uty_cli_out_newline(vty_io vio) +{ + uty_cli_write(vio, telnet_newline, 2) ; + vio->cli_drawn = 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", + }, + { /* 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", + } +} ; + +static void +uty_cli_response(vty_io vio, enum cli_do cli_do) +{ + const char* str ; + int len ; + + if (cli_do == cli_do_nothing) + 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. + */ +extern void +uty_cli_wipe(vty_io vio) +{ + int a ; + int b ; + + if (!vio->cli_drawn == 0) + return ; /* quit if already wiped */ + + assert(vio->cl.cp <= vio->cl.len) ; + + /* deal with echo suppression first */ + if (vio->cli_echo_suppress) + { + b = 0 ; + a = 0 ; + } + else + { + b = vio->cl.cp ; /* behind cursor */ + a = vio->cl.len - b ; /* ahead of cursor */ + } + + /* Stuff ahead of the current position */ + uty_cli_out_wipe_n(vio, a + vio->cli_extra_len) ; + + /* Stuff behind the current position */ + uty_cli_out_wipe_n(vio, vio->cli_prompt_len + b) ; + + vio->cli_drawn = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * 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. + * + * If there is a command queued, uses a dummy prompt -- because by the time + * the command is executed, the node may have changed, so the current prompt + * may be invalid. + * + * Sets: cli_drawn = true + * 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(vty_io vio, enum node_type node) +{ + const char* prompt ; + + if (vio->cli_drawn) + uty_cli_wipe(vio) ; + + vio->cli_drawn = 1 ; + vio->cli_extra_len = 0 ; + vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + /* Sort out what the prompt is. */ + + if (vio->cmd_in_progress) + { + /* If there is a queued command, the prompt is a minimal affair. */ + prompt = "~ " ; + vio->cli_prompt_len = strlen(prompt) ; + } + 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 = qs_chars(&vio->cli_prompt_for_node) ; + vio->cli_prompt_len = vio->cli_prompt_for_node.len ; + } ; + + uty_cli_write(vio, prompt, vio->cli_prompt_len) ; + + if ((vio->cl.len != 0) && !vio->cli_echo_suppress) + { + uty_cli_write(vio, qs_chars(&vio->cl), vio->cl.len) ; + if (vio->cl.cp < vio->cl.len) + uty_cli_write_n(vio, telnet_backspaces, vio->cl.len - vio->cl.cp) ; + } ; +} ; + +/*============================================================================== + * Command line processing loop + */ + +#define CONTROL(X) ((X) - '@') + +static void uty_telnet_command(vty_io vio, keystroke stroke) ; +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(vio, node) ; + + if (!uty_cli_get_keystroke(vio, &stroke)) + 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) ; + break ; + + /* Single byte escape ------------------------------------------------*/ + 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 ; +} ; + +/*============================================================================== + * Support for the "--More--" handling + */ + +#define MSG_MORE_PROMPT "--more--" + +/*------------------------------------------------------------------------------ + * Output the "--more--" prompt and enter waiting for more state. + * + * As enters the waiting state, empties all the available input into the + * keystroke FIFO... so doesn't treat anything that arrived before the + * prompt was issued as a response to the prompt ! + * + * Enables read -- now waiting for response to --more-- + * + * NB: it is the caller's responsibility to clear the "--more--" prompt from + * the CLI buffer. + */ +extern void +uty_cli_want_more(vty_io vio) +{ + int get ; + + /* Empty the input buffers into the keystroke FIFO */ + do + { + get = uty_read(vio, NULL) ; + } while (get > 0) ; + + /* Output the prompt and update the state */ + uty_cli_write_s(vio, MSG_MORE_PROMPT) ; + + vio->cmd_wait_more = 1 ; + + uty_file_set_read(&vio->file, on) ; +} ; + +/*------------------------------------------------------------------------------ + * Waiting for response to "--more--" prompt. + * + * Steal the next available keystroke and process it. Error and EOF appear as + * a null keystroke. + * + * Wipes the prompt and exits cli_wait_more state when has stolen keystroke. + * + * Enables write -- need to clear the prompt from the cli buffers and may + * now continue command output, or signal it's done. + */ +extern void +uty_cli_wait_more(vty_io vio) +{ + struct keystroke steal ; + + /* 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 ((steal.type == ks_null) || (steal.value != knull_eof)) + return ; + + /* 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_discard(vio) ; + break; + + default: /* evrything else, thrown away */ + break ; + } ; + } ; + + /* Wipe out the prompt and update state. */ + uty_cli_out_wipe_n(vio, - (int)strlen(MSG_MORE_PROMPT)) ; + + vio->cmd_wait_more = 0 ; + + uty_file_set_write(&vio->file, on) ; +} ; + +/*============================================================================== + * 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 c ; + VTY_ASSERT_LOCKED() ; + + c = vio->cl.len - vio->cl.cp ; + if (n > c) + n = c ; + + 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) +{ + int c ; + VTY_ASSERT_LOCKED() ; + + c = vio->cl.len - vio->cl.cp ; + if (n > c) + n = c ; + + assert(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 ; + + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.len - vio->cl.cp) && (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) ; + + vio->cl.len -= 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) +{ + char* prev_line ; + char* line ; + char* e ; + int prev_index ; + + VTY_ASSERT_LOCKED() ; + + /* make sure have a suitable history vector */ + vector_set_min_length(&vio->hist, VTY_MAXHIST) ; + + /* trim leading and trailing spaces before considering for history */ + + while (*cmd_line == ' ') + ++cmd_line ; /* hack off leading spaces */ + + if (*cmd_line == '\0') + return ; /* ignore empty lines */ + + line = XSTRDUP(MTYPE_VTY_HIST, cmd_line) ; + + e = line + strlen(line) ; + while (*(e - 1) == ' ') + --e ; + *e = '\0' ; /* hack off any trailing spaces */ + + /* 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) ; + + /* Insert unless same as previous line */ + if ((prev_line == NULL) || (strcmp(line, prev_line) != 0)) + { + /* Insert history entry. */ + if (prev_line != NULL) + XFREE (MTYPE_VTY_HIST, prev_line) ; + + vector_set_item(&vio->hist, vio->hindex, line) ; + + /* History index rotation. */ + vio->hindex++; + if (vio->hindex == VTY_MAXHIST) + vio->hindex = 0; + } + else + { + XFREE(MTYPE_VTY_HIST, line) ; + } ; + + vio->hp = vio->hindex; +} ; + +/*------------------------------------------------------------------------------ + * Replace command line by current history. + * + * This function is called from vty_next_line and vty_previous_line. + */ +static void +uty_cli_history_use(vty_io vio, int step) +{ + int index ; + unsigned new_len ; + unsigned old_len ; + char* hist ; + + VTY_ASSERT_LOCKED() ; + + /* See if have anything usable */ + index = vio->hp ; + + if ((step > 0) && (index == vio->hindex)) + return ; /* cannot step forward past the insertion point */ + + index += step ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + else if (index >= VTY_MAXHIST) ; + index = 0 ; + + if ((step < 0) && (index == vio->hindex)) + return ; /* cannot step back to the insertion point */ + + hist = vector_get_item(&vio->hist, index) ; + if (hist == NULL) + return ; /* cannot step to unused entry */ + + 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 ; + new_len = qs_set(&vio->cl, hist) ; + vio->cl.cp = new_len ; + + uty_cli_echo(vio, hist, new_len) ; + + if (old_len < new_len) + uty_cli_del_to_eol(vio) ; + + 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 ; + 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: + for (i = 0; i < vector_end(matched); i++) + { + if ((i % 6) == 0) + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out (vio, "%-10s ", (char*)vector_get_item(matched, i)); + } + + break; + + case CMD_ERR_NOTHING_TODO: + 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 1 /* 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" + */ +extern 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" + */ +extern 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" + */ +extern 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" + */ +extern 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 + */ +extern 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)); +} + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * In particular: get telnet window size. + */ +static void +uty_telnet_command(vty_io vio, keystroke stroke) +{ + uint8_t* p ; + uint8_t o ; + int left ; + + /* Echo to the other end if required */ + if (TELNET_OPTION_DEBUG) + { + 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 */ + if (stroke->flags != 0) + return ; /* 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) ; + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; +} ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h new file mode 100644 index 00000000..4fda2db8 --- /dev/null +++ b/lib/vty_cli.h @@ -0,0 +1,43 @@ +/* VTY Command Line Handler + * Copyright (C) 1997 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_CLI_H +#define _ZEBRA_VTY_CLI_H + +#include "vty_io.h" + +extern void uty_cli(vty_io vio) ; +extern void uty_cli_hist_add (vty_io vio, const char* cmd_line) ; +extern void uty_cli_want_more(vty_io vio) ; +extern void uty_cli_wait_more(vty_io vio) ; +extern void uty_cli_wipe(vty_io vio) ; +extern void uty_free_host_name(void) ; +extern void uty_check_host_name(void) ; + +extern void uty_will_echo (vty_io vio) ; +extern void uty_will_suppress_go_ahead (vty_io vio) ; +extern void uty_dont_linemode (vty_io vio) ; +extern void uty_do_window_size (vty_io vio) ; +extern void uty_dont_lflow_ahead (vty_io vio) ; + +#endif /* _ZEBRA_VTY_CLI_H */ diff --git a/lib/vty_io.c b/lib/vty_io.c new file mode 100644 index 00000000..15f90219 --- /dev/null +++ b/lib/vty_io.c @@ -0,0 +1,2202 @@ +/* VTY IO Functions + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "zebra.h" + +#include "vty.h" +#include "vty_io.h" +#include "vty_cli.h" +#include "qstring.h" +#include "keystroke.h" + +#include "memory.h" + +#include "prefix.h" +#include "filter.h" +#include "privs.h" +#include "sockunion.h" +#include "network.h" + +#include <arpa/telnet.h> +#include <sys/un.h> /* for VTYSH */ +#include <sys/socket.h> + +#define VTYSH_DEBUG 0 + +/*============================================================================== + * VTY Command Output -- base functions + * + * ALL vty command output ends up here. + * + * vty == NULL => vprintf(stdout, ...) + * VTY_SHELL => vprintf(stdout, ...) + * VTY_STDOUT => vprintf(stdout, ...) + * + * VTY_FILE => write(fd, ....) + * + * VTY_TERM => command FIFO + * VTY_SHELL_SERV => command FIFO + * + * During command processing the output sent here is held until the command + * completes. + */ + +/*------------------------------------------------------------------------------ + * 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) + */ +extern int +uty_vout(struct vty *vty, const char *format, va_list args) +{ + enum vty_type type ; + vty_io vio ; + int len ; + + VTY_ASSERT_LOCKED() ; + + /* Establish type of vty -- if any */ + if (vty != NULL) + { + vio = vty->vio ; + if (!vio->file.write_open) + return 0 ; /* discard output if not open ! */ + + type = vio->type ; + } + else + { + vio = NULL ; + type = VTY_STDOUT ; + } ; + + /* Output -- process depends on type */ + switch (type) + { + case VTY_STDOUT: + case VTY_SHELL: + len = vprintf (format, args); + break ; + + case VTY_FILE: + case VTY_TERM: + case VTY_SHELL_SERV: + + len = qs_vprintf(&vio->cmd_vbuf, format, args) ; + if (len > 0) + { + if (type == VTY_FILE) + len = write(vio->file.fd, vio->cmd_vbuf.body, len) ; + else + vio_fifo_put(&vio->cmd_obuf, vio->cmd_vbuf.body, len) ; + } ; + break ; + + default: + zabort("impossible VTY type") ; + } ; + + return len; +} ; + +/*------------------------------------------------------------------------------ + * Discard the current contents of the command FIFO + * + * TODO: worry about line control ?? + */ +extern void +uty_out_discard(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + vio_fifo_set_empty(&vio->cmd_obuf) ; +} ; + +/*============================================================================== + * The watch dog. + * + * The watch dog starts up every now and checks: + * + * * for changes to the host name, which should be reflected in the + * prompt for any terminals. + * + * * the death watch list + */ + +enum { vty_watch_dog_interval = 5 } ; + +static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ; +static int vty_watch_dog_thread(struct thread *thread) ; + +/*------------------------------------------------------------------------------ + * Watch dog action + */ +static void +uty_watch_dog_bark(void) +{ + uty_check_host_name() ; /* check for host name change */ + + /* TODO: death watch scan */ + + /* Set timer to go off again later */ + if (vty_cli_nexus) + qtimer_set(vty_watch_dog.qnexus, qt_add_monotonic(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) ; + } ; +} ; + +static void +uty_watch_dog_start() +{ + if (vty_cli_nexus) + vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, NULL) ; + + uty_watch_dog_bark() ; /* start up by barking the first time */ +} + +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) ; + } ; +} + +/*------------------------------------------------------------------------------ + * qnexus watch dog action + */ +static void +vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) +{ + VTY_LOCK() ; + uty_watch_dog_bark() ; + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * thread watch dog action + */ +static int +vty_watch_dog_thread(struct thread *thread) +{ + VTY_LOCK() ; + uty_watch_dog_bark() ; + VTY_UNLOCK() ; + return 0 ; +} ; + +/*============================================================================== + * Prototypes. + */ +static void uty_file_init_new(vio_file file, int fd, void* info) ; +static void uty_file_half_close(vio_file file) ; +static void uty_file_close(vio_file file) ; + +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) ; + +/*============================================================================== + * Creation and destruction of VTY objects + */ + +/*------------------------------------------------------------------------------ + * Allocate new vty struct + * + * Allocates and initialises vty_io structure, complete with: + * + * Output buffer + * Input buffer + * qpselect file -- added to CLI nexus ) if running CLI nexus + * qtimer ) + * + * Adds to the known vty's -- which locks/unlocks momentarily. + * + * Returns: new vty + */ +extern struct vty * +uty_new (int fd, enum vty_type type) +{ + struct vty *vty ; + struct vty_io* vio ; + + VTY_ASSERT_LOCKED() ; + + if (vty_watch_dog.anon == NULL) + uty_watch_dog_start() ; + + vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); + vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; + + vty->vio = vio ; + vio->vty = vty ; + + /* Zeroising the vty structure has set: + * + * node = 0 TODO: something better for node value ???? + * buf = NULL -- no command line, yet + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet + */ + confirm(AUTH_NODE == 0) ; /* default node type */ + + if (type == VTY_TERM) + vty->newline = "\r\n" ; + else + vty->newline = "\n" ; + + /* Zeroising the vty_io structure has set: + * + * vio_list both pointers NULL + * + * half_closed = 0 -- NOT half closed (important !) + * timed_out = 0 -- NOT timed out + * + * mon_list both pointers NULL + * + * name = NULL -- no name, yet + * + * cli_drawn = 0 -- not drawn + * 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 + * + * cli_do = 0 = cli_do_nothing + * + * cmd_wait_more = 0 -- not waiting for response to "--more--" + * + * 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 -- unset + * + * monitor = 0 -- not a monitor + * + * config = 0 -- not holder of "config" mode + */ + confirm(cli_do_nothing == 0) ; + + vio->type = type ; + + uty_file_init_new(&vio->file, fd, vio) ; + + vio->key_stream = keystroke_stream_new('\0') ; /* TODO: CSI ?? */ + + qs_init_new(&vio->ibuf, 0) ; + + qs_init_new(&vio->cli_prompt_for_node, 0) ; + + qs_init_new(&vio->cl, 0) ; + qs_init_new(&vio->clx, 0) ; + + qs_init_new(&vio->cli_vbuf, 0) ; + vio_fifo_init_new(&vio->cli_obuf, 4 * 1024) ; /* allocate in 4K lumps */ + + qs_init_new(&vio->cmd_vbuf, 0) ; + vio_fifo_init_new(&vio->cmd_obuf, 16 * 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 vty_sock, union sockunion *su) +{ + struct vty *vty ; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (vty_sock, VTY_TERM) ; + vio = vty->vio ; + + /* Set the action functions */ + if (vty_cli_nexus) + { + vio->file.action.read.qnexus = vty_read_qnexus ; + vio->file.action.write.qnexus = vty_write_qnexus ; + vio->file.action.timer.qnexus = vty_timer_qnexus ; + } + else + { + vio->file.action.read.thread = vty_read_thread ; + vio->file.action.write.thread = vty_write_thread ; + vio->file.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->file.v_timeout = vty_timeout_val; + + /* Use global 'lines' setting, otherwise is unset */ + if (host.lines >= 0) + vio->lines = host.lines; + else + vio->lines = -1; + + /* 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) ; + + /* Set CLI into state waiting for output to complete. */ + vio->cli_blocked = 1 ; + uty_file_set_write(&vio->file, on) ; + + /* Reject connection if password isn't set, and not "no password" */ + if ((host.password == NULL) && (host.password_encrypt == NULL) + && ! no_password_check) + { + uty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); + uty_half_close (vio); + return NULL; + } + + /* 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); + + 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 vty_sock) +{ + struct vty *vty ; + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + /* Allocate new vty structure and set up default values. */ + vty = uty_new (vty_sock, VTY_SHELL_SERV) ; + vio = vty->vio ; + + /* Set the action functions */ + if (vty_cli_nexus) + { + vio->file.action.read.qnexus = vtysh_read_qnexus ; + vio->file.action.write.qnexus = vty_write_qnexus ; + vio->file.action.timer.qnexus = NULL ; + } + else + { + vio->file.action.read.thread = vtysh_read_thread ; + vio->file.action.write.thread = vty_write_thread ; + vio->file.action.timer.thread = NULL ; + } ; + + vty->node = VIEW_NODE; + + /* Enable the command output to clear the output to date, and set cli + * state to blocked waiting for that output to complete. + */ + vio->cli_blocked = 1 ; + uty_file_set_write(&vio->file, on) ; + + 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->file.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. + * + * Shuts the read side and discards any buffered input. + * + * Leaves the output running, but places the VTY on "death watch". When + * all output completes and there is no queued command or anything else + * active, the VTY is finally put to sleep. + */ +extern void +uty_half_close (vty_io vio) +{ + char* line ; + + VTY_ASSERT_LOCKED() ; + + if (vio->half_closed) + return ; /* cannot do it again */ + + vio->half_closed = 1 ; + + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->file.fd) ; + + uty_file_half_close(&vio->file) ; + uty_set_monitor(vio, 0) ; + + keystroke_stream_free(vio->key_stream) ; + qs_free_body(&vio->ibuf) ; + + uty_cli_wipe(vio) ; + + while ((line = vector_ream_keep(&vio->hist)) != NULL) + XFREE(MTYPE_VTY_HIST, line) ; + + /* Hit the width, height and lines so that all output clears without + * interruption. + */ + vio->width = 0 ; + vio->height = 0 ; + vio->lines = 0 ; + + /* Make sure no longer holding the config symbol of power */ + uty_config_unlock(vio->vty, AUTH_NODE) ; + + /* Move to the death watch list */ + sdl_del(vio_list_base, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY. + * + * Shuts down everything and discards all buffers etc. etc. + * + * If not already on it, places the VTY on "death watch". When there is no + * queued command or anything else active, the VTY is finally put to sleep. + */ +extern void +uty_close (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + uty_file_close(&vio->file) ; /* bring the file to a complete stop */ + + uty_half_close(vio) ; /* deal with the input side, and place on + death watch -- if not already done */ + + qs_free_body(&vio->cli_prompt_for_node) ; + qs_free_body(&vio->cl) ; + qs_free_body(&vio->clx) ; + qs_free_body(&vio->cli_vbuf) ; + qs_free_body(&vio->cmd_vbuf) ; + + vio_fifo_reset_keep(&vio->cli_obuf) ; + vio_fifo_reset_keep(&vio->cmd_obuf) ; + + vio->vty->buf = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Closing down VTY completely. + * + * Shuts down everything and discards all buffers etc. etc. + * + * If not already on it, places the VTY on "death watch". When there is no + * queued command or anything else active, the VTY is finally put to sleep. + */ +extern void +uty_full_close (vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + uty_file_close(&vio->file) ; /* bring the file to a complete stop */ + + uty_half_close(vio) ; /* deal with the input side, and place on + death watch -- if not already done */ + + qs_free_body(&vio->cli_prompt_for_node) ; + qs_free_body(&vio->cl) ; + qs_free_body(&vio->clx) ; + qs_free_body(&vio->cli_vbuf) ; + qs_free_body(&vio->cmd_vbuf) ; + + vio_fifo_reset_keep(&vio->cli_obuf) ; + vio_fifo_reset_keep(&vio->cmd_obuf) ; + + vio->vty->buf = NULL ; +} ; + +/*============================================================================== + * Dealing with am I/O error on VTY + * + * 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_io_error(vty_io vio, const char* what) +{ + /* can no longer be a monitor ! */ + uty_set_monitor(vio, 0) ; + + /* if this is the first error, log it */ + if (vio->file.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_io_error()") ; + } ; + + vio->file.error_seen = errno ; + uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + type, what, vio->file.fd, safe_strerror(vio->file.error_seen)) ; + } ; + + return -1 ; +} ; + +/*============================================================================== + * vio_file level operations + */ + +/*------------------------------------------------------------------------------ + * Initialise a new vio_file structure. + * + * Requires that: the vio_file structure is not currently in use. + * + * if fd >= 0 then: file is open and ready read and write + * otherwise: file is not open + * + * there are no errors, yet. + * + * Sets timeout to no timeout at all -- timeout is optional. + */ +static void +uty_file_init_new(vio_file file, int fd, void* info) +{ + memset(file, 0, sizeof(struct vio_file)) ; + + /* 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 + */ + file->fd = fd ; + file->info = info ; + + file->read_open = (fd >= 0) ; + file->write_open = (fd >= 0) ; + + if (vty_cli_nexus) + { + file->qf = qps_file_init_new(NULL, NULL); + if (fd >= 0) + qps_add_file(vty_cli_nexus->selection, file->qf, file->fd, file->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_file_restart_timer(vio_file file) +{ + if (file->v_timeout != 0) + { + assert(file->action.timer.anon != NULL) ; + + if (vty_cli_nexus) + { + if (file->qtr == NULL) /* allocate qtr if required */ + file->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, file->info) ; + qtimer_set(file->qtr, qt_add_monotonic(QTIME(file->v_timeout)), + file->action.timer.qnexus) ; + } + else + { + if (file->t_timer != NULL) + thread_cancel (file->t_timer); + file->t_timer = thread_add_timer (vty_master, + file->action.timer.thread, file->info, file->v_timeout) ; + } ; + + file->timer_running = 1 ; + } + else if (file->timer_running) + { + if (vty_cli_nexus) + { + if (file->qtr != NULL) + qtimer_unset(file->qtr) ; + } + else + { + if (file->t_timer != NULL) + thread_cancel (file->t_timer) ; + } ; + + file->timer_running = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set a new timer value. + */ +extern void +uty_file_set_timer(vio_file file, unsigned long timeout) +{ + file->v_timeout = timeout ; + if (file->timer_running) + uty_file_restart_timer(file) ; +} ; + +/*------------------------------------------------------------------------------ + * Set read on/off -- restart timer. + */ +extern void +uty_file_set_read(vio_file file, bool on) +{ + if (file->fd < 0) + return ; + + if (on) + { + assert(file->action.read.anon != NULL) ; + + if (vty_cli_nexus) + { + qps_enable_mode(file->qf, qps_read_mnum, file->action.read.qnexus) ; + } + else + { + if (file->t_read != NULL) + thread_cancel(file->t_read) ; + + file->t_read = thread_add_read(vty_master, + file->action.read.thread, file->info, file->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + { + qps_disable_modes(file->qf, qps_read_mbit) ; + } + else + { + if (file->t_read != NULL) + thread_cancel (file->t_read) ; + } ; + } ; + + uty_file_restart_timer(file) ; +} ; + +/*------------------------------------------------------------------------------ + * Set write on/off -- restart timer. + */ +extern void +uty_file_set_write(vio_file file, bool on) +{ + if (file->fd < 0) + return ; + + if (on) + { + assert(file->action.write.anon != NULL) ; + + if (vty_cli_nexus) + { + qps_enable_mode(file->qf, qps_write_mnum, file->action.write.qnexus) ; + } + else + { + if (file->t_write != NULL) + thread_cancel(file->t_write) ; + + file->t_write = thread_add_write(vty_master, + file->action.write.thread, file->info, file->fd) ; + } ; + } + else + { + if (vty_cli_nexus) + { + qps_disable_modes(file->qf, qps_write_mbit) ; + } + else + { + if (file->t_write != NULL) + thread_cancel (file->t_write) ; + } ; + } ; + + uty_file_restart_timer(file) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vty file for reading. + * + * Sets timer to timeout for clearing any pending output. + */ +static void +uty_file_half_close(vio_file file) +{ + VTY_ASSERT_LOCKED() ; + + if (file->fd >= 0) + { + shutdown(file->fd, SHUT_RD) ; /* actual half close */ + + file->v_timeout = 30 ; /* for output to clear */ + uty_file_set_read(file, off) ; + } ; + + file->read_open = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vio_file, completely -- shut down any timer. + * + * Structure is cleared of everything except the last error ! + */ +static void +uty_file_close(vio_file file) +{ + VTY_ASSERT_LOCKED() ; + + if (file->fd >= 0) + close(file->fd) ; + + if (vty_cli_nexus && (file->fd >= 0)) + qps_remove_file(file->qf) ; + + if (file->qf != NULL) + qps_file_free(file->qf) ; + + if (file->t_read != NULL) + thread_cancel(file->t_write) ; + if (file->t_write != NULL) + thread_cancel(file->t_write) ; + + file->fd = -1 ; + file->qf = NULL ; + file->t_read = NULL ; + file->t_write = NULL ; + + file->info = NULL ; + file->action.read.anon = NULL ; + file->action.write.anon = NULL ; + file->action.timer.anon = NULL ; + + file->read_open = 0 ; + file->write_open = 0 ; + + if (file->qtr != NULL) + qtimer_free(file->qtr) ; + if (file->t_timer != NULL) + thread_cancel(file->t_timer) ; + + file->v_timeout = 0 ; + file->qtr = NULL ; + file->t_timer = NULL ; +} ; + +/*============================================================================== + * Reading from the VTY_TERM type file. + * + * 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. + */ + +/*------------------------------------------------------------------------------ + * Ready to read -> kicking CLI + * + * Have two CLI: one (trivial one) when waiting on "--more--", + * and the standard one. + * + * End up here when there is something ready to be read. + * + * Also ends up here when was write_ready and did not block in uty_write. + * + * 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 +uty_read_ready(vty_io vio) +{ + uty_file_set_read(&vio->file, off) ; /* restarts timer */ + + /* Execute the required command processor */ + if (vio->cmd_wait_more) + uty_cli_wait_more(vio) ; /* run "--more--" CLI */ + else + uty_cli(vio) ; /* run standard 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->file.fd == qf->fd) && (vio == vio->file.info)) ; + + uty_read_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->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + + vio->file.t_read = NULL ; /* implicitly */ + uty_read_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->file.read_open) + return -1 ; /* at EOF if not open */ + + get = read_nb(vio->file.fd, buf, sizeof(buf)) ; + if (get > 0) + keystroke_input(vio->key_stream, buf, get, steal) ; + else if (get < 0) + { + if (get == -1) + uty_io_error(vio, "read") ; + + vio->file.read_open = 0 ; + keystroke_input(vio->key_stream, NULL, 0, steal) ; + + get = -1 ; + } ; + + return get ; +} ; + +/*============================================================================== + * The write file 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 not written to the file while + * cmd_in_progress. + * + * The cli output takes precedence. + * + * Output of command stuff is subject to line_control, and may go through the + * "--more--" mechanism. + */ + +static bool uty_write(vty_io vio) ; +static int uty_flush_fifo(vty_io vio, vio_fifo vf, + struct vty_line_control* line_control) ; +static void uty_empty_out_fifos(vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Flush as much as possible of what there is. + * + * May end up with: + * + * * something in the buffers waiting to go, but output is currently + * threatening to block. + * + * in this case will have set write on, and things will progress when next + * write_ready. + * + * * otherwise: + * + * will be set write off, so does a read_ready in order t kick the CLI, + * which may wish to set either read or write on. + */ +static void +uty_write_ready(vty_io vio) +{ + bool blocked ; + + VTY_ASSERT_LOCKED() ; + + uty_file_set_write(&vio->file, off) ; /* restarts timer, too */ + + blocked = uty_write(vio) ; + + if (!blocked) + uty_read_ready(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * 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->file.fd == qf->fd) && (vio == vio->file.info)) ; + + uty_write_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->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + + vio->file.t_write = NULL; /* implicitly */ + uty_write_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 outputting everything available for output + * by write() threatening to block. + * + * Sets read on if enters cmd_wait_more state. + * + * Returns: true <=> blocked by I/O + * + * Note that this means that returns true iff sets write on. + */ +static bool +uty_write(vty_io vio) +{ + int ret ; + + VTY_ASSERT_LOCKED() ; + + /* empty the CLI FIFO + * + * NB: if the file is !write_open, or if it fails during output here + * and becomes !write_open, then ret == 0 -- as if everything + * has been written. + */ + ret = uty_flush_fifo(vio, &vio->cli_obuf, NULL) ; + if (ret != 0) + return 1 ; /* blocked by I/O */ + + if ((vio->cmd_in_progress) || (vio->cmd_wait_more)) + return 0 ; /* not blocked by I/O */ + + /* write from the command FIFO + * + * NB: if the file is !write_open, or if it fails during output here + * and becomes !write_open, then ret == 0 -- as if everything + * has been written. + */ + ret = uty_flush_fifo(vio, &vio->cmd_obuf, &vio->line_control) ; + if (ret == 1) + return 1 ; /* blocked by I/O */ + + if (ret == 2) + { + /* Want now to wait for "--more--" + * + * Note that this produces CLI output, which must deal with here. + */ + uty_cli_want_more(vio) ; /* NB: sets cmd_wait_more */ + + ret = uty_flush_fifo(vio, &vio->cli_obuf, NULL) ; + if (ret == 1) + return 1 ; /* blocked by I/O */ + + if (vio->file.write_open) + return 0 ; /* not blocked by I/O */ + } ; + + /* Reach here iff both CLI and command FIFOs are empty and is not + * cmd_in_progress + */ + vio->cli_blocked = 0 ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Flush the given FIFO to output -- subject to possible line control. + * + * If ends up needing to write more, sets write on. + * + * Returns: 0 => written everything there is -- or not (now) write_open + * 1 => written everything that could -- needs to write more + * 2 => written everything that could -- needs a "--more--" + */ +static int +uty_flush_fifo(vty_io vio, vio_fifo vf, struct vty_line_control* line_control) +{ + char* src ; + size_t have ; + int done ; + bool wait_more ; + + if (!vio->file.write_open) + { + uty_empty_out_fifos(vio) ; + return 0 ; + } ; + + wait_more = 0 ; + + while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + { + if (line_control != NULL) /* TODO: line control */ + { + /* Account for what happens if output have bytes from src... + * ... and if necessary reduce "have". + * + * set wait_more if now need to wait + */ + } ; + + done = write_nb(vio->file.fd, src, have) ; + + if (done < 0) + { + uty_io_error(vio, "write") ; + + vio->file.write_open = 0 ; + uty_empty_out_fifos(vio) ; + return 0 ; /* no longer open */ + } + + vio_fifo_got_upto(vf, src + done) ; + + if (done < (int)have) + { + if (line_control != NULL) + { + /* "put back" have - done bytes for next time */ + } ; + + uty_file_set_write(&vio->file, on) ; + return 1 ; /* output is full */ + } ; + + /* If now wants to wait for a "--more--", then exit + * + * Note that the line_control cannot tell if the place it wants to + * stop is, in fact, the end of the FIFO -- can only tell that + * now... + */ + if (wait_more) + return vio_fifo_empty(vf) ? 0 : 2 ; + } ; + + return 0 ; /* all gone */ +} ; + +/*------------------------------------------------------------------------------ + * Empty the output FIFOs + * + * This is for use when the output has failed or is closed. + */ +static void +uty_empty_out_fifos(vty_io vio) +{ + vio_fifo_set_empty(&vio->cli_obuf) ; + vio_fifo_set_empty(&vio->cmd_obuf) ; + + vio->cmd_wait_more = 0 ; +} ; + +/*============================================================================== + * 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) ; /* bring input side to a halt */ + + vio->timed_out = 1 ; /* why stopped */ +} ; + +/*------------------------------------------------------------------------------ + * 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->file.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_file file ; +}; + +/* 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_file_close(&listener->file) ; /* 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", gai_strerror (ret)); + 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() ; + + /* 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 (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", + safe_strerror(errno)); + umask (old_mask); + 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, safe_strerror(errno)); + + 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, + safe_strerror(errno)); + } ; + + 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", + safe_strerror (errno) ); + } + } + + 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_file_init_new(&listener->file, fd, listener) ; + + listener->type = type ; + + if (vty_cli_nexus) + listener->file.action.read.qnexus = vty_accept_qnexus ; + else + listener->file.action.read.thread = vty_accept_thread ; + + uty_file_set_read(&listener->file, 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_file_set_read(&listener->file, 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->file.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; + union sockunion su; + int ret; + unsigned int on; + struct prefix *p ; + char buf[SU_ADDRSTRLEN] ; + + VTY_ASSERT_LOCKED() ; + + /* We can handle IPv4 or IPv6 socket. */ + sockunion_init_new(&su, 0) ; + + sock = sockunion_accept (listener->file.fd, &su); + + if (sock < 0) + { + if (sock == -1) + uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", + safe_strerror (errno)); + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock) ; + 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", + sockunion2str (&su, buf, sizeof(buf))); + close (sock); + return 0; + } ; + + /* Final options (optional) */ + on = 1 ; + ret = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, + (void*)&on, sizeof (on)); + if (ret < 0) + uzlog (NULL, LOG_INFO, "can't set sockopt to sock %d: %s", + (int)sock, safe_strerror (errno)); + + /* All set -- create the VTY_TERM */ + uty_new_term(sock, &su); + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", + sockunion2str (&su, buf, sizeof(buf)), sock); + + return 0; +} + +/*------------------------------------------------------------------------------ + * Accept action -- create and dispatch VTY_SHELL_SERV + */ +static int +uty_accept_shell_serv (vty_listener listener) +{ + int sock ; + int ret ; + int client_len ; + struct sockaddr_un client ; + + VTY_ASSERT_LOCKED() ; + + client_len = sizeof(client); + memset (&client, 0, client_len); + + sock = accept(listener->file.fd, (struct sockaddr *) &client, + (socklen_t *) &client_len) ; + + if (sock < 0) + { + uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + safe_strerror (errno)); + return -1; + } + + /* Really MUST have non-blocking */ + ret = set_nonblocking(sock) ; /* issues WARNING if fails */ + if (ret < 0) + { + close(sock) ; + return -1 ; + } ; + + /* All set -- create the VTY_SHELL_SERV */ + if (VTYSH_DEBUG) + printf ("VTY shell accept\n"); + + uty_new_shell_serv(sock) ; + + /* Log new VTY */ + uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock); + return 0; +} + +/*============================================================================== + * Reading from the VTY_SHELL_SERV type file. + * + * 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_file_set_read(&vio->file, 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->file.fd == qf->fd) && (vio == vio->file.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->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + + vio->file.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 file. + * + * 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->file.read_open) + return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ + + qs_need(buf, 500) ; /* need a reasonable lump */ + qs_set_empty(buf) ; /* set cp = len = 0 */ + + get = read_nb(vio->file.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_io_error(vio, "read") ; + + vio->file.read_open = 0 ; + + return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ + } ; + } ; +} ; + +/*============================================================================== + * Output to vty which are set to "monitor". + * + * If there is something in the command FIFO and command is not in progress, + * then throw logging away -- console is busy dealing with the output from + * some command. + * + * Wipes the command line and flushes the output. If the CLI FIFO is now + * empty, add the logging line to it and flush. Enable read, so that the + * CLI will be reentered, and the command line restored in due course. + */ + +/*------------------------------------------------------------------------------ + * 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_io next ; + + VTY_ASSERT_LOCKED() ; + + next = sdl_head(vio_monitors_base) ; + + if (next == NULL) + return ; /* go no further if no "monitor" vtys */ + + /* Prepare line for output. */ + uvzlog_line(ll, zl, priority, format, va, 1) ; /* with crlf */ + + /* write to all known "monitor" vty + * + * While writing to a given "monitor" the monitor flag is cleared. This + * means that if the write fails, and logs a message, then will recurse + * through here -- but won't log to the monitor that has failed. + * + * If one of the other monitors fails during this process, will recurse + * again, now with two monitors with their monitor flags cleared. + * + * Once the output (and any recursed output) has completed, then the + * monitor flag is restored -- but only if the vty is still write_open. + * + * A monitor that is not write_open at the end of this, is removed from the + * monitors list. The current vio *cannot* be the current vio at a higher + * level in any recursion stack, because... if anything higher up the stack + * will have their monitor flag cleared, and therefore have been stepped + * over at the current level. + */ + while (next != NULL) + { + vio = next ; + + if ( vio->monitor /* may be temporarily not a monitor */ + && (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) ) + { + vio->monitor = 0 ; /* avoid recursion */ + + uty_cli_wipe(vio) ; + uty_write(vio) ; + + if (vio_fifo_empty(&vio->cli_obuf) && vio->file.write_open) + { + vio_fifo_put(&vio->cli_obuf, ll->line, ll->len) ; + uty_write(vio) ; + } ; + + uty_file_set_read(&vio->file, on) ; + + /* It is possible that something failed, so that is no longer + * write_open, and should no longer be a monitor. + */ + vio->monitor = vio->file.write_open ; + } ; + + next = sdl_next(vio, mon_list) ; + + /* take self off list if no onger a monitor */ + if (!vio->monitor) + sdl_del(vio_monitors_base, 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->file.fd, buf, len) ; + write(vio->file.fd, "\r\n", 2) ; + + vio = sdl_next(vio, mon_list) ; + } ; +} ; diff --git a/lib/vty_io.h b/lib/vty_io.h new file mode 100644 index 00000000..06cefe06 --- /dev/null +++ b/lib/vty_io.h @@ -0,0 +1,297 @@ +/* VTY IO Structure and Functions -- header + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_VTY_IO_H +#define _ZEBRA_VTY_IO_H + +#include <stdbool.h> +#include <errno.h> + +#include "uty.h" +#include "vty.h" +#include "vio_fifo.h" +#include "keystroke.h" +#include "thread.h" +#include "command.h" +#include "qstring.h" + +/*============================================================================== + * Here are structures and other definitions which are shared by: + * + * vty.c -- the main vty handler + * vty_cli.c -- which handles the command line stuff + * vty_io.c -- .... + * + * The "struct vty" is used extensively across the Quagga daemons, where it + * has two functions relating to command handling as: + * + * 1) a "file handle" for output produced by commands + * + * 2) the holder of some context -- notably the current command "node" -- for + * command execution to use + * + * The bulk of "struct vty" is, therefore, private to vty.c and is factored + * out into the "struct vty_io". + * + * To reduce the size of vty.c, some groups of functions are separated into: + * + * vty_cli.c -- which looks after the keystroke by keystroke handling + * of the command line. + * + */ + +/*------------------------------------------------------------------------------ + * VTY file structure + * + * Used + */ + +typedef int thread_action(struct thread *) ; + +union file_action +{ + qps_action* qnexus ; + thread_action* thread ; + void* anon ; +} ; + +union timer_action +{ + qtimer_action* qnexus ; + thread_action* thread ; + void* anon ; +} ; + +struct vio_file_actions +{ + union file_action read ; + union file_action write ; + union timer_action timer ; +}; + +typedef struct vio_file* vio_file ; +struct vio_file +{ + int fd ; + + void* info ; /* for action routines */ + + struct vio_file_actions action ; + + bool read_open ; /* read returns 0 if not open */ + bool write_open ; /* write completes instantly if not open */ + int error_seen ; /* non-zero => failed */ + + qps_file qf ; /* when running qnexus */ + + struct thread *t_read; /* when running threads */ + struct thread *t_write; + + unsigned long v_timeout; /* time-out in seconds -- 0 => none */ + bool timer_running ; /* true when timer is running */ + + qtimer qtr; /* when running qnexus */ + struct thread *t_timer; /* when running threads */ + +} ; + +struct vty_line_control +{ + int tba ; +} ; + +enum +{ + off = false, + on = true +}; + +/*------------------------------------------------------------------------------ + * + */ + +struct vty_io { + /* List of all vty_io objects */ + struct dl_list_pair(vty_io) vio_list ; + + bool half_closed ; /* => on death watch list */ + bool timed_out ; /* closed by timer */ + + /* List of all vty_io that are in monitor state */ + struct dl_list_pair(vty_io) mon_list ; + + /* The attached to this vty */ + struct vty* vty ; + + /* Type of VTY */ + enum vty_type type; + + /* File level stuff */ + struct vio_file file ; + + /* "name" of the VTY (for VTY_TERM is IP address) */ + char *name ; + + /* Keystroke stream and raw input buffer */ + keystroke_stream key_stream ; + qstring_t ibuf ; + + /*--------------------------------------------------------------------*/ + /* Command line and related state */ + + /* cli_drawn <=> the current prompt and user input occupy the current + * line on the screen. + * + * If cli_drawn is true, the following are valid: + * + * cli_prompt_len -- the length of the prompt part. + * + * cli_extra_len -- the length of any ^X at the cursor position + * (for when blocked waiting for queued command) + * + * cli_echo_suppress -- the user part of the command line is suppressed + * + * NB: cli_echo_suppress is only used for password entry. + */ + int cli_drawn ; + + int cli_prompt_len ; /* for drawn line (if any) */ + int cli_extra_len ; /* for for drawn line (if any) */ + + bool cli_echo_suppress ; /* non-zero => suppress cli echo */ + + /* "cache" for prompt -- when node or host name changes, prompt does */ + enum node_type cli_prompt_node ; + bool cli_prompt_set ; + qstring_t cli_prompt_for_node ; + + /* State of the CLI + * + * cli_blocked -- blocked from processing keystrokes + * cmd_in_progress -- command dispatched (may be queued) + * + * cli_wait_more -- is in "--more--" wait state + * + */ + bool cli_blocked ; + bool cmd_in_progress ; + + /* Command Line(s) + * + * cli_do -- when current command being prepared is completed (by + * CR/LF or otherwise) this says what there now is to be done. + * + * cl -- current command line being prepared. + * + * clx -- current command line being executed. + * + * NB: during command execution vty->buf is set to point at the '\0' + * terminated current command line being executed. + */ + enum cli_do cli_do ; + + qstring_t cl ; + qstring_t clx ; + + /* CLI output buffering */ + qstring_t cli_vbuf ; /* for uty_cli_out */ + vio_fifo_t cli_obuf ; + + /* Command output buffering */ + qstring_t cmd_vbuf ; /* for uty_vout() */ + vio_fifo_t cmd_obuf ; + + bool cmd_wait_more ; + + struct vty_line_control line_control ; + /* 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. */ + int width; + int height; + + /* Configure lines. */ + int lines; + + /* Terminal monitor. */ + bool monitor ; + + /* In configure mode. */ + bool config; +} ; + +/*============================================================================== + * Functions + */ + +extern struct vty* +uty_new (int fd, enum vty_type type) ; + +extern void +uty_open_listeners(const char *addr, unsigned short port, const char *path) ; +extern void +uty_close_listeners(void) ; + +extern void +uty_half_close (vty_io vio) ; +extern void +uty_close (vty_io vio) ; +extern void +uty_full_close (vty_io vio) ; +extern void +uty_watch_dog_stop(void) ; + +extern int +uty_out (struct vty *vty, const char *format, ...) PRINTF_ATTRIBUTE(2, 3) ; +extern int +uty_vout(struct vty *vty, const char *format, va_list args) ; +extern void +uty_out_discard(vty_io vio) ; + +extern void +uty_file_set_read(vio_file file, bool on) ; +extern void +uty_file_set_write(vio_file file, bool on) ; +extern void +uty_file_set_timer(vio_file file, unsigned long timeout) ; + +extern int +uty_read (vty_io vio, keystroke steal) ; +extern int +utysh_read (vty_io vio, qstring cl, qstring buf) ; + + +extern const char* +uty_get_name(vty_io vio) ; + +extern void +uty_set_monitor(vty_io vio, bool on) ; + +#endif /* _ZEBRA_VTY_IO_H */ diff --git a/lib/workqueue.h b/lib/workqueue.h index 5d2f2da2..9ff7cdb5 100644 --- a/lib/workqueue.h +++ b/lib/workqueue.h @@ -78,6 +78,13 @@ CONFIRM(offsetof(work_queue_item_t, args) == 0) ; #define WQ_UNPLUGGED (1 << 0) /* available for draining */ +typedef struct work_queue* work_queue ; + +typedef wq_item_status wq_workfunc(work_queue, work_queue_item); +typedef void wq_errorfunc(work_queue, work_queue_item); +typedef void wq_del_item_data(work_queue, work_queue_item); +typedef void wq_completion_func(work_queue); + struct work_queue { /* Everything but the specification struct is private @@ -91,23 +98,23 @@ struct work_queue * Public, must be set before use by caller. May be modified at will. */ struct { - /* optional opaque user data, global to the queue. */ + /* optional opaque user data, global to the queue. */ void *data; /* work function to process items with: * First argument is the workqueue queue. * Second argument is the item data */ - wq_item_status (*workfunc) (struct work_queue *, work_queue_item); + wq_workfunc* workfunc ; - /* error handling function, optional */ - void (*errorfunc) (struct work_queue *, work_queue_item); + /* error handling function -- optional */ + wq_errorfunc* errorfunc ; - /* callback to delete user specific item data */ - void (*del_item_data) (struct work_queue *, work_queue_item); + /* callback to delete user specific item data -- optional */ + wq_del_item_data* del_item_data ; - /* completion callback, called when queue is emptied, optional */ - void (*completion_func) (struct work_queue *); + /* completion callback, called when queue is emptied -- optional */ + wq_completion_func* completion_func ; /* max number of retries to make for item that errors */ unsigned int max_retries; diff --git a/tests/Makefile.am b/tests/Makefile.am index 33861328..d6e24db7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -5,8 +5,9 @@ AM_CFLAGS = $(PICFLAGS) AM_LDFLAGS = $(PILDFLAGS) noinst_PROGRAMS = testsig testbuffer testmemory heavy heavywq heavythread \ - aspathtest testprivs teststream testbgpcap ecommtest \ - testbgpmpattr testchecksum testvector testsymtab + aspathtest testprivs teststream testbgpcap ecommtest \ + testbgpmpattr testchecksum testvector testsymtab \ + testlistutil testsig_SOURCES = test-sig.c testbuffer_SOURCES = test-buffer.c @@ -23,6 +24,7 @@ testbgpmpattr_SOURCES = bgp_mp_attr_test.c testchecksum_SOURCES = test-checksum.c testvector_SOURCES = test-vector.c testsymtab_SOURCES = test-symtab.c +testlistutil_SOURCES = test-list_util.c testsig_LDADD = ../lib/libzebra.la @LIBCAP@ testbuffer_LDADD = ../lib/libzebra.la @LIBCAP@ @@ -39,3 +41,4 @@ testbgpmpattr_LDADD = ../lib/libzebra.la @LIBCAP@ -lm ../bgpd/libbgp.a testchecksum_LDADD = ../lib/libzebra.la @LIBCAP@ testvector_LDADD = ../lib/libzebra.la @LIBCAP@ testsymtab_LDADD = ../lib/libzebra.la @LIBCAP@ +testlistutil_LDADD = ../lib/libzebra.la @LIBCAP@ diff --git a/tests/test-list_util.c b/tests/test-list_util.c new file mode 100644 index 00000000..fc81a562 --- /dev/null +++ b/tests/test-list_util.c @@ -0,0 +1,2112 @@ +#include <zebra.h> +#include <list_util.h> +#include <string.h> + +/* List util torture tests + * + */ + +struct thread_master *master; /* allow lib/zebra.c to link ! */ + +/* prototypes */ +int main(int argc, char **argv); + +static void test_ssl(void); +static void test_sdl(void); +static void test_ddl(void); + +#define test_assert(true, message) \ + do { if (!(true)) test_assert_fail(#true, message, __func__, __LINE__) ; \ + } while (0) + +static void +test_assert_fail(const char* true, const char* message, const char* func, + int line) +{ + printf("*** %s %d: (%s) not true: %s\n", func, line, true, message) ; + +} ; + +/*============================================================================== + * The tests. + */ +int +main(int argc, char **argv) +{ + printf("Starting list_util tests -- %s\n", +#ifdef __GNUC__LIST_UTIL + "GNUC version" +#else + "not GNUC version" +#endif + " -- v0.01 26-Feb-2010") ; + + srand(22) ; /* Consistent testing required */ + + test_ssl() ; + test_sdl() ; + test_ddl() ; + + return 0; +} + +/*------------------------------------------------------------------------------ + * Construct a majic mark from two addresses + */ +static unsigned majic(void* a, void* b) +{ + uintptr_t z = (uintptr_t)a ^ (uintptr_t)b ^ (uintptr_t)&majic ; + return z ; +} ; + +/*============================================================================== + * Single Base, Single Link + * + * ssl_push(base, item, next) -- add at head of list + * ssl_del(base, item, next) -- delete from list + * ssl_del_head(base, next) -- delete head of list + * ssl_pop(dst, base, next) -- pop head of list, if any + * ssl_head(base) -- return head of list + * ssl_next(item, next) -- step to next item, if any + * + * Cases to cover: + * + * a) adding when list is empty + * b) adding when list is not empty + * c) deletion of first item and more than one item on list + * d) deletion of first item when only one item on the list + * e) deletion of arbitrary item (list implicitly not empty) + * f) deletion when item not on list and list not empty + * g) deletion when item not on list and list is empty + * h) deletion of NULL item and list not empty + * i) deletion of NULL item and list empty + * j) pop of head when list not empty + * k) pop of head when list contains one item + * l) pop of head when list empty + * m) deletion of head when list not empty + * n) deletion of head when contains one item + * o) deletion of head when list empty + * p) head when list not empty + * q) head when list is empty + * r) next when not last + * s) next when last + */ + +typedef struct ssl_test* ssl_test ; +struct ssl_test +{ + ssl_test next ; /* pointer at start of structure */ + + unsigned majic ; + char dummy ; + + ssl_test other_next ; /* pointer elsewhere in structure */ +} ; + +struct ssl_test_parent +{ + unsigned rubbish ; + char fred ; + + ssl_test base ; + + int z[5] ; +} ; + +static struct ssl_test_parent ssl_parent ; + +static void +test_ssl(void) +{ + ssl_test base = NULL ; + + ssl_test del = NULL ; + ssl_test other_del = NULL ; + ssl_test last = NULL ; + ssl_test first = NULL ; + + struct ssl_test dummy ; + + int n = 47 ; + + int i_del = 7 ; /* NB: neither of these may be 0 or 1 */ + int i_other_del = 37 ; + + ssl_test prev ; + ssl_test this ; + ssl_test other_this ; + ssl_test take ; + ssl_test temp ; + + int i ; + int ret ; + + static struct ssl_test_parent* other = &ssl_parent ; + + memset(other, 77, sizeof(struct ssl_test_parent)) ; + other->base = NULL ; + + /* Repeated insertion, starting from empty list + * + * a) adding when list is empty + * b) adding when list is not empty + * + * Creates lists for following tests. + */ + printf("=== Testing ssl -- Single Base, Single Link -- stuff\n") ; + + printf(" Creating list of items") ; + for (i = 1 ; i <= n ; ++i) + { + ssl_test this = calloc(1, sizeof(struct ssl_test)) ; + + if (last == NULL) + last = this ; + + this->majic = majic(this, &base) ; + + this->dummy = i ; + + ssl_push(base, this, next) ; + if (i & 1) + ssl_push(other->base, this, other_next) ; + else + ssl_push(ssl_parent.base, this, other_next) ; + + if (i == i_del) + del = this ; + if (i == i_other_del) + other_del = this ; + + first = this ; + + printf("+") ; + } ; + + test_assert((base == first) && (other->base == first), + "Failed to create consistent lists") ; + printf("\n") ; + + passert((del != base) && (del != last)) ; + passert((other_del != base) && (other_del != last)) ; + + /* Walk to check that have the expected items + * + * l) head when list not empty + * r) next when not last + * s) next when last + */ + printf(" Walking list of items") ; + + this = ssl_head(base) ; + test_assert(this == first, "ssl_head failed") ; + + this = ssl_head(other->base) ; + test_assert(this == first, "ssl_head failed") ; + + this = ssl_head(ssl_parent.base) ; + test_assert(this == first, "ssl_head failed") ; + + this = ssl_next(del, next) ; + test_assert((this == del->next) && (this != NULL), "ssl_next failed") ; + this = ssl_next(last, next) ; + test_assert((this == last->next) && (this == NULL), + "ssl_next failed at end") ; + + this = ssl_next(other_del, other_next) ; + test_assert((this == other_del->other_next) && (this != NULL), + "ssl_next failed") ; + this = ssl_next(last, other_next) ; + test_assert((this == last->other_next) && (this == NULL), + "ssl_next failed at end") ; + + this = base ; + other_this = other->base ; + + prev = NULL ; + i = n ; + while (1) + { + test_assert(this == other_this, "this and other_this not in step") ; + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + + printf(".") ; + this = ssl_next(this, next) ; + other_this = ssl_next(other_this, other_next) ; + } ; + printf("\n") ; + + /* Deletion specifically at the start of the list + * + * c) deletion of first item and more than one item on list + */ + printf(" Deleting the first item") ; + + this = base ; + first = base->next ; + ret = ssl_del(base, this, next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + test_assert(first == base, "ssl_del of first item failed") ; + + this = other->base ; + ret = ssl_del(other->base, this, other_next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + test_assert(first == other->base, "ssl_del of first item failed") ; + + printf("\n") ; + + --n ; /* one less on the lists ! */ + + /* Deletion of items from arbitrary place in list + * + * e) deletion of arbitrary item (list implicitly not empty) + */ + printf(" Deleting arbitrary items") ; + ret = ssl_del(base, del, next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + ret = ssl_del(ssl_parent.base, other_del, other_next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + printf("\n") ; + + /* Deletion of items from arbitrary place in list + * + * f) deletion when item not on list and list not empty + */ + printf(" Deleting non-existant items") ; + ret = ssl_del(base, &dummy, next) ; + test_assert(ret == -1, "ssl_del did not return -1") ; + ret = ssl_del(other->base, &dummy, other_next) ; + test_assert(ret == -1, "ssl_del did not return -1") ; + printf("\n") ; + + /* Deletion of NULL items + * + * h) deletion of NULL item and list not empty + */ + printf(" Deleting NULL items") ; + + this = NULL ; + + ret = ssl_del(base, this, next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + + ret = ssl_del(ssl_parent.base, this, other_next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + + printf("\n") ; + + /* Scan lists to check after deletion */ + printf(" Base list scan") ; + + this = base ; + prev = NULL ; + i = n ; + while (1) + { + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + if (i == i_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + + printf("*") ; + this = ssl_next(this, next) ; + } ; + printf("\n") ; + + printf(" Other list scan") ; + + other_this = other->base ; + prev = NULL ; + i = n ; + while (1) + { + if (other_this == NULL) + break ; + + test_assert(other_this != prev, "this is same as prev") ; + prev = other_this ; + + if (i == i_other_del) + --i ; + + test_assert(other_this->dummy == i--, "don't have the right dummy") ; + test_assert(other_this->majic == majic(other_this, &base), + "don't have the right majic") ; + + printf("*") ; + other_this = ssl_next(other_this, other_next) ; + } ; + printf("\n") ; + + /* Dismantle lists + * + * j) pop of head when list not empty + * k) pop of head when list contains one item + * l) pop of head when list empty + * p) head when list not empty + * q) head when list is empty + */ + printf(" Popping the head until list is empty\n") ; + printf(" Base list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = base ; + test_assert(this == ssl_head(base), "this is not head !") ; + + temp = ssl_pop(&take, base, next) ; + test_assert(this == take, "this is not same as deleted head !") ; + test_assert(temp == take, "temp is not same as deleted head !") ; + + if (this == NULL) + break ; + + test_assert(base == this->next, "ssl_pop broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + if (i == i_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(base == NULL, "Base list should be empty") ; + + this = ssl_head(base) ; + test_assert(this == NULL, "ssl_head of empty list failed") ; + + printf("\n") ; + + printf(" Other list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = other->base ; + test_assert(this == ssl_head(other->base), "this is not head !") ; + + if (i & 1) + temp = ssl_pop(&take, other->base, other_next) ; + else + temp = ssl_pop(&take, ssl_parent.base, other_next) ; + + test_assert(this == take, "this is not same as deleted head !") ; + test_assert(temp == take, "temp is not same as deleted head !") ; + + if (this == NULL) + break ; + + test_assert(other->base == this->other_next, "ssl_pop broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + if (i == i_other_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(other->base == NULL, "Other list should be empty") ; + + this = ssl_head(other->base) ; + test_assert(this == NULL, "ssl_head of empty list failed") ; + + printf("\n") ; + + /* Rebuild lists to do: + * + * m) deletion of head when list not empty + * n) deletion of head when contains one item + * o) deletion of head when list empty + */ + passert((base == NULL) && (other->base == NULL)) ; + + last = NULL ; + first = NULL ; + prev = NULL ; + printf(" Building list of items again") ; + for (i = 1 ; i <= n ; ++i) + { + ssl_test this = calloc(1, sizeof(struct ssl_test)) ; + + if (last == NULL) + last = this ; + + this->majic = majic(this, &base) ; + this->dummy = i ; + + ssl_push(base, this, next) ; + if (i & 1) + ssl_push(ssl_parent.base, this, other_next) ; + else + ssl_push(other->base, this, other_next) ; + + test_assert(this->next == prev, "broken ssl_push") ; + test_assert(this->other_next == prev, "broken ssl_push") ; + + test_assert(base == this, "broken ssl_push") ; + test_assert(other->base == this, "broken ssl_push") ; + + first = this ; + prev = this ; + + printf("+") ; + } ; + + printf("\n") ; + + printf(" Deleting the head until list is empty\n") ; + printf(" Base list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = base ; + + ssl_del_head(base, next) ; + + if (this == NULL) + break ; + + test_assert(base == this->next, "ssl_del_head broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(base == NULL, "Base list should be empty") ; + + printf("\n") ; + + printf(" Other list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = other->base ; + + if (i & 1) + ssl_del_head(ssl_parent.base, other_next) ; + else + ssl_del_head(other->base, other_next) ; + + if (this == NULL) + break ; + + test_assert(other->base == this->other_next, "ssl_del_head broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(other->base == NULL, "Other list should be empty") ; + + printf("\n") ; + + /* Final few tests + * + * d) deletion of first item when only one item on the list + * g) deletion when item not on list and list is empty + * i) deletion of NULL item and list empty + */ + + /* Deletion of items from arbitrary place in list */ + printf(" Miscellaneous") ; + + del->next = &dummy ; + ssl_push(base, del, next) ; + test_assert((base == del) && (del->next == NULL), "ssl_push failed ??") ; + ssl_del(base, del, next) ; + test_assert(base == NULL, "ssl_del of first and only item failed") ; + + other_del->other_next = &dummy ; + ssl_push(other->base, other_del, other_next) ; + test_assert((other->base == other_del) && (other_del->other_next == NULL), + "ssl_push failed ??") ; + ssl_del(other->base, other_del, other_next) ; + test_assert(other->base == NULL, "ssl_del of first and only item failed") ; + + ret = ssl_del(base, del, next) ; + test_assert(ret == -1, "ssl_del did not return -1") ; + test_assert(base == NULL, "ssl_del on empty list") ; + + ret = ssl_del(other->base, other_del, other_next) ; + test_assert(ret == -1, "ssl_del did not return -1") ; + test_assert(other->base == NULL, "ssl_del on empty list") ; + + this = NULL ; + + ret = ssl_del(base, this, next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + test_assert(base == NULL, "ssl_del on empty list") ; + + ret = ssl_del(other->base, this, other_next) ; + test_assert(ret == 0, "ssl_del did not return 0") ; + test_assert(other->base == NULL, "ssl_del on empty list") ; + + printf("\n") ; + +} ; + +/*============================================================================== + * Single Base, Double Link + * + * sdl_push(base, item, list) -- add at head of list + * sdl_del(base, item, list) -- delete from list + * sdl_pop(&dst, base, next) -- pop head of list, if any + * sdl_head(base) -- return head of list + * sdl_next(item, next) -- step to next item, if any + * sdl_prev(item, next) -- step to prev item, if any + * + * Cases to cover: + * + * a) adding when list is empty + * b) adding when list is not empty + * c) deletion of first item and more than one item on list + * d) deletion of first item when only one item on the list + * e) deletion of arbitrary item (list implicitly not empty) + * f) deletion of NULL item and list not empty + * g) deletion of NULL item and list empty + * h) pop of head when list not empty + * i) pop of head when list contains one item + * j) pop of head when list empty + * k) deletion of head when list not empty + * l) deletion of head when list contains one item + * m) deletion of head when list empty + * n) head when list not empty + * o) head when list is empty + * p) next when not last + * q) next when last + * r) prev when not first + * s) prev when first + * + * NB: unlike single link stuff, cannot attempt to remove item which is + * not on the list ! + */ + +typedef struct sdl_test* sdl_test ; +struct sdl_test +{ + struct dl_list_pair(sdl_test) list ; + + unsigned majic ; + char dummy ; + + struct dl_list_pair(sdl_test) other_list ; +}; + +struct sdl_test_parent +{ + long rubbish ; + char fred ; + + sdl_test base ; + + int z[7] ; +} ; + +static struct sdl_test_parent sdl_parent ; + +static void +test_sdl(void) +{ + sdl_test base = NULL ; + + sdl_test del = NULL ; + sdl_test other_del = NULL ; + sdl_test last = NULL ; + sdl_test first = NULL ; + + struct sdl_test dummy ; + + int n = 57 ; + + int i_del = 9 ; /* NB: neither of these may be 0 or 1 */ + int i_other_del = 49 ; + + sdl_test prev ; + sdl_test this ; + sdl_test other_this ; + sdl_test take ; + sdl_test temp ; + + int i ; + + static struct sdl_test_parent* other = &sdl_parent ; + + memset(other, 99, sizeof(struct sdl_test_parent)) ; + other->base = NULL ; + + /* Repeated insertion, starting from empty list + * + * a) adding when list is empty + * b) adding when list is not empty + * + * Creates lists for following tests. + */ + printf("=== Testing sdl -- Single Base, Double Link -- stuff\n") ; + + printf(" Creating list of items") ; + for (i = 1 ; i <= n ; ++i) + { + sdl_test this = calloc(1, sizeof(struct sdl_test)) ; + + if (last == NULL) + last = this ; + + this->majic = majic(this, &base) ; + + this->dummy = i ; + + sdl_push(base, this, list) ; + if (i & 1) + sdl_push(other->base, this, other_list) ; + else + sdl_push(sdl_parent.base, this, other_list) ; + + if (i == i_del) + del = this ; + if (i == i_other_del) + other_del = this ; + + first = this ; + + printf("+") ; + } ; + + test_assert((base == first) && (other->base == first), + "Failed to create consistent lists") ; + + printf("\n") ; + + passert((del != base) && (del != last)) ; + passert((other_del != base) && (other_del != last)) ; + + /* Walk to check that have the expected items + * + * n) head when list not empty + * p) next when not last + * q) next when last + * r) prev when not first + * s) prev when first + */ + printf(" Walking list of items") ; + + this = sdl_head(base) ; + test_assert(this == first, "sdl_head failed") ; + + this = sdl_head(other->base) ; + test_assert(this == first, "sdl_head failed") ; + + this = sdl_head(sdl_parent.base) ; + test_assert(this == first, "sdl_head failed") ; + + /* next on both lists */ + this = sdl_next(first, list) ; + test_assert((this == first->list.next) && (this != NULL), + "sdl_next failed at start") ; + this = sdl_next(del, list) ; + test_assert((this == del->list.next) && (this != NULL), "sdl_next failed") ; + this = sdl_next(last, list) ; + test_assert((this == last->list.next) && (this == NULL), + "sdl_next failed at end") ; + + this = sdl_next(first, other_list) ; + test_assert((this == first->other_list.next) && (this != NULL), + "sdl_next failed at start") ; + this = sdl_next(other_del, other_list) ; + test_assert((this == other_del->other_list.next) && (this != NULL), + "sdl_next failed") ; + this = sdl_next(last, other_list) ; + test_assert((this == last->other_list.next) && (this == NULL), + "sdl_next failed at end") ; + + /* prev on both lists */ + this = sdl_prev(first, list) ; + test_assert((this == first->list.prev) && (this == NULL), + "sdl_prev failed at start") ; + this = sdl_prev(del, list) ; + test_assert((this == del->list.prev) && (this != NULL), "sdl_prev failed") ; + this = sdl_prev(last, list) ; + test_assert((this == last->list.prev) && (this != NULL), + "sdl_prev failed at end") ; + + this = sdl_prev(first, other_list) ; + test_assert((this == first->other_list.prev) && (this == NULL), + "sdl_prev failed at start") ; + this = sdl_prev(other_del, other_list) ; + test_assert((this == other_del->other_list.prev) && (this != NULL), + "sdl_prev failed") ; + this = sdl_prev(last, other_list) ; + test_assert((this == last->other_list.prev) && (this != NULL), + "sdl_prev failed at end") ; + + this = base ; + other_this = other->base ; + + prev = NULL ; + i = n ; + while (1) + { + test_assert(this == other_this, "this and other_this not in step") ; + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + + printf(".") ; + this = sdl_next(this, list) ; + other_this = sdl_next(other_this, other_list) ; + } ; + printf("\n") ; + + /* Deletion specifically at the start of the list + * + * c) deletion of first item and more than one item on list + */ + printf(" Deleting the first item") ; + + this = base ; + first = base->list.next ; + sdl_del(base, this, list) ; + test_assert(first == base, "sdl_del of first item failed") ; + test_assert((base == NULL) || (base->list.prev == NULL), "sdl_del failed") ; + + this = other->base ; + sdl_del(sdl_parent.base, this, other_list) ; + test_assert(first == other->base, "sdl_del of first item failed") ; + test_assert((base == NULL) || (base->other_list.prev == NULL), + "sdl_del failed") ; + + printf("\n") ; + + --n ; /* one less on the lists ! */ + + /* Deletion of items from arbitrary place in list + * + * e) deletion of arbitrary item (list implicitly not empty) + */ + printf(" Deleting arbitrary items") ; + sdl_del(base, del, list) ; + test_assert((base == NULL) || (base->list.prev == NULL), "sdl_del failed") ; + sdl_del(sdl_parent.base, other_del, other_list) ; + test_assert((base == NULL) || (base->other_list.prev == NULL), + "sdl_del failed") ; + printf("\n") ; + + /* Deletion of NULL items + * + * f) deletion of NULL item and list not empty + */ + printf(" Deleting NULL items") ; + this = NULL ; + sdl_del(base, this, list) ; + test_assert((base == NULL) || (base->list.prev == NULL), "sdl_del failed") ; + sdl_del(other->base, this, other_list) ; + test_assert((base == NULL) || (base->other_list.prev == NULL), + "sdl_del failed") ; + printf("\n") ; + + /* Scan lists to check after deletion */ + printf(" Base list scan") ; + + this = base ; + prev = NULL ; + i = n ; + while (1) + { + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + test_assert(this->list.prev == prev, "broken prev pointer") ; + + if (i == i_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("*") ; + + prev = this ; + this = sdl_next(this, list) ; + + test_assert(this == prev->list.next, "broken sdl_next") ; + if (this != NULL) + test_assert(prev == sdl_prev(this, list), "broken sdl_prev") ; + } ; + printf("\n") ; + + printf(" Other list scan") ; + + this = other->base ; + prev = NULL ; + i = n ; + while (1) + { + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + test_assert(this->other_list.prev == prev, "broken prev pointer") ; + + if (i == i_other_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + + printf("*") ; + + prev = this ; + this = sdl_next(this, other_list) ; + + test_assert(this == prev->other_list.next, "broken sdl_next") ; + if (this != NULL) + test_assert(prev == sdl_prev(this, other_list), "broken sdl_prev") ; + } ; + printf("\n") ; + + /* Dismantle lists + * + * h) pop of head when list not empty + * i) pop of head when list contains one item + * j) pop of head when list empty + * o) head when list is empty + */ + printf(" Popping the head until list is empty\n") ; + printf(" Base list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = sdl_head(base) ; + test_assert(this == base, "broken sdl_head !") ; + + temp = sdl_pop(&take, base, list) ; + test_assert(this == take, "take is not same as deleted head !") ; + test_assert(this == temp, "temp is not same as deleted head !") ; + if (base != NULL) + test_assert(base->list.prev == NULL, "sdl_pop failed") ; + + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + if (i == i_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(base == NULL, "Base list should be empty") ; + + this = sdl_head(base) ; + test_assert(this == NULL, "sdl_head of empty list failed") ; + + printf("\n") ; + + printf(" Other list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = sdl_head(other->base) ; + test_assert(this == other->base, "broken sdl_head !") ; + + if (i & 1) + temp = sdl_pop(&take, sdl_parent.base, other_list) ; + else + temp = sdl_pop(&take, other->base, other_list) ; + + test_assert(this == take, "take is not same as deleted head !") ; + test_assert(this == temp, "temp is not same as deleted head !") ; + if (other->base != NULL) + test_assert(other->base->other_list.prev == NULL, + "sdl_pop failed") ; + if (this == NULL) + break ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + if (i == i_other_del) + --i ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(other->base == NULL, "Other list should be empty") ; + + this = sdl_head(other->base) ; + test_assert(this == NULL, "sdl_head of empty list failed") ; + + printf("\n") ; + + /* Rebuild lists to do: + * + * k) deletion of head when list not empty + * l) deletion of head when list contains one item + * m) deletion of head when list empty + */ + passert((base == NULL) && (other->base == NULL)) ; + + last = NULL ; + first = NULL ; + prev = NULL ; + printf(" Building list of items again") ; + for (i = 1 ; i <= n ; ++i) + { + sdl_test this = calloc(1, sizeof(struct sdl_test)) ; + + if (last == NULL) + last = this ; + + this->majic = majic(this, &base) ; + this->dummy = i ; + + sdl_push(base, this, list) ; + if (i & 1) + sdl_push(sdl_parent.base, this, other_list) ; + else + sdl_push(other->base, this, other_list) ; + + test_assert(this->list.next == prev, "broken sdl_push") ; + test_assert(this->other_list.next == prev, "broken sdl_push") ; + + test_assert(base == this, "broken sdl_push") ; + test_assert(other->base == this, "broken sdl_push") ; + + first = this ; + prev = this ; + + printf("+") ; + } ; + + printf("\n") ; + + printf(" Deleting the head until list is empty\n") ; + printf(" Base list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = base ; + + sdl_del_head(base, list) ; + + if (this == NULL) + break ; + + test_assert(base == this->list.next, "sdl_del_head broken") ; + if (base != NULL) + test_assert(base->list.prev == NULL, "sdl_del_head broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(base == NULL, "Base list should be empty") ; + + printf("\n") ; + + printf(" Other list") ; + + prev = NULL ; + i = n ; + while (1) + { + this = other->base ; + + if (i & 1) + sdl_del_head(other->base, other_list) ; + else + sdl_del_head(sdl_parent.base, other_list) ; + + if (this == NULL) + break ; + + test_assert(other->base == this->other_list.next, "sdl_del_head broken") ; + if (other->base != NULL) + test_assert(other->base->other_list.prev == NULL, + "sdl_del_head broken") ; + + test_assert(this != prev, "this is same as prev") ; + prev = this ; + + test_assert(this->dummy == i--, "don't have the right dummy") ; + test_assert(this->majic == majic(this, &base), + "don't have the right majic") ; + printf("-") ; + } ; + test_assert(i == 0, "not the expected final value of 'i'") ; + test_assert(other->base == NULL, "Other list should be empty") ; + + printf("\n") ; + + /* Final few tests + * + * d) deletion of first item when only one item on the list + * g) deletion of NULL item and list empty + */ + + /* Deletion of items from arbitrary place in list */ + printf(" Miscellaneous") ; + + del->list.next = &dummy ; + sdl_push(base, del, list) ; + test_assert((base == del) && (del->list.next == NULL), "sdl_push failed ??") ; + sdl_del(base, del, list) ; + test_assert(base == NULL, "sdl_del of first and only item failed") ; + + other_del->other_list.next = &dummy ; + sdl_push(other->base, other_del, other_list) ; + test_assert((other->base == other_del) && (other_del->other_list.next == NULL), + "sdl_push failed ??") ; + sdl_del(other->base, other_del, other_list) ; + test_assert(other->base == NULL, "sdl_del of first and only item failed") ; + + this = NULL ; + sdl_del(base, this, list) ; + test_assert(base == NULL, "sdl_del of NULL item with empty list") ; + + sdl_del(other->base, this, other_list) ; + test_assert(other->base == NULL, "sdl_del of NULL item with empty list") ; + + printf("\n") ; +} ; + +/*============================================================================== + * Double Base, Double Link + * + * ddl_init(base) -- initialise base + * ddl_push(base, item, list) -- insert at head of list + * ddl_append(base, item, list) -- insert at tail of list + * ddl_in_after(after, base, item, list) -- insert after + * ddl_in_before(before, base, item, list) -- insert before + * ddl_pop(&dst, base, next) -- pop head of list, if any + * ddl_crop(&dst, base, next) -- crop tail of list, if any + * ddl_del(base, item, list) -- delete from list + * ddl_del_head(base, next) -- delete head of list + * ddl_del_tail(base, next) -- delete tail of list + * ddl_head(base) -- return head of list + * ddl_tail(base) -- return tail of list + * ddl_next(item, next) -- step to next item, if any + * ddl_prev(item, next) -- step to prev item, if any + * + * Cases to cover: + */ + +/* Testing runs two lists through struct ddt_item objects. */ + +enum list { a_list, b_list, list_count } ; + +typedef struct ddt_item* ddt_item ; + +struct ddt_list_pair dl_list_pair(ddt_item) ; /* Example struct constructor */ +struct ddt_base_pair dl_base_pair(ddt_item) ; + +typedef struct dl_base_pair(ddt_item) ddt_base_pair_t ; + /* Example typedef constructor */ +typedef struct dl_base_pair(ddt_item)* p_ddt_base_pair ; + /* Example typedef constructor */ + +typedef struct ddt_list_pair* ddt_list_pair ; +typedef struct ddt_base_pair* ddt_base_pair ; + +typedef struct ddt_rank* ddt_rank ; + +struct ddt_rank /* information for each list */ +{ + struct ddt_list_pair list ; /* the thing we are testing */ + + int lister ; + int list_found ; + unsigned majic ; + + int ordinal ; +}; + +struct ddt_item /* the test items */ +{ + struct ddt_rank a ; + + char a_rubbish[21] ; + + struct ddt_rank b ; + + char b_rubbish[19] ; +} ; + +/* The test list base pairs, and pointers to the actual bases, for use in + * the verification code. + */ +static ddt_base_pair ddt_bases[list_count] ; + +/* For some tests want to construct lists and know the first, last and + * somewhere between items. + */ + +enum where { first, middle, last, where_count } ; + +struct ddt_test_list_items +{ + struct + { + ddt_item where[where_count] ; + } list[list_count] ; +} ; + +/*------------------------------------------------------------------------------ + * The test list items -- keep track here also for use in verification. + */ +enum { ddt_max_items = 1000 } ; + +static unsigned ddt_item_count = 0 ; +static unsigned ddt_item_alloc = 0 ; +static ddt_item ddt_items[ddt_max_items] ; + +static inline ddt_item +ddt_new_item(void) +{ + ddt_item item ; + + assert(ddt_item_count <= ddt_item_alloc) ; + + if (ddt_item_count == ddt_item_alloc) + { + assert(ddt_item_alloc < ddt_max_items) ; + ddt_items[ddt_item_alloc++] = malloc(sizeof(struct ddt_item)) ; + } ; + + item = ddt_items[ddt_item_count++] ; + + memset(item, ddt_item_count & 0xFF, sizeof(struct ddt_item)) ; + + item->a.lister = 0 ; + item->b.lister = 0 ; + + item->a.ordinal = 0 ; + item->b.ordinal = 0 ; + + return item ; +} ; + +/*------------------------------------------------------------------------------ + * Given an item and a list ordinal, return pointer to "rank" for item. + */ +static inline ddt_rank +ddt_get_rank(ddt_item item, enum list l) +{ + if (item == NULL) + return NULL ; + + if (l == a_list) + return &item->a ; + if (l == b_list) + return &item->b ; + + assert(0) ; +} ; + +/*------------------------------------------------------------------------------ + * Keep track of what should be found on the lists, and majic marker to check + * that don't get lost and point into space. + */ +static inline unsigned +ddt_get_majic(ddt_item item, enum list l) +{ + return majic(item, ddt_bases[l]) ; +} ; + +static void +ddt_test_list_add(ddt_item item, enum list l) +{ + ddt_rank rank = ddt_get_rank(item, l) ; + + test_assert(rank->lister == 0, "already on list") ; + + rank->lister = 1 ; + rank->majic = ddt_get_majic(item, l) ; + rank->ordinal = 0 ; /* unknown ordinal */ +} ; + +static void +ddt_test_list_del(ddt_item item, enum list l) +{ + ddt_rank rank ; + + if (item == NULL) + return ; + + rank = ddt_get_rank(item, l) ; + + test_assert(rank->lister == 1, "not on list") ; + + rank->lister = 0 ; + rank->majic = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Verification code. + * + * Blunt instrument to check that all known lists are valid. Checks: + * + * * bases are both NULL together, or both not NULL. + * + * * first and last items on the list have suitable prev/next pointers. + * + * * walk list to confirm, for each item: + * + * -- prev pointer valid for each item + * -- item majic is correct (so not pointing somewhere invalid) + * -- item is supposed to be on the list + * -- item has not already been seen on list (list bent) + * -- ordinal, if not zero, is bigger than any previous non-zero ordinal + * + * * last item visited on walk is what the tail points to + * + * * for any items which are supposed to be on list, but were not found + */ +static void +ddt_verify_lists(void) +{ + ddt_base_pair base ; + ddt_rank r ; + ddt_item this ; + ddt_item prev ; + int l ; + int i ; + + /* Wash the found flags */ + for (l = 0 ; l < list_count ; ++l) + for (i = 0 ; i < (int)ddt_item_count ; ++i) + ddt_get_rank(ddt_items[i], l)->list_found = 0 ; + + /* Walk the lists */ + for (l = 0 ; l < list_count ; ++l) + { + int ordinal = 0 ; + + base = ddt_bases[l] ; + if (base == NULL) + continue ; + + if ((base->head == NULL) || (base->tail == NULL)) + test_assert(base->head == base->tail, "broken list bases") ; + else + { + r = ddt_get_rank(base->head, l) ; + test_assert(r->list.prev == NULL, "broken list first item->prev") ; + r = ddt_get_rank(base->tail, l) ; + test_assert(r->list.next == NULL, "broken list last item->next") ; + + this = base->head ; + prev = NULL ; + + while (this != NULL) + { + r = ddt_get_rank(this, l) ; + + test_assert(r->list.prev == prev, "broken item->prev") ; + + test_assert(r->lister, "should not be on this list") ; + + test_assert(!r->list_found, "circular list") ; + r->list_found = 1 ; + + if (r->ordinal != 0) + { + test_assert(r->ordinal > ordinal, "list out of order") ; + ordinal = r->ordinal ; + } + + test_assert(r->majic == ddt_get_majic(this, l), + "wrong sort of majic") ; + prev = this ; + this = r->list.next ; + } ; + + test_assert(base->tail == prev, "broken tail pointer") ; + } ; + } ; + + /* Verify that did not miss anything should have found */ + /* Wash the found flags */ + for (l = 0 ; l < list_count ; ++l) + for (i = 0 ; i < (int)ddt_item_count ; ++i) + { + r = ddt_get_rank(ddt_items[i], l) ; + + if (r->lister) + test_assert(r->list_found, "should have found this on list") ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Reset the test list handling + * + */ +static void +ddt_reset_lists(void) +{ + int l ; + + for (l = 0 ; l < list_count ; ++l) + ddl_init(*(ddt_bases[l])) ; + + ddt_item_count = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Make lists with 'n' items each. + * + * If 'n' 0..3, makes lists with exactly that many items. + * + * Otherwise, list length has +/- 25% jitter. + */ +static void +ddt_test_make_lists(struct ddt_test_list_items* test, int n) +{ + ddt_base_pair base ; + ddt_item item ; + ddt_rank rank ; + enum list l ; + int t ; + int m ; + + int req[list_count] ; + int mid[list_count] ; + + /* Capture the requirements */ + t = 0 ; + m = 1 ; + for (l = 0 ; l < list_count ; ++l) + { + m += m ; + int j = (n + 1) / 4 ; + + if (n <= 3) + req[l] = n ; + else + req[l] = n - j + (rand() % (j + j + 1)) ; + + mid[l] = req[l] / 2 ; + + t += req[l] ; + + test->list[l].where[first] = NULL ; + test->list[l].where[middle] = NULL ; + test->list[l].where[last] = NULL ; + } ; + + ddt_reset_lists() ; + + /* Have t = total number of items still required + * m = 2^n -- where there are 'n' lists + */ + while (t != 0) + { + int b ; + int r ; + r = (rand() % (m - 1)) + 1 ; /* bit pattern, at least one set */ + + item = ddt_new_item() ; + + b = 1 ; + for (l = 0 ; l < list_count ; ++l) + { + if ((req[l] != 0) && ((r & b) != 0)) + { + --req[l] ; + --t ; + + ddt_test_list_add(item, l) ; + + if (test->list[l].where[first] == NULL) + test->list[l].where[first] = item ; + + if (mid[l]-- == 0) + test->list[l].where[middle] = item ; + + test->list[l].where[last] = item ; + + base = ddt_bases[l] ; + rank = ddt_get_rank(item, l) ; + + if (base->head == NULL) + { + base->head = item ; + base->tail = item ; + rank->list.next = NULL ; + rank->list.prev = NULL ; + } + else if (rand() & 1) + { + rank->list.next = base->head ; + rank->list.prev = NULL ; + ddt_get_rank(base->head, l)->list.prev = item ; + base->head = item ; + } + else + { + rank->list.next = NULL ; + rank->list.prev = base->tail ; + ddt_get_rank(base->tail, l)->list.next = item ; + base->tail = item ; + } + } ; + b <<= 1 ; + } + } ; + + /* Number the items */ + for (l = 0 ; l < list_count ; ++l) + { + int o = 0 ; + + base = ddt_bases[l] ; + + item = base->head ; + while (item != NULL) + { + rank = ddt_get_rank(item, l) ; + rank->ordinal = ++o ; /* first is 1 */ + item = rank->list.next ; + } ; + } ; + + ddt_verify_lists() ; +} ; + +/*------------------------------------------------------------------------------ + * ddl_init(base) -- initialise base + * ddl_push(base, item, list) -- insert at head of list + * ddl_append(base, item, list) -- insert at tail of list + * ddl_in_after(after, base, item, list) -- insert after + * ddl_in_before(before, base, item, list) -- insert before + * ddl_pop(&dst, base, next) -- pop head of list, if any + * ddl_crop(&dst, base, next) -- crop tail of list, if any + * ddl_del(base, item, list) -- delete from list + * ddl_del_head(base, next) -- delete head of list + * ddl_del_tail(base, next) -- delete tail of list + * ddl_head(base) -- return head of list + * ddl_tail(base) -- return tail of list + * ddl_next(item, next) -- step to next item, if any + * ddl_prev(item, next) -- step to prev item, if any + */ + +static struct ddt_parent +{ + char zlxq[37] ; + + struct ddt_base_pair base ; + + char qxlz[45] ; +} ddt_parent ; + +static void +test_ddl(void) +{ + struct ddt_base_pair a_base ; + struct ddt_parent* b ; + + struct ddt_test_list_items test ; + int n ; + int o ; + + int base_n = 23 ; + int rand_n = 17 ; + + printf("=== Testing ddl -- Double Base, Double Link -- stuff\n") ; + + /* Initialise the test support */ + ddt_bases[a_list] = &a_base ; + ddt_bases[b_list] = &ddt_parent.base ; + + ddt_item_count = 0 ; + ddt_item_alloc = 0 ; + + /* Initialise the list bases */ + b = &ddt_parent ; + memset(b, 42, sizeof(struct ddt_parent)) ; + + ddl_init(a_base) ; + ddl_init(b->base) ; + + ddt_verify_lists() ; /* start as mean to go on */ + + + /* ddl_push(base, item, list) -- insert at head of list + * + * Cases: (a) empty list + * (b) list with one item + * (c) list with multiple items + */ + printf(" ddl_push test") ; + ddt_reset_lists() ; + + n = base_n + (rand() % rand_n) ; + while (n) + { + ddt_item item ; + int r ; + + printf(".") ; + + item = ddt_new_item() ; + r = (rand() % 3) + 1 ; + + if (r & 1) + { + ddl_push(a_base, item, a.list) ; + test_assert(a_base.head == item, "ddl_push broken") ; + ddt_test_list_add(item, a_list) ; + item->a.ordinal = n ; + } ; + ddt_verify_lists() ; + + if (r & 2) + { + ddl_push(b->base, item, b.list) ; + test_assert(b->base.head == item, "ddl_push broken") ; + ddt_test_list_add(item, b_list) ; + item->b.ordinal = n ; + } ; + ddt_verify_lists() ; + + --n ; + } ; + printf("\n") ; + + /* ddl_append(base, item, list) -- insert at tail of list + * + * Cases: (a) empty list + * (b) list with one item + * (c) list with multiple items + */ + printf(" ddl_append test") ; + ddt_reset_lists() ; + + n = base_n + (rand() % rand_n) ; + o = 0 ; + while (n) + { + ddt_item item ; + int r ; + + printf(".") ; + ++o ; + + item = ddt_new_item() ; + r = (rand() % 3) + 1 ; + + if (r & 1) + { + ddl_append(a_base, item, a.list) ; + test_assert(a_base.tail == item, "ddl_append broken") ; + ddt_test_list_add(item, a_list) ; + item->a.ordinal = o ; + } ; + ddt_verify_lists() ; + + if (r & 2) + { + ddl_append(b->base, item, b.list) ; + test_assert(b->base.tail == item, "ddl_append broken") ; + ddt_test_list_add(item, b_list) ; + item->b.ordinal = o ; + } ; + ddt_verify_lists() ; + + --n ; + } ; + printf("\n") ; + + /* ddl_in_after(after, base, item, list) -- insert after + * + * Cases: (a) after first and only (so is also last) + * (b) after first when more than one + * (c) after last when more than one + * (d) after something between + */ + printf(" ddl_in_after test") ; + + n = base_n + (rand() % rand_n) ; + while (n) + { + ddt_item item ; + ddt_item after ; + + printf(".") ; + ddt_test_make_lists(&test, n) ; + + item = ddt_new_item() ; + after = test.list[a_list].where[n % where_count] ; + ddl_in_after(after, a_base, item, a.list) ; + test_assert(after->a.list.next == item, "ddl_in_after broken") ; + test_assert(item->a.list.prev == after, "ddl_in_after broken") ; + ddt_test_list_add(item, a_list) ; + ddt_verify_lists() ; + + item = ddt_new_item() ; + after = test.list[b_list].where[n % where_count] ; + ddl_in_after(after, b->base, item, b.list) ; + test_assert(after->b.list.next == item, "ddl_in_after broken") ; + test_assert(item->b.list.prev == after, "ddl_in_after broken") ; + ddt_test_list_add(item, b_list) ; + ddt_verify_lists() ; + + --n ; + } ; + printf("\n") ; + + /* ddl_in_before(before, base, item, list) -- insert before + * + * Cases: (a) before first and only (so is also last) + * (b) before first when more than one + * (c) before last when more than one + * (d) before something between + */ + printf(" ddl_in_before test") ; + + n = base_n + (rand() % rand_n) ; + while (n) + { + ddt_item item ; + ddt_item before ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + item = ddt_new_item() ; + before = test.list[a_list].where[n % where_count] ; + ddl_in_before(before, a_base, item, a.list) ; + test_assert(before->a.list.prev == item, "ddl_in_before broken") ; + test_assert(item->a.list.next == before, "ddl_in_before broken") ; + ddt_test_list_add(item, a_list) ; + ddt_verify_lists() ; + + item = ddt_new_item() ; + before = test.list[b_list].where[n % where_count] ; + ddl_in_before(before, b->base, item, b.list) ; + test_assert(before->b.list.prev == item, "ddl_in_before broken") ; + test_assert(item->b.list.next == before, "ddl_in_before broken") ; + ddt_test_list_add(item, b_list) ; + ddt_verify_lists() ; + + --n ; + } ; + printf("\n") ; + + /* ddl_pop(&dst, base, next) -- pop head of list, if any + * + * Cases: (a) list with more than one item + * (b) list with one item + * (c) empty list + */ + printf(" ddl_pop test") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + ddt_item item ; + ddt_item temp ; + ddt_item peek ; + int ordinal ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + ordinal = 0 ; + while (1) + { + peek = a_base.head ; + temp = ddl_pop(&item, a_base, a.list) ; + test_assert(temp == item, "ddl_pop broken") ; + test_assert(peek == item, "ddl_pop broken") ; + + ddt_test_list_del(item, a_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + + ++ordinal ; + test_assert(item->a.ordinal == ordinal, "ddl_pop not in order") ; + } ; + + ordinal = 0 ; + while (1) + { + peek = b->base.head ; + temp = ddl_pop(&item, b->base, b.list) ; + test_assert(temp == item, "ddl_pop broken") ; + test_assert(peek == item, "ddl_pop broken") ; + + ddt_test_list_del(item, b_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + + ++ordinal ; + test_assert(item->b.ordinal == ordinal, "ddl_pop not in order") ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_crop(&dst, base, next) -- crop tail of list, if any + * + * Cases: (a) list with more than one item + * (b) list with one item + * (c) empty list + */ + + printf(" ddl_crop test") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + ddt_item item ; + ddt_item temp ; + ddt_item peek ; + int ordinal ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + ordinal = 0 ; + while (1) + { + peek = a_base.tail ; + temp = ddl_crop(&item, a_base, a.list) ; + test_assert(temp == item, "ddl_crop broken") ; + test_assert(peek == item, "ddl_crop broken") ; + + ddt_test_list_del(item, a_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + + if (ordinal == 0) + ordinal = item->a.ordinal ; + else + { + test_assert(ordinal > 0, "ordinal out of whack") ; + --ordinal ; + test_assert(item->a.ordinal == ordinal, "ddl_crop not in order") ; + } ; + } ; + + ordinal = 0 ; + while (1) + { + peek = b->base.tail ; + temp = ddl_crop(&item, b->base, b.list) ; + test_assert(temp == item, "ddl_crop broken") ; + test_assert(peek == item, "ddl_crop broken") ; + + ddt_test_list_del(item, b_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + + if (ordinal == 0) + ordinal = item->b.ordinal ; + else + { + test_assert(ordinal > 0, "ordinal out of whack") ; + --ordinal ; + test_assert(item->b.ordinal == ordinal, "ddl_crop not in order") ; + } ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_del(base, item, list) -- delete from list + * + * Cases: (a) first and only (so is also last) + * (b) first when more than one + * (c) last when more than one + * (d) something between + */ + printf(" ddl_del test") ; + + n = base_n + (rand() % rand_n) ; + while (n) + { + ddt_item item ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + item = test.list[a_list].where[n % where_count] ; + ddl_del(a_base, item, a.list) ; + ddt_test_list_del(item, a_list) ; + ddt_verify_lists() ; + + item = test.list[b_list].where[n % where_count] ; + ddl_del(b->base, item, b.list) ; + ddt_test_list_del(item, b_list) ; + ddt_verify_lists() ; + + --n ; + } ; + printf("\n") ; + + /* ddl_del_head(base, next) -- delete head of list + * + * Cases: (a) list with more than one item + * (b) list with one item + * (c) empty list + */ + printf(" ddl_del_head test") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + ddt_item item ; + ddt_item peek ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + while (1) + { + item = a_base.head ; + peek = (item != NULL) ? item->a.list.next : NULL ; + + ddl_del_head(a_base, a.list) ; + + test_assert(a_base.head == peek, "ddl_del_head broken") ; + + ddt_test_list_del(item, a_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + } ; + + while (1) + { + item = b->base.head ; + peek = (item != NULL) ? item->b.list.next : NULL ; + + ddl_del_head(b->base, b.list) ; + + test_assert(b->base.head == peek, "ddl_del_head broken") ; + + ddt_test_list_del(item, b_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_del_tail(base, next) -- delete tail of list + * + * Cases: (a) list with more than one item + * (b) list with one item + * (c) empty list + */ + printf(" ddl_del_tail test") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + ddt_item item ; + ddt_item peek ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + while (1) + { + item = a_base.tail ; + peek = (item != NULL) ? item->a.list.prev : NULL ; + + ddl_del_tail(a_base, a.list) ; + + test_assert(a_base.tail == peek, "ddl_del_tail broken") ; + + ddt_test_list_del(item, a_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + } ; + + while (1) + { + item = b->base.tail ; + peek = (item != NULL) ? item->b.list.prev : NULL ; + + ddl_del_tail(b->base, b.list) ; + + test_assert(b->base.tail == peek, "ddl_del_tail broken") ; + + ddt_test_list_del(item, b_list) ; + ddt_verify_lists() ; + + if (item == NULL) + break ; + } ; + + --n ; + } ; + printf("\n") ; + + /* ddl_head(base) -- return head of list + * ddl_tail(base) -- return tail of list + * + * Cases: (a) list with more than one item + * (b) list with one item + * (c) empty list + */ + printf(" ddl_head & ddl_tail test") ; + + n = base_n + (rand() % rand_n) ; + while (n >= 0) + { + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + test_assert(ddl_head(a_base) == a_base.head, "ddl_head broken") ; + test_assert(ddl_tail(a_base) == a_base.tail, "ddl_head broken") ; + test_assert(ddl_head(b->base) == b->base.head, "ddl_head broken") ; + test_assert(ddl_tail(b->base) == b->base.tail, "ddl_head broken") ; + + --n ; + } ; + printf("\n") ; + + + /* ddl_next(item, next) -- step to next item, if any + * ddl_prev(item, next) -- step to prev item, if any + * + * Cases: (a) at first and only (so is also last) + * (b) at first when more than one + * (c) at last when more than one + * (d) at something between + */ + printf(" ddl_next and ddl_prev test") ; + + n = base_n + (rand() % rand_n) ; + while (n) + { + ddt_item item ; + ddt_item where ; + + printf(".") ; + + ddt_test_make_lists(&test, n) ; + + where = test.list[a_list].where[n % where_count] ; + item = ddl_next(where, a.list) ; + test_assert(item == where->a.list.next, "ddl_next broken") ; + + where = test.list[b_list].where[n % where_count] ; + item = ddl_next(where, b.list) ; + test_assert(item == where->b.list.next, "ddl_next broken") ; + + where = test.list[a_list].where[n % where_count] ; + item = ddl_prev(where, a.list) ; + test_assert(item == where->a.list.prev, "ddl_prev broken") ; + + where = test.list[b_list].where[n % where_count] ; + item = ddl_prev(where, b.list) ; + test_assert(item == where->b.list.prev, "ddl_prev broken") ; + + --n ; + } ; + printf("\n") ; + + +} + +/* + * TODO + * + */ diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index c5e24546..4c9322ae 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -64,7 +64,7 @@ struct vtysh_client /* We need direct access to ripd to implement vtysh_exit_ripd_only. */ static struct vtysh_client *ripd_client = NULL; - + /* Using integrated config from Quagga.conf. Default is no. */ int vtysh_writeconfig_integrated = 0; @@ -109,7 +109,7 @@ vtysh_client_config (struct vtysh_client *vclient, char *line) vclient_close (vclient); return CMD_SUCCESS; } - + /* Allow enough room for buffer to read more than a few pages from socket. */ bufsz = 5 * getpagesize() + 1; buf = XMALLOC(MTYPE_TMP, bufsz); @@ -191,7 +191,7 @@ vtysh_client_execute (struct vtysh_client *vclient, const char *line, FILE *fp) int ret; char buf[1001]; int nbytes; - int i; + int i; int numnulls = 0; if (vclient->fd < 0) @@ -203,7 +203,7 @@ vtysh_client_execute (struct vtysh_client *vclient, const char *line, FILE *fp) vclient_close (vclient); return CMD_SUCCESS; } - + while (1) { nbytes = read (vclient->fd, buf, sizeof(buf)-1); @@ -222,19 +222,19 @@ vtysh_client_execute (struct vtysh_client *vclient, const char *line, FILE *fp) buf[nbytes] = '\0'; fputs (buf, fp); fflush (fp); - - /* check for trailling \0\0\0<ret code>, - * even if split across reads + + /* check for trailling \0\0\0<ret code>, + * even if split across reads * (see lib/vty.c::vtysh_read) */ - if (nbytes >= 4) + if (nbytes >= 4) { i = nbytes-4; numnulls = 0; } else i = 0; - + while (i < nbytes && numnulls < 3) { if (buf[i++] == '\0') @@ -398,7 +398,7 @@ vtysh_execute_func (const char *line, int pager) return CMD_SUCCESS; } - ret = cmd_execute_command (vline, vty, &cmd, , NULL, 1); + ret = cmd_execute_command (vline, vty, &cmd, NULL, NULL, 1); cmd_free_strvec (vline); if (ret != CMD_SUCCESS_DAEMON) break; @@ -474,7 +474,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) ret = cmd_execute_command_strict (vline, vty, &cmd); /* Try again with setting node to CONFIG_NODE. */ - if (ret != CMD_SUCCESS + if (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING) { @@ -484,8 +484,8 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) vtysh_exit_ripd_only (); ret = cmd_execute_command_strict (vline, vty, &cmd); - if (ret != CMD_SUCCESS - && ret != CMD_SUCCESS_DAEMON + if (ret != CMD_SUCCESS + && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING) { vtysh_exit_ripd_only (); @@ -500,7 +500,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) vty->node = CONFIG_NODE; ret = cmd_execute_command_strict (vline, vty, &cmd); } - } + } cmd_free_strvec (vline); @@ -564,7 +564,7 @@ vtysh_rl_describe (void) vline = vector_init (1); vector_set (vline, '\0'); } - else + else if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1])) vector_set (vline, '\0'); @@ -587,7 +587,7 @@ vtysh_rl_describe (void) rl_on_new_line (); return 0; break; - } + } /* Get width of command string. */ width = 0; @@ -952,7 +952,7 @@ DEFUNSH (VTYSH_RIPD, { vty->node = KEYCHAIN_NODE; return CMD_SUCCESS; -} +} DEFUNSH (VTYSH_RIPD, key, @@ -1047,7 +1047,7 @@ DEFUNSH (VTYSH_ALL, } DEFUNSH (VTYSH_ALL, - vtysh_enable, + vtysh_enable, vtysh_enable_cmd, "enable", "Turn on privileged mode command\n") @@ -1057,7 +1057,7 @@ DEFUNSH (VTYSH_ALL, } DEFUNSH (VTYSH_ALL, - vtysh_disable, + vtysh_disable, vtysh_disable_cmd, "disable", "Turn off privileged mode command\n") @@ -1303,7 +1303,7 @@ DEFSH (VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_OSPFD, "description .LINE", "Interface specific description\n" "Characters describing this interface\n") - + DEFSH (VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_OSPFD, no_interface_desc_cmd, "no description", @@ -1334,16 +1334,16 @@ DEFUN (vtysh_show_memory, unsigned int i; int ret = CMD_SUCCESS; char line[] = "show memory\n"; - + for (i = 0; i < VTYSH_INDEX_MAX; i++) if ( vtysh_client[i].fd >= 0 ) { - fprintf (stdout, "Memory statistics for %s:\n", + fprintf (stdout, "Memory statistics for %s:\n", vtysh_client[i].name); ret = vtysh_client_execute (&vtysh_client[i], line, stdout); fprintf (stdout,"\n"); } - + return ret; } @@ -1357,16 +1357,16 @@ DEFUN (vtysh_show_logging, unsigned int i; int ret = CMD_SUCCESS; char line[] = "show logging\n"; - + for (i = 0; i < VTYSH_INDEX_MAX; i++) if ( vtysh_client[i].fd >= 0 ) { - fprintf (stdout,"Logging configuration for %s:\n", + fprintf (stdout,"Logging configuration for %s:\n", vtysh_client[i].name); ret = vtysh_client_execute (&vtysh_client[i], line, stdout); fprintf (stdout,"\n"); } - + return ret; } @@ -1734,7 +1734,7 @@ DEFUN (vtysh_write_terminal, } vty_out (vty, "end%s", VTY_NEWLINE); - + return CMD_SUCCESS; } @@ -1779,7 +1779,7 @@ write_config_integrated(void) unlink (integrate_sav); rename (integrate_default, integrate_sav); free (integrate_sav); - + fp = fopen (integrate_default, "w"); if (fp == NULL) { @@ -1797,7 +1797,7 @@ write_config_integrated(void) if (chmod (integrate_default, CONFIGFILE_MASK) != 0) { - fprintf (stdout,"%% Can't chmod configuration file %s: %s (%d)\n", + fprintf (stdout,"%% Can't chmod configuration file %s: %s (%d)\n", integrate_default, safe_strerror(errno), errno); return CMD_WARNING; } @@ -1818,16 +1818,16 @@ DEFUN (vtysh_write_memory, int ret = CMD_SUCCESS; char line[] = "write memory\n"; u_int i; - + /* If integrated Quagga.conf explicitely set. */ if (vtysh_writeconfig_integrated) return write_config_integrated(); fprintf (stdout,"Building Configuration...\n"); - + for (i = 0; i < VTYSH_INDEX_MAX; i++) ret = vtysh_client_execute (&vtysh_client[i], line, stdout); - + fprintf (stdout,"[OK]\n"); return ret; @@ -1835,7 +1835,7 @@ DEFUN (vtysh_write_memory, ALIAS (vtysh_write_memory, vtysh_copy_runningconfig_startupconfig_cmd, - "copy running-config startup-config", + "copy running-config startup-config", "Copy from one file to another\n" "Copy from current system configuration\n" "Copy to startup configuration\n") @@ -2108,11 +2108,11 @@ vtysh_connect (struct vtysh_client *vclient) ret = stat (vclient->path, &s_stat); if (ret < 0 && errno != ENOENT) { - fprintf (stderr, "vtysh_connect(%s): stat = %s\n", - vclient->path, safe_strerror(errno)); + fprintf (stderr, "vtysh_connect(%s): stat = %s\n", + vclient->path, safe_strerror(errno)); exit(1); } - + if (ret >= 0) { if (! S_ISSOCK(s_stat.st_mode)) @@ -2121,7 +2121,7 @@ vtysh_connect (struct vtysh_client *vclient) vclient->path); exit (1); } - + } sock = socket (AF_UNIX, SOCK_STREAM, 0); @@ -2382,7 +2382,7 @@ vtysh_init_vty (void) /* "write terminal" command. */ install_element (ENABLE_NODE, &vtysh_write_terminal_cmd); - + install_element (CONFIG_NODE, &vtysh_integrated_config_cmd); install_element (CONFIG_NODE, &no_vtysh_integrated_config_cmd); @@ -2421,7 +2421,7 @@ vtysh_init_vty (void) install_element (ENABLE_NODE, &vtysh_start_shell_cmd); install_element (ENABLE_NODE, &vtysh_start_bash_cmd); install_element (ENABLE_NODE, &vtysh_start_zsh_cmd); - + install_element (VIEW_NODE, &vtysh_show_memory_cmd); install_element (ENABLE_NODE, &vtysh_show_memory_cmd); |