diff options
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); |