diff options
76 files changed, 9590 insertions, 8568 deletions
diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 393a22dd..d79f25f2 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -37,32 +37,32 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "bgpd/bgp_debug.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_ecommunity.h" - + /* Attribute strings for logging. */ -static const struct message attr_str [] = -{ - { BGP_ATTR_ORIGIN, "ORIGIN" }, - { BGP_ATTR_AS_PATH, "AS_PATH" }, - { BGP_ATTR_NEXT_HOP, "NEXT_HOP" }, - { BGP_ATTR_MULTI_EXIT_DISC, "MULTI_EXIT_DISC" }, - { BGP_ATTR_LOCAL_PREF, "LOCAL_PREF" }, - { BGP_ATTR_ATOMIC_AGGREGATE, "ATOMIC_AGGREGATE" }, - { BGP_ATTR_AGGREGATOR, "AGGREGATOR" }, - { BGP_ATTR_COMMUNITIES, "COMMUNITY" }, +static const struct message attr_str [] = +{ + { BGP_ATTR_ORIGIN, "ORIGIN" }, + { BGP_ATTR_AS_PATH, "AS_PATH" }, + { BGP_ATTR_NEXT_HOP, "NEXT_HOP" }, + { BGP_ATTR_MULTI_EXIT_DISC, "MULTI_EXIT_DISC" }, + { BGP_ATTR_LOCAL_PREF, "LOCAL_PREF" }, + { BGP_ATTR_ATOMIC_AGGREGATE, "ATOMIC_AGGREGATE" }, + { BGP_ATTR_AGGREGATOR, "AGGREGATOR" }, + { BGP_ATTR_COMMUNITIES, "COMMUNITY" }, { BGP_ATTR_ORIGINATOR_ID, "ORIGINATOR_ID" }, - { BGP_ATTR_CLUSTER_LIST, "CLUSTERLIST" }, + { BGP_ATTR_CLUSTER_LIST, "CLUSTERLIST" }, { BGP_ATTR_DPA, "DPA" }, { BGP_ATTR_ADVERTISER, "ADVERTISER"} , { BGP_ATTR_RCID_PATH, "RCID_PATH" }, { BGP_ATTR_MP_REACH_NLRI, "MP_REACH_NLRI" }, { BGP_ATTR_MP_UNREACH_NLRI, "MP_UNREACH_NLRI" }, { BGP_ATTR_EXT_COMMUNITIES, "EXT_COMMUNITIES" }, - { BGP_ATTR_AS4_PATH, "AS4_PATH" }, - { BGP_ATTR_AS4_AGGREGATOR, "AS4_AGGREGATOR" }, + { BGP_ATTR_AS4_PATH, "AS4_PATH" }, + { BGP_ATTR_AS4_AGGREGATOR, "AS4_AGGREGATOR" }, { BGP_ATTR_AS_PATHLIMIT, "AS_PATHLIMIT" }, }; static const int attr_str_max = sizeof(attr_str)/sizeof(attr_str[0]); - + static struct hash *cluster_hash; static void * @@ -106,7 +106,7 @@ int cluster_loop_check (struct cluster_list *cluster, struct in_addr originator) { int i; - + for (i = 0; i < cluster->length / 4; i++) if (cluster->list[i].s_addr == originator.s_addr) return 1; @@ -123,7 +123,7 @@ cluster_hash_key_make (void *p) length = cluster->length; pnt = (caddr_t) cluster->list; - + while (length) key += pnt[--length]; @@ -164,7 +164,7 @@ cluster_dup (struct cluster_list *cluster) } else new->list = NULL; - + return new; } #endif @@ -207,7 +207,7 @@ cluster_finish (void) hash_free (cluster_hash); cluster_hash = NULL; } - + /* Unknown transit attribute. */ static struct hash *transit_hash; @@ -265,7 +265,7 @@ transit_hash_key_make (void *p) length = transit->length; pnt = (caddr_t) transit->val; - + while (length) key += pnt[--length]; @@ -294,7 +294,7 @@ transit_finish (void) hash_free (transit_hash); transit_hash = NULL; } - + /* Attribute hash routines. */ static struct hash *attrhash; @@ -364,7 +364,7 @@ attrhash_key_make (void *p) key += attr->pathlimit.ttl; key += attr->pathlimit.as; } - + if (attr->extra) { key += attr->extra->aggregator_as; @@ -372,12 +372,12 @@ attrhash_key_make (void *p) key += attr->extra->weight; key += attr->extra->mp_nexthop_global_in.s_addr; } - + if (attr->aspath) key += aspath_key_make (attr->aspath); if (attr->community) key += community_hash_make (attr->community); - + if (attr->extra) { if (attr->extra->ecommunity) @@ -390,7 +390,7 @@ attrhash_key_make (void *p) #ifdef HAVE_IPV6 { int i; - + key += attr->extra->mp_nexthop_len; for (i = 0; i < 16; i++) key += attr->extra->mp_nexthop_global.s6_addr[i]; @@ -421,7 +421,7 @@ attrhash_cmp (const void *p1, const void *p2) { const struct attr_extra *ae1 = attr1->extra; const struct attr_extra *ae2 = attr2->extra; - + if (ae1 && ae2 && ae1->aggregator_as == ae2->aggregator_as && ae1->aggregator_addr.s_addr == ae2->aggregator_addr.s_addr @@ -463,14 +463,14 @@ attr_show_all_iterator (struct hash_backet *backet, struct vty *vty) { struct attr *attr = backet->data; - vty_out (vty, "attr[%ld] nexthop %s%s", attr->refcnt, + vty_out (vty, "attr[%ld] nexthop %s%s", attr->refcnt, safe_inet_ntoa (attr->nexthop), VTY_NEWLINE); } void attr_show_all (struct vty *vty) { - hash_iterate (attrhash, + hash_iterate (attrhash, (void (*)(struct hash_backet *, void *)) attr_show_all_iterator, vty); @@ -517,7 +517,7 @@ bgp_attr_intern (struct attr *attr) if (attr->extra) { struct attr_extra *attre = attr->extra; - + if (attre->ecommunity) { if (! attre->ecommunity->refcnt) @@ -554,7 +554,7 @@ bgp_attr_default_set (struct attr *attr, u_char origin) { memset (attr, 0, sizeof (struct attr)); bgp_attr_extra_get (attr); - + attr->origin = origin; attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_ORIGIN); attr->aspath = aspath_empty (); @@ -576,15 +576,15 @@ bgp_attr_default_intern (u_char origin) struct attr attr; struct attr *new; struct attr_extra *attre; - + memset (&attr, 0, sizeof (struct attr)); attre = bgp_attr_extra_get (&attr); - + bgp_attr_default_set(&attr, origin); new = bgp_attr_intern (&attr); bgp_attr_extra_free (&attr); - + aspath_unintern (new->aspath); return new; } @@ -600,7 +600,7 @@ bgp_attr_aggregate_intern (struct bgp *bgp, u_char origin, memset (&attr, 0, sizeof (struct attr)); attre = bgp_attr_extra_get (&attr); - + /* Origin attribute. */ attr.origin = origin; attr.flag |= ATTR_FLAG_BIT (BGP_ATTR_ORIGIN); @@ -636,7 +636,7 @@ bgp_attr_aggregate_intern (struct bgp *bgp, u_char origin, new = bgp_attr_intern (&attr); bgp_attr_extra_free (&attr); - + aspath_unintern (new->aspath); return new; } @@ -665,7 +665,7 @@ bgp_attr_unintern (struct attr *attr) /* If reference becomes zero then free attribute object. */ if (attr->refcnt == 0) - { + { ret = hash_release (attrhash, attr); assert (ret != NULL); bgp_attr_extra_free (attr); @@ -710,32 +710,32 @@ bgp_attr_aspathlimit (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag, u_char *startp) { bgp_size_t total; - + total = length + (CHECK_FLAG (flag, BGP_ATTR_FLAG_EXTLEN) ? 4 : 3); - + if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS) || !CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL)) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "AS-Pathlimit attribute flag isn't transitive %d", flag); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; } - + if (length != 5) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "AS-Pathlimit length, %u, is not 5", length); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; } - + attr->pathlimit.ttl = stream_getc (BGP_INPUT(peer)); attr->pathlimit.as = stream_getl (BGP_INPUT(peer)); attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_AS_PATHLIMIT); @@ -743,7 +743,7 @@ bgp_attr_aspathlimit (struct peer *peer, bgp_size_t length, } /* Get origin attribute of the update message. */ static int -bgp_attr_origin (struct peer *peer, bgp_size_t length, +bgp_attr_origin (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag, u_char *startp) { bgp_size_t total; @@ -758,10 +758,10 @@ bgp_attr_origin (struct peer *peer, bgp_size_t length, attribute (type, length and value). */ if (flag != BGP_ATTR_FLAG_TRANS) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "Origin attribute flag isn't transitive %d", flag); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; @@ -776,7 +776,7 @@ bgp_attr_origin (struct peer *peer, bgp_size_t length, { zlog (peer->log, LOG_ERR, "Origin attribute length is not one %d", length); - bgp_notify_send_with_data (peer, BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, startp, total); return -1; @@ -795,8 +795,8 @@ bgp_attr_origin (struct peer *peer, bgp_size_t length, zlog (peer->log, LOG_ERR, "Origin attribute value is invalid %d", attr->origin); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_INVAL_ORIGIN, startp, total); return -1; @@ -811,7 +811,7 @@ bgp_attr_origin (struct peer *peer, bgp_size_t length, /* Parse AS path information. This function is wrapper of aspath_parse. */ static int -bgp_attr_aspath (struct peer *peer, bgp_size_t length, +bgp_attr_aspath (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag, u_char *startp) { bgp_size_t total; @@ -822,28 +822,27 @@ bgp_attr_aspath (struct peer *peer, bgp_size_t length, if (CHECK_FLAG (flag, BGP_ATTR_FLAG_OPTIONAL) || ! CHECK_FLAG (flag, BGP_ATTR_FLAG_TRANS)) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "As-Path attribute flag isn't transitive %d", flag); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; } /* - * peer with AS4 => will get 4Byte ASnums + * peer with AS4 => will get 4Byte ASnums provided AS4 was advertised * otherwise, will get 16 Bit */ - attr->aspath = aspath_parse (peer->ibuf, length, - CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV)); + attr->aspath = aspath_parse (peer->ibuf, length, PEER_CAP_AS4_USE(peer)) ; /* In case of IBGP, length will be zero. */ if (! attr->aspath) { zlog (peer->log, LOG_ERR, "Malformed AS path length is %d", length); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_AS_PATH); return -1; } @@ -857,7 +856,7 @@ bgp_attr_aspath (struct peer *peer, bgp_size_t length, return 0; } -static int bgp_attr_aspath_check( struct peer *peer, +static int bgp_attr_aspath_check( struct peer *peer, struct attr *attr) { /* These checks were part of bgp_attr_aspath, but with @@ -871,14 +870,14 @@ static int bgp_attr_aspath_check( struct peer *peer, struct aspath *aspath; bgp = peer->bgp; - + /* Confederation sanity check. */ if ((peer_sort (peer) == BGP_PEER_CONFED && ! aspath_left_confed_check (attr->aspath)) || (peer_sort (peer) == BGP_PEER_EBGP && aspath_confed_check (attr->aspath))) { zlog (peer->log, LOG_ERR, "Malformed AS path from %s", peer->host); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_AS_PATH); return -1; } @@ -886,7 +885,7 @@ static int bgp_attr_aspath_check( struct peer *peer, /* First AS check for EBGP. */ if (bgp != NULL && bgp_flag_check (bgp, BGP_FLAG_ENFORCE_FIRST_AS)) { - if (peer_sort (peer) == BGP_PEER_EBGP + if (peer_sort (peer) == BGP_PEER_EBGP && ! aspath_firstas_check (attr->aspath, peer->as)) { zlog (peer->log, LOG_ERR, @@ -915,7 +914,7 @@ static int bgp_attr_aspath_check( struct peer *peer, /* Parse AS4 path information. This function is another wrapper of aspath_parse. */ static int -bgp_attr_as4_path (struct peer *peer, bgp_size_t length, +bgp_attr_as4_path (struct peer *peer, bgp_size_t length, struct attr *attr, struct aspath **as4_path) { *as4_path = aspath_parse (peer->ibuf, length, 1); @@ -929,7 +928,7 @@ bgp_attr_as4_path (struct peer *peer, bgp_size_t length, /* Nexthop attribute. */ static int -bgp_attr_nexthop (struct peer *peer, bgp_size_t length, +bgp_attr_nexthop (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag, u_char *startp) { bgp_size_t total; @@ -940,10 +939,10 @@ bgp_attr_nexthop (struct peer *peer, bgp_size_t length, if (CHECK_FLAG (flag, BGP_ATTR_FLAG_OPTIONAL) || ! CHECK_FLAG (flag, BGP_ATTR_FLAG_TRANS)) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "Origin attribute flag isn't transitive %d", flag); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; @@ -955,8 +954,8 @@ bgp_attr_nexthop (struct peer *peer, bgp_size_t length, zlog (peer->log, LOG_ERR, "Nexthop attribute length isn't four [%d]", length); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, startp, total); return -1; @@ -970,7 +969,7 @@ bgp_attr_nexthop (struct peer *peer, bgp_size_t length, /* MED atrribute. */ static int -bgp_attr_med (struct peer *peer, bgp_size_t length, +bgp_attr_med (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag, u_char *startp) { bgp_size_t total; @@ -980,11 +979,11 @@ bgp_attr_med (struct peer *peer, bgp_size_t length, /* Length check. */ if (length != 4) { - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "MED attribute length isn't four [%d]", length); - - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, startp, total); return -1; @@ -999,7 +998,7 @@ bgp_attr_med (struct peer *peer, bgp_size_t length, /* Local preference attribute. */ static int -bgp_attr_local_pref (struct peer *peer, bgp_size_t length, +bgp_attr_local_pref (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { /* If it is contained in an UPDATE message that is received from an @@ -1011,9 +1010,9 @@ bgp_attr_local_pref (struct peer *peer, bgp_size_t length, return 0; } - if (length == 4) + if (length == 4) attr->local_pref = stream_getl (peer->ibuf); - else + else attr->local_pref = 0; /* Set atomic aggregate flag. */ @@ -1024,15 +1023,15 @@ bgp_attr_local_pref (struct peer *peer, bgp_size_t length, /* Atomic aggregate. */ static int -bgp_attr_atomic (struct peer *peer, bgp_size_t length, +bgp_attr_atomic (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { if (length != 0) { zlog (peer->log, LOG_ERR, "Bad atomic aggregate length %d", length); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } @@ -1050,22 +1049,23 @@ bgp_attr_aggregator (struct peer *peer, bgp_size_t length, { int wantedlen = 6; struct attr_extra *attre = bgp_attr_extra_get (attr); - + /* peer with AS4 will send 4 Byte AS, peer without will send 2 Byte */ - if ( CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV ) ) + if ( PEER_CAP_AS4_USE(peer) ) wantedlen = 8; - + if (length != wantedlen) { - zlog (peer->log, LOG_ERR, "Aggregator length is not %d [%d]", wantedlen, length); + zlog (peer->log, LOG_ERR, "Aggregator length is not %d [%d]", wantedlen, + length); bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } - - if ( CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV ) ) + + if ( PEER_CAP_AS4_USE(peer) ) attre->aggregator_as = stream_getl (peer->ibuf); else attre->aggregator_as = stream_getw (peer->ibuf); @@ -1110,8 +1110,8 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, int ignore_as4_path = 0; struct aspath *newpath; struct attr_extra *attre = attr->extra; - - if ( CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV) ) + + if ( PEER_CAP_AS4_USE(peer) ) { /* peer can do AS4, so we ignore AS4_PATH and AS4_AGGREGATOR * if given. @@ -1123,15 +1123,15 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH))) zlog_debug ("[AS4] %s %s AS4_PATH", peer->host, "AS4 capable peer, yet it sent"); - + if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR))) zlog_debug ("[AS4] %s %s AS4_AGGREGATOR", peer->host, "AS4 capable peer, yet it sent"); } - + return 0; } - + if (attr->flag & ( ATTR_FLAG_BIT( BGP_ATTR_AS4_PATH)) && !(attr->flag & ( ATTR_FLAG_BIT( BGP_ATTR_AS_PATH)))) { @@ -1142,11 +1142,11 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, * But... yeah, paranoia * Take this as a "malformed attribute" */ - zlog (peer->log, LOG_ERR, + zlog (peer->log, LOG_ERR, "%s BGP not AS4 capable peer sent AS4_PATH but" " no AS_PATH, cant do anything here", peer->host); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return -1; } @@ -1159,11 +1159,11 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, if ( attr->flag & (ATTR_FLAG_BIT (BGP_ATTR_AGGREGATOR) ) ) { assert (attre); - + /* received both. * if the as_number in aggregator is not AS_TRANS, * then AS4_AGGREGATOR and AS4_PATH shall be ignored - * and the Aggregator shall be taken as + * and the Aggregator shall be taken as * info on the aggregating node, and the AS_PATH * shall be taken as the AS_PATH * otherwise @@ -1176,7 +1176,7 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, { /* ignore */ if ( BGP_DEBUG(as4, AS4)) - zlog_debug ("[AS4] %s BGP not AS4 capable peer" + zlog_debug ("[AS4] %s BGP not AS4 capable peer" " send AGGREGATOR != AS_TRANS and" " AS4_AGGREGATOR, so ignore" " AS4_AGGREGATOR and AS4_PATH", peer->host); @@ -1218,7 +1218,7 @@ bgp_attr_munge_as4_attrs (struct peer *peer, struct attr *attr, /* Community attribute. */ static int -bgp_attr_community (struct peer *peer, bgp_size_t length, +bgp_attr_community (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { if (length == 0) @@ -1228,7 +1228,7 @@ bgp_attr_community (struct peer *peer, bgp_size_t length, } else { - attr->community = + attr->community = community_parse ((u_int32_t *)stream_pnt (peer->ibuf), length); stream_forward_getp (peer->ibuf, length); } @@ -1240,20 +1240,20 @@ bgp_attr_community (struct peer *peer, bgp_size_t length, /* Originator ID attribute. */ static int -bgp_attr_originator_id (struct peer *peer, bgp_size_t length, +bgp_attr_originator_id (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { if (length != 4) { zlog (peer->log, LOG_ERR, "Bad originator ID length %d", length); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } - (bgp_attr_extra_get (attr))->originator_id.s_addr + (bgp_attr_extra_get (attr))->originator_id.s_addr = stream_get_ipv4 (peer->ibuf); attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_ORIGINATOR_ID); @@ -1263,7 +1263,7 @@ bgp_attr_originator_id (struct peer *peer, bgp_size_t length, /* Cluster list attribute. */ static int -bgp_attr_cluster_list (struct peer *peer, bgp_size_t length, +bgp_attr_cluster_list (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { /* Check length. */ @@ -1271,13 +1271,13 @@ bgp_attr_cluster_list (struct peer *peer, bgp_size_t length, { zlog (peer->log, LOG_ERR, "Bad cluster list length %d", length); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } - (bgp_attr_extra_get (attr))->cluster + (bgp_attr_extra_get (attr))->cluster = cluster_parse ((struct in_addr *)stream_pnt (peer->ibuf), length); stream_forward_getp (peer->ibuf, length);; @@ -1299,35 +1299,35 @@ bgp_mp_reach_parse (struct peer *peer, bgp_size_t length, struct attr *attr, int ret; struct stream *s; struct attr_extra *attre = bgp_attr_extra_get(attr); - + /* Set end of packet. */ s = BGP_INPUT(peer); start = stream_get_getp(s); - + /* safe to read statically sized header? */ #define BGP_MP_REACH_MIN_SIZE 5 #define LEN_LEFT (length - (stream_get_getp(s) - start)) if ((length > STREAM_READABLE(s)) || (length < BGP_MP_REACH_MIN_SIZE)) { - zlog_info ("%s: %s sent invalid length, %lu", + zlog_info ("%s: %s sent invalid length, %lu", __func__, peer->host, (unsigned long)length); return -1; } - + /* Load AFI, SAFI. */ afi = stream_getw (s); safi = stream_getc (s); /* Get nexthop length. */ attre->mp_nexthop_len = stream_getc (s); - + if (LEN_LEFT < attre->mp_nexthop_len) { - zlog_info ("%s: %s, MP nexthop length, %u, goes past end of attribute", + zlog_info ("%s: %s, MP nexthop length, %u, goes past end of attribute", __func__, peer->host, attre->mp_nexthop_len); return -1; } - + /* Nexthop length check. */ switch (attre->mp_nexthop_len) { @@ -1371,7 +1371,7 @@ bgp_mp_reach_parse (struct peer *peer, bgp_size_t length, struct attr *attr, break; #endif /* HAVE_IPV6 */ default: - zlog_info ("%s: (%s) Wrong multiprotocol next hop length: %d", + zlog_info ("%s: (%s) Wrong multiprotocol next hop length: %d", __func__, peer->host, attre->mp_nexthop_len); return -1; } @@ -1382,14 +1382,14 @@ bgp_mp_reach_parse (struct peer *peer, bgp_size_t length, struct attr *attr, __func__, peer->host); return -1; } - + { - u_char val; + u_char val; if ((val = stream_getc (s))) zlog_warn ("%s sent non-zero value, %u, for defunct SNPA-length field", peer->host, val); } - + /* must have nrli_len, what is left of the attribute */ nlri_len = LEN_LEFT; if ((!nlri_len) || (nlri_len > STREAM_READABLE(s))) @@ -1398,11 +1398,11 @@ bgp_mp_reach_parse (struct peer *peer, bgp_size_t length, struct attr *attr, __func__, peer->host); return -1; } - + if (safi != BGP_SAFI_VPNV4) { ret = bgp_nlri_sanity_check (peer, afi, stream_pnt (s), nlri_len); - if (ret < 0) + if (ret < 0) { zlog_info ("%s: (%s) NLRI doesn't pass sanity check", __func__, peer->host); @@ -1423,7 +1423,7 @@ bgp_mp_reach_parse (struct peer *peer, bgp_size_t length, struct attr *attr, /* Multiprotocol unreachable parse */ int -bgp_mp_unreach_parse (struct peer *peer, bgp_size_t length, +bgp_mp_unreach_parse (struct peer *peer, bgp_size_t length, struct bgp_nlri *mp_withdraw) { struct stream *s; @@ -1433,14 +1433,14 @@ bgp_mp_unreach_parse (struct peer *peer, bgp_size_t length, int ret; s = peer->ibuf; - + #define BGP_MP_UNREACH_MIN_SIZE 3 if ((length > STREAM_READABLE(s)) || (length < BGP_MP_UNREACH_MIN_SIZE)) return -1; - + afi = stream_getw (s); safi = stream_getc (s); - + withdraw_len = length - BGP_MP_UNREACH_MIN_SIZE; if (safi != BGP_SAFI_VPNV4) @@ -1462,7 +1462,7 @@ bgp_mp_unreach_parse (struct peer *peer, bgp_size_t length, /* Extended Community attribute. */ static int -bgp_attr_ext_communities (struct peer *peer, bgp_size_t length, +bgp_attr_ext_communities (struct peer *peer, bgp_size_t length, struct attr *attr, u_char flag) { if (length == 0) @@ -1472,7 +1472,7 @@ bgp_attr_ext_communities (struct peer *peer, bgp_size_t length, } else { - (bgp_attr_extra_get (attr))->ecommunity = + (bgp_attr_extra_get (attr))->ecommunity = ecommunity_parse ((u_int8_t *)stream_pnt (peer->ibuf), length); stream_forward_getp (peer->ibuf, length); } @@ -1493,9 +1493,9 @@ bgp_attr_unknown (struct peer *peer, struct attr *attr, u_char flag, if (BGP_DEBUG (normal, NORMAL)) zlog_debug ("%s Unknown attribute is received (type %d, length %d)", peer->host, type, length); - + if (BGP_DEBUG (events, EVENTS)) - zlog (peer->log, LOG_DEBUG, + zlog (peer->log, LOG_DEBUG, "Unknown attribute type %d length %d is received", type, length); /* Forward read pointer of input stream. */ @@ -1511,8 +1511,8 @@ bgp_attr_unknown (struct peer *peer, struct attr *attr, u_char flag, if (! CHECK_FLAG (flag, BGP_ATTR_FLAG_OPTIONAL)) { /* Adjust startp to do not include flag value. */ - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_UNREC_ATTR, startp, total); return -1; @@ -1536,7 +1536,7 @@ bgp_attr_unknown (struct peer *peer, struct attr *attr, u_char flag, transit = attre->transit; if (transit->val) - transit->val = XREALLOC (MTYPE_TRANSIT_VAL, transit->val, + transit->val = XREALLOC (MTYPE_TRANSIT_VAL, transit->val, transit->length + total); else transit->val = XMALLOC (MTYPE_TRANSIT_VAL, total); @@ -1579,13 +1579,13 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, if (endp - BGP_INPUT_PNT (peer) < BGP_ATTR_MIN_LEN) { /* XXX warning: long int format, int arg (arg 5) */ - zlog (peer->log, LOG_WARNING, + zlog (peer->log, LOG_WARNING, "%s error BGP attribute length %lu is smaller than min len", peer->host, (unsigned long) (endp - STREAM_PNT (BGP_INPUT (peer)))); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } @@ -1599,13 +1599,13 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, if (CHECK_FLAG (flag, BGP_ATTR_FLAG_EXTLEN) && ((endp - startp) < (BGP_ATTR_MIN_LEN + 1))) { - zlog (peer->log, LOG_WARNING, + zlog (peer->log, LOG_WARNING, "%s Extended length set, but just %lu bytes of attr header", peer->host, (unsigned long) (endp - STREAM_PNT (BGP_INPUT (peer)))); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } @@ -1615,7 +1615,7 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, length = stream_getw (BGP_INPUT (peer)); else length = stream_getc (BGP_INPUT (peer)); - + /* If any attribute appears more than once in the UPDATE message, then the Error Subcode is set to Malformed Attribute List. */ @@ -1626,8 +1626,8 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, "%s error BGP attribute type %d appears twice in a message", peer->host, type); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return -1; } @@ -1642,10 +1642,10 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, if (attr_endp > endp) { - zlog (peer->log, LOG_WARNING, + zlog (peer->log, LOG_WARNING, "%s BGP type %d length %d is too large, attribute total length is %d. attr_endp is %p. endp is %p", peer->host, type, length, size, attr_endp, endp); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } @@ -1662,7 +1662,7 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, case BGP_ATTR_AS4_PATH: ret = bgp_attr_as4_path (peer, length, attr, &as4_path ); break; - case BGP_ATTR_NEXT_HOP: + case BGP_ATTR_NEXT_HOP: ret = bgp_attr_nexthop (peer, length, attr, flag, startp); break; case BGP_ATTR_MULTI_EXIT_DISC: @@ -1710,10 +1710,10 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, if (ret < 0) { zlog (peer->log, LOG_WARNING, - "%s: Attribute %s, parse error", - peer->host, + "%s: Attribute %s, parse error", + peer->host, LOOKUP (attr_str, type)); - bgp_notify_send (peer, + bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return ret; @@ -1722,11 +1722,20 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, /* Check the fetched length. */ if (BGP_INPUT_PNT (peer) != attr_endp) { - zlog (peer->log, LOG_WARNING, - "%s: BGP attribute %s, fetch error", + zlog (peer->log, LOG_WARNING, + "%s: BGP attribute %s, fetch error", peer->host, LOOKUP (attr_str, type)); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + + if (type == BGP_ATTR_AS_PATH) + { + zlog (peer->log, LOG_WARNING, + "%s: is %sAS4", + peer->host, + PEER_CAP_AS4_USE(peer) ? "" : "NOT ") ; + } ; + + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } @@ -1735,16 +1744,16 @@ bgp_attr_parse (struct peer *peer, struct attr *attr, bgp_size_t size, /* Check final read pointer is same as end pointer. */ if (BGP_INPUT_PNT (peer) != endp) { - zlog (peer->log, LOG_WARNING, + zlog (peer->log, LOG_WARNING, "%s BGP attribute %s, length mismatch", peer->host, LOOKUP (attr_str, type)); - bgp_notify_send (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); return -1; } - /* + /* * At this place we can see whether we got AS4_PATH and/or * AS4_AGGREGATOR from a 16Bit peer and act accordingly. * We can not do this before we've read all attributes because @@ -1801,7 +1810,7 @@ int bgp_attr_check (struct peer *peer, struct attr *attr) { u_char type = 0; - + if (! CHECK_FLAG (attr->flag, ATTR_FLAG_BIT (BGP_ATTR_ORIGIN))) type = BGP_ATTR_ORIGIN; @@ -1817,18 +1826,18 @@ bgp_attr_check (struct peer *peer, struct attr *attr) if (type) { - zlog (peer->log, LOG_WARNING, + zlog (peer->log, LOG_WARNING, "%s Missing well-known attribute %d.", peer->host, type); - bgp_notify_send_with_data (peer, - BGP_NOTIFY_UPDATE_ERR, + bgp_notify_send_with_data (peer, + BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MISS_ATTR, &type, 1); return -1; } return 0; } - + int stream_put_prefix (struct stream *, struct prefix *); /* Make attribute packet. */ @@ -1843,7 +1852,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, struct aspath *aspath; int send_as4_path = 0; int send_as4_aggregator = 0; - int use32bit = (CHECK_FLAG (peer->cap, PEER_CAP_AS4_RCV)) ? 1 : 0; + int use32bit = PEER_CAP_AS4_USE(peer) ; if (! bgp) bgp = bgp_get_default (); @@ -1864,7 +1873,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, && (! CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_AS_PATH_UNCHANGED) || attr->aspath->segments == NULL) && (! CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT))) - { + { aspath = aspath_dup (attr->aspath); if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) @@ -1904,13 +1913,13 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, aspath_sizep = stream_get_endp (s); stream_putw (s, 0); stream_putw_at (s, aspath_sizep, aspath_put (s, aspath, use32bit)); - - /* OLD session may need NEW_AS_PATH sent, if there are 4-byte ASNs + + /* OLD session may need NEW_AS_PATH sent, if there are 4-byte ASNs * in the path */ if (!use32bit && aspath_has_as4 (aspath)) send_as4_path = 1; /* we'll do this later, at the correct place */ - + /* Nexthop attribute. */ if (attr->flag & ATTR_FLAG_BIT (BGP_ATTR_NEXT_HOP) && afi == AFI_IP) { @@ -1959,11 +1968,11 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, if (attr->flag & ATTR_FLAG_BIT (BGP_ATTR_AGGREGATOR)) { assert (attr->extra); - + /* Common to BGP_ATTR_AGGREGATOR, regardless of ASN size */ stream_putc (s, BGP_ATTR_FLAG_OPTIONAL|BGP_ATTR_FLAG_TRANS); stream_putc (s, BGP_ATTR_AGGREGATOR); - + if (use32bit) { /* AS4 capable peer */ @@ -1974,12 +1983,12 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, { /* 2-byte AS peer */ stream_putc (s, 6); - + /* Is ASN representable in 2-bytes? Or must AS_TRANS be used? */ if ( attr->extra->aggregator_as > 65535 ) { stream_putw (s, BGP_AS_TRANS); - + /* we have to send AS4_AGGREGATOR, too. * we'll do that later in order to send attributes in ascending * order. @@ -1993,7 +2002,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, } /* Community attribute. */ - if (CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) + if (CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) && (attr->flag & ATTR_FLAG_BIT (BGP_ATTR_COMMUNITIES))) { if (attr->community->size * 4 > 255) @@ -2023,13 +2032,13 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) stream_put_in_addr (s, &attr->extra->originator_id); - else + else stream_put_in_addr (s, &from->remote_id); /* Cluster list. */ stream_putc (s, BGP_ATTR_FLAG_OPTIONAL); stream_putc (s, BGP_ATTR_CLUSTER_LIST); - + if (attr->extra && attr->extra->cluster) { stream_putc (s, attr->extra->cluster->length + 4); @@ -2038,7 +2047,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, stream_put_in_addr (s, &bgp->cluster_id); else stream_put_in_addr (s, &bgp->router_id); - stream_put (s, attr->extra->cluster->list, + stream_put (s, attr->extra->cluster->list, attr->extra->cluster->length); } else @@ -2058,9 +2067,9 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, { unsigned long sizep; struct attr_extra *attre = attr->extra; - + assert (attr->extra); - + stream_putc (s, BGP_ATTR_FLAG_OPTIONAL); stream_putc (s, BGP_ATTR_MP_REACH_NLRI); sizep = stream_get_endp (s); @@ -2077,7 +2086,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, stream_put (s, &attre->mp_nexthop_global, 16); stream_put (s, &attre->mp_nexthop_local, 16); } - + /* SNPA */ stream_putc (s, 0); @@ -2143,14 +2152,14 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, } /* Extended Communities attribute. */ - if (CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY) + if (CHECK_FLAG (peer->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY) && (attr->flag & ATTR_FLAG_BIT (BGP_ATTR_EXT_COMMUNITIES))) { struct attr_extra *attre = attr->extra; - + assert (attre); - - if (peer_sort (peer) == BGP_PEER_IBGP + + if (peer_sort (peer) == BGP_PEER_IBGP || peer_sort (peer) == BGP_PEER_CONFED) { if (attre->ecommunity->size * 8 > 255) @@ -2225,7 +2234,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, * Hm, I wonder... confederation things *should* only be at * the beginning of an aspath, right? Then we should use * aspath_delete_confed_seq for this, because it is already - * there! (JK) + * there! (JK) * Folks, talk to me: what is reasonable here!? */ aspath = aspath_delete_confed_seq (aspath); @@ -2240,7 +2249,7 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, if (aspath != attr->aspath) aspath_free (aspath); - if ( send_as4_aggregator ) + if ( send_as4_aggregator ) { assert (attr->extra); @@ -2254,25 +2263,25 @@ bgp_packet_attribute (struct bgp *bgp, struct peer *peer, stream_putl (s, attr->extra->aggregator_as); stream_put_ipv4 (s, attr->extra->aggregator_addr.s_addr); } - + /* AS-Pathlimit */ if (attr->pathlimit.ttl) { u_int32_t as = attr->pathlimit.as; - - /* should already have been done in announce_check(), + + /* should already have been done in announce_check(), * but just in case.. */ if (!as) as = peer->local_as; - + stream_putc (s, BGP_ATTR_FLAG_OPTIONAL|BGP_ATTR_FLAG_TRANS); stream_putc (s, BGP_ATTR_AS_PATHLIMIT); stream_putc (s, 5); stream_putc (s, attr->pathlimit.ttl); stream_putl (s, as); } - + /* Unknown transit attribute. */ if (attr->extra && attr->extra->transit) stream_put (s, attr->extra->transit->val, attr->extra->transit->length); @@ -2352,7 +2361,7 @@ bgp_attr_finish (void) /* Make attribute packet. */ void -bgp_dump_routes_attr (struct stream *s, struct attr *attr, +bgp_dump_routes_attr (struct stream *s, struct attr *attr, struct prefix *prefix) { unsigned long cp; @@ -2373,12 +2382,12 @@ bgp_dump_routes_attr (struct stream *s, struct attr *attr, stream_putc (s, attr->origin); aspath = attr->aspath; - + stream_putc (s, BGP_ATTR_FLAG_TRANS|BGP_ATTR_FLAG_EXTLEN); stream_putc (s, BGP_ATTR_AS_PATH); aspath_lenp = stream_get_endp (s); stream_putw (s, 0); - + stream_putw_at (s, aspath_lenp, aspath_put (s, aspath, 1)); /* Nexthop attribute. */ @@ -2457,7 +2466,7 @@ bgp_dump_routes_attr (struct stream *s, struct attr *attr, { int sizep; struct attr_extra *attre = attr->extra; - + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MP_REACH_NLRI); sizep = stream_get_endp (s); diff --git a/bgpd/bgp_common.h b/bgpd/bgp_common.h index 19db9d8a..d35d6da6 100644 --- a/bgpd/bgp_common.h +++ b/bgpd/bgp_common.h @@ -23,6 +23,7 @@ #define _QUAGGA_BGP_COMMON_H #include <stdint.h> +#include <stdbool.h> #include <sys/socket.h> #include "bgpd/bgp.h" @@ -34,11 +35,6 @@ #endif /*============================================================================== - * Local "bool" - */ -typedef uint8_t flag_t ; - -/*============================================================================== * Here are a number of "incomplete" declarations, which allow a number of * bgpd structures to refer to each other. */ diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c index 1c427318..41cde9a3 100644 --- a/bgpd/bgp_connection.c +++ b/bgpd/bgp_connection.c @@ -35,6 +35,7 @@ #include "lib/symtab.h" #include "lib/stream.h" #include "lib/sockunion.h" +#include "lib/list_util.h" /*============================================================================== * BGP Connections. @@ -80,6 +81,8 @@ static bgp_connection bgp_connection_queue ; /* BGP Engine connection queue */ +static bgp_connection bgp_connection_list ; /* list of known connections */ + enum { CUT_LOOSE_LOCK_COUNT = 1000 } ; /*============================================================================== @@ -119,6 +122,7 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session, * * * state bgp_fsm_Initial * * comatose not comatose + * * half_open not half open * * next NULL -- not on the connection queue * * prev NULL -- not on the connection queue * * follow_on bgp_fsm_null_event @@ -144,12 +148,15 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session, confirm(bgp_fsm_null_event == 0) ; confirm(bgp_session_null_event == 0) ; - /* Link back to session, point at its mutex and point session here */ + /* Put on the connections that exist list */ + sdl_push(bgp_connection_list, connection, exist) ; + + /* Link back to session, point at its mutex and point session here */ connection->session = session ; connection->p_mutex = &session->mutex ; connection->lock_count = 0 ; /* no question about it */ - connection->paf = sockunion_family(session->su_peer) ; + connection->paf = AF_UNSPEC ; connection->ordinal = ordinal ; connection->accepted = (ordinal == bgp_connection_secondary) ; @@ -157,12 +164,12 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session, session->connections[ordinal] = connection ; /* qps_file structure */ - qps_file_init_new(&connection->qf, NULL) ; + connection->qf = qps_file_init_new(NULL, NULL) ; /* Initialise all the timers */ - qtimer_init_new(&connection->hold_timer, bgp_nexus->pile, + connection->hold_timer = qtimer_init_new(NULL, bgp_nexus->pile, NULL, connection) ; - qtimer_init_new(&connection->keepalive_timer, bgp_nexus->pile, + connection->keepalive_timer = qtimer_init_new(NULL, bgp_nexus->pile, NULL, connection) ; /* Copy log destination and make host name + (primary)/(secondary) */ @@ -334,6 +341,10 @@ bgp_connection_free(bgp_connection connection) bgp_connection_close_down(connection) ; /* Free any components which still exist */ + connection->qf = qps_file_free(connection->qf) ; + connection->hold_timer = qtimer_free(connection->hold_timer) ; + connection->keepalive_timer = qtimer_free(connection->hold_timer) ; + bgp_notify_unset(&connection->notification) ; bgp_open_state_unset(&connection->open_recv) ; sockunion_unset(&connection->su_local) ; @@ -345,10 +356,24 @@ bgp_connection_free(bgp_connection connection) bgp_write_buffer_free(&connection->wbuff) ; /* Free the body */ + sdl_del(bgp_connection_list, connection, exist) ; + XFREE(MTYPE_BGP_CONNECTION, connection) ; } ; /*------------------------------------------------------------------------------ + * Terminate all known connections. + * + * TODO: for bringing the BGP Engine to a dead halt. + * + * Problem: can it be assumed that all sessions have been closed ? + * + * if not... how are all the connections to be pursuaded to adopt + * an appropriate posture ? + */ + + +/*------------------------------------------------------------------------------ * If required, allocate new write buffer. * Initialise pointers empty and writable. * @@ -551,6 +576,7 @@ bgp_connection_add_pending(bgp_connection connection, mqueue_block mqb, * * closes any file that may be lingering (should never be) * * reset all stream buffers to empty (should already be) * * set write buffer unwritable + * * clears half_open * * Sets: * @@ -577,7 +603,7 @@ bgp_connection_add_pending(bgp_connection connection, mqueue_block mqb, * NB: requires the session to be LOCKED. */ extern void -bgp_connection_open(bgp_connection connection, int fd) +bgp_connection_open(bgp_connection connection, int fd, int family) { bgp_session session = connection->session ; @@ -586,12 +612,19 @@ bgp_connection_open(bgp_connection connection, int fd) bgp_connection_close(connection) ; /* FSM deals with timers */ /* Set the file going */ - qps_add_file(bgp_nexus->selection, &connection->qf, fd, connection) ; + qps_add_file(bgp_nexus->selection, connection->qf, fd, connection) ; connection->err = 0 ; /* so far, so good */ bgp_open_state_unset(&connection->open_recv) ; + /* Note the address family for the socket. + * + * This is the real family -- so is IPv6 independent of whether one or + * both addresses are actually mapped IPv4. + */ + connection->paf = family ; + /* Copy the original hold_timer_interval and keepalive_timer_interval * Assume these have sensible initial values. * @@ -604,7 +637,8 @@ bgp_connection_open(bgp_connection connection, int fd) /*------------------------------------------------------------------------------ * Start connection which has just come up -- connect() or accept() * - * Copy the local and remote addresses and note the effective address family. + * Copy the local and remote addresses (IPv4 mapped IPv6 addresses appear as + * IPv4 addresses). * * Make sure now have a write buffer, and set it empty and writable. */ @@ -615,8 +649,6 @@ bgp_connection_start(bgp_connection connection, union sockunion* su_local, sockunion_set_dup(&connection->su_local, su_local) ; sockunion_set_dup(&connection->su_remote, su_remote) ; - connection->paf = sockunion_family(connection->su_local) ; - bgp_write_buffer_init(&connection->wbuff, bgp_wbuff_size) ; } ; @@ -665,6 +697,7 @@ bgp_connection_stop(bgp_connection connection, int stop_writer) extern void bgp_connection_enable_accept(bgp_connection connection) { + assert(connection->ordinal == bgp_connection_secondary) ; connection->session->index_entry->accept = connection ; } ; @@ -692,6 +725,7 @@ bgp_connection_disable_accept(bgp_connection connection) * * empties the pending queue -- destroying all messages * * * for secondary connection: disable accept + * * clear half_open * * * if required: unset all timers * @@ -722,23 +756,25 @@ bgp_connection_full_close(bgp_connection connection, int unset_timers) int fd ; /* Close connection's file, if any. */ - qps_remove_file(&connection->qf) ; + qps_remove_file(connection->qf) ; - fd = qps_file_unset_fd(&connection->qf) ; + fd = qps_file_unset_fd(connection->qf) ; if (fd != fd_undef) close(fd) ; /* If required, unset the timers. */ if (unset_timers) { - qtimer_unset(&connection->hold_timer) ; - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->hold_timer) ; + qtimer_unset(connection->keepalive_timer) ; } ; /* If this is the secondary connection, do not accept any more. */ if (connection->ordinal == bgp_connection_secondary) bgp_connection_disable_accept(connection) ; + connection->half_open = 0 ; + /* forget any addresses */ sockunion_unset(&connection->su_local) ; sockunion_unset(&connection->su_remote) ; @@ -780,14 +816,14 @@ bgp_connection_part_close(bgp_connection connection) bgp_size_t mlen ; /* Check that have a usable file descriptor */ - fd = qps_file_fd(&connection->qf) ; + fd = qps_file_fd(connection->qf) ; if (fd == fd_undef) return 0 ; /* Shutdown the read side of this connection */ shutdown(fd, SHUT_RD) ; - qps_disable_modes(&connection->qf, qps_read_mbit) ; + qps_disable_modes(connection->qf, qps_read_mbit) ; /* Stop all buffering activity, except for write buffer. */ bgp_connection_stop(connection, 0) ; @@ -858,7 +894,7 @@ bgp_connection_write(bgp_connection connection, struct stream* s) /* If buffer is empty, enable write mode */ if (bgp_write_buffer_empty(wb)) - qps_enable_mode(&connection->qf, qps_write_mnum, + qps_enable_mode(connection->qf, qps_write_mnum, bgp_connection_write_action) ; /* Transfer the obuf contents to the write buffer. */ @@ -918,7 +954,7 @@ bgp_connection_write_action(qps_file qf, void* file_info) /* Buffer is empty -- reset it and disable write mode */ bgp_write_buffer_reset(wb) ; - qps_disable_modes(&connection->qf, qps_write_mbit) ; + qps_disable_modes(connection->qf, qps_write_mbit) ; /* If waiting to send NOTIFICATION, just did it. */ /* Otherwise: is writable again -- so add to connection_queue */ @@ -947,7 +983,7 @@ bgp_connection_read_action(qps_file qf, void* file_info) ; extern void bgp_connection_read_enable(bgp_connection connection) { - qps_enable_mode(&connection->qf, qps_read_mnum, bgp_connection_read_action) ; + qps_enable_mode(connection->qf, qps_read_mnum, bgp_connection_read_action) ; } ; /*------------------------------------------------------------------------------ @@ -990,7 +1026,7 @@ bgp_connection_read_action(qps_file qf, void* file_info) */ while (1) { - ret = stream_read_nonblock(connection->ibuf, qps_file_fd(&connection->qf), + ret = stream_read_nonblock(connection->ibuf, qps_file_fd(connection->qf), want) ; if (ret >= 0) { diff --git a/bgpd/bgp_connection.h b/bgpd/bgp_connection.h index d50d2985..3d63edb7 100644 --- a/bgpd/bgp_connection.h +++ b/bgpd/bgp_connection.h @@ -22,6 +22,8 @@ #ifndef _QUAGGA_BGP_CONNECTION_H #define _QUAGGA_BGP_CONNECTION_H +#include <stdbool.h> + #include "lib/mqueue.h" #include "lib/qpthreads.h" #include "lib/qtimers.h" @@ -132,6 +134,9 @@ enum { bgp_wbuff_size = BGP_MSG_MAX_L * 10 } ; */ struct bgp_connection { + struct dl_list_pair(bgp_connection) exist ; + /* list of existing connections */ + bgp_session session ; /* session connection belongs to */ /* NULL if connection stopping */ qpt_mutex p_mutex ; /* session mutex* */ @@ -139,10 +144,11 @@ struct bgp_connection unsigned lock_count ; /* session mutex lock count */ bgp_connection_ord_t ordinal ; /* primary/secondary connection */ - int accepted ; /* came via accept() */ + bool accepted ; /* came via accept() */ bgp_fsm_state_t state ; /* FSM state of connection */ - int comatose ; /* Idle and no timer set */ + bool comatose ; /* Idle and no timer set */ + bool half_open ; /* Idle but accepted connection */ bgp_connection next ; /* for the connection queue */ bgp_connection prev ; /* NULL <=> not on the queue */ @@ -156,7 +162,7 @@ struct bgp_connection bgp_open_state open_recv ; /* the open received. */ - struct qps_file qf ; /* qpselect file structure */ + qps_file qf ; /* qpselect file structure */ pAF_t paf ; /* address family */ union sockunion* su_local ; /* address of the near end */ @@ -168,17 +174,17 @@ struct bgp_connection unsigned hold_timer_interval ; /* subject to negotiation */ unsigned keepalive_timer_interval ; /* subject to negotiation */ - flag_t as4 ; /* subject to negotiation */ - flag_t route_refresh ; /* subject to negotiation */ - flag_t orf_prefix ; /* subject to negotiation */ + bool as4 ; /* subject to negotiation */ + bool route_refresh ; /* subject to negotiation */ + bool orf_prefix ; /* subject to negotiation */ - struct qtimer hold_timer ; - struct qtimer keepalive_timer ; + qtimer hold_timer ; + qtimer keepalive_timer ; struct stream* ibuf ; /* a single input "stream" */ unsigned read_pending ; /* how much input waiting for */ - flag_t read_header ; /* reading message header */ + bool read_header ; /* reading message header */ uint8_t msg_type ; /* copy of message type */ bgp_size_t msg_body_size ; /* size of message *body* */ bgp_msg_handler* msg_func ; /* function to handle message */ @@ -201,7 +207,7 @@ extern bgp_connection bgp_connection_init_new(bgp_connection connection, bgp_session session, bgp_connection_ord_t ordinal) ; extern void -bgp_connection_open(bgp_connection connection, int fd) ; +bgp_connection_open(bgp_connection connection, int fd, int family) ; extern void bgp_connection_start(bgp_connection connection, union sockunion* su_local, diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h index 6137226e..2d36b260 100644 --- a/bgpd/bgp_engine.h +++ b/bgpd/bgp_engine.h @@ -62,7 +62,9 @@ static struct queue_stats routing_engine_queue_stats ; Inline void bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) { - double average ; + unsigned long average ; + unsigned av_i ; + unsigned av_f ; unsigned my_count ; mqueue_block mqb ; @@ -96,11 +98,13 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) qpt_mutex_unlock(&mq->mutex) ; - average = stats->total ; - average /= stats->count ; + average = stats->total * 1000 ; + average = (average / stats->count) + 5 ; + av_i = average / 1000 ; + av_f = (average % 1000) / 10 ; - zlog_debug("%s queue: max=%u recent: max=%u av=%3.1f (%u) [x=%u e=%u u=%u]", - name, stats->max, stats->recent, average, stats->count, + zlog_debug("%s queue: max=%u recent: max=%u av=%d.%.2d (%u) [x=%u e=%u u=%u]", + name, stats->max, stats->recent, av_i, av_f, stats->count, stats->xon, stats->event, stats->update) ; stats->recent = 0 ; diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index d51ab09b..87e13ecb 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -390,7 +390,7 @@ static inline void bgp_hold_timer_recharge(bgp_connection connection) { if (connection->hold_timer_interval != 0) - qtimer_set_interval(&connection->hold_timer, + qtimer_set_interval(connection->hold_timer, QTIME(connection->hold_timer_interval), NULL) ; } ; @@ -634,11 +634,11 @@ bgp_fsm_io_error(bgp_connection connection, int err) if (err == 0) plog_debug(connection->log, "%s [Event] BGP connection closed fd %d", - connection->host, qps_file_fd(&connection->qf)) ; + connection->host, qps_file_fd(connection->qf)) ; else plog_debug(connection->log, "%s [Event] BGP connection closed fd %d (%s)", - connection->host, qps_file_fd(&connection->qf), + connection->host, qps_file_fd(connection->qf), safe_strerror(err)) ; } ; @@ -755,6 +755,7 @@ static bgp_fsm_action(bgp_fsm_enter) ; static bgp_fsm_action(bgp_fsm_stop) ; static bgp_fsm_action(bgp_fsm_invalid) ; static bgp_fsm_action(bgp_fsm_start) ; +static bgp_fsm_action(bgp_fsm_half_open) ; static bgp_fsm_action(bgp_fsm_connect) ; static bgp_fsm_action(bgp_fsm_accept) ; static bgp_fsm_action(bgp_fsm_send_open) ; @@ -827,7 +828,14 @@ static bgp_fsm_action(bgp_fsm_exit) ; * * raised when a connect() connection succeeds * - * b. secondary connection: in sActive state (-> OpenSent) + * b. secondary connection: in sIdle state (stay sIdle) + * + * raised when an accept() connection is accepted. + * + * The connection is not refused, but it is ignored until the + * IdleHoldTimer expires. + * + * c. secondary connection: in sActive state (-> OpenSent) * * raised when an accept() connection is accepted. * @@ -1082,12 +1090,19 @@ bgp_fsm[bgp_fsm_last_state + 1][bgp_fsm_last_event + 1] = * * * eBGP_Stop -- for whatever reason * + * * eTCP_connection_open -- generated if connection is accepted + * + * This set the connection "half_open", but stays in sIdle until + * the IdleHoldTimer expires. This avoids rejecting inbound + * connections, but also enforces the IdleHoldTimer if the other end + * is being vexatious. + * * All other events (other than null) are invalid (should not happen). */ {bgp_fsm_null, bgp_fsm_sIdle}, /* null event */ {bgp_fsm_start, bgp_fsm_sConnect}, /* BGP_Start */ {bgp_fsm_stop, bgp_fsm_sIdle}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_connection_open */ + {bgp_fsm_half_open, bgp_fsm_sIdle}, /* TCP_connection_open */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_connection_closed */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_connection_open_failed */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_fatal_error */ @@ -1397,9 +1412,6 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) ; /*------------------------------------------------------------------------------ * Signal event to FSM for the given connection. - * - * - * */ static void bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) @@ -1535,7 +1547,10 @@ static bgp_fsm_action(bgp_fsm_null) static bgp_fsm_action(bgp_fsm_enter) { if (connection->ordinal == bgp_connection_secondary) - bgp_prepare_to_accept(connection) ; + { + bgp_prepare_to_accept(connection) ; + bgp_connection_enable_accept(connection) ; + } ; return next_state ; } ; @@ -1582,22 +1597,72 @@ static bgp_fsm_action(bgp_fsm_invalid) } ; /*------------------------------------------------------------------------------ + * Half open a BGP Connection + * + * Used during sIdle when a connection is made before the IdleHoldTimer + * expires. + * + * Expected only for the secondary connection. + * + * Sets the connection half_open, and remains in sIdle. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_half_open) +{ + assert( (connection->ordinal == bgp_connection_secondary) + && !connection->half_open ) ; + + connection->half_open = 1 ; + + return next_state ; +} ; + +/*------------------------------------------------------------------------------ * Start up BGP Connection * * Used on exit from sIdle to sConnect or sActive -- when the IdleHoldTimer * expires. * - * Enters either sConnect or sActive, depending on primary/secondary. + * If not half open: + * + * Enters either sConnect or sActive, depending on primary/secondary. + * + * Throws a session_eStart exception so the Routing Engine gets to see this, + * and a follow-on fsm_eBGP_Start event to kick the connect() or accept() + * into life. * - * Throws a session_eStart exception so the Routing Engine gets to see this, - * and a follow-on fsm_eBGP_Start event to kick the connect() or accept() into - * life. + * If is half open: + * + * Must be secondary. Enters sActive. + * + * Throws a session_eStart exception so the Routing Engine gets to see this, + * and a follow-on bgp_fsm_eTCP_connection_open event to kick sActive into + * processing the already open connection. * * NB: requires the session LOCKED */ static bgp_fsm_action(bgp_fsm_start) { - bgp_fsm_throw(connection, bgp_session_eStart, NULL, 0, bgp_fsm_eBGP_Start) ; + bgp_fsm_event_t fsm_event ; + + if (!connection->half_open) + /* Straightforward -- set eBGP_Start follow-on event */ + fsm_event = bgp_fsm_eBGP_Start ; + else + { + /* Is half open -- so set a eTCP_connection_open follow-on event, then + * change to sActive where the event will be collected. + */ + assert(connection->ordinal == bgp_connection_secondary) ; + + connection->half_open = 0 ; + + fsm_event = bgp_fsm_eTCP_connection_open ; + } ; + + bgp_fsm_throw(connection, bgp_session_eStart, NULL, 0, fsm_event) ; + return (connection->ordinal == bgp_connection_primary) ? bgp_fsm_sConnect : bgp_fsm_sActive ; } ; @@ -1636,6 +1701,7 @@ static bgp_fsm_action(bgp_fsm_accept) return next_state ; } ; + /*------------------------------------------------------------------------------ * TCP connection open has come up -- connect() or accept() * @@ -1833,7 +1899,7 @@ static bgp_fsm_action(bgp_fsm_recv_open) } ; /* All is well: send a KEEPALIVE message to acknowledge the OPEN */ - bgp_msg_send_keepalive(connection) ; + bgp_msg_send_keepalive(connection, 1) ; /* Transition to OpenConfirm state */ return next_state ; @@ -1895,7 +1961,7 @@ static bgp_fsm_action(bgp_fsm_sent_nom) */ static bgp_fsm_action(bgp_fsm_send_kal) { - bgp_msg_send_keepalive(connection) ; + bgp_msg_send_keepalive(connection, 0) ; return next_state ; } ; @@ -2060,7 +2126,7 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) next_state = connection->state ; /* Make sure that cannot pop out a Keepalive ! */ - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->keepalive_timer) ; /* Write the message */ bgp_msg_write_notification(connection, send_notification) ; @@ -2207,7 +2273,7 @@ bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs, static void bgp_hold_timer_set(bgp_connection connection, unsigned secs) { - bgp_timer_set(connection, &connection->hold_timer, secs, no_jitter, + bgp_timer_set(connection, connection->hold_timer, secs, no_jitter, bgp_hold_timer_action) ; } ; @@ -2248,7 +2314,8 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) * When entering sIdle from anything other than Initial state, and not * falling into a coma, extend the IdleHoldTimer. * - * In sIdle state refuses connections. + * In sIdle state doesn't refuse connections (unless comatose), but won't + * act on them until the IdleHoldTimer expires. */ case bgp_fsm_sIdle: interval = session->idle_hold_timer_interval ; @@ -2277,21 +2344,28 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) session->idle_hold_timer_interval = interval ; + if (connection->ordinal == bgp_connection_secondary) + bgp_connection_enable_accept(connection) ; + /* if sibling is comatose, set time for it to come round */ if ((sibling != NULL) && (sibling->comatose)) { - connection->comatose = 0 ; /* no longer comatose */ - bgp_timer_set(sibling, &sibling->hold_timer, interval, + sibling->comatose = 0 ; /* no longer comatose */ + + if (sibling->ordinal == bgp_connection_secondary) + bgp_connection_enable_accept(sibling) ; + + bgp_timer_set(sibling, sibling->hold_timer, interval, with_jitter, bgp_idle_hold_timer_action) ; } ; } ; } ; - bgp_timer_set(connection, &connection->hold_timer, interval, + bgp_timer_set(connection, connection->hold_timer, interval, with_jitter, bgp_idle_hold_timer_action) ; - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->keepalive_timer) ; break; @@ -2304,10 +2378,10 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) */ case bgp_fsm_sConnect: case bgp_fsm_sActive: - bgp_timer_set(connection, &connection->hold_timer, + bgp_timer_set(connection, connection->hold_timer, session->connect_retry_timer_interval, with_jitter, bgp_connect_retry_timer_action) ; - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->keepalive_timer) ; break; /* In sOpenSent state is waiting for an OPEN from the other end, before @@ -2317,7 +2391,7 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) */ case bgp_fsm_sOpenSent: bgp_hold_timer_set(connection, session->open_hold_timer_interval) ; - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->keepalive_timer) ; break; /* In sOpenConfirm state is waiting for an "ack" before proceeding to @@ -2336,7 +2410,7 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) * value will also be zero, and this will unset both timers. */ case bgp_fsm_sOpenConfirm: - bgp_timer_set(connection, &connection->keepalive_timer, + bgp_timer_set(connection, connection->keepalive_timer, connection->keepalive_timer_interval, with_jitter, bgp_keepalive_timer_action) ; case bgp_fsm_sEstablished: @@ -2349,7 +2423,7 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) * or for the "courtesy" time to expire. */ case bgp_fsm_sStopping: - qtimer_unset(&connection->keepalive_timer) ; + qtimer_unset(connection->keepalive_timer) ; break ; @@ -2393,7 +2467,7 @@ bgp_connect_retry_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) BGP_FSM_DEBUG(connection, "Timer (connect timer expire)") ; - bgp_timer_set(connection, &connection->hold_timer, + bgp_timer_set(connection, connection->hold_timer, connection->session->connect_retry_timer_interval, with_jitter, NULL) ; bgp_fsm_event(connection, bgp_fsm_eConnectRetry_timer_expired) ; @@ -2427,7 +2501,7 @@ bgp_keepalive_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) BGP_FSM_DEBUG(connection, "Timer (keepalive timer expire)") ; - bgp_timer_set(connection, &connection->keepalive_timer, + bgp_timer_set(connection, connection->keepalive_timer, connection->session->keepalive_timer_interval, with_jitter, NULL) ; diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 366949af..32cbf91b 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -19,6 +19,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <zebra.h> +#include <stdbool.h> #include "vector.h" #include "vty.h" @@ -37,7 +38,6 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "plist.h" #include "qpnexus.h" #include "qlib_init.h" -#include "thread.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" @@ -72,57 +72,24 @@ static const struct option longopts[] = { "dryrun", no_argument, NULL, 'C'}, { "help", no_argument, NULL, 'h'}, { "threaded", no_argument, NULL, 't'}, + { "ignore_warnings", no_argument, NULL, 'I'}, { 0 } }; - -/* signal definitions */ -void sighup (void); -void sigint (void); -void sigusr1 (void); -void sigusr2 (void); - -/* prototypes */ -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); -static void sighup_enqueue(void); -static void sigterm_action(mqueue_block mqb, mqb_flag_t flag); -static void sigterm_enqueue(void); - -static struct quagga_signal_t bgp_signals[] = -{ - { - .signal = SIGHUP, - .handler = &sighup, - }, - { - .signal = SIGUSR1, - .handler = &sigusr1, - }, - { - .signal = SIGUSR2, - .handler = &sigusr2, - }, - { - .signal = SIGINT, - .handler = &sigint, - }, - { - .signal = SIGTERM, - .handler = &sigint, - }, -}; - /* Configuration file and directory. */ char config_default[] = SYSCONFDIR BGP_DEFAULT_CONFIG; /* Route retain mode flag. */ static int retain_mode = 0; +/* whether to ignore warnings in configuration file */ +static bool config_ignore_warnings = false; + +/* whether configured to run with qpthreads */ +static bool config_threaded = 0; + +/* whether configured to run as an AS2 speaker */ +static bool config_as2_speaker = 0; + /* Master of threads. */ struct thread_master *master; @@ -130,12 +97,12 @@ struct thread_master *master; char *config_file = NULL; /* Have we done the second stage initialization? */ -static int done_2nd_state_init = 0; +static int done_2nd_stage_init = 0; /* Process ID saved for use by init system */ static const char *pid_file = PATH_BGPD_PID; /* VTY port number and address. */ -int vty_port = BGP_VTY_PORT; +int vty_port = BGP_VTY_PORT; char *vty_addr = NULL; /* privileges */ @@ -167,59 +134,113 @@ usage (char *progname, int status) fprintf (stderr, "Try `%s --help' for more information.\n", progname); else { - printf ("Usage : %s [OPTION...]\n\n\ -Daemon which manages kernel routing table management and \ -redistribution between different routing protocols.\n\n\ --d, --daemon Runs in daemon mode\n\ --f, --config_file Set configuration file name\n\ --i, --pid_file Set process identifier file name\n\ --p, --bgp_port Set bgp protocol's port number\n\ --l, --listenon Listen on specified address (implies -n)\n\ --A, --vty_addr Set vty's bind address\n\ --P, --vty_port Set vty's port number\n\ --r, --retain When program terminates, retain added route by bgpd.\n\ --n, --no_kernel Do not install route to kernel.\n\ --u, --user User to run as\n\ --g, --group Group to run as\n\ --v, --version Print program version\n\ --C, --dryrun Check configuration for validity and exit\n\ --h, --help Display this help and exit\n\ --t, --threaded Use pthreads\n\ -\n\ -Report bugs to %s\n", progname, ZEBRA_BUG_ADDRESS); + printf ( + "Usage : %s [OPTION...]\n" + "\n" + "Daemon which manages kernel routing table management and redistribution " + "between different routing protocols.\n" + "\n" + "-d, --daemon Runs in daemon mode\n" + "-f, --config_file Set configuration file name\n" + "-i, --pid_file Set process identifier file name\n" + "-p, --bgp_port Set bgp protocol's port number\n" + "-l, --listenon Listen on specified address (implies -n)\n" + "-A, --vty_addr Set vty's bind address\n" + "-P, --vty_port Set vty's port number\n" + "-r, --retain When program terminates, retain added route by bgpd.\n" + "-n, --no_kernel Do not install route to kernel.\n" + "-u, --user User to run as\n" + "-g, --group Group to run as\n" + "-v, --version Print program version\n" + "-C, --dryrun Check configuration for validity and exit\n" + "-h, --help Display this help and exit\n" + "-t, --threaded Use pthreads\n" + "-I, --ignore_warnings Ignore warnings while reading configuration file\n" + "-2, --as2 Do not advertise AS4 capability\n" + "\n" + "Report bugs to %s\n", progname, ZEBRA_BUG_ADDRESS); } exit (status); } +/*============================================================================== + * Signal Handling. + * + * Actual signals are caught in lib/sigevent. When a signal is caught, a flag + * is set and the immediate signal handler returns. + * + * Those flags are polled in the qpnexus loop, and the Quagga level signal + * handler called -- in the main (CLI) thread. + */ + +/* signal definitions */ +void sighup (void); +void sigint (void); +void sigusr1 (void); +void sigusr2 (void); + +/* prototypes */ +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 void routing_finish(void) ; +static int routing_foreground(void); +static int routing_background(void); +static void sighup_action(mqueue_block mqb, mqb_flag_t flag); +static void sighup_enqueue(void); +static void sigterm_action(mqueue_block mqb, mqb_flag_t flag); +static void sigterm_enqueue(void); + +static struct quagga_signal_t bgp_signals[] = +{ + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGUSR2, + .handler = &sigusr2, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; -/* SIGHUP handler. */ +/*------------------------------------------------------------------------------ + * SIGHUP handler. + * + * The vty level is reset, closing all terminals and vtysh servers, and + * closing all listeners. + * + * A message is sent to the Routeing Engine to restart. + * + * When the Routeing Engine has restarted, it will send a message to the CLI + * to restart the listeners. + */ void sighup (void) { zlog (NULL, LOG_INFO, "SIGHUP received"); - /* tell the routing engine */ - sighup_enqueue(); - - zlog_info ("bgpd restarting!"); - - /* Reload config file. */ - vty_reset(); - vty_read_config (config_file, config_default); - - /* Create VTY's socket */ - vty_serv_sock (vty_addr, vty_port, BGP_VTYSH_PATH); + vty_reset_because("Reloading configuration"); + sighup_enqueue(); /* tell the Routeing Engine */ - /* Try to return to normal operation. */ } /* SIGINT handler. */ void sigint (void) { -#ifdef QDEBUG - vty_goodbye(); -#endif zlog_notice ("Terminating on signal"); /* tell the routing engine to send notifies to peers and wait @@ -245,20 +266,23 @@ sigusr2 (void) exit(1); } - -/* - Try to free up allocations we know about so that diagnostic tools such as - valgrind are able to better illuminate leaks. - - Zebra route removal and protocol teardown are not meant to be done here. - For example, "retain_mode" may be set. -*/ +/*------------------------------------------------------------------------------ + * Final exit code... + * + * ...try to free up allocations we know about so that diagnostic tools such as + * valgrind are able to better illuminate leaks. + * + * Zebra route removal and protocol teardown are not meant to be done here. + * For example, "retain_mode" may be set. + * + * Note that by the time reach here, only the main (CLI) thread is running, + * + */ static void bgp_exit (int status) { struct bgp *bgp; struct listnode *node, *nnode; - int *socket; struct interface *ifp; extern struct zclient *zclient; extern struct zclient *zlookup; @@ -271,14 +295,6 @@ bgp_exit (int status) bgp_delete (bgp); list_free (bm->bgp); - /* reverse bgp_master_init */ - for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, socket)) - { - if (close ((int)(long)socket) == -1) - zlog_err ("close (%d): %s", (int)(long)socket, safe_strerror (errno)); - } - list_delete (bm->listen_sockets); - /* reverse bgp_zebra_init/if_init */ if (retain_mode) if_add_hook (IF_DELETE_HOOK, NULL); @@ -286,6 +302,9 @@ bgp_exit (int status) if_delete (ifp); list_free (iflist); + /* curtains */ + zlog_notice ("Terminated"); + /* reverse bgp_attr_init */ bgp_attr_finish (); @@ -332,43 +351,59 @@ bgp_exit (int status) if (zlog_default) closezlog (zlog_default); + zlog_default = NULL ; + + if (qpthreads_enabled) + { + qpn_reset_free(routing_nexus); + qpn_reset_free(bgp_nexus); + } ; + cli_nexus = qpn_reset_free(cli_nexus); if (CONF_BGP_DEBUG (normal, NORMAL)) log_memstats_stderr ("bgpd"); - if (qpthreads_enabled) - { - routing_nexus = qpn_free(routing_nexus); - bgp_nexus = qpn_free(bgp_nexus); - } - cli_nexus = qpn_free(cli_nexus); - qexit (status); } +/*------------------------------------------------------------------------------ + * Second stage initialisation and qpthreads_enabled. + * + * Really want to do this before the configuration file is read. However, + * also want to allow qpthreads to be enabled by configuration file. + * + * So... configuration file reader has a mechanism to look for a given + * command as the *first* in the file and: + * + * 1. if it's there, invoke the command in the usual way + * + * 2. if it's not there, invoke the command but with a NULL set of arguments, + * which signals the "default" nature of the call. + * + * This mechanism is used so that the "threaded_cmd" is the time at which + * second stage initialisation is done. (But only once -- not on rereading + * the configuration file.) + */ + /* Threaded command. If present must be the first command in the - * configuration file. If not the first command it will log and abort. */ + * configuration file. If not the first command it will log and abort. + */ DEFUN_HID_CALL (threaded, threaded_cmd, "threaded", "Use pthreads\n") { - if (!qpthreads_enabled) - init_second_stage(1); + if (argv != NULL) + config_threaded = 1 ; /* Explicit command => turn on threading */ + if (!done_2nd_stage_init) + init_second_stage(config_threaded) ; + else + vty_out(vty, "pthreads are %s\n", qpthreads_enabled ? "enabled" + : "disabled") ; return CMD_SUCCESS; } -/* If neither the command line nor the first command enabled pthreads - * then disable pthreads. This is a call back after processing the - * first command in the configuration file - */ -static void after_first_cmd() -{ - if (!qpthreads_enabled) - init_second_stage(0); -} - /* Enable or disables pthreads. Create the nexus(es). Perform * any post nexus creation initialization. The nexus(es) need * to be created as soon as we know the pthread state so that @@ -377,10 +412,9 @@ static void after_first_cmd() static void init_second_stage(int pthreads) { - if (done_2nd_state_init) - return; + assert(!done_2nd_stage_init) ; - done_2nd_state_init = 1; + done_2nd_stage_init = 1; qlib_init_second_stage(pthreads); bgp_peer_index_mutex_init(); @@ -411,6 +445,7 @@ init_second_stage(int pthreads) 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(&routing_nexus->in_thread_final, routing_finish) ; qpn_add_hook_function(&bgp_nexus->in_thread_final, bgp_close_listeners) ; qpn_add_hook_function(&routing_nexus->foreground, routing_foreground) ; @@ -445,7 +480,6 @@ main (int argc, char **argv) int dryrun = 0; char *progname; int tmp_port; - int threaded = 0; /* Set umask before anything for security */ umask (0027); @@ -468,7 +502,7 @@ main (int argc, char **argv) /* Command line argument treatment. */ while (1) { - opt = getopt_long (argc, argv, "df:i:hp:l:A:P:rnu:g:vCt", longopts, 0); + opt = getopt_long (argc, argv, "df:i:hp:l:A:P:rnu:g:vCtI2", longopts, 0); if (opt == EOF) break; @@ -533,9 +567,15 @@ main (int argc, char **argv) case 'h': usage (progname, 0); break; - case 't': - threaded = 1; - break; + case 't': + config_threaded = 1; + break; + case 'I': + config_ignore_warnings = 1; + break ; + case '2': + config_as2_speaker = 1; + break ; default: usage (progname, 1); break; @@ -554,17 +594,25 @@ main (int argc, char **argv) vty_init (master); memory_init (); - if (threaded) - init_second_stage(1); + /* Read config file. + * + * NB: second state initialisation is done in the threaded_cmd, which must + * either be the first command in the file, or is executed by default + * before the first command in the file. + * + * NB: if fails to open the configuration file, fails to read anything, or + * it is completely empty (or effectively so), then may still need to do + * second stage initialisation. + */ + done_2nd_stage_init = 0 ; - /* Parse config file. */ - vty_read_config_first_cmd_special (config_file, config_default, after_first_cmd); + vty_read_config_first_cmd_special(config_file, config_default, + &threaded_cmd, config_ignore_warnings) ; - /* if not threaded and empty config file then still need to - * do second stage initialization - */ - if (!done_2nd_state_init) - init_second_stage(0); + if (!done_2nd_stage_init) + init_second_stage(config_threaded) ; + + bm->as2_speaker = config_as2_speaker ; /* Start execution only if not in dry-run mode */ if (dryrun) @@ -575,18 +623,18 @@ main (int argc, char **argv) */ assert(!qpthreads_thread_created); - /* Turn into daemon if daemon_mode is set. */ + /* Turn into daemon if daemon_mode is set. */ if (daemon_mode && daemon (0, 0) < 0) { zlog_err("BGPd daemon failed: %s", safe_strerror(errno)); return (1); } - /* Process ID file creation. */ + /* Process ID file creation. */ pid_output (pid_file); - /* Make bgp vty socket. */ - vty_serv_sock (vty_addr, vty_port, BGP_VTYSH_PATH); + /* Ready to run VTY now. */ + vty_start(vty_addr, vty_port, BGP_VTYSH_PATH); /* Print banner. */ #ifdef QDEBUG @@ -614,9 +662,12 @@ main (int argc, char **argv) } else { - qpn_exec(cli_nexus); /* only nexus - on main thread */ + qpn_exec(cli_nexus); /* only nexus - on main thread */ } + /* Note that from this point forward is running in the main (CLI) thread + * and any other threads have been joined and their nexuses freed. + */ bgp_exit(0); } @@ -624,7 +675,7 @@ main (int argc, char **argv) static void bgp_in_thread_init(void) { - bgp_open_listeners(bm->port, bm->address); + bgp_open_listeners(bm->address, bm->port); } /* routing_nexus in-thread initialization -- for gdb ! */ @@ -637,6 +688,12 @@ routing_start(void) routing_started = 1 ; } +static void +routing_finish(void) +{ + routing_started = 0 ; +} + /* legacy threads in routing engine */ static int routing_foreground(void) @@ -651,14 +708,17 @@ routing_background(void) return thread_dispatch_background(master) ; } -/* SIGINT/TERM SIGHUP need to tell routing engine what to do */ - +/*------------------------------------------------------------------------------ + * SIGHUP: message sent to Routeing engine and the action it then takes. + * + * TODO: should SIGHUP be a priority message (!) + */ static void sighup_enqueue(void) { mqueue_block mqb = mqb_init_new(NULL, sighup_action, NULL) ; - mqueue_enqueue(routing_nexus->queue, mqb, 0) ; + mqueue_enqueue(routing_nexus->queue, mqb, 1) ; } /* dispatch a command from the message queue block */ @@ -667,19 +727,32 @@ sighup_action(mqueue_block mqb, mqb_flag_t flag) { if (flag == mqb_action) { + zlog_info ("bgpd restarting!"); + bgp_terminate (0, 0); /* send notifies */ bgp_reset (); + + /* Reload config file. */ + vty_read_config (config_file, config_default); + + /* Create VTY's socket */ + vty_restart(vty_addr, vty_port, BGP_VTYSH_PATH); + + /* Try to return to normal operation. */ } mqb_free(mqb); } +/*------------------------------------------------------------------------------ + * SIGTERM: message sent to Routeing engine and the action it then takes. + */ static void sigterm_enqueue(void) { mqueue_block mqb = mqb_init_new(NULL, sigterm_action, NULL) ; - mqueue_enqueue(routing_nexus->queue, mqb, 0) ; + mqueue_enqueue(routing_nexus->queue, mqb, 1) ; } /* dispatch a command from the message queue block */ diff --git a/bgpd/bgp_msg_read.c b/bgpd/bgp_msg_read.c index 425038fd..e8a2897e 100644 --- a/bgpd/bgp_msg_read.c +++ b/bgpd/bgp_msg_read.c @@ -1499,7 +1499,7 @@ bgp_msg_route_refresh_receive(bgp_connection connection, bgp_size_t body_size) if (suck_left(&ssr) != 0) { uint8_t when_to_refresh ; - flag_t defer = 0 ; + bool defer = 0 ; when_to_refresh = suck_b(&ssr) ; @@ -1620,9 +1620,9 @@ bgp_msg_orf_recv(bgp_connection connection, bgp_route_refresh rr, { do { - flag_t remove_all = 0 ; - flag_t remove = 0 ; - flag_t deny = 0 ; + bool remove_all = 0 ; + bool remove = 0 ; + bool deny = 0 ; uint8_t common ; common = suck_b(sr) ; diff --git a/bgpd/bgp_msg_write.c b/bgpd/bgp_msg_write.c index 6ba76545..1fdb4e7a 100644 --- a/bgpd/bgp_msg_write.c +++ b/bgpd/bgp_msg_write.c @@ -21,13 +21,13 @@ * Boston, MA 02111-1307, USA. */ +#include <zebra.h> +#include <stdbool.h> + #include "bgpd/bgp_common.h" #include "bgpd/bgp_msg_write.h" #include "bgpd/bgp_route_refresh.h" - -#include <zebra.h> - #include "thread.h" #include "stream.h" #include "network.h" @@ -155,11 +155,11 @@ bgp_msg_write_notification(bgp_connection connection, bgp_notify notification) /*------------------------------------------------------------------------------ * Make KEEPALIVE message and dispatch. * - * NB: does nothing if the write buffer is not empty. This is not a problem, - * the KEEPALIVE is redundant if there is stuff waiting to go ! + * Generally, does nothing if the write buffer is not empty -- a KEEPALIVE is + * redundant if there is stuff waiting to go ! * - * KEEPALIVE is sent in response to OPEN, and that MUST be sent. But if the - * buffers are full at that point, something is broken ! + * KEEPALIVE is sent in response to OPEN, and that MUST be sent, hence the + * 'must_send' option. * * Returns: 1 => written to wbuff -- qpselect will write from there * 0 => nothing written -- no need, buffer not empty ! @@ -170,12 +170,12 @@ bgp_msg_write_notification(bgp_connection connection, bgp_notify notification) * NB: requires the session LOCKED -- connection-wise */ extern int -bgp_msg_send_keepalive(bgp_connection connection) +bgp_msg_send_keepalive(bgp_connection connection, bool must_send) { struct stream *s = connection->obuf ; int length; - if (!bgp_connection_write_empty(connection)) + if (!must_send && !bgp_connection_write_empty(connection)) return 0 ; ++connection->session->stats.keepalive_out ; @@ -505,7 +505,7 @@ bgp_msg_send_route_refresh(bgp_connection connection, bgp_route_refresh rr) { struct stream *s = connection->obuf ; uint8_t msg_type ; - flag_t done ; + bool done ; bgp_size_t msg_len ; ++connection->session->stats.refresh_out ; @@ -583,8 +583,8 @@ bgp_msg_orf_part(struct stream* s, bgp_connection connection, bgp_size_t left ; bgp_size_t length ; - flag_t done ; - flag_t first ; + bool done ; + bool first ; /* Heading for Prefix-Address ORF type section */ whenp = stream_get_endp(s) ; /* position of "when" */ diff --git a/bgpd/bgp_msg_write.h b/bgpd/bgp_msg_write.h index 5355ed70..77bfc1f2 100644 --- a/bgpd/bgp_msg_write.h +++ b/bgpd/bgp_msg_write.h @@ -25,6 +25,7 @@ #define _QUAGGA_BGP_MSG_WRITE_H #include <stdint.h> +#include <stdbool.h> #include "bgpd/bgp_common.h" #include "bgpd/bgp_connection.h" @@ -38,7 +39,7 @@ extern int bgp_msg_write_notification(bgp_connection connection, bgp_notify notification) ; extern int -bgp_msg_send_keepalive(bgp_connection connection) ; +bgp_msg_send_keepalive(bgp_connection connection, bool must_send) ; extern int bgp_msg_send_open(bgp_connection connection, bgp_open_state open_state) ; diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c index 856ac879..b929bf14 100644 --- a/bgpd/bgp_network.c +++ b/bgpd/bgp_network.c @@ -20,6 +20,7 @@ */ #include <zebra.h> +#include <stdbool.h> #include "sockunion.h" #include "sockopt.h" @@ -55,10 +56,11 @@ static void bgp_accept_action(qps_file qf, void* file_info) ; static int -bgp_getsockname(int fd, union sockunion* su_local, union sockunion* su_remote) ; +bgp_get_names(int sock_fd, union sockunion* su_local, + union sockunion* su_remote) ; static int -bgp_socket_set_common_options(int fd, union sockunion* su, int ttl, +bgp_socket_set_common_options(int sock_fd, union sockunion* su, int ttl, const char* password) ; static int bgp_md5_set_listeners(union sockunion* su, const char* password) ; @@ -98,7 +100,76 @@ struct bgp_listener } ; /* Forward reference */ -static int bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) ; +static int bgp_open_listener_on(const char* address, unsigned short port); +static int bgp_init_listener(int sock_fd, struct sockaddr *sa, socklen_t salen); + +/*------------------------------------------------------------------------------ + * Open Listeners. + * + * Using given address and port, get all possible addresses and set up a + * listener on each one. + * + * Accepts: address = NULL => any local address + * address = comma separated list of addresses + * + * NB: an empty address counts as "any local address", so: + * + * "80.177.246.130,80.177.246.131" -- will listen on those addresses. + * + * "80.177.246.130," -- will list on that address and + * any other local address. + * + * NB: only listens on AF_INET and (if HAVE_IPV6) AF_INET6. + * + * Returns: > 0 => OK -- number of listeners set up + * -1 => failed -- no listeners set up + */ +extern int +bgp_open_listeners(const char* address, unsigned short port) +{ + int count ; + bool do_null ; + + count = 0 ; + do_null = (address == NULL) ; + + if (!do_null) + { + char* copy ; + char* next ; + char* this ; + + copy = XSTRDUP(MTYPE_TMP, address) ; + + next = copy ; + while (next != NULL) + { + this = next ; + next = strchr(address, ',') ; + + if (next != NULL) + *next++ = '\0' ; + + if (*this == '\0') + do_null = true ; /* empty address => do_null */ + else + count += bgp_open_listener_on(this, port) ; + } ; + + XFREE(MTYPE_TMP, copy) ; + } ; + + if (do_null) + count += bgp_open_listener_on(NULL, port) ; + + if (count == 0) + { + zlog_err ("%s: no usable addresses", __func__); + return -1; + } + + return 0; +} ; /*------------------------------------------------------------------------------ * Open Listeners. @@ -110,10 +181,9 @@ static int bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) ; * * Returns: 0 => OK * -1 => failed -- no listeners set up - * */ -extern int -bgp_open_listeners(unsigned short port, const char *address) +static int +bgp_open_listener_on(const char* address, unsigned short port) { #if defined (HAVE_IPV6) && ! defined (NRL) /*----------------------------*/ @@ -137,47 +207,41 @@ bgp_open_listeners(unsigned short port, const char *address) if (ret != 0) { zlog_err ("getaddrinfo: %s", gai_strerror (ret)); - return -1; + return 0 ; } count = 0; for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next) { - int sock; + int sock_fd; if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) continue; - sock = sockunion_socket(ainfo->ai_family, ainfo->ai_socktype, + sock_fd = sockunion_socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); - if (sock < 0) + if (sock_fd < 0) { zlog_err ("socket: %s", safe_strerror (errno)); continue; } - ret = bgp_init_listener(sock, ainfo->ai_addr, ainfo->ai_addrlen); + ret = bgp_init_listener(sock_fd, ainfo->ai_addr, ainfo->ai_addrlen); if (ret == 0) ++count; else - close(sock); + close(sock_fd); } freeaddrinfo (ainfo_save); - if (count == 0) - { - zlog_err ("%s: no usable addresses", __func__); - return -1; - } - - return 0; + return count ; } #else /*----------------------------------------------------*/ /* Traditional IPv4 only version. */ - int sock; + int sock_fd; int socklen; struct sockaddr_in sin; int ret, en; @@ -197,20 +261,20 @@ bgp_open_listeners(unsigned short port, const char *address) sin.sin_len = socklen; #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - sock = socket (AF_INET, SOCK_STREAM, 0); - if (sock < 0) + sock_fd = socket (AF_INET, SOCK_STREAM, 0); + if (sock_fd < 0) { zlog_err ("socket: %s", safe_strerror (errno)); - return sock; + return sock_fd; } - ret = bgp_init_listener (sock, (struct sockaddr *) &sin, socklen); + ret = bgp_init_listener (sock_fd, (struct sockaddr *) &sin, socklen); if (ret < 0) { - close (sock); + close (sock_fd); return ret; } - return sock; + return sock_fd; } #endif /* HAVE_IPV6 && !NRL --------------------------------------------------*/ @@ -264,57 +328,65 @@ bgp_reset_listeners(bgp_listener* p_listener) * != 0 : error number (from errno or otherwise) */ static int -bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) +bgp_init_listener(int sock_fd, struct sockaddr *sa, socklen_t salen) { bgp_listener listener ; - int ret ; + int ret, err ; - ret = bgp_socket_set_common_options(sock, (union sockunion*)sa, 0, NULL) ; - if (ret != 0) - return ret ; + err = bgp_socket_set_common_options(sock_fd, (union sockunion*)sa, 0, NULL) ; + if (err != 0) + return err ; -#ifdef IPV6_V6ONLY - /* Want only IPV6 on ipv6 socket (not mapped addresses) */ +#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + /* Want only IPV6 on ipv6 socket (not mapped addresses) + * + * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the + * attempt to bind to :: after binding to 0.0.0.0. + */ if (sa->sa_family == AF_INET6) { int on = 1; - /* TODO: trap errors when setting IPPROTO_IPV6, IPV6_V6ONLY ?? */ - setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)) ; + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if (ret < 0) + return errno ; } #endif if (bgpd_privs.change(ZPRIVS_RAISE)) { - ret = errno ; - zlog_err("%s: could not raise privs", __func__); - - return ret ; + err = errno ; + zlog_err("%s: could not raise privs: %s", __func__, safe_strerror(errno)); } ; - ret = bind(sock, sa, salen) ; + ret = bind(sock_fd, sa, salen) ; if (ret < 0) { - ret = errno ; - zlog_err ("bind: %s", safe_strerror(ret)); + err = errno ; + zlog_err ("%s: bind: %s", __func__, safe_strerror(err)); } ; if (bgpd_privs.change(ZPRIVS_LOWER)) { - if (ret == 0) - ret = errno ; - zlog_err("%s: could not lower privs", __func__) ; + if (err == 0) + err = errno ; + zlog_err("%s: could not lower privs: %s", __func__, safe_strerror(errno)); } ; - if (ret != 0) - return ret ; + if (err == 0) + { + ret = listen (sock_fd, 43); + if (ret < 0) + { + err = errno ; + zlog_err ("%s: listen: %s", __func__, safe_strerror(err)) ; + } + } ; - ret = listen (sock, 3); - if (ret < 0) + if (err != 0) { - ret = errno ; - zlog_err ("listen: %s", safe_strerror(ret)) ; - return ret; - } + close(sock_fd) ; + return err ; + } ; /* Having successfully opened the listener, record it so that can be found * again, add it to the BGP Engine Nexus file selection and enable it for @@ -324,7 +396,7 @@ bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) listener = XCALLOC(MTYPE_BGP_LISTENER, sizeof(struct bgp_listener)) ; qps_file_init_new(&listener->qf, NULL) ; - qps_add_file(bgp_nexus->selection, &listener->qf, sock, listener) ; + qps_add_file(bgp_nexus->selection, &listener->qf, sock_fd, listener) ; qps_enable_mode(&listener->qf, qps_read_mnum, bgp_accept_action) ; memcpy(&listener->su, sa, salen) ; @@ -352,11 +424,11 @@ bgp_init_listener(int sock, struct sockaddr *sa, socklen_t salen) extern void bgp_prepare_to_accept(bgp_connection connection) { - int ret ; + int err ; if (connection->session->password != NULL) { - ret = bgp_md5_set_listeners(connection->session->su_peer, + err = bgp_md5_set_listeners(connection->session->su_peer, connection->session->password) ; /* TODO: failure to set password in bgp_prepare_to_accept ? */ @@ -376,11 +448,11 @@ bgp_prepare_to_accept(bgp_connection connection) extern void bgp_not_prepared_to_accept(bgp_connection connection) { - int ret ; + int err ; if (connection->session->password != NULL) { - ret = bgp_md5_set_listeners(connection->session->su_peer, NULL) ; + err = bgp_md5_set_listeners(connection->session->su_peer, NULL) ; /* TODO: failure to clear password in bgp_not_prepared_to_accept ? */ } ; @@ -441,17 +513,18 @@ bgp_accept_action(qps_file qf, void* file_info) union sockunion su_remote ; union sockunion su_local ; int exists ; - int fd ; - int ret ; + int sock_fd ; + int err ; + int family ; char buf[SU_ADDRSTRLEN] ; /* Accept client connection. */ - fd = sockunion_accept(qps_file_fd(qf), &su_remote) ; - if (fd < 0) + sock_fd = sockunion_accept(qps_file_fd(qf), &su_remote) ; + if (sock_fd < 0) { - if (fd == -1) - zlog_err("[Error] BGP socket accept failed (%s)", - safe_strerror(errno)) ; + err = errno ; + if (sock_fd == -1) + zlog_err("[Error] BGP socket accept failed (%s)", safe_strerror(err)) ; return ; /* have no connection to report this to */ } ; @@ -468,7 +541,7 @@ bgp_accept_action(qps_file qf, void* file_info) ? "[Event] BGP accept IP address %s is not accepting" : "[Event] BGP accept IP address %s is not configured", sockunion2str(&su_remote, buf, sizeof(buf))) ; - close(fd) ; + close(sock_fd) ; return ; /* quietly reject connection */ /* TODO: RFC recommends sending a NOTIFICATION when refusing accept() */ } ; @@ -489,34 +562,46 @@ bgp_accept_action(qps_file qf, void* file_info) /* Set the common socket options. * Does not set password -- that is inherited from the listener. * - * If all is well, set up the listener connection, and set it ready - * to go. Set session not to accept further inbound connections. - * - * Kicks the FSM with bgp_fsm_TCP_connection_open. + * At this point, su_remote is the value returned by accept(), so is the + * actual address (which may be IPv6 mapped IPv4). */ + err = bgp_socket_set_common_options(sock_fd, &su_remote, + connection->session->ttl, NULL) ; - ret = bgp_getsockname(fd, &su_local, &su_remote) ; - if (ret != 0) - ret = bgp_socket_set_common_options(fd, &su_remote, - connection->session->ttl, NULL) ; + /* Get the actual socket family. */ + if (err == 0) + { + family = sockunion_getsockfamily(sock_fd) ; + if (family < 0) + err = errno ; + } ; - if (ret == 0) - bgp_connection_open(connection, fd) ; + /* Get the local and remote addresses -- noting that IPv6 mapped IPv4 + * addresses are rendered as IPv4 addresses. + */ + if (err == 0) + err = bgp_get_names(sock_fd, &su_local, &su_remote) ; + + /* If all is well, set up the accept connection, and set it ready + * to go. Set session not to accept further inbound connections. + */ + if (err == 0) + bgp_connection_open(connection, sock_fd, family) ; else - close(fd) ; + close(sock_fd) ; BGP_CONNECTION_SESSION_UNLOCK(connection) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ /* Now kick the FSM in an appropriate fashion */ - bgp_fsm_connect_completed(connection, ret, &su_local, &su_remote) ; + bgp_fsm_connect_completed(connection, err, &su_local, &su_remote) ; } ; /*============================================================================== * Open BGP Connection -- connect() to the other end */ -static int bgp_bind_ifname(bgp_connection connection, int fd) ; -static int bgp_bind_ifaddress(bgp_connection connection, int fd) ; +static int bgp_bind_ifname(bgp_connection connection, int sock_fd) ; +static int bgp_bind_ifaddress(bgp_connection connection, int sock_fd) ; /*------------------------------------------------------------------------------ * Open BGP Connection -- connect() to the other end @@ -533,45 +618,55 @@ static int bgp_bind_ifaddress(bgp_connection connection, int fd) ; extern void bgp_open_connect(bgp_connection connection) { - int fd ; - int ret ; + int sock_fd ; + int err ; + int family ; union sockunion* su = connection->session->su_peer ; + err = 0 ; + /* Make socket for the connect connection. */ - fd = sockunion_socket(sockunion_family(su), SOCK_STREAM, 0) ; - ret = (fd >= 0) ? 0 : errno ; + family = sockunion_family(su) ; + sock_fd = sockunion_socket(family, SOCK_STREAM, 0) ; + if (sock_fd < 0) + err = errno ; /* Set the common options. */ - if (ret == 0) - ret = bgp_socket_set_common_options(fd, su, connection->session->ttl, + if (err == 0) + err = bgp_socket_set_common_options(sock_fd, su, connection->session->ttl, connection->session->password) ; /* Bind socket. */ - if (ret == 0) - ret = bgp_bind_ifname(connection, fd) ; + if (err == 0) + err = bgp_bind_ifname(connection, sock_fd) ; /* Update source bind. */ - if (ret == 0) - ret = bgp_bind_ifaddress(connection, fd) ; + if (err == 0) + err = bgp_bind_ifaddress(connection, sock_fd) ; if (BGP_DEBUG(events, EVENTS)) - plog_debug(connection->log, "%s [Event] Connect start to %s fd %d", - connection->host, connection->host, fd); + plog_debug(connection->log, "%s [Event] Connect start to %s socket %d", + connection->host, connection->host, sock_fd); /* Connect to the remote peer. */ - if (ret == 0) - ret = sockunion_connect(fd, su, connection->session->port, + if (err == 0) + { + int ret ; + ret = sockunion_connect(sock_fd, su, connection->session->port, connection->session->ifindex) ; /* does not report EINPROGRESS as an error. */ + if (ret < 0) + err = errno ; + } ; - /* If not OK now, close the fd and signal the error */ + /* If not OK now, close the sock_fd and signal the error */ - if (ret != 0) + if (err != 0) { - if (fd >= 0) - close(fd) ; + if (sock_fd >= 0) + close(sock_fd) ; - bgp_fsm_connect_completed(connection, ret, NULL, NULL) ; + bgp_fsm_connect_completed(connection, err, NULL, NULL) ; return ; } ; @@ -584,15 +679,15 @@ bgp_open_connect(bgp_connection connection) * up immediately). * if fails: will become readable (may also become writable) * - * Generally, expect it to be a while before the fd becomes readable or + * Generally, expect it to be a while before the sock_fd becomes readable or * writable. But for local connections this may happen immediately. But, * in any case, this will be handled by the qpselect action. */ - bgp_connection_open(connection, fd) ; + bgp_connection_open(connection, sock_fd, family) ; - qps_enable_mode(&connection->qf, qps_read_mnum, bgp_connect_action) ; - qps_enable_mode(&connection->qf, qps_write_mnum, bgp_connect_action) ; + qps_enable_mode(connection->qf, qps_read_mnum, bgp_connect_action) ; + qps_enable_mode(connection->qf, qps_write_mnum, bgp_connect_action) ; return ; } ; @@ -609,6 +704,11 @@ bgp_open_connect(bgp_connection connection) * * Either way, use getsockopt() to extract any error condition. * + * If becomes both readable and writable at the same time, then the first to + * arrive here will disable the file for both read and write, which will + * discard the other pending event -- so will not attempt to do this more than + * once. + * * NB: does not require the session mutex. * * Events and Errors: @@ -634,7 +734,7 @@ bgp_connect_action(qps_file qf, void* file_info) { bgp_connection connection ; int ret, err ; - socklen_t len = sizeof(err) ; + socklen_t len ; union sockunion su_remote ; union sockunion su_local ; @@ -643,54 +743,73 @@ bgp_connect_action(qps_file qf, void* file_info) /* See if connection successful or not. */ /* If successful, set the connection->su_local and ->su_remote */ + len = sizeof(err) ; + err = 0 ; ret = getsockopt(qps_file_fd(qf), SOL_SOCKET, SO_ERROR, &err, &len) ; - if (ret != 0) - ret = errno ; - else if (len != sizeof(err)) - zabort("getsockopt returned unexpected length") ; - else if (err != 0) - ret = err ; + if (ret != 0) + { + err = errno ; + if (err == 0) /* cannot be and cannot continue */ + zabort("Invalid return from getsockopt()") ; + } else - ret = bgp_getsockname(qps_file_fd(qf), &su_local, &su_remote) ; + { + if (len != sizeof(err)) + zabort("getsockopt returned unexpected length") ; + } ; + + if (err == 0) + err = bgp_get_names(qps_file_fd(qf), &su_local, &su_remote) ; /* In any case, disable both read and write for this file. */ qps_disable_modes(qf, qps_write_mbit | qps_read_mbit) ; /* Now kick the FSM in an appropriate fashion */ - bgp_fsm_connect_completed(connection, ret, &su_local, &su_remote) ; + bgp_fsm_connect_completed(connection, err, &su_local, &su_remote) ; } ; /*============================================================================== - * Set the TTL for the given connection (if any), if there is an fd. + * Set the TTL for the given connection (if any), if there is an sock_fd. */ extern void bgp_set_ttl(bgp_connection connection, int ttl) { - int fd ; + int sock_fd ; if (connection == NULL) return ; - fd = qps_file_fd(&connection->qf) ; - if (fd < 0) + sock_fd = qps_file_fd(connection->qf) ; + if (sock_fd < 0) return ; if (ttl != 0) - sockopt_ttl(connection->paf, fd, ttl) ; + sockopt_ttl(sock_fd, ttl) ; } ; /*============================================================================== * Get local and remote address and port for connection. + * + * Returns: 0 => OK + * != 0 : error number (from errno or otherwise) */ static int -bgp_getsockname(int fd, union sockunion* su_local, union sockunion* su_remote) +bgp_get_names(int sock_fd, union sockunion* su_local, + union sockunion* su_remote) { - int ret_l, ret_r ; + int ret, err ; - ret_l = sockunion_getsockname(fd, su_local) ; - ret_r = sockunion_getpeername(fd, su_remote) ; + err = 0 ; - return (ret_l != 0) ? ret_l : ret_r ; + ret = sockunion_getsockname(sock_fd, su_local) ; + if (ret < 0) + err = errno ; + + ret = sockunion_getpeername(sock_fd, su_remote) ; + if ((ret < 0) && (err == 0)) + err = errno ; + + return err ; } ; /*============================================================================== @@ -704,44 +823,46 @@ bgp_getsockname(int fd, union sockunion* su_local, union sockunion* su_remote) * If there is a specific interface to bind an outbound connection to, that * is done here. * - * * Returns: 0 : OK (so far so good) * != 0 : error number (from errno or otherwise) */ static int -bgp_bind_ifname(bgp_connection connection, int fd) +bgp_bind_ifname(bgp_connection connection, int sock_fd) { #ifdef SO_BINDTODEVICE - int ret, retp ; + int ret, err ; struct ifreq ifreq; if (connection->session->ifname == NULL) return 0; strncpy ((char *)&ifreq.ifr_name, connection->session->ifname, - sizeof (ifreq.ifr_name)); + sizeof (ifreq.ifr_name)) ; - ret = 0 ; - retp = 0 ; + err = 0 ; if (bgpd_privs.change (ZPRIVS_RAISE)) { - zlog_err ("bgp_bind: could not raise privs"); - retp = -1 ; + err = errno ; + zlog_err ("bgp_bind: could not raise privs: %s", safe_strerror(errno)); } ; - ret = setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof (ifreq)); + ret = setsockopt (sock_fd, SOL_SOCKET, SO_BINDTODEVICE, + &ifreq, sizeof (ifreq)) ; + if (ret < 0) + err = errno ; if (bgpd_privs.change (ZPRIVS_LOWER) ) { - zlog_err ("bgp_bind: could not lower privs"); - retp = -1 ; + if (err == 0) + err = errno ; + zlog_err ("bgp_bind: could not lower privs: %s", safe_strerror(errno)); } ; - if ((ret < 0) || (retp < 0)) + if (err != 0) { - zlog (connection->log, LOG_INFO, "bind to interface %s failed", - connection->session->ifname); - return -1 ; + zlog (connection->log, LOG_INFO, "bind to interface %s failed (%s)", + connection->session->ifname, safe_strerror(err)); + return err ; } #endif /* SO_BINDTODEVICE */ return 0; @@ -754,12 +875,34 @@ bgp_bind_ifname(bgp_connection connection, int fd) * != 0 : error number (from errno or otherwise) */ static int -bgp_bind_ifaddress(bgp_connection connection, int fd) +bgp_bind_ifaddress(bgp_connection connection, int sock_fd) { if (connection->session->ifaddress != NULL) { - union sockunion su = *(connection->session->ifaddress) ; - return sockunion_bind (fd, &su, 0, &su) ; + union sockunion su ; + int ret ; + int family ; + + sockunion_new_sockaddr(&su, &connection->session->ifaddress->sa) ; + + family = sockunion_getsockfamily(sock_fd) ; + if (family < 0) + return errno ; + +#ifdef HAVE_IPV6 + if (family != sockunion_family(&su)) + { + if (family == AF_INET) + sockunion_unmap_ipv4(&su) ; + if (family == AF_INET6) + sockunion_map_ipv4(&su) ; + } ; +#endif + + ret = sockunion_bind (sock_fd, &su, 0, &su) ; + + if (ret < 0) + return errno ; } ; return 0 ; } ; @@ -769,7 +912,7 @@ bgp_bind_ifaddress(bgp_connection connection, int fd) */ static int -bgp_md5_set_socket(int fd, union sockunion *su, const char *password) ; +bgp_md5_set_socket(int sock_fd, union sockunion *su, const char *password) ; /*------------------------------------------------------------------------------ * Common socket options: @@ -787,39 +930,39 @@ bgp_md5_set_socket(int fd, union sockunion *su, const char *password) ; * != 0 == errno -- not that we really expect any errors here */ static int -bgp_socket_set_common_options(int fd, union sockunion* su, int ttl, +bgp_socket_set_common_options(int sock_fd, union sockunion* su, int ttl, const char* password) { - int ret ; + int err ; int val ; /* Make socket non-blocking */ - val = fcntl(fd, F_GETFL, 0) ; - if (val != -1) /* Don't really expect it to be -1 (see POSIX) */ - val = fcntl(fd, F_SETFL, val | O_NONBLOCK) ; + val = fcntl(sock_fd, F_GETFL, 0) ; + if (val != -1) /* POSIX says "return value is not negative" */ + val = fcntl(sock_fd, F_SETFL, val | O_NONBLOCK) ; if (val == -1) return errno ; /* Reuse addr and port */ - if (sockopt_reuseaddr(fd) < 0) + if (sockopt_reuseaddr(sock_fd) < 0) return errno ; - if (sockopt_reuseport(fd) < 0) + if (sockopt_reuseport(sock_fd) < 0) return errno ; /* Adjust ttl if required */ if (ttl != 0) - if ((ret = sockopt_ttl(sockunion_family(su), fd, ttl)) != 0) - return ret ; + if (sockopt_ttl(sock_fd, ttl) != 0) + return errno ; /* Set the TCP MD5 "password", if required. */ if (password != NULL) - if ((ret = bgp_md5_set_socket(fd, su, password)) != 0) - return ret ; + if ((err = bgp_md5_set_socket(sock_fd, su, password)) != 0) + return err ; #ifdef IPTOS_PREC_INTERNETCONTROL /* set IPPROTO_IP/IP_TOS -- if is AF_INET */ if (sockunion_family(su) == AF_INET) - if (setsockopt_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) < 0) + if (setsockopt_ipv4_tos(sock_fd, IPTOS_PREC_INTERNETCONTROL) < 0) return errno ; #endif @@ -840,34 +983,36 @@ bgp_socket_set_common_options(int fd, union sockunion* su, int ttl, * NB: has to change up privileges, which can fail (if things are badly set up) */ static int -bgp_md5_set_socket(int fd, union sockunion *su, const char *password) +bgp_md5_set_socket(int sock_fd, union sockunion *su, const char *password) { - int ret ; + int err, ret ; - assert(fd >= 0) ; + assert(sock_fd >= 0) ; + + err = 0 ; if (bgpd_privs.change(ZPRIVS_RAISE)) { - ret = errno ; - zlog_err("%s: could not raise privs", __func__); - - return ret ; + err = errno ; + zlog_err("%s: could not raise privs: %s", __func__, safe_strerror(errno)); } ; - ret = sockopt_tcp_signature(fd, su, password) ; + ret = sockopt_tcp_signature(sock_fd, su, password) ; if (ret != 0) /* TODO: error already logged as zlog_err() */ - zlog (NULL, LOG_WARNING, "can't set TCP_MD5SIG option on socket %d: %s", - fd, safe_strerror(ret)); + err = errno ; if (bgpd_privs.change(ZPRIVS_LOWER)) { - if (ret == 0) - ret = errno ; - zlog_err("%s: could not lower privs", __func__) ; + if (err == 0) + err = errno ; + zlog_err("%s: could not lower privs: %s", __func__, safe_strerror(errno)); } ; - return ret; + if (err != 0) + zlog (NULL, LOG_WARNING, "cannot set TCP_MD5SIG option on socket %d: %s", + sock_fd, safe_strerror(err)) ; + return err ; } ; /*------------------------------------------------------------------------------ @@ -891,7 +1036,7 @@ static int bgp_md5_set_listeners(union sockunion* su, const char* password) { bgp_listener listener ; - int ret ; + int err ; #ifdef HAVE_IPV6 assert((su->sa.sa_family == AF_INET) || (su->sa.sa_family == AF_INET6)) ; @@ -903,9 +1048,9 @@ bgp_md5_set_listeners(union sockunion* su, const char* password) while (listener != NULL) { - ret = bgp_md5_set_socket(qps_file_fd(&listener->qf), su, password) ; - if (ret != 0) - return ret ; + err = bgp_md5_set_socket(qps_file_fd(&listener->qf), su, password) ; + if (err != 0) + return err ; listener = listener->next ; } ; diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h index 30ec3b19..4aa62aac 100644 --- a/bgpd/bgp_network.h +++ b/bgpd/bgp_network.h @@ -25,7 +25,7 @@ #include "bgpd/bgp_connection.h" extern int -bgp_open_listeners(unsigned short port, const char *address) ; +bgp_open_listeners(const char *address, unsigned short port) ; extern void bgp_close_listeners(void) ; diff --git a/bgpd/bgp_open_state.c b/bgpd/bgp_open_state.c index dda00aa3..17f9ae36 100644 --- a/bgpd/bgp_open_state.c +++ b/bgpd/bgp_open_state.c @@ -137,8 +137,10 @@ bgp_peer_open_state_init_new(bgp_open_state state, bgp_peer peer) CHECK_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN) && (! CHECK_FLAG(peer->flags, PEER_FLAG_DONT_CAPABILITY) ) ; - /* Announce self as AS4 speaker if required */ - state->can_as4 = ((peer->cap & PEER_CAP_AS4_ADV) != 0) ; + /* Announce self as AS4 speaker always */ + if (!bm->as2_speaker) + SET_FLAG(peer->cap, PEER_CAP_AS4_ADV) ; + state->can_as4 = CHECK_FLAG(peer->cap, PEER_CAP_AS4_ADV) ? 1 : 0 ; state->my_as2 = (state->my_as > BGP_AS2_MAX ) ? BGP_ASN_TRANS : state->my_as ; @@ -150,8 +152,9 @@ bgp_peer_open_state_init_new(bgp_open_state state, bgp_peer peer) if (peer->afc[afi][safi]) state->can_mp_ext |= qafx_bit(qafx_num_from_qAFI_qSAFI(afi, safi)) ; - /* Route refresh. */ - state->can_r_refresh = (peer->cap & PEER_CAP_REFRESH_ADV) + /* Route refresh -- always */ + SET_FLAG(peer->cap, PEER_CAP_REFRESH_ADV) ; + state->can_r_refresh = CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_ADV) ? (bgp_form_pre | bgp_form_rfc) : bgp_form_none ; @@ -175,10 +178,13 @@ bgp_peer_open_state_init_new(bgp_open_state state, bgp_peer peer) /* Dynamic Capabilities TODO: check requirement */ state->can_dynamic = ( CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_CAPABILITY) != 0 ) ; + if (state->can_dynamic) + SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV) ; /* Graceful restart capability */ if (bgp_flag_check(peer->bgp, BGP_FLAG_GRACEFUL_RESTART)) { + SET_FLAG(peer->cap, PEER_CAP_RESTART_ADV) ; state->can_g_restart = 1 ; state->restart_time = peer->bgp->restart_time ; } @@ -249,7 +255,7 @@ bgp_open_state_unknown_cap(bgp_open_state state, unsigned index) */ extern bgp_cap_afi_safi bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi, - flag_t known, uint8_t cap_code) + bool known, uint8_t cap_code) { bgp_cap_afi_safi afi_safi ; diff --git a/bgpd/bgp_open_state.h b/bgpd/bgp_open_state.h index 0612bbff..8ef109bd 100644 --- a/bgpd/bgp_open_state.h +++ b/bgpd/bgp_open_state.h @@ -56,28 +56,28 @@ struct bgp_cap_mp typedef struct bgp_cap_orf* bgp_cap_orf ; struct bgp_cap_orf { - flag_t known_orf_type ; + bool known_orf_type ; - uint8_t type ; - flag_t send ; - flag_t recv ; + uint8_t type ; + bool send ; + bool recv ; } ; typedef struct bgp_cap_gr* bgp_cap_gr ; struct bgp_cap_gr { - flag_t has_preserved ; + bool has_preserved ; } ; typedef struct bgp_cap_afi_safi* bgp_cap_afi_safi ; struct bgp_cap_afi_safi { - flag_t known_afi_safi ; + bool known_afi_safi ; - iAFI_t afi ; - iSAFI_t safi ; + iAFI_t afi ; + iSAFI_t safi ; - uint8_t cap_code ; /* eg BGP_CAN_MP_EXT */ + uint8_t cap_code ; /* eg BGP_CAN_MP_EXT */ union { struct bgp_cap_mp mp ; @@ -145,7 +145,7 @@ bgp_open_state_unknown_cap(bgp_open_state state, unsigned index) ; extern bgp_cap_afi_safi bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi, - flag_t known, uint8_t cap_code) ; + bool known, uint8_t cap_code) ; extern int bgp_open_state_afi_safi_count(bgp_open_state state) ; diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index ce70bee7..71779567 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -772,13 +772,6 @@ bgp_notify_send_with_data (struct peer *peer, u_char code, u_char sub_code, bgp_notify notification; notification = bgp_notify_new_with_data(code, sub_code, data, datalen); - /* For debug */ - bgp_notify_print (peer, notification, "sending"); - - if (BGP_DEBUG (normal, NORMAL)) - zlog_debug ("%s send message type %d, length (incl. header) %d", - peer->host, BGP_MSG_NOTIFY, notification->length); - /* peer reset cause */ if (sub_code != BGP_NOTIFY_CEASE_CONFIG_CHANGE) { diff --git a/bgpd/bgp_peer.c b/bgpd/bgp_peer.c index 99430c1d..18634fb0 100644 --- a/bgpd/bgp_peer.c +++ b/bgpd/bgp_peer.c @@ -424,6 +424,7 @@ bgp_peer_stop (struct peer *peer) BGP_TIMER_OFF (peer->t_asorig); BGP_TIMER_OFF (peer->t_routeadv); + peer->cap = 0 ; for (afi = AFI_IP ; afi < AFI_MAX ; afi++) for (safi = SAFI_UNICAST ; safi < SAFI_MAX ; safi++) { @@ -1036,19 +1037,21 @@ void bgp_peer_enable(bgp_peer peer) { /* Don't enable the session if: - * 1) Peer not idle, means we're not ready yet, clearing, deleting or waiting - * for disable. - * 2) Shutdown - * 3) Dealing with prefix overflow, its timer will enable peer when ready + * 1) the peer not idle, which means not ready yet: clearing, deleting or + * waiting for disable. + * 2) no address family is activated + * 3) the peer has been shutdown + * 4) is 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) + && peer_active (peer) && !CHECK_FLAG (peer->flags, PEER_FLAG_SHUTDOWN) && !CHECK_FLAG (peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) { /* enable the session */ + zlog_err ("%s: enabling peer %s:", __func__, peer->host) ; + bgp_session_enable(peer); } } diff --git a/bgpd/bgp_peer.h b/bgpd/bgp_peer.h index 7e8a7e74..91070377 100644 --- a/bgpd/bgp_peer.h +++ b/bgpd/bgp_peer.h @@ -204,6 +204,10 @@ struct peer #define PEER_CAP_AS4_ADV (1 << 7) /* as4 advertised */ #define PEER_CAP_AS4_RCV (1 << 8) /* as4 received */ +#define PEER_CAP_AS4_BOTH (PEER_CAP_AS4_ADV + PEER_CAP_AS4_RCV) +#define PEER_CAP_AS4_USE(peer) \ + (((peer)->cap & PEER_CAP_AS4_BOTH) == PEER_CAP_AS4_BOTH) + /* Capability flags (reset in bgp_stop) */ u_int16_t af_cap[AFI_MAX][SAFI_MAX]; #define PEER_CAP_ORF_PREFIX_SM_ADV (1 << 0) /* send-mode advertised */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index f8c4c74e..5ca43c72 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -7346,7 +7346,7 @@ DEFUN (show_ipv6_mbgp_prefix, static int -bgp_show_regexp (struct vty *vty, int argc, const char **argv, afi_t afi, +bgp_show_regexp (struct vty *vty, int argc, argv_t argv, afi_t afi, safi_t safi, enum bgp_show_type type) { int i; @@ -7925,8 +7925,8 @@ DEFUN (show_ipv6_mbgp_community_all, #endif /* HAVE_IPV6 */ static int -bgp_show_community (struct vty *vty, int argc, const char **argv, int exact, - u_int16_t afi, u_char safi) +bgp_show_community (struct vty *vty, int argc, argv_t argv, + int exact, u_int16_t afi, u_char safi) { struct community *com; struct buffer *b; @@ -7941,7 +7941,8 @@ bgp_show_community (struct vty *vty, int argc, const char **argv, int exact, buffer_putc (b, ' '); else { - if ((strcmp (argv[i], "unicast") == 0) || (strcmp (argv[i], "multicast") == 0)) + if ( (strcmp (argv[i], "unicast") == 0) + || (strcmp (argv[i], "multicast") == 0) ) continue; first = 1; } diff --git a/bgpd/bgp_route_refresh.c b/bgpd/bgp_route_refresh.c index 11448e67..5c2b6e5c 100644 --- a/bgpd/bgp_route_refresh.c +++ b/bgpd/bgp_route_refresh.c @@ -84,7 +84,7 @@ bgp_route_refresh_free(bgp_route_refresh rr) * Set the defer flag as required */ extern void -bgp_route_refresh_set_orf_defer(bgp_route_refresh rr, flag_t defer) +bgp_route_refresh_set_orf_defer(bgp_route_refresh rr, bool defer) { rr->defer = (defer != 0) ; } ; @@ -154,12 +154,12 @@ bgp_orf_entry_new(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form, */ extern bgp_orf_entry bgp_orf_add(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form, - flag_t remove, flag_t deny) + bool remove, bool deny) { bgp_orf_entry orfe = bgp_orf_entry_new(rr, orf_type, form, 0) ; - orfe->remove = (remove != 0) ; - orfe->deny = (deny != 0) ; + orfe->remove = remove ; + orfe->deny = deny ; return orfe ; } ; diff --git a/bgpd/bgp_route_refresh.h b/bgpd/bgp_route_refresh.h index 3afd997e..b44ca9e0 100644 --- a/bgpd/bgp_route_refresh.h +++ b/bgpd/bgp_route_refresh.h @@ -51,13 +51,13 @@ struct bgp_orf_entry uint8_t orf_type ; /* BGP_ORF_T_xxx -- _rfc version ! */ bgp_form_t form ; /* bgp_form_none/_rfc/_pre */ - flag_t unknown ; /* ignore everything other than the */ + bool unknown ; /* ignore everything other than the */ /* unknown data part */ - flag_t remove_all ; /* rest is ignored if this is set */ + bool remove_all ; /* rest is ignored if this is set */ - flag_t remove ; /* otherwise: add */ - flag_t deny ; /* otherwise: permit */ + bool remove ; /* otherwise: add */ + bool deny ; /* otherwise: permit */ union { /* must be last... */ struct orf_prefix orf_prefix ; @@ -83,7 +83,7 @@ struct bgp_route_refresh struct vector entries ; /* empty => simple ROUTE-REFRESH */ - flag_t defer ; /* otherwise: immediate */ + bool defer ; /* otherwise: immediate */ /* These support the output of ROUTE-REFRESH messages. * @@ -104,11 +104,11 @@ extern void bgp_route_refresh_free(bgp_route_refresh rr) ; extern void -bgp_route_refresh_set_orf_defer(bgp_route_refresh rr, flag_t defer) ; +bgp_route_refresh_set_orf_defer(bgp_route_refresh rr, bool defer) ; extern bgp_orf_entry bgp_orf_add(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form, - flag_t remove, flag_t deny) ; + bool remove, bool deny) ; extern void bgp_orf_add_remove_all(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form); diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c index 9d17e36c..e1cb103d 100644 --- a/bgpd/bgp_session.c +++ b/bgpd/bgp_session.c @@ -31,9 +31,11 @@ #include "bgpd/bgp_network.h" #include "bgpd/bgp_packet.h" +#include "bgp_debug.h" #include "lib/memory.h" #include "lib/sockunion.h" +#include "lib/log.h" #include "lib/mqueue.h" #include "lib/zassert.h" @@ -421,19 +423,6 @@ bgp_session_disable(bgp_peer peer, bgp_notify notification) args = mqb_get_args(mqb) ; args->notification = notification ; - { - int c, s ; - if (notification != NULL) - { - c = notification->code ; - s = notification->subcode ; - } - else - { - c = 0 ; - s = 0 ; - } ; - } ; ++bgp_engine_queue_stats.event ; @@ -456,6 +445,16 @@ bgp_session_do_disable(mqueue_block mqb, mqb_flag_t flag) /* Immediately discard any other messages for this session. */ mqueue_revoke(bgp_nexus->queue, session) ; + /* For debug */ + if (args->notification != NULL) + { + bgp_notify_print (session->peer, args->notification, "sending"); + + if (BGP_DEBUG (normal, NORMAL)) + zlog_debug ("%s send message type %d, length (incl. header) %d", + session->host, BGP_MSG_NOTIFY, args->notification->length); + } ; + /* Get the FSM to send any notification and close connections */ bgp_fsm_disable_session(session, args->notification) ; } diff --git a/bgpd/bgp_session.h b/bgpd/bgp_session.h index 5b144db1..615c6829 100644 --- a/bgpd/bgp_session.h +++ b/bgpd/bgp_session.h @@ -146,11 +146,11 @@ struct bgp_session /* The following are set by the Routeing Engine before a session is * enabled, and not changed at any other time by either engine. */ - flag_t connect ; /* initiate connections */ - flag_t listen ; /* listen for connections */ + bool connect ; /* initiate connections */ + bool listen ; /* listen for connections */ - flag_t cap_override ; /* override ... TODO: what ? */ - flag_t cap_strict ; /* strict... TODO: what ? */ + bool cap_override ; /* override ... TODO: what ? */ + bool cap_strict ; /* strict... TODO: what ? */ int ttl ; /* TTL to set, if not zero */ unsigned short port ; /* destination port for peer */ @@ -183,9 +183,9 @@ struct bgp_session unsigned hold_timer_interval ; /* subject to negotiation */ unsigned keepalive_timer_interval ; /* subject to negotiation */ - flag_t as4 ; /* set by OPEN */ - flag_t route_refresh_pre ; /* use pre-RFC version */ - flag_t orf_prefix_pre ; /* use pre-RFC version */ + bool as4 ; /* set by OPEN */ + bool route_refresh_pre ; /* use pre-RFC version */ + bool orf_prefix_pre ; /* use pre-RFC version */ /* These are cleared by the Routeing Engine before a session is enabled, * and set by the BGP Engine when the session is established. @@ -213,7 +213,7 @@ struct bgp_session */ bgp_connection connections[bgp_connection_count] ; - flag_t active ; + bool active ; } ; /*============================================================================== diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 71d124cd..5e0374c1 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -6757,8 +6757,8 @@ bgp_show_summary (struct vty *vty, struct bgp *bgp, int afi, int safi) count++; - len = vty_out (vty, "%s", peer->host); - len = 16 - len; + vty_out (vty, "%s", peer->host); + len = 16 - strlen(peer->host); if (len < 1) vty_out (vty, "%s%*s", VTY_NEWLINE, 16, " "); else @@ -8002,8 +8002,8 @@ bgp_write_rsclient_summary (struct vty *vty, struct peer *rsclient, return count; } - len = vty_out (vty, "%s", rsclient->host); - len = 16 - len; + vty_out (vty, "%s", rsclient->host); + len = 16 - strlen(rsclient->host) ; if (len < 1) vty_out (vty, "%s%*s", VTY_NEWLINE, 16, " "); @@ -9990,7 +9990,7 @@ community_list_perror (struct vty *vty, int ret) /* VTY interface for community_set() function. */ static int -community_list_set_vty (struct vty *vty, int argc, const char **argv, +community_list_set_vty (struct vty *vty, int argc, argv_t argv, int style, int reject_all_digit_name) { int ret; @@ -10043,7 +10043,7 @@ community_list_set_vty (struct vty *vty, int argc, const char **argv, /* Communiyt-list entry delete. */ static int -community_list_unset_vty (struct vty *vty, int argc, const char **argv, +community_list_unset_vty (struct vty *vty, int argc, argv_t argv, int style) { int ret; @@ -10349,7 +10349,7 @@ DEFUN (show_ip_community_list_arg, } static int -extcommunity_list_set_vty (struct vty *vty, int argc, const char **argv, +extcommunity_list_set_vty (struct vty *vty, int argc, argv_t argv, int style, int reject_all_digit_name) { int ret; @@ -10397,8 +10397,7 @@ extcommunity_list_set_vty (struct vty *vty, int argc, const char **argv, } static int -extcommunity_list_unset_vty (struct vty *vty, int argc, const char **argv, - int style) +extcommunity_list_unset_vty (struct vty *vty, int argc, argv_t argv, int style) { int ret; int direct = 0; diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index b8846609..b1ea09dc 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -936,6 +936,10 @@ peer_deactivate (struct peer *peer, afi_t afi, safi_t safi) { if (peer->state == bgp_peer_sEstablished) { + /* Unless can dynamically reconfigure: once a session is established, + * turning off an address family requires the session to be dropped + * and restarted. + */ if (CHECK_FLAG (peer->cap, PEER_CAP_DYNAMIC_RCV)) { peer->afc_adv[afi][safi] = 0; @@ -963,6 +967,13 @@ peer_deactivate (struct peer *peer, afi_t afi, safi_t safi) BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } + else if ((peer->state == bgp_peer_sIdle) && !peer_active (peer)) + { + /* In sIdle, the BGP Engine may be trying to connect... if no address + * family is now active, need now to shut down the session. + */ + bgp_peer_disable(peer, NULL) ; + } ; } return 0; } @@ -4646,10 +4657,9 @@ bgp_master_init (void) memset (&bgp_master, 0, sizeof (struct bgp_master)); bm = &bgp_master; - bm->bgp = list_new (); - bm->listen_sockets = list_new (); - bm->port = BGP_PORT_DEFAULT; - bm->master = thread_master_create (); + bm->bgp = list_new (); + bm->port = BGP_PORT_DEFAULT; + bm->master = thread_master_create (); bm->start_time = time (NULL); } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index d6ce07e3..cd92576a 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -21,6 +21,8 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _QUAGGA_BGPD_H #define _QUAGGA_BGPD_H +#include "stdbool.h" + #include "bgpd/bgp_common.h" #include "bgpd/bgp_notification.h" #include "bgpd/bgp_peer.h" @@ -39,9 +41,6 @@ struct bgp_master /* BGP thread master. */ struct thread_master *master; - /* Listening sockets */ - struct list *listen_sockets; - /* BGP port number. */ u_int16_t port; @@ -56,6 +55,9 @@ struct bgp_master #define BGP_OPT_NO_FIB (1 << 0) #define BGP_OPT_MULTIPLE_INSTANCE (1 << 1) #define BGP_OPT_CONFIG_CISCO (1 << 2) + + /* Do not announce AS4 */ + bool as2_speaker ; }; /* BGP instance structure. */ diff --git a/lib/Makefile.am b/lib/Makefile.am index 17940f8d..9022c245 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -15,7 +15,8 @@ libzebra_la_SOURCES = \ sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \ qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \ command_queue.c qlib_init.c pthread_safe.c list_util.c \ - vty_io.c vty_cli.c keystroke.c qstring.c vio_fifo.c + vty_io.c vty_cli.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ + qiovec.c qfstring.c BUILT_SOURCES = memtypes.h route_types.h @@ -34,7 +35,8 @@ pkginclude_HEADERS = \ qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \ command_queue.h qlib_init.h qafi_safi.h \ confirm.h miyagi.h pthread_safe.h list_util.h node_type.h uty.h \ - vty_io.h vty_cli.h keystroke.h qstring.h vio_fifo.h + vty_io.h vty_cli.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ + qiovec.h qfstring.h EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt diff --git a/lib/command.c b/lib/command.c index 251c8963..6e1726dc 100644 --- a/lib/command.c +++ b/lib/command.c @@ -33,6 +33,7 @@ Boston, MA 02111-1307, USA. */ #include "uty.h" #include "qstring.h" #include "command.h" +//#include "lib/route_types.h" #include "workqueue.h" #include "command_queue.h" @@ -172,7 +173,7 @@ print_version (const char *progname) /* Utility function to concatenate argv argument into a single string with inserting ' ' character between each argument. */ char * -argv_concat (const char **argv, int argc, int shift) +argv_concat (const char* const* argv, int argc, int shift) { int i; size_t len; @@ -196,6 +197,28 @@ argv_concat (const char **argv, int argc, int shift) return str; } +#if 0 //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/* Compare two command's string. Used in sort_node (). */ +static int +cmp_node (const void *p, const void *q) +{ + const struct cmd_element *a = *(struct cmd_element * const *)p; + const struct cmd_element *b = *(struct cmd_element * const *)q; + + return strcmp (a->string, b->string); +} + +static int +cmp_desc (const void *p, const void *q) +{ + const struct desc *a = *(struct desc * const *)p; + const struct desc *b = *(struct desc * const *)q; + + return strcmp (a->cmd, b->cmd); +} + +#endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + /* Install top node of command vector. */ void install_node (struct cmd_node *node, @@ -208,41 +231,57 @@ install_node (struct cmd_node *node, /* Compare two command's string. Used in sort_node (). */ static int -cmp_node (const struct cmd_element *a, const struct cmd_element *b) +cmp_node (const struct cmd_element **a, const struct cmd_element **b) { - return strcmp (a->string, b->string); + return strcmp ((*a)->string, (*b)->string); } static int -cmp_desc (const struct desc *a, const struct desc *b) +cmp_desc (const struct desc **a, const struct desc **b) { - return strcmp (a->cmd, b->cmd); + return strcmp ((*a)->cmd, (*b)->cmd); } /* Sort each node's command element according to command string. */ void sort_node () { - unsigned int i, j; - struct cmd_node *cnode; - struct cmd_element *cmd_element; + unsigned int i ; - for (i = 0; i < vector_active (cmdvec); i++) - if ((cnode = vector_slot (cmdvec, i)) != NULL) - { - vector cmd_vector = cnode->cmd_vector; - vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ; + for (i = 0; i < vector_length(cmdvec); i++) + { + struct cmd_node *cnode; + vector cmd_vector ; + unsigned int j; - for (j = 0; j < vector_active (cmd_vector); j++) - if ((cmd_element = vector_slot (cmd_vector, j)) != NULL - && vector_active (cmd_element->strvec)) - { - vector descvec = vector_slot (cmd_element->strvec, - vector_active (cmd_element->strvec) - 1); - vector_sort(descvec, (vector_sort_cmp*)cmp_desc) ; - } - } -} + cnode = vector_get_item(cmdvec, i) ; + + if (cnode == NULL) + continue ; + + cmd_vector = cnode->cmd_vector; + if (cmd_vector == NULL) + continue ; + + vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ; + + for (j = 0; j < vector_length(cmd_vector); j++) + { + struct cmd_element *cmd_element ; + vector descvec ; + + cmd_element = vector_get_item (cmd_vector, j); + if (cmd_element == NULL) + continue ; + + descvec = vector_get_last_item(cmd_element->strvec) ; + if (descvec == NULL) + continue ; + + vector_sort(descvec, (vector_sort_cmp*)cmp_desc) ; + } ; + } ; +} ; /*------------------------------------------------------------------------------ * Take string and break it into tokens. @@ -260,57 +299,132 @@ sort_node () * 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) +static vector +cmd_make_vline(vector vline, qstring qs, const char *string) { - const char *cp, *start; - char *token; - int strlen; - vector strvec; + char *token, *tp ; + const char *cp, *sp, *ep, *op ; - if (string == NULL) - return NULL; + /* Reset any existing vline, and empty the qstring if given. */ + if (vline != NULL) + vector_set_length(vline, 0) ; + qs_clear(qs) ; + + /* Strip leading and trailing white-space and deal with empty or effectively + * empty lines -- comment lines are treated as effectively empty. + */ cp = string; - /* Skip white spaces. */ + if (string == NULL) + return NULL; + while (isspace((int) *cp)) cp++; - /* Return if line is empty or effectively empty */ - if ((*cp == '\0') || (*cp == '!') || (*cp == '#')) + ep = cp + strlen(cp) ; + + while ((ep > cp) && (isspace((int)*(ep - 1)))) + --ep ; + + if ((cp == ep) || (*cp == '!') || (*cp == '#')) return NULL; /* Prepare return vector -- expect some reasonable number of tokens. */ - strvec = vector_init (10) ; + if (vline == NULL) + vline = vector_init(10) ; - /* Copy each command piece and set into vector. */ - while (1) + /* If writing the words to a qstring, copy the body of the original (less + * any leading/trailing whitespace) to the qstring and '\0' terminate. + */ + if (qs != NULL) { - start = 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_push_item(strvec, token); + qs_set_n(qs, cp, ep - cp) ; + tp = (char*)qs->body ; /* start at the beginning */ + } + else + tp = NULL ; /* not used, but not undefined */ + op = cp ; /* original start position */ + + /* Now have string cp..ep with no leading/trailing whitespace. + * + * If using a qstring, a copy of that exists at tp, complete with terminating + * '\0'. Writes '\0' terminators after each word found -- overwriting first + * separating white-space or the '\0' at the end. + * + * If not using a qstring, construct a new MTYPE_STRVEC for each word. + */ + while (cp < ep) + { while (isspace((int) *cp)) - cp++ ; /* skip white-space */ + cp++ ; /* skip white-space */ - if (*cp == '\0') - return strvec; - } + sp = cp ; + while ((cp < ep) && !isspace((int) *cp)) + cp++ ; /* eat token characters */ + + if (qs == NULL) + { + /* creating array of MTYPE_STRVEC */ + size_t len ; + + len = cp - sp ; + token = XMALLOC (MTYPE_STRVEC, len + 1); + memcpy (token, sp, len); + *(token + len) = '\0'; + } + else + { + /* using qstring */ + token = tp + (sp - op) ; /* token in qstring */ + *(tp + (cp - op)) = '\0' ; /* terminate */ + } ; + + vector_push_item(vline, token); + } ; + + return vline ; } /*------------------------------------------------------------------------------ + * Take string and break it into tokens. + * + * Discards leading and trailing white-space. + * + * Treats lines that start with '!' or '#' (after any leading white-space) + * as empty -- these are comment lines. + * + * Tokens are non-whitespace separated by one or more white-space. + * + * White-space is anything that isspace() thinks is a space. (Which in the + * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.) + * + * Returns: NULL => empty line (after white-space trimming) or comment line. + * otherwise: is vector containing one or more tokens. + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the caller's responsibility to release the vector and its contents, + * see cmd_free_strvec(). + */ +extern vector +cmd_make_strvec (const char *string) +{ + return cmd_make_vline(NULL, NULL, string) ; +} ; + +/*------------------------------------------------------------------------------ * Add given string to vector of strings. * * Create vector if required. @@ -478,23 +592,27 @@ cmd_cmdsize (vector strvec) { unsigned int i; int size = 0; - vector descvec; - struct desc *desc; - for (i = 0; i < vector_active (strvec); i++) - if ((descvec = vector_slot (strvec, i)) != NULL) + for (i = 0; i < vector_length(strvec); i++) { - if ((vector_active (descvec)) == 1 - && (desc = vector_slot (descvec, 0)) != NULL) - { - if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) - return size; - else - size++; - } - else - size++; - } + vector descvec; + + descvec = vector_get_item (strvec, i) ; + if (descvec == NULL) + continue ; + + if (vector_length(descvec) == 1) + { + struct desc *desc; + + desc = vector_get_item(descvec, 0) ; + if (desc != NULL) + if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) + break ; + } + size++; + } ; + return size; } @@ -509,7 +627,7 @@ cmd_prompt (enum node_type node) cnode = NULL ; if (node < cmdvec->limit) - cnode = vector_slot (cmdvec, node); + cnode = vector_get_item (cmdvec, node); if (cnode == NULL) { @@ -530,7 +648,7 @@ install_element (enum node_type ntype, struct cmd_element *cmd) if (!cmdvec) return; - cnode = vector_slot (cmdvec, ntype); + cnode = vector_get_item (cmdvec, ntype); if (cnode == NULL) { @@ -675,7 +793,7 @@ config_write_host (struct vty *vty) static vector cmd_node_vector (vector v, enum node_type ntype) { - struct cmd_node *cnode = vector_slot (v, ntype); + struct cmd_node *cnode = vector_get_item (v, ntype); return cnode->cmd_vector; } @@ -1290,12 +1408,14 @@ static enum match_type cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) { unsigned int i; + unsigned int k; enum match_type match_type; match_type = no_match; - /* If command and cmd_element string does not match set NULL to vector */ - for (i = 0; i < vector_active (cmd_v); i++) + /* If command and cmd_element string does not match, remove from vector */ + k = 0 ; + for (i = 0; i < vector_length (cmd_v); i++) { const char *str; struct cmd_element *cmd_element; @@ -1304,110 +1424,113 @@ cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) unsigned int j; int matched ; + cmd_element = vector_get_item(cmd_v, i) ; + /* Skip past cmd_v entries that have already been set NULL */ - if ((cmd_element = vector_slot (cmd_v, i)) == NULL) + if (cmd_element == NULL) continue ; /* 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 ; - } ; + descvec = vector_get_item(cmd_element->strvec, index) ; + if (descvec == NULL) + continue ; /* See if get any sort of match at current position */ matched = 0 ; - descvec = vector_slot (cmd_element->strvec, index); - - 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; + for (j = 0; j < vector_length (descvec); j++) + { + desc = vector_get_item(descvec, j) ; + if (desc == NULL) + continue ; + + str = desc->cmd; + + if (CMD_VARARG (str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE (str)) + { + if (cmd_range_match (str, command)) + { + if (match_type < range_match) + match_type = range_match; - matched++; - } - } + matched++; + } + } #ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command)) - { - if (match_type < ipv6_match) - match_type = ipv6_match; + 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_PREFIX (str)) + { + if (cmd_ipv6_prefix_match (command)) + { + if (match_type < ipv6_prefix_match) + match_type = ipv6_prefix_match; - matched++; - } - } + 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++; - } ; - } ; + 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; - } + /* Keep cmd_v entry that has a match at this position */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; + + vector_set_length(cmd_v, k) ; /* discard what did not keep */ return match_type; -} +} ; /*------------------------------------------------------------------------------ * Filter vector by command character with index. @@ -1420,107 +1543,120 @@ cmd_filter_by_completion (char *command, vector cmd_v, unsigned int index) static enum match_type cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) { - unsigned int i; - const char *str; - struct cmd_element *cmd_element; + unsigned int i ; + unsigned int k ; 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 (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 (cmd_v, i) = NULL; - else - { - unsigned int j; - int matched = 0; + /* If command and cmd_element string do match, keep in vector */ + k = 0 ; + for (i = 0; i < vector_length(cmd_v); i++) + { + unsigned int j; + int matched ; + const char *str; + struct cmd_element *cmd_element; + vector descvec; + struct desc *desc; - descvec = vector_slot (cmd_element->strvec, index); + cmd_element = vector_get_item(cmd_v, i) ; - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - str = desc->cmd; + /* Skip past NULL cmd_v entries (just in case) */ + if (cmd_element == 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; - matched++; - } - } + /* Discard cmd_v entry that has no token at the current position */ + descvec = vector_get_item (cmd_element->strvec, index) ; + if (descvec == NULL) + continue ; + + /* See if have a match against any of the current possibilities */ + matched = 0 ; + for (j = 0; j < vector_length(descvec); j++) + { + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; + + str = desc->cmd; + + if (CMD_VARARG (str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE (str)) + { + if (cmd_range_match (str, command)) + { + if (match_type < range_match) + match_type = range_match; + matched++; + } + } #ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command) == exact_match) - { - if (match_type < ipv6_match) - match_type = ipv6_match; - matched++; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command) == exact_match) - { - if (match_type < ipv6_prefix_match) - match_type = ipv6_prefix_match; - matched++; - } - } + else if (CMD_IPV6 (str)) + { + if (cmd_ipv6_match (command) == exact_match) + { + if (match_type < ipv6_match) + match_type = ipv6_match; + matched++; + } + } + else if (CMD_IPV6_PREFIX (str)) + { + if (cmd_ipv6_prefix_match (command) == exact_match) + { + if (match_type < ipv6_prefix_match) + match_type = ipv6_prefix_match; + matched++; + } + } #endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command) == exact_match) - { - if (match_type < ipv4_match) - match_type = ipv4_match; - matched++; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command) == exact_match) - { - if (match_type < ipv4_prefix_match) - match_type = ipv4_prefix_match; - matched++; - } - } - else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (match_type < extend_match) - match_type = extend_match; - matched++; - } - else - { - if (strcmp (command, str) == 0) - { - match_type = exact_match; - matched++; - } - } - } - if (!matched) - vector_slot (cmd_v, i) = NULL; - } - } + else if (CMD_IPV4 (str)) + { + if (cmd_ipv4_match (command) == exact_match) + { + if (match_type < ipv4_match) + match_type = ipv4_match; + matched++; + } + } + else if (CMD_IPV4_PREFIX (str)) + { + if (cmd_ipv4_prefix_match (command) == exact_match) + { + if (match_type < ipv4_prefix_match) + match_type = ipv4_prefix_match; + matched++; + } + } + else if (CMD_OPTION (str) || CMD_VARIABLE (str)) + { + if (match_type < extend_match) + match_type = extend_match; + matched++; + } + else + { + if (strcmp (command, str) == 0) + { + match_type = exact_match; + matched++; + } ; + } ; + } ; + + /* Keep cmd_element if have a match */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; + + vector_set_length(cmd_v, k) ; /* discard what did not keep */ + return match_type; } @@ -1558,102 +1694,146 @@ cmd_filter_by_string (char *command, vector cmd_v, unsigned int index) * than one number range. * 2 => partial match for ipv4_prefix or ipv6_prefix * (missing '/' or no digits after '/'). + * + * NB: it is assumed that cannot find both 1 and 2 states. But in any case, + * returns 1 in preference. */ static int is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type) { unsigned int i; - unsigned int j; - const char *str = NULL; - struct cmd_element *cmd_element; - const char *matched = NULL; - vector descvec; - struct desc *desc; + unsigned int k; + int ret ; - for (i = 0; i < vector_active (cmd_v); i++) - if ((cmd_element = vector_slot (cmd_v, i)) != NULL) - { - int match = 0; + ret = 0 ; /* all's well so far */ + k = 0 ; /* nothing kept, yet */ - descvec = vector_slot (cmd_element->strvec, index); + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + struct cmd_element *cmd_element; + const char *str_matched ; + vector descvec; + struct desc *desc; + int matched ; + enum match_type mt ; - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - enum match_type ret; + cmd_element = vector_get_item (cmd_v, i) ; - str = desc->cmd; + /* Skip past NULL cmd_v entries (just in case) */ + if (cmd_element == NULL) + continue ; + + /* The cmd_v entry MUST have a token at the current position */ + descvec = vector_get_item (cmd_element->strvec, index) ; + assert(descvec != NULL) ; + + /* See if have a match against any of the current possibilities + * + * str_matched is set the first time get a partial string match, + * or the first time get a number range match. + * + * If get a second partial string match or number range match, then + * unless + */ + str_matched = NULL ; + matched = 0; + for (j = 0; j < vector_length (descvec); j++) + { + enum match_type ret; + const char *str ; + + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; + + str = desc->cmd; + + switch (type) + { + case exact_match: + if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) + && strcmp (command, str) == 0) + matched++; + break; + + case partly_match: + if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) + && strncmp (command, str, strlen (command)) == 0) + { + if (str_matched && (strcmp (str_matched, str) != 0)) + ret = 1; /* There is ambiguous match. */ + else + str_matched = str; + matched++; + } + break; + + case range_match: + if (cmd_range_match (str, command)) + { + if (str_matched && strcmp (str_matched, str) != 0) + ret = 1; + else + str_matched = str; + matched++; + } + break; - switch (type) - { - case exact_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strcmp (command, str) == 0) - match++; - break; - case partly_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strncmp (command, str, strlen (command)) == 0) - { - if (matched && strcmp (matched, str) != 0) - return 1; /* There is ambiguous match. */ - else - matched = str; - match++; - } - break; - case range_match: - if (cmd_range_match (str, command)) - { - if (matched && strcmp (matched, str) != 0) - return 1; - else - matched = str; - match++; - } - break; #ifdef HAVE_IPV6 - case ipv6_match: - if (CMD_IPV6 (str)) - match++; - break; - case ipv6_prefix_match: - if ((ret = cmd_ipv6_prefix_match (command)) != no_match) - { - if (ret == partly_match) - return 2; /* There is incomplete match. */ + case ipv6_match: + if (CMD_IPV6 (str)) + matched++; + break; - match++; - } - break; + case ipv6_prefix_match: + if ((mt = cmd_ipv6_prefix_match (command)) != no_match) + { + if (mt == partly_match) + if (ret != 1) + ret = 2; /* There is incomplete match. */ + + matched++; + } + break; #endif /* HAVE_IPV6 */ - case ipv4_match: - if (CMD_IPV4 (str)) - match++; - break; - case ipv4_prefix_match: - if ((ret = cmd_ipv4_prefix_match (command)) != no_match) - { - if (ret == partly_match) - return 2; /* There is incomplete match. */ - match++; - } - break; - case extend_match: - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - match++; - break; - case no_match: - default: - break; - } - } - if (!match) - vector_slot (cmd_v, i) = NULL; - } - return 0; -} + case ipv4_match: + if (CMD_IPV4 (str)) + matched++; + break; + + case ipv4_prefix_match: + if ((mt = cmd_ipv4_prefix_match (command)) != no_match) + { + if (mt == partly_match) + if (ret != 1) + ret = 2; /* There is incomplete match. */ + + matched++; + } + break; + + case extend_match: + if (CMD_OPTION (str) || CMD_VARIABLE (str)) + matched++; + break; + + case no_match: + default: + break; + } ; + } ; + + /* Keep cmd_element if have a match */ + if (matched) + vector_set_item(cmd_v, k++, cmd_element) ; + } ; + + vector_set_length(cmd_v, k) ; /* discard what did not keep */ + + return ret ; +} ; /*------------------------------------------------------------------------------ * If src matches dst return dst string, otherwise return NULL @@ -1748,14 +1928,14 @@ cmd_entry_function_desc (const char *src, const char *dst) * Returns: 0 => found same string in the vector * 1 => NOT found same string in the vector */ -static int +static bool cmd_unique_string (vector v, const char *str) { unsigned int i; char *match; - for (i = 0; i < vector_active (v); i++) - if ((match = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_length (v); i++) + if ((match = vector_get_item (v, i)) != NULL) if (strcmp (match, str) == 0) return 0; return 1; @@ -1763,20 +1943,20 @@ cmd_unique_string (vector v, const char *str) /* Compare string to description vector. If there is same string return 1 else return 0. */ -static int +static bool desc_unique_string (vector v, const char *str) { unsigned int i; struct desc *desc; - for (i = 0; i < vector_active (v); i++) - if ((desc = vector_slot (v, i)) != NULL) + for (i = 0; i < vector_length (v); i++) + if ((desc = vector_get_item (v, i)) != NULL) if (strcmp (desc->cmd, str) == 0) return 1; return 0; } -static int +static bool cmd_try_do_shortcut (enum node_type node, char* first_word) { return (node >= MIN_DO_SHORTCUT_NODE) && (first_word != NULL) @@ -1798,13 +1978,13 @@ cmd_describe_command_real (vector vline, int node, int *status) char *command; /* Set index. */ - if (vector_active (vline) == 0) + if (vector_length (vline) == 0) { *status = CMD_ERR_NO_MATCH; return NULL; } else - index = vector_active (vline) - 1; + index = vector_length (vline) - 1; /* Make copy vector of current node's command vector. */ cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); @@ -1815,7 +1995,7 @@ cmd_describe_command_real (vector vline, int node, int *status) /* Filter commands. */ /* Only words precedes current word will be checked in this loop. */ for (i = 0; i < index; i++) - if ((command = vector_slot (vline, i))) + if ((command = vector_get_item (vline, i))) { match = cmd_filter_by_completion (command, cmd_vector, i); @@ -1825,15 +2005,15 @@ cmd_describe_command_real (vector vline, int node, int *status) vector descvec; unsigned int j, k; - for (j = 0; j < vector_active (cmd_vector); j++) - if ((cmd_element = vector_slot (cmd_vector, j)) != NULL - && (vector_active (cmd_element->strvec))) + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_element->strvec))) { - descvec = vector_slot (cmd_element->strvec, - vector_active (cmd_element->strvec) - 1); - for (k = 0; k < vector_active (descvec); k++) + descvec = vector_get_item (cmd_element->strvec, + vector_length (cmd_element->strvec) - 1); + for (k = 0; k < vector_length (descvec); k++) { - struct desc *desc = vector_slot (descvec, k); + struct desc *desc = vector_get_item (descvec, k); vector_set (matchvec, desc); } } @@ -1863,53 +2043,58 @@ cmd_describe_command_real (vector vline, int node, int *status) /* Prepare match vector */ /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - /* Make sure that cmd_vector is filtered based on current word */ - command = vector_slot (vline, index); + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (vline, index); if (command) match = cmd_filter_by_completion (command, cmd_vector, index); /* Make description vector. */ - for (i = 0; i < vector_active (cmd_vector); i++) - if ((cmd_element = vector_slot (cmd_vector, i)) != NULL) - { - vector strvec = cmd_element->strvec; + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector strvec ; + + cmd_element = vector_get_item (cmd_vector, i) ; + if (cmd_element == NULL) + continue ; + + /* Ignore cmd_element if no tokens at index position. + * + * Deal with special case of possible <cr> completion. + */ + strvec = cmd_element->strvec; + if (index >= vector_length (strvec)) + { + if (command == NULL && index == vector_length (strvec)) + { + if (!desc_unique_string (matchvec, command_cr)) + vector_push_item(matchvec, &desc_cr); + } + continue ; + } ; + + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (strvec, index); + struct desc *desc; + + for (j = 0; j < vector_length (descvec); j++) + if ((desc = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, desc->cmd); + if (string) + { + /* Uniqueness check */ + if (!desc_unique_string (matchvec, string)) + vector_push_item(matchvec, desc); + } + } ; + } ; - /* if command is NULL, index may be equal to vector_active */ - if (command && index >= vector_active (strvec)) - vector_slot (cmd_vector, i) = NULL; - else - { - /* Check if command is completed. */ - if (command == NULL && index == vector_active (strvec)) - { - if (!desc_unique_string (matchvec, command_cr)) - vector_set (matchvec, &desc_cr); - } - else - { - unsigned int j; - vector descvec = vector_slot (strvec, index); - struct desc *desc; - - for (j = 0; j < vector_active (descvec); j++) - if ((desc = vector_slot (descvec, j))) - { - const char *string; - - string = cmd_entry_function_desc (command, desc->cmd); - if (string) - { - /* Uniqueness check */ - if (!desc_unique_string (matchvec, string)) - vector_set (matchvec, desc); - } - } - } - } - } vector_free (cmd_vector); - if (vector_slot (matchvec, 0) == NULL) + if (vector_length(matchvec) == 0) { vector_free (matchvec); *status = CMD_ERR_NO_MATCH; @@ -1938,7 +2123,7 @@ cmd_describe_command (vector vline, int node, int *status) { vector ret; - if ( cmd_try_do_shortcut(node, vector_slot(vline, 0) ) ) + if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) { vector shifted_vline; unsigned int index; @@ -1947,7 +2132,7 @@ cmd_describe_command (vector vline, int node, int *status) shifted_vline = vector_init (vector_count(vline)); /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) + for (index = 1; index < vector_length (vline); index++) { vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); } @@ -2023,7 +2208,7 @@ cmd_complete_command_real (vector vline, int node, int *status) int n ; /* Stop immediately if the vline is empty. */ - if (vector_active (vline) == 0) + if (vector_length (vline) == 0) { *status = CMD_ERR_NO_MATCH; return NULL; @@ -2033,7 +2218,7 @@ cmd_complete_command_real (vector vline, int node, int *status) cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); /* First, filter upto, but excluding last token */ - last_ivl = vector_active (vline) - 1; + last_ivl = vector_length (vline) - 1; for (ivl = 0; ivl < last_ivl; ivl++) { @@ -2041,7 +2226,7 @@ cmd_complete_command_real (vector vline, int node, int *status) int ret; /* TODO: does this test make any sense ? */ - if ((token = vector_slot (vline, ivl)) == NULL) + if ((token = vector_get_item (vline, ivl)) == NULL) continue ; /* First try completion match, return best kind of match */ @@ -2078,37 +2263,33 @@ cmd_complete_command_real (vector vline, int node, int *status) /* Now we got into completion */ index = last_ivl ; - token = vector_slot(vline, last_ivl) ; /* is now the last token */ + token = vector_get_item(vline, last_ivl) ; /* is now the last token */ - for (i = 0; i < vector_active (cmd_v); i++) + for (i = 0; i < vector_length (cmd_v); i++) { unsigned int j; const char *string; - if ((cmd_element = vector_slot (cmd_v, i)) == NULL) + if ((cmd_element = vector_get_item (cmd_v, i)) == NULL) continue ; - vector strvec = cmd_element->strvec; + descvec = vector_get_item (cmd_element->strvec, index); + if (descvec == NULL) + continue ; - /* Check field length */ - if (index >= vector_active (strvec)) + for (j = 0; j < vector_length (descvec); j++) { - vector_slot (cmd_v, i) = NULL; - continue ; - } - - descvec = vector_slot (strvec, index); + desc = vector_get_item (descvec, j) ; + if (desc == NULL) + continue ; - 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) ; - } + 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 */ + n = vector_length(matchvec) ; /* number of entries in the matchvec */ /* We don't need cmd_v any more. */ vector_free (cmd_v); @@ -2121,13 +2302,12 @@ cmd_complete_command_real (vector vline, int node, int *status) /* In case of 'command \t' pattern. Do you need '?' command at the end of the line. */ if (*token == '\0') - *status = CMD_ERR_NOTHING_TODO; + *status = CMD_COMPLETE_ALREADY; else *status = CMD_ERR_NO_MATCH; return NULL; } -/* XXX: TODO: stop poking around inside vector */ /* Only one matched */ if (n == 1) { @@ -2152,7 +2332,7 @@ cmd_complete_command_real (vector vline, int node, int *status) cmd_free_strvec(matchvec) ; /* discard the match vector */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); + matchvec = vector_init (1); vector_push_item(matchvec, lcdstr) ; *status = CMD_COMPLETE_MATCH; @@ -2173,7 +2353,7 @@ cmd_complete_command (vector vline, int node, int *status) { vector ret; - if ( cmd_try_do_shortcut(node, vector_slot(vline, 0) ) ) + if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) ) { vector shifted_vline; unsigned int index; @@ -2182,7 +2362,7 @@ cmd_complete_command (vector vline, int node, int *status) shifted_vline = vector_init (vector_count(vline)); /* use memcpy? */ - for (index = 1; index < vector_active (vline); index++) + for (index = 1; index < vector_length (vline); index++) { vector_set_index (shifted_vline, index-1, vector_lookup(vline, index)); } @@ -2224,104 +2404,259 @@ node_parent ( enum node_type node ) } /*------------------------------------------------------------------------------ + * Initialise a new struct cmd_parsed, allocating if required + */ +extern cmd_parsed +cmd_parse_init_new(cmd_parsed parsed) +{ + if (parsed == NULL) + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; + else + memset(parsed, 0, sizeof(*parsed)) ; + + /* Zeroising the structure has set: + * + * cmd = NULL -- no command parsed, yet + * cnode -- no node set, yet + * + * do_shortcut -- false + * onode -- not material (do_shortcut is false) + * + * line = zeroised qstring -- empty + * words = zeroised qstring -- empty + * + * vline = zeroised vector -- empty + * + * so nothing else to do + */ + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise a new struct cmd_parsed, allocating if required + */ +extern cmd_parsed +cmd_parse_reset(cmd_parsed parsed, bool free_structure) +{ + if (parsed != NULL) + { + qs_reset_keep(&parsed->words) ; + vector_reset_keep(&parsed->vline) ; + + if (free_structure) + XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + else + cmd_parse_init_new(parsed) ; + } ; + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ * Parse a command in the given "node", if possible, ready for execution. * - * If "strict" use: cmd_filter_by_string() - * otherwise use: cmd_filter_by_completion() + * If 'strict': use cmd_filter_by_string() + * otherwise: use cmd_filter_by_completion() * - * Takes the node from parsed->cnode. + * If 'do': see if there is a 'do' at the front and proceed accordingly. + * + * If 'tree': move up the node tree to find command if not found in the + * current node. + */ + +static enum cmd_return_code +cmd_parse_this(struct cmd_parsed* parsed, bool strict) ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", or (if required) any of its ancestors. * * 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. + * NB: parsed->cnode may have changed. + * + * NB: parsed->cmd->daemon => daemon + * + * CMD_EMPTY => empty or comment line + * + * NB: parsed->cmd == NULL + * + * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? + * + * CMD_ERR_NO_MATCH ) + * CMD_ERR_AMBIGUOUS ) failed to parse + * CMD_ERR_INCOMPLETE ) * - * otherwise => some failure to parse + * NB: if has failed to parse in the current node + * and in any ancestor nodes, returns the error + * from the attempt to parse in the current node + * (parsed->cnode which is returned unchanged). * - * NB: the argv[] in the parsed structure contains *copies* of the char* - * pointers in the given vline. + * NB: the command line MUST be preserved until the parsed command is no + * longer required -- no copy is made. * - * The vline may not be released until the parsed structure is. + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. * - * The parsed structure may be released without worrying about the contents - * of the argv[]. + * See elsewhere for description of parsed structure. */ -enum cmd_parse_type +extern enum cmd_return_code +cmd_parse_command(struct vty* vty, enum cmd_parse_type type) { - cmd_parse_completion = 0, - cmd_parse_strict = 1, -}; + enum cmd_return_code ret ; + enum cmd_return_code first_ret ; + cmd_parsed parsed ; -static int -cmd_parse_command(vector vline, int first, struct cmd_parsed* parsed, - enum cmd_parse_type type) + /* Initialise the parsed structure -- assuming no 'do' */ + if (vty->parsed == NULL) + vty->parsed = cmd_parse_init_new(NULL) ; + parsed = vty->parsed ; + + parsed->onode = parsed->cnode = vty->node ; + + parsed->cmd = NULL ; + parsed->do_shortcut = 0 ; + + /* Parse the line into words -- set up parsed->words and parsed->vline */ + cmd_make_vline(&parsed->vline, &parsed->words, vty->buf) ; + + if (vector_length(&parsed->vline) == 0) + return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ + + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if ((type & cmd_parse_do) && + cmd_try_do_shortcut(parsed->cnode, vector_get_item(&parsed->vline, 0))) + { + parsed->cnode = ENABLE_NODE ; + parsed->do_shortcut = 1 ; + } ; + + /* Try in the current node */ + ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; + + if (ret != CMD_SUCCESS) + { + if (((type & cmd_parse_tree) == 0) || parsed->do_shortcut) + return ret ; /* done if not allowed to walk tree + or just tried to parse a 'do' */ + + /* Try in parent node(s) */ + first_ret = ret ; + + while (ret != CMD_SUCCESS) + { + if (parsed->cnode <= CONFIG_NODE) + { + parsed->cnode = parsed->onode ; /* restore node state */ + return first_ret ; /* return original result */ + } ; + + parsed->cnode = node_parent(parsed->cnode) ; + ret = cmd_parse_this(parsed, ((type & cmd_parse_strict) != 0)) ; + } ; + } ; + + return vty->parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Work function for cmd_parse_command + * + * Takes a parsed structure, with the: + * + * cnode -- node to parse in + * vline -- the line broken into words + * do_shortcut -- true if first word is 'do' (to be ignored) + * + * and parses either strictly or with command completion. + * + * If successful, reduces the vline structure down to the variable portions, + * ie to the argv[] for the command function. + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH ) + * CMD_ERR_AMBIGUOUS ) failed to parse + * CMD_ERR_INCOMPLETE ) + */ +static enum cmd_return_code +cmd_parse_this(cmd_parsed parsed, bool strict) { unsigned int i ; unsigned int ivl ; unsigned index ; + unsigned first ; + unsigned argc ; vector cmd_v; struct cmd_element *cmd_element; struct cmd_element *matched_element; unsigned int matched_count, incomplete_count; - int argc; enum match_type match = 0; int varflag; char *command; - int strict = (type == cmd_parse_strict) ; + /* Need length of vline, discounting the first entry if required */ + first = parsed->do_shortcut ? 1 : 0 ; - parsed->cmd = NULL ; /* return this if parsing fails */ - parsed->argc = 0 ; + assert(vector_length(&parsed->vline) >= first) ; + ivl = vector_length(&parsed->vline) - first ; /* 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 ; + for (index = 0 ; index < ivl; index++) + { + int ret ; - index = ivl - first ; - match = strict ? cmd_filter_by_string(command, cmd_v, index) - : cmd_filter_by_completion(command, cmd_v, index) ; + command = vector_get_item(&parsed->vline, index + first) ; + if (command == NULL) + continue ; - if (match == vararg_match) - break; + match = strict ? cmd_filter_by_string(command, cmd_v, index) + : cmd_filter_by_completion(command, cmd_v, index) ; - ret = is_cmd_ambiguous (command, cmd_v, index, match); + if (match == vararg_match) + break; - if (ret != 0) - { - assert((ret == 1) || (ret == 2)) ; - vector_free (cmd_v); - return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; - } - } + ret = is_cmd_ambiguous (command, cmd_v, index, match); + + if (ret != 0) + { + 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; incomplete_count = 0; - 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) - { - matched_element = cmd_element; + for (i = 0; i < vector_length(cmd_v); i++) + { + cmd_element = vector_get_item(cmd_v, i) ; + if (cmd_element == NULL) + continue ; + + if (match == vararg_match || index >= cmd_element->cmdsize) + { + matched_element = cmd_element; #if 0 - printf ("DEBUG: %s\n", cmd_element->string); + printf ("DEBUG: %s\n", cmd_element->string); #endif - matched_count++; - } - else - { - incomplete_count++; - } - } + matched_count++; + } + else + { + incomplete_count++; + } + } ; /* Finished with cmd_v. */ vector_free (cmd_v); @@ -2339,18 +2674,17 @@ cmd_parse_command(vector vline, int first, struct cmd_parsed* parsed, varflag = 0 ; argc = 0 ; - for (ivl = first; ivl < vector_active (vline); ivl++) + for (index = 0; index < ivl ; index++) { int take = varflag ; if (!varflag) { - int index = ivl - first ; - vector descvec = vector_slot (matched_element->strvec, index); + vector descvec = vector_get_item (matched_element->strvec, index); - if (vector_active (descvec) == 1) + if (vector_length (descvec) == 1) { - struct desc *desc = vector_slot (descvec, 0); + struct desc *desc = vector_get_item (descvec, 0); if (CMD_VARARG (desc->cmd)) take = varflag = 1 ; @@ -2362,109 +2696,53 @@ cmd_parse_command(vector vline, int first, struct cmd_parsed* parsed, } if (take) - { - if (argc >= CMD_ARGC_MAX) - return CMD_ERR_EXCEED_ARGC_MAX ; - parsed->argv[argc++] = vector_slot (vline, ivl); - } ; + vector_assign_item(&parsed->vline, argc++, index + first) ; } ; + vector_set_length(&parsed->vline, argc) ; /* set to new length */ + /* Everything checks out... ready to execute command */ parsed->cmd = matched_element ; - parsed->argc = argc ; - - if (parsed->cmd->daemon) - return CMD_SUCCESS_DAEMON; 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. + * Dispatch a parsed command. * - * CMD_SUCCESS_DAEMON => as CMD_SUCCESS, and the command has a - * "daemon" value. + * Returns: command return code. NB: may be CMD_QUEUED (unless no_queue). * - * 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. + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. */ -static int -cmd_parse_command_tree(vector vline, int first, struct cmd_parsed* parsed, - enum cmd_parse_type type) +extern enum cmd_return_code +cmd_dispatch(struct vty* vty, bool no_queue) { - int ret ; - int first_ret ; - enum node_type first_node ; - - /* Try in the current node */ - ret = cmd_parse_command(vline, first, parsed, type) ; + cmd_parsed parsed = vty->parsed ; + enum cmd_return_code ret ; - if ((ret == CMD_SUCCESS) || (ret == CMD_SUCCESS_DAEMON)) - return ret ; /* done if found command */ + if (parsed->cmd == NULL) + return CMD_SUCCESS ; /* NULL commands are easy */ - /* Try in parent node(s) */ - first_node = parsed->cnode ; - first_ret = ret ; + vty->node = parsed->cnode ; - while (parsed->cnode <= CONFIG_NODE) + if (no_queue || !vty_cli_nexus) { - parsed->cnode = node_parent(parsed->cnode) ; - ret = cmd_parse_command(vline, first, parsed, type) ; - - if ((ret == CMD_SUCCESS) || (ret == CMD_SUCCESS_DAEMON)) - return ret ; /* done if found command */ - } ; - - parsed->cnode = first_node ; /* restore node state */ - return first_ret ; /* return original result */ -} - -/*------------------------------------------------------------------------------ - * 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) ; - - 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 ; + ret = cmd_dispatch_call(vty) ; + cmd_post_command(vty, ret) ; } else { - parsed->do_shortcut = 0 ; - first = 0 ; + /* Don't do it now, but send to bgp qpthread */ + if (parsed->cmd->attr & CMD_ATTR_CALL) + cq_enqueue(vty, vty_cli_nexus) ; + else + cq_enqueue(vty, vty_cmd_nexus) ; + + ret = CMD_QUEUED ; } ; - return first ; + return ret ; } ; /*------------------------------------------------------------------------------ @@ -2477,266 +2755,153 @@ cmd_pre_command(struct vty* vty, struct cmd_parsed* parsed, vector vline) * 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) +cmd_post_command(struct vty* vty, int ret) { - if (parsed->do_shortcut) + if (vty->parsed->do_shortcut) { - if (vty_get_node(vty) == ENABLE_NODE) - vty_set_node(vty, parsed->onode) ; + if (vty->node == ENABLE_NODE) + vty->node = vty->parsed->onode ; } ; } ; /*------------------------------------------------------------------------------ - * Execute command: + * Parse and execute a command. + * + * The command is given by vty->buf and vty->node. + * + * Uses vty->parsed. * - * -- use cmd_parse_completion type parsing. + * -- use strict/completion parsing, as required. * - * -- unless vtysh: if does not parse in current node, try ancestors + * -- parse in current node and in ancestors, as required * * If does not find in any ancestor, return error from current node. * - * -- implement the "do" shortcut + * -- implement the "do" shortcut, as required * * 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. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. */ -extern int -cmd_execute_command (vector vline, struct vty *vty, - struct cmd_element **cmd, qpn_nexus to_nexus, - qpn_nexus from_nexus, int vtysh) +extern enum cmd_return_code +cmd_execute_command(struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) { - 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) - ret = cmd_parse_command(vline, first, &parsed, cmd_parse_completion) ; - else - ret = cmd_parse_command_tree(vline, first, &parsed, cmd_parse_completion) ; - - if (cmd) - *cmd = parsed.cmd ; /* for vtysh */ + enum cmd_return_code ret ; - if (ret != CMD_SUCCESS) - return ret ; /* NB: CMD_SUCCESS_DAEMON exits here */ - - /* Execute the parsed command */ - - vty_set_node(vty, parsed.cnode) ; + /* Try to parse in vty->node or, if required, ancestors thereof. */ + ret = cmd_parse_command(vty, type) ; - if (qpthreads_enabled && !(parsed.cmd->attr & CMD_ATTR_CALL)) - { - /* Don't do it now, but send to bgp qpthread */ - cq_enqueue(vty, &parsed, to_nexus, from_nexus) ; - ret = CMD_QUEUED ; - } - else - { - ret = (*(parsed.cmd->func))(parsed.cmd, vty, parsed.argc, parsed.argv) ; + if (cmd != NULL) + *cmd = vty->parsed->cmd ; /* for vtysh */ - cmd_post_command(vty, &parsed, ret, 1) ; - } ; + if (ret == CMD_SUCCESS) + ret = cmd_dispatch(vty, 0) ; + else if (ret == CMD_EMPTY) + ret = CMD_SUCCESS ; return ret ; } ; /*------------------------------------------------------------------------------ - * Execute command by argument readline. + * Read configuration from file. * - * -- use cmd_parse_strict type parsing. + * In the qpthreads world this assumes that it is running with the vty + * locked, and that all commands are to be executed directly. * - * -- unless vtysh: if does not parse in current node, try ancestors + * If the 'first_cmd' argument is not NULL it is the address of the first + * command that is expected to appear. If the first command is not this, then + * the 'first_cmd' is called with argv == NULL (and argc == 0) to signal the + * command is being invoked by default. * - * If does not find in any ancestor, return error from current node. + * Command processing continues while CMD_SUCCESS is returned by the command + * parser and command execution. + * + * If 'ignore_warning' is set, then any CMD_WARNING returned by command + * execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned + * by command parsing (or in execution of any default 'first_cmd'). + * + * Returns: cmd_return_code for last command + * vty->buf is last line processed + * vty->lineno is number of last line processed (1 is first) * - * -- does NOT implement the "do" shortcut + * If the file is empty, will return CMD_SUCCESS. * - * At all times executes the command immediately (no queueing, even if - * qpthreads_enabled). + * Never returns CMD_EMPTY -- that counts as CMD_SUCCESS. * - * The vty->node may be changed either when parsing or executing the command. + * If + * + * If return code is not CMD_SUCCESS, the the output buffering contains the + * output from the last command attempted. */ -extern int -cmd_execute_command_strict (vector vline, struct vty *vty, - struct cmd_element **cmd, int vtysh) +extern enum cmd_return_code +config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd, + qstring buf, bool ignore_warning) { -#if 0 /* replaced by cmd_parse_command() */ - unsigned int i; - unsigned int index; - vector cmd_vector; - struct cmd_element *cmd_element; - struct cmd_element *matched_element; - unsigned int matched_count, incomplete_count; - int argc; - const char *argv[CMD_ARGC_MAX]; - int varflag; - enum match_type match = 0; - char *command; - - /* Make copy of command element */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty_get_node(vty))); - - for (index = 0; index < vector_active (vline); index++) - if ((command = vector_slot (vline, index))) - { - int ret; - - match = cmd_filter_by_string (vector_slot (vline, index), - cmd_vector, index); - - /* If command meets '.VARARG' then finish matching. */ - if (match == vararg_match) - break; - - ret = is_cmd_ambiguous (command, cmd_vector, index, match); - if (ret == 1) - { - vector_free (cmd_vector); - return CMD_ERR_AMBIGUOUS; - } - if (ret == 2) - { - vector_free (cmd_vector); - return CMD_ERR_NO_MATCH; - } - } - - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; - incomplete_count = 0; - for (i = 0; i < vector_active (cmd_vector); i++) - if (vector_slot (cmd_vector, i) != NULL) - { - cmd_element = vector_slot (cmd_vector, i); - - if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; - matched_count++; - } - else - incomplete_count++; - } - - /* Finish of using cmd_vector. */ - vector_free (cmd_vector); - - /* To execute command, matched_count must be 1. */ - if (matched_count == 0) - { - if (incomplete_count) - return CMD_ERR_INCOMPLETE; - else - return CMD_ERR_NO_MATCH; - } - - if (matched_count > 1) - return CMD_ERR_AMBIGUOUS; + enum cmd_return_code ret; - /* Argument treatment */ - varflag = 0; - argc = 0; + vty->buf = buf->body ; + vty->lineno = 0 ; - for (i = 0; i < vector_active (vline); i++) - { - if (varflag) - argv[argc++] = vector_slot (vline, i); - else - { - vector descvec = vector_slot (matched_element->strvec, i); - - 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); - } - else - argv[argc++] = vector_slot (vline, i); - } - - if (argc >= CMD_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) ; - - if (cmd) - *cmd = parsed.cmd ; /* for vtysh */ - - if (ret != CMD_SUCCESS) - return ret ; /* NB: CMD_SUCCESS_DAEMON exits here */ - - /* Now execute matched command */ - vty_set_node(vty, parsed.cnode) ; - - return (*(parsed.cmd->func)) (parsed.cmd, vty, parsed.argc, parsed.argv); -} - -/*------------------------------------------------------------------------------ - * Read configuration from file. - * - */ -int -config_from_file (struct vty *vty, FILE *fp, void (*after_first_cmd)(void), - qstring buf) -{ - int ret; - vector vline; - int first_cmd = 1; + ret = CMD_SUCCESS ; /* in case file is empty */ + vty_out_clear(vty) ; while (fgets (buf->body, buf->size, fp)) { - vline = cmd_make_strvec (buf->body); + ++vty->lineno ; + + /* Execute configuration command : this is strict match */ + ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - /* In case of comment line */ - if (vline == NULL) - continue; + if (ret == CMD_EMPTY) + continue ; /* skip empty/comment */ - /* Execute configuration command : this is strict match */ - ret = cmd_execute_command_strict (vline, vty, NULL, 0); + if (ret != CMD_SUCCESS) + break ; /* stop on *any* parsing issue */ - /* special handling for after the first command */ - if (first_cmd && after_first_cmd) + /* special handling before of first command */ + if (first_cmd != NULL) { - after_first_cmd(); - first_cmd = 0; + if (first_cmd != vty->parsed->cmd) + { + ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; + if (ret != CMD_SUCCESS) + break ; /* stop on *any* issue with "default" */ + } ; + first_cmd = NULL ; } - cmd_free_strvec (vline); + /* Standard command handling */ + ret = cmd_dispatch(vty, cmd_no_queue) ; + + if (ret != CMD_SUCCESS) + { + /* Ignore CMD_WARNING if required + * + * Ignore CMD_CLOSE at all times. + */ + if ( ((ret == CMD_WARNING) && ignore_warning) + || (ret == CMD_CLOSE) ) + ret = CMD_SUCCESS ; /* in case at EOF */ + else + break ; /* stop */ + } ; - /* TODO: why does config file not stop on CMD_WARNING ?? */ - if (ret != CMD_SUCCESS && ret != CMD_WARNING - && ret != CMD_ERR_NOTHING_TODO) - return ret; + vty_out_clear(vty) ; } ; - return CMD_SUCCESS; -} + if (ret == CMD_EMPTY) + ret = CMD_SUCCESS ; /* OK if end on empty line */ + + return ret ; +} ; /*----------------------------------------------------------------------------*/ @@ -2762,7 +2927,7 @@ DEFUN_CALL (enable, { /* If enable password is NULL, change to ENABLE_NODE */ if ((host.enable == NULL && host.enable_encrypt == NULL) || - vty_get_type(vty) == VTY_SHELL_SERV) + vty_shell_serv(vty)) vty_set_node(vty, ENABLE_NODE); else vty_set_node(vty, AUTH_ENABLE_NODE); @@ -2787,8 +2952,7 @@ DEFUN_CALL (config_exit, "exit", "Exit current mode and down to previous mode\n") { - vty_cmd_exit(vty) ; - return CMD_SUCCESS; + return vty_cmd_exit(vty) ; } /* quit is alias of exit. */ @@ -2803,8 +2967,7 @@ DEFUN_CALL (config_end, "end", "End current mode and change to enable mode.") { - vty_cmd_end(vty) ; - return CMD_SUCCESS; + return vty_cmd_end(vty) ; } /* Show version. */ @@ -2852,11 +3015,11 @@ DEFUN_CALL (config_list, "Print command list\n") { unsigned int i; - struct cmd_node *cnode = vector_slot (cmdvec, vty_get_node(vty)); + struct cmd_node *cnode = vector_get_item (cmdvec, vty_get_node(vty)); struct cmd_element *cmd; - for (i = 0; i < vector_active (cnode->cmd_vector); i++) - if ((cmd = vector_slot (cnode->cmd_vector, i)) != NULL + for (i = 0; i < vector_length (cnode->cmd_vector); i++) + if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL && !(cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN))) vty_out (vty, " %s%s", cmd->string, VTY_NEWLINE); @@ -2872,12 +3035,12 @@ DEFUN (config_write_file, { unsigned int i; int fd; + int err; struct cmd_node *node; char *config_file; char *config_file_tmp = NULL; char *config_file_sav = NULL; int ret = CMD_WARNING; - struct vty *file_vty; /* Check and see if we are operating under vtysh configuration */ if (host.config == NULL) @@ -2909,47 +3072,61 @@ DEFUN (config_write_file, } /* Make vty for configuration file. */ - file_vty = vty_new (fd, VTY_FILE); + vty_open_config_write(vty, fd) ; /* Config file header print. */ - vty_out (file_vty, "!\n! Zebra configuration saved from vty\n! "); - vty_time_print (file_vty, 1); - vty_out (file_vty, "!\n"); + vty_out (vty, "!\n! Zebra configuration saved from vty\n! "); + vty_time_print (vty, 1); + vty_out (vty, "!\n"); - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func) { - if ((*node->func) (file_vty)) - vty_out (file_vty, "!\n"); + if ((*node->func) (vty)) + vty_out (vty, "!\n"); } - vty_close (file_vty); + + err = vty_close_config_write(vty) ; + close(fd) ; + + if (err != 0) + { + vty_out (vty, "Failed while writing configuration file %s.%s", + config_file_tmp, VTY_NEWLINE); + goto finished; + } if (unlink (config_file_sav) != 0) if (errno != ENOENT) { - vty_out (vty, "Can't unlink backup configuration file %s.%s", config_file_sav, - VTY_NEWLINE); + vty_out (vty, "Can't unlink backup configuration file %s.%s", + config_file_sav, VTY_NEWLINE); goto finished; - } + } ; + if (link (config_file, config_file_sav) != 0) { - vty_out (vty, "Can't backup old configuration file %s.%s", config_file_sav, - VTY_NEWLINE); + vty_out (vty, "Can't backup old configuration file %s.%s", + config_file_sav, VTY_NEWLINE); goto finished; - } - sync (); + } ; + + sync () ; + if (unlink (config_file) != 0) { - vty_out (vty, "Can't unlink configuration file %s.%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Can't unlink configuration file %s.%s", + config_file, VTY_NEWLINE); goto finished; - } + } ; + if (link (config_file_tmp, config_file) != 0) { - vty_out (vty, "Can't save configuration file %s.%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Can't save configuration file %s.%s", + config_file, VTY_NEWLINE); goto finished; - } + } ; + sync (); if (chmod (config_file, CONFIGFILE_MASK) != 0) @@ -2959,8 +3136,8 @@ DEFUN (config_write_file, goto finished; } - vty_out (vty, "Configuration saved to %s%s", config_file, - VTY_NEWLINE); + vty_out (vty, "Configuration saved to %s%s", config_file, VTY_NEWLINE); + ret = CMD_SUCCESS; finished: @@ -2998,10 +3175,10 @@ DEFUN (config_write_terminal, unsigned int i; struct cmd_node *node; - if (vty_get_type(vty) == VTY_SHELL_SERV) + if (vty_shell_serv(vty)) { - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func && node->vtysh) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func && node->vtysh) { if ((*node->func) (vty)) vty_out (vty, "!%s", VTY_NEWLINE); @@ -3013,8 +3190,8 @@ DEFUN (config_write_terminal, VTY_NEWLINE); vty_out (vty, "!%s", VTY_NEWLINE); - for (i = 0; i < vector_active (cmdvec); i++) - if ((node = vector_slot (cmdvec, i)) && node->func) + for (i = 0; i < vector_length (cmdvec); i++) + if ((node = vector_get_item (cmdvec, i)) && node->func) { if ((*node->func) (vty)) vty_out (vty, "!%s", VTY_NEWLINE); @@ -4012,39 +4189,52 @@ cmd_terminate () if (cmdvec) { - for (i = 0; i < vector_active (cmdvec); i++) - if ((cmd_node = vector_slot (cmdvec, i)) != NULL) - { - cmd_node_v = cmd_node->cmd_vector; + for (i = 0; i < vector_length (cmdvec); i++) + { + cmd_node = vector_get_item (cmdvec, i) ; + if (cmd_node == NULL) + continue ; - for (j = 0; j < vector_active (cmd_node_v); j++) - if ((cmd_element = vector_slot (cmd_node_v, j)) != NULL && - cmd_element->strvec != NULL) - { - cmd_element_v = cmd_element->strvec; - - for (k = 0; k < vector_active (cmd_element_v); k++) - if ((desc_v = vector_slot (cmd_element_v, k)) != NULL) - { - for (l = 0; l < vector_active (desc_v); l++) - if ((desc = vector_slot (desc_v, l)) != NULL) - { - if (desc->cmd) - XFREE (MTYPE_STRVEC, desc->cmd); - if (desc->str) - XFREE (MTYPE_STRVEC, desc->str); - - XFREE (MTYPE_DESC, desc); - } - vector_free (desc_v); - } - - cmd_element->strvec = NULL; - vector_free (cmd_element_v); - } + cmd_node_v = cmd_node->cmd_vector; - vector_free (cmd_node_v); - } + for (j = 0; j < vector_length (cmd_node_v); j++) + { + cmd_element = vector_get_item (cmd_node_v, j) ; + if (cmd_element == NULL) + continue ; + + cmd_element_v = cmd_element->strvec ; + if (cmd_element_v == NULL) + continue ; + + for (k = 0; k < vector_length (cmd_element_v); k++) + { + desc_v = vector_get_item (cmd_element_v, k) ; + if (desc_v == NULL) + continue ; + + for (l = 0; l < vector_length (desc_v); l++) + { + desc = vector_get_item (desc_v, l) ; + if (desc == NULL) + continue ; + + if (desc->cmd) + XFREE (MTYPE_STRVEC, desc->cmd); + if (desc->str) + XFREE (MTYPE_STRVEC, desc->str); + + XFREE (MTYPE_DESC, desc); + } ; + vector_free (desc_v); + } ; + + cmd_element->strvec = NULL; + vector_free (cmd_element_v); + } ; + + vector_free (cmd_node_v); + } ; vector_free (cmdvec); cmdvec = NULL; diff --git a/lib/command.h b/lib/command.h index 0e8b3630..bd1ad99c 100644 --- a/lib/command.h +++ b/lib/command.h @@ -23,12 +23,17 @@ #ifndef _ZEBRA_COMMAND_H #define _ZEBRA_COMMAND_H +#include <stdbool.h> + #include "node_type.h" #include "vector.h" -#include "uty.h" -#include "vty.h" #include "qstring.h" -#include "lib/route_types.h" + +#ifndef Inline +#define Inline static inline +#endif + +struct vty ; /* in case command.h expanded first */ /* Host configuration variable */ struct host @@ -86,16 +91,66 @@ enum { /* bit significant */ CMD_ATTR_DEPRECATED = 0x01, - CMD_ATTR_HIDDEN = 0x02, - CMD_ATTR_CALL = 0x04, + CMD_ATTR_HIDDEN = 0x02, + CMD_ATTR_CALL = 0x04, }; +/* 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_ERROR, + + CMD_EMPTY, + CMD_SUCCESS_DAEMON, + + CMD_CLOSE, + CMD_QUEUED, + + CMD_ERR_NO_MATCH, + CMD_ERR_AMBIGUOUS, + CMD_ERR_INCOMPLETE, + + CMD_COMPLETE_FULL_MATCH, + CMD_COMPLETE_MATCH, + CMD_COMPLETE_LIST_MATCH, + CMD_COMPLETE_ALREADY +} ; + +#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" + /* Structure of command element. */ + +struct cmd_element ; +typedef struct cmd_element* cmd_element ; + +typedef const char* const argv_t[] ; + +#define DEFUN_CMD_ARG_UNUSED __attribute__ ((unused)) +#define DEFUN_CMD_FUNCTION(name) \ + enum cmd_return_code name (cmd_element self DEFUN_CMD_ARG_UNUSED, \ + struct vty* vty DEFUN_CMD_ARG_UNUSED, \ + int argc DEFUN_CMD_ARG_UNUSED, \ + argv_t argv DEFUN_CMD_ARG_UNUSED) + +typedef DEFUN_CMD_FUNCTION((cmd_function)) ; + struct cmd_element { - const char *string; /* Command specification by string. */ - int (*func) (struct cmd_element *, struct vty *, int, const char *[]); - const char *doc; /* Documentation of this command. */ + const char *string; /* Command specification by string. */ + cmd_function* func ; + const char *doc; /* Documentation of this command. */ int daemon; /* Daemon to which this command belong. */ vector strvec; /* Pointing out each description vector. */ unsigned int cmdsize; /* Command index count. */ @@ -111,55 +166,48 @@ struct desc char *str; /* Command's description. */ }; -/* Argc max counts. */ -#define CMD_ARGC_MAX 25 +/* Command parsing options */ +enum cmd_parse_type /* bit significant */ +{ + cmd_parse_completion = 0x00, + cmd_parse_strict = 0x01, + + cmd_parse_do = 0x02, + cmd_parse_tree = 0x04, +} ; -/* Parsed command */ +/* Parsed command */ +typedef struct cmd_parsed* cmd_parsed ; struct cmd_parsed { - struct cmd_element *cmd ; + struct cmd_element *cmd ; /* NULL if empty command + or fails to parse */ + + enum node_type cnode ; /* node command is in */ + enum node_type onode ; /* node the parser started in */ - enum node_type cnode ; /* node command is in */ + bool do_shortcut ; /* true => is "do" command */ - int do_shortcut ; /* true => is "do" command */ - enum node_type onode ; /* vty->node before "do" */ + qstring_t words ; /* the words, '\0' separated */ - int argc ; - const char* argv[CMD_ARGC_MAX] ; + vector_t vline ; /* pointers to the words */ } ; -/* 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 +/* Command dispatch options */ +enum { + cmd_no_queue = true, + cmd_may_queue = false, } ; -#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" +/*------------------------------------------------------------------------------ + * Can now include these + */ +#include "vty.h" +#include "uty.h" + +/*----------------------------------------------------------------------------*/ /* Turn off these macros when uisng cpp with extract.pl */ #ifndef VTYSH_EXTRACT_PL @@ -175,14 +223,10 @@ enum cmd_return_code }; #define DEFUN_CMD_FUNC_DECL(funcname) \ - static int funcname (struct cmd_element *, struct vty *, int, const char *[]); + static cmd_function funcname; #define DEFUN_CMD_FUNC_TEXT(funcname) \ - static int funcname \ - (struct cmd_element *self __attribute__ ((unused)), \ - struct vty *vty __attribute__ ((unused)), \ - int argc __attribute__ ((unused)), \ - const char *argv[] __attribute__ ((unused)) ) + static DEFUN_CMD_FUNCTION(funcname) /* DEFUN for vty command interafce. Little bit hacky ;-). */ #define DEFUN(funcname, cmdname, cmdstr, helpstr) \ @@ -339,7 +383,7 @@ extern void sort_node (void); /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated string with a space between each element (allocated using XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ -extern char *argv_concat (const char **argv, int argc, int shift); +extern char *argv_concat (const char* const* argv, int argc, int shift); extern vector cmd_make_strvec (const char *); extern vector cmd_add_to_strvec (vector v, const char* str) ; @@ -347,17 +391,35 @@ extern void cmd_free_strvec (vector); extern vector cmd_describe_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), qstring buf); +extern enum cmd_return_code +config_from_file (struct vty* vty, FILE *fp, struct cmd_element* first_cmd, + qstring buf, bool stop_on_warning) ; extern enum node_type node_parent (enum node_type); -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 enum cmd_return_code cmd_execute_command (struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) ; +extern enum cmd_return_code cmd_execute_command_strict (struct vty *vty, + enum cmd_parse_type type, struct cmd_element **cmd) ; + +extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; +extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; +extern enum cmd_return_code cmd_parse_command(struct vty* vty, + enum cmd_parse_type type) ; +extern enum cmd_return_code cmd_dispatch(struct vty* vty, bool no_queue) ; + +Inline enum cmd_return_code +cmd_dispatch_call(struct vty* vty) +{ + cmd_parsed parsed = vty->parsed ; + return (*(parsed->cmd->func))(parsed->cmd, vty, + vector_length(&parsed->vline), + (const char * const*)vector_body(&parsed->vline)) ; +} ; + +#define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0) +#define cmd_parse_reset_free(parsed) cmd_parse_reset(parsed, 1) + extern void config_replace_string (struct cmd_element *, char *, ...); + extern void cmd_init (int); extern void cmd_terminate (void); diff --git a/lib/command_queue.c b/lib/command_queue.c index b6aab580..ea8ac469 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -25,6 +25,10 @@ #include "qpnexus.h" #include "memory.h" #include "command_queue.h" +#include "vty.h" +#include "uty.h" +#include "vector.h" +#include "qstring.h" /*------------------------------------------------------------------------------ * Form of message passed with command to be executed @@ -32,17 +36,7 @@ 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 */ + enum cmd_return_code ret ; /* return code from command */ } ; MQB_ARGS_SIZE_OK(cq_command_args) ; @@ -56,28 +50,16 @@ static void cq_return(mqueue_block mqb, mqb_flag_t flag); * Enqueue vty and argv[] for execution in given nexus. */ void -cq_enqueue(struct vty *vty, struct cmd_parsed* parsed, qpn_nexus to_nexus, - qpn_nexus from_nexus) +cq_enqueue(struct vty *vty, qpn_nexus dst) { - 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 ; - - args->ret_nexus = from_nexus ; - args->ret = CMD_SUCCESS ; - - for (i = 0; i < parsed->argc; ++i) - mqb_push_argv_p(mqb, XSTRDUP(MTYPE_MARSHAL, parsed->argv[i])); + args->ret = CMD_QUEUED ; - mqueue_enqueue(to_nexus->queue, mqb, 0) ; + mqueue_enqueue(dst->queue, mqb, 0) ; } /*------------------------------------------------------------------------------ @@ -85,6 +67,8 @@ cq_enqueue(struct vty *vty, struct cmd_parsed* parsed, qpn_nexus to_nexus, * * When done (or revoked/deleted) return the message, so that the sender knows * that the command has been dealt with (one way or another). + * + * Note that if the command is revoked the return is set to CMD_QUEUED. */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) @@ -97,59 +81,63 @@ cq_action(mqueue_block mqb, mqb_flag_t flag) if (flag == mqb_action) { - const char** argv = mqb_get_argv(mqb) ; - - args->ret = (args->cmd->func)(args->cmd, vty, args->argc, argv) ; + args->ret = cmd_dispatch_call(vty) ; + assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */ } else args->ret = CMD_QUEUED ; mqb_set_action(mqb, cq_return) ; - mqueue_enqueue(args->ret_nexus->queue, mqb, 0) ; + mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; } ; /*------------------------------------------------------------------------------ - * Accept return from command executed in another thread. + * Accept return from command which has completed. * * 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. + * Do not care whether the message is being revoked or not... the command + * has completed and that must be signalled to the CLI. Any pending output + * is released. + * + * The command itself may have been revoked before it was executed. That + * makes no difference either... the output buffers will simply be empty. + * However, the return code is CMD_QUEUED, to signal the fact that the command + * was never executed. */ 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)) ; + /* signal end of command */ + cmd_post_command(vty, args->ret) ; + vty_queued_result(vty, args->ret) ; - /* update the state of the vty -- passing the "action" state */ - vty_queued_result(vty, args->ret, (flag == mqb_action)); - - if (qpthreads_enabled) - qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); +//if (qpthreads_enabled) +// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); mqb_free(mqb); } + +/*------------------------------------------------------------------------------ + * Revoke any messages related to the given VTY + * + * Revokes in vty_cmd_nexus -- so before command is started + * and in vty_cli_nexus -- so after command has completed + * + * Can do nothing about any command actually being executed in the + * vty_cmd_nexus. + */ +void +cq_revoke(struct vty *vty) +{ + mqueue_revoke(vty_cmd_nexus->queue, vty) ; + mqueue_revoke(vty_cli_nexus->queue, vty) ; +} + diff --git a/lib/command_queue.h b/lib/command_queue.h index 257c9552..804dfa1f 100644 --- a/lib/command_queue.h +++ b/lib/command_queue.h @@ -25,8 +25,7 @@ #include "command.h" #include "qpnexus.h" -extern void -cq_enqueue(struct vty *vty, struct cmd_parsed* parsed, qpn_nexus to_nexus, - qpn_nexus from_nexus) ; +extern void cq_enqueue(struct vty *vty, qpn_nexus dst) ; +extern void cq_revoke(struct vty* vty) ; #endif /* COMMAND_QUEUE_H_ */ diff --git a/lib/keystroke.c b/lib/keystroke.c index 09f0fed0..c6eb811e 100644 --- a/lib/keystroke.c +++ b/lib/keystroke.c @@ -19,7 +19,8 @@ * Boston, MA 02111-1307, USA. */ -#include "string.h" +#include <stdbool.h> +#include <string.h> #include "keystroke.h" @@ -41,7 +42,7 @@ * * Handles: * - * 0. Null, returned as: + * 0. Nothing, returned as: * * type = ks_null * value = knull_eof @@ -52,7 +53,10 @@ * broken = false * buf -- not used * - * This is returned when there is nothing else available. + * This is returned when there is nothing there. + * + * Note that this is NOT returned for NUL ('\0') characters. Those are + * real characters (whose value just happens to be null). * * 1. Characters, returned as: * @@ -67,6 +71,8 @@ * buf -- if OK, the representation for the character (UTF-8 ?) * if truncated or broken, the raw bytes * + * See notes below on the handling of '\r' and '\n'. + * * 2. ESC X -- where X is single character, other than '['. * * Returned as: @@ -147,6 +153,30 @@ * * Extended Option objects (O = 0xFF) are exotic, but the above will * parse them. + * + *------------------------------------------------------------------------------ + * CR, LF and NUL + * + * Telnet requires CR LF newlines. Where a CR is to appear alone it must be + * followed by NUL. + * + * This code accepts: + * + * * CR LF pair, returning LF ('\n') -- discards CR + * + * * CR NUL pair, returning CR ('\r') -- discards NUL + * + * * CR CR pair, returning CR ('\r') == discards one CR (seems pointless) + * + * * CR XX pair, returning CR and XX -- where XX is anything other than + * CR, LF or NUL + * + * It is tempting to throw away all NUL characters... but that doesn't seem + * like a job for this level. + * + * As a small compromise, will not steal a NUL character. + * + * Note that NUL appears as a real character. ks_null means literally nothing. */ /*------------------------------------------------------------------------------ @@ -225,6 +255,7 @@ enum stream_state kst_null, /* nothing special (but see iac) */ kst_char, /* collecting a multi-byte character */ + kst_cr, /* collecting '\r''\0' or '\r''\n' */ kst_esc, /* collecting an ESC sequence */ kst_csi, /* collecting an ESC '[' or CSI sequence */ @@ -243,6 +274,9 @@ struct keystroke_stream { vio_fifo_t fifo ; /* the keystrokes */ + keystroke_callback* iac_callback ; + void* iac_callback_context ; + uint8_t CSI ; /* CSI character value (if any) */ bool eof_met ; /* nothing more to come */ @@ -264,17 +298,19 @@ enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */ /*------------------------------------------------------------------------------ * Prototypes */ +static void keystroke_in_push(keystroke_stream stream, uint8_t u) ; +static void keystroke_in_pop(keystroke_stream stream) ; + 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) ; +inline static 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_put_iac_one(keystroke_stream stream, uint8_t u); +static void keystroke_put_iac_long(keystroke_stream stream, bool broken) ; +static void keystroke_clear_iac(keystroke_stream stream) ; static void keystroke_steal_char(keystroke steal, keystroke_stream stream, uint8_t u) ; static void keystroke_steal_esc(keystroke steal, keystroke_stream stream, @@ -282,7 +318,7 @@ static void keystroke_steal_esc(keystroke steal, keystroke_stream stream, 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) ; + bool broken, uint8_t* bytes, int len) ; /*============================================================================== * Creating and freeing keystroke streams and keystroke stream buffers. @@ -292,9 +328,21 @@ static void keystroke_put(keystroke_stream stream, enum keystroke_type type, * Create and initialise a keystroke stream. * * Can set CSI character value. '\0' => none. (As does '\x1B' !) + * + * The callback function is called when an IAC sequence is seen, the callback + * is: + * + * bool callback(void* context, keystroke stroke) + * + * see: #define keystroke_iac_callback_args + * and: typedef for keystroke_callback + * + * The callback must return true iff the IAC sequence has been dealt with, and + * should NOT be stored for later processing. */ extern keystroke_stream -keystroke_stream_new(uint8_t csi_char) +keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, + void* iac_callback_context) { keystroke_stream stream ; @@ -302,6 +350,9 @@ keystroke_stream_new(uint8_t csi_char) /* Zeroising the structure sets: * + * iac_callback = NULL -- none + * iac_callback_context = NULL -- none + * * eof_met = false -- no EOF yet * steal = false -- no stealing set * iac = false -- last character was not an IAC @@ -314,6 +365,9 @@ keystroke_stream_new(uint8_t csi_char) */ confirm(kst_null == 0) ; + stream->iac_callback = iac_callback ; + stream->iac_callback_context = iac_callback_context ; + vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ; stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ; @@ -323,16 +377,20 @@ keystroke_stream_new(uint8_t csi_char) /*------------------------------------------------------------------------------ * Free keystroke stream and all associated buffers. + * + * Returns NULL */ -extern void +extern keystroke_stream keystroke_stream_free(keystroke_stream stream) { - if (stream == NULL) - return ; + if (stream != NULL) + { + vio_fifo_reset_keep(&stream->fifo) ; - vio_fifo_reset_keep(&stream->fifo) ; + XFREE(MTYPE_KEY_STREAM, stream) ; + } ; - XFREE(MTYPE_KEY_STREAM, stream) ; + return NULL ; } ; /*============================================================================== @@ -349,7 +407,7 @@ keystroke_stream_free(keystroke_stream stream) extern bool keystroke_stream_empty(keystroke_stream stream) { - return vio_fifo_empty(&stream->fifo) ; + return (stream == NULL) || vio_fifo_empty(&stream->fifo) ; } ; /*------------------------------------------------------------------------------ @@ -370,7 +428,7 @@ keystroke_stream_eof(keystroke_stream stream) * 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 ; + return (stream == NULL) || (vio_fifo_empty(&stream->fifo) && stream->eof_met); } ; /*------------------------------------------------------------------------------ @@ -385,15 +443,14 @@ keystroke_stream_set_eof(keystroke_stream stream) { vio_fifo_reset_keep(&stream->fifo) ; - stream->eof_met = 1 ; /* essential information */ + stream->eof_met = true ; /* essential information */ - stream->steal_this = 0 ; /* keep tidy */ - stream->iac = 0 ; + stream->steal_this = false ; /* keep tidy */ + stream->iac = false ; stream->in.state = kst_null ; stream->pushed_in.state = kst_null ; } ; - /*============================================================================== * Input raw bytes to given keyboard stream. * @@ -437,42 +494,71 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, */ if ((len == 0) && (ptr == NULL)) { - stream->eof_met = 1 ; - stream->steal_this = 0 ; + stream->eof_met = true ; + stream->steal_this = false ; - 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. + /* Loop to deal with any pending IAC and partial keystroke. + * + * An IAC in the middle of a real keystroke sequence appears before + * it. Do the same here, even with broken sequences. + * + * A partial IAC sequence may have pushed a partial real keystroke + * sequence -- so loop until have dealt with that. */ - while (stream->in.state != kst_null) + do { switch (stream->in.state) { + case kst_null: /* not expecting anything, unless iac */ + keystroke_clear_iac(stream) ; + break ; + + case kst_cr: /* expecting something after CR */ + keystroke_clear_iac(stream) ; + + stream->in.len = 0 ; + keystroke_add_raw(stream, '\r') ; + keystroke_put(stream, ks_char, true, + stream->in.raw, stream->in.len) ; + break ; + case kst_esc: /* expecting rest of escape */ + keystroke_clear_iac(stream) ; + keystroke_put_esc(stream, '\0', 0) ; - stream->in.state = kst_null ; break ; case kst_csi: + keystroke_clear_iac(stream) ; + keystroke_put_csi(stream, '\0') ; - stream->in.state = kst_null ; break ; case kst_iac_option: /* expecting rest of IAC */ + assert(!stream->iac) ; + /* fall through */ case kst_iac_sub: - keystroke_put_iac_long(stream, 1) ; - /* pops the stream->pushed_in */ + keystroke_put_iac_long(stream, true) ; + + /* For kst_iac_sub, an incomplete IAC could be anything, so + * don't include in the broken IAC, but don't lose it + * either. + */ + keystroke_clear_iac(stream) ; break ; - case kst_char: /* TBD */ + case kst_char: /* TBD */ zabort("impossible keystroke stream state") ; default: zabort("unknown keystroke stream state") ; } ; - } ; + + assert(!stream->iac) ; /* must have dealt with this */ + + keystroke_in_pop(stream) ; /* pops kst_null, when all done */ + + } while (stream->in.state != kst_null) ; } ; /* Update the stealing state @@ -492,7 +578,7 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, * keystroke_input(), while still wish to steal. */ if (steal == NULL) - stream->steal_this = 0 ; /* clear as not now required */ + stream->steal_this = false; /* not now required */ else stream->steal_this = (stream->in.state == kst_null) ; /* want to and can can steal the next @@ -518,15 +604,25 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, /* IAC handling takes precedence over everything, except the <option> * byte, which may be EXOPL, which happens to be 255 as well ! + * + * stream->iac means that the last thing seen was an IAC. + * + * First IAC sets the flag. On the next byte: + * + * * if is IAC, clears stream->iac, and lets the escaped IAC value + * through for further processing -- NOT in IAC state. + * + * * if is not IAC, will be let through for further processing, in + * IAC state. */ if ((u == tn_IAC) && (stream->in.state != kst_iac_option)) { if (stream->iac) - stream->iac = 0 ; /* IAC IAC => single IAC byte value */ + stream->iac = false ; /* IAC IAC => single IAC byte value */ else { - stream->iac = 1 ; /* seen an IAC */ - continue ; /* wait for next character */ + stream->iac = true ; /* seen an IAC */ + continue ; /* wait for next character */ } ; } ; @@ -543,24 +639,23 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, */ if (stream->iac) { - stream->iac = 0 ; /* assume will eat the IAC XX */ + stream->iac = false ; /* expect will eat the IAC XX */ switch (stream->in.state) { case kst_null: + case kst_cr: case kst_esc: case kst_csi: if (u < tn_SB) - keystroke_put_iac(stream, u, 1) ; + /* This is a simple IAC XX, one byte IAC */ + keystroke_put_iac_one(stream, u) ; else - { - stream->pushed_in = stream->in ; - - stream->in.len = 1 ; - stream->in.raw[0] = u ; - - stream->in.state = kst_iac_option ; - } + /* This is a multi-byte IAC, so push whatever real + * keystroke sequence is currently on preparation, and + * set into kst_iac_option state. + */ + keystroke_in_push(stream, u) ; break ; case kst_iac_sub: @@ -569,11 +664,11 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, if (u != tn_SE) { --ptr ; /* put back the XX */ - stream->iac = 1 ; /* put back the IAC */ + stream->iac = true ; /* put back the IAC */ } ; keystroke_put_iac_long(stream, (u != tn_SE)) ; - /* pops the stream->pushed_in */ + keystroke_in_pop(stream) ; break ; case kst_char: /* TBD */ @@ -593,7 +688,9 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, case kst_null: /* Expecting anything */ stream->steal_this = (steal != NULL) ; - if (u == 0x1B) + if (u == '\r') + stream->in.state = kst_cr ; + else if (u == 0x1B) stream->in.state = kst_esc ; else if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */ { @@ -602,12 +699,13 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, } else { - if (!stream->steal_this) + /* Won't steal NUL */ + if (!stream->steal_this || (u == '\0')) keystroke_put_char(stream, u) ; else { keystroke_steal_char(steal, stream, u) ; - stream->steal_this = 0 ; + stream->steal_this = false ; steal = NULL ; } ; @@ -615,9 +713,33 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, } ; break ; - case kst_char: /* TBD */ + case kst_char: /* TBD */ zabort("impossible keystroke stream state") ; + case kst_cr: /* expecting something after CR */ + if ((u != '\n') && (u != '\r')) + { + if (u != '\0') + { + --ptr ; /* put back the duff XX */ + stream->iac = (u == tn_IAC) ; + /* re=escape if is IAC */ + } ; + u = '\r' ; + } ; + + if (!stream->steal_this) + keystroke_put_char(stream, u) ; + else + { + keystroke_steal_char(steal, stream, u) ; + stream->steal_this = false ; + steal = NULL ; + } ; + + stream->in.state = kst_null ; + break ; + case kst_esc: /* Expecting XX after ESC */ if (u == '[') { @@ -631,7 +753,7 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, else { keystroke_steal_esc(steal, stream, u) ; - stream->steal_this = 0 ; + stream->steal_this = false ; steal = NULL ; } ; @@ -644,32 +766,26 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, keystroke_add_raw(stream, u) ; else { - int ok = 1 ; - int l ; + bool ok ; - if ((u < 0x40) || (u > 0x7F)) + ok = stream->in.len < keystroke_max_len ; + /* have room for terminator */ + + if ((u < 0x40) || (u > 0x7E)) { --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 */ + ok = false ; /* broken */ } ; - 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 ; + stream->steal_this = false ; steal = NULL ; } ; stream->in.state = kst_null ; @@ -680,11 +796,13 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, assert(stream->in.len == 1) ; keystroke_add_raw(stream, u) ; - if (stream->in.raw[0]== tn_SB) + 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 */ + { + keystroke_put_iac_long(stream, false) ; + keystroke_in_pop(stream) ; + } ; break ; case kst_iac_sub: /* Expecting sub stuff */ @@ -706,6 +824,37 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, keystroke_set_null(stream, steal) ; } ; +/*------------------------------------------------------------------------------ + * Single level stack for keystroke input state, so that can handle IAC + * sequences transparently. + */ + +/* Push current state and set new current state for start of IAC option + * sequence. + */ +static void +keystroke_in_push(keystroke_stream stream, uint8_t u) +{ + assert(stream->pushed_in.state == kst_null) ; + + stream->pushed_in = stream->in ; + + stream->in.len = 1 ; + stream->in.raw[0] = u ; + + stream->in.state = kst_iac_option ; +} ; + +/* Pop the pushed state and clear the pushed state to kst_null */ +static void +keystroke_in_pop(keystroke_stream stream) +{ + stream->in = stream->pushed_in ; + + stream->pushed_in.state = kst_null ; + stream->pushed_in.len = 0 ; +} ; + /*============================================================================== * Fetch next keystroke from keystroke stream * @@ -742,9 +891,11 @@ keystroke_get(keystroke_stream stream, keystroke stroke) stroke->type = b & kf_type_mask ; stroke->value = 0 ; - stroke->flags = b & (kf_broken | kf_truncated) ; + stroke->flags = b & kf_flag_mask ; stroke->len = keystroke_get_byte(stream) ; + assert(stroke->len <= keystroke_max_len) ; + /* Fetch what we need to the stroke buffer */ p = stroke->buf ; e = p + stroke->len ; @@ -884,7 +1035,7 @@ keystroke_put_char(keystroke_stream stream, uint32_t u) /*------------------------------------------------------------------------------ * Store simple ESC. Is broken if length (after ESC) == 0 ! */ -inline static void +static void keystroke_put_esc(keystroke_stream stream, uint8_t u, int len) { keystroke_put(stream, ks_esc, (len == 0), &u, len) ; @@ -894,46 +1045,94 @@ keystroke_put_esc(keystroke_stream stream, uint8_t u, int 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. + * the existing last character -- the sequence is truncated, but this way at + * least the end of the sequence is preserved. * * Is broken if u == '\0'. May also be truncated ! */ -inline static void +static void keystroke_put_csi(keystroke_stream stream, uint8_t u) { + int l ; + + l = stream->in.len++ ; + + if (l >= keystroke_max_len) + l = keystroke_max_len - 1 ; + + stream->in.raw[l] = u ; /* plant terminator */ + 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 + * Store IAC -- if not broken, send it via any call-back. */ -inline static void -keystroke_put_iac(keystroke_stream stream, uint8_t u, int len) +static void +keystroke_put_iac(keystroke_stream stream, bool broken, uint8_t* bytes, int len) { - keystroke_put(stream, ks_iac, (len == 0), &u, len) ; + bool dealt_with = false ; + + if (!broken && (stream->iac_callback != NULL)) + { + struct keystroke stroke ; + + assert((len >= 1) && (bytes != NULL) && (len <= keystroke_max_len)) ; + + stroke.type = ks_iac ; + stroke.value = bytes[0] ; + stroke.flags = 0 ; + stroke.len = len ; + + memcpy(&stroke.buf, bytes, len) ; + + dealt_with = (*stream->iac_callback)(stream->iac_callback_context, + &stroke) ; + } ; + + if (!dealt_with) + keystroke_put(stream, ks_iac, broken, bytes, len) ; +} ; + +/*------------------------------------------------------------------------------ + * Store one byte IAC. + */ +static void +keystroke_put_iac_one(keystroke_stream stream, uint8_t u) +{ + keystroke_put_iac(stream, false, &u, 1) ; } ; /*------------------------------------------------------------------------------ * 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) +static void +keystroke_put_iac_long(keystroke_stream stream, bool broken) { - keystroke_put(stream, ks_iac, broken, stream->in.raw, stream->in.len) ; + keystroke_put_iac(stream, broken, stream->in.raw, stream->in.len) ; +} ; - stream->in = stream->pushed_in ; - stream->pushed_in.state = kst_null ; +/*------------------------------------------------------------------------------ + * If in IAC state, issue broken IAC and clear state. + */ +static void +keystroke_clear_iac(keystroke_stream stream) +{ + if (stream->iac) + { + keystroke_put_iac(stream, true, NULL, 0) ; + stream->iac = 0 ; + } ; } ; /*------------------------------------------------------------------------------ * Store <first> <len> [<bytes>] + * + * If len == 0, bytes may be NULL */ static void -keystroke_put(keystroke_stream stream, enum keystroke_type type, int broken, - uint8_t* p, int len) +keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken, + uint8_t* bytes, int len) { if (len > keystroke_max_len) { @@ -946,7 +1145,7 @@ keystroke_put(keystroke_stream stream, enum keystroke_type type, int broken, vio_fifo_put_byte(&stream->fifo, len) ; if (len > 0) - vio_fifo_put(&stream->fifo, (void*)p, len) ; + vio_fifo_put(&stream->fifo, (void*)bytes, len) ; } ; /*------------------------------------------------------------------------------ @@ -976,30 +1175,28 @@ keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u) } ; /*------------------------------------------------------------------------------ - * Steal CSI escape. + * Steal CSI escape -- cannot be broken or truncated. * * 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) ; + len = stream->in.len ; /* excludes the escape terminator */ + assert((len < keystroke_max_len) && (u >= 0x40) && (u <= 0x7E)) ; steal->type = ks_esc ; steal->value = u ; steal->flags = 0 ; - steal->len = len - 1 ; + steal->len = len ; - memcpy(steal->buf, stream->in.raw, len - 1) ; + memcpy(steal->buf, stream->in.raw, len) ; steal->buf[len] = '\0' ; } ; diff --git a/lib/keystroke.h b/lib/keystroke.h index 4dc94d12..2b1d4d93 100644 --- a/lib/keystroke.h +++ b/lib/keystroke.h @@ -64,11 +64,14 @@ enum keystroke_null 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_flag_mask = 0x70, /* flags for the keystroke */ + kf_type_mask = 0x0F, /* extraction of type */ } ; @@ -80,7 +83,7 @@ typedef struct keystroke_stream* keystroke_stream ; struct keystroke { enum keystroke_type type ; - uint8_t flags ; + uint8_t flags ; /* the kf_flag_mask flags */ uint32_t value ; @@ -88,6 +91,9 @@ struct keystroke uint8_t buf[keystroke_max_len] ; } ; +#define keystroke_iac_callback_args void* context, keystroke stroke +typedef bool (keystroke_callback)(keystroke_iac_callback_args) ; + /* Telnet commands/options */ enum tn_Command { @@ -165,12 +171,13 @@ enum tn_Option * Functions */ extern keystroke_stream -keystroke_stream_new(uint8_t csi_char) ; +keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, + void* iac_callback_context) ; extern void keystroke_stream_set_eof(keystroke_stream stream) ; -extern void +extern keystroke_stream keystroke_stream_free(keystroke_stream stream) ; extern bool diff --git a/lib/list_util.h b/lib/list_util.h index b658c7ce..10ba8b0c 100644 --- a/lib/list_util.h +++ b/lib/list_util.h @@ -87,7 +87,7 @@ * struct item * { * .... - * struct list_pair(struct item*) foo_list ; + * struct dl_list_pair(struct item*) foo_list ; * .... * } ; * @@ -99,15 +99,15 @@ * * A double base may be declared: * - * struct base_pair(struct item*) foo_base ; + * struct dl_base_pair(struct item*) foo_base ; * * Various ways to construct structures or structure types: * - * typedef struct list_pair(struct foo*) foo_list ; + * typedef struct dl_list_pair(struct foo*) foo_list ; * - * struct foo_list list_pair(struct foo*) ; + * struct foo_list dl_list_pair(struct foo*) ; * - * struct foo_base base_pair(struct foo*) ; + * struct foo_base dl_base_pair(struct foo*) ; */ #define dl_list_pair(ptr_t) { ptr_t next ; ptr_t prev ; } @@ -37,6 +37,7 @@ #include <ucontext.h> #endif #include "qpthreads.h" +#include "qfstring.h" /* log is protected by the same mutext as vty, see comments in vty.c */ @@ -89,6 +90,8 @@ const char *zlog_priority[] = * with any number of decimal digits, but at most 6 will be significant. */ +static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ; + /*------------------------------------------------------------------------------ * Fill buffer with current time, to given number of decimal digits. * @@ -100,21 +103,25 @@ const char *zlog_priority[] = * * NB: buflen MUST be > 1 and buf MUST NOT be NULL. */ -size_t +extern size_t quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { - size_t result; + qf_str_t qfs ; + VTY_LOCK() ; - result = uquagga_timestamp(timestamp_precision, buf, buflen); + + qfs_init(&qfs, buf, buflen) ; + uquagga_timestamp(&qfs, timestamp_precision) ; + VTY_UNLOCK() ; - return result; + return qfs_len(&qfs) ; } /*------------------------------------------------------------------------------ * unprotected version for when mutex already held */ -size_t -uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) +static void +uquagga_timestamp(qf_str qfs, int timestamp_precision) { static struct { time_t last; @@ -124,11 +131,6 @@ uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) struct timeval clock; - 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); @@ -138,61 +140,36 @@ uquagga_timestamp(int timestamp_precision, char *buf, size_t buflen) struct tm tm; 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) ; + cache.len = strftime(cache.buf, sizeof(cache.buf), TIMESTAMP_FORM, &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 */ - - 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 */ + /* 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 + */ - memcpy(buf, cache.buf, len) ; + qfs_append_n(qfs, cache.buf, cache.len) ; - /* Can do decimal part if there is room for the '.' character */ - if ((timestamp_precision > 0) && (left > 0)) + /* Add decimal part as required. */ + if (timestamp_precision > 0) { /* 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 */ - { - *p-- = '0'; - --prec ; - } ; + if (prec > 6) + prec = 6 ; - clock.tv_usec /= divisor[prec]; + qfs_append_n(qfs, ".", 1) ; + qfs_unsigned(qfs, clock.tv_usec / divisor[prec], 0, 0, prec) ; - while (prec > 0) /* could have been reduced to 0 */ - { - *p-- = '0'+(clock.tv_usec % 10); - clock.tv_usec /= 10; - --prec ; - } ; - - *p = '.'; + if (prec < timestamp_precision) + qfs_append_ch_x_n(qfs, '0', timestamp_precision - prec) ; } ; - - buf[len] = '\0'; - - return len ; } ; /*============================================================================== @@ -223,7 +200,7 @@ uvzlog (struct zlog *zl, int priority, const char *format, va_list va) /* When zlog_default is also NULL, use stderr for logging. */ if (zl == NULL) { - uvzlog_line(&ll, zl, priority, format, va, 0) ; + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; write(fileno(stderr), ll.line, ll.len) ; } else @@ -240,14 +217,14 @@ uvzlog (struct zlog *zl, int priority, const char *format, va_list va) /* File output. */ if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) { - uvzlog_line(&ll, zl, priority, format, va, 0) ; + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; write(fileno(zl->fp), ll.line, ll.len) ; } /* stdout output. */ if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) { - uvzlog_line(&ll, zl, priority, format, va, 0) ; + uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; write(fileno(zl->fp), ll.line, ll.len) ; } @@ -264,54 +241,37 @@ uvzlog (struct zlog *zl, int priority, const char *format, va_list va) */ extern void uvzlog_line(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va, int crlf) + const char *format, va_list va, enum ll_term term) { - char* p ; + char* p ; + const char* q ; 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)) + if (term == ll->term) return ; /* exit here if all set */ } else { /* must construct the line */ - const char* q ; - char* e ; - size_t len ; + qf_str_t qfs ; 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 */ + qfs_init(&qfs, ll->line, sizeof(ll->line) - 2) ; + /* leave space for '\n' or '\r''\n' */ + /* "<time stamp>" */ + uquagga_timestamp(&qfs, (zl != NULL) ? zl->timestamp_precision : 0) ; - if (p < e) - *p++ = ' ' ; + qfs_append_n(&qfs, " ", 1) ; /* "<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++ = ' ' ; + qfs_append(&qfs, zlog_priority[priority]) ; + qfs_append(&qfs, ": ") ; } ; /* "<protocol>: " or "unknown: " */ @@ -320,48 +280,29 @@ uvzlog_line(struct logline* ll, struct zlog *zl, int priority, 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++ = ' ' ; + qfs_append(&qfs, q) ; + qfs_append(&qfs, ": ") ; /* 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 */ + va_copy(vac, va); + qfs_vprintf(&qfs, format, vac) ; + va_end(vac); - if (p > e) - p = e ; /* actual end */ - } ; - - ll->p_nl = p ; /* set end pointer */ - - assert(p <= e) ; + /* Set pointer to where the '\0' is. */ + p = ll->p_nl = qfs_end(&qfs) ; } ; /* finish off with '\r''\n''\0' or '\n''\0' as required */ - if (crlf) + if (term == llt_crlf) *p++ = '\r' ; - *p++ = '\n' ; + if (term != llt_nul) + *p++ = '\n' ; + *p = '\0' ; ll->len = p - ll->line ; - ll->crlf = crlf ; + ll->term = term ; } ; /*============================================================================*/ @@ -228,9 +228,6 @@ 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 */ -extern size_t uquagga_timestamp(int timestamp_precision /* # subsecond digits */, - char *buf, size_t buflen); /* Generate line to be logged * @@ -241,24 +238,31 @@ extern size_t uquagga_timestamp(int timestamp_precision /* # subsecond digits */ * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging. */ enum { logline_buffer_len = 1008 } ; +enum ll_term +{ + llt_nul = 0, /* NB: also length of the terminator */ + llt_lf = 1, + llt_crlf = 2, +} ; + struct logline { - char* p_nl ; /* address of the first byte of "\n" or "\r\n" */ - /* NULL => not filled in yet */ + char* p_nl ; /* address of the terminator */ + + enum ll_term term ; /* how line is terminated */ - 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" */ + size_t len ; /* length including either '\r''\n' or '\n' */ - char buf[logline_buffer_len]; /* buffer */ + char line[logline_buffer_len]; /* buffer */ } ; extern void uvzlog_line(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va, int crlf) ; + const char *format, va_list va, enum ll_term term) ; /* Defines for use in command construction: */ -#define LOG_LEVELS "(emergencies|alerts|critical|errors|warnings|notifications|informational|debugging)" +#define LOG_LEVELS "(emergencies|alerts|critical|errors|" \ + "warnings|notifications|informational|debugging)" #define LOG_LEVEL_DESC \ "System is unusable\n" \ diff --git a/lib/mem_tracker.c b/lib/mem_tracker.c index a7ee430f..fa967476 100644 --- a/lib/mem_tracker.c +++ b/lib/mem_tracker.c @@ -286,6 +286,9 @@ mem_md_free(enum MTYPE mtype, void* address) mem_descriptor md, prev_md ; md_index this, next ; + if (address == NULL) + return ; + base = mem_md_base(address) ; prev_md = NULL ; diff --git a/lib/memtypes.c b/lib/memtypes.c index 4ed5cbc0..d0cb1eee 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -15,7 +15,7 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, - { MTYPE_STRVEC, "String vector" }, + { MTYPE_STRVEC, "String vector" }, { MTYPE_VECTOR, "Vector structure" }, { MTYPE_VECTOR_BODY, "Vector body" }, { MTYPE_SYMBOL_TABLE, "Symbol Table structure" }, @@ -41,17 +41,21 @@ struct memory_list memory_list_lib[] = { MTYPE_QTIMER_PILE, "qtimer pile structure" }, { MTYPE_QTIMER, "qtimer timer" }, { MTYPE_QPN_NEXUS, "qtn nexus" }, - { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, + { MTYPE_CMD_PARSED, "Parsed command" }, + { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, { 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_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, + { MTYPE_VIO_LC, "VTY IO Line Control" }, { MTYPE_QSTRING, "qstring structure" }, { MTYPE_QSTRING_BODY, "qstring body" }, + { MTYPE_QIOVEC, "qiovec structure" }, + { MTYPE_QIOVEC_VEC, "qiovec iovec vector" }, { MTYPE_IF, "Interface" }, { MTYPE_CONNECTED, "Connected" }, { MTYPE_CONNECTED_LABEL, "Connected interface label" }, diff --git a/lib/mqueue.c b/lib/mqueue.c index 8b557dfe..90e1616c 100644 --- a/lib/mqueue.c +++ b/lib/mqueue.c @@ -178,6 +178,7 @@ mqueue_finish(void) while ((mqb = mqb_free_list) != NULL) { assert(mqb_free_count != 0) ; + mqb_free_count-- ; mqb_free_list = mqb->next ; XFREE(MTYPE_MQUEUE_BLOCK, mqb) ; } ; @@ -526,12 +527,17 @@ static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ; * for a signal type message queue, each message that arrives will kick one * waiter. * + * If mq is NULL, the message is not queued but is immediately destroyed. + * * NB: this works perfectly well if !qpthreads enabled. Of course, there can * never be any waiters... so no kicking is ever done. */ extern void mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) { + if (mq == NULL) + return mqb_dispatch_destroy(mqb) ; + qpt_mutex_lock(&mq->mutex) ; if (mq->head == NULL) @@ -634,6 +640,8 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, int priority) * NB: the argument is ignored if !wait or !qpthreads_enabled, so may be NULL. * * Returns a message block if one is available. (And not otherwise.) + * + * NB: if mq is NULL, returns NULL -- nothing available */ extern mqueue_block mqueue_dequeue(mqueue_queue mq, int wait, void* arg) @@ -644,6 +652,9 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg) mqueue_thread_signal mtsig ; qtime_mono_t timeout_time ; + if (mq == NULL) + return NULL ; + qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ while (1) @@ -748,6 +759,8 @@ done: * operations may be performed. Enqueued items may promptly be revoked, except * for priority items if the revoke operation has already moved past the last * priority item. + * + * If mq is NULL, does nothing. */ extern void mqueue_revoke(mqueue_queue mq, void* arg0) @@ -755,6 +768,9 @@ mqueue_revoke(mqueue_queue mq, void* arg0) mqueue_block mqb ; mqueue_block prev ; + if (mq == NULL) + return ; + qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ prev = NULL ; diff --git a/lib/qfstring.c b/lib/qfstring.c new file mode 100644 index 00000000..30ee441c --- /dev/null +++ b/lib/qfstring.c @@ -0,0 +1,1066 @@ +/* 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 <stdbool.h> +#include <stdint.h> + +#include "qfstring.h" + +/*============================================================================== + */ + +/*------------------------------------------------------------------------------ + * Initialise qf_str -- to given size (which includes the '\n') + * + * Sets pointers and terminates an empty string with one byte reserved for the + * terminating '\n'. + * + * This operation is async-signal-safe. + */ +extern void +qfs_init(qf_str qfs, char* str, size_t size) +{ + assert(size > 0) ; + + qfs->str = qfs->ptr = str ; + qfs->end = qfs->str + size - 1 ; + + *str = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Terminate string with the given string. + * + * If necessary, characters are discarded from the end of the string in order + * to fit in the terminating stuff. + * + * If the terminating stuff won't fit, as much of the end if the terminating + * stuff as possible is copied to the string -- displacing any existing + * contents. + * + * This operation is async-signal-safe. + */ +extern void +qfs_term(qf_str qfs, const char* src) +{ + int len ; + int excess ; + + if ((src == NULL) || (*src == '\0')) + { + *qfs->ptr = '\0' ; /* should be true anyway */ + return ; + } ; + + len = strlen(src) ; + excess = qfs_len(qfs) - len ; + if (excess > 0) + { + if (excess <= (qfs->ptr - qfs->str)) + qfs->ptr -= excess ; + else + { + int want = len ; + len = qfs->end - qfs->str ; /* take what can... */ + src += (want - len) ; /* ... from the end */ + qfs->ptr = qfs->str ; + } ; + } ; + + memcpy(qfs->ptr, src, len + 1) ; /* include the '\0' */ + qfs->ptr += len ; +} ; + +/*============================================================================== + * Appending to the string + */ + +/*------------------------------------------------------------------------------ + * Append as much as possible of the source string to the given qf_str. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append(qf_str qfs, const char* src) +{ + int n ; + + if ((src == NULL) || (*src == '\0')) + return ; + + n = strlen(src) ; + + if (n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n == 0) + return ; + + memcpy(qfs->ptr, src, n + 1) ; + qfs->ptr += n ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the first 'n' bytes of the source string to + * the given qf_str. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_n(qf_str qfs, const char* src, size_t n) +{ + if ((int)n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n <= 0) + return ; + + memcpy(qfs->ptr, src, n) ; + qfs->ptr += n ; + + *qfs->ptr = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Append upto 'n' copies of the given character to the qf_str + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_ch_x_n(qf_str qfs, char ch, size_t n) +{ + if ((int)n > qfs_left(qfs)) + n = qfs_left(qfs) ; + + if (n <= 0) + return ; + + while (n--) + *qfs->ptr++ = ch ; + + *qfs->ptr = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the source string to the given qf_str, left or + * right justified to the given width. + * + * Ignores the width if the string is longer than it. + * + * Negative width => left justify. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_justified(qf_str qfs, const char* src, int width) +{ + size_t n ; + + if ((src == NULL) || (*src == '\0')) + n = 0 ; + else + n = strlen(src) ; + + qfs_append_justified_n(qfs, src, n, width) ; +} ; + +/*------------------------------------------------------------------------------ + * Append as much as possible of the first 'n' bytes of the source string to + * the given qf_str, left or right justified to the given width. + * + * Ignores the width if the string is longer than it. + * + * Negative width => left justify. + * + * May append nothing at all ! + * + * This operation is async-signal-safe. + */ +extern void +qfs_append_justified_n(qf_str qfs, const char* src, size_t n, int width) +{ + if (n >= abs(width)) + width = 0 ; + + if (width > 0) + qfs_append_ch_x_n(qfs, ' ', width - n) ; + + qfs_append_n(qfs, src, n) ; + + if (width < 0) + qfs_append_ch_x_n(qfs, ' ', - width - n) ; +} ; + +/*============================================================================== + * Number conversion + */ + +static void +qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags, + int width, int precision) ; + +/*------------------------------------------------------------------------------ + * Signed integer -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags, + int width, int precision) +{ + uintmax_t u_val ; + int sign ; + + if (s_val < 0) + { + sign = -1 ; + u_val = (uintmax_t)(-(s_val + 1)) + 1 ; + } + else + { + sign = +1 ; + u_val = s_val ; + } ; + + qfs_number(qfs, u_val, sign, flags & ~pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Unsigned integer -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags, + int width, int precision) +{ + qfs_number(qfs, u_val, 0, flags | pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Address -- converted as per flags, width and precision. + * + * Result is appended to the given qf_str. + * + * This operation is async-signal-safe. + */ +extern void +qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, + int width, int precision) +{ + confirm(sizeof(uintmax_t) >= sizeof(uintptr_t)) ; + qfs_number(qfs, (uintptr_t)p_val, 0, flags | pf_unsigned, width, precision) ; +} ; + +/*------------------------------------------------------------------------------ + * Number conversion function. + * + * All number conversion ends up here. + * + * Accepts: pf_commas -- format with commas + * pf_plus -- requires '+' or '-' + * pf_space -- requires space or '-' + * pf_zeros -- zero fill to width + * pf_alt -- add '0x' or '0X' if hex (no effect on decimal) + * + * pf_precision -- explicit precision (needed if precision == 0) + * + * pf_hex -- render in hex + * pf_uc -- render in upper case + * + * pf_unsigned -- value is unsigned + * pf_ptr -- value is a void* pointer + * + * NB: pf_hex does NOT imply pf_unsigned. + * pf_uc does NOT imply pf_hex + * + * If the width is < 0 -- left justify in abs(width) -- zero fill ignored + * == 0 -- no width -- zero fill ignored + * > 0 -- right justify in width -- zero filling if req. + * + * If the precision is < 0 it is ignored (unless pf_hex, see below). + * + * If the precision is 0 it is ignored unless bf_precision is set. + * + * Precedence issues: + * + * * precision comes first. Disables zero fill. + * + * * commas come before zero fill. + * + * * signs and prefixes come before zero fill + * + * * pf_plus takes precedence over pf_space + * + * * pf_unsigned or sign == 0 takes precedence over pf_plus and pf_space. + * + * For hex output, pf_commas groups digits in 4's, separated by '_'. + * + * For hex output if precision is: + * + * -1 set precision to multiple of 2, just long enough for the value + * -2 set precision to multiple of 4, just long enough for the value + * + * (under all other conditions, -ve precision is ignored). + * + * Note: if the precision is explicitly 0, and the value is 0, and no other + * characters are to be generated -- ie no: pf_plus, pf_space, pf_zeros, + * or pf_alt (with pf_hex) -- then nothing is generated. + * + * This operation is async-signal-safe. + */ +static void +qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags, + int width, int precision) +{ + enum + { + max_bits = 256, /* size of number can convert */ + max_digits = 90, /* could do octal ! */ + buf_size = 128, /* buffer to use for that */ + } ; + + confirm((sizeof(uintmax_t) * 8) <= max_bits) ; /* check max_bits */ + confirm((max_digits * 3) >= max_bits) ; /* check max_digits */ + + /* Buffer requires space for sign, '0x', digits, '00', commas, '\0' + * + * The '00' is for zero fill will commas, and is enough to extend the + * number to "000,...." -- that is, a full leading triple. + */ + confirm(buf_size > (1 + 2 + max_digits + (2 + (max_digits / 3)) + 1)) ; + + /* For hex commas the sum is similar, but smaller. */ + confirm((3 + (max_digits / 4)) < (2 + (max_digits / 3))) ; + + unsigned base ; + const char* digits ; + const char* radix_str ; + const char* sign_str ; + char num[buf_size] ; + char* p ; + char* e ; + int len ; + int radix_len ; + int sign_len ; + uintmax_t v ; + + char comma ; + int interval ; + + int zeros ; + + static const char lc[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' } ; + static const char uc[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' } ; + + /* Tidy up the options */ + if (precision < 0) + { + if ((flags & pf_hex) && (precision >= -2)) + { + /* special precision for hex output */ + int unit = (precision == -1) ? 2 : 4 ; + v = val | 1 ; + precision = 0 ; + while (v != 0) + { + precision += unit ; + v >>= (unit * 4) ; + } ; + } + else + { + /* mostly, -ve precision is ignored */ + precision = 0 ; + flags &= ~pf_precision ; /* ignore precision < 0 */ + } ; + } ; + + if (precision > 0) + flags |= pf_precision ; /* act on precision > 0 */ + + if ((flags & pf_precision) || (width <= 0)) + flags &= ~pf_zeros ; /* turn off zero fill */ + + /* Set up any required sign and radix prefix */ + if ((flags & pf_unsigned) || (sign == 0)) + sign_str = "" ; + else if (sign < 0) + sign_str = "-" ; + else if (flags & pf_plus) + sign_str = "+" ; + else if (flags & pf_space) + sign_str = " " ; + else + sign_str = "" ; + + sign_len = strlen(sign_str) ; + + radix_str = "" ; + if ((flags & (pf_hex | pf_alt)) == (pf_hex | pf_alt)) + radix_str = (flags & pf_uc) ? "0X" : "0x" ; + + radix_len = strlen(radix_str) ; + + /* Turn off zero fill if left justify (width < 0) */ + if (width < 0) + flags &= ~pf_zeros ; + + /* Special case of explicit zero precision and value == 0 */ + if ((flags & pf_precision) && (precision == 0) && (val == 0)) + { + if (((flags & pf_zeros) == 0) && (sign_len == 0) && (radix_len == 0)) + { + qfs_append_justified_n(qfs, NULL, 0, width) ; + return ; + } ; + } ; + + /* Start with the basic digit conversion. */ + base = (flags & pf_hex) ? 16 : 10 ; + digits = (flags & pf_uc) ? uc : lc ; + + e = p = num + sizeof(num) - 1 ; + *p = '\0' ; + v = val ; + do + { + *--p = digits[v % base] ; + v /= base ; + } while ((v > 0) && (p > num)) ; + + assert(v == 0) ; + + len = e - p ; + + /* Worry about the precision */ + while ((precision > len) && (len < max_digits)) + { + *--p = '0' ; + ++len ; + } ; + + /* Worry about commas */ + comma = (flags & pf_hex) ? '_' : ',' ; + interval = (flags & pf_hex) ? 4 : 3 ; + + if (flags & pf_commas) + { + int c ; + int t ; + char* cq ; + char* cp ; + + c = (len - 1) / interval ; /* number of commas to insert */ + t = len % interval ; /* digits before first comma */ + if (t == 0) + t = interval ; + + len += c ; /* account for the commas */ + + cq = p ; + p -= c ; + cp = p ; + + assert(p > num) ; + + while (c--) + { + while (t--) + *cp++ = *cq++ ; + *cp++ = comma ; + } ; + + assert(len == (e - p)) ; + + /* commas and zero fill interact. Here fill the leading group. */ + zeros = width - (sign_len + radix_len + len) ; + if ((flags & pf_zeros) && (zeros > 0)) + { + int group_fill = interval - (len % (interval + 1)) ; + assert(group_fill < interval) ; + if (group_fill > zeros) + group_fill = zeros ; + + len += group_fill ; + while (group_fill--) + { + assert(p > num) ; + *--p = '0' ; + } ; + } ; + } ; + + assert(len == (e - p)) ; + + /* See if still need to worry about zero fill */ + zeros = width - (sign_len + radix_len + len) ; + if ((flags & pf_zeros) && (zeros > 0)) + { + /* Need to insert zeros and possible commas between sign and radix + * and the start of the number. + * + * Note that for commas the number has been arranged to have a full + * leading group. + * + * The width can be large... so do this by appending any sign and + * radix to the qf_str, and then the required leading zeros (with or + * without commas). + */ + if (sign_len != 0) + qfs_append_n(qfs, sign_str, sign_len) ; + + if (radix_len != 0) + qfs_append_n(qfs, radix_str, radix_len) ; + + if (flags & pf_commas) + { + /* Leading zeros with commas ! + * + * Start with ',', '0,', '00,' etc to complete the first group. + * Thereafter add complete groups. + */ + int g ; + int r ; + g = (zeros + interval - 1) / (interval + 1) ; + r = (zeros - 1) % (interval + 1) ; + + if (r == 0) + { + qfs_append_ch_x_n(qfs, comma, 1) ; + r = interval ; + } + + while (g--) + { + qfs_append_ch_x_n(qfs, '0', r) ; + qfs_append_ch_x_n(qfs, comma, 1) ; + r = interval ; + } ; + } + else + qfs_append_ch_x_n(qfs, '0', zeros) ; + + width = 0 ; /* have dealt with the width. */ + } + else + { + /* No leading zeros, so complete the number by adding any sign + * and radix. + */ + char* cp ; + + p -= sign_len + radix_len ; + len += sign_len + radix_len ; + assert(p >= num) ; + + cp = p ; + while (sign_len--) + *cp++ = *sign_str++ ; + while (radix_len--) + *cp++ = *radix_str++ ; + } ; + + /* Finally, can append the number -- respecting any remaining width */ + assert(len == (e - p)) ; + + qfs_append_justified_n(qfs, p, len, width) ; +} ; + +/*============================================================================== + * printf() and vprintf() type functions + */ + +enum pf_phase +{ + pfp_null, /* in ascending order */ + pfp_flags, + pfp_width, + pfp_precision, + pfp_num_type, + + pfp_done, + pfp_failed +} ; + +/* Number types for printing */ +enum arg_num_type +{ + ant_char, /* hh */ + ant_short, /* h */ + ant_int, /* default */ + ant_long, /* l */ + ant_long_long, /* ll */ + ant_intmax_t, /* j */ + ant_size_t, /* z */ + ant_ptr_t, /* void* */ + + ant_default = ant_int, +}; + +static enum pf_phase qfs_arg_string(qf_str qfs, va_list args, + enum pf_flags flags, int width, int precision) ; +static enum pf_phase qfs_arg_char(qf_str qfs, va_list args, + enum pf_flags flags, int width, int precision) ; +static enum pf_phase qfs_arg_number(qf_str qfs, va_list args, + enum pf_flags flags, int width, int precision, enum arg_num_type ant) ; + +/*------------------------------------------------------------------------------ + * Formatted print to qf_str -- cf printf() + * + * This operation is async-signal-safe. + */ +extern void +qfs_printf(qf_str qfs, const char* format, ...) +{ + va_list args; + + va_start (args, format); + qfs_vprintf(qfs, format, args); + va_end (args); +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to qf_str -- cf vprintf() + * + * This operation is async-signal-safe. + */ +extern void +qfs_vprintf(qf_str qfs, const char *format, va_list args) +{ + if (format == NULL) + return ; + + while ((qfs->ptr < qfs->end) && (*format != '\0')) + { + /* Have space for one byte and current format byte is not '\0' */ + if (*format != '%') + *qfs->ptr++ = *format++ ; + else + { + const char* start = format++ ; /* start points at the '%' ... + ... step past it now */ + bool star = false ; + bool digit = false ; + int d = 0 ; + int width_sign = +1 ; + int width = 0 ; + int precision = 0 ; + enum arg_num_type ant = ant_default ; + enum pf_flags flags = pf_none ; + enum pf_phase phase = pfp_null ; + + while (phase < pfp_done) + { + switch (*format++) /* get next and step past it */ + { + case '%': /* %% only */ + if (phase == pfp_null) + *qfs->ptr++ = '%' ; + phase = (phase == pfp_null) ? pfp_done : pfp_failed ; + break ; + + case '\'': + flags |= pf_commas ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '-': + width_sign = -1 ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '+': + flags |= pf_plus ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case ' ': + flags |= pf_space ; + phase = (phase <= pfp_flags) ? pfp_flags : pfp_failed ; + break ; + + case '0': + if (phase <= pfp_flags) + { + flags |= pf_zeros ; + phase = pfp_flags ; + break ; + } ; + /* fall through */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d = *(format - 1) - '0' ; + if (!star && (phase <= pfp_width)) + { + phase = pfp_width ; + width = (width * 10) + (d * width_sign) ; + } + else if (!star && (phase == pfp_precision)) + precision = (precision * 10) + d ; + else + phase = pfp_failed ; + + digit = true ; + break ; + + case '*': + if (!star && !digit && (phase <= pfp_width)) + { + phase = pfp_width ; + width = va_arg(args, int) ; + } + else if (!star && !digit && (phase == pfp_precision)) + { + precision = va_arg(args, int) ; + if (precision < 0) + { + precision = 0 ; + flags &= ~pf_precision ; + } ; + } + else + phase = pfp_failed ; + + star = true ; + break ; + + case '.': + phase = (phase <= pfp_precision) ? pfp_precision : pfp_failed; + flags |= pf_precision ; + precision = 0 ; + break ; + + case 'l': /* 1 or 2 'l', not 'h', 'j' or 'z' */ + phase = pfp_num_type ; + if (ant == ant_default) + ant = ant_long ; + else if (ant == ant_long) + ant = ant_long_long ; + else + phase = pfp_failed ; + break ; + + case 'h': /* 1 or 2 'h', not 'l', 'j' or 'z' */ + phase = pfp_num_type ; + if (ant == ant_default) + ant = ant_short ; + else if (ant == ant_short) + ant = ant_char ; + else + phase = pfp_failed ; + break ; + + case 'j': /* 1 'j', not 'h', 'l' or 'z' */ + phase = (phase <= pfp_num_type) ? pfp_num_type : pfp_failed ; + ant = ant_intmax_t ; + break ; + + case 'z': /* 1 'z', not 'h', 'l' or 'j' */ + phase = (phase <= pfp_num_type) ? pfp_num_type : pfp_failed ; + ant = ant_size_t ; + break ; + + case 's': + if (phase == pfp_num_type) + phase = pfp_failed ; /* don't do 'l' etc. */ + else + phase = qfs_arg_string(qfs, args, flags, width, precision) ; + break ; + + case 'c': + if (phase == pfp_num_type) + phase = pfp_failed ; /* don't do 'l' etc. */ + else + phase = qfs_arg_char(qfs, args, flags, width, precision) ; + break ; + + case 'd': + case 'i': + phase = qfs_arg_number(qfs, args, flags, width, precision, + ant) ; + break ; + + case 'u': + phase = qfs_arg_number(qfs, args, flags | pf_unsigned, width, + precision, ant) ; + break ; + + case 'x': + phase = qfs_arg_number(qfs, args, flags | pf_hex_x, width, + precision, ant) ; + break ; + + case 'X': + phase = qfs_arg_number(qfs, args, flags | pf_hex_X, width, + precision, ant) ; + break ; + + case 'p': + if (phase == pfp_num_type) + phase = pfp_failed ; + else + phase = qfs_arg_number(qfs, args, flags | pf_void_p, width, + precision, ant_ptr_t) ; + break ; + + default: /* unrecognised format */ + phase = pfp_failed ; + break ; + } ; + } ; + + if (phase == pfp_failed) + { + format = start ; /* back to the start */ + *qfs->ptr++ = *format++ ; /* copy the '%' */ + } ; + } ; + } ; + + *qfs->ptr = '\0' ; +} ; + +/*------------------------------------------------------------------------------ + * %s handler + * + * Accepts: width + * precision + * pf_precision -- explicit precision + * + * Rejects: pf_commas -- "'" seen + * pf_plus -- "+" seen + * pf_space -- " " seen + * pf_zeros -- "0" seen + * pf_alt -- "#" seen + * + * Won't get: pf_hex + * pf_uc + * pf_unsigned + * pf_ptr + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags, + int width, int precision) +{ + const char* src ; + int len ; + + src = va_arg(args, char*) ; + + if (flags != (flags & pf_precision)) + return pfp_failed ; + + len = strlen(src) ; + if (((precision > 0) || (flags & pf_precision)) && (len > precision)) + len = precision ; + + qfs_append_justified_n(qfs, src, len, width) ; + + return pfp_done ; +} ; + +/*------------------------------------------------------------------------------ + * %c handler + * + * Accepts: width + * + * Rejects: precision + * pf_precision -- explicit precision + * pf_commas -- "'" seen + * pf_plus -- "+" seen + * pf_space -- " " seen + * pf_zeros -- "0" seen + * pf_alt -- "#" seen + * + * Won't get: pf_hex + * pf_uc + * pf_unsigned + * pf_ptr + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_char(qf_str qfs, va_list args, enum pf_flags flags, + int width, int precision) +{ + unsigned char ch ; + + ch = va_arg(args, int) ; + + if ((flags != 0) || (precision != 0)) + return pfp_failed ; + + qfs_append_justified_n(qfs, (char*)&ch, 1, width) ; + + return pfp_done ; +} ; + +/*------------------------------------------------------------------------------ + * %d, %i, %u, %x, %X and %p handler + * + * Accepts: pf_commas -- format with commas + * pf_minus -- left justify (any width will be -ve) + * pf_plus -- requires sign + * pf_space -- requires space or '-' + * pf_zeros -- zero fill to width + * pf_alt -- '0x' or '0X' for hex + * + * pf_precision -- precision specified + * + * pf_unsigned -- value is unsigned + * pf_ptr -- value is a void* pointer + * pf_hex -- render in hex + * pf_uc -- render hex in upper case + * + * and: all the number argument types. + * + * This operation is async-signal-safe. + */ +static enum pf_phase +qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, + int width, int precision, enum arg_num_type ant) +{ + uintmax_t u_val ; + intmax_t s_val ; + + /* Special for hex with '0... if no explicit precision, set -1 for byte + * and -2 for everything else -- see qfs_number(). + */ + if (((flags & pf_precision) == 0) && (flags & pf_hex)) + { + if ((flags & (pf_commas | pf_zeros)) == (pf_commas | pf_zeros)) + { + precision = (ant == ant_char) ? -1 : -2 ; + flags |= pf_precision ; + } ; + } ; + + /* It is assumed that all values can be mapped to a uintmax_t */ + confirm(sizeof(uintmax_t) >= sizeof(uintptr_t)) ; + + if (flags & pf_unsigned) + { + switch (ant) + { + case ant_char: + case ant_short: + u_val = va_arg(args, int) ; + break ; + + case ant_int: + u_val = va_arg(args, unsigned int) ; + break ; + + case ant_long: + u_val = va_arg(args, unsigned long) ; + break ; + + case ant_long_long: + u_val = va_arg(args, unsigned long long) ; + break ; + + case ant_intmax_t: + u_val = va_arg(args, uintmax_t) ; + break ; + + case ant_size_t: + u_val = va_arg(args, size_t) ; + break ; + + case ant_ptr_t: + u_val = va_arg(args, uintptr_t) ; + break ; + + default: + zabort("impossible integer size") ; + } ; + + qfs_unsigned(qfs, u_val, flags, width, precision) ; + } + else + { + switch (ant) + { + case ant_char: + case ant_short: + s_val = va_arg(args, int) ; + break ; + + case ant_int: + s_val = va_arg(args, signed int) ; + break ; + + case ant_long: + s_val = va_arg(args, signed long) ; + break ; + + case ant_long_long: + s_val = va_arg(args, signed long long) ; + break ; + + case ant_intmax_t: + s_val = va_arg(args, intmax_t) ; + break ; + + case ant_size_t: + s_val = va_arg(args, ssize_t) ; + break ; + + case ant_ptr_t: + s_val = va_arg(args, intptr_t) ; + break ; + + default: + zabort("impossible integer size") ; + } ; + + qfs_signed(qfs, s_val, flags, width, precision) ; + } ; + + /* construct a digit string, the hard way */ + + return pfp_done ; +} ; + diff --git a/lib/qfstring.h b/lib/qfstring.h new file mode 100644 index 00000000..fbc83fe8 --- /dev/null +++ b/lib/qfstring.h @@ -0,0 +1,158 @@ +/* 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_QFSTRING_H +#define _ZEBRA_QFSTRING_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#ifndef Inline +#define Inline static inline +#endif + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +/*============================================================================== + * These "qfstrings" address the issues of dealing with *fixed* length + * strings, particularly where the string handling must be async-signal-safe. + */ + +typedef struct qf_str qf_str_t ; +typedef struct qf_str* qf_str ; + +/* When initialised a qf_string is set: + * + * str = start of string -- and this is never changed + * ptr = start of string -- this is moved as stuff is appended + * end = last possible position for terminating '\0' + * -- and this is never changed + */ +struct qf_str +{ + char* str ; /* start of string */ + char* ptr ; /* current position */ + char* end ; /* end of string */ +} ; + +/*------------------------------------------------------------------------------ + * Print format flags for number printing + */ +enum pf_flags +{ + pf_none = 0, + + /* The following correspond to the "flags" */ + pf_commas = 1 << 0, /* "'" seen */ + pf_plus = 1 << 1, /* "+" seen */ + pf_space = 1 << 2, /* " " seen */ + pf_zeros = 1 << 3, /* "0" seen */ + pf_alt = 1 << 4, /* "#" seen */ + + pf_precision = 1 << 7, /* '.' seen */ + + /* The following signal how to render the value */ + pf_hex = 1 << 8, /* hex */ + pf_uc = 1 << 9, /* upper-case */ + + /* The following signal the type of value */ + pf_ptr = 1 << 14, /* is a pointer */ + pf_unsigned = 1 << 15, /* unsigned value */ + + /* Common combination */ + pf_hex_x = pf_unsigned | pf_hex, + pf_hex_X = pf_unsigned | pf_hex | pf_uc, + + pf_void_p = pf_ptr | pf_hex_x, +} ; + +/*============================================================================== + * Functions + */ + +extern void qfs_init(qf_str qfs, char* str, size_t size) ; + +extern void qfs_term(qf_str qfs, const char* src) ; + +Inline int qfs_len(qf_str qfs) ; +Inline void* qfs_end(qf_str qfs) ; +Inline int qfs_left(qf_str qfs) ; + +extern void qfs_append(qf_str qfs, const char* src) ; +extern void qfs_append_n(qf_str qfs, const char* src, size_t n) ; + +extern void qfs_append_ch_x_n(qf_str qfs, char ch, size_t n) ; +extern void qfs_append_justified(qf_str qfs, const char* src, int width) ; +extern void qfs_append_justified_n(qf_str qfs, const char* src, + size_t n, int width) ; + +extern void qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags, + int width, int precision) ; +extern void qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags, + int width, int precision) ; +extern void qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, + int width, int precision) ; + +extern void qfs_printf(qf_str qfs, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern void qfs_vprintf(qf_str qfs, const char *format, va_list args) ; + +/*============================================================================== + * The Inline functions. + */ + +/*------------------------------------------------------------------------------ + * Current length of qf_str, not counting the terminating '\0'. + */ +Inline int +qfs_len(qf_str qfs) +{ + return qfs->ptr - qfs->str ; +} ; + +/*------------------------------------------------------------------------------ + * Address of the terminating '\0'. + */ +Inline void* +qfs_end(qf_str qfs) +{ + return qfs->ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Current space left in the qstr, given what has been reserved for terminating + * '\0' and any other reservation. + */ +Inline int +qfs_left(qf_str qfs) +{ + return qfs->end - qfs->ptr ; +} ; + + +#endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qiovec.c b/lib/qiovec.c new file mode 100644 index 00000000..546dfcb0 --- /dev/null +++ b/lib/qiovec.c @@ -0,0 +1,261 @@ +/* Flexible iovec handler + * 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 "memory.h" +#include "zassert.h" +#include "miyagi.h" + +#include "qiovec.h" + +/*============================================================================== + * Initialise, allocate and reset qiovec + */ + +/*------------------------------------------------------------------------------ + * Initialise new qiovec -- allocate if required. + * + * This is for initialising a new structure. Any pre-exiting contents are + * lost. + * + * Returns: address of qiovec + */ +extern qiovec +qiovec_init_new(qiovec viov) +{ + if (viov == NULL) + viov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ; + else + memset(viov, 0, sizeof(struct qiovec)) ; + + /* Zeroising has set: + * + * vec = NULL - no array, yet + * writing = false -- no writing going on + * + * i_get = 0 -- next entry to get + * i_put = 0 -- next entry to put + * + * i_alloc = 0; -- no entries allocated + * + * Nothing more is required. + */ + + return viov ; +} ; + +/*------------------------------------------------------------------------------ + * Reset qiovec (if any) -- release body and (if required) the structure. + * + * Returns: address of qiovec (if any) -- NULL if structure released + */ +extern qiovec +qiovec_reset(qiovec viov, bool free_structure) +{ + if (viov != NULL) + { + if (viov->vec != NULL) + XFREE(MTYPE_QIOVEC_VEC, viov->vec) ; + + if (free_structure) + XFREE(MTYPE_QIOVEC, viov) ; /* sets viov = NULL */ + else + qiovec_init_new(viov) ; /* re-initialise */ + } ; + + return viov ; +} ; + +/*------------------------------------------------------------------------------ + * Clear given qiovec. + */ +extern void +qiovec_clear(qiovec viov) +{ + viov->i_get = 0 ; + viov->i_put = 0 ; + viov->writing = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Push item to given qiovec + * + * NB: avoids pushing zero length items. + */ +extern void +qiovec_push(qiovec viov, const void* base, size_t len) +{ + struct iovec* p_iov ; + + if (len == 0) + return ; + + if (viov->i_put >= viov->i_alloc) + { + size_t size ; + assert(viov->i_put == viov->i_alloc) ; + + assert( ((viov->i_alloc == 0) && (viov->vec == NULL)) + || ((viov->i_alloc != 0) && (viov->vec != NULL)) ) ; + + if (viov->i_get > 200) /* keep in check */ + { + size = (viov->i_put - viov->i_get) * sizeof(struct iovec) ; + if (size != 0) + memmove(viov->vec, &viov->vec[viov->i_get], size) ; + viov->i_put -= viov->i_get ; + viov->i_get = 0 ; + } + else + { + viov->i_alloc += 100 ; /* a sizable chunk */ + + size = viov->i_alloc * sizeof(struct iovec) ; + if (viov->vec == NULL) + viov->vec = XMALLOC(MTYPE_QIOVEC_VEC, size) ; + else + viov->vec = XREALLOC(MTYPE_QIOVEC_VEC, viov->vec, size) ; + } ; + } ; + + p_iov = &viov->vec[viov->i_put++] ; + + p_iov->iov_base = miyagi(base) ; + p_iov->iov_len = len ; +} ; + +/*------------------------------------------------------------------------------ + * Write given qiovec -- assuming NON-BLOCKING. + * + * Does nothing if the qiovec is empty. + * + * Loops internally if gets EINTR. + * + * When there is nothing left to output, resets the i_put & i_get to zero. + * + * Returns: > 0 => one or more bytes left to output + * 0 => all done -- zero bytes left to output + * -1 => failed -- see errno + */ +extern int +qiovec_write_nb(int fd, qiovec viov) +{ + int n ; + int l ; + + n = viov->i_put - viov->i_get ; + + l = iovec_write_nb(fd, &viov->vec[viov->i_get], n) ; + + if (l == 0) + { + viov->writing = 0 ; + viov->i_get = viov->i_put = 0 ; + } + else + { + viov->writing = 1 ; + viov->i_get += (n - l) ; + } ; + + return l ; +} ; + +/*------------------------------------------------------------------------------ + * Write given iovec -- assuming NON-BLOCKING. + * + * Does nothing if given zero iovec entries (and array may be NULL). + * + * Loops internally if gets EINTR. + * + * If does not manage to write everything, then: + * + * -- updates the length field of all entries up to and including the + * last one for which data has been written. + * + * -- updates the address field of the first entry that still has some + * data to be output. + * + * Can call this again with the same 'p_iov' and the same 'n' -- the entries + * which have zero lengths will be stepped over. Output will continue from + * where it left off. + * + * Alternatively, if this returns 'l', then do "p_iov += n - l", and set + * "n = l" before calling this again. + * + * Returns: > 0 => number of entries left to output + * 0 => all done -- nothing left to output + * -1 => failed -- see errno + */ +extern int +iovec_write_nb(int fd, struct iovec p_iov[], int n) +{ + ssize_t ret ; + + assert(n >= 0) ; + + /* Skip past any leading zero length entries */ + while ((n > 0) && (p_iov->iov_len == 0)) + { + ++p_iov ; + --n ; + } ; + + while (n > 0) + { + ret = writev(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ; + + if (ret > 0) + { + while (ret > 0) + { + if (ret >= (ssize_t)p_iov->iov_len) + { + assert(n > 0) ; + ret -= p_iov->iov_len ; + p_iov->iov_len = 0 ; + ++p_iov ; + --n ; + } + else + { + p_iov->iov_base = (char*)p_iov->iov_base + ret ; + p_iov->iov_len -= ret ; + ret = 0 ; + } ; + } ; + } + 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 n ; +} ; diff --git a/lib/qiovec.h b/lib/qiovec.h new file mode 100644 index 00000000..ee03d2f2 --- /dev/null +++ b/lib/qiovec.h @@ -0,0 +1,99 @@ +/* Flexible iovec -- 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_QIOVEC_H +#define _ZEBRA_QIOVEC_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * Flexible size "struct iovec" + * + * NB: a completely zero structure is a valid, empty qiovec. + */ +typedef struct qiovec* qiovec ; +typedef struct qiovec qiovec_t ; +struct qiovec +{ + struct iovec* vec ; /* the actual iovec array */ + + bool writing ; /* started, but not finished */ + + unsigned i_get ; /* next entry to get */ + unsigned i_put ; /* next entry to put */ + + unsigned i_alloc ; /* number of entries allocated */ +} ; + +/*============================================================================== + * Functions + */ + +extern qiovec qiovec_init_new(qiovec qiov) ; +extern qiovec qiovec_reset(qiovec qiov, bool free_structure) ; + +#define qiovec_reset_keep(qiov) qiovec_reset(qiov, 0) +#define qiovec_reset_free(qiov) qiovec_reset(qiov, 1) + +Inline bool qiovec_empty(qiovec qiov) ; +extern void qiovec_clear(qiovec qiov) ; +extern void qiovec_push(qiovec qiov, const void* base, size_t len) ; +extern int qiovec_write_nb(int fd, qiovec qiov) ; + +extern int iovec_write_nb(int fd, struct iovec* p_iov, int n) ; +Inline void iovec_set(struct iovec* p_iov, const void* base, size_t len) ; + +/*------------------------------------------------------------------------------ + * Is given qiov empty ? + * + * NB: arranges to never add zero length entries to the iovec vector, so + * is empty when there are no active entries. + */ +Inline bool +qiovec_empty(qiovec qiov) +{ + return (qiov->i_get == qiov->i_put) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a given struct iovec + * + * Gets around the fact that the standard structure does not have a const + * pointer ! + */ +#include "miyagi.h" + +Inline void +iovec_set(struct iovec* p_iov, const void* base, size_t len) +{ + p_iov->iov_base = miyagi(base) ; + p_iov->iov_len = len ; +} ; + +#endif /* _ZEBRA_QIOVEC_H */ diff --git a/lib/qpnexus.c b/lib/qpnexus.c index 6fc9129d..e568e7d8 100644 --- a/lib/qpnexus.c +++ b/lib/qpnexus.c @@ -20,6 +20,7 @@ */ #include <zebra.h> +#include <stdbool.h> #include "qpnexus.h" #include "memory.h" @@ -65,6 +66,9 @@ qpn_init_new(qpn_nexus qpn, int main_thread) qpn->main_thread = main_thread; qpn->start = qpn_start; + if (main_thread) + qpn->thread_id = qpt_thread_self(); + return qpn; } @@ -79,11 +83,15 @@ qpn_add_hook_function(qpn_hook_list list, void* hook) } ; /*------------------------------------------------------------------------------ - * free timers, selection, message queue and nexus - * return NULL + * Reset given nexus and, if required, free the nexus structure. + * + * Free timers, selection, message queue and its thread signal. + * + * Leaves all pointers to these things NULL -- which generally means that the + * object is empty or otherwise out of action. */ -qpn_nexus -qpn_free(qpn_nexus qpn) +extern qpn_nexus +qpn_reset(qpn_nexus qpn, bool free_structure) { qps_file qf; qtimer qtr; @@ -96,6 +104,7 @@ qpn_free(qpn_nexus qpn) { while ((qtr = qtimer_pile_ream(qpn->pile, 1))) qtimer_free(qtr); + qpn->pile = NULL ; } /* files and selection */ @@ -103,6 +112,7 @@ qpn_free(qpn_nexus qpn) { while ((qf = qps_selection_ream(qpn->selection, 1))) qps_file_free(qf); + qpn->selection = NULL ; } if (qpn->queue != NULL) @@ -111,10 +121,11 @@ qpn_free(qpn_nexus qpn) if (qpn->mts != NULL) qpn->mts = mqueue_thread_signal_reset(qpn->mts, 1); - XFREE(MTYPE_QPN_NEXUS, qpn) ; + if (free_structure) + XFREE(MTYPE_QPN_NEXUS, qpn) ; /* sets qpn = NULL */ - return NULL; -} + return qpn ; +} ; /*============================================================================== * Execution of a nexus @@ -173,8 +184,8 @@ qpn_start(void* arg) 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]))() ; + for (i = 0; i < qpn->in_thread_init.count ;) + ((qpn_init_function*)(qpn->in_thread_init.hooks[i++]))() ; /* Until required to terminate, loop */ done = 1 ; @@ -193,8 +204,8 @@ qpn_start(void* arg) done = 0 ; /* Foreground hooks, if any. */ - for (i = 0; i < qpn->foreground.count ; ++i) - done |= ((qpn_hook_function*)(qpn->foreground.hooks[i]))() ; + for (i = 0; i < qpn->foreground.count ;) + done |= ((qpn_hook_function*)(qpn->foreground.hooks[i++]))() ; /* drain the message queue, will be in waiting for signal state * when it's empty */ @@ -246,8 +257,8 @@ qpn_start(void* arg) } ; /* custom in-thread finalization */ - for (i = qpn->in_thread_final.count - 1; i > 0 ; --i) - ((qpn_init_function*)(qpn->in_thread_final.hooks[i]))() ; + for (i = qpn->in_thread_final.count; i > 0 ;) + ((qpn_init_function*)(qpn->in_thread_final.hooks[--i]))() ; return NULL; } diff --git a/lib/qpnexus.h b/lib/qpnexus.h index c2cc6463..f4195a4d 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -150,6 +150,9 @@ extern qpn_nexus qpn_init_new(qpn_nexus qpn, int main_thread); extern void qpn_add_hook_function(qpn_hook_list list, void* hook) ; extern void qpn_exec(qpn_nexus qpn); extern void qpn_terminate(qpn_nexus qpn); -extern qpn_nexus qpn_free(qpn_nexus qpn); +extern qpn_nexus qpn_reset(qpn_nexus qpn, bool free_structure); + +#define qpn_reset_free(qpn) qpn_reset(qpn, 1) +#define qpn_reset_keep(qpn) qpn_reset(qpn, 0) #endif /* _ZEBRA_QPNEXUS_H */ diff --git a/lib/qpselect.c b/lib/qpselect.c index 882f4173..3cca3805 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -503,17 +503,32 @@ qps_file_init_new(qps_file qf, qps_file template) } ; /*------------------------------------------------------------------------------ - * Free dynamically allocated qps_file structure. + * Free dynamically allocated qps_file structure -- if any. * - * It is the caller's responsibility to have removed it from any selection it - * may have been in. + * Removes from any selection may be a member of. + * + * If there is a valid fd -- close it ! + * + * Returns: NULL */ -void +extern qps_file qps_file_free(qps_file qf) { - assert(qf->selection == NULL) ; /* Mustn't be a selection member ! */ + if (qf != NULL) + { + if (qf->selection != NULL) + qps_remove_file(qf) ; + + if (qf->fd >= 0) + { + close(qf->fd) ; + qf->fd = fd_undef ; + } ; + + XFREE(MTYPE_QPS_FILE, qf) ; + } ; - XFREE(MTYPE_QPS_FILE, qf) ; + return NULL ; } ; /*------------------------------------------------------------------------------ @@ -574,7 +589,7 @@ qps_set_action(qps_file qf, qps_mnum_t mnum, qps_action* action) /*------------------------------------------------------------------------------ * Disable file for one or more modes. * - * If there are any pending pending results for the modes, those are discarded. + * If there are any pending results for the modes, those are discarded. * * Note that this is modestly "optimised" to deal with disabling a single mode. * (Much of the time only the write mode will be being disabled !) diff --git a/lib/qpselect.h b/lib/qpselect.h index 561eebb2..8901ea36 100644 --- a/lib/qpselect.h +++ b/lib/qpselect.h @@ -199,7 +199,7 @@ qps_dispatch_next(qps_selection qps) ; extern qps_file qps_file_init_new(qps_file qf, qps_file template) ; -extern void +extern qps_file qps_file_free(qps_file qf) ; extern void diff --git a/lib/qpthreads.h b/lib/qpthreads.h index 4d71e12d..d73182ef 100644 --- a/lib/qpthreads.h +++ b/lib/qpthreads.h @@ -27,6 +27,7 @@ #include <pthread.h> #include <unistd.h> #include <errno.h> +#include <stdbool.h> #include "zassert.h" #include "qtime.h" @@ -142,20 +143,30 @@ private int qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */ /*============================================================================== - * Thread self knowledge -- returns 'NULL' if !qpthreads_enabled + * Thread self knowledge -- even when !qpthreads_enabled there is one thread */ Inline qpt_thread_t qpt_thread_self(void) { - return qpthreads_enabled ? pthread_self() : (qpt_thread_t)NULL; + return pthread_self() ; } ; -/*============================================================================== - * Thread equality -- returns non-zero (true) if threads are *equal* - * -- all threads are equal if !qpthreads_enabled +/*------------------------------------------------------------------------------ + * Thread equality -- returns true iff threads are *equal* + * -- even when !qpthreads_enabled there is one thread + */ +Inline bool qpt_threads_equal(qpt_thread_t a_id, qpt_thread_t b_id) +{ + return pthread_equal(a_id, b_id) != 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Thread identity -- returns true iff current thread is the given thread + * -- even when !qpthreads_enabled there is one thread */ -Inline int qpt_threads_equal(qpt_thread_t a, qpt_thread_t b) +Inline bool qpt_thread_is_self(qpt_thread_t id) { - return !qpthreads_enabled || pthread_equal(a, b) ? 1 : 0 ; + pthread_t self = pthread_self() ; + return pthread_equal(self, id) != 0 ; } ; /*============================================================================== diff --git a/lib/qstring.c b/lib/qstring.c index f847e0b0..a3dc95cd 100644 --- a/lib/qstring.c +++ b/lib/qstring.c @@ -35,29 +35,18 @@ * Returns: address of qstring * * NB: assumes initialising a new structure. If not, then caller should - * use qs_reset() or qs_set_empty(). + * use qs_reset() or qs_clear(). */ extern qstring qs_init_new(qstring qs, size_t len) { if (qs == NULL) - qs = XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; + qs = qs_new() ; 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 - */ + memset(qs, 0, sizeof(qstring_t)) ; /* see qs_new() */ if (len != 0) - qs_alloc(qs, len) ; + return qs_make_to_length(qs, len) ; return qs ; } ; @@ -65,48 +54,102 @@ qs_init_new(qstring qs, size_t len) /*------------------------------------------------------------------------------ * Allocate or reallocate so that string is big enough for the given length. * - * Allocates to 16 byte boundaries. + * Allocate qstring if required. Returns with a body with size > 0. * - * Returns: the number of bytes *allocated*, which includes the byte for - * possible trailing '\0'. + * Allocates to 16 byte boundaries with space for '\0' beyond given length. * - * NB: allocates EXTRA space for trailing '\0' beyond given length. + * Returns: address of qstring + * + * NB: allocates new body if the size == 0. + * + * If the qstring is a "dummy", its contents are now copied to the new + * real qstring body -- up to a maximum of the new length. */ -extern size_t -qs_alloc(qstring qs, size_t len) +extern qstring +qs_make_to_length(qstring qs, size_t len) { - len = (len + 0x10) & ~(size_t)(0x10 - 1) ; + size_t size = (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 + if (qs == NULL) + qs = qs_new() ; + + if (size > qs->size) { - assert(qs->size > 0) ; - qs->size *= 2 ; - if (qs->size < len) - qs->size = len ; - qs->body = XREALLOC(MTYPE_QSTRING_BODY, qs->body, qs->size) ; - } ; + if (qs->size == 0) + { + void* old ; + old = qs->body ; + + qs->size = size ; + qs->body = XMALLOC(MTYPE_QSTRING_BODY, qs->size) ; + + if ((qs->len != 0) && (old != NULL)) + memcpy(qs->body, old, (qs->len <= len) ? qs->len : len) ; + } + else + { + qs->size *= 2 ; + if (qs->size < size) + qs->size = size ; + qs->body = XREALLOC(MTYPE_QSTRING_BODY, qs->body, qs->size) ; + } ; + }; - return qs->size ; + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Add 'n' to the current string length, allocating or extending the body as + * required. + * + * Allocate qstring if required. Returns with a body with size > 0. + * + * Allocates to 16 byte boundaries with space for '\0' beyond new length. + * + * Returns: address of qstring + * + * also: sets char** p_ep to point at the *end* of the new len. + * + * NB: allocates new body if the size == 0. + * + * If the qstring is a "dummy", its contents are now copied to the new + * real qstring body -- up to a maximum of the new length. + */ +extern qstring +qs_add_len(qstring qs, size_t n, char** p_ep) +{ + size_t len ; + len = (qs != NULL) ? qs->len + n : n ; + + qs = qs_make_to_length(qs, len) ; + + qs->len = len ; + + *p_ep = (char*)qs->body + len ; + + return qs ; } ; /*------------------------------------------------------------------------------ * Free body of qstring -- zeroise size, len and cp + * + * Does nothing if qstring is NULL + * + * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain + * the old body. */ extern void qs_free_body(qstring qs) { - if (qs->body != NULL) - XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ + if (qs != NULL) + { + if (qs->size != 0) + XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */ - qs->size = 0 ; - qs->len = 0 ; - qs->cp = 0 ; + qs->size = 0 ; + qs->len = 0 ; + qs->cp = 0 ; + } ; } ; /*------------------------------------------------------------------------------ @@ -115,73 +158,102 @@ qs_free_body(qstring qs) * If not freeing the structure, zeroise size, len and cp -- qs_free_body() * * Returns: NULL if freed the structure - * address of structure, otherwise + * address of structure (if any), otherwise + * + * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain + * the old body. */ 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 + if (qs != NULL) { - qs->size = 0 ; - qs->len = 0 ; - qs->cp = 0 ; - } ; + if (qs->size != 0) + 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 + * printf() and vprintf() type functions */ /*------------------------------------------------------------------------------ * Formatted print to qstring -- cf printf() + * + * Allocate qstring if required. + * + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) */ -extern int +extern qstring qs_printf(qstring qs, const char* format, ...) { va_list args; - int result ; va_start (args, format); - result = qs_vprintf(qs, format, args); + qs = qs_vprintf(qs, format, args); va_end (args); - return result; + return qs; } ; /*------------------------------------------------------------------------------ * 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'. + * Allocate qstring if required. * - * Also note that given a zero length the string address may be NULL, and the - * result is still the length required. + * Returns: address of qstring if OK + * NULL if failed (unlikely though that is) */ -extern int +extern qstring qs_vprintf(qstring qs, const char *format, va_list args) { va_list ac ; - int len ; + int len ; + qstring qqs ; + + qqs = qs ; + if (qs == NULL) + qs = qs_new() ; while (1) { + /* Note that vsnprintf() returns the length of what it would like to have + * produced, if it had the space. That length does not include the + * trailing '\0'. + * + * Also note that given a zero length the string address may be NULL, and + * the result is still the length required. + */ va_copy(ac, args); qs->len = len = vsnprintf (qs->body, qs->size, format, ac) ; va_end(ac); + if (len < 0) + break ; + if (len < (int)qs->size) - return len ; /* quit if done (or error) */ + return qs ; - qs_alloc(qs, len) ; + qs_make_to_length(qs, len) ; } ; + + if (qqs == NULL) + qs_reset_free(qs) ; /* discard what was allocated */ + else + qs->len = 0 ; + + return NULL ; } ; /*============================================================================== @@ -191,37 +263,249 @@ qs_vprintf(qstring qs, const char *format, va_list args) /*------------------------------------------------------------------------------ * Set qstring to be copy of the given string. * + * Allocates a qstring, if required. + * * Sets qs->len to the length of the string (excluding trailing '\0') * - * NB: if stc == NULL, sets qstring to be zero length string. + * NB: if src == NULL, sets qstring to be zero length string. + * + * Returns: address of the qstring copied to. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. */ -extern size_t +extern qstring qs_set(qstring qs, const char* src) { - qs_set_len(qs, (src != NULL) ? strlen(src) : 0) ; + qs = qs_set_len(qs, (src != NULL) ? strlen(src) : 0) ; if (qs->len != 0) memcpy(qs->body, src, qs->len + 1) ; else *((char*)qs->body) = '\0' ; - return qs->len ; + return qs ; } ; /*------------------------------------------------------------------------------ * Set qstring to be leading 'n' bytes of given string. * - * NB: src string MUST be at least that long. + * Allocates qstring if required. + * + * Inserts '\0' terminator after the 'n' bytes copied. + * + * Returns: address of the qstring copied to. * - * NB: src may not be NULL unless len == 0. + * NB: src string MUST be at least 'n' bytes long. + * + * NB: src may not be NULL unless n == 0. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. */ -extern size_t +extern qstring qs_set_n(qstring qs, const char* src, size_t n) { - qs_need(qs, n) ; /* sets qs->len */ + qs = qs_set_len(qs, n) ; /* ensures have body > n */ if (n != 0) memcpy(qs->body, src, n) ; *((char*)qs->body + n) = '\0' ; - return n ; + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Append given string to a qstring. + * + * Allocates a qstring, if required. + * + * Sets qs->len to the length of the result (excluding trailing '\0') + * + * NB: if src == NULL, appends nothing -- but result will be '\0' terminated. + * + * Returns: address of the qstring copied to. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring qs_append(qstring qs, const char* src) +{ + size_t n ; + char* ep ; + + n = (src != NULL) ? strlen(src) : 0 ; + + qs = qs_add_len(qs, n, &ep) ; + ep = (char*)qs->body + qs->len ; + + if (n != 0) + memcpy(ep - n, src, n + 1) ; + else + *ep = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be leading 'n' bytes of given string. + * + * Allocates qstring if required. + * + * Returns: address of the qstring copied to. + * + * NB: src string MUST be at least 'n' bytes long. + * + * NB: src may not be NULL unless n == 0. + * + * NB: if n == 0, appends nothing -- but result will be '\0' terminated. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_append_n(qstring qs, const char* src, size_t n) +{ + char* ep ; + + qs = qs_add_len(qs, n, &ep) ; + + if (n != 0) + memcpy(ep - n, src, n) ; + + *ep = '\0' ; + + return qs ; +} ; + +/*------------------------------------------------------------------------------ + * Copy one qstring to another + * + * If both are NULL, returns NULL. + * + * Otherwise if dst is NULL, creates a new qstring. + * + * Sets dst: body = copy of src->len bytes of src->body -- '\0' terminated. + * cp = src->cp + * len = src->len + * + * Where a NULL src has zero cp and len. + * + * If not NULL, the destination is guaranteed to have a body, and that will be + * '\0' terminated. + * + * Returns: the destination qstring + * + * NB: if copying to a dummy qstring, the old body is simply discarded. + */ +extern qstring +qs_copy(qstring dst, qstring src) +{ + size_t n ; + + if (src == NULL) + { + if (dst == NULL) + return dst ; + + n = 0 ; + dst->cp = 0 ; + } + else + { + if (dst == NULL) + dst = qs_new() ; + + n = src->len ; + dst->cp = src->cp ; + } ; + + qs_set_len(dst, n) ; + + if (n > 0) + memcpy(dst->body, src->body, n) ; + + *((char*)dst->body + n) = '\0' ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Compare significant parts of two qstrings. + * + * By significant, mean excluding leading/trailing isspace() and treating + * multiple isspace() as single isspace(). + * + * Compares the 'len' portions of the strings. + * + * If either is NULL, it is deemed to be an empty string. + * + * Returns: -1 => a < b + * 0 => a == b + * +1 => a > b + */ +extern int +qs_cmp_sig(qstring a, qstring b) +{ + const unsigned char* p_a ; + const unsigned char* e_a ; + const unsigned char* p_b ; + const unsigned char* e_b ; + + /* Set up pointers and dispense with leading and trailing isspace() + * + * Dummy up if NULL + */ + if (a != NULL) + { + p_a = a->body ; + e_a = p_a + a->len ; + + while ((p_a < e_a) && isspace(*p_a)) + ++p_a ; + while ((p_a < e_a) && isspace(*(e_a - 1))) + --e_a ; + } + else + { + p_a = NULL ; + e_a = NULL ; + } + + if (b != NULL) + { + p_b = b->body ; + e_b = p_b + b->len ; + + while ((p_b < e_b) && isspace(*p_b)) + ++p_b ; + while ((p_b < e_b) && isspace(*(e_b - 1))) + --e_b ; + } + else + { + p_b = NULL ; + e_b = NULL ; + } ; + + /* Now set about finding the first difference */ + while ((p_a != e_a) && (p_b != e_b)) + { + if (isspace(*p_a) && isspace(*p_b)) + { + do { ++p_a ; } while isspace(*p_a) ; + do { ++p_b ; } while isspace(*p_b) ; + } ; + + if (*p_a != *p_b) + return (*p_a < *p_b) ? -1 : +1 ; + + ++p_a ; + ++p_b ; + } ; + + /* No difference before ran out of one or both */ + if (p_a != e_a) + return +1 ; + else if (p_b != e_b) + return -1 ; + else + return 0 ; } ; diff --git a/lib/qstring.h b/lib/qstring.h index 1841657e..0597eda8 100644 --- a/lib/qstring.h +++ b/lib/qstring.h @@ -27,6 +27,8 @@ #include <stddef.h> #include <stdint.h> +#include "memory.h" + #ifndef Inline #define Inline static inline #endif @@ -39,10 +41,14 @@ #endif /* __GNUC__ */ /*============================================================================== - * These "qstrings" address the ... - * + * These "qstrings" address address the lack of a flexible length string in 'C'. * + * This is not a general purpose strings module, but provides a limited number + * of useful string operations such that the caller does not need to worry + * about the length of the string, and allocating space and so on. * + * The caller does, however, have to explicitly release the contents of a + * qstring when it is done with. */ typedef struct qstring qstring_t ; @@ -50,7 +56,13 @@ typedef struct qstring* qstring ; struct qstring { - void* body ; + union + { + void* body ; + const void* const_body ; + char* char_body ; + unsigned char* uchar_body ; + } ; size_t size ; size_t len ; @@ -115,117 +127,161 @@ qs_ep_byte(qstring qs) * 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) ; +extern qstring qs_init_new(qstring qs, size_t len) ; +extern qstring qs_make_to_length(qstring qs, size_t len) ; +extern void qs_free_body(qstring qs) ; +extern qstring qs_reset(qstring qs, int free_structure) ; #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) ; +Inline qstring qs_new(void) ; +Inline qstring qs_dummy(qstring qs, const char* src, int pos) ; -extern int -qs_vprintf(qstring qs, const char *format, va_list args) ; +extern qstring qs_printf(qstring qs, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; -extern size_t -qs_set(qstring qs, const char* s) ; +extern qstring qs_set(qstring qs, const char* src) ; +extern qstring qs_set_n(qstring qs, const char* src, size_t n) ; -extern size_t -qs_set_n(qstring qs, const char* s, size_t len) ; +extern qstring qs_append(qstring qs, const char* src) ; +extern qstring qs_append_n(qstring qs, const char* src, size_t n) ; -Inline size_t -qs_need(qstring qs, size_t len) ; +Inline qstring qs_need(qstring qs, size_t len) ; +Inline qstring qs_set_len(qstring qs, size_t len) ; +extern qstring qs_add_len(qstring qs, size_t n, char** p_ep) ; +Inline void qs_clear(qstring qs) ; +Inline size_t qs_len(qstring qs) ; +Inline size_t qs_size(qstring qs) ; +Inline void* qs_term(qstring qs) ; -Inline size_t -qs_set_len(qstring qs, size_t len) ; +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) ; -Inline void -qs_set_empty(qstring qs) ; +extern qstring qs_copy(qstring dst, qstring src) ; +extern int qs_cmp_sig(qstring a, qstring b) ; -Inline size_t -qs_len(qstring qs) ; - -Inline size_t -qs_size(qstring qs) ; - -Inline void* -qs_term(qstring qs) ; +/*============================================================================== + * The Inline functions. + */ -Inline size_t -qs_insert(qstring qs, const void* src, size_t n) ; +/*------------------------------------------------------------------------------ + * Make a brand new, completely empty qstring + */ +Inline qstring +qs_new(void) +{ + /* Zeroising has set: + * + * body = NULL -- no body + * size = 0 -- no body + * + * len = 0 + * cp = 0 + * + * Nothing more to do unless initial size != 0 + */ + return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; +} ; -Inline void -qs_replace(qstring qs, const void* src, size_t n) ; +/*------------------------------------------------------------------------------ + * Construct a "dummy" qstring from the given string. + * + * Allocates a qstring if required. + * + * This sets: body = the src + * len = strlen(src) (0 if src is NULL) + * cp = 0 if 'pos' is zero + * len otherwise + * size = 0 + * + * The zero size means that the qstring handling will not attempt to free + * the body, nor will it write to it... Operations which require the qstring + * to have a size will allocate a new body, and discard this one. + * + * Returns: the address of the dummy qstring. + */ +Inline qstring +qs_dummy(qstring qs, const char* src, int pos) +{ + if (qs == NULL) + qs = qs_new() ; -Inline size_t -qs_delete(qstring qs, size_t n) ; + qs->const_body = src ; + qs->len = (src != NULL) ? strlen(src) : 0 ; + qs->cp = (pos == 0) ? 0 : qs->len ; + qs->size = 0 ; -/*============================================================================== - * The Inline functions. - */ + return qs ; +} /*------------------------------------------------------------------------------ * 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') + * Allocates the qstring, if required. + * + * Returns: address of qstring * * NB: asking for 0 bytes will cause a body to be allocated, ready for any * '\0' ! * - * NB: has no effect on 'cp' or 'len'. + * NB: has no effect on 'cp' or 'len'. (Will be zero if new qstring allocated.) */ -Inline size_t +Inline qstring qs_need(qstring qs, size_t len) { - if (len < qs->size) - { - assert(qs->body != NULL) ; - return qs->size ; - } - else - return qs_alloc(qs, len) ; + if ((qs == NULL) || (len >= qs->size)) + return qs_make_to_length(qs, len) ; + + assert(qs->body != NULL) ; + return qs ; } ; /*------------------------------------------------------------------------------ * Set 'len' -- allocate or extend body as required. * - * Returns: size of the qstring body - * (which includes the extra space allowed for '\0') + * Allocates the qstring, if required. * - * NB: asking for 0 bytes will cause a body to be allocated, ready for any + * Returns: address of qstring + * + * NB: setting len == 0 bytes will cause a body to be allocated, ready for any * '\0' ! * * NB: has no effect on 'cp' -- even if 'cp' > 'len'. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. */ -Inline size_t +Inline qstring qs_set_len(qstring qs, size_t len) { + qs = qs_need(qs, len) ; qs->len = len ; - return qs_need(qs, len) ; + return qs ; } ; /*------------------------------------------------------------------------------ * Reset contents of qstring. * + * Does nothing if qstring is NULL + * * Sets 'cp' = 'len' = 0. Sets first byte of body (if any) to NULL. + * + * For "dummy" qstring, discards the body. */ Inline void -qs_set_empty(qstring qs) +qs_clear(qstring qs) { - qs->len = 0 ; - qs->cp = 0 ; - if (qs->body != NULL) - *((char*)qs->body) = '\0' ; + if (qs != NULL) + { + qs->len = 0 ; + qs->cp = 0 ; + if (qs->size > 0) + *((char*)qs->body) = '\0' ; + else + qs->body = NULL ; + } ; } ; /*------------------------------------------------------------------------------ @@ -238,31 +294,63 @@ qs_set_empty(qstring qs) Inline size_t qs_len(qstring qs) { - return qs->len = (qs->body != NULL) ? strlen(qs_chars(qs)) : 0 ; + return (qs != NULL) ? (qs->len = (qs->body != NULL) ? strlen(qs_chars(qs)) + : 0) + : 0 ; } ; /*------------------------------------------------------------------------------ * Get size of qstring body. * - * NB: if no body has been allocated, size = 0 + * NB: if no body has been allocated, size == 0 + * if qstring is NULL, size == 0 + * + * NB: if this is a "dummy" qstring, size == 0. */ Inline size_t qs_size(qstring qs) { - return qs->size ; + return (qs != NULL) ? qs->size : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Get address of current end of qstring body -- ie byte at 'len'. + * + * NB: allocates body if required. + * + * There will be space for '\0' after 'len', so the address returned + * is within the real body of the string. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. + * + * NB: address of qstring may NOT be NULL. + */ +Inline void* +qs_end(qstring qs) +{ + if (qs->len >= qs->size) + qs_make_to_length(qs, qs->len) ; /* allows for trailing '\0' */ + + return (char*)qs->body + qs->len ; } ; /*------------------------------------------------------------------------------ * Set '\0' at qs->len -- allocate or extend body as required. * - * Returns address of body. + * Returns address of body -- NULL if the qstring is NULL + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. */ Inline void* qs_term(qstring qs) { size_t len ; + + if (qs == NULL) + return NULL ; + if ((len = qs->len) >= qs->size) - qs_alloc(qs, len) ; + qs_make_to_length(qs, len) ; *qs_chars_at(qs, len) = '\0' ; @@ -276,10 +364,14 @@ qs_term(qstring qs) * * Returns: number of bytes beyond 'cp' that were moved before insert. * + * NB: qstring MUST NOT be NULL + * * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce * one or more undefined bytes. * * NB: the string is NOT re-terminated. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. */ Inline size_t qs_insert(qstring qs, const void* src, size_t n) @@ -308,15 +400,19 @@ qs_insert(qstring qs, const void* src, size_t n) * * May increase 'len'. but does not affect 'cp'. * + * NB: qstring MUST NOT be NULL + * * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce * one or more undefined bytes. * * NB: the string is NOT re-terminated. + * + * NB: if this is a "dummy" qstring, a copy is made of the original body. */ Inline void qs_replace(qstring qs, const void* src, size_t n) { - if (qs->len < qs->cp + n) + if ((qs->len < qs->cp + n) || (qs->size == 0)) qs_set_len(qs, qs->cp + n) ; /* set len and ensure have space */ if (n > 0) @@ -330,6 +426,8 @@ qs_replace(qstring qs, const void* src, size_t n) * * Returns: number of bytes beyond 'cp' that were moved before insert. * + * NB: qstring MUST NOT be NULL + * * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce * one or more undefined bytes. * @@ -341,6 +439,10 @@ qs_delete(qstring qs, size_t n) size_t after ; char* p ; + /* Watch out for "dummy" */ + if (qs->size == 0) + qs_make_to_length(qs, qs->len) ; + /* If deleting up to or beyond len, then simply set len == cp */ if ((qs->cp + n) >= qs->len) { @@ -362,4 +464,5 @@ qs_delete(qstring qs, size_t n) return after ; } ; + #endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtimers.c b/lib/qtimers.c index 508fc7d7..8c08a6bc 100644 --- a/lib/qtimers.c +++ b/lib/qtimers.c @@ -81,8 +81,11 @@ enum { qdebug = * timer (which may, or may not, be the current qtimer time). * * During an action function timers may be set/unset, actions changed, and so - * on... there are no restrictions EXCEPT that the qtimer structure may NOT be - * freed. + * on... there are no restrictions EXCEPT that may NOT recurse into the + * dispatch function. + * + * If nothing is done with the time during the action function, the timer is + * implicitly unset when the action function returns. */ static int @@ -99,7 +102,8 @@ qtimer_cmp(qtimer* a, qtimer* b) /* the heap discipline */ * qtimer_pile handling */ -/* Initialise a timer pile -- allocating it if required. +/*------------------------------------------------------------------------------ + * Initialise a timer pile -- allocating it if required. * * Returns the qtimer_pile. */ @@ -114,6 +118,7 @@ qtimer_pile_init_new(qtimer_pile qtp) /* Zeroising has initialised: * * timers -- invalid heap -- need to properly initialise + * current = NULL -- no current timer */ /* (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining @@ -126,13 +131,14 @@ qtimer_pile_init_new(qtimer_pile qtp) return qtp ; } ; -/* Get the timer time for the first timer due to go off in the given pile. +/*------------------------------------------------------------------------------ + * Get the timer time for the first timer due to go off in the given pile. * * The caller must provide a maximum acceptable time. If the qtimer pile is * empty, or the top entry times out after the maximum time, then the maximum * is returned. */ -qtime_t +extern qtime_t qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) { qtime_t top_wait ; @@ -146,7 +152,8 @@ qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) return (top_wait < max_wait) ? top_wait : max_wait ; } ; -/* Dispatch the next timer whose time is <= the given "upto" time. +/*------------------------------------------------------------------------------ + * Dispatch the next timer whose time is <= the given "upto" time. * * The upto time must be a qtimer time (!) -- see qtimer_time_now(). * @@ -155,8 +162,10 @@ qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) * * Returns true <=> dispatched a timer, and there may be more to do. * false <=> nothing to do (and nothing done). + * + * NB: it is a sad, very sad, mistake to recurse into this ! */ -int +extern bool qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) { qtimer qtr ; @@ -165,23 +174,26 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) qtimer_pile_verify(qtp) ; qtr = heap_top_item(&qtp->timers) ; - if ((qtr != NULL) && (qtr->time <= upto)) - { - passert(qtp == qtr->pile); - qtr->state = qtr_state_unset_pending ; - qtr->action(qtr, qtr->timer_info, upto) ; + if ((qtr == NULL) || (qtr->time > upto)) + return 0 ; - if (qtr->state == qtr_state_unset_pending) - qtimer_unset(qtr) ; + passert((qtp == qtr->pile) && (qtr->active)) ; - return 1 ; - } + qtp->implicit_unset = qtr ; /* Timer must be unset if is still here + when the action function returns */ + qtr->action(qtr, qtr->timer_info, upto) ; + + if (qtp->implicit_unset == qtr) + qtimer_unset(qtr) ; else - return 0 ; + assert(qtp->implicit_unset == NULL) ; /* check for tidy-ness */ + + return 1 ; } ; -/* Ream out (another) item from qtimer_pile. +/*------------------------------------------------------------------------------ + * Ream out (another) item from qtimer_pile. * * If pile is empty, release the qtimer_pile structure, if required. * @@ -207,7 +219,7 @@ qtimer_pile_ream(qtimer_pile qtp, int free_structure) qtr = heap_ream_keep(&qtp->timers) ; /* ream, keeping the heap structure */ if (qtr != NULL) - qtr->state = qtr_state_inactive ; /* has been removed from pile */ + qtr->active = false ; /* has been removed from pile */ else if (free_structure) /* pile is empty, may now free it */ XFREE(MTYPE_QTIMER_PILE, qtp) ; @@ -219,7 +231,8 @@ qtimer_pile_ream(qtimer_pile qtp, int free_structure) * qtimer handling */ -/* Initialise qtimer structure -- allocating one if required. +/*------------------------------------------------------------------------------ + * Initialise qtimer structure -- allocating one if required. * * Associates qtimer with the given pile of timers, and sets up the action and * the timer_info. @@ -242,7 +255,7 @@ qtimer_init_new(qtimer qtr, qtimer_pile qtp, * pile -- NULL -- not in any pile (yet) * backlink -- unset * - * state -- not active + * active -- false * * time -- unset * action -- NULL -- no action set (yet) @@ -251,8 +264,6 @@ qtimer_init_new(qtimer qtr, qtimer_pile qtp, * interval -- unset */ - confirm(qtr_state_inactive == 0) ; - qtr->pile = qtp ; qtr->action = action ; qtr->timer_info = timer_info ; @@ -260,53 +271,54 @@ qtimer_init_new(qtimer qtr, qtimer_pile qtp, return qtr ; } ; -/* Free given timer. +/*------------------------------------------------------------------------------ + * Free given timer -- if any. * - * Unsets it first if it is active. + * Unsets it first if it is active or pending unset. * - * The timer MAY NOT be currently the subject of qtimer_pile_dispatch_next(). + * Returns: NULL */ -void +extern qtimer qtimer_free(qtimer qtr) { - assert(qtr->state != qtr_state_unset_pending) ; + /* Note that if is the current dispatched timer and an unset is still + * pending, then it must still be active. + */ + if (qtr != NULL) + { + if (qtr->active) + qtimer_unset(qtr) ; - if (qtr->state != qtr_state_inactive) - qtimer_unset(qtr) ; + XFREE(MTYPE_QTIMER, qtr) ; + } ; - XFREE(MTYPE_QTIMER, qtr) ; + return NULL ; } ; -/* Set pile in which given timer belongs. +/*------------------------------------------------------------------------------ + * Set pile in which given timer belongs. + * + * Does nothing if timer already belongs to the given pile. * - * Unsets the timer if active in another pile. - * (Does nothing if active in the "new" pile.) + * Unsets the timer if active in another pile, before reassigning it. */ -void +extern void qtimer_set_pile(qtimer qtr, qtimer_pile qtp) { - if (qtr_is_active(qtr) && (qtr->pile != qtp)) + if (qtr->pile == qtp) + return ; + + /* Note that if is the current dispatched timer and an unset is still + * pending, then it must still be active. + */ + if (qtr->active) qtimer_unset(qtr) ; + qtr->pile = qtp ; } -/* Set action for given timer. - */ -void -qtimer_set_action(qtimer qtr, qtimer_action* action) -{ - qtr->action = action ; -} ; - -/* Set timer_info for given timer. - */ -void -qtimer_set_info(qtimer qtr, void* timer_info) -{ - qtr->timer_info = timer_info ; -} ; - -/* Set given timer. +/*------------------------------------------------------------------------------ + * Set given timer. * * Setting a -ve time => qtimer_unset. * @@ -319,7 +331,7 @@ qtimer_set_info(qtimer qtr, void* timer_info) * * It is an error to set a timer which has a NULL action. */ -void +extern void qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) { qtimer_pile qtp ; @@ -328,20 +340,30 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) return qtimer_unset(qtr) ; qtp = qtr->pile ; - dassert(qtp != NULL) ; + assert(qtp != NULL) ; + if (qdebug) qtimer_pile_verify(qtp) ; qtr->time = when ; - if (qtr_is_active(qtr)) - heap_update_item(&qtp->timers, qtr) ; /* update in heap */ + if (qtr->active) + { + /* Is active, so update the timer in the pile. */ + heap_update_item(&qtp->timers, qtr) ; + + if (qtr == qtp->implicit_unset) + qtp->implicit_unset = NULL ; /* no unset required, now */ + } else - heap_push_item(&qtp->timers, qtr) ; /* add to heap */ + { + /* Is not active, so insert the timer into the pile. */ + heap_push_item(&qtp->timers, qtr) ; - assert(qtp == qtr->pile); + assert(qtr != qtp->implicit_unset) ; /* because it's not active */ - qtr->state = qtr_state_active ; /* overrides any unset pending */ + qtr->active = true ; + } ; if (action != NULL) qtr->action = action ; @@ -352,28 +374,35 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) qtimer_pile_verify(qtp) ; } ; -/* Unset given timer +/*------------------------------------------------------------------------------ + * Unset given timer * * If the timer is active, removes from pile and sets inactive. */ -void +extern void qtimer_unset(qtimer qtr) { - if (qtr_is_active(qtr)) - { - qtimer_pile qtp = qtr->pile ; - dassert(qtp != NULL) ; + qtimer_pile qtp = qtr->pile ; - if (qdebug) - qtimer_pile_verify(qtp) ; + assert(qtp != NULL) ; + + if (qdebug) + qtimer_pile_verify(qtp) ; + + if (qtr->active) + { + if (qtr == qtp->implicit_unset) + qtp->implicit_unset = NULL ; /* no unset required, now */ heap_delete_item(&qtp->timers, qtr) ; if (qdebug) qtimer_pile_verify(qtp) ; - qtr->state = qtr_state_inactive ; /* overrides any unset pending */ - } ; + qtr->active = false ; + } + else + assert(qtr != qtp->implicit_unset) ; } ; /*============================================================================== @@ -387,6 +416,9 @@ qtimer_pile_verify(qtimer_pile qtp) vector_index i ; vector_index e ; qtimer qtr ; + bool seen ; + + assert(qtp != NULL) ; /* (The typedef is required to stop Eclipse (3.4.2 with CDT 5.0) whining * about first argument of offsetof().) @@ -402,9 +434,17 @@ qtimer_pile_verify(qtimer_pile qtp) for (i = 0 ; i < e ; ++i) { qtr = vector_get_item(v, i) ; + assert(qtr != NULL) ; + + if (qtr == qtp->implicit_unset) + seen = 1 ; + + assert(qtr->active) ; assert(qtr->pile == qtp) ; assert(qtr->backlink == i) ; assert(qtr->action != NULL) ; } ; + + assert(seen || (qtp->implicit_unset == NULL)) ; } ; diff --git a/lib/qtimers.h b/lib/qtimers.h index 0bc3d7a1..5beb931b 100644 --- a/lib/qtimers.h +++ b/lib/qtimers.h @@ -22,6 +22,8 @@ #ifndef _ZEBRA_QTIMERS_H #define _ZEBRA_QTIMERS_H +#include <stdbool.h> + #include "zassert.h" #include "qtime.h" #include "heap.h" @@ -46,22 +48,12 @@ typedef struct qtimer_pile* qtimer_pile ; typedef void (qtimer_action)(qtimer qtr, void* timer_info, qtime_mono_t when) ; -enum qtimer_state { - qtr_state_inactive = 0, - qtr_state_active = 1, /* timer is active in its pile */ - qtr_state_unset_pending = 3 /* timer is active, but unset is pending */ -} ; - -#define qtr_is_active(qtr) ((qtr)->state != qtr_state_inactive) - -typedef enum qtimer_state qtimer_state_t ; - struct qtimer { qtimer_pile pile ; /* pile currently allocated to */ heap_backlink_t backlink ; - qtimer_state_t state ; + bool active ; /* true => in the pile */ qtime_mono_t time ; /* current time to trigger action */ qtimer_action* action ; @@ -72,74 +64,50 @@ struct qtimer struct qtimer_pile { - struct heap timers ; + struct heap timers ; + + qtimer implicit_unset ; /* used during dispatch */ } ; /*============================================================================== * Functions */ -qtimer_pile -qtimer_pile_init_new(qtimer_pile qtp) ; - -int -qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ; - -qtime_t -qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) ; - -qtimer -qtimer_pile_ream(qtimer_pile qtp, int free_structure) ; +extern qtimer_pile qtimer_pile_init_new(qtimer_pile qtp) ; +extern bool qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ; +extern qtime_t qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) ; +extern qtimer qtimer_pile_ream(qtimer_pile qtp, int free_structure) ; /* Ream out qtimer pile and free the qtimer structure. */ #define qtimer_pile_ream_free(qtp) qtimer_pile_ream(qtp, 1) /* Ream out qtimer pile but keep the qtimer structure. */ #define qtimer_pile_ream_keep(qtp) qtimer_pile_ream(qtp, 0) -qtimer -qtimer_init_new(qtimer qtr, qtimer_pile qtp, +extern qtimer qtimer_init_new(qtimer qtr, qtimer_pile qtp, qtimer_action* action, void* timer_info) ; -void -qtimer_set_pile(qtimer qtr, qtimer_pile qtp) ; +extern void qtimer_set_pile(qtimer qtr, qtimer_pile qtp) ; +Inline void qtimer_set_action(qtimer qtr, qtimer_action* action) ; +Inline void qtimer_set_info(qtimer qtr, void* timer_info) ; -void -qtimer_set_action(qtimer qtr, qtimer_action* action) ; +extern qtimer qtimer_free(qtimer qtr) ; +extern void qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) ; +extern void qtimer_unset(qtimer qtr) ; -void -qtimer_set_info(qtimer qtr, void* timer_info) ; +Inline void qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) ; +Inline qtime_mono_t qtimer_get(qtimer qtr) ; +Inline void qtimer_set_interval(qtimer qtr, qtime_t interval, + qtimer_action* action) ; +Inline void qtimer_add_interval(qtimer qtr, qtimer_action* action) ; -void -qtimer_free(qtimer qtr) ; - -void -qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) ; - -void -qtimer_unset(qtimer qtr) ; - -Inline void -qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) ; - -Inline qtime_mono_t -qtimer_get(qtimer qtr) ; - -Inline void -qtimer_set_interval(qtimer qtr, qtime_t interval, qtimer_action* action) ; - -Inline void -qtimer_add_interval(qtimer qtr, qtimer_action* action) ; - -Inline qtime_t -qtimer_get_interval(qtimer qtr) ; - -void -qtimer_pile_verify(qtimer_pile qtp) ; +Inline qtime_t qtimer_get_interval(qtimer qtr) ; +extern void qtimer_pile_verify(qtimer_pile qtp) ; /*============================================================================== * Inline functions */ -/* Set given timer to given time later than *its* current time. +/*------------------------------------------------------------------------------ + * Set given timer to given time later than *its* current time. */ Inline void qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) @@ -147,7 +115,8 @@ qtimer_add(qtimer qtr, qtime_t interval, qtimer_action* action) qtimer_set(qtr, qtimer_get(qtr) + interval, action); } ; -/* Get the given timer's time. +/*------------------------------------------------------------------------------ + * Get the given timer's time. */ Inline qtime_mono_t qtimer_get(qtimer qtr) @@ -155,6 +124,27 @@ qtimer_get(qtimer qtr) return qtr->time ; } ; +/*------------------------------------------------------------------------------ + * Set action for given timer -- setting a NULL action unsets the timer. + */ +Inline void +qtimer_set_action(qtimer qtr, qtimer_action* action) +{ + if (action == NULL) + qtimer_unset(qtr) ; + qtr->action = action ; +} ; + +/*------------------------------------------------------------------------------ + * Set timer_info for given timer. + */ +Inline void +qtimer_set_info(qtimer qtr, void* timer_info) +{ + qtr->timer_info = timer_info ; +} ; + + /* Interval handling ---------------------------------------------------------*/ /* Set the interval field diff --git a/lib/sockopt.c b/lib/sockopt.c index 3e4580ac..ad4af9af 100644 --- a/lib/sockopt.c +++ b/lib/sockopt.c @@ -25,44 +25,56 @@ #include "sockunion.h" int -setsockopt_so_recvbuf (int sock, int size) +setsockopt_so_recvbuf (int sock_fd, int size) { int ret; - if ( (ret = setsockopt (sock, SOL_SOCKET, SO_RCVBUF, (char *) - &size, sizeof (int))) < 0) - zlog_err ("fd %d: can't setsockopt SO_RCVBUF to %d: %s", - sock,size,safe_strerror(errno)); + ret = setsockopt (sock_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) ; + if (ret < 0) + { + int err = errno ; + zlog_err ("socket %d: cannot setsockopt SO_RCVBUF to %d: %s", + sock_fd, size, safe_strerror(err)) ; + errno = err ; + } ; return ret; } int -setsockopt_so_sendbuf (const int sock, int size) +setsockopt_so_sendbuf (const int sock_fd, int size) { - int ret = setsockopt (sock, SOL_SOCKET, SO_SNDBUF, - (char *)&size, sizeof (int)); + int ret ; + + ret = setsockopt (sock_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); if (ret < 0) - zlog_err ("fd %d: can't setsockopt SO_SNDBUF to %d: %s", - sock, size, safe_strerror (errno)); + { + int err = errno ; + zlog_err ("socket %d: cannot setsockopt SO_SNDBUF to %d: %s", + sock_fd, size, safe_strerror (err)); + errno = err ; + } ; return ret; } int -getsockopt_so_sendbuf (const int sock) +getsockopt_so_sendbuf (const int sock_fd) { u_int32_t optval; socklen_t optlen = sizeof (optval); - int ret = getsockopt (sock, SOL_SOCKET, SO_SNDBUF, - (char *)&optval, &optlen); + + int ret = getsockopt (sock_fd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen); if (ret < 0) { - zlog_err ("fd %d: can't getsockopt SO_SNDBUF: %d (%s)", - sock, errno, safe_strerror (errno)); + int err = errno ; + zlog_err ("socket %d: cannot getsockopt SO_SNDBUF: %s", + sock_fd, safe_strerror (err)); + errno = err ; return ret; } + return optval; } @@ -84,89 +96,125 @@ getsockopt_cmsg_data (struct msghdr *msgh, int level, int type) #ifdef HAVE_IPV6 /* Set IPv6 packet info to the socket. */ int -setsockopt_ipv6_pktinfo (int sock, int val) +setsockopt_ipv6_pktinfo (int sock_fd, int val) { int ret; #ifdef IPV6_RECVPKTINFO /*2292bis-01*/ - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_RECVPKTINFO : %s", safe_strerror (errno)); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_RECVPKTINFO: %s", safe_strerror (err)); + errno = err ; + } ; #else /*RFC2292*/ - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_PKTINFO : %s", safe_strerror (errno)); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_PKTINFO: %s", safe_strerror (err)); + errno = err ; + } ; #endif /* INIA_IPV6 */ return ret; } /* Set multicast hops val to the socket. */ int -setsockopt_ipv6_checksum (int sock, int val) +setsockopt_ipv6_checksum (int sock_fd, int val) { int ret; #ifdef GNU_LINUX - ret = setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); #else - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, sizeof(val)); #endif /* GNU_LINUX */ if (ret < 0) - zlog_warn ("can't setsockopt IPV6_CHECKSUM"); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_CHECKSUM: %s", safe_strerror (err)); + errno = err ; + } ; return ret; } /* Set multicast hops val to the socket. */ int -setsockopt_ipv6_multicast_hops (int sock, int val) +setsockopt_ipv6_multicast_hops (int sock_fd, int val) { int ret; - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_MULTICAST_HOPS"); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_MULTICAST_HOPS: %s", + safe_strerror (err)); + errno = err ; + } ; return ret; } /* Set multicast hops val to the socket. */ int -setsockopt_ipv6_unicast_hops (int sock, int val) +setsockopt_ipv6_unicast_hops (int sock_fd, int val) { int ret; - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)); + ret = setsockopt(sock_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_UNICAST_HOPS"); + { + int err = errno ; + zlog_warn("cannot setsockopt IPV6_UNICAST_HOPS: %s", safe_strerror (err)); + errno = err ; + } ; return ret; } int -setsockopt_ipv6_hoplimit (int sock, int val) +setsockopt_ipv6_hoplimit (int sock_fd, int val) { int ret; #ifdef IPV6_RECVHOPLIMIT /*2292bis-01*/ - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val)); + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, + sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_RECVHOPLIMIT"); + { + int err = errno ; + zlog_warn("cannot setsockopt IPV6_RECVHOPLIMIT: %s", safe_strerror (err)); + errno = err ; + } ; #else /*RFC2292*/ - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val)); + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_HOPLIMIT"); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_HOPLIMIT: %s", safe_strerror (err)); + errno = err ; + } ; #endif return ret; } /* Set multicast loop zero to the socket. */ int -setsockopt_ipv6_multicast_loop (int sock, int val) +setsockopt_ipv6_multicast_loop (int sock_fd, int val) { int ret; - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, - sizeof (val)); + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof (val)); if (ret < 0) - zlog_warn ("can't setsockopt IPV6_MULTICAST_LOOP"); + { + int err = errno ; + zlog_warn ("cannot setsockopt IPV6_MULTICAST_LOOP: %s", + safe_strerror (err)); + errno = err ; + } ; return ret; } @@ -204,12 +252,14 @@ getsockopt_ipv6_ifindex (struct msghdr *msgh) * allow leaves, or implicitly leave all groups joined to down interfaces. */ int -setsockopt_multicast_ipv4(int sock, - int optname, - struct in_addr if_addr /* required */, - unsigned int mcast_addr, - unsigned int ifindex /* optional: if non-zero, may be - used instead of if_addr */) +setsockopt_multicast_ipv4(int sock_fd, + int optname, + struct in_addr if_addr /* required */, + unsigned int mcast_addr, + unsigned int ifindex /* optional: if non-zero, + may be used instead of + if_addr */ + ) { #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX @@ -232,31 +282,28 @@ setsockopt_multicast_ipv4(int sock, else mreqn.imr_address = if_addr; - ret = setsockopt(sock, IPPROTO_IP, optname, - (void *)&mreqn, sizeof(mreqn)); + ret = setsockopt(sock_fd, IPPROTO_IP, optname, &mreqn, sizeof(mreqn)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back up */ char buf[2][INET_ADDRSTRLEN]; zlog_info("setsockopt_multicast_ipv4 attempting to drop and " "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)", - sock, + sock_fd, inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &mreqn.imr_multiaddr, buf[1], sizeof(buf[1])), ifindex); - setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, - (void *)&mreqn, sizeof(mreqn)); - ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, - (void *)&mreqn, sizeof(mreqn)); + setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mreqn, sizeof(mreqn)); + ret = setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreqn, sizeof(mreqn)); } return ret; - break; default: /* Can out and give an understandable error */ errno = EINVAL; return -1; - break; } /* Example defines for another OS, boilerplate off other code in this @@ -281,7 +328,7 @@ setsockopt_multicast_ipv4(int sock, switch (optname) { case IP_MULTICAST_IF: - return setsockopt (sock, IPPROTO_IP, optname, (void *)&m, sizeof(m)); + return setsockopt (sock_fd, IPPROTO_IP, optname, (void *)&m, sizeof(m)); break; case IP_ADD_MEMBERSHIP: @@ -290,48 +337,56 @@ setsockopt_multicast_ipv4(int sock, mreq.imr_multiaddr.s_addr = mcast_addr; mreq.imr_interface = m; - ret = setsockopt (sock, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq)); + ret = setsockopt (sock_fd, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back up */ char buf[2][INET_ADDRSTRLEN]; zlog_info("setsockopt_multicast_ipv4 attempting to drop and " "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)", - sock, + sock_fd, inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &mreq.imr_multiaddr, buf[1], sizeof(buf[1])), ifindex); - setsockopt (sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, - (void *)&mreq, sizeof(mreq)); - ret = setsockopt (sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, - (void *)&mreq, sizeof(mreq)); + setsockopt (sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)); + ret = setsockopt (sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); } return ret; - break; default: /* Can out and give an understandable error */ errno = EINVAL; return -1; - break; } #endif /* #if OS_TYPE */ } static int -setsockopt_ipv4_ifindex (int sock, int val) +setsockopt_ipv4_ifindex (int sock_fd, int val) { int ret; #if defined (IP_PKTINFO) - if ((ret = setsockopt (sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof (val))) < 0) - zlog_warn ("Can't set IP_PKTINFO option for fd %d to %d: %s", - sock,val,safe_strerror(errno)); + ret = setsockopt (sock_fd, IPPROTO_IP, IP_PKTINFO, &val, sizeof (val)) ; + if (ret < 0) + { + int err = errno ; + zlog_warn ("Can't set IP_PKTINFO option for fd %d to %d: %s", + sock_fd, val, safe_strerror(err)); + errno = err ; + } ; #elif defined (IP_RECVIF) - if ((ret = setsockopt (sock, IPPROTO_IP, IP_RECVIF, &val, sizeof (val))) < 0) - zlog_warn ("Can't set IP_RECVIF option for fd %d to %d: %s", - sock,val,safe_strerror(errno)); + ret = setsockopt (sock_fd, IPPROTO_IP, IP_RECVIF, &val, sizeof (val)) ; + if (ret < 0) + { + int err = errno ; + zlog_warn ("Can't set IP_RECVIF option for fd %d to %d: %s", + sock_fd, val, safe_strerror(err)); + errno = err ; + } ; #else #warning "Neither IP_PKTINFO nor IP_RECVIF is available." #warning "Will not be able to receive link info." @@ -343,35 +398,42 @@ setsockopt_ipv4_ifindex (int sock, int val) } int -setsockopt_ipv4_tos(int sock, int tos) +setsockopt_ipv4_tos(int sock_fd, int tos) { int ret; - ret = setsockopt (sock, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); + ret = setsockopt (sock_fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); if (ret < 0) - zlog_warn ("Can't set IP_TOS option for fd %d to %#x: %s", - sock, tos, safe_strerror(errno)); + { + int err = errno ; + zlog_warn ("Can't set IP_TOS option for fd %d to %#x: %s", + sock_fd, tos, safe_strerror(err)); + errno = err ; + } ; return ret; } int -setsockopt_ifindex (int af, int sock, int val) +setsockopt_ifindex (int af, int sock_fd, int val) { int ret = -1; switch (af) { case AF_INET: - ret = setsockopt_ipv4_ifindex (sock, val); + ret = setsockopt_ipv4_ifindex (sock_fd, val); break; #ifdef HAVE_IPV6 case AF_INET6: - ret = setsockopt_ipv6_pktinfo (sock, val); + ret = setsockopt_ipv6_pktinfo (sock_fd, val); break; #endif default: zlog_warn ("setsockopt_ifindex: unknown address family %d", af); + ret = -1 ; + errno = EINVAL; + break ; } return ret; } @@ -498,17 +560,17 @@ sockopt_iphdrincl_swab_systoh (struct ip *iph) /*============================================================================== * Set TCP MD5 signature socket option. * - * Returns: 0 => OK - * errno => failed. + * Returns: >= 0 => OK + * < 0 => failed, see errno. * * NB: returns ENOSYS if TCP MD5 is not supported */ int -sockopt_tcp_signature (int sock, union sockunion *su, const char *password) +sockopt_tcp_signature (int sock_fd, union sockunion *su, const char *password) { +#if defined(HAVE_TCP_MD5_LINUX24) && defined(GNU_LINUX) int ret ; -#if defined(HAVE_TCP_MD5_LINUX24) && defined(GNU_LINUX) /* Support for the old Linux 2.4 TCP-MD5 patch, taken from Hasso Tepper's * version of the Quagga patch (based on work by Rick Payne, and Bruce * Simpson) @@ -529,9 +591,9 @@ sockopt_tcp_signature (int sock, union sockunion *su, const char *password) cmd.keylen = (password != NULL ? strlen (password) : 0); cmd.key = password; - ret = setsockopt (sock, IPPROTO_TCP, TCP_MD5_AUTH, &cmd, sizeof cmd) ; + ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_MD5_AUTH, &cmd, sizeof cmd) ; - return (ret >= 0) ? 0 : errno ; + return (ret >= 0) ? 0 : -1 ; #elif HAVE_DECL_TCP_MD5SIG #ifndef GNU_LINUX @@ -545,11 +607,13 @@ sockopt_tcp_signature (int sock, union sockunion *su, const char *password) struct tcp_md5sig md5sig ; union sockunion *su2 ; union sockunion susock ; + int ret, err ; /* Figure out whether the socket and the sockunion are the same family.. * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think.. */ - if ((ret = sockunion_getsockname(sock, &susock)) != 0) + ret = sockunion_getsockname(sock_fd, &susock) ; + if (ret < 0) return ret ; if (susock.sa.sa_family == su->sa.sa_family) @@ -564,7 +628,7 @@ sockopt_tcp_signature (int sock, union sockunion *su, const char *password) #ifdef HAVE_IPV6 /* If this does not work, then all users of this sockopt will need to - * differentiate between IPv4 and IPv6, and keep seperate sockets for + * differentiate between IPv4 and IPv6, and keep separate sockets for * each. * * Sadly, it doesn't seem to work at present. It's unknown whether @@ -590,65 +654,84 @@ sockopt_tcp_signature (int sock, union sockunion *su, const char *password) #endif /* GNU_LINUX */ - ret = setsockopt(sock, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof md5sig) ; + err = 0 ; + ret = setsockopt(sock_fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) ; if (ret < 0) { - ret = errno ; + err = errno ; /* ENOENT is harmless. It is returned when we clear a password for which one was not previously set. */ - if (ret == ENOENT) - ret = 0 ; + if (err == ENOENT) + err = 0 ; else - zlog_err ("sockopt_tcp_signature: setsockopt(%d): %s", - sock, safe_strerror(ret)); + { + zlog_err ("sockopt_tcp_signature: setsockopt(%d): %s", + sock_fd, safe_strerror(err)); + errno = err ; + } ; } - else - ret = 0 ; /* no error */ + + return (err == 0) ? 0 : -1 ; #else /* HAVE_TCP_MD5SIG */ - ret = ENOSYS ; /* TCP MD5 is not supported */ + errno = ENOSYS ; /* TCP MD5 is not supported */ + return -1 ; #endif /* !HAVE_TCP_MD5SIG */ - - return ret; } ; /*============================================================================== - * Set TTL for socket (only used in bgpd) + * Set TTL for socket * - * Returns: 0 : OK (so far so good) - * != 0 : error number (from errno or otherwise) + * Returns: >= 0 => OK + * < 0 => failed, see errno. */ - int -sockopt_ttl (int family, int sock, int ttl) +sockopt_ttl (int sock_fd, int ttl) { const char* msg ; int ret ; + int family ; ret = 0 ; + msg = NULL ; + family = sockunion_getsockfamily(sock_fd) ; + if (family < 0) + return -1 ; + + switch (family) + { + case AF_INET: #ifdef IP_TTL - if (family == AF_INET) - { - ret = setsockopt (sock, IPPROTO_IP, IP_TTL,(void*)&ttl, sizeof(int)); - msg = "can't set sockopt IP_TTL %d to socket %d" ; - } + ret = setsockopt (sock_fd, IPPROTO_IP, IP_TTL,(void*)&ttl, sizeof(int)); + msg = "IP_TTL" ; #endif /* IP_TTL */ + break ; + #ifdef HAVE_IPV6 - if (family == AF_INET6) - { - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - (void*)&ttl, sizeof(int)); - msg = "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d" ; - } -#endif /* HAVE_IPV6 */ + case AF_INET6: + ret = setsockopt (sock_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + (void*)&ttl, sizeof(int)); + msg = "IPV6_UNICAST_HOPS" ; + break ; +#endif - ret = (ret < 0) ? errno : 0 ; + default: /* ignore unknown family */ + ret = 0 ; + break ; + } ; if (ret != 0) - zlog (NULL, LOG_WARNING, msg, ttl, sock) ; - - return ret ; + { + int err = errno ; + zlog (NULL, LOG_WARNING, + "cannot set sockopt %s %d to socket %d: %s", msg, ttl, sock_fd, + safe_strerror(err)) ; + errno = err ; + return -1 ; + } ; + + return 0 ; } ; diff --git a/lib/sockopt.h b/lib/sockopt.h index cb05c6fb..ad86f053 100644 --- a/lib/sockopt.h +++ b/lib/sockopt.h @@ -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. */ #ifndef _ZEBRA_SOCKOPT_H @@ -24,9 +24,9 @@ #include "sockunion.h" -extern int setsockopt_so_recvbuf (int sock, int size); -extern int setsockopt_so_sendbuf (const int sock, int size); -extern int getsockopt_so_sendbuf (const int sock); +extern int setsockopt_so_recvbuf (int sock_fd, int size); +extern int setsockopt_so_sendbuf (const int sock_fd, int size); +extern int getsockopt_so_sendbuf (const int sock_fd); #ifdef HAVE_IPV6 extern int setsockopt_ipv6_pktinfo (int, int); @@ -82,25 +82,26 @@ extern int setsockopt_ipv6_multicast_loop (int, int); (((af) == AF_INET) : SOPT_SIZE_CMSG_IFINDEX_IPV4() \ ? SOPT_SIZE_CMSG_PKTINFO_IPV6()) -extern int setsockopt_multicast_ipv4(int sock, int optname, +extern int setsockopt_multicast_ipv4(int sock_fd, int optname, struct in_addr if_addr /* required: interface to join on */, unsigned int mcast_addr, unsigned int ifindex /* optional: if non-zero, may be used instead of if_addr */); -extern int setsockopt_ipv4_tos(int sock, int tos); +extern int setsockopt_ipv4_tos(int sock_fd, int tos); /* Ask for, and get, ifindex, by whatever method is supported. */ extern int setsockopt_ifindex (int, int, int); extern int getsockopt_ifindex (int, struct msghdr *); -/* swab the fields in iph between the host order and system order expected +/* swab the fields in iph between the host order and system order expected * for IP_HDRINCL. */ extern void sockopt_iphdrincl_swab_htosys (struct ip *iph); extern void sockopt_iphdrincl_swab_systoh (struct ip *iph); -extern int sockopt_tcp_signature(int sock, union sockunion *su, +extern int sockopt_ttl (int sock_fd, int ttl); +extern int sockopt_tcp_signature(int sock_fd, union sockunion *su, const char *password); #endif /*_ZEBRA_SOCKOPT_H */ diff --git a/lib/sockunion.c b/lib/sockunion.c index 4043783c..95626513 100644 --- a/lib/sockunion.c +++ b/lib/sockunion.c @@ -139,7 +139,7 @@ sockunion_sin_len(sockunion su) sizeof(struct sockaddr_in); } ; -#if HAVE_IPV6 +#ifdef HAVE_IPV6 inline static int sockunion_sin6_len(sockunion su) { @@ -167,11 +167,11 @@ sockunion_set_family(sockunion su, sa_family_t family) #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN if (family == AF_INET) - su->sin.sin_len = sizeof(struct sockaddr_in); + sockunion_sin_len(sockunion su) ; #endif #if defined(HAVE_IPV6) && defined(SIN6_LEN) if (family == AF_INET6) - su->sin6.sin6_len = sizeof(struct sockaddr_in6); + sockunion_sin6_len(sockunion su) ; #endif return 0 ; @@ -244,7 +244,7 @@ sockunion_init_new(sockunion su, sa_family_t family) else memset(su, 0, sizeof(union sockunion)) ; - if (family != 0) + if (family != AF_UNSPEC) sockunion_set_family(su, family) ; return su ; @@ -263,7 +263,7 @@ str2sockunion (const char *str, union sockunion *su) assert(su != NULL) ; - sockunion_init_new(su, 0) ; + sockunion_init_new(su, AF_UNSPEC) ; ret = inet_pton (AF_INET, str, &su->sin.sin_addr); if (ret > 0) /* Valid IPv4 address format. */ @@ -341,23 +341,45 @@ sockunion_su2str (union sockunion *su, enum MTYPE type) } /*------------------------------------------------------------------------------ - * Convert IPv4 compatible IPv6 address to IPv4 address. + * If have an IPv6 mapped IPv4 address, convert it to an IPv4 address. */ -static void -sockunion_normalise_mapped (union sockunion *su) +extern void +sockunion_unmap_ipv4 (union sockunion *su) { - struct sockaddr_in sin; +#ifdef HAVE_IPV6 + union sockunion sux ; + struct sockaddr_in* su_in = &sux.sin ; + if ( (su->sa.sa_family == AF_INET6) + && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr) ) + { + sockunion_init_new(&sux, AF_INET) ; + memcpy (&su_in->sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4) ; + su_in->sin_port = su->sin6.sin6_port ; + memcpy (su, &sux, sizeof(sux)) ; + confirm(sizeof(*su) == sizeof(sux)) ; + } +#endif /* HAVE_IPV6 */ +} + +/*------------------------------------------------------------------------------ + * If have an IPv4 address, convert it to an IPv6 mapped IPv4 address. + */ +extern void +sockunion_map_ipv4 (union sockunion *su) +{ #ifdef HAVE_IPV6 - if (su->sa.sa_family == AF_INET6 - && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr)) + union sockunion sux ; + struct sockaddr_in6* su_in6 = &sux.sin6 ; + + if (su->sa.sa_family == AF_INET) { - memset (&sin, 0, sizeof (struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = su->sin6.sin6_port; - memcpy (&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); - memset (su, 0, sizeof(union sockunion)) ; - memcpy (su, &sin, sizeof (struct sockaddr_in)); + sockunion_init_new(&sux, AF_INET6) ; + memset (((char *)&su_in6->sin6_addr) + 10, 0xFF, 2) ; + memcpy (((char *)&su_in6->sin6_addr) + 12, &su->sin.sin_addr, 4) ; + su_in6->sin6_port = su->sin.sin_port ; + memcpy (su, &sux, sizeof(sux)) ; + confirm(sizeof(*su) == sizeof(sux)) ; } #endif /* HAVE_IPV6 */ } @@ -372,31 +394,34 @@ sockunion_normalise_mapped (union sockunion *su) * * EINTR -- the usual suspect. * + * Sets the given sockunion to the result of the accept(), converting any + * IPv6 mapped IPv4 addresses to IPv4 form. (Hiding the family for the socket.) + * * Returns: >= 0 -- OK, this is the fd (socket) * -1 -- error -- not one of the above * -2 -- error -- one of the above */ int -sockunion_accept (int sock, union sockunion *su) +sockunion_accept (int sock_fd, union sockunion *su) { socklen_t len; - int ret ; + int new_fd, err ; - len = sizeof(union sockunion); + len = sizeof(*su); memset(su, 0, len) ; - ret = accept(sock, &su->sa, &len) ; + new_fd = accept(sock_fd, &su->sa, &len) ; - if (ret >= 0) + if (new_fd >= 0) { - sockunion_normalise_mapped(su); - return ret ; /* OK -- got socket */ + sockunion_unmap_ipv4(su); + return new_fd ; /* OK -- got socket */ } ; - ret = errno ; - return ( (ret == EAGAIN) - || (ret == EWOULDBLOCK) - || (ret == ECONNABORTED) - || (ret == EINTR) ) ? -2 : -1 ; + err = errno ; + return ( (err == EAGAIN) + || (err == EWOULDBLOCK) + || (err == ECONNABORTED) + || (err == EINTR) ) ? -2 : -1 ; } ; /*------------------------------------------------------------------------------ @@ -410,18 +435,20 @@ sockunion_accept (int sock, union sockunion *su) extern int sockunion_socket(sa_family_t family, int type, int protocol) { - int sockfd ; + int sock_fd ; + int err ; - sockfd = socket(family, type, protocol); - if (sockfd < 0) - { - zlog (NULL, LOG_WARNING, - "Can't make socket family=%d, type=%d, protocol=%d : %s", - (int)family, type, protocol, safe_strerror(errno)) ; - return -1; - } + sock_fd = socket(family, type, protocol); + + if (sock_fd >= 0) + return sock_fd ; - return sockfd ; + err = errno ; + zlog (NULL, LOG_WARNING, + "Can't make socket family=%d, type=%d, protocol=%d : %s", + (int)family, type, protocol, safe_strerror(err)) ; + errno = err ; + return -1; } /*------------------------------------------------------------------------------ @@ -454,12 +481,12 @@ sockunion_stream_socket (union sockunion *su) * Logs a LOG_INFO message if fails. */ extern int -sockunion_connect(int fd, union sockunion* peer_su, unsigned short port, +sockunion_connect(int sock_fd, union sockunion* peer_su, unsigned short port, unsigned int ifindex) { char buf[SU_ADDRSTRLEN] ; union sockunion su ; - int ret ; + int ret, err ; int sa_len ; memcpy(&su, peer_su, sizeof(union sockunion)) ; @@ -486,13 +513,15 @@ sockunion_connect(int fd, union sockunion* peer_su, unsigned short port, # endif /* KAME */ #endif /* HAVE_IPV6 */ - ret = connect(fd, &su.sa, sa_len) ; + ret = connect(sock_fd, &su.sa, sa_len) ; + err = (ret >= 0) ? 0 : errno ; - if ((ret == 0) || (errno == EINPROGRESS)) + if ((err == 0) || (err == EINPROGRESS)) return 0 ; /* instant success or EINPROGRESS as expected */ - zlog_info("can't connect to %s port %d fd %d : %s", - sockunion2str(&su, buf, sizeof(buf)), port, fd, safe_strerror(errno)) ; + zlog_info("cannot connect to %s port %d socket %d : %s", + sockunion2str(&su, buf, sizeof(buf)), port, sock_fd, safe_strerror(err)) ; + errno = err ; return ret ; } ; @@ -500,27 +529,26 @@ sockunion_connect(int fd, union sockunion* peer_su, unsigned short port, /*------------------------------------------------------------------------------ * 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) +sockunion_listen(int sock_fd, int backlog) { - int ret ; + int ret, err ; - ret = listen(fd, backlog) ; + ret = listen(sock_fd, backlog) ; if (ret == 0) return 0 ; - zlog (NULL, LOG_WARNING, "can't listen on fd %d : %s", - fd, safe_strerror(errno)) ; + err = errno ; + zlog (NULL, LOG_WARNING, "cannot listen on socket %d : %s", + sock_fd, safe_strerror(err)) ; + errno = err ; + return ret ; } ; @@ -541,10 +569,11 @@ sockunion_listen(int fd, int backlog) * < 0 => failed -- see errno */ int -sockunion_bind (int sock, union sockunion *su, unsigned short port, void* any) +sockunion_bind (int sock_fd, union sockunion *su, unsigned short port, + void* any) { int sa_len ; - int ret; + int ret ; char buf[SU_ADDRSTRLEN] ; if (any == NULL) @@ -552,10 +581,15 @@ sockunion_bind (int sock, union sockunion *su, unsigned short port, void* any) sa_len = sockunion_set_port(su, port) ; - ret = bind (sock, &su->sa, sa_len); + ret = bind (sock_fd, &su->sa, sa_len); if (ret < 0) - zlog (NULL, LOG_WARNING, "can't bind to %s port %d fd %d : %s", - sockunion2str(su, buf, sizeof(buf)), port, sock, safe_strerror(ret)) ; + { + int err = errno ; + zlog (NULL, LOG_WARNING, "cannot bind to %s port %d socket %d : %s", + sockunion2str(su, buf, sizeof(buf)), port, sock_fd, + safe_strerror(err)) ; + errno = err ; + } ; return ret; } @@ -569,20 +603,23 @@ sockunion_bind (int sock, union sockunion *su, unsigned short port, void* any) * Logs a LOG_WARNING message if fails. */ int -sockopt_reuseaddr (int sock) +sockopt_reuseaddr (int sock_fd) { int ret; int on = 1; - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, - (void *) &on, sizeof (on)); + ret = setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *) &on, + sizeof (on)); if (ret < 0) { + int err = errno ; zlog (NULL, LOG_WARNING, - "can't set sockopt SO_REUSEADDR to socket %d", sock); - return -1; - } - return 0; + "cannot set sockopt SO_REUSEADDR to socket %d %s", sock_fd, + safe_strerror(err)); + errno = err ; + } ; + + return ret ; } /*------------------------------------------------------------------------------ @@ -594,27 +631,34 @@ sockopt_reuseaddr (int sock) * Logs a LOG_WARNING message if fails. */ int -sockopt_reuseport (int sock) +sockopt_reuseport (int sock_fd) { -#ifdef SO_REUSEPORT int ret; + +#ifdef SO_REUSEPORT int on = 1; + ret = setsockopt (sock_fd, SOL_SOCKET, SO_REUSEPORT, + (void *) &on, sizeof (on)); +#else + ret = 0 ; +#endif - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, - (void *) &on, sizeof (on)); if (ret < 0) { + int err = errno ; zlog (NULL, LOG_WARNING, - "can't set sockopt SO_REUSEPORT to socket %d", sock); - return -1; - } -#endif + "cannot set sockopt SO_REUSEPORT to socket %d: %s", sock_fd, + safe_strerror(err)); + errno = err ; + } ; - return 0; + return ret ; } ; /*------------------------------------------------------------------------------ * If same family and same prefix return 1. + * + * Returns 0 if same family, but not a known family. */ int sockunion_same (union sockunion *su1, union sockunion *su2) @@ -625,7 +669,7 @@ sockunion_same (union sockunion *su1, union sockunion *su2) return 0; switch (su1->sa.sa_family) - { + { case AF_INET: return (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) ; @@ -633,27 +677,62 @@ sockunion_same (union sockunion *su1, union sockunion *su2) case AF_INET6: ret = memcmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, sizeof (struct in6_addr)); - break; + return (ret == 0) ; #endif /* HAVE_IPV6 */ - } - if (ret == 0) - return 1; - else - return 0; -} + + default: + return 0 ; + } ; +} ; /*------------------------------------------------------------------------------ - * After TCP connection is established. Get local or remote address and port. + * Get the address family the given socket is set to. * - * Returns: 0 => OK - * != 0 => failed, value = errno + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * NB: gets the actual address family -- does NOT look for mapped IPv4. + */ +extern int +sockunion_getsockfamily(int sock_fd) +{ + union sockunion su ; + int ret ; + socklen_t len ; + + if (sock_fd < 0) + return AF_UNSPEC ; + + sockunion_init_new(&su, AF_UNSPEC) ; + len = sizeof(su) ; + + ret = getsockname(sock_fd, (struct sockaddr *)&su, &len) ; + if (ret < 0) + { + int err = errno ; + zlog_warn ("Failed in getsockname for socket %d: %s", + sock_fd, safe_strerror(err)) ; + errno = err ; + return -1 ; + } ; + + return su.sa.sa_family ; +} ; + +/*------------------------------------------------------------------------------ + * Get local or remote address and port. + * + * Returns: >= 0 => OK + * < 0 => failed (or unknown family) -- see errno + * + * If address is an IPv4 mapped IPv6 address, returns the IPv4 address. * * NB: returns EAFNOSUPPORT if don't recognise the address family. */ static int -sockunion_get_name(int fd, union sockunion* su, int local) +sockunion_get_name(int sock_fd, union sockunion* su, int local) { - int ret; + int ret ; union { @@ -671,18 +750,20 @@ sockunion_get_name(int fd, union sockunion* su, int local) memset(su, 0, sizeof(union sockunion)) ; if (local) - ret = getsockname(fd, (struct sockaddr *)&name, &len) ; + ret = getsockname(sock_fd, (struct sockaddr *)&name, &len) ; else - ret = getpeername(fd, (struct sockaddr *)&name, &len) ; + ret = getpeername(sock_fd, (struct sockaddr *)&name, &len) ; if (ret < 0) { - ret = errno ; - zlog_warn ("Can't get %s address and port: %s", - local ? "local" : "remote", safe_strerror(ret)) ; + int err = errno ; + zlog_warn ("Cannot get %s address and port: %s", + local ? "local" : "remote", safe_strerror(err)) ; + errno = err ; return ret ; } + ret = 0 ; /* assume all will be well */ switch (name.sa.sa_family) { case AF_INET: @@ -692,43 +773,38 @@ sockunion_get_name(int fd, union sockunion* su, int local) #ifdef HAVE_IPV6 case AF_INET6: memcpy(su, &name, sizeof (struct sockaddr_in6)) ; - sockunion_normalise_mapped(su) ; + sockunion_unmap_ipv4(su) ; break ; #endif /* HAVE_IPV6 */ default: - ret = EAFNOSUPPORT ; + errno = EAFNOSUPPORT ; + ret = -1 ; } ; return ret ; } ; /*------------------------------------------------------------------------------ - * After TCP connection is established. Get local address and port. + * Get local address and port -- ie getsockname(). * - * Returns: 0 => OK - * != 0 => failed, value = errno - * - * NB: returns EAFNOSUPPORT if don't recognise the socket's address family. + * See: sockunion_get_name() */ int -sockunion_getsockname(int fd, union sockunion* su_local) +sockunion_getsockname(int sock_fd, union sockunion* su_local) { - return sockunion_get_name(fd, su_local, 1) ; + return sockunion_get_name(sock_fd, su_local, 1) ; } ; /*------------------------------------------------------------------------------ - * After TCP connection is established. Get remote address and port. - * - * Returns: 0 => OK - * != 0 => failed, value = errno + * Get remote address and port -- ie getpeername(). * - * NB: returns EAFNOSUPPORT if don't recognise the socket's address family. + * See: sockunion_get_name() */ int -sockunion_getpeername (int fd, union sockunion* su_remote) +sockunion_getpeername (int sock_fd, union sockunion* su_remote) { - return sockunion_get_name(fd, su_remote, 0) ; + return sockunion_get_name(sock_fd, su_remote, 0) ; } ; /*------------------------------------------------------------------------------ @@ -774,6 +850,12 @@ sockunion_print (union sockunion *su) /*------------------------------------------------------------------------------ * Compare two sockunion values + * + * Compares family values first, then the body of the address. + * + * Returns: +1 => su1 > su2 + * 0 => su1 == su2 (or same, but unknown, family) + * -1 => su1 < su2 */ int sockunion_cmp (union sockunion *su1, union sockunion *su2) @@ -837,6 +919,8 @@ sockunion_free (union sockunion *su) * * It is the caller's responsibility to free the sockunion at some point. * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. */ extern sockunion sockunion_new_prefix(sockunion su, struct prefix* p) @@ -870,32 +954,34 @@ sockunion_new_prefix(sockunion su, struct prefix* p) } ; /*------------------------------------------------------------------------------ - * Create new sockunion from given sockaddr. + * Create new sockunion from given sockaddr -- taking only the address part * * It is the caller's responsibility to free the sockunion at some point. * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. */ extern sockunion sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) { sa_family_t family ; - family = (sa != NULL) ? sa->sa_family : 0 ; + family = (sa != NULL) ? sa->sa_family : AF_UNSPEC ; su = sockunion_init_new(su, family) ; switch (family) { - case 0: + case AF_UNSPEC: break ; case AF_INET: - su->sin = *(struct sockaddr_in*)sa ; + su->sin.sin_addr = ((struct sockaddr_in*)sa)->sin_addr ; break ; #ifdef HAVE_IPV6 case AF_INET6: - su->sin6 = *(struct sockaddr_in6*)sa ; + su->sin6.sin6_addr = ((struct sockaddr_in6*)sa)->sin6_addr ; break ; #endif diff --git a/lib/sockunion.h b/lib/sockunion.h index d1f29b13..3f71cb55 100644 --- a/lib/sockunion.h +++ b/lib/sockunion.h @@ -55,13 +55,6 @@ union sockunion #endif /* HAVE_IPV6 */ }; -enum connect_result -{ - connect_error, - connect_success, - connect_in_progress -}; - /* Default address family. */ #ifdef HAVE_IPV6 #define AF_INET_UNION AF_INET6 @@ -109,19 +102,23 @@ extern int sockunion_same (union sockunion *, union sockunion *); 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 *); +extern int sockunion_accept (int sock_fd, union sockunion *); 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 *, +extern int sockunion_bind (int sock_fd, union sockunion *, unsigned short, void* any); -extern int sockopt_ttl (int family, int sock, int ttl); 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_connect (int sock_fd, union sockunion *su, + unsigned short port, unsigned int) ; +extern int sockunion_listen(int sock_fd, int backlog) ; + +extern int sockunion_getsockfamily(int sock_fd) ; extern int sockunion_getsockname (int, union sockunion*); extern int sockunion_getpeername (int, union sockunion*); +extern void sockunion_unmap_ipv4 (union sockunion *su) ; +extern void sockunion_map_ipv4 (union sockunion *su) ; + extern union sockunion *sockunion_dup (union sockunion *); extern void sockunion_free (union sockunion *); @@ -43,16 +43,56 @@ * * vty * command + * command_queue * 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.) + * There are also: + * + * vty_io + * vty_cli + * + * Which are the "immediate family" of vty: + * + * * *nothing* in their ".h" is for use anywhere except the immediate family. + * + * * things for use within the rest of the family are published here. */ /*============================================================================== + * 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 ; + +/*============================================================================== * To make vty qpthread safe we use a single mutex. * * vty and log recurse through each other, so the same mutex is used @@ -80,7 +120,7 @@ extern qpt_mutex_t vty_mutex ; #if VTY_DEBUG extern int vty_lock_count ; -extern int vty_lock_assert_fail ; +extern int vty_assert_fail ; #endif @@ -97,24 +137,45 @@ VTY_UNLOCK(void) { if (VTY_DEBUG) --vty_lock_count ; - qpt_mutex_lock(&vty_mutex) ; + qpt_mutex_unlock(&vty_mutex) ; } ; +/* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that + * is required. + * + * In some cases, need also to be running in the CLI thread as well. + */ #if VTY_DEBUG Inline void -VTY_ASSERT_LOCKED(void) +VTY_ASSERT_FAILED(void) { - if (vty_lock_count == 0 && !vty_lock_assert_fail) + if (vty_assert_fail == 0) ; { - vty_lock_assert_fail = 1; - assert(0); - } + vty_assert_fail = 1 ; + assert(0) ; + } ; +} ; + +Inline void +VTY_ASSERT_LOCKED(void) +{ + if (vty_lock_count == 0) + VTY_ASSERT_FAILED() ; +} ; + +Inline void +VTY_ASSERT_CLI_THREAD(void) +{ + if (qpthreads_enabled) + if (!qpt_thread_is_self(vty_cli_nexus->thread_id)) + VTY_ASSERT_FAILED() ; } ; #else #define VTY_ASSERT_LOCKED() +#define VTY_ASSERT_CLI_THREAD() #endif @@ -131,75 +192,48 @@ enum cli_do cli_do_ctrl_d, /* received ^d on empty line */ cli_do_ctrl_z, /* received ^z */ + cli_do_eof, /* hit "EOF" */ + 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 enum cmd_return_code uty_command(struct vty *vty) ; +extern enum cmd_return_code uty_auth (struct vty *vty, const char *buf, + enum cli_do cli_do) ; +extern enum cmd_return_code vty_cmd_exit(struct vty* vty) ; +extern enum cmd_return_code vty_cmd_end(struct vty* vty) ; +extern enum cmd_return_code uty_cmd_close(struct vty *vty, const char* reason) ; +extern enum cmd_return_code uty_stop_input(struct vty *vty) ; +extern enum cmd_return_code uty_end_config (struct vty *vty) ; +extern enum cmd_return_code 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 + * Functions in vty_cli -- used outside the immediate vty family */ -extern void -vty_queued_result(struct vty* vty, int ret, int action); - -extern void -uty_set_host_name(const char* name) ; +extern void vty_queued_result(struct vty* vty, enum cmd_return_code ret); +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. + * Functions in vty_io -- used outside the immediate vty family */ +extern void vty_open_config_write(struct vty* vty, int fd) ; +extern int vty_close_config_write(struct vty*) ; + 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); +/*============================================================================== + * Functions in command.c + */ +extern void cmd_post_command(struct vty* vty, int ret) ; + #endif /* _ZEBRA_UTY_H */ diff --git a/lib/vector.c b/lib/vector.c index 3fb4cbd9..646f19a5 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -855,32 +855,26 @@ vector_sak(int to_copy, vector to, * Legacy Vector Operations */ -/* This function only returns next empty slot index. It does not mean - the slot's index memory is assigned, please call vector_ensure() - after calling this function. - - Index returned is <= current (logical) end. -*/ +/* Set value to the smallest empty slot. */ int -vector_empty_slot (vector v) +vector_set (vector v, void *val) { vector_index i; - for (i = 0; i < v->end; i++) - if (v->p_items[i] == NULL) - break ; + i = 0 ; + while (1) + { + if (i == v->end) + { + i = vector_extend_by_1(v) ; + break ; + } - return i; -} + if (v->p_items[i] == NULL) + break ; -/* Set value to the smallest empty slot. */ -int -vector_set (vector v, void *val) -{ - vector_index i; - i = vector_empty_slot(v) ; /* NB: i <= v->end */ - if (i == v->end) - i = vector_extend_by_1(v) ; + ++i ; + } ; v->p_items[i] = val; diff --git a/lib/vector.h b/lib/vector.h index 727d2626..f098d78a 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -44,11 +44,14 @@ typedef struct vector* vector ; /* pointer to vector structure */ typedef struct vector vector_t ; /* embedded vector structure */ struct vector { - p_vector_item *p_items ; /* pointer to array of vector item pointers */ - vector_index end ; /* number of "active" item entries */ - vector_index limit ; /* number of allocated item entries */ + p_vector_item* p_items ; /* pointer to array of vector item pointers */ + vector_index end ; /* number of "active" item entries */ + vector_index limit ; /* number of allocated item entries */ }; +/* Under very controlled circumstances, may access the vector body */ +typedef p_vector_item const* vector_body_t ; + /* Values that control the allocation of the vector body. */ /* NB: these must all be powers of 2. */ @@ -103,7 +106,6 @@ struct vector extern vector vector_init (unsigned int size); Inline void vector_ensure(vector v, vector_index i) ; -extern int vector_empty_slot (vector v); extern int vector_set (vector v, void *val); extern int vector_set_index (vector v, vector_index i, void *val); #define vector_unset(v, i) (void)vector_unset_item(v, i) @@ -131,14 +133,20 @@ extern p_vector_item vector_ream(vector v, int free_structure) ; Inline void vector_set_min_length(vector v, unsigned int len) ; extern void vector_set_new_min_length(vector v, unsigned int len) ; +Inline void vector_set_length(vector v, unsigned int len) ; +#define vector_set_end(v, l) vector_set_length(v, l) + Inline vector_index vector_length(vector v) ; #define vector_end(v) vector_length(v) Inline int vector_is_empty(vector v) ; +Inline vector_body_t vector_body(vector v) ; + Inline p_vector_item vector_get_item(vector v, vector_index i) ; Inline p_vector_item vector_get_first_item(vector v) ; Inline p_vector_item vector_get_last_item(vector v) ; Inline void vector_set_item(vector v, vector_index i, p_vector_item p_v) ; +Inline void vector_assign_item(vector v, vector_index dst, vector_index src) ; extern p_vector_item vector_unset_item(vector v, vector_index i) ; extern vector_index vector_trim(vector v) ; extern vector_index vector_condense(vector v) ; @@ -228,16 +236,34 @@ vector_ensure(vector v, vector_index i) } ; /* 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 */ + if (len > v->end) /* will not reduce the length */ vector_set_new_min_length(v, len) ; } ; +/* Want vector to be the given length. */ +/* */ +/* If this is less than the current length, items are discarded. It */ +/* is the caller's responsibility to have freed anything that needs it. */ +/* */ +/* 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_length(vector v, unsigned int len) +{ + if (len > v->end) + vector_set_new_min_length(v, len) ; /* Extend if new length greater */ + else + v->end = len ; /* chop */ +} ; + /* Return index of end of vector (index of last item + 1) */ Inline vector_index vector_length(vector v) @@ -252,6 +278,19 @@ vector_is_empty(vector v) return (v->end == 0) ; } ; +/* Returns highly restricted pointer to vector body */ +//Inline const void* const* +//vector_body(vector v) +//{ +// return (const void* const*)v->p_items ; +//} ; + +Inline vector_body_t +vector_body(vector v) +{ + return (vector_body_t)v->p_items ; +} ; + /* Access functions -- Inline for obvious reasons. */ /* Get pointer to item. Returns NULL if accessing beyond end. */ @@ -285,6 +324,17 @@ vector_set_item(vector v, vector_index i, void* p_v) v->p_items[i] = (p_vector_item)p_v ; } ; +/* Set dst item to be a copy of the src item. Extend vector if required. + * + * NB: it is the caller's responsibility to look after the memory being + * used by the current dst item or the new (duplicated) src item. + */ +Inline void +vector_assign_item(vector v, vector_index dst, vector_index src) +{ + vector_set_item(v, dst, vector_get_item(v, src)) ; +} ; + /* Push value onto vector, extending as required. */ Inline void vector_push_item(vector v, void* p_v) diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index fb62f192..685f33ec 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -23,6 +23,7 @@ #include <string.h> #include "vio_fifo.h" +#include "network.h" #include "list_util.h" #include "memory.h" @@ -37,6 +38,9 @@ * The last lump is never released. So, it may be that only one lump is * ever needed. * + * When releasing lumps, keeps one lump "spare", to be reused as necessary. + * This is used in ... TODO <<<< And is released... + * *------------------------------------------------------------------------------ * Implementation notes: * @@ -114,13 +118,18 @@ vio_fifo_init_new(vio_fifo vf, size_t size) /* Zeroising the the vio_fifo_t has set: * - * lump -- base pair, both pointers NULL => list is empty + * 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 * - * put_ptr -- NULL ) no lump to put anything into - * put_end -- NULL ) put_ptr == put_end => no room in current lump + * rdr_lump -- NULL ) no rdr_lump + * rdr_ptr -- NULL * - * get_ptr -- NULL ) no lump to get anything from - * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump + * spare -- NULL no spare lump * * ALSO put_ptr == get_ptr => FIFO is empty ! */ @@ -139,6 +148,8 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * * If does not free the FIFO structure, resets it all empty. * + * Frees *all* FIFO lumps. + * * See also: vio_fifo_reset_keep(vio_fifo) * vio_fifo_reset_free(vio_fifo) */ @@ -153,6 +164,9 @@ vio_fifo_reset(vio_fifo vf, int free_structure) while (ddl_pop(&lump, vf->base, list) != NULL) XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + if (vf->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; + if (free_structure) XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ else @@ -162,33 +176,105 @@ vio_fifo_reset(vio_fifo vf, int free_structure) } ; /*------------------------------------------------------------------------------ - * Set FIFO empty, discarding current contents -- will continue to use the FIFO. + * The FIFO is empty, with one lump -- reset all pointers. + */ +inline static void +vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +{ + if (vf->rdr_lump != NULL) + { + assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; + vf->rdr_ptr = lump->data ; + } ; + + /* Note that sets the lump->end to the true lump->end */ + vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; + vf->put_end = lump->end = lump->data + lump->size ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + */ +inline static void +vio_fifo_ptr_unset(vio_fifo vf) +{ + assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; + + vf->one = false ; + + vf->put_ptr = NULL ; + vf->put_end = NULL ; + vf->get_ptr = NULL ; + vf->get_end = NULL ; + + vf->rdr_lump = NULL ; + vf->rdr_ptr = NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Clear out contents of FIFO -- will continue to use the FIFO. + * + * Keeps one FIFO lump. (Frees everything else, including any spare.) */ extern void -vio_fifo_set_empty(vio_fifo vf) +vio_fifo_clear(vio_fifo vf) { - vio_fifo_lump lump ; + vio_fifo_lump tail ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; assert(vf != NULL) ; - while (ddl_head(vf->base) != ddl_tail(vf->base)) - { - ddl_pop(&lump, vf->base, list) ; - XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; - } ; + tail = ddl_tail(vf->base) ; - lump = ddl_head(vf->base) ; - if (lump != NULL) + if (tail != NULL) { - vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; - vf->put_end = lump->end ; - } ; + while (ddl_head(vf->base) != tail) + { + vio_fifo_lump lump ; + ddl_pop(&lump, vf->base, list) ; + XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; + } ; + + vf->rdr_lump = NULL ; /* clear rdr */ + vf->rdr_ptr = NULL ; + + vf->one = true ; + vio_fifo_ptr_reset(vf, tail) ; + } + else + vio_fifo_ptr_unset(vf) ; + + if (vf->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * See how much room there is in the FIFO. + * + * If no lumps have been allocated, returns the size of the lump that would + * allocate. + * + * Otherwise, returns the amount of space available *without* allocating any + * further lumps. + * + * Returns: room available as described + */ +extern size_t +vio_fifo_room(vio_fifo vf) +{ + if (vf->put_ptr != NULL) + return vf->put_end - vf->put_ptr ; + else + return vf->size ; } ; /*------------------------------------------------------------------------------ * Allocate another lump for putting into. * - * Call when (vf->put_ptr >= vf->put_end) -- asserts that the pointers are equal. + * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. * * Set the put_ptr/put_end pointers to point at the new lump. * @@ -198,23 +284,19 @@ vio_fifo_set_empty(vio_fifo vf) * to reflect the fact that the out lump is now full. */ extern void -vio_fifo_lump_new(vio_fifo vf) +vio_fifo_lump_new(vio_fifo vf, size_t size) { 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 ; + vf->get_end = vf->put_ptr ; /* update get_end */ + + lump = ddl_tail(vf->base) ; first_alloc = (lump == NULL) ; /* extra initialisation needed */ @@ -223,9 +305,21 @@ vio_fifo_lump_new(vio_fifo vf) 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 ; + size = vio_fifo_size(size) ; + + if ((vf->spare != NULL) && (vf->spare->size >= size)) + { + lump = vf->spare ; + vf->spare = NULL ; + } + else + { + lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, + offsetof(vio_fifo_lump_t, data[size])) ; + lump->size = size ; + } ; + + lump->end = lump->data + lump->size ; ddl_append(vf->base, lump, list) ; @@ -243,6 +337,207 @@ vio_fifo_lump_new(vio_fifo vf) VIO_FIFO_DEBUG_VERIFY(vf) ; } ; +/*------------------------------------------------------------------------------ + * Release lump, head or tail (or both) and update pointers. + * + * Note that this does release the lump if it is the only lump. + * + * Do nothing if nothing is yet allocated. + * + * If releasing the only lump in the FIFO, resets all pointers to NULL. + * + * If releasing the head lump: + * + * * the lump MUST be finished with -- so vf->get_ptr must be at the end + * + * If releasing the only lump, the FIFO MUST be empty. + * + * * if the lump is the current vf->rdr_lump, the reader must be at the + * end too -- ie it must be the same as the vf->get_ptr ! + * + * If releasing the tail lump: + * + * * the lump MUST be empty + * + * If releasing the only lump, the FIFO MUST be empty. + * + * * if the lump is the current vf->rdr_lump, the reader must be at the + * end too -- ie it must be the same as the vf->get_ptr ! + */ +static void +vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) +{ + vio_fifo_lump head ; + vio_fifo_lump tail ; + vio_fifo_lump free ; + bool release_head ; + bool release_tail ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + /* Prepare and check whether removing head or tail (or both) */ + head = ddl_head(vf->base) ; + tail = ddl_tail(vf->base) ; + + release_head = (lump == head) ; + release_tail = (lump == tail) ; + + assert(release_head || release_tail) ; + + /* Unless nothing ever allocated -- release the lump. */ + free = lump ; /* expect to free the lump */ + if (lump != NULL) + { + vio_fifo_lump keep ; + + /* Consistency checks */ + if (release_head) + { + if (release_tail) + assert(vf->get_ptr == vf->put_ptr) ; + else + assert(vf->get_ptr == lump->end) ; + + if (vf->rdr_lump == lump) + assert(vf->rdr_ptr == vf->get_ptr) ; + } + else if (release_tail) + { + assert(vf->put_ptr == lump->data) ; + + if (vf->rdr_lump == lump) + assert(vf->rdr_ptr == vf->put_ptr) ; + } ; + + /* Remove lump from FIFO and decide whether to keep as spare, or + * which of spare and this to free. + */ + ddl_del(vf->base, lump, list) ; + + keep = vf->spare ; /* expect to keep current spare */ + + if ((keep == NULL) || (keep->size < lump->size)) + { + keep = lump ; + free = vf->spare ; + } ; + + vf->spare = keep ; + + head = ddl_head(vf->base) ; /* changed if released head */ + tail = ddl_tail(vf->base) ; /* changed if released tail */ + } ; + + /* Now update pointers... depending on what was released and what have + * left. + */ + if (head == NULL) + { + /* Deal with FIFO that now has no lumps or had none to start with */ + if (lump != NULL) + assert(vf->one) ; + + vio_fifo_ptr_unset(vf) ; + } + else + { + /* Have at least one lump left -- so must have had at least two ! */ + assert(!vf->one) ; + + vf->one = (head == tail) ; /* update */ + + if (release_head) + { + /* Released the head. + * + * Update the vf->get_ptr and the vf->get_end. + */ + vf->get_ptr = head->data ; + if (vf->one) + vf->get_end = vf->put_ptr ; + else + vf->get_end = head->end ; + + /* Update vf->rdr_ptr and vf->rdr_lump. */ + if (vf->rdr_lump == lump) + { + vf->rdr_lump = head ; + vf->rdr_ptr = head->data ; + } ; + } + else + { + /* Released the tail. + * Update the vf->put_ptr and vf->put_end + */ + vf->put_ptr = vf->put_end = tail->end ; + + /* Update vf->rdr_ptr and vf->rdr_lump. */ + if (vf->rdr_lump == lump) + { + vf->rdr_lump = tail ; + vf->rdr_ptr = tail->end ; + } ; + } ; + } ; + + /* Finally, free any lump that is actually to be freed */ + + if (free != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Re-allocate lump for putting into. + * + * Call when vf->put_ptr == start of last lump, and that lump is not big + * enough ! + * + * There must be at least one lump. + * + * Updates put_ptr/put_end pointers to point at the new lump. + * + * Updates get_ptr/get_end pointers if required. + * + * Updates rdr_ptr if required. + */ +static void +vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +{ + bool rdr_set ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + /* FIFO may not be completely empty. + * This must be the last lump. + * The last lump must be empty. + */ + assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; + + /* Remove the last, *empty* lump, and update all pointers to suit. */ + rdr_set = (vf->rdr_lump == lump) ; + + vio_fifo_lump_release(vf, lump) ; + + /* Now allocate a new lump with the required size */ + vio_fifo_lump_new(vf, size) ; + + /* Restore the rdr_ptr, if required */ + if (rdr_set) + { + vio_fifo_lump tail ; + + tail = ddl_tail(vf->base) ; + + vf->rdr_lump = tail ; + vf->rdr_ptr = tail->data ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + /*============================================================================== * Put data to the FIFO. */ @@ -260,7 +555,7 @@ vio_fifo_put(vio_fifo vf, const char* src, size_t n) while (n > 0) { if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf) ; /* traps broken vf->put_ptr > vf->put_end */ + vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ take = (vf->put_end - vf->put_ptr) ; if (take > n) @@ -276,6 +571,103 @@ vio_fifo_put(vio_fifo vf, const char* src, size_t n) VIO_FIFO_DEBUG_VERIFY(vf) ; } ; +/*------------------------------------------------------------------------------ + * Formatted print to fifo -- cf printf() + * + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) + */ +extern int +vio_fifo_printf(vio_fifo vf, const char* format, ...) +{ + va_list args; + int len ; + + va_start (args, format); + len = vio_fifo_vprintf(vf, format, args); + va_end (args); + + return len; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to fifo -- cf vprintf() + * + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) + */ +extern int +vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) +{ + va_list ac ; + int len ; + int have ; + size_t size ; + vio_fifo_lump lump ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + size = vf->size ; /* standard allocation size */ + while (1) + { + /* Find what space is left in the tail lump, and allocate a new, + * empty lump if required. + */ + if (vf->put_ptr >= vf->put_end) + vio_fifo_lump_new(vf, size) ; /* traps put_ptr > put_end */ + + have = vf->put_end - vf->put_ptr ; + assert(have > 0) ; + + /* Note that vsnprintf() returns the length of what it would like to + * have produced, if it had the space. That length does not include + * the trailing '\0'. + */ + va_copy(ac, args) ; + len = vsnprintf(vf->put_ptr, have, format, ac) ; + va_end(ac) ; + + if (len < have) + { + if (len < 0) + break ; /* quit if failed */ + + vf->put_ptr += len ; + break ; /* done */ + } ; + + /* Not able to complete the operation in the current buffer. + * + * If the required space (len + 1) is greater than the standard + * allocation, then need to increase the allocation for the next lump. + * + * If the current lump is empty, need to renew it with a fresh lump of + * the now known required size. + * + * If the current lump is not empty, need to cut the end off and then + * allocate a fresh lump (of the standard or now known required size). + */ + if (len >= (int)size) + size = len + 1 ; /* need a non-standard size */ + + lump = ddl_tail(vf->base) ; + + if (vf->put_ptr == lump->data) + /* Need to replace the last, empty, lump with another empty lump, but + * big enough. + */ + vio_fifo_lump_renew(vf, lump, size) ; + else + /* Need to cut this lump short, and allocate new lump at top of loop. + */ + lump->end = vf->put_end = vf->put_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + return len ; +} ; + /*============================================================================== * Get data from the FIFO. */ @@ -293,6 +685,8 @@ static bool vio_fifo_get_next_lump(vio_fifo vf) ; static inline bool vio_fifo_get_ready(vio_fifo vf) { + assert(vf->rdr_lump == NULL) ; + if (vf->one) vf->get_end = vf->put_ptr ; /* make sure have everything */ @@ -380,8 +774,8 @@ vio_fifo_get_next_byte(vio_fifo vf) /*------------------------------------------------------------------------------ * 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 + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_have = 0 * * If the FIFO is not empty, will return pointer to at least one byte. * @@ -389,15 +783,15 @@ vio_fifo_get_next_byte(vio_fifo vf) * further lumps beyond the current one. */ extern void* -vio_fifo_get_lump(vio_fifo vf, size_t* have) +vio_fifo_get_lump(vio_fifo vf, size_t* p_have) { if (!vio_fifo_get_ready(vf)) { - *have = 0 ; + *p_have = 0 ; return NULL ; } ; - *have = (vf->get_end - vf->get_ptr) ; + *p_have = (vf->get_end - vf->get_ptr) ; return vf->get_ptr ; } ; @@ -422,6 +816,195 @@ vio_fifo_got_upto(vio_fifo vf, void* here) } ; /*------------------------------------------------------------------------------ + * Write contents of FIFO -- assuming non-blocking file + * + * Will write all of FIFO, or upto but excluding the last lump. + * + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see errno + * + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". + */ +extern int +vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +{ + char* src ; + size_t have ; + int done ; + + while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + { + if (!all && vf->one) + break ; /* don't write last lump */ + + done = write_nb(fd, src, have) ; + + if (done < 0) + return -1 ; /* failed */ + + vio_fifo_got_upto(vf, src + done) ; + + if (done < (int)have) + return 1 ; /* blocked */ + } ; + + return 0 ; /* all gone */ +} ; + +/*------------------------------------------------------------------------------ + * Get the current rdr_end value. + * + * Unlike get_end, do not have a field for this, but find it each time. + */ +inline static char* +vio_fifo_rdr_end(vio_fifo vf) +{ + if (vf->rdr_lump == ddl_tail(vf->base)) + return vf->put_ptr ; + else + return vf->rdr_lump->end ; +} ; + +/*------------------------------------------------------------------------------ + * Get the current rdr position -- sets it up if not currently set. + * + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_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. + * + * NB: unless returns FIFO is empty, it is a mistake to now do any "get" + * operation other than vio_fifo_step_rdr(), until do vio_fifo_sync_rdr() + * or vio_fifo_drop_rdr. + */ +extern void* +vio_fifo_get_rdr(vio_fifo vf, size_t* p_have) +{ + if (!vio_fifo_get_ready(vf)) + { + *p_have = 0 ; + return NULL ; + } ; + + if (vf->rdr_lump == NULL) /* set up new rdr if required */ + { + vf->rdr_lump = ddl_head(vf->base) ; + vf->rdr_ptr = vf->get_ptr ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; + return vf->rdr_ptr ; +} ; + +/*------------------------------------------------------------------------------ + * Step the rdr forward by the given number of bytes. + * + * Returns: address of next byte to get, *p_have = number of bytes available + * or: NULL => FIFO is empty, *p_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. + * + * NB: this does not change the get pointers, so all the data being stepped + * over is preserved in the FIFO, until vio_fifo_sync_rdr(). + * + * NB: the step may NOT exceed the last reported "have". + */ +extern void* +vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +{ + char* rdr_end ; + + assert(vf->rdr_lump != NULL) ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + rdr_end = vio_fifo_rdr_end(vf) ; + vf->rdr_ptr += step ; + + if (vf->rdr_ptr >= rdr_end) + { + assert(vf->rdr_ptr == rdr_end) ; + + if (vf->rdr_lump != ddl_tail(vf->base)) + { + vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; + vf->rdr_ptr = vf->rdr_lump->data ; + + rdr_end = vio_fifo_rdr_end(vf) ; + } ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + *p_have = (rdr_end - vf->rdr_ptr) ; + return (*p_have > 0) ? vf->rdr_ptr : NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Move FIFO get position to the rdr position, if any. + * + * This clears the rdr position, and removes all data between the current and + * new get positions from the FIFO. + */ +extern void +vio_fifo_sync_rdr(vio_fifo vf) +{ + vio_fifo_lump head ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; + + if (vf->rdr_lump == NULL) + return ; + + while ((head = ddl_head(vf->base)) != vf->rdr_lump) + { + vf->get_ptr = vf->get_end ; /* jump to end of lump */ + vio_fifo_lump_release(vf, head) ; + } ; + + vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + + vf->rdr_lump = NULL ; /* clear the rdr */ + vf->rdr_ptr = NULL ; + + if (vf->one) + { + if (vf->put_ptr == vf->get_ptr) /* reset pointers if FIFO empty */ + vio_fifo_ptr_reset(vf, head) ; + else + vf->get_end = vf->put_ptr ; + } + else + vf->get_end = head->end ; + + VIO_FIFO_DEBUG_VERIFY(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Drop the rdr position (if any). + * + * This clears the rdr position leaving the get position and FIFO unchanged. + */ +extern void +vio_fifo_drop_rdr(vio_fifo vf) +{ + VIO_FIFO_DEBUG_VERIFY(vf) ; + + vf->rdr_lump = NULL ; + vf->rdr_ptr = NULL ; +} ; + +/*------------------------------------------------------------------------------ * Move on to next lump to get stuff from. * * Advance pointers etc. so that have at least one byte available, unless @@ -439,70 +1022,51 @@ vio_fifo_got_upto(vio_fifo vf, void* here) * 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. + * of the last lump -- does not release 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 */ + head = ddl_head(vf->base) ; /* current lump for get */ - /* Deal with case of one lump only */ + /* Deal with the simple case of one lump, first. + * + * To save work when putting data into the FIFO (particularly when putting + * a byte at a time) does not keep the vf->get_end up to date (when there is + * only one lump). + * + * If the FIFO is empty, reset pointers and return empty. + */ if (vf->one) { - assert( (head != NULL) - && (head == tail) ) ; + assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; - if (vf->get_ptr == vf->put_ptr) + 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) ; + /* Had an out of date vf->get_end */ + vf->get_end = vf->put_ptr ; - return 0 ; /* FIFO empty */ + return true ; /* FIFO not 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 */ - } ; + assert(vf->get_ptr == vf->put_ptr) ; - /* Deal with case of not yet allocated */ - if (head == NULL) - { - assert( (tail == NULL) - && (vf->put_ptr == vf->get_ptr) ); + /* FIFO is empty -- reset pointers and exit */ + vio_fifo_ptr_reset(vf, head) ; - return 0 ; /* FIFO empty */ + return false ; /* 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) ; + /* Release the head and update pointers + * + * Deals with possibility that nothing has yet been allocated + */ + vio_fifo_lump_release(vf, head) ; return (vf->get_ptr < vf->get_end) ; } ; @@ -514,6 +1078,7 @@ Private void vio_fifo_verify(vio_fifo vf) { vio_fifo_lump head ; + vio_fifo_lump lump ; vio_fifo_lump tail ; head = ddl_head(vf->base) ; @@ -524,10 +1089,11 @@ vio_fifo_verify(vio_fifo vf) if (head == NULL) { if ( (tail != NULL) - || (vf->put_ptr != NULL) - || (vf->put_end != NULL) - || (vf->get_ptr != NULL) - || (vf->get_end != NULL) + || (vf->put_ptr != NULL) + || (vf->put_end != NULL) + || (vf->get_ptr != NULL) + || (vf->rdr_lump != NULL) + || (vf->rdr_ptr != NULL) || (vf->one) ) zabort("nothing allocated, but not all NULL") ; return ; @@ -541,7 +1107,7 @@ vio_fifo_verify(vio_fifo vf) /* 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) ) @@ -568,5 +1134,32 @@ vio_fifo_verify(vio_fifo vf) if (vf->get_end != head->end) zabort("get_end is not head->end when !vf->one") ; + } ; + + /* If have an rdr_lump -- make sure everything else is valid */ + if (vf->rdr_lump != NULL) + { + lump = head ; + while (lump != vf->rdr_lump) + { + if (lump == tail) + zabort("rdr_lump is not part of FIFO") ; + lump = ddl_next(lump, list) ; + } ; + + if ( (lump->data > vf->rdr_ptr) + || (vf->rdr_ptr > lump->end) ) + zabort("rdr_ptr outside its lump") ; + + if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) + zabort("rdr_ptr is less than get_ptr in first lump") ; + + if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) + zabort("rdr_ptr is greater than put_ptr in last lump") ; + } + else + { + if (vf->rdr_ptr != NULL) + zabort("rdr_ptr not NULL when rdr_lump is") ; } } ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h index 6d99afe5..52f3455e 100644 --- a/lib/vio_fifo.h +++ b/lib/vio_fifo.h @@ -22,7 +22,7 @@ #ifndef _ZEBRA_VIO_FIFO_H #define _ZEBRA_VIO_FIFO_H -#include <stddef.h> +#include "zebra.h" #include <stdint.h> #include <stdbool.h> @@ -37,6 +37,13 @@ #define Private extern #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__ */ + /*============================================================================== * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. */ @@ -70,7 +77,12 @@ struct vio_fifo char* get_ptr ; char* get_end ; + vio_fifo_lump rdr_lump ; + char* rdr_ptr ; + size_t size ; + + vio_fifo_lump spare ; } ; struct vio_fifo_lump @@ -78,6 +90,7 @@ struct vio_fifo_lump struct dl_list_pair(vio_fifo_lump) list ; char* end ; /* end of this particular lump */ + size_t size ; /* size of lump when allocated */ char data[] ; } ; @@ -85,52 +98,44 @@ struct vio_fifo_lump * 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) ; +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) ; +extern void vio_fifo_clear(vio_fifo vf) ; +Inline bool vio_fifo_empty(vio_fifo vf) ; +extern size_t vio_fifo_room(vio_fifo vf) ; -Inline int -vio_fifo_get_byte(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 void* -vio_fifo_get_lump(vio_fifo vf, size_t* have) ; +extern int vio_fifo_printf(vio_fifo vf, const char* format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern int vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) ; -extern void -vio_fifo_got_upto(vio_fifo vf, void* here) ; +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) ; +Inline bool vio_fifo_full_lump(vio_fifo vf) ; +extern int vio_fifo_write_nb(vio_fifo vf, int fd, bool all) ; -Private void -vio_fifo_lump_new(vio_fifo vf) ; +extern void* vio_fifo_get_rdr(vio_fifo vf, size_t* have) ; +extern void* vio_fifo_step_rdr(vio_fifo vf, size_t* have, size_t step) ; +extern void vio_fifo_sync_rdr(vio_fifo vf) ; +extern void vio_fifo_drop_rdr(vio_fifo vf) ; -Private int -vio_fifo_get_next_byte(vio_fifo vf) ; +Private void vio_fifo_lump_new(vio_fifo vf, size_t size) ; +Private int vio_fifo_get_next_byte(vio_fifo vf) ; /*============================================================================== * Debug -- verification function */ -Private void -vio_fifo_verify(vio_fifo vf) ; +Private void vio_fifo_verify(vio_fifo vf) ; #if VIO_FIFO_DEBUG # define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf) @@ -158,7 +163,7 @@ 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_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ VIO_FIFO_DEBUG_VERIFY(vf) ; @@ -182,4 +187,19 @@ vio_fifo_get_byte(vio_fifo vf) return (unsigned char)*vf->get_ptr++ ; } ; +/*------------------------------------------------------------------------------ + * See if have at least one full lump. + * + * This may be used with vio_fifo_write_nb(..., false) to use FIFO as a sort of + * double buffer. + * + * Returns: true <=> there is at least one full lump in the FIFO + * (excluding the last lump if it happens to be full) + */ +Inline bool +vio_fifo_full_lump(vio_fifo vf) +{ + return (!vf->one && (vf->put_ptr != NULL)) ; +} ; + #endif /* _ZEBRA_VIO_FIFO_H */ diff --git a/lib/vio_lines.c b/lib/vio_lines.c new file mode 100644 index 00000000..c2e9c43c --- /dev/null +++ b/lib/vio_lines.c @@ -0,0 +1,380 @@ +/* Line Control for VTY Terminal output + * 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 <stdint.h> + +#include "memory.h" +#include "zassert.h" + +#include "vio_lines.h" +#include "qiovec.h" + +/*============================================================================== + * Line control handles the output of simple text to a telnet connection, + * folding and counting lines (for "--more--" purposes) if required. + * + * LIMITATIONS: + * + * 1) does not handle '\r' except as part of '\r''\n' pairs. + * + * Telnet requires that bare '\r' be sent as '\r''\0'. That is not + * implemented. + * + * The handling of '\r' which is not part of '\r''\n' is UNDEFINED. + * (In particular, the '\r' may be sent as is, or not sent at all.) + * + * 2) does not worry about '\t' or '\b' or any other control character. + * + * Apart from '\r' and '\n' all characters are deemed to be printing + * characters -- and to have width == 1. + * + * 3) has no idea about escape sequences or telnet commands. + * + * In particular: when looking for '\n' (and '\r') has no way of telling + * if those are part of an escape sequence. + * + * 4) DOES NOT handle 0xFF character value. + * + * For Telnet this should be escaped. It isn't. + * + * Current use of VTY command output will not be troubled by these limitations. + * To do more would cost code and cpu unnecessarily. + * + * WHAT IT DOES DO: + * + * 1) maps bare '\n' to '\r''\n'. + * + * Swallows '\r' immediately before '\n' if present. + * + * 2) if required, breaks output into screen width chunks, and counts + * down the height of a "screen full". + * + */ + +/*============================================================================== + * Initialise, allocate, reset etc. + */ + +/*------------------------------------------------------------------------------ + * Initialise new vio_line_control -- allocate if required. + * + * This is for initialising a new structure. Any current contents are lost. + * + * A width of <= 0 => very large width indeed. + * A height of <= 0 => indefinite height + * + * Pause is unset. vio_lc_append will collect an indefinite number of lines. + * + * Column and line position set to zero. + * + * Returns: address of vio_line_control + */ +extern vio_line_control +vio_lc_init_new(vio_line_control lc, int width, int height) +{ + if (lc == NULL) + lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ; + else + memset(lc, 0, sizeof(struct vio_line_control)) ; + + /* Zeroising has set: + * + * pause = 0 -- no limit on the number of lines to append + * paused = 0 -- not paused + * + * col = 0 -- at column 0 + * lines = 0 -- no lines collected, yet + * + * iov = all 0 -- empty + * writing = 0 -- not writing + */ + + lc->width = width >= 0 ? width : 0 ; + lc->height = height >= 0 ? height : 0 ; + + return lc ; +} ; + +/*------------------------------------------------------------------------------ + * Reset vio_line_control (if any) -- release body and (if required) the + * structure. + * + * Returns: address of vio_line_control (if any) -- NULL if structure released + */ +extern vio_line_control +vio_lc_reset(vio_line_control lc, bool free_structure) +{ + if (lc != NULL) + { + if (free_structure) + XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */ + else + vio_lc_init_new(lc, lc->width, lc->height) ; + /* re-initialise */ + } ; + + return lc ; +} ; + +/*------------------------------------------------------------------------------ + * Clear given vio_line_control. + * + * Sets: pause = 0 + * paused = 0 + * col = 0 + * writing = 0 + * + * NB: it is the callers responsibility to release anything buffered because + * it was earlier appended. + */ +extern void +vio_lc_clear(vio_line_control lc) +{ + qiovec_clear(&lc->qiov) ; + + lc->pause = 0 ; + lc->paused = 0 ; + lc->col = 0 ; + lc->writing = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Sets width and height for line control + * + * A width of <= 0 => very large width indeed. + * A height of <= 0 => indefinite height + * + * Pause is adjusted if it is not zero, and may become zero and set paused. + */ +extern void +vio_lc_set_window(vio_line_control lc, int width, int height) +{ + unsigned old_height ; + + old_height = lc->height ; + + lc->width = width >= 0 ? width : 0 ; + lc->height = height >= 0 ? height : 0 ; + + if (lc->pause != 0) + { + if (lc->height > old_height) + lc->pause += lc->height - old_height ; + else + { + if (lc->pause >= (old_height - lc->height)) + lc->pause = 0 ; + else + lc->pause -= old_height - lc->height ; + } ; + lc->paused = (lc->pause == 0) ; + } ; +} ; + +/*============================================================================== + * Appending and writing + */ + +/*------------------------------------------------------------------------------ + * Sets pause to the current height and clear paused. + */ +extern void +vio_lc_set_pause(vio_line_control lc) +{ + lc->pause = lc->height ; + lc->paused = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Put newline (if required) and account for it + */ +static inline void +vio_lc_newline(vio_line_control lc, bool required) +{ + if (required) + qiovec_push(&lc->qiov, "\r\n", 2) ; + + lc->col = 0 ; + lc->line += 1 ; + if (lc->pause != 0) + { + lc->pause -= 1 ; + lc->paused = (lc->pause == 0) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Append a lump of output to the given line control's buffers. + * + * Breaks the output into lines which are no longer than the lc->width. + * + * Maps '\n' to '\r''\n'. + * + * Discards '\r' if found before '\n', and possibly at other times. + * + * If lc->width == 0, use a very large width indeed. + * + * If lc->pause == 0, append an indefinite number of lines + * + * NB: the buffer presented MUST be retained until the contents of the + * line control's buffers have been written. + * + * Returns: number of bytes able to append + */ +extern size_t +vio_lc_append(vio_line_control lc, const void* buf, size_t len) +{ + const char* p ; + const char* end ; + + unsigned width ; + unsigned pause ; + + /* Prepare local width and pause */ + if (lc->width > 0) + width = lc->width ; + else + width = UINT_MAX ; + + if (lc->pause > 0) + pause = 0 ; + else + pause = 1 ; + + lc->paused = 0 ; + + /* Append: stop when run out of data or run out of lines */ + end = (const char*)buf + len ; + p = buf ; + + while ((p < end) && (lc->pause != pause)) + { + const char* e ; + bool nl ; + int nlx ; + + nlx = 0 ; /* no line ending chars yet */ + + /* scan for '\n'. */ + e = memchr(p, '\n', (end - p)) ; + nl = (e != NULL) ; + if (nl) + ++nlx ; /* account for the '\n' */ + else + e = end ; /* use all there is */ + + /* peel off trailing '\r'. + * + * NB: if have not got a '\n', then this may discard a bare + * '\r' -- but bare '\r' are undefined in any case. + */ + if ((e > p) && (*(e - 1) == '\r')) + { + --e ; /* strip the '\r' */ + ++nlx ; /* but account for it */ + } + + /* have p..e characters and possibly nl to add to the output. + * + * Note that if enters the while, (e - p) > 0. So there is at least one + * character to add. This avoids generating a spurious line ending if + * the width has been reduced, and the next thing output is a line end. + */ + while ((p < e) && (lc->pause != pause)) + { + const char* t ; + unsigned col ; + + col = lc->col + (e - p) ; /* NB: e > p */ + + if (col > width) + { + /* can use only part of what there is */ + if (width > lc->col) + t = p + (width - lc->col) ; + /* take to edge of screen */ + else + t = p ; + assert(t < e) ; /* if not need to deal with nl */ + } + else + { + /* can use all of what there is */ + if (nlx == 2) /* if have crlf, use it */ + { + e += nlx ; /* use the crlf that's there */ + nlx = 0 ; /* used it */ + } ; + + t = e ; /* take it all */ + } ; + + assert(t >= p) ; + if (t != p) + qiovec_push(&lc->qiov, p, (t - p)) ; + + /* advance. If not taken all the line, need a crlf */ + p = t ; + + if (p < e) + vio_lc_newline(lc, 1) ; + } ; + + /* If taken all of line, deal with any outstanding nl and nlx */ + if (p == e) + { + if (nl) + vio_lc_newline(lc, (nlx != 0)) ; + + p += nlx ; /* step past '\r' or '\n' */ + } ; + } ; + + /* Exhausted the available data or the line count */ + assert(p <= end) ; + + return (p - (const char*)buf) ; /* what have taken */ +} ; + +/*------------------------------------------------------------------------------ + * Write away any collected output -- assuming NON-BLOCKING. + * + * Does nothing if the line control is empty. + * + * Loops internally if gets EINTR. + * + * Returns: > 0 => one or more bytes left to output + * 0 => all done -- zero bytes left to output + * -1 => failed -- see errno + * + * Sets lc->writing if write does not complete + */ +extern int +vio_lc_write_nb(int fd, vio_line_control lc) +{ + int ret ; + + ret = qiovec_write_nb(fd, &lc->qiov) ; + + lc->writing = (ret > 0) ; + + return ret ; +} ; diff --git a/lib/vio_lines.h b/lib/vio_lines.h new file mode 100644 index 00000000..ffef94ec --- /dev/null +++ b/lib/vio_lines.h @@ -0,0 +1,91 @@ +/* Line Control for VTY Terminal output -- 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_VIO_LINES_H +#define _ZEBRA_VIO_LINES_H + +#include "zebra.h" + +#include <stddef.h> +#include <stdint.h> + +#include "qiovec.h" + +#ifndef Inline +#define Inline static inline +#endif + +/*============================================================================== + * + */ + +/*------------------------------------------------------------------------------ + * Line control -- collecting lines of a given width for output. + * + * NB: a completely zero structure is a valid, clear vio_line_control. + */ + +typedef struct vio_line_control* vio_line_control ; +typedef struct vio_line_control vio_line_control_t ; +struct vio_line_control +{ + unsigned width ; /* console width -- 0 => HUGE */ + unsigned height ; /* console height -- 0 => indefinite */ + + unsigned pause ; /* number of lines to next pause + 0 => indefinite */ + bool paused ; /* true <=> last append stopped on pause */ + + unsigned col ; /* current column position */ + unsigned line ; /* line number of last complete line */ + + struct qiovec qiov ; /* iovec control */ + bool writing ; /* write started, but not completed */ +} ; + +/*============================================================================== + * Functions + */ +extern vio_line_control vio_lc_init_new(vio_line_control lc, int width, + int height) ; +extern vio_line_control vio_lc_reset(vio_line_control lc, bool free_structure) ; + +#define vio_lc_reset_keep(lc) vio_lc_reset(lc, 0) +#define vio_lc_reset_free(lc) vio_lc_reset(lc, 1) + +Inline bool vio_lc_empty(vio_line_control lc) ; +extern void vio_lc_clear(vio_line_control lc) ; +extern void vio_lc_set_window(vio_line_control lc, int width, int height) ; + +extern void vio_lc_set_pause(vio_line_control lc) ; +extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) ; +extern int vio_lc_write_nb(int fd, vio_line_control lc) ; + +/*------------------------------------------------------------------------------ + * Is given line control empty ? + */ +Inline bool +vio_lc_empty(vio_line_control lc) +{ + return qiovec_empty(&lc->qiov) ; +} ; + +#endif /* _ZEBRA_VIO_LINES_H */ @@ -23,6 +23,7 @@ #include "zebra.h" #include <stdbool.h> +#include "version.h" #include "vty_io.h" #include "vty.h" @@ -32,15 +33,13 @@ #include "list_util.h" #include "command.h" +#include "command_queue.h" #include "memory.h" #include "log.h" +#include "mqueue.h" /*============================================================================== - * Variables etc. - */ - -/*------------------------------------------------------------------------------ - * Static and Global (see uty.h) Variables + * Variables etc. (see uty.h) */ /* The mutex and related debug counters */ @@ -48,8 +47,8 @@ qpt_mutex_t vty_mutex ; #if VTY_DEBUG -int vty_lock_count = 0 ; -int vty_lock_assert_fail = 0 ; +int vty_lock_count = 0 ; +int vty_assert_fail = 0 ; #endif @@ -107,10 +106,25 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; /*------------------------------------------------------------------------------ * Prototypes */ -static void uty_reset (bool final) ; +static void uty_reset (bool final, const char* why) ; static void uty_init_commands (void) ; static void vty_save_cwd (void) ; +/*------------------------------------------------------------------------------ + * Tracking the initialisation state. + */ +enum vty_init_state +{ + vty_init_pending = 0, /* first and lowest numbered state */ + vty_init_1st_stage, + vty_init_2nd_stage, + vty_init_started, + vty_init_reset, + vty_init_terminated /* final and highest numbered state */ +}; + +static enum vty_init_state vty_init_state ; + /*============================================================================== * Public Interface */ @@ -119,11 +133,19 @@ static void vty_save_cwd (void) ; * Initialise vty handling (threads and pthreads) * * Install vty's own commands like `who' command. + * + * This runs before any pthreads or nexus stuff starts -- so is, implicitly, + * in the CLI thread. + * + * NB: may be called once and once only. */ extern void vty_init (struct thread_master *master_thread) { - VTY_LOCK() ; + VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ + VTY_ASSERT_CLI_THREAD() ; /* True if !qpthreads_enabled */ + + assert(vty_init_state == vty_init_pending) ; vty_master = master_thread; /* Local pointer to the master thread */ @@ -140,6 +162,8 @@ vty_init (struct thread_master *master_thread) uty_init_commands() ; /* install nodes */ + vty_init_state = vty_init_1st_stage ; + VTY_UNLOCK() ; } @@ -149,30 +173,28 @@ vty_init (struct thread_master *master_thread) * This is done during "second stage" initialisation, when all nexuses have * been set up and the qpthread_enabled state established. * + * This is before any threads have been started, so is, implicitly, in the + * CLI thread. + * * Need to know where the CLI nexus and the Routeing Engine nexus are. * * Initialise mutex. + * + * Cannot lock or assert in CLI thread while initialising those things ! + * + * NB: may be called once and once only. */ extern void vty_init_r (qpn_nexus cli, qpn_nexus cmd) { + assert(vty_init_state == vty_init_1st_stage) ; + vty_cli_nexus = cli ; vty_cmd_nexus = cmd ; qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); -} ; -/*------------------------------------------------------------------------------ - * 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() ; + vty_init_state = vty_init_2nd_stage ; } ; /*------------------------------------------------------------------------------ @@ -189,86 +211,243 @@ vty_init_vtysh (void) } ; /*------------------------------------------------------------------------------ - * Create a new VTY of the given type + * Start the VTY going. + * + * This starts the listeners for VTY_TERM and VTY_SHELL_SERV. + * + * Also starts the watch dog. + * + * This is run during early morning start, after any daemonisation, but before + * any threads are started -- so is, implicitly, in the CLI thread. + * + * NB: may be called once and once only. + * + * NB: MUST be in the CLI thread (if any). */ -extern struct vty * -vty_new (int fd, enum vty_type type) +extern void +vty_start(const char *addr, unsigned short port, const char *path) { - struct vty* vty ; - VTY_LOCK() ; - vty = uty_new(fd, type); - VTY_UNLOCK() ; + VTY_ASSERT_CLI_THREAD() ; - return vty ; + assert( (vty_init_state == vty_init_1st_stage) + || (vty_init_state == vty_init_2nd_stage) ) ; + + uty_watch_dog_start() ; + + uty_open_listeners(addr, port, path) ; + + vty_init_state = vty_init_started ; + VTY_UNLOCK() ; } ; /*------------------------------------------------------------------------------ - * Close the given VTY completely + * Reset all VTY status for reasons unknown -- probably SIGHUP */ extern void -vty_close (struct vty *vty) +vty_reset() { - VTY_LOCK() ; - uty_close(vty->vio); - VTY_UNLOCK() ; + vty_reset_because("Reset") ; } /*------------------------------------------------------------------------------ * Reset all VTY status * - * This is done just before the configuration file is re-read (SIGHUP). + * This is done in response to SIGHUP -- and runs in the CLI thread. * - * Half closes all VTY, leaving the death watch to tidy up once all output has - * completed. + * Half closes all VTY, leaving the death watch to tidy up once all output + * and any command in progress have completed. * - * NB: old code discarded all output and hard closed all the VTY... + * Closes all listening sockets. * - * TODO: ...SIGHUP while a command is queued ? + * TODO: revoke ? * - * Closes all listening sockets. + * NB: old code discarded all output and hard closed all the VTY... */ extern void -vty_reset(void) +vty_reset_because(const char* why) { VTY_LOCK() ; - uty_reset(0) ; /* not final ! */ + VTY_ASSERT_CLI_THREAD() ; + + assert(vty_init_state == vty_init_started) ; + + uty_reset(0, why) ; /* not final ! */ + + vty_init_state = vty_init_reset ; VTY_UNLOCK() ; } /*------------------------------------------------------------------------------ + * Restart the VTY, following a vty_reset(). + * + * This starts the listeners for VTY_TERM and VTY_SHELL_SERV, again. + * + * NB: may be called once, and once only, *after* a vty_reset(). + * + * NB: need not be in the CLI thread (if any). + */ +struct vty_restart_args +{ + char* addr ; + unsigned short port ; + char* path ; +} ; +MQB_ARGS_SIZE_OK(vty_restart_args) ; + +static void uty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; +static void uty_restart(const char *addr, unsigned short port, + const char *path) ; +extern void +vty_restart(const char *addr, unsigned short port, const char *path) +{ + VTY_LOCK() ; + + /* If not running qnexus-wise, call uty_restart directly. + * + * Otherwise, construct and dispatch message to do a uty_restart. + */ + if (!vty_cli_nexus) + uty_restart(addr, port, path) ; + else + { + mqueue_block mqb ; + struct vty_restart_args* args ; + + mqb = mqb_init_new(NULL, uty_restart_action, vty_cli_nexus) ; + args = mqb_get_args(mqb) ; + + if (addr != NULL) + args->addr = XSTRDUP(MTYPE_TMP, addr) ; + else + args->addr = NULL ; + + args->port = port ; + + if (path != NULL) + args->path = XSTRDUP(MTYPE_TMP, path) ; + else + args->path = NULL ; + + mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; + } ; + + VTY_UNLOCK() ; +} ; + +/* Deal with the uty_restart message */ +static void +uty_restart_action(mqueue_block mqb, mqb_flag_t flag) +{ + struct vty_restart_args* args ; + args = mqb_get_args(mqb) ; + + if (flag == mqb_action) + { + VTY_LOCK() ; + + uty_restart(args->addr, args->port, args->path) ; + + VTY_UNLOCK() ; + } ; + + if (args->addr != NULL) + XFREE(MTYPE_TMP, args->addr) ; + if (args->path != NULL) + XFREE(MTYPE_TMP, args->path) ; +} ; + +/* Do the actual restart */ +static void +uty_restart(const char *addr, unsigned short port, const char *path) +{ + VTY_ASSERT_LOCKED() ; + assert(vty_init_state == vty_init_reset) ; + + uty_open_listeners(addr, port, path) ; + + vty_init_state = vty_init_started ; +} ; + +/*------------------------------------------------------------------------------ * System shut-down * - * Reset all known vty and release all memory. + * In the pthreads world, all threads other than the main (CLI) thread have + * been joined -- so this is, implicitly, in the CLI thread. + * + * Close all known vty and release all memory -- discard all pending output. + * + * NB: this may be done in any initialisation state. + * + * Note that all the locking stuff does nothing if not qpthreads_enabled, so + * these may be done in any state of initialisation. (It is assumed that the + * switch into qpthreads_enabled is an atomic action... so all second stage + * initialisation completes together.) */ extern void vty_terminate (void) { + if ( (vty_init_state == vty_init_pending) + || (vty_init_state == vty_init_terminated) ) + return ; /* nothing to do ! */ + VTY_LOCK() ; - uty_reset(1) ; /* final reset */ + VTY_ASSERT_CLI_THREAD() ; + + assert( (vty_init_state > vty_init_pending) + && (vty_init_state < vty_init_terminated) ) ; + + uty_reset(1, "Shut down") ; /* final reset */ + VTY_UNLOCK() ; qpt_mutex_destroy(&vty_mutex, 0); + + vty_init_state = vty_init_terminated ; } /*------------------------------------------------------------------------------ * Reset -- final or for SIGHUP + * + * Closes listeners. + * + * Closes (final) or half closes (SIGHUP) all VTY, and revokes any outstanding + * commands. + * + * Resets the vty timeout and access lists. + * + * When reach final reset it should not be possible for there to be any + * commands still in progress. If there are, they are simply left on the + * death-watch list... there is no pressing need to do anything more radical, + * and the presence of anything on the death watch is grounds for some debug + * activity ! */ static void -uty_reset (bool curtains) +uty_reset (bool curtains, const char* why) { vty_io vio ; + vty_io next ; VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; uty_close_listeners() ; - while ((vio = sdl_pop(&vio, vio_list_base, vio_list)) != NULL) + next = sdl_head(vio_list_base) ; + while (next != NULL) { - uty_half_close(vio) ; /* TODO: reason for close */ + vio = next ; + next = sdl_next(vio, vio_list) ; + + cq_revoke(vio->vty) ; + + if (why != NULL) + vio->close_reason = why ; if (curtains) - uty_full_close(vio) ; + uty_close(vio) ; + else + uty_half_close(vio, why) ; } ; vty_timeout_val = VTY_TIMEOUT_DEFAULT; @@ -287,9 +466,48 @@ uty_reset (bool curtains) if (curtains && vty_cwd) XFREE (MTYPE_TMP, vty_cwd); + + if (curtains) + uty_watch_dog_stop() ; /* and final death watch run */ } ; /*============================================================================== + * Opening and closing VTY. + * + * VTY without a socket may be opened and closed at will. + * + * TODO: sort out the relationship between the non-socket VTY and vty_reset() + */ + +/*------------------------------------------------------------------------------ + * Create a new VTY of the given type + * + * The type may NOT be: VTY_TERM or VTY_SHELL_SERV + */ +extern struct vty * +vty_open(enum vty_type type) +{ + struct vty* vty ; + + VTY_LOCK() ; + vty = uty_new(type, -1) ; /* fails for VTY_TERM or VTY_SHELL_SERV */ + VTY_UNLOCK() ; + + return vty ; +} ; + +/*------------------------------------------------------------------------------ + * Close the given VTY + */ +extern void +vty_close (struct vty *vty) +{ + VTY_LOCK() ; + uty_close(vty->vio) ; + VTY_UNLOCK() ; +} + +/*============================================================================== * General VTY output. * * This is mostly used during command execution, to output the results of the @@ -324,17 +542,14 @@ vty_out (struct vty *vty, const char *format, ...) const char vty_spaces_string[] = " " ; CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ; -extern int +extern void vty_out_indent(struct vty *vty, int indent) { - while (indent > VTY_MAX_SPACES) + while (indent > 0) { - int ret = vty_out(vty, VTY_SPACES(indent)) ; - if (ret < 0) - return ret ; + vty_out(vty, VTY_SPACES(indent)) ; indent -= VTY_MAX_SPACES ; } - return vty_out(vty, VTY_SPACES(indent)) ; } ; /*------------------------------------------------------------------------------ @@ -394,81 +609,91 @@ vty_hello (struct vty *vty) VTY_UNLOCK() ; } +/*------------------------------------------------------------------------------ + * Clear the contents of the command output FIFO etc. + */ +extern void +vty_out_clear(struct vty* vty) +{ + VTY_LOCK() ; + uty_out_clear(vty->vio) ; + VTY_UNLOCK() ; +} ; + /*============================================================================== * Command Execution */ /*------------------------------------------------------------------------------ - * Execute command, adding it to the history if not empty or comment + * Execute command -- adding to history is not empty or just comment + * + * This is for VTY_TERM type VTY. * * Outputs diagnostics if fails to parse. * - * Returns: CMD_xxxx result. + * Returns: command return code */ -extern int -uty_command(struct vty *vty, const char *buf) +extern enum cmd_return_code +uty_command(struct vty *vty) { - int ret; - vector vline; - const char *protocolname; + enum cmd_return_code ret; VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - /* Split readline string up into the vector */ - vline = cmd_make_strvec (buf); + assert(vty->vio->type == VTY_TERM) ; - if (vline == NULL) - return CMD_SUCCESS; /* quit if empty or comment */ - - uty_cli_hist_add (vty->vio, buf) ; + /* Parse the command and add to history (if not empty) */ + ret = cmd_parse_command(vty, + cmd_parse_completion + cmd_parse_do + cmd_parse_tree) ; + if (ret != CMD_EMPTY) + uty_cli_hist_add (vty->vio, vty->buf) ; + /* If parsed and not empty, dispatch */ + if (ret == CMD_SUCCESS) + { #ifdef CONSUMED_TIME_CHECK - { - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; - GETRUSAGE(&before); + GETRUSAGE(&before); #endif /* CONSUMED_TIME_CHECK */ -//VTY_UNLOCK() ; - ret = cmd_execute_command (vline, vty, NULL, vty_cmd_nexus, vty_cli_nexus, 0); -//VTY_LOCK() ; - - /* Get the name of the protocol if any */ - protocolname = uzlog_get_proto_name(NULL); + ret = cmd_dispatch(vty, cmd_may_queue) ; #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); - } + 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, vty->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; -} + /* Deal with the return code */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE); + break; + + case CMD_ERR_NO_MATCH: + uty_out (vty, "%% Unknown command.%s", VTY_NEWLINE) ; + break; + + case CMD_ERR_INCOMPLETE: + uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE); + break; + + default: + break ; + } ; + + return ret ; +} ; /*------------------------------------------------------------------------------ * Authentication of vty @@ -479,19 +704,21 @@ uty_command(struct vty *vty, const char *buf) * Note that if the AUTH_NODE password fails too many times, the terminal is * closed. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code 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 *); + enum cmd_return_code ret ; vty_io vio = vty->vio ; VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; /* What to do ? * @@ -502,20 +729,23 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) { case cli_do_nothing: case cli_do_ctrl_c: - case cli_do_ctrl_d: case cli_do_ctrl_z: - return 0 ; + return CMD_SUCCESS ; case cli_do_command: break ; + case cli_do_ctrl_d: + case cli_do_eof: + return uty_cmd_close(vty, "End") ; + default: zabort("unknown or invalid cli_do") ; } ; /* Ordinary command dispatch -- see if password is OK. */ switch (vty->node) - { + { case AUTH_NODE: if (host.encrypt) passwd = host.password_encrypt; @@ -526,6 +756,7 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) else next_node = VIEW_NODE; break; + case AUTH_ENABLE_NODE: if (host.encrypt) passwd = host.enable_encrypt; @@ -533,7 +764,10 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) passwd = host.enable; next_node = ENABLE_NODE; break; - } + + default: + zabort("unknown node type") ; + } if (passwd) { @@ -545,6 +779,8 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) else fail = 1; + ret = CMD_SUCCESS ; + if (! fail) { vio->fail = 0; @@ -557,9 +793,7 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) { if (vty->node == AUTH_NODE) { - uty_out (vty, "%% Bad passwords, too many failures!%s", - VTY_NEWLINE); - uty_half_close(vio) ; + ret = uty_cmd_close(vty, "Bad passwords, too many failures!%s") ; } else { @@ -568,11 +802,13 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) uty_out (vty, "%% Bad enable passwords, too many failures!%s", VTY_NEWLINE); vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; + + ret = CMD_WARNING ; } } } - return 0 ; + return ret ; } ; /*------------------------------------------------------------------------------ @@ -580,13 +816,16 @@ uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) * * Falls back one NODE level. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code vty_cmd_exit(struct vty* vty) { - VTY_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + enum cmd_return_code ret ; + + VTY_LOCK() ; + ret = CMD_SUCCESS ; switch (vty->node) { case VIEW_NODE: @@ -594,8 +833,8 @@ vty_cmd_exit(struct vty* vty) case RESTRICTED_NODE: if (vty_shell (vty)) exit (0); -// else -// vty_set_status(vty, VTY_CLOSE); + else + ret = uty_cmd_close(vty, "Exit") ; break; case CONFIG_NODE: uty_config_unlock (vty, ENABLE_NODE); @@ -628,8 +867,8 @@ vty_cmd_exit(struct vty* vty) break; } - VTY_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ - return 0 ; + VTY_UNLOCK() ; + return ret ; } /*------------------------------------------------------------------------------ @@ -637,12 +876,12 @@ vty_cmd_exit(struct vty* vty) * * Falls back to ENABLE_NODE. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code vty_cmd_end(struct vty* vty) { - VTY_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + VTY_LOCK() ; switch (vty->node) { @@ -676,8 +915,22 @@ vty_cmd_end(struct vty* vty) break; } - VTY_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ - return 0 ; + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Result of command is to close the input. + * + * Posts the reason for the close. + * + * Returns: CMD_CLOSE + */ +extern enum cmd_return_code +uty_cmd_close(struct vty *vty, const char* reason) +{ + vty->vio->close_reason = reason ; + return CMD_CLOSE ; } ; /*------------------------------------------------------------------------------ @@ -689,9 +942,9 @@ vty_cmd_end(struct vty* vty) * * Resets the history pointer. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code uty_stop_input(struct vty *vty) { vty_io vio = vty->vio ; @@ -724,7 +977,7 @@ uty_stop_input(struct vty *vty) /* Set history pointer to the latest one. */ vio->hp = vio->hindex; - return 0 ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ @@ -734,9 +987,9 @@ uty_stop_input(struct vty *vty) * * Fall back to ENABLE_NODE if in any one of a number of nodes. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code uty_end_config (struct vty *vty) { VTY_ASSERT_LOCKED() ; @@ -774,7 +1027,7 @@ uty_end_config (struct vty *vty) break; } - return 0 ; + return CMD_SUCCESS ; } /*------------------------------------------------------------------------------ @@ -782,9 +1035,9 @@ uty_end_config (struct vty *vty) * * Same as "exit" command. * - * Returns: 0 <=> not queued. + * Returns: command return code */ -extern int +extern enum cmd_return_code uty_down_level (struct vty *vty) { return vty_cmd_exit(vty) ; @@ -792,10 +1045,28 @@ uty_down_level (struct vty *vty) /*============================================================================== * Reading of configuration file + * + * The reading of the configuration file occurs at two times: + * + * 1. early in the morning, before daemonisation, and before any threads + * or nexuses have been set up. + * + * In the qpthreads world, this means that it is running in the main (CLI) + * and only thread and nexus. + * + * 2. at SIGHUP time. + * + * In the qpthreads world, this is running in whatever thread is executing + * commands. + * + * Sets up a VTY_CONFIG_READ in which to execute commands. This has no CLI + * and no socket. All output is buffered in the cmd_obuf. All commands are + * run directly in the thread -- no commands are queued. */ static FILE * vty_use_backup_config (char *fullpath) ; -static void vty_read_file (FILE *confp, void (*after_first_cmd)(void)) ; +static void vty_read_file (FILE *confp, struct cmd_element* first_cmd, + bool ignore_warnings) ; /*------------------------------------------------------------------------------ * Read the given configuration file. @@ -804,7 +1075,7 @@ extern void vty_read_config (char *config_file, char *config_default) { - vty_read_config_first_cmd_special(config_file, config_default, NULL); + vty_read_config_first_cmd_special(config_file, config_default, NULL, 1); } /*------------------------------------------------------------------------------ @@ -827,7 +1098,8 @@ vty_read_config (char *config_file, extern void vty_read_config_first_cmd_special(char *config_file, char *config_default, - void (*after_first_cmd)(void)) + struct cmd_element* first_cmd, + bool ignore_warnings) { char cwd[MAXPATHLEN]; FILE *confp = NULL; @@ -902,7 +1174,7 @@ vty_read_config_first_cmd_special(char *config_file, fprintf(stderr, "Reading config file: %s\n", fullpath); #endif - vty_read_file (confp, after_first_cmd); + vty_read_file (confp, first_cmd, ignore_warnings); fclose (confp); host_config_set (fullpath); @@ -998,15 +1270,28 @@ vty_use_backup_config (char *fullpath) * 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. + * + * In the qpthreads world: + * + * * when the configuration is first read, this runs in the CLI thread + * (the main and only thread). + * + * * when the configuration is reread, this runs in the command processor + * thread. + * + * All consoles are shut down, so there can be no interference from that + * quarter. + * + * so all commands are executed directly. */ static void -vty_read_file (FILE *confp, void (*after_first_cmd)(void)) +vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings) { - int ret; - struct vty *vty; + enum cmd_return_code ret ; + struct vty *vty ; - /* TODO: sort out what VTY Type should use for reading config file */ - vty = vty_new (0, VTY_TERM); /* stdout */ + /* Set up configuration file reader VTY -- which buffers all output */ + vty = vty_open(VTY_CONFIG_READ); vty->node = CONFIG_NODE; /* Make sure we have a suitable buffer, and set vty->buf to point at @@ -1016,60 +1301,51 @@ vty_read_file (FILE *confp, void (*after_first_cmd)(void)) vty->buf = qs_chars(&vty->vio->clx) ; /* Execute configuration file */ - ret = config_from_file (vty, confp, after_first_cmd, &vty->vio->clx) ; + ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx, + ignore_warnings) ; VTY_LOCK() ; - if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) ) + if (ret != CMD_SUCCESS) { + fprintf (stderr, "%% while processing line %u of the configuration:\n" + "%s", vty->lineno, vty->buf) ; + 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->buf); + { + case CMD_WARNING: + fprintf (stderr, "%% Warning...\n"); + break; - exit (1); - } + case CMD_ERROR: + fprintf (stderr, "%% Error...\n"); + break; - uty_half_close (vty->vio); - VTY_UNLOCK() ; -} + case CMD_ERR_AMBIGUOUS: + fprintf (stderr, "%% Ambiguous command.\n"); + break; -#ifdef QDEBUG -/* Tell all terminals that we are shutting down */ -void -vty_goodbye (void) -{ - unsigned int i; - struct vty *vty; + case CMD_ERR_NO_MATCH: + fprintf (stderr, "%% There is no such command.\n"); + break; - VTY_LOCK() ; + case CMD_ERR_INCOMPLETE: + fprintf (stderr, "%% Incomplete command.\n"); + break; - 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); + default: + fprintf(stderr, "%% (unknown cause %d)\n", ret) ; + break ; + } ; - /* Wake up */ - if (vty_cli_nexus) - vty_event (VTY_WRITE, vty->vio->fd, vty); - } - } - if (qpthreads_enabled) - qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); - } + uty_out_fflush(vty->vio, stderr) ; /* flush command output buffer */ - VTY_UNLOCK() ; -} -#endif + exit (1); + } ; + + uty_close(vty->vio) ; + VTY_UNLOCK() ; +} ; /*============================================================================== * Configuration node/state handling @@ -1197,8 +1473,9 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) timeout += strtol (sec_str, NULL, 10); vty_timeout_val = timeout; - vty->vio->file.v_timeout = timeout; -// vty_event (VTY_TIMEOUT_RESET, 0, vty); + + if (vty_term(vty) || vty_shell_serv(vty)) + uty_sock_set_timer(&vty->vio->sock, timeout) ; VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1445,7 +1722,7 @@ DEFUN_CALL (show_history, for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) { - const char* line ; + qstring line ; if (index == VTY_MAXHIST) { @@ -1455,7 +1732,7 @@ DEFUN_CALL (show_history, line = vector_get_item(&vty->vio->hist, index) ; if (line != NULL) - uty_out (vty, " %s%s", line, VTY_NEWLINE); + uty_out (vty, " %s%s", line->char_body, VTY_NEWLINE); index++; } @@ -1545,27 +1822,37 @@ vty_get_cwd () * Access functions for VTY values, where locking is or might be required. */ -int +bool vty_shell (struct vty *vty) { + bool result; VTY_LOCK() ; - int result; - result = (vty->vio->type == VTY_SHELL) ? 1 : 0 ; + result = (vty->vio->type == VTY_SHELL) ; VTY_UNLOCK() ; return result; } -int +bool +vty_term(struct vty *vty) +{ + bool result; + VTY_LOCK() ; + result = (vty->vio->type == VTY_TERM); + VTY_UNLOCK() ; + return result; +} + +bool vty_shell_serv (struct vty *vty) { + bool result; VTY_LOCK() ; - int result; - result = ((vty->vio->type == VTY_SHELL_SERV) ? 1 : 0); + result = (vty->vio->type == VTY_SHELL_SERV); VTY_UNLOCK() ; return result; } -int +enum node_type vty_get_node(struct vty *vty) { int result; @@ -1576,39 +1863,129 @@ vty_get_node(struct vty *vty) } void -vty_set_node(struct vty *vty, int node) +vty_set_node(struct vty *vty, enum node_type node) { VTY_LOCK() ; vty->node = node; VTY_UNLOCK() ; } -int -vty_get_type(struct vty *vty) +void +vty_set_lines(struct vty *vty, int lines) { - int result; VTY_LOCK() ; - result = vty->vio->type; + vty->vio->lines = lines; + vty->vio->lines_set = 1 ; + uty_set_height(vty->vio) ; VTY_UNLOCK() ; - return result; } -int -vty_get_lines(struct vty *vty) +/*============================================================================== + * + */ +const char* wordlist[] = + { + "Lorem", + "ipsum", + "dolor", + "magna", + "vita", + "brevis", + "Aliquot", + "in", + "tempura", + "mores", + "ad", + "Astronomica", + "per", + "impedimenta", + "quod", + "et", + "sed", + "semper", + "ut", + "Elisium", + "est", + }; + + +DEFUN (delay_secs, + delay_secs_cmd, + "delay <0-600> secs <0-10000> lines", + "Delay for a number of seconds and spit out a number of lines\n" + "Delay time\n" + "Delay time units\n" + "How much to output\n" + "Output units\n") { - int result; - VTY_LOCK() ; - result = vty->vio->lines; - VTY_UNLOCK() ; - return result; -} + unsigned long delay ; + unsigned long lines ; -void -vty_set_lines(struct vty *vty, int lines) -{ - VTY_LOCK() ; - vty->vio->lines = lines; - VTY_UNLOCK() ; + unsigned long unit ; + + delay = strtol(argv[0], NULL, 10) ; + lines = strtol(argv[1], NULL, 10) ; + + vty_out(vty, "delay %d secs %d lines\n", (int)delay, (int)lines) ; + + unit = (lines * 100) / delay ; + + while (delay--) + { + char buf[200] ; + char* e ; + int n ; + int w = sizeof(wordlist) / sizeof(char*) ; + + sleep(1) ; + + n = ((rand() % (unit + 1)) + (unit / 2)) / 100 ; + + if ((n > (int)lines) || (delay == 0)) + n = lines ; + + lines -= n ; + + while (n--) + { + char* q ; + const char* p ; + int a ; + + q = buf ; + e = buf + (rand() % 120) + 30 ; + + if ((rand() % 6) == 0) + e = buf ; + + a = (rand() % 4) == 1 ; + while (q < e) + { + int s ; + s = 0 ; + if (a == 1) + s = (rand() % 5) + 1 ; + else if (a > 1) + s = 1 ; + + while (s--) + *q++ = ' ' ; + + p = wordlist[rand() % w] ; + while (*p != '\0') + *q++ = *p++ ; + + a = (rand() % 4) + 1 ; + } ; + + *q++ = '\n' ; + *q++ = '\0' ; + + vty_out(vty, buf) ; + } ; + } ; + + return CMD_SUCCESS; } /*============================================================================== @@ -1632,6 +2009,9 @@ uty_init_commands (void) install_node (&vty_node, vty_config_write); + install_element (VIEW_NODE, &delay_secs_cmd); + install_element (ENABLE_NODE, &delay_secs_cmd); + install_element (RESTRICTED_NODE, &config_who_cmd); install_element (RESTRICTED_NODE, &show_history_cmd); install_element (VIEW_NODE, &config_who_cmd); diff --git a/lib/vty.c.x b/lib/vty.c.x deleted file mode 100644 index bed6fc28..00000000 --- a/lib/vty.c.x +++ /dev/null @@ -1,4414 +0,0 @@ -/* - * 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 @@ -33,6 +33,8 @@ #include "qtimers.h" #include "qpnexus.h" #include "list_util.h" +#include "vector.h" +#include "qstring.h" #include "node_type.h" /* Macro in case there are particular compiler issues. */ @@ -58,16 +60,42 @@ enum { VTYSH_ENABLED = VTYSH_DEFINED } ; #undef VTYSH_DEFINED /*============================================================================== + * VTY Types + */ +enum vty_type +{ + VTY_NONE = 0, /* no type at all */ + + VTY_TERM, /* a telnet terminal -- input and output */ + VTY_SHELL_SERV, /* a vty_shell slave -- input and output */ + + VTY_CONFIG_READ, /* reading config file -- output is to buffer + -- no input */ + + VTY_CONFIG_WRITE, /* writing config file -- output is to file + -- no input */ + + VTY_STDOUT, /* general output -- output is to stdout + -- no input */ + + VTY_STDERR, /* general output -- output is to stderr + -- no input */ + + VTY_SHELL, /* vty in vtysh -- output is to stdout */ +} ; + +/*============================================================================== * VTY struct. */ typedef struct vty_io* vty_io ; /* private to vty.c */ +struct cmd_parsed ; /* in case vty.h expanded before command.h */ + struct vty { /*---------------------------------------------------------------------- - * The following are used outside vty.c, and represent the context - * in which commands are executed. + * The following are the context in which commands are executed. */ /* Node status of this vty @@ -75,15 +103,7 @@ struct 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 ; + enum node_type node ; /* For current referencing point of interface, route-map, access-list * etc... @@ -91,62 +111,49 @@ struct vty * 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 ; + 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 ; + void *index_sub ; /* String which is newline... read only -- no locking */ const char* newline ; + /*---------------------------------------------------------------------------- + * The current command line. + * + * These are set when a command is parsed and dispatched. + * + * They are not touched until the command completes -- so may be read while + * the command is being parsed and executed. + */ + const char* buf ; + struct cmd_parsed* parsed ; + unsigned lineno ; + /*---------------------------------------------------------------------- * The following are used inside vty.c only. */ - /* Pointer to related vty_io structure -- if any. */ + /* Pointer to related vty_io structure */ vty_io vio ; }; /*------------------------------------------------------------------------------ - * VTY events + * Can now include this */ -enum vty_event -{ - VTY_SERV, - VTY_READ, - VTY_WRITE, - VTY_TIMEOUT_RESET, - - VTYSH_SERV, - VTYSH_READ, - VTYSH_WRITE -}; -/*------------------------------------------------------------------------------ - * VTY Types - */ -enum vty_type -{ - VTY_TERM, /* a telnet session -- input and output */ - VTY_FILE, /* writing config file -- output is to file - -- no input */ - - VTY_STDOUT, /* reading config file -- output is to stdout - -- no input */ - - VTY_SHELL, /* vty in vtysh -- output is to stdout */ - VTY_SHELL_SERV /* vty in daemon -- input and output */ -} ; +#include "command.h" /*------------------------------------------------------------------------------ * */ #define VTY_BUFSIZ 512 -#define VTY_MAXHIST 20 +#define VTY_MAXHIST 51 /* Integrated configuration file. */ #define INTEGRATE_DEFAULT_CONFIG "Quagga.conf" @@ -241,37 +248,37 @@ extern char integrate_default[]; */ 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, +extern void vty_start(const char *addr, unsigned short port, const char *path) ; +#define vty_serv_sock(addr, port, path) vty_start(addr, port, path) +extern void vty_restart(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 struct vty* vty_open(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 void vty_reset_because(const char* why) ; extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); -extern int vty_out_indent(struct vty *vty, int indent) ; +extern void vty_out_indent(struct vty *vty, int indent) ; +extern void vty_out_clear(struct vty *vty) ; + +extern void vty_read_config (char *config_file, char *config_default); +extern void vty_read_config_first_cmd_special( + char *config_file, char *config_default, + struct cmd_element* first_cmd, bool ignore_warnings) ; -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 char *vty_get_cwd (void); -extern int vty_shell (struct vty *); -extern int vty_shell_serv (struct vty *); +extern bool vty_shell (struct vty *); +extern bool vty_term (struct vty *); +extern bool vty_shell_serv (struct vty *); extern void vty_hello (struct vty *); -extern int vty_get_node(struct vty *); -extern void vty_set_node(struct vty *, int); -extern int vty_get_type(struct vty *); -extern int vty_get_status(struct vty *); -extern void vty_set_status(struct vty *, int); -extern int vty_get_lines(struct vty *); +extern enum node_type vty_get_node(struct vty *); +extern void vty_set_node(struct vty *, enum node_type); extern void vty_set_lines(struct vty *, int); -#ifdef QDEBUG -extern void vty_goodbye (void); -#endif - #endif /* _ZEBRA_VTY_H */ diff --git a/lib/vty_cli.c b/lib/vty_cli.c index de9bb53c..53d64716 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -26,10 +26,12 @@ #include "keystroke.h" #include "vty.h" #include "uty.h" -#include "vty_io.h" #include "vty_cli.h" +#include "vty_io.h" +#include "vio_lines.h" #include "command.h" +#include "command_queue.h" #include "memory.h" @@ -130,60 +132,41 @@ uty_new_host_name(const char* name) * * 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. + * cli_blocked -- the CLI is unable to process any further keystrokes. * - * * on write_ready: + * cmd_in_progress -- a command has been dispatched and has not yet + * completed (may have been queued). * - * - empty out the CLI buffer + * cmd_out_enabled -- the command FIFO is may be emptied. * - * - if ! cmd_in_progress: + * This is set when a command completes, and cleared when + * everything is written away. * - * * empty out the command buffer + * cli_more_wait -- is in "--more--" wait state * - * * if the command buffer is empty, clear cli_blocked (if was set) + * The following are the valid combinations: * - * - generate a read_ready event unless write() would block. + * blkd : cip : o_en : m_wt : + * -----:------:------:------:-------------------------------------------- + * 0 : 0 : 0 : 0 : collecting a new command + * 0 : 1 : 0 : 0 : command dispatched + * 1 : 1 : 0 : 0 : waiting for (queued) command to complete + * 1 : 0 : 1 : 0 : waiting for command output to complete + * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away + * 0 : 0 : 0 : 1 : waiting for "--more--" response + * 1 : 1 : 1 : 0 : waiting for command to complete, after the + * CLI has been closed * - * 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 + * There are two output FIFOs: * - * * clear cmd_in_progress + * 1. for the CLI itself -- uty_cli_out and friends * - * * set cli_blocked + * 2. for output generated by commands -- vty_out and friends. * - * - set write on (or read on) as required. + * The CLI FIFO is emptied whenever possible, in preference to the command + * FIFO. The command FIFO is emptied when cmd_out_enabled. While + * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a + * given command is collected together before being sent to the file. * * Note that only sets read on when the keystroke stream is empty and has not * yet hit eof. The CLI process is driven mostly by write_ready -- which @@ -203,13 +186,13 @@ uty_new_host_name(const char* name) * * 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: + * While cmd_in_progress is true cmd_out_enabled will be false. When the + * command completes: * * * cmd_in_progress is cleared * + * * cmd_out_enabled is set + * * * cli_blocked will be set * * * the line_control structure is reset @@ -218,10 +201,10 @@ uty_new_host_name(const char* name) * * 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.) + * main CLI. (See the cli_more_wait flag and its handling.) * - * When all the output has completed, cli_blocked is cleared and the CLI will - * be kicked. + * When all the output has completed the CLI will be kicked, which will see + * that the output buffer is now empty, and it can proceed. * * It is expected that the output will end with a newline -- so that when the * CLI is kicked, the cursor will be at the start of an empty line. @@ -275,70 +258,191 @@ uty_new_host_name(const char* name) */ /*============================================================================== - * Command Line Interface + * The CLI + */ + +#define CONTROL(X) ((X) - '@') + +static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; +static enum vty_readiness uty_cli_standard(vty_io vio) ; +static enum vty_readiness uty_cli_more_wait(vty_io vio) ; +static void uty_cli_draw(vty_io vio) ; +static void uty_cli_draw_this(vty_io vio, enum node_type node) ; +static void uty_cli_wipe(vty_io vio, int len) ; + +static void uty_will_echo (vty_io vio) ; +static void uty_will_suppress_go_ahead (vty_io vio) ; +static void uty_dont_linemode (vty_io vio) ; +static void uty_do_window_size (vty_io vio) ; +static void uty_dont_lflow_ahead (vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Initialise CLI. * - * State of the CLI: + * It is assumed that the following have been initialised, empty or zero: + * + * cli_prompt_for_node + * cl + * clx + * cli_vbuf + * cli_obuf * - * cli_blocked -- a command has been dispatched, and now waiting - * for it and/or its output to complete. + * cli_drawn + * cli_dirty * - * cmd_in_progress -- a command has been dispatched (and may have been - * queued). + * cli_prompt_set * - * Can continue in the CLI until another command is - * ready to be executed. + * cli_blocked + * cmd_in_progress + * cmd_out_enabled + * cli_wait_more + * + * cli_more_enabled + * + * Sets the CLI such that there is apparently a command in progress, so that + * further initialisation (in particular hello messages and the like) is + * treated as a "start up command". + * + * Sends a suitable set of Telnet commands to start the process. + */ +extern void +uty_cli_init(vty_io vio) +{ + assert(vio->type == VTY_TERM) ; + + vio->cmd_in_progress = 1 ; + vio->cli_blocked = 1 ; + + vio->cli_do = cli_do_nothing ; + + /* Setting up terminal. */ + uty_will_echo (vio); + uty_will_suppress_go_ahead (vio); + uty_dont_linemode (vio); + uty_do_window_size (vio); + if (0) + uty_dont_lflow_ahead (vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Start the CLI. + * + * All start-up operations are complete -- so the "command" is now complete. + * + * Returns: write_ready -- so the first event is a write event, to flush + * any output to date. + */ +extern enum vty_readiness +uty_cli_start(vty_io vio) +{ + uty_cli_cmd_complete(vio, CMD_SUCCESS) ; + return write_ready ; +} ; + +/*------------------------------------------------------------------------------ + * Close the CLI + * + * Note that if any command is revoked, then will clear cmd_in_progress and + * set cmd_out_enabled -- so any output can now clear. + */ +extern void +uty_cli_close(vty_io vio) +{ + cq_revoke(vio->vty) ; + + vio->cli_blocked = 1 ; /* don't attempt any more */ + vio->cmd_out_enabled = 1 ; /* allow output to clear */ +} ; + +/*------------------------------------------------------------------------------ + * CLI for VTY_TERM + * + * Do nothing at all if half closed. + * + * Otherwise do: standard CLI + * or: "--more--" CLI + * + * NB: on return, requires that an attempt is made to write away anything that + * may be ready for that. + */ +extern enum vty_readiness +uty_cli(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; + + if (vio->half_closed) + return not_ready ; /* Nothing more if half closed */ + + /* Standard or "--more--" CLI ? */ + if (vio->cli_more_wait) + return uty_cli_more_wait(vio) ; + else + return uty_cli_standard(vio) ; +} ; + +/*============================================================================== + * The Standard CLI */ -static 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) ; +static bool uty_cli_dispatch(vty_io vio) ; /*------------------------------------------------------------------------------ - * Run the CLI until: + * Standard CLI for VTY_TERM -- if not blocked, runs 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 ! + * + * Returns: not_ready blocked and was blocked when entered + * write_ready if there is anything in the keystroke stream + * read_ready otherwise */ -extern void -uty_cli(vty_io vio) +static enum vty_readiness +uty_cli_standard(vty_io vio) { - bool won ; - VTY_ASSERT_LOCKED() ; + assert(vio->type == VTY_TERM) ; /* cli_blocked is set when is waiting for a command, or its output to - * complete. + * complete -- unless either of those has happened, is still blocked. * - * There is no good reason to arrive here in that state, and nothing to be - * done if that happens. + * NB: in both these cases, assumes that other forces are at work to + * keep things moving. */ if (vio->cli_blocked) - return ; + { + assert(vio->cmd_in_progress || vio->cmd_out_enabled) ; - /* 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 + if (vio->cmd_in_progress) + { + assert(!vio->cmd_out_enabled) ; + return not_ready ; + } ; + + if (!vio_fifo_empty(&vio->cmd_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + vio->cmd_out_enabled = 0 ; + } ; + + /* If there is nothing pending, then can run the CLI until there is * something to do, or runs out of input. + * + * If there is something to do, that is because a previous command has + * now completed, which may have wiped the pending command or changed + * the required prompt. */ - if (vio->cli_do != cli_do_nothing) - uty_cli_draw(vio, vio->vty->node) ; - else + if (vio->cli_do == cli_do_nothing) vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + else + uty_cli_draw_this(vio, vio->vty->node) ; /* If have something to do, do it. */ if (vio->cli_do != cli_do_nothing) @@ -357,73 +461,21 @@ uty_cli(vty_io vio) vio->cli_blocked = 1 ; } ; - /* If there is anything in the CLI output FIFO, must set write on to clear - * it. + /* Use write_ready as a proxy for read_ready on the keystroke stream. * - * If there is anything in the command output FIFO *and* is !cmd_in_progress, - * must set write on to clear it. + * Also, if the command line is not drawn, then return write_ready, so + * that * - * 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. + * Note that if has just gone cli_blocked, still returns ready. This is + * defensive: at worst will generate one unnecessary read_ready/write_ready + * event. */ - 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) ; + if (keystroke_stream_empty(vio->key_stream)) + return read_ready ; + else + return write_ready ; } ; -/* 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. * @@ -432,19 +484,19 @@ uty_cli(vty_io vio) * 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 + * Returns: true <=> command completed and output is pending + * false => command has been queued and is now in progress * * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. * * Can set vio->cl_do = and vio->cl to be a follow-on command. */ -static int +static bool uty_cli_dispatch(vty_io vio) { qstring_t tmp ; enum cli_do cli_do ; - int queued ; + enum cmd_return_code ret ; struct vty* vty = vio->vty ; @@ -455,7 +507,8 @@ uty_cli_dispatch(vty_io vio) * * Clear vio->cl and vio->cl_do. */ - vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ + vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ + vio->cmd_out_enabled = 0 ; /* => collect all output */ tmp = vio->clx ; /* swap clx and cl */ vio->clx = vio->cl ; @@ -466,18 +519,17 @@ uty_cli_dispatch(vty_io vio) cli_do = vio->cli_do ; /* current operation */ vio->cli_do = cli_do_nothing ; /* clear */ - qs_set_empty(&vio->cl) ; /* set cl empty (with '\0') */ + qs_clear(&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 */ + uty_out_clear(vio) ; /* clears FIFO and line control */ /* Dispatch command */ if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) { /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ - queued = uty_auth(vty, vty->buf, cli_do) ; + ret = uty_auth(vty, vty->buf, cli_do) ; } else { @@ -488,23 +540,27 @@ uty_cli_dispatch(vty_io vio) break ; case cli_do_command: - queued = uty_command(vty, vty->buf) ; + ret = uty_command(vty) ; break ; case cli_do_ctrl_c: - queued = uty_stop_input(vty) ; + ret = uty_stop_input(vty) ; break ; case cli_do_ctrl_d: - queued = uty_down_level(vty) ; + ret = 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 ; + ret = uty_command(vty) ; + if (ret == CMD_QUEUED) + vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ else - queued = uty_end_config(vty) ; + ret = uty_end_config(vty) ; /* do the ^Z now */ + break ; + + case cli_do_eof: + ret = uty_cmd_close(vio->vty, "End") ; break ; default: @@ -512,13 +568,16 @@ uty_cli_dispatch(vty_io vio) } ; } ; - if (!queued) + if (ret == CMD_QUEUED) + { + uty_cli_draw(vio) ; /* draw the prompt */ + return 0 ; /* command not complete */ + } + else { - vio->cmd_in_progress = 0 ; /* command complete */ - vty->buf = NULL ; /* finished with command line */ + uty_cli_cmd_complete(vio, ret) ; + return 1 ; /* command complete */ } ; - - return ! (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) ; } ; /*------------------------------------------------------------------------------ @@ -528,7 +587,7 @@ uty_cli_dispatch(vty_io vio) * or not... write_ready will kick read_ready. */ extern void -vty_queued_result(struct vty *vty, int result, int action) +vty_queued_result(struct vty *vty, enum cmd_return_code ret) { vty_io vio ; @@ -536,20 +595,210 @@ vty_queued_result(struct vty *vty, int result, int action) vio = vty->vio ; - vio->cmd_in_progress = 0 ; /* command complete */ - vty->buf = NULL ; /* finished with command line */ + if (!vio->closed) + { + uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ - vio->cli_blocked = !vio_fifo_empty(&vio->cmd_obuf) ; - /* blocked if output is now pending */ + /* Do the command completion actions that were deferred because the + * command was queued. + * + * Return of CMD_QUEUED => command was revoked before being executed. + * However interesting that might be... frankly don't care. + */ + uty_cli_cmd_complete(vio, ret) ; - uty_cli_wipe(vio) ; /* wipe any partly constructed line */ + /* Kick the socket -- to write away any outstanding output, and + * re-enter the CLI when that's done. + */ + uty_sock_set_readiness(&vio->sock, write_ready) ; + } + else + { + /* If the VTY is closed, the only reason it still exists is because + * there was cmd_in_progress. + */ + vio->cmd_in_progress = 0 ; + + uty_close(vio) ; /* Final close */ + } ; - uty_file_set_write(&vty->vio->file, on) ; - /* flush any output -- which will do a - * read_ready when all finished */ VTY_UNLOCK() ; } +/*------------------------------------------------------------------------------ + * Command has completed, so: + * + * * clear cmd_in_progress + * * set cmd_out_enabled -- so any output can now proceed + * * set cli_blocked -- waiting for output to complete + * * and prepare the line control for output + * + * If the return is CMD_CLOSE, then also now does the required half close. + * + * Note that apart from CMD_CLOSE, don't really care what the return was. Any + * diagnostics or other action must be dealt with elsewhere (as part of the + * command execution. + * + * Note that everything proceeds as if there is some output. So after every + * command goes through at least one write_ready event. + * + * This ensures some multiplexing at the command level. + * + * It also means that the decision about whether there is anything to output + * is left to the output code. + */ +static void +uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; + + if (ret == CMD_CLOSE) + uty_half_close(vio, NULL) ; + + vio->cmd_in_progress = 0 ; /* command complete */ + vio->cmd_out_enabled = 1 ; /* enable the output */ + vio->cli_blocked = 1 ; /* now blocked waiting for output */ + + vio->vty->buf = NULL ; /* finished with command line */ + + uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ +} ; + +/*============================================================================== + * The "--more--" CLI + * + * While command output is being cleared from its FIFO, the CLI is cli_blocked. + * + * When the output side signals that "--more--" is required, it sets the + * cli_more_wait flag and clears the cmd_out_enabled flag. + * + * The first stage of handling "--more--" is to suck the input dry, so that + * (as far as is reasonably possible) does not steal a keystroke as the + * "--more--" response which was typed before the prompt was issued. + * + * The cli_blocked flag indicates that the CLI is in this first stage. + */ + +/*------------------------------------------------------------------------------ + * Change the CLI to the "--more--" CLI. + * + * Outputs the new prompt line. + */ +extern void +uty_cli_go_more_wait(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; + + uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is + wiped before change the CLI state */ + + vio->cmd_out_enabled = 0 ; /* stop output pro tem */ + vio->cli_more_wait = 1 ; /* new state */ + + uty_cli_draw(vio) ; /* draw the "--more--" */ +} ; + +/*------------------------------------------------------------------------------ + * Handle the "--more--" state. + * + * Deals with the first stage if cli_blocked. + * + * Tries to steal a keystroke, and when succeeds wipes the "--more--" + * prompt and exits cli_more_wait -- and may cancel all outstanding output. + * + * EOF on input causes immediate exit from cli_more_state. + * + * Returns: read_ready -- waiting to steal a keystroke + * now_ready -- just left cli_more_wait + * not_ready -- otherwise + */ +static enum vty_readiness +uty_cli_more_wait(vty_io vio) +{ + struct keystroke steal ; + + VTY_ASSERT_LOCKED() ; + + /* Deal with the first stage of "--more--" */ + if (vio->cli_blocked) + { + int get ; + + /* If the CLI buffer is not yet empty, then is waiting for the + * initial prompt to clear, so nothing to be done here. + */ + if (!vio_fifo_empty(&vio->cli_obuf)) + return not_ready ; + + vio->cli_blocked = 0 ; + + /* empty the input buffer into the keystroke stream */ + do + { + get = uty_read(vio, NULL) ; + } while (get > 0) ; + } ; + + /* Go through the "--more--" process, unless no longer write_open (!) */ + if (vio->sock.write_open) + { + /* The read fetches a reasonable lump from the I/O -- so if there + * is a complete keystroke available, expect to get it. + * + * If no complete keystroke available to steal, returns ks_null. + * + * If has hit EOF (or error etc), returns knull_eof. + */ + uty_read(vio, &steal) ; + + /* If nothing stolen, make sure prompt is drawn and wait for more + * input. + */ + if ((steal.type == ks_null) && (steal.value != knull_eof)) + { + if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */ + return write_ready ; + else + return read_ready ; + } ; + + /* Stolen a keystroke -- a (very) few terminate all output */ + if (steal.type == ks_char) + { + switch (steal.value) + { + case CONTROL('C'): + case 'q': + case 'Q': + uty_out_clear(vio) ; + break; + + default: /* everything else, thrown away */ + break ; + } ; + } ; + } ; + + /* End of "--more--" process + * + * Wipe out the prompt and update state. + * + * Return write_ready to tidy up the screen and, unless cleared, write + * some more. + */ + uty_cli_wipe(vio, 0) ; + + vio->cli_blocked = 1 ; /* back to blocked waiting for output */ + vio->cli_more_wait = 0 ; /* exit more_wait */ + vio->cmd_out_enabled = 1 ; /* re-enable output */ + + return now_ready ; +} ; + /*============================================================================== * CLI VTY output * @@ -600,21 +849,17 @@ 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, ...) +uty_cli_out(vty_io vio, const char *format, ...) { VTY_ASSERT_LOCKED() ; - if (vio->file.write_open) + if (vio->sock.write_open) { - va_list args; - int len ; + va_list args ; va_start (args, format); - len = qs_vprintf(&vio->cli_vbuf, format, args) ; + vio_fifo_vprintf(&vio->cli_obuf, format, args) ; va_end(args); - - if (len > 0) - uty_cli_write(vio, qs_chars(&vio->cli_vbuf), len) ; } ; } ; @@ -624,11 +869,11 @@ uty_cli_out (vty_io vio, const char *format, ...) * 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) +uty_cli_echo(vty_io vio, const char *this, size_t len) { VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->file.write_open) + if (vio->cli_echo_suppress || !vio->sock.write_open) return ; uty_cli_write(vio, this, len) ; @@ -644,7 +889,7 @@ 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) + if (vio->cli_echo_suppress || !vio->sock.write_open) return ; uty_cli_write_n(vio, chars, n) ; @@ -658,7 +903,7 @@ uty_cli_write(vty_io vio, const char *this, int len) { VTY_ASSERT_LOCKED() ; - if (vio->file.write_open) + if (vio->sock.write_open) vio_fifo_put(&vio->cli_obuf, this, len) ; } ; @@ -704,13 +949,14 @@ uty_cli_write_s(vty_io vio, const char *str) /*------------------------------------------------------------------------------ * Send newline to the console. * - * Clears the cli_drawn flag. + * Clears the cli_drawn and the cli_dirty flags. */ static void uty_cli_out_newline(vty_io vio) { uty_cli_write(vio, telnet_newline, 2) ; vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; } ; /*------------------------------------------------------------------------------ @@ -747,16 +993,18 @@ uty_cli_out_wipe_n(vty_io vio, int n) static const char* cli_response [2][cli_do_count] = { { /* when not waiting for previous command to complete */ - [cli_do_command] = "", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", + [cli_do_command] = "", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" }, { /* when waiting for a previous command to complete */ - [cli_do_command] = "^", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", + [cli_do_command] = "^", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + [cli_do_eof] = "^*" } } ; @@ -766,7 +1014,7 @@ uty_cli_response(vty_io vio, enum cli_do cli_do) const char* str ; int len ; - if (cli_do == cli_do_nothing) + if ((cli_do == cli_do_nothing) || (vio->half_closed)) return ; str = (cli_do < cli_do_count) @@ -811,36 +1059,72 @@ uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) /*------------------------------------------------------------------------------ * Wipe the current console line -- if any. */ -extern void -uty_cli_wipe(vty_io vio) +static void +uty_cli_wipe(vty_io vio, int len) { int a ; int b ; - if (!vio->cli_drawn == 0) + if (!vio->cli_drawn) 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 + /* Establish how much ahead and how much behind the cursor */ + a = vio->cli_extra_len ; + b = vio->cli_prompt_len ; + + if (!vio->cli_echo_suppress && !vio->cli_more_wait) { - b = vio->cl.cp ; /* behind cursor */ - a = vio->cl.len - b ; /* ahead of cursor */ - } + a += vio->cl.len - vio->cl.cp ; + b += vio->cl.cp ; + } ; - /* Stuff ahead of the current position */ - uty_cli_out_wipe_n(vio, a + vio->cli_extra_len) ; + /* Stuff ahead of the current position if any ahead of new len */ + if ((a + b) > len) + uty_cli_out_wipe_n(vio, +a) ; - /* Stuff behind the current position */ - uty_cli_out_wipe_n(vio, vio->cli_prompt_len + b) ; + /* Stuff behind current position, but ahead of new len */ + if (b > len) + { + uty_cli_out_wipe_n(vio, -(b - len)) ; + b = len ; /* moved the cursor back */ + } ; + + /* Back to the beginning of the line */ + uty_cli_write_n(vio, telnet_backspaces, b) ; + /* Nothing there any more */ vio->cli_drawn = 0 ; + vio->cli_dirty = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * If not currently drawn, draw prompt etc according to the current state + * and node. + * + * See uty_cli_draw(). + */ +extern bool +uty_cli_draw_if_required(vty_io vio) +{ + if (vio->cli_drawn) + return false ; + + uty_cli_draw(vio) ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt etc for the current vty node. + * + * See uty_cli_draw_this() + */ +static void +uty_cli_draw(vty_io vio) +{ + uty_cli_draw_this(vio, vio->vty->node) ; } ; /*------------------------------------------------------------------------------ @@ -851,34 +1135,52 @@ uty_cli_wipe(vty_io vio) * * 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. + * Draws prompt according to the given 'node', except: + * + * * if is half_closed, draw nothing -- wipes the current line + * + * * if is cli_more_wait, draw the "--more--" prompt + * + * * if is cmd_in_progress, draw the vestigial prompt. + * + * By the time the current command completes, the node may have changed, so + * the current prompt may be invalid. * * Sets: cli_drawn = true + * cli_dirty = false * cli_prompt_len = length of prompt used * cli_extra_len = 0 * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) */ static void -uty_cli_draw(vty_io vio, enum node_type node) +uty_cli_draw_this(vty_io vio, enum node_type node) { const char* prompt ; + size_t l_len ; + int p_len ; - 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) ; + if (vio->cli_dirty) + uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */ /* Sort out what the prompt is. */ - - if (vio->cmd_in_progress) + if (vio->half_closed) + { + prompt = "" ; + p_len = 0 ; + l_len = 0 ; + } + else if (vio->cli_more_wait) + { + prompt = "--more--" ; + p_len = strlen(prompt) ; + l_len = 0 ; + } + else if (vio->cmd_in_progress) { /* If there is a queued command, the prompt is a minimal affair. */ prompt = "~ " ; - vio->cli_prompt_len = strlen(prompt) ; + p_len = strlen(prompt) ; + l_len = vio->cl.len ; } else { @@ -909,27 +1211,99 @@ uty_cli_draw(vty_io vio, enum node_type node) vio->cli_prompt_set = 1 ; } ; - prompt = qs_chars(&vio->cli_prompt_for_node) ; - vio->cli_prompt_len = vio->cli_prompt_for_node.len ; + prompt = vio->cli_prompt_for_node.body ; + p_len = vio->cli_prompt_for_node.len ; + l_len = vio->cl.len ; } ; - uty_cli_write(vio, prompt, vio->cli_prompt_len) ; + /* Now, if line is currently drawn, time to wipe it */ + if (vio->cli_drawn) + uty_cli_wipe(vio, p_len + l_len) ; + + /* Set about writing the prompt and the line */ + vio->cli_drawn = 1 ; + vio->cli_extra_len = 0 ; + vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + vio->cli_prompt_len = p_len ; + + uty_cli_write(vio, prompt, p_len) ; - if ((vio->cl.len != 0) && !vio->cli_echo_suppress) + if (l_len != 0) { - 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) ; + uty_cli_write(vio, qs_chars(&vio->cl), l_len) ; + if (vio->cl.cp < l_len) + uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ; } ; } ; /*============================================================================== - * Command line processing loop + * Monitor output. + * + * To prepare for monitor output, wipe as much as is necessary for the + * monitor line to appear correctly. + * + * After monitor output, may need to do two things: + * + * * if the output was incomplete, place the rump in the CLI buffer, + * so that: + * + * a. don't mess up the console with partial lines + * + * b. don't lose part of a message + * + * c. act as a brake on further monitor output -- cannot do any more + * until the last, part, line is dealt with. + * + * * restore the command line, unless it is empty ! */ -#define CONTROL(X) ((X) - '@') + /*----------------------------------------------------------------------------- + * Prepare for new monitor output line. + * + * Wipe any existing command line. + */ +extern void +uty_cli_pre_monitor(vty_io vio, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_wipe(vio, len) ; +} ; -static void uty_telnet_command(vty_io vio, keystroke stroke) ; +/*------------------------------------------------------------------------------ + * Recover from monitor line output. + * + * If monitor line failed to complete, append the rump to the CLI buffer. + * + * If have a non-empty command line, or is cli_more_wait, redraw the command + * line. + * + * Returns: 0 => rump was empty and no command line stuff written + * > 0 => rump not empty or some command line stuff written + */ +extern int +uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (len != 0) + uty_cli_write(vio, buf, len) ; + + if (vio->cli_more_wait || (vio->cl.len != 0)) + { + uty_cli_draw(vio) ; + ++len ; + } ; + + return len ; +} ; + +/*============================================================================== + * Command line processing loop + */ + +static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ; static int uty_cli_insert (vty_io vio, const char* chars, int n) ; static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; static int uty_cli_word_overwrite (vty_io vio, char *str) ; @@ -999,10 +1373,13 @@ uty_cli_process(vty_io vio, enum node_type node) while (1) { if (!vio->cli_drawn) - uty_cli_draw(vio, node) ; + uty_cli_draw_this(vio, node) ; if (!uty_cli_get_keystroke(vio, &stroke)) - break ; + { + ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; + break ; + } ; if (stroke.flags != 0) { @@ -1160,10 +1537,10 @@ uty_cli_process(vty_io vio, enum node_type node) /* Telnet Command ----------------------------------------------------*/ case ks_iac: - uty_telnet_command(vio, &stroke) ; + uty_telnet_command(vio, &stroke, false) ; break ; - /* Single byte escape ------------------------------------------------*/ + /* Unknown -----------------------------------------------------------*/ default: zabort("unknown keystroke type") ; } ; @@ -1185,95 +1562,6 @@ uty_cli_process(vty_io vio, enum node_type node) } ; /*============================================================================== - * 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 */ @@ -1289,7 +1577,7 @@ uty_cli_insert (vty_io vio, const char* chars, int n) VTY_ASSERT_LOCKED() ; - assert((vio->cl.cp <= vio->cl.len)&& (n >= 0)) ; + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; if (n <= 0) return n ; /* avoid trouble */ @@ -1359,12 +1647,12 @@ uty_cli_word_overwrite (vty_io vio, char *str) static int uty_cli_forwards(vty_io vio, int n) { - int c ; + int have ; VTY_ASSERT_LOCKED() ; - c = vio->cl.len - vio->cl.cp ; - if (n > c) - n = c ; + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; assert(n >= 0) ; @@ -1385,17 +1673,18 @@ uty_cli_forwards(vty_io vio, int n) 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 ; + if ((int)vio->cl.cp < n) + n = vio->cl.cp ; assert(n >= 0) ; - uty_cli_echo_n(vio, telnet_backspaces, n) ; - vio->cl.cp -= n ; + if (n > 0) + { + uty_cli_echo_n(vio, telnet_backspaces, n) ; + vio->cl.cp -= n ; + } ; return n ; } @@ -1409,10 +1698,15 @@ static int uty_cli_del_forwards(vty_io vio, int n) { int after ; + int have ; VTY_ASSERT_LOCKED() ; - assert((vio->cl.len - vio->cl.cp) && (n >= 0)) ; + have = vio->cl.len - vio->cl.cp ; + if (have < n) + n = have ; /* cannot delete more than have */ + + assert(n >= 0) ; if (n <= 0) return 0 ; @@ -1425,8 +1719,6 @@ uty_cli_del_forwards(vty_io vio, int n) uty_cli_echo_n(vio, telnet_spaces, n) ; uty_cli_echo_n(vio, telnet_backspaces, after + n) ; - vio->cl.len -= n ; - return n ; } @@ -1660,31 +1952,18 @@ uty_cli_transpose_chars(vty_io vio) extern void uty_cli_hist_add (vty_io vio, const char* cmd_line) { - char* prev_line ; - char* line ; - char* e ; + qstring prev_line ; + qstring_t line ; int prev_index ; VTY_ASSERT_LOCKED() ; + /* Construct a dummy qstring for the given command line */ + qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ + /* make sure have a suitable history vector */ vector_set_min_length(&vio->hist, VTY_MAXHIST) ; - /* 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) @@ -1692,24 +1971,27 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) 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) ; + /* If the previous line is NULL, that means the history is empty. + * + * If the previous line is essentially the same as the current line, + * replace it with the current line -- so that the latest whitespace + * version is saved. + * + * Either way, replace the the previous line entry by moving hindex + * back ! + */ + if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) + vio->hindex = prev_index ; + else + prev_line = vector_get_item(&vio->hist, vio->hindex) ; - vector_set_item(&vio->hist, vio->hindex, line) ; + /* Now replace the hindex entry */ + vector_set_item(&vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; - /* History index rotation. */ - vio->hindex++; - if (vio->hindex == VTY_MAXHIST) - vio->hindex = 0; - } - else - { - XFREE(MTYPE_VTY_HIST, line) ; - } ; + /* Advance to the near future and reset the history pointer */ + vio->hindex++; + if (vio->hindex == VTY_MAXHIST) + vio->hindex = 0; vio->hp = vio->hindex; } ; @@ -1718,36 +2000,58 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * Replace command line by current history. * * This function is called from vty_next_line and vty_previous_line. + * + * Step +1 is towards the present + * -1 is into the past */ static void uty_cli_history_use(vty_io vio, int step) { int index ; - unsigned new_len ; unsigned old_len ; - char* hist ; + unsigned after ; + unsigned back ; + qstring hist ; VTY_ASSERT_LOCKED() ; - /* See if have anything usable */ + assert((step == +1) || (step == -1)) ; + index = vio->hp ; - if ((step > 0) && (index == vio->hindex)) - return ; /* cannot step forward past the insertion point */ + /* Special case of being at the insertion point */ + if (index == vio->hindex) + { + if (step > 0) + return ; /* already in the present */ + /* before stepping back from the present, take a copy of the + * current command line -- so can get back to it. + */ + hist = vector_get_item(&vio->hist, vio->hindex) ; + vector_set_item(&vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + } ; + + /* Advance or retreat */ index += step ; if (index < 0) index = VTY_MAXHIST - 1 ; - else if (index >= VTY_MAXHIST) ; + 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 */ + /* If moving backwards in time, may not move back to the insertion + * point (that would be wrapping round to the present) and may not + * move back to a NULL entry (that would be going back before '.'). + */ + if (step < 0) + if ((hist == NULL) || (index == vio->hindex)) + return ; + + /* Now, if arrived at the insertion point, this is returning to the + * present, which is fine. + */ vio->hp = index; /* Move back to the start of the current line */ @@ -1755,13 +2059,26 @@ uty_cli_history_use(vty_io vio, int step) /* 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 ; + qs_copy(&vio->cl, hist) ; + + /* Sort out wiping out any excess and setting the cursor position */ + if (old_len > vio->cl.len) + after = old_len - vio->cl.len ; + else + after = 0 ; - uty_cli_echo(vio, hist, new_len) ; + back = after ; + if (vio->cl.len > vio->cl.cp) + back += (vio->cl.len - vio->cl.cp) ; + + if (vio->cl.len > 0) + uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; + + if (after > 0) + uty_cli_echo_n(vio, telnet_spaces, after) ; - if (old_len < new_len) - uty_cli_del_to_eol(vio) ; + if (back > 0) + uty_cli_echo_n(vio, telnet_backspaces, back) ; return ; } ; @@ -1804,6 +2121,8 @@ uty_cli_complete_command (vty_io vio, enum node_type node) { unsigned i ; int ret ; + int len ; + int n ; vector matched ; vector vline ; @@ -1841,16 +2160,32 @@ uty_cli_complete_command (vty_io vio, enum node_type node) break ; case CMD_COMPLETE_LIST_MATCH: + len = 6 ; for (i = 0; i < vector_end(matched); i++) { - if ((i % 6) == 0) + int sl = strlen((char*)vector_get_item(matched, i)) ; + if (len < sl) + len = sl ; + } ; + + n = vio->width ; + if (n == 0) + n = 60 ; + n = n / (len + 2) ; + if (n == 0) + n = 1 ; + + for (i = 0; i < vector_end(matched); i++) + { + if ((i % n) == 0) uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out (vio, "%-10s ", (char*)vector_get_item(matched, i)); + uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i)); } + uty_cli_out_newline(vio) ; break; - case CMD_ERR_NOTHING_TODO: + case CMD_COMPLETE_ALREADY: default: break; } ; @@ -2052,7 +2387,7 @@ uty_cli_cmd_prepare(vty_io vio, int help) * VTY telnet stuff */ -#define TELNET_OPTION_DEBUG 1 /* 0 to turn off */ +#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ static const char* telnet_commands[256] = { @@ -2150,7 +2485,7 @@ uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) /*------------------------------------------------------------------------------ * Send telnet: "WILL TELOPT_ECHO" */ -extern void +static void uty_will_echo (vty_io vio) { unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; @@ -2161,7 +2496,7 @@ uty_will_echo (vty_io vio) /*------------------------------------------------------------------------------ * Send telnet: "suppress Go-Ahead" */ -extern void +static void uty_will_suppress_go_ahead (vty_io vio) { unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; @@ -2172,7 +2507,7 @@ uty_will_suppress_go_ahead (vty_io vio) /*------------------------------------------------------------------------------ * Send telnet: "don't use linemode" */ -extern void +static void uty_dont_linemode (vty_io vio) { unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; @@ -2183,7 +2518,7 @@ uty_dont_linemode (vty_io vio) /*------------------------------------------------------------------------------ * Send telnet: "Use window size" */ -extern void +static void uty_do_window_size (vty_io vio) { unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; @@ -2194,7 +2529,7 @@ uty_do_window_size (vty_io vio) /*------------------------------------------------------------------------------ * Send telnet: "don't use lflow" -- not currently used */ -extern void +static void uty_dont_lflow_ahead (vty_io vio) { unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; @@ -2203,20 +2538,42 @@ uty_dont_lflow_ahead (vty_io vio) } /*------------------------------------------------------------------------------ + * The keystroke iac callback function. + * + * This deals with IAC sequences that should be dealt with as soon as they + * are read -- not stored in the keystroke stream for later processing. + */ +extern bool +uty_cli_iac_callback(keystroke_iac_callback_args) +{ + return uty_telnet_command((vty_io)context, stroke, true) ; +} ; + +/*------------------------------------------------------------------------------ * Process incoming Telnet Option(s) * + * May be called during keystroke iac callback, or when processing CLI + * keystrokes. + * * In particular: get telnet window size. + * + * Returns: true <=> dealt with, for: + * + * * telnet window size. */ -static void -uty_telnet_command(vty_io vio, keystroke stroke) +static bool +uty_telnet_command(vty_io vio, keystroke stroke, bool callback) { uint8_t* p ; uint8_t o ; int left ; + bool dealt_with ; /* Echo to the other end if required */ if (TELNET_OPTION_DEBUG) { + uty_cli_wipe(vio, 0) ; + p = stroke->buf ; left = stroke->len ; @@ -2245,16 +2602,17 @@ uty_telnet_command(vty_io vio, keystroke stroke) } } ; - if (!(stroke->flags & kf_broken)) + if (stroke->flags & kf_broken) uty_cli_out (vio, "BROKEN") ; uty_cli_out (vio, "\r\n") ; - } ; /* Process the telnet command */ + dealt_with = false ; + if (stroke->flags != 0) - return ; /* go no further if broken */ + return dealt_with ; /* go no further if broken */ p = stroke->buf ; left = stroke->len ; @@ -2294,6 +2652,9 @@ uty_telnet_command(vty_io vio, keystroke stroke) uty_cli_out(vio, "TELNET NAWS window size received: " "width %d, height %d%s", vio->width, vio->height, telnet_newline) ; + uty_set_height(vio) ; + + dealt_with = true ; } ; break ; @@ -2305,4 +2666,6 @@ uty_telnet_command(vty_io vio, keystroke stroke) default: /* no other IAC X */ break ; } ; + + return dealt_with ; } ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h index 4fda2db8..5bf682ac 100644 --- a/lib/vty_cli.h +++ b/lib/vty_cli.h @@ -25,19 +25,23 @@ #define _ZEBRA_VTY_CLI_H #include "vty_io.h" +#include "keystroke.h" + +extern void uty_cli_init(vty_io vio) ; +extern enum vty_readiness uty_cli_start(vty_io vio) ; +extern void uty_cli_close(vty_io vio) ; + +extern enum vty_readiness uty_cli(vty_io vio) ; +extern keystroke_callback uty_cli_iac_callback ; -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_cli_go_more_wait(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) ; +extern bool uty_cli_draw_if_required(vty_io vio) ; + +extern void uty_cli_pre_monitor(vty_io vio, size_t len) ; +extern int uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) ; #endif /* _ZEBRA_VTY_CLI_H */ diff --git a/lib/vty_io.c b/lib/vty_io.c index 15f90219..7137fbdc 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -46,21 +46,12 @@ /*============================================================================== * 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. */ +static int uty_config_write(vty_io vio, bool all) ; + /*------------------------------------------------------------------------------ * VTY output function -- cf fprintf * @@ -84,71 +75,111 @@ uty_out (struct vty *vty, const char *format, ...) * * Returns: >= 0 => OK * < 0 => failed (see errno) + * + * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: + * + * * MAY NOT do any command output if !cmd_enabled + * + * * first, the life of a vty is not guaranteed unless cmd_in_progress, + * so should not attempt to use a vty anywhere other than command + * execution. + * + * * second, cmd_out_enabled is false most of the time, and is only + * set true when a command completes, and it is time to write away + * the results. + * + * * all output is placed in the vio->cmd_obuf. When the command completes, + * the contents of the cmd_obuf will be written away -- subject to line + * control. + * + * * output is discarded if the vty is no longer write_open */ extern int uty_vout(struct vty *vty, const char *format, va_list args) { - enum vty_type type ; vty_io vio ; - int len ; + int ret ; 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 ; - } ; + vio = vty->vio ; - /* Output -- process depends on type */ - switch (type) + switch (vio->type) { case VTY_STDOUT: case VTY_SHELL: - len = vprintf (format, args); + ret = vprintf (format, args) ; + break ; + + case VTY_STDERR: + ret = vfprintf (stderr, format, args) ; + break ; + + case VTY_CONFIG_WRITE: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; + if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) + ret = uty_config_write(vio, false) ; break ; - case VTY_FILE: case VTY_TERM: case VTY_SHELL_SERV: + assert(vio->cmd_in_progress) ; - 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) ; - } ; + if (!vio->sock.write_open) + return 0 ; /* discard output if not open ! */ + + /* fall through.... */ + + case VTY_CONFIG_READ: + ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; break ; default: zabort("impossible VTY type") ; } ; - return len; + return ret ; } ; /*------------------------------------------------------------------------------ - * Discard the current contents of the command FIFO + * Clear the contents of the command output FIFO etc. * - * TODO: worry about line control ?? + * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc + * flags -- competent parties must deal with those */ extern void -uty_out_discard(vty_io vio) +uty_out_clear(vty_io vio) { VTY_ASSERT_LOCKED() ; - vio_fifo_set_empty(&vio->cmd_obuf) ; + vio_fifo_clear(&vio->cmd_obuf) ; + + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Flush the contents of the command output FIFO to the given file. + * + * Takes no notice of any errors ! + */ +extern void +uty_out_fflush(vty_io vio, FILE* file) +{ + char* src ; + size_t have ; + + VTY_ASSERT_LOCKED() ; + + fflush(file) ; + + while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + { + fwrite(src, 1, have, file) ; + vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + } ; + + fflush(file) ; } ; /*============================================================================== @@ -167,30 +198,13 @@ 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) ; +static void uty_watch_dog_bark(void) ; +static bool uty_death_watch_scan(void) ; + /*------------------------------------------------------------------------------ - * Watch dog action + * Start watch dog -- the first time a VTY is created. */ -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 +extern void uty_watch_dog_start() { if (vty_cli_nexus) @@ -200,6 +214,12 @@ uty_watch_dog_start() uty_watch_dog_bark() ; /* start up by barking the first time */ } +/*------------------------------------------------------------------------------ + * Stop watch dog timer -- at close down. + * + * Final run along the death-watch + * + */ extern void uty_watch_dog_stop(void) { @@ -210,6 +230,8 @@ uty_watch_dog_stop(void) else thread_cancel(vty_watch_dog.thread) ; } ; + + uty_death_watch_scan() ; /* scan the death-watch list */ } /*------------------------------------------------------------------------------ @@ -219,7 +241,9 @@ static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) { VTY_LOCK() ; + uty_watch_dog_bark() ; + VTY_UNLOCK() ; } ; @@ -230,17 +254,77 @@ static int vty_watch_dog_thread(struct thread *thread) { VTY_LOCK() ; + + vty_watch_dog.thread = NULL ; uty_watch_dog_bark() ; + VTY_UNLOCK() ; return 0 ; } ; +/*------------------------------------------------------------------------------ + * Watch dog action + */ +static void +uty_watch_dog_bark(void) +{ + uty_check_host_name() ; /* check for host name change */ + + uty_death_watch_scan() ; /* scan the death-watch list */ + + /* Set timer to go off again later */ + if (vty_cli_nexus) + qtimer_set(vty_watch_dog.qnexus, + qt_add_monotonic(QTIME(vty_watch_dog_interval)), + vty_watch_dog_qnexus) ; + else + { + if (vty_watch_dog.thread != NULL) + thread_cancel (vty_watch_dog.thread); + vty_watch_dog.thread = thread_add_timer (vty_master, + vty_watch_dog_thread, NULL, vty_watch_dog_interval) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Scan the death watch list. + * + * A vty may finally be freed if it is closed and there is no command in + * progress. + */ +static bool +uty_death_watch_scan(void) +{ + vty_io vio ; + vty_io next ; + + next = vio_death_watch ; + while (next != NULL) + { + vio = next ; + next = sdl_next(vio, vio_list) ; + + if (vio->closed && !vio->cmd_in_progress) + { + uty_close(vio) ; /* closes again to ensure that all buffers + are released. */ + + sdl_del(vio_death_watch, vio, vio_list) ; + + XFREE(MTYPE_VTY, vio->vty) ; + XFREE(MTYPE_VTY, vio) ; + } ; + } ; + + return (vio_death_watch == NULL) ; +} ; + /*============================================================================== * 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 uty_sock_init_new(vio_sock sock, int fd, void* info) ; +static void uty_sock_half_close(vio_sock sock) ; +static void uty_sock_close(vio_sock sock) ; static void vty_read_qnexus (qps_file qf, void* file_info) ; static void vty_write_qnexus (qps_file qf, void* file_info) ; @@ -253,6 +337,8 @@ static int vty_timer_thread (struct thread *thread) ; static void vtysh_read_qnexus (qps_file qf, void* file_info) ; static int vtysh_read_thread (struct thread *thread) ; +static enum vty_readiness uty_write(vty_io vio) ; + /*============================================================================== * Creation and destruction of VTY objects */ @@ -260,109 +346,151 @@ static int vtysh_read_thread (struct thread *thread) ; /*------------------------------------------------------------------------------ * Allocate new vty struct * - * Allocates and initialises vty_io structure, complete with: + * Allocates and initialises basic vty and vty_io structures, setting the + * given type. * - * Output buffer - * Input buffer - * qpselect file -- added to CLI nexus ) if running CLI nexus - * qtimer ) + * Note that where is not setting up a vty_sock, this *may* be called from + * any thread. * - * Adds to the known vty's -- which locks/unlocks momentarily. + * NB: may not create a VTY_CONFIG_WRITE type vty directly + * + * see: vty_open_config_write() and vty_close_config_write() + * + * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. + * (So MUST be in the CLI thread to set those up !) + * + * the sock_fd is ignored for everything else. * * Returns: new vty */ extern struct vty * -uty_new (int fd, enum vty_type type) +uty_new(enum vty_type type, int sock_fd) { struct vty *vty ; struct vty_io* vio ; VTY_ASSERT_LOCKED() ; - if (vty_watch_dog.anon == NULL) - uty_watch_dog_start() ; + /* If this is a VTY_TERM or a VTY_SHELL, place */ + switch (type) + { + case VTY_TERM: /* Require fd -- Telnet session */ + case VTY_SHELL_SERV: /* Require fd -- Unix socket */ + assert(sock_fd >= 0) ; + break ; + + case VTY_CONFIG_WRITE: + zabort("may not make a new VTY_CONFIG_WRITE VTY") ; + break ; + case VTY_CONFIG_READ: + case VTY_STDOUT: + case VTY_STDERR: + case VTY_SHELL: + sock_fd = -1 ; /* No fd -- output to stdout/stderr */ + break ; + + default: + zabort("unknown VTY type") ; + } ; + + /* Basic allocation */ vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; vty->vio = vio ; vio->vty = vty ; - /* Zeroising the vty 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: * + * name = NULL -- no name, yet + * * vio_list both pointers NULL + * mon_list both pointers NULL * - * half_closed = 0 -- NOT half closed (important !) - * timed_out = 0 -- NOT timed out + * half_closed = 0 -- NOT half closed (important !) + * closed = 0 -- NOT closed (important !) + * close_reason = NULL -- no reason, yet * - * mon_list both pointers NULL + * real_type = 0 -- not material + * file_fd = 0 -- not material + * file_error = 0 -- not material * - * name = NULL -- no name, yet + * key_stream = NULL -- no key stream (always empty, at EOF) * - * cli_drawn = 0 -- not drawn + * cli_drawn = 0 -- not drawn + * cli_dirty = 0 -- not dirty * cli_prompt_len = 0 ) - * cli_extra_len = 0 ) not material + * 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_prompt_node = 0 -- not material + * cli_prompt_set = 0 -- so prompt needs to be constructed + * + * cli_blocked = 0 -- not blocked + * cmd_in_progress = 0 -- no command in progress + * cmd_out_enabled = 0 -- command output is disabled + * cli_wait_more = 0 -- not waiting for response to "--more--" + * + * cli_more_enabled = 0 -- not enabled for "--more--" * - * cli_blocked = 0 -- not blocked - * cmd_in_progress = 0 -- no command in progress + * cmd_out_done = 0 -- not material * - * cli_do = 0 = cli_do_nothing + * cli_do = 0 == cli_do_nothing * - * cmd_wait_more = 0 -- not waiting for response to "--more--" + * cmd_lc = NULL -- no line control * - * fail = 0 -- no login failures yet + * fail = 0 -- no login failures yet * * hist = empty vector - * hp = 0 -- at the beginning - * hindex = 0 -- the beginning + * hp = 0 -- at the beginning + * hindex = 0 -- the beginning * - * width = 0 -- unknown console width - * height = 0 -- unknown console height + * width = 0 -- unknown console width + * height = 0 -- unknown console height * - * lines = 0 -- unset + * lines = 0 -- no limit + * lines_set = 0 -- no explicit setting * - * monitor = 0 -- not a monitor + * monitor = 0 -- not a monitor + * monitor_busy = 0 -- not a busy monitor * - * config = 0 -- not holder of "config" mode + * config = 0 -- not holder of "config" mode */ confirm(cli_do_nothing == 0) ; + confirm(AUTH_NODE == 0) ; /* default node type */ vio->type = type ; - uty_file_init_new(&vio->file, fd, vio) ; - - vio->key_stream = keystroke_stream_new('\0') ; /* TODO: CSI ?? */ + /* Zeroising the vty structure has set: + * + * node = 0 TODO: something better for node value ???? + * buf = NULL -- no command line, yet + * parsed = NULL -- no parsed command, yet + * lineno = 0 -- nothing read, yet + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet + */ + if (type == VTY_TERM) + vty->newline = "\n" ; /* line control looks after "\r\n" */ + else + vty->newline = "\n" ; - qs_init_new(&vio->ibuf, 0) ; + /* Initialise the vio_sock, */ + uty_sock_init_new(&vio->sock, sock_fd, vio) ; + /* Make sure all buffers etc. are initialised clean and empty. + * + * Note that no buffers are actually allocated at this stage. + */ qs_init_new(&vio->cli_prompt_for_node, 0) ; qs_init_new(&vio->cl, 0) ; qs_init_new(&vio->clx, 0) ; - qs_init_new(&vio->cli_vbuf, 0) ; - vio_fifo_init_new(&vio->cli_obuf, 4 * 1024) ; /* allocate in 4K lumps */ + vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ - qs_init_new(&vio->cmd_vbuf, 0) ; - vio_fifo_init_new(&vio->cmd_obuf, 16 * 1024) ; + vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; /* Place on list of known vio/vty */ sdl_push(vio_list_base, vio, vio_list) ; @@ -376,29 +504,34 @@ uty_new (int fd, enum vty_type type) * Returns: new vty */ static struct vty * -uty_new_term(int vty_sock, union sockunion *su) +uty_new_term(int sock_fd, union sockunion *su) { struct vty *vty ; vty_io vio ; + enum vty_readiness ready ; VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; /* Allocate new vty structure and set up default values. */ - vty = uty_new (vty_sock, VTY_TERM) ; + vty = uty_new (VTY_TERM, sock_fd) ; vio = vty->vio ; - /* Set the action functions */ + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + + /* Set the socket action functions */ if (vty_cli_nexus) { - vio->file.action.read.qnexus = vty_read_qnexus ; - vio->file.action.write.qnexus = vty_write_qnexus ; - vio->file.action.timer.qnexus = vty_timer_qnexus ; + vio->sock.action.read.qnexus = vty_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.action.timer.qnexus = vty_timer_qnexus ; } else { - vio->file.action.read.thread = vty_read_thread ; - vio->file.action.write.thread = vty_write_thread ; - vio->file.action.timer.thread = vty_timer_thread ; + vio->sock.action.read.thread = vty_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = vty_timer_thread ; } ; /* The text form of the address identifies the VTY */ @@ -418,41 +551,38 @@ uty_new_term(int vty_sock, union sockunion *su) vty->node = AUTH_NODE; /* Pick up current timeout setting */ - vio->file.v_timeout = vty_timeout_val; + vio->sock.v_timeout = vty_timeout_val; - /* Use global 'lines' setting, otherwise is unset */ - if (host.lines >= 0) - vio->lines = host.lines; - else - vio->lines = -1; + /* Use global 'lines' setting, as default. May be -1 => unset */ + vio->lines = host.lines ; - /* 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) ; + /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ + vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; + uty_set_height(vio) ; /* set initial state */ - /* Set CLI into state waiting for output to complete. */ - vio->cli_blocked = 1 ; - uty_file_set_write(&vio->file, on) ; + /* Initialise the CLI, ready for start-up messages etc. */ + uty_cli_init(vio) ; /* Reject connection if password isn't set, and not "no password" */ if ((host.password == NULL) && (host.password_encrypt == NULL) && ! no_password_check) { - uty_out (vty, "Vty password is not set.%s", VTY_NEWLINE); - uty_half_close (vio); - return NULL; + uty_half_close (vio, "Vty password is not set."); + vty = NULL; } + else + { + /* Say hello to the world. */ + vty_hello (vty); - /* Say hello to the world. */ - vty_hello (vty); - - if (! no_password_check) - uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, + if (! no_password_check) + uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + } ; + + /* Now start the CLI and set a suitable state of readiness */ + ready = uty_cli_start(vio) ; + uty_sock_set_readiness(&vio->sock, ready) ; return vty; } ; @@ -463,7 +593,7 @@ uty_new_term(int vty_sock, union sockunion *su) * Returns: new vty */ static struct vty * -uty_new_shell_serv(int vty_sock) +uty_new_shell_serv(int sock_fd) { struct vty *vty ; vty_io vio ; @@ -471,30 +601,27 @@ uty_new_shell_serv(int vty_sock) VTY_ASSERT_LOCKED() ; /* Allocate new vty structure and set up default values. */ - vty = uty_new (vty_sock, VTY_SHELL_SERV) ; + vty = uty_new (VTY_SHELL_SERV, sock_fd) ; 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 ; + vio->sock.action.read.qnexus = vtysh_read_qnexus ; + vio->sock.action.write.qnexus = vty_write_qnexus ; + vio->sock.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 ; + vio->sock.action.read.thread = vtysh_read_thread ; + vio->sock.action.write.thread = vty_write_thread ; + vio->sock.action.timer.thread = NULL ; } ; vty->node = VIEW_NODE; - /* 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) ; + /* Kick start the CLI etc. */ + uty_sock_set_readiness(&vio->sock, write_ready) ; return vty; } ; @@ -512,7 +639,7 @@ uty_set_monitor(vty_io vio, bool on) if (on && !vio->monitor) { - if ((vio->type == VTY_TERM) && vio->file.write_open) + if ((vio->type == VTY_TERM) && vio->sock.write_open) { vio->monitor = 1 ; sdl_push(vio_monitors_base, vio, mon_list) ; @@ -539,50 +666,83 @@ uty_get_name(vty_io vio) /*------------------------------------------------------------------------------ * Closing down VTY for reading. * - * Shuts the read side and discards any buffered input. + * For VTY_TERM (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input, setting it to "EOF" + * * turns off any monitor status ! + * * drop down to RESTRICTED_NODE + * + * For VTY_SHELL_SERV (must be in CLI thread): + * + * * shut the socket for reading + * * discard all buffered input + * * drop down to RESTRICTED_NODE * - * 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. + * In all cases: + * + * * place on death watch + * * set the vty half_closed + * * sets the reason for closing (if any given) + * + * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all + * the buffers, the VTY is closed. + * + * May already have set the vio->close_reason, or can set it now. (Passing a + * NULL reason has no effect on any existing posted reason.) */ extern void -uty_half_close (vty_io vio) +uty_half_close (vty_io vio, const char* reason) { - char* line ; - VTY_ASSERT_LOCKED() ; if (vio->half_closed) - return ; /* cannot do it again */ - - vio->half_closed = 1 ; + return ; - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->file.fd) ; + if (reason != NULL) + vio->close_reason = reason ; - uty_file_half_close(&vio->file) ; + /* Do the file side of things + * + * Note that half closing the file sets a new timeout, sets read off + * and write on. + */ + uty_sock_half_close(&vio->sock) ; uty_set_monitor(vio, 0) ; - keystroke_stream_free(vio->key_stream) ; - qs_free_body(&vio->ibuf) ; + /* Discard everything in the keystroke stream and force it to EOF */ + if (vio->key_stream != NULL) + keystroke_stream_set_eof(vio->key_stream) ; - uty_cli_wipe(vio) ; - - while ((line = vector_ream_keep(&vio->hist)) != NULL) - XFREE(MTYPE_VTY_HIST, line) ; + /* Turn off "--more--" so that all output clears without interruption. + * + * Note that if is waiting for "--more--", then shutting the read side + * causes it to be readable, but EOF -- so that will flush through. + */ + vio->cli_more_enabled = 0 ; - /* Hit the width, height and lines so that all output clears without - * interruption. + /* If a command is not in progress, enable output, which will clear + * the output buffer if there is anything there, plus any close reason, + * and then close. + * + * If command is in progress, then this process will start when it + * completes. */ - vio->width = 0 ; - vio->height = 0 ; - vio->lines = 0 ; + if (!vio->cmd_in_progress) + vio->cmd_out_enabled = 1 ; /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, AUTH_NODE) ; + uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + + /* Log closing of VTY_TERM */ + if (vio->type == VTY_TERM) + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; /* Move to the death watch list */ sdl_del(vio_list_base, vio, vio_list) ; sdl_push(vio_death_watch, vio, vio_list) ; + + vio->half_closed = 1 ; } ; /*------------------------------------------------------------------------------ @@ -590,118 +750,185 @@ uty_half_close (vty_io vio) * * 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. + * If cmd_in_progress, cannot complete the process -- but sets the closed + * flag. + * + * Can call vty_close() any number of times. + * + * The vty structure is placed on death watch, which will finally free the + * structure once no longer cmd_in_progress. */ extern void uty_close (vty_io vio) { VTY_ASSERT_LOCKED() ; - uty_file_close(&vio->file) ; /* bring the file to a complete stop */ + /* Empty all the output buffers */ + vio_fifo_reset_keep(&vio->cli_obuf) ; + vio_fifo_reset_keep(&vio->cmd_obuf) ; + vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; + + /* If not already closed, close. */ + if (!vio->closed) + { + uty_half_close(vio, NULL) ; /* place on death watch -- if not + already done */ + uty_cli_close(vio) ; /* tell the CLI to stop */ + + vio->closed = 1 ; /* now closed (stop uty_write() + from recursing) */ + + if (vio->sock.write_open) + uty_write(vio) ; /* last gasp attempt */ - uty_half_close(vio) ; /* deal with the input side, and place on - death watch -- if not already done */ + uty_sock_close(&vio->sock) ; + + } ; + + /* Nothing more should happen, so can now release almost everything, + * the exceptions being the things that are related to a cmd_in_progress. + * + * All writing to buffers is suppressed, and as the sock has been closed, + * there will be no more read_ready or write_ready events. + */ + if (vio->name != NULL) + XFREE(MTYPE_VTY_NAME, vio->name) ; + + vio->key_stream = keystroke_stream_free(vio->key_stream) ; qs_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) ; + { + qstring line ; + while ((line = vector_ream_keep(&vio->hist)) != NULL) + qs_reset_free(line) ; + } ; - vio->vty->buf = NULL ; + /* The final stage cannot be completed if cmd_in_progress. + * + * The clx is pointed at by vty->buf -- containing the current command. + * + * Once everything is released, can take the vty off death watch, and + * release the vio and the vty. + */ + if (!vio->cmd_in_progress) + { + qs_free_body(&vio->clx) ; + vio->vty->buf = NULL ; + } ; } ; +/*============================================================================== + * For writing configuration file by command, temporarily redirect output to + * an actual file. + */ + /*------------------------------------------------------------------------------ - * 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. + * Set the given fd as the VTY_FILE output. */ extern void -uty_full_close (vty_io vio) +vty_open_config_write(struct vty* vty, int fd) { - VTY_ASSERT_LOCKED() ; + vty_io vio ; - uty_file_close(&vio->file) ; /* bring the file to a complete stop */ + VTY_LOCK() ; - uty_half_close(vio) ; /* deal with the input side, and place on - death watch -- if not already done */ + vio = vty->vio ; - 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) ; + assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; - vio_fifo_reset_keep(&vio->cli_obuf) ; - vio_fifo_reset_keep(&vio->cmd_obuf) ; + vio->real_type = vio->type ; + + vio->type = VTY_CONFIG_WRITE ; + vio->file_fd = fd ; + vio->file_error = 0 ; - vio->vty->buf = NULL ; + VTY_UNLOCK() ; } ; -/*============================================================================== - * Dealing with am I/O error on VTY - * - * If this is the first error for this VTY, produce suitable log message. +/*------------------------------------------------------------------------------ + * Write away configuration file stuff -- all or just the full lump(s). * - * If is a "monitor", turn that off, *before* issuing log message. + * Returns: > 0 => blocked + * 0 => all gone (up to last lump if !all) + * < 0 => failed -- see vio->file_error */ static int -uty_io_error(vty_io vio, const char* what) +uty_config_write(vty_io vio, bool all) { - /* can no longer be a monitor ! */ - uty_set_monitor(vio, 0) ; + int ret ; - /* if this is the first error, log it */ - if (vio->file.error_seen == 0) + VTY_ASSERT_LOCKED() ; + + if (vio->file_error == 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()") ; - } ; + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; - 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)) ; - } ; + if (ret < 0) + vio->file_error = errno ; + } + else + ret = -1 ; - return -1 ; + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Write away any pending stuff, and return the VTY to normal. + */ +extern int +vty_close_config_write(struct vty* vty) +{ + vty_io vio ; + int err ; + + VTY_LOCK() ; + + vio = vty->vio ; + + assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; + + uty_config_write(vio, true) ; /* write all that is left */ + + err = vio->file_error ; + + vio->type = vio->real_type ; + vio->file_fd = -1 ; + vio->file_error = 0 ; + + VTY_UNLOCK() ; + + return err ; } ; /*============================================================================== - * vio_file level operations + * vio_sock level operations */ /*------------------------------------------------------------------------------ - * Initialise a new vio_file structure. + * Initialise a new vio_sock structure. * - * Requires that: the vio_file structure is not currently in use. + * Requires that: the vio_sock structure is not currently in use. * - * if fd >= 0 then: file is open and ready read and write - * otherwise: file is not open + * if fd >= 0 then: sock is open and ready read and write + * otherwise: sock is not open * * there are no errors, yet. * * Sets timeout to no timeout at all -- timeout is optional. + * + * NB: MUST be in the CLI thread if the fd is >= 0 ! */ static void -uty_file_init_new(vio_file file, int fd, void* info) +uty_sock_init_new(vio_sock sock, int fd, void* info) { - memset(file, 0, sizeof(struct vio_file)) ; + VTY_ASSERT_LOCKED() ; + + if (fd >= 0) + VTY_ASSERT_CLI_THREAD() ; + + memset(sock, 0, sizeof(struct vio_sock)) ; /* Zeroising the structure has set: * @@ -718,17 +945,16 @@ uty_file_init_new(vio_file file, int fd, void* info) * t_timer = NULL -- no timer thread, yet * qtr = NULL -- no qtimer, yet */ - file->fd = fd ; - file->info = info ; + sock->fd = fd ; + sock->info = info ; - file->read_open = (fd >= 0) ; - file->write_open = (fd >= 0) ; + sock->read_open = (fd >= 0) ; + sock->write_open = (fd >= 0) ; - if (vty_cli_nexus) + if ((fd >= 0) && 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); + sock->qf = qps_file_init_new(NULL, NULL); + qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); } ; } ; @@ -740,250 +966,368 @@ uty_file_init_new(vio_file file, int fd, void* info) * If no timeout time is set, and the timer is running, unset it. */ static void -uty_file_restart_timer(vio_file file) +uty_sock_restart_timer(vio_sock sock) { - if (file->v_timeout != 0) + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->v_timeout != 0) { - assert(file->action.timer.anon != NULL) ; + assert(sock->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) ; + if (sock->qtr == NULL) /* allocate qtr if required */ + sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, + NULL, sock->info) ; + qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), + sock->action.timer.qnexus) ; } else { - if (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) ; + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer); + sock->t_timer = thread_add_timer (vty_master, + sock->action.timer.thread, sock->info, sock->v_timeout) ; } ; - file->timer_running = 1 ; + sock->timer_running = 1 ; } - else if (file->timer_running) + else if (sock->timer_running) { if (vty_cli_nexus) { - if (file->qtr != NULL) - qtimer_unset(file->qtr) ; + if (sock->qtr != NULL) + qtimer_unset(sock->qtr) ; } else { - if (file->t_timer != NULL) - thread_cancel (file->t_timer) ; + if (sock->t_timer != NULL) + thread_cancel (sock->t_timer) ; } ; - file->timer_running = 0 ; + sock->timer_running = 0 ; } ; } ; /*------------------------------------------------------------------------------ - * Set a new timer value. + * Set read on/off + * + * Returns: the on/off state set */ -extern void -uty_file_set_timer(vio_file file, unsigned long timeout) +static bool +uty_sock_set_read(vio_sock sock, bool on) { - file->v_timeout = timeout ; - if (file->timer_running) - uty_file_restart_timer(file) ; -} ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; -/*------------------------------------------------------------------------------ - * Set read on/off -- restart timer. - */ -extern void -uty_file_set_read(vio_file file, bool on) -{ - if (file->fd < 0) - return ; + if (sock->fd < 0) + return 0 ; if (on) { - assert(file->action.read.anon != NULL) ; + assert(sock->action.read.anon != NULL) ; if (vty_cli_nexus) - { - qps_enable_mode(file->qf, qps_read_mnum, file->action.read.qnexus) ; - } + qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; else { - if (file->t_read != NULL) - thread_cancel(file->t_read) ; + if (sock->t_read != NULL) + thread_cancel(sock->t_read) ; - file->t_read = thread_add_read(vty_master, - file->action.read.thread, file->info, file->fd) ; + sock->t_read = thread_add_read(vty_master, + sock->action.read.thread, sock->info, sock->fd) ; } ; } else { if (vty_cli_nexus) - { - qps_disable_modes(file->qf, qps_read_mbit) ; - } + qps_disable_modes(sock->qf, qps_read_mbit) ; else { - if (file->t_read != NULL) - thread_cancel (file->t_read) ; + if (sock->t_read != NULL) + thread_cancel (sock->t_read) ; } ; } ; - uty_file_restart_timer(file) ; + return on ; } ; /*------------------------------------------------------------------------------ - * Set write on/off -- restart timer. + * Set write on/off + * + * Returns: the on/off state set */ -extern void -uty_file_set_write(vio_file file, bool on) +static bool +uty_sock_set_write(vio_sock sock, bool on) { - if (file->fd < 0) - return ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + if (sock->fd < 0) + return 0 ; if (on) { - assert(file->action.write.anon != NULL) ; + assert(sock->action.write.anon != NULL) ; if (vty_cli_nexus) - { - qps_enable_mode(file->qf, qps_write_mnum, file->action.write.qnexus) ; - } + qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; else { - if (file->t_write != NULL) - thread_cancel(file->t_write) ; + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; - file->t_write = thread_add_write(vty_master, - file->action.write.thread, file->info, file->fd) ; + sock->t_write = thread_add_write(vty_master, + sock->action.write.thread, sock->info, sock->fd) ; } ; } else { if (vty_cli_nexus) - { - qps_disable_modes(file->qf, qps_write_mbit) ; - } + qps_disable_modes(sock->qf, qps_write_mbit) ; else { - if (file->t_write != NULL) - thread_cancel (file->t_write) ; + if (sock->t_write != NULL) + thread_cancel (sock->t_write) ; } ; } ; - uty_file_restart_timer(file) ; + return on ; +} ; + +/*------------------------------------------------------------------------------ + * Set read/write readiness -- for VTY_TERM + * + * Note that for VTY_TERM, set only one of read or write, and sets write for + * preference. + */ +extern void +uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + uty_sock_set_read(sock, (ready == read_ready)) ; + uty_sock_set_write(sock, (ready >= write_ready)) ; } ; /*------------------------------------------------------------------------------ - * Close given vty file for reading. + * Set a new timer value. + */ +extern void +uty_sock_set_timer(vio_sock sock, unsigned long timeout) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; + + sock->v_timeout = timeout ; + if (sock->timer_running) + uty_sock_restart_timer(sock) ; +} ; + +/*------------------------------------------------------------------------------ + * Close given vty sock for reading. * * Sets timer to timeout for clearing any pending output. + * + * NB: if there is a socket, MUST be in the CLI thread */ static void -uty_file_half_close(vio_file file) +uty_sock_half_close(vio_sock sock) { VTY_ASSERT_LOCKED() ; - if (file->fd >= 0) - { - shutdown(file->fd, SHUT_RD) ; /* actual half close */ + sock->read_open = 0 ; /* make sure */ - file->v_timeout = 30 ; /* for output to clear */ - uty_file_set_read(file, off) ; - } ; + if (sock->fd < 0) + return ; /* nothing more if no socket */ + + VTY_ASSERT_CLI_THREAD() ; - file->read_open = 0 ; + shutdown(sock->fd, SHUT_RD) ; /* actual half close */ + + uty_sock_set_read(sock, off) ; + uty_sock_set_write(sock, on) ; + sock->v_timeout = 30 ; /* for output to clear */ + uty_sock_restart_timer(sock) ; } ; /*------------------------------------------------------------------------------ - * Close given vio_file, completely -- shut down any timer. + * Close given vio_sock, completely -- shut down any timer. * * Structure is cleared of everything except the last error ! + * + * NB: if there is a socket, MUST be in the CLI thread */ static void -uty_file_close(vio_file file) +uty_sock_close(vio_sock sock) { VTY_ASSERT_LOCKED() ; - if (file->fd >= 0) - close(file->fd) ; + sock->read_open = 0 ; /* make sure */ + sock->write_open = 0 ; - if (vty_cli_nexus && (file->fd >= 0)) - qps_remove_file(file->qf) ; + if (sock->fd < 0) + { + assert( (sock->qf == NULL) + && (sock->qtr == NULL) + && (sock->t_read == NULL) + && (sock->t_write == NULL) + && (sock->t_timer == NULL) ) ; + return ; /* no more to be done here */ + } ; - if (file->qf != NULL) - qps_file_free(file->qf) ; + VTY_ASSERT_CLI_THREAD() ; + close(sock->fd) ; - if (file->t_read != NULL) - thread_cancel(file->t_write) ; - if (file->t_write != NULL) - thread_cancel(file->t_write) ; + if (vty_cli_nexus) + { + assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; + qps_remove_file(sock->qf) ; + qps_file_free(sock->qf) ; + sock->qf = NULL ; + } ; - file->fd = -1 ; - file->qf = NULL ; - file->t_read = NULL ; - file->t_write = NULL ; + sock->fd = -1 ; - file->info = NULL ; - file->action.read.anon = NULL ; - file->action.write.anon = NULL ; - file->action.timer.anon = NULL ; + if (sock->t_read != NULL) + thread_cancel(sock->t_write) ; + if (sock->t_write != NULL) + thread_cancel(sock->t_write) ; - file->read_open = 0 ; - file->write_open = 0 ; + sock->t_read = NULL ; + sock->t_write = NULL ; - if (file->qtr != NULL) - qtimer_free(file->qtr) ; - if (file->t_timer != NULL) - thread_cancel(file->t_timer) ; + sock->info = NULL ; + sock->action.read.anon = NULL ; + sock->action.write.anon = NULL ; + sock->action.timer.anon = NULL ; - file->v_timeout = 0 ; - file->qtr = NULL ; - file->t_timer = NULL ; + if (sock->qtr != NULL) + qtimer_free(sock->qtr) ; + if (sock->t_timer != NULL) + thread_cancel(sock->t_timer) ; + + sock->v_timeout = 0 ; + sock->qtr = NULL ; + sock->t_timer = NULL ; } ; -/*============================================================================== - * Reading from the VTY_TERM type file. +/*------------------------------------------------------------------------------ + * Dealing with an I/O error on VTY socket * - * The select/pselect call-back ends up in uty_read_ready(). + * If this is the first error for this VTY, produce suitable log message. * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. + * If is a "monitor", turn that off, *before* issuing log message. */ +static int +uty_sock_error(vty_io vio, const char* what) +{ + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; -/*------------------------------------------------------------------------------ - * Ready to read -> kicking CLI + /* can no longer be a monitor ! *before* any logging ! */ + uty_set_monitor(vio, 0) ; + + /* if this is the first error, log it */ + if (vio->sock.error_seen == 0) + { + const char* type ; + switch (vio->type) + { + case VTY_TERM: + type = "VTY Terminal" ; + break ; + case VTY_SHELL_SERV: + type = "VTY Shell Server" ; + break ; + default: + zabort("unknown VTY type for uty_sock_error()") ; + } ; + + vio->sock.error_seen = errno ; + uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + type, what, vio->sock.fd, safe_strerror(vio->sock.error_seen)) ; + } ; + + return -1 ; +} ; + +/*============================================================================== + * Readiness and the VTY_TERM type VTY. * - * Have two CLI: one (trivial one) when waiting on "--more--", - * and the standard one. + * For VTY_TERM the driving force is write ready. This is used to prompt the + * VTY_TERM when there is outstanding output (obviously), but also if there + * is buffered input in the keystroke stream. * - * End up here when there is something ready to be read. + * The VTY_TERM uses read ready only when it doesn't set write ready. Does + * not set both at once. * - * Also ends up here when was write_ready and did not block in uty_write. + * So there is only one, common, uty_ready function, which: * - * 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. + * 1. attempts to clear any output it can. * - * Note that nothing is actually read here -- reading is done in the CLI itself, - * if required. + * The state of the output affects the CLI, so must always do this before + * before invoking the CLI. * - * The CLI decides whether to re-enable read, or enable write, or both. + * If this write enters the "--more--" state, then will have tried to + * write away the prompt. + * + * 2. invokes the CLI + * + * Which will do either the standard CLI stuff or the special "--more--" + * stuff. + * + * 3. attempts to write any output there now is. + * + * If the CLI generated new output, as much as possible is written away + * now. + * + * If this write enters the "--more--" state, then it returns now_ready, + * if the prompt was written away, which loops back to the CLI. + * + * Note that this is arranging: + * + * a. to write away the "--more--" prompt as soon as the tranche of output to + * which it refers, completes + * + * b. to enter the cli_more_wait CLI for the first time immediately after the + * "--more--" prompt is written away. + * + * The loop limits itself to one trache of command output each time. + * + * Resets the timer because something happened. */ static void -uty_read_ready(vty_io vio) +uty_ready(vty_io vio) { - uty_file_set_read(&vio->file, off) ; /* restarts timer */ + enum vty_readiness ready ; - /* 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 */ + VTY_ASSERT_LOCKED() ; + + vio->cmd_out_done = 0 ; /* not done any command output yet */ + + uty_write(vio) ; /* try to clear outstanding stuff */ + do + { + ready = uty_cli(vio) ; /* do any CLI work... */ + ready |= uty_write(vio) ; /* ...and any output that generates */ + } while (ready >= now_ready) ; + + uty_sock_set_readiness(&vio->sock, ready) ; + uty_sock_restart_timer(&vio->sock) ; } ; +/*============================================================================== + * Reading from VTY_TERM. + * + * The select/pselect call-back ends up in uty_read_ready(). + * + * Note that uty_write_ready() also calls uty_read_ready, in order to kick the + * current CLI. + */ + /*------------------------------------------------------------------------------ * Callback -- qnexus: ready to read -> kicking CLI */ @@ -994,9 +1338,9 @@ vty_read_qnexus(qps_file qf, void* file_info) VTY_LOCK() ; - assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ; + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - uty_read_ready(vio) ; + uty_ready(vio) ; VTY_UNLOCK() ; } @@ -1011,10 +1355,10 @@ vty_read_thread(struct thread *thread) VTY_LOCK() ; - assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - vio->file.t_read = NULL ; /* implicitly */ - uty_read_ready(vio); + vio->sock.t_read = NULL ; /* implicitly */ + uty_ready(vio); VTY_UNLOCK() ; return 0 ; @@ -1035,18 +1379,18 @@ uty_read (vty_io vio, keystroke steal) unsigned char buf[500] ; int get ; - if (!vio->file.read_open) + if (!vio->sock.read_open) return -1 ; /* at EOF if not open */ - get = read_nb(vio->file.fd, buf, sizeof(buf)) ; - if (get > 0) + get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; + if (get >= 0) keystroke_input(vio->key_stream, buf, get, steal) ; else if (get < 0) { if (get == -1) - uty_io_error(vio, "read") ; + uty_sock_error(vio, "read") ; - vio->file.read_open = 0 ; + vio->sock.read_open = 0 ; keystroke_input(vio->key_stream, NULL, 0, steal) ; get = -1 ; @@ -1056,14 +1400,14 @@ uty_read (vty_io vio, keystroke steal) } ; /*============================================================================== - * The write file action for VTY_TERM type VTY + * The write sock action for VTY_TERM type VTY * * There are two sets of buffering: * * cli -- command line -- which reflects the status of the command line * - * cmd -- command output -- which is not written to the file while - * cmd_in_progress. + * cmd -- command output -- which is written to the file only while + * cmd_out_enabled. * * The cli output takes precedence. * @@ -1071,41 +1415,8 @@ uty_read (vty_io vio, keystroke steal) * "--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) ; -} ; +static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; +static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; /*------------------------------------------------------------------------------ * Callback -- qnexus: ready to write -> try to empty buffers @@ -1117,9 +1428,9 @@ vty_write_qnexus(qps_file qf, void* file_info) VTY_LOCK() ; - assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ; + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - uty_write_ready(vio) ; + uty_ready(vio) ; VTY_UNLOCK() ; } @@ -1134,10 +1445,10 @@ vty_write_thread(struct thread *thread) VTY_LOCK() ; - assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - vio->file.t_write = NULL; /* implicitly */ - uty_write_ready(vio) ; + vio->sock.t_write = NULL; /* implicitly */ + uty_ready(vio) ; VTY_UNLOCK() ; return 0 ; @@ -1152,154 +1463,337 @@ vty_write_thread(struct thread *thread) * 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 + * Sets write on if prevented from writing 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. + * Returns: write_ready if should now set write on + * now_ready if should loop back and try again + * not_ready otherwise */ -static bool +static enum vty_readiness 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 */ + ret = -1 ; + while (vio->sock.write_open) + { + /* Any outstanding line control output takes precedence */ + if (vio->cmd_lc != NULL) + { + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + if (ret != 0) + break ; + } - if ((vio->cmd_in_progress) || (vio->cmd_wait_more)) - return 0 ; /* not blocked by I/O */ + /* Next: empty out the cli output */ + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret != 0) + break ; - /* 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 */ + /* Finished now if not allowed to progress the command stuff */ + if (!vio->cmd_out_enabled) + return not_ready ; /* done all can do */ - if (ret == 2) - { - /* Want now to wait for "--more--" + /* Last: if there is something in the command buffer, do that */ + if (!vio_fifo_empty(&vio->cmd_obuf)) + { + if (vio->cmd_out_done) + break ; /* ...but not if done once */ + + vio->cmd_out_done = 1 ; /* done this once */ + + assert(!vio->cli_more_wait) ; + + if (vio->cmd_lc != NULL) + ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + else + ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; + + /* If moved into "--more--" state@ + * + * * the "--more--" prompt is ready to be written, so do that now + * + * * if that completes, then want to run the CLI *now* to perform the + * first stage of the "--more--" process. + */ + if (vio->cli_more_wait) + { + ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; + if (ret == 0) + return now_ready ; + } ; + + if (ret != 0) + break ; + } + + /* Exciting stuff: there is nothing left to output... * - * Note that this produces CLI output, which must deal with here. + * ... watch out for half closed state. */ - uty_cli_want_more(vio) ; /* NB: sets cmd_wait_more */ + if (vio->half_closed) + { + if (vio->close_reason != NULL) + { + vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ + + struct vty* vty = vio->vty ; + if (vio->cli_drawn || vio->cli_dirty) + vty_out(vty, VTY_NEWLINE) ; + vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; - ret = uty_flush_fifo(vio, &vio->cli_obuf, NULL) ; - if (ret == 1) - return 1 ; /* blocked by I/O */ + vio->cmd_in_progress = 0 ; + + vio->close_reason = NULL ; /* MUST discard now... */ + continue ; /* ... and write away */ + } ; + + if (!vio->closed) /* avoid recursion */ + uty_close(vio) ; + + return not_ready ; /* it's all over */ + } ; - if (vio->file.write_open) - return 0 ; /* not blocked by I/O */ + /* For VTY_TERM: if the command line is not drawn, now is a good + * time to do that. + */ + if (vio->type == VTY_TERM) + if (uty_cli_draw_if_required(vio)) + continue ; /* do that now. */ + + /* There really is nothing left to output */ + return not_ready ; } ; - /* Reach here iff both CLI and command FIFOs are empty and is not - * cmd_in_progress + /* Arrives here if there is more to do, or failed (or was !write_open) */ + + if (ret >= 0) + return write_ready ; + + /* If is write_open, then report the error + * + * If still read_open, let the reader pick up and report the error, when it + * has finished anything it has buffered. */ - vio->cli_blocked = 0 ; + if (vio->sock.write_open) + { + if (!vio->sock.read_open) + uty_sock_error(vio, "write") ; - return 0 ; + vio->sock.write_open = 0 ; /* crash close write */ + } ; + + /* For whatever reason, is no longer write_open -- clear all buffers. + */ + vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ + uty_out_clear(vio) ; /* throw away cmd stuff */ + + vio->close_reason = NULL ; /* too late for this */ + + return not_ready ; /* NB: NOT blocked by I/O */ +} ; + +/*------------------------------------------------------------------------------ + * Write as much as possible -- for "monitor" output. + * + * Outputs only: + * + * a. outstanding line control stuff. + * + * b. contents of CLI buffer + * + * And: + * + * a. does not report any errors. + * + * b. does not change anything except the state of the buffers. + * + * In particular, for the qpthreaded world, does not attempt to change + * the state of the qfile or any other "thread private" structures. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed (or !write_open) + */ +static int +uty_write_monitor(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (!vio->sock.write_open) + return -1 ; + + if (vio->cmd_lc != NULL) + { + int ret ; + ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; + + if (ret != 0) + return ret ; + } ; + + return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; } ; /*------------------------------------------------------------------------------ - * Flush the given FIFO to output -- subject to possible line control. + * Write the given FIFO to output -- subject to possible line control. + * + * Note that even if no "--more--" is set, will have set some height, so + * that does not attempt to empty the FIFO completely all in one go. + * + * If the line control becomes "paused", it is time to enter "--more--" state + * -- unless the FIFO is empty (or "--more--" is not enabled). * - * If ends up needing to write more, sets write on. + * NB: expects that the sock is write_open * - * 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--" + * Returns: > 0 => blocked or completed one tranche + * 0 => all gone + * < 0 => failed */ static int -uty_flush_fifo(vty_io vio, vio_fifo vf, struct vty_line_control* line_control) +uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) { + int ret ; char* src ; size_t have ; - int done ; - bool wait_more ; - if (!vio->file.write_open) + /* Collect another line_control height's worth of output. + * + * Expect the line control to be empty at this point, but it does not have + * to be. + */ + vio_lc_set_pause(lc) ; /* clears lc->paused */ + + src = vio_fifo_get_rdr(vf, &have) ; + + while ((src != NULL) && (!lc->paused)) { - uty_empty_out_fifos(vio) ; - return 0 ; + size_t take ; + take = vio_lc_append(lc, src, have) ; + src = vio_fifo_step_rdr(vf, &have, take) ; } ; - wait_more = 0 ; + vio->cli_dirty = (lc->col != 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 - */ - } ; + /* Write the contents of the line control */ + ret = uty_write_lc(vio, vf, lc) ; - done = write_nb(vio->file.fd, src, have) ; + if (ret < 0) + return ret ; /* give up now if failed. */ - if (done < 0) - { - uty_io_error(vio, "write") ; + if ((ret == 0) && vio_fifo_empty(vf)) + return 0 ; /* FIFO and line control empty */ - vio->file.write_open = 0 ; - uty_empty_out_fifos(vio) ; - return 0 ; /* no longer open */ - } + /* If should now do "--more--", now is the time to prepare for that. + * + * Entering more state issues a new prompt in the CLI buffer, which can + * be written once line control write completes. + * + * The "--more--" cli will not do anything until the CLI buffer has + * cleared. + */ + if (lc->paused && vio->cli_more_enabled) + uty_cli_go_more_wait(vio) ; - vio_fifo_got_upto(vf, src + done) ; + return 1 ; /* FIFO or line control, not empty */ +} ; - if (done < (int)have) - { - if (line_control != NULL) - { - /* "put back" have - done bytes for next time */ - } ; +/*------------------------------------------------------------------------------ + * Write contents of line control (if any). + * + * NB: expects that the sock is write_open + * + * NB: does nothing other than write() and buffer management. + * + * Returns: > 0 => blocked + * 0 => all gone + * < 0 => failed + */ +static int +uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) +{ + int ret ; - uty_file_set_write(&vio->file, on) ; - return 1 ; /* output is full */ - } ; + ret = vio_lc_write_nb(vio->sock.fd, lc) ; - /* 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 ; - } ; + if (ret <= 0) + vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ - return 0 ; /* all gone */ + return ret ; } ; /*------------------------------------------------------------------------------ - * Empty the output FIFOs + * Start command output -- clears down the line control. * - * This is for use when the output has failed or is closed. + * Requires that that current line is empty -- restarts the line control + * on the basis that is at column 0. */ -static void -uty_empty_out_fifos(vty_io vio) +extern void +uty_cmd_output_start(vty_io vio) { - vio_fifo_set_empty(&vio->cli_obuf) ; - vio_fifo_set_empty(&vio->cmd_obuf) ; + if (vio->cmd_lc != NULL) + vio_lc_clear(vio->cmd_lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the effective height for line control (if any) + * + * If using line_control, may enable the "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. + */ +extern void +uty_set_height(vty_io vio) +{ + bool on ; + + on = 0 ; /* default state */ + + if ((vio->cmd_lc != NULL) && !vio->half_closed) + { + int height ; + + height = 0 ; /* default state */ - vio->cmd_wait_more = 0 ; + if ((vio->width) != 0) + { + /* If window size is known, use lines or given height */ + if (vio->lines >= 0) + height = vio->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = vio->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (vio->lines_set) + height = vio->lines ; + } ; + + if (height > 0) + on = 1 ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(vio->cmd_lc, vio->width, height) ; + } ; + + vio->cli_more_enabled = on ; } ; /*============================================================================== @@ -1321,10 +1815,8 @@ uty_timer_expired (vty_io vio) 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 */ -} ; + uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ + } ; /*------------------------------------------------------------------------------ * Callback -- qnexus: deal with timer timeout. @@ -1351,7 +1843,7 @@ vty_timer_thread (struct thread *thread) VTY_LOCK() ; - vio->file.t_timer = NULL ; /* implicitly */ + vio->sock.t_timer = NULL ; /* implicitly */ uty_timer_expired(vio) ; @@ -1373,7 +1865,7 @@ struct vty_listener enum vty_type type ; - struct vio_file file ; + struct vio_sock sock ; }; /* List of listeners so can tidy up. */ @@ -1450,7 +1942,7 @@ uty_close_listeners(void) while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) { - uty_file_close(&listener->file) ; /* no ceremony, no flowers */ + uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ XFREE(MTYPE_VTY, listener) ; } ; } ; @@ -1537,6 +2029,8 @@ uty_serv_sock(const char* addr, unsigned short port) VTY_ASSERT_LOCKED() ; + n = 0 ; /* nothing opened yet */ + /* If have an address, see what kind and whether valid */ sa = NULL ; @@ -1620,6 +2114,19 @@ uty_serv_sock_open(sa_family_t family, int type, int protocol, if (ret >= 0) ret = set_nonblocking(sock); +#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) + /* Want only IPV6 on ipv6 socket (not mapped addresses) + * + * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the + * attempt to bind to :: after binding to 0.0.0.0. + */ + if ((ret >= 0) && (sa->sa_family == AF_INET6)) + { + int on = 1; + ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); + } +#endif + if (ret >= 0) ret = sockunion_bind (sock, &su, port, sa) ; @@ -1673,7 +2180,6 @@ uty_serv_vtysh(const char *path) { uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", safe_strerror(errno)); - umask (old_mask); return -1 ; } @@ -1711,10 +2217,8 @@ uty_serv_vtysh(const char *path) { /* set group of socket */ if ( chown (path, -1, ids.gid_vty) ) - { - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", safe_strerror (errno) ); - } } umask (old_mask); @@ -1745,16 +2249,16 @@ uty_serv_start_listener(int fd, enum vty_type type) listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); ssl_push(vty_listeners_list, listener, next) ; - uty_file_init_new(&listener->file, fd, listener) ; + uty_sock_init_new(&listener->sock, fd, listener) ; listener->type = type ; if (vty_cli_nexus) - listener->file.action.read.qnexus = vty_accept_qnexus ; + listener->sock.action.read.qnexus = vty_accept_qnexus ; else - listener->file.action.read.thread = vty_accept_thread ; + listener->sock.action.read.thread = vty_accept_thread ; - uty_file_set_read(&listener->file, on) ; + uty_sock_set_read(&listener->sock, on) ; } ; /*------------------------------------------------------------------------------ @@ -1770,7 +2274,7 @@ vty_accept_thread(struct thread *thread) result = uty_accept(listener, THREAD_FD(thread)); - uty_file_set_read(&listener->file, on) ; + uty_sock_set_read(&listener->sock, on) ; VTY_UNLOCK() ; return result ; @@ -1797,7 +2301,7 @@ uty_accept(vty_listener listener, int listen_sock) { VTY_ASSERT_LOCKED() ; - assert(listener->file.fd == listen_sock) ; + assert(listener->sock.fd == listen_sock) ; switch (listener->type) { @@ -1828,9 +2332,9 @@ uty_accept_term(vty_listener listener) VTY_ASSERT_LOCKED() ; /* We can handle IPv4 or IPv6 socket. */ - sockunion_init_new(&su, 0) ; + sockunion_init_new(&su, AF_UNSPEC) ; - sock = sockunion_accept (listener->file.fd, &su); + sock = sockunion_accept (listener->sock.fd, &su); if (sock < 0) { @@ -1918,7 +2422,7 @@ uty_accept_shell_serv (vty_listener listener) client_len = sizeof(client); memset (&client, 0, client_len); - sock = accept(listener->file.fd, (struct sockaddr *) &client, + sock = accept(listener->sock.fd, (struct sockaddr *) &client, (socklen_t *) &client_len) ; if (sock < 0) @@ -1948,7 +2452,7 @@ uty_accept_shell_serv (vty_listener listener) } /*============================================================================== - * Reading from the VTY_SHELL_SERV type file. + * Reading from the VTY_SHELL_SERV type sock. * * The select/pselect call-back ends up in utysh_read_ready(). */ @@ -1970,7 +2474,7 @@ uty_accept_shell_serv (vty_listener listener) static void utysh_read_ready(vty_io vio) { - uty_file_set_read(&vio->file, off) ; + uty_sock_set_read(&vio->sock, off) ; /* TODO: need minimal "CLI" for VTY_SHELL_SERV * NB: when output from command is flushed out, must append the @@ -1989,7 +2493,7 @@ vtysh_read_qnexus(qps_file qf, void* file_info) VTY_LOCK() ; - assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ; + assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; utysh_read_ready(vio) ; @@ -2006,9 +2510,9 @@ vtysh_read_thread(struct thread *thread) VTY_LOCK() ; - assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ; + assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - vio->file.t_read = NULL ; /* implicitly */ + vio->sock.t_read = NULL ; /* implicitly */ utysh_read_ready(vio); VTY_UNLOCK() ; @@ -2025,7 +2529,7 @@ vtysh_read_thread(struct thread *thread) * 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. + * When empties "buf", reads a lump from the sock. * * Returns: 0 => command line is incomplete * 1 => have a complete command line @@ -2070,13 +2574,13 @@ utysh_read (vty_io vio, qstring cl, qstring buf) /* buffer is empty -- try and get some more stuff */ assert(buf->len == buf->cp) ; - if (!vio->file.read_open) + if (!vio->sock.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 */ + qs_clear(buf) ; /* set cp = len = 0 */ - get = read_nb(vio->file.fd, buf->body, buf->size) ; + get = read_nb(vio->sock.fd, buf->body, buf->size) ; if (get > 0) buf->len = get ; else if (get == 0) @@ -2084,9 +2588,9 @@ utysh_read (vty_io vio, qstring cl, qstring buf) else { if (get == -1) - uty_io_error(vio, "read") ; + uty_sock_error(vio, "read") ; - vio->file.read_open = 0 ; + vio->sock.read_open = 0 ; return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ } ; @@ -2096,13 +2600,65 @@ utysh_read (vty_io vio, qstring cl, qstring buf) /*============================================================================== * 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. + * This is VERY TRICKY. + * + * If not running qpthreaded, then the objective is to get the message away + * immediately -- do not wish it to be delayed in any way by the thread + * system. + * + * So proceed as follows: + * + * a. wipe command line -- which adds output to the CLI buffer + * + * b. write the CLI buffer to the sock and any outstanding line control. + * + * c. write the monitor output. + * + * If that does not complete, put the tail end to the CLI buffer. + * + * d. restore any command line -- which adds output to the CLI buffer + * + * e. write the CLI buffer to the sock + * + * If that all succeeds, nothing has changed as far as the VTY stuff is + * concerned -- except that possibly some CLI output was sent before it got + * round to it. + * + * Note that step (b) will deal with any output hanging around from an + * earlier step (e). If cannot complete that, then does not add fuel to the + * fire -- but the message will be discarded. + * + * If that fails, or does not complete, then can set write on, to signal that + * there is some output in the CLI buffer that needs to be sent, or some + * error to be dealt with. + * + * The output should be tidy. + * + * To cut down the clutter, step (d) is performed only if the command line + * is not empty (or if in cli_more_wait). Once a the user has started to enter + * a command, the prompt and the command will remain visible. * - * 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. + * When logging an I/O error for a vty that happens to be a monitor, the + * monitor-ness has already been turned off. The monitor output code does not + * attempt to log any errors, sets write on so that the error will be picked + * up that way. + * + * However, in the event of an assertion failure, it is possible that an + * assertion will fail inside the monitor output. The monitor_busy flag + * prevents disaster. It is also left set if I/O fails in monitor output, so + * will not try to use the monitor again. + * + * Note that an assertion which is false for all vty monitors will recurse + * through all the monitors, setting each one busy, in turn ! + * + + + * TODO: sort out write on in the qpthreads world ?? + * + * The problem is that the qpselect structure is designed to be accessed ONLY + * within the thread to which it belongs. This makes it impossible for the + * monitor output to set/clear read/write on the vty sock... so some way + * around this is required. */ /*------------------------------------------------------------------------------ @@ -2113,67 +2669,52 @@ 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) ; + vio = sdl_head(vio_monitors_base) ; - if (next == NULL) + if (vio == NULL) return ; /* go no further if no "monitor" vtys */ /* Prepare line for output. */ - uvzlog_line(ll, zl, priority, format, va, 1) ; /* with crlf */ + uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* 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) + while (vio != NULL) { - vio = next ; - - if ( vio->monitor /* may be temporarily not a monitor */ - && (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) ) + if (!vio->monitor_busy) { - vio->monitor = 0 ; /* avoid recursion */ + int ret ; - uty_cli_wipe(vio) ; - uty_write(vio) ; + vio->monitor_busy = 1 ; /* close the door */ - if (vio_fifo_empty(&vio->cli_obuf) && vio->file.write_open) + uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ + + ret = uty_write_monitor(vio) ; + if (ret == 0) { - vio_fifo_put(&vio->cli_obuf, ll->line, ll->len) ; - uty_write(vio) ; + ret = write_nb(vio->sock.fd, ll->line, ll->len) ; + + if (ret >= 0) + { + ret = uty_cli_post_monitor(vio, ll->line + ret, + ll->len - ret) ; + if (ret > 0) + ret = uty_write_monitor(vio) ; + } ; } ; - uty_file_set_read(&vio->file, on) ; + if (ret != 0) + /* need to prod */ ; - /* 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 ; + if (ret >= 0) + vio->monitor_busy = 0 ; } ; - 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) ; + vio = sdl_next(vio, mon_list) ; } ; } ; @@ -2194,8 +2735,8 @@ vty_log_fixed (const char *buf, size_t len) vio = sdl_head(vio_monitors_base) ; while (vio != NULL) { - write(vio->file.fd, buf, len) ; - write(vio->file.fd, "\r\n", 2) ; + write(vio->sock.fd, buf, len) ; + write(vio->sock.fd, "\r\n", 2) ; vio = sdl_next(vio, mon_list) ; } ; diff --git a/lib/vty_io.h b/lib/vty_io.h index 06cefe06..19689853 100644 --- a/lib/vty_io.h +++ b/lib/vty_io.h @@ -31,6 +31,7 @@ #include "uty.h" #include "vty.h" #include "vio_fifo.h" +#include "vio_lines.h" #include "keystroke.h" #include "thread.h" #include "command.h" @@ -62,14 +63,17 @@ */ /*------------------------------------------------------------------------------ - * VTY file structure + * VTY sock structure * - * Used + * Used for VTY_TERM and VTY_SHELL_SERV VTY types, which are attached to TCP + * and UNIX sockets, respectively. + * + * Also used for the associated listeners. */ typedef int thread_action(struct thread *) ; -union file_action +union sock_action { qps_action* qnexus ; thread_action* thread ; @@ -83,21 +87,21 @@ union timer_action void* anon ; } ; -struct vio_file_actions +struct vio_sock_actions { - union file_action read ; - union file_action write ; + union sock_action read ; + union sock_action write ; union timer_action timer ; }; -typedef struct vio_file* vio_file ; -struct vio_file +typedef struct vio_sock* vio_sock ; +struct vio_sock { int fd ; void* info ; /* for action routines */ - struct vio_file_actions action ; + struct vio_sock_actions action ; bool read_open ; /* read returns 0 if not open */ bool write_open ; /* write completes instantly if not open */ @@ -116,56 +120,67 @@ struct vio_file } ; -struct vty_line_control +enum { - int tba ; + on = true, + off = false } ; -enum +enum vty_readiness /* bit significant */ { - off = false, - on = true -}; + not_ready = 0, + read_ready = 1, + write_ready = 2, /* takes precedence */ + now_ready = 4 +} ; /*------------------------------------------------------------------------------ - * + * The vty_io structure */ struct vty_io { + struct vty* vty ; /* the related vty */ + char *name ; /* for VTY_TERM is IP address) */ + /* 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 */ + /* VTY type and sock stuff */ enum vty_type type; - /* File level stuff */ - struct vio_file file ; + struct vio_sock sock ; /* for VTY_TERM and VTY_SHELL_SERV */ - /* "name" of the VTY (for VTY_TERM is IP address) */ - char *name ; + bool half_closed ; /* => on death watch list */ + bool closed ; /* => all I/O terminated + will also be half_closed */ - /* Keystroke stream and raw input buffer */ - keystroke_stream key_stream ; - qstring_t ibuf ; + const char* close_reason ; /* message to be sent, once all other + output has completed, giving reason + for closing the VTY. */ + + /* When writing configuration file */ + enum vty_type real_type ; + + int file_fd ; + int file_error ; /*--------------------------------------------------------------------*/ /* Command line and related state */ + keystroke_stream key_stream ; + /* cli_drawn <=> the current prompt and user input occupy the current * line on the screen. * + * cli_dirty <=> the last command output did not end with a newline. + * * If cli_drawn is true, the following are valid: * * cli_prompt_len -- the length of the prompt part. + * (will be the "--more--" prompt in cli_more_wait) * * cli_extra_len -- the length of any ^X at the cursor position * (for when blocked waiting for queued command) @@ -174,28 +189,38 @@ struct vty_io { * * NB: cli_echo_suppress is only used for password entry. */ - int cli_drawn ; + bool cli_drawn ; + bool cli_dirty ; - int cli_prompt_len ; /* for drawn line (if any) */ - int cli_extra_len ; /* for for drawn line (if any) */ + int cli_prompt_len ; + int cli_extra_len ; - bool cli_echo_suppress ; /* non-zero => suppress cli echo */ + bool cli_echo_suppress ; /* "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 ; + 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 - * + * cmd_out_enabled -- contents of the command FIFO may be written away + * cli_more_wait -- is in "--more--" wait state */ bool cli_blocked ; bool cmd_in_progress ; + bool cmd_out_enabled ; + bool cli_more_wait ; + + /* This is used to control command output, so that each write_ready event + * generates at most one tranche of output. + */ + bool cmd_out_done ; + + /* This is set only if the "--more--" handling is enabled */ + bool cli_more_enabled ; /* Command Line(s) * @@ -211,87 +236,75 @@ struct vty_io { */ enum cli_do cli_do ; - qstring_t cl ; - qstring_t clx ; + 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 ; + vio_line_control cmd_lc ; - struct vty_line_control line_control ; /* Failure count for login attempts */ - int fail; + 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; + /* Window width/height as reported by Telnet. 0 => unknown */ + int width; + int height; /* Configure lines. */ - int lines; + int lines; + bool lines_set ; /* true <=> explicitly set */ /* Terminal monitor. */ - bool monitor ; + bool monitor ; + bool monitor_busy ; /* In configure mode. */ - bool config; + 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) ; +extern struct vty* uty_new (enum vty_type type, int sock_fd) ; + +extern void uty_open_listeners(const char *addr, unsigned short port, + const char *path) ; +extern void uty_close_listeners(void) ; + +extern void uty_watch_dog_start(void) ; +extern void uty_watch_dog_stop(void) ; + +extern void uty_half_close (vty_io vio, const char* reason) ; +extern void uty_close (vty_io vio) ; + +extern int uty_out (struct vty *vty, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern int uty_vout(struct vty *vty, const char *format, va_list args) ; +extern void uty_out_clear(vty_io vio) ; +extern void uty_out_fflush(vty_io vio, FILE* file) ; + +extern void uty_set_height(vty_io vio) ; +extern void uty_cmd_output_start(vty_io vio) ; + +extern void uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) ; +extern void uty_sock_set_timer(vio_sock sock, unsigned long timeout) ; + +extern 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/zassert.h b/lib/zassert.h index a766eb7b..8ca2203f 100644 --- a/lib/zassert.h +++ b/lib/zassert.h @@ -49,11 +49,8 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file, #define dassert(EX) #endif -/* TODO: implement _zlog_abort() to give required messages */ - /* Abort with message */ -#define zabort(MS) _zlog_assert_failed(MS, __FILE__, __LINE__, \ - __ASSERT_FUNCTION) +#define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION) /* Abort with message and errno and strerror() thereof */ #define zabort_errno(MS) _zlog_abort_errno(MS, __FILE__, __LINE__, \ diff --git a/lib/zclient.c b/lib/zclient.c index 8cfa5d52..f365a1aa 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -116,7 +116,7 @@ void zclient_init (struct zclient *zclient, int redist_default) { int i; - + /* Enable zebra client connection by default. */ zclient->enable = 1; @@ -202,8 +202,8 @@ zclient_socket(void) sock = socket (AF_INET, SOCK_STREAM, 0); if (sock < 0) return -1; - - /* Make server socket. */ + + /* Make server socket. */ memset (&serv, 0, sizeof (struct sockaddr_in)); serv.sin_family = AF_INET; serv.sin_port = htons (ZEBRA_PORT); @@ -235,8 +235,8 @@ zclient_socket_un (const char *path) sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) return -1; - - /* Make server socket. */ + + /* Make server socket. */ memset (&addr, 0, sizeof (struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy (addr.sun_path, path, strlen (path)); @@ -370,7 +370,7 @@ zebra_message_send (struct zclient *zclient, int command) /* Send very simple command only Zebra message. */ zclient_create_header (s, command); - + return zclient_send_message(zclient); } @@ -396,7 +396,7 @@ zclient_start (struct zclient *zclient) return 0; /* Check timer */ - if (zclient->qtr && qtr_is_active(zclient->qtr)) + if (zclient->qtr && zclient->qtr->active) return 0; /* Make socket. */ @@ -421,7 +421,7 @@ zclient_start (struct zclient *zclient) zclient->fail = 0; if (zclient_debug) zlog_debug ("zclient connect success with socket [%d]", zclient->sock); - + if (zclient_nexus) qps_add_file(zclient_nexus->selection, zclient->qf, zclient->sock, zclient); @@ -521,13 +521,13 @@ zlookup_connect_r (qtimer qtr, void* timer_info, qtime_t when) } - /* + /* * "xdr_encode"-like interface that allows daemon (client) to send * a message to zebra server for a route that needs to be * added/deleted to the kernel. Info about the route is specified * by the caller in a struct zapi_ipv4. zapi_ipv4_read() then writes * the info down the zclient socket using the stream_* functions. - * + * * The corresponding read ("xdr_decode") function on the server * side is zread_ipv4_add()/zread_ipv4_delete(). * @@ -539,11 +539,11 @@ zlookup_connect_r (qtimer qtr, void* timer_info, qtime_t when) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Destination IPv4 Prefix for route | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Nexthop count | + * | Nexthop count | * +-+-+-+-+-+-+-+-+ * - * - * A number of IPv4 nexthop(s) or nexthop interface index(es) are then + * + * A number of IPv4 nexthop(s) or nexthop interface index(es) are then * described, as per the Nexthop count. Each nexthop described as: * * +-+-+-+-+-+-+-+-+ @@ -553,18 +553,18 @@ zlookup_connect_r (qtimer qtr, void* timer_info, qtime_t when) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Alternatively, if the flags field has ZEBRA_FLAG_BLACKHOLE or - * ZEBRA_FLAG_REJECT is set then Nexthop count is set to 1, then _no_ + * ZEBRA_FLAG_REJECT is set then Nexthop count is set to 1, then _no_ * nexthop information is provided, and the message describes a prefix * to blackhole or reject route. * * If ZAPI_MESSAGE_DISTANCE is set, the distance value is written as a 1 * byte value. - * + * * If ZAPI_MESSAGE_METRIC is set, the metric value is written as an 8 * byte value. * * XXX: No attention paid to alignment. - */ + */ int zapi_ipv4_route (u_char cmd, struct zclient *zclient, struct prefix_ipv4 *p, struct zapi_ipv4 *api) @@ -576,9 +576,9 @@ zapi_ipv4_route (u_char cmd, struct zclient *zclient, struct prefix_ipv4 *p, /* Reset stream. */ s = zclient->obuf; stream_reset (s); - + zclient_create_header (s, cmd); - + /* Put type and nexthop. */ stream_putc (s, api->type); stream_putc (s, api->flags); @@ -644,7 +644,7 @@ zapi_ipv6_route (u_char cmd, struct zclient *zclient, struct prefix_ipv6 *p, stream_putc (s, api->type); stream_putc (s, api->flags); stream_putc (s, api->message); - + /* Put prefix information. */ psize = PSIZE (p->prefixlen); stream_putc (s, p->prefixlen); @@ -679,10 +679,10 @@ zapi_ipv6_route (u_char cmd, struct zclient *zclient, struct prefix_ipv6 *p, } #endif /* HAVE_IPV6 */ -/* +/* * send a ZEBRA_REDISTRIBUTE_ADD or ZEBRA_REDISTRIBUTE_DELETE * for the route type (ZEBRA_ROUTE_KERNEL etc.). The zebra server will - * then set/unset redist[type] in the client handle (a struct zserv) for the + * then set/unset redist[type] in the client handle (a struct zserv) for the * sending client */ int @@ -692,12 +692,12 @@ zebra_redistribute_send (int command, struct zclient *zclient, int type) s = zclient->obuf; stream_reset(s); - + zclient_create_header (s, command); stream_putc (s, type); - + stream_putw_at (s, 0, stream_get_endp (s)); - + return zclient_send_message(zclient); } @@ -716,7 +716,7 @@ zebra_router_id_update_read (struct stream *s, struct prefix *rid) } /* Interface addition from zebra daemon. */ -/* +/* * The format of the message sent with type ZEBRA_INTERFACE_ADD or * ZEBRA_INTERFACE_DELETE from zebra to the client is: * 0 1 2 3 @@ -776,11 +776,11 @@ zebra_interface_add_read (struct stream *s) if (ifp->hw_addr_len) stream_get (ifp->hw_addr, s, ifp->hw_addr_len); #endif /* HAVE_STRUCT_SOCKADDR_DL */ - + return ifp; } -/* +/* * Read interface up/down msg (ZEBRA_INTERFACE_UP/ZEBRA_INTERFACE_DOWN) * from zebra server. The format of this message is the same as * that sent for ZEBRA_INTERFACE_ADD/ZEBRA_INTERFACE_DELETE (see @@ -818,7 +818,7 @@ zebra_interface_state_read (struct stream *s) return ifp; } -/* +/* * format of message for address additon is: * 0 * 0 1 2 3 4 5 6 7 @@ -918,7 +918,7 @@ zebra_interface_address_read (int type, struct stream *s) stream_get (&d.u.prefix, s, plen); d.family = family; - if (type == ZEBRA_INTERFACE_ADDRESS_ADD) + if (type == ZEBRA_INTERFACE_ADDRESS_ADD) { /* N.B. NULL destination pointers are encoded as all zeroes */ ifc = connected_add_by_prefix(ifp, &p,(memconstant(&d.u.prefix,0,plen) ? @@ -995,15 +995,15 @@ zclient_read (struct zclient *zclient) marker = stream_getc (zclient->ibuf); version = stream_getc (zclient->ibuf); command = stream_getw (zclient->ibuf); - + if (marker != ZEBRA_HEADER_MARKER || version != ZSERV_VERSION) { zlog_err("%s: socket %d version mismatch, marker %d, version %d", __func__, zclient->sock, marker, version); return zclient_failed(zclient); } - - if (length < ZEBRA_HEADER_SIZE) + + if (length < ZEBRA_HEADER_SIZE) { zlog_err("%s: socket %d message length %u is less than %d ", __func__, zclient->sock, length, ZEBRA_HEADER_SIZE); @@ -1112,7 +1112,7 @@ void zclient_redistribute (int command, struct zclient *zclient, int type) { - if (command == ZEBRA_REDISTRIBUTE_ADD) + if (command == ZEBRA_REDISTRIBUTE_ADD) { if (zclient->redist[type]) return; @@ -1140,7 +1140,7 @@ zclient_redistribute_default (int command, struct zclient *zclient) return; zclient->default_information = 1; } - else + else { if (!zclient->default_information) return; @@ -1169,20 +1169,20 @@ zclient_event_r (enum event event, struct zclient *zclient) switch (event) { case ZLOOKUP_SCHEDULE: - if (!qtr_is_active(zclient->qtr)) + if (!zclient->qtr->active) qtimer_set(zclient->qtr, qt_get_monotonic(), zlookup_connect_r) ; break; case ZCLIENT_SCHEDULE: - if (!qtr_is_active(zclient->qtr)) + if (!zclient->qtr->active) qtimer_set(zclient->qtr, qt_get_monotonic(), zclient_connect_r) ; break; case ZCLIENT_CONNECT: if (zclient->fail >= 10) return; if (zclient_debug) - zlog_debug ("zclient connect schedule interval is %d", + zlog_debug ("zclient connect schedule interval is %d", zclient->fail < 3 ? 10 : 60); - if (!qtr_is_active(zclient->qtr)) + if (!zclient->qtr->active) qtimer_set(zclient->qtr, qt_add_monotonic(QTIME(zclient->fail < 3 ? 10 : 60)), zclient_connect_r) ; break; diff --git a/ospf6d/ospf6_top.c b/ospf6d/ospf6_top.c index 82370268..65a184fb 100644 --- a/ospf6d/ospf6_top.c +++ b/ospf6d/ospf6_top.c @@ -14,9 +14,9 @@ * 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. + * 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> @@ -189,7 +189,7 @@ ospf6_disable (struct ospf6 *o) if (! CHECK_FLAG (o->flag, OSPF6_DISABLED)) { SET_FLAG (o->flag, OSPF6_DISABLED); - + for (ALL_LIST_ELEMENTS (o->area_list, node, nnode, oa)) ospf6_area_disable (oa); @@ -229,7 +229,7 @@ ospf6_maxage_remover (struct thread *thread) { for (ALL_LIST_ELEMENTS_RO (oa->if_list, j, oi)) OSPF6_LSDB_MAXAGE_REMOVER (oi->lsdb); - + OSPF6_LSDB_MAXAGE_REMOVER (oa->lsdb); } OSPF6_LSDB_MAXAGE_REMOVER (o->lsdb); @@ -520,9 +520,11 @@ DEFUN (show_ipv6_ospf6_route_match, "Display routes which match the specified route\n" ) { - const char *sargv[CMD_ARGC_MAX]; + const char** sargv ; int i, sargc; + sargv = XMALLOC(MTYPE_TMP, (argc + 2) * sizeof(char*)) ; + /* copy argv to sargv and then append "match" */ for (i = 0; i < argc; i++) sargv[i] = argv[i]; @@ -531,6 +533,8 @@ DEFUN (show_ipv6_ospf6_route_match, sargv[sargc] = NULL; ospf6_route_table_show (vty, sargc, sargv, ospf6->route_table); + + XFREE(MTYPE_TMP, sargv) ; return CMD_SUCCESS; } @@ -546,9 +550,11 @@ DEFUN (show_ipv6_ospf6_route_match_detail, "Detailed information\n" ) { - const char *sargv[CMD_ARGC_MAX]; + const char** sargv ; int i, sargc; + sargv = XMALLOC(MTYPE_TMP, (argc + 2) * sizeof(char*)) ; + /* copy argv to sargv and then append "match" and "detail" */ for (i = 0; i < argc; i++) sargv[i] = argv[i]; @@ -558,6 +564,7 @@ DEFUN (show_ipv6_ospf6_route_match_detail, sargv[sargc] = NULL; ospf6_route_table_show (vty, sargc, sargv, ospf6->route_table); + XFREE(MTYPE_TMP, sargv) ; return CMD_SUCCESS; } @@ -611,9 +618,11 @@ DEFUN (show_ipv6_ospf6_route_type_detail, "Detailed information\n" ) { - const char *sargv[CMD_ARGC_MAX]; + const char** sargv ; int i, sargc; + sargv = XMALLOC(MTYPE_TMP, (argc + 2) * sizeof(char*)) ; + /* copy argv to sargv and then append "detail" */ for (i = 0; i < argc; i++) sargv[i] = argv[i]; @@ -622,6 +631,7 @@ DEFUN (show_ipv6_ospf6_route_type_detail, sargv[sargc] = NULL; ospf6_route_table_show (vty, sargc, sargv, ospf6->route_table); + XFREE(MTYPE_TMP, sargv) ; return CMD_SUCCESS; } diff --git a/ospf6d/ospf6d.c b/ospf6d/ospf6d.c index bb091d4f..837a718e 100644 --- a/ospf6d/ospf6d.c +++ b/ospf6d/ospf6d.c @@ -14,9 +14,9 @@ * 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. + * 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> @@ -77,7 +77,7 @@ route_prev (struct route_node *node) return prev; } - + /* show database functions */ DEFUN (show_version_ospf6, show_version_ospf6_cmd, @@ -1733,12 +1733,14 @@ DEFUN (show_ipv6_ospf6_linkstate_detail, "Display linkstate routing table\n" ) { - const char *sargv[CMD_ARGC_MAX]; + const char** sargv; int i, sargc; struct listnode *node; struct ospf6_area *oa; - /* copy argv to sargv and then append "detail" */ + sargv = XMALLOC(MTYPE_TMP, (argc + 2) * sizeof(char*)) ; + + /* copy argv to sargv and then append "detail" */ for (i = 0; i < argc; i++) sargv[i] = argv[i]; sargc = argc; @@ -1753,6 +1755,7 @@ DEFUN (show_ipv6_ospf6_linkstate_detail, } vty_out (vty, "%s", VNL); + XFREE(MTYPE_TMP, sargv) ; return CMD_SUCCESS; } diff --git a/tests/test-vector.c b/tests/test-vector.c index 6aecfd6e..b54ae9f9 100644 --- a/tests/test-vector.c +++ b/tests/test-vector.c @@ -102,16 +102,14 @@ test_vector_init(void) printf("test_vector_init\n"); v = vector_init(10); - + assert_true(v != NULL, "v == NULL"); assert_true(vector_count(v) == 0, "vector_count != 0"); assert_true(vector_active(v) == 0, "vector_active != 0"); - assert_true(vector_empty_slot(v) == 0, - "test_vector_init: vector_empty_slot != 0"); - + vector_free(v); } @@ -128,8 +126,6 @@ test_vector_set_index(void) "vector_count != 1"); assert_true(vector_active(v) == 1001, "vector_active != 1001"); - assert_true(vector_empty_slot(v) == 0, - "vector_empty_slot != 0"); assert_true(vector_slot(v,1000) == s1000, "vector_slot != 1000"); diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 4c9322ae..64c1b549 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -19,7 +19,7 @@ * 02111-1307, USA. */ -#include <zebra.h> +#include "zebra.h" #include <sys/un.h> #include <setjmp.h> @@ -277,20 +277,20 @@ vtysh_execute_func (const char *line, int pager) { int ret, cmd_stat; u_int i; - vector vline; struct cmd_element *cmd; FILE *fp = NULL; int closepager = 0; int tried = 0; int saved_ret, saved_node; - /* Split readline string up into the vector. */ - vline = cmd_make_strvec (line); + /* TODO: how well does vtysh_execute_func work ?? -- esp. qpthreads_enabled */ + vty->buf = line ; - if (vline == NULL) - return CMD_SUCCESS; + saved_ret = ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); + + if ((ret == CMD_SUCCESS) && (cmd == NULL)) + return ret ; /* quit if nothing to do ??? */ - saved_ret = ret = cmd_execute_command (vline, vty, &cmd, NULL, 1); saved_node = vty->node; /* If command doesn't succeeded in current node, try to walk up in node tree. @@ -300,7 +300,7 @@ vtysh_execute_func (const char *line, int pager) && vty->node > CONFIG_NODE) { vty->node = node_parent(vty->node); - ret = cmd_execute_command (vline, vty, &cmd, NULL, 1); + ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); tried++; } @@ -334,8 +334,6 @@ vtysh_execute_func (const char *line, int pager) ret = saved_ret; } - cmd_free_strvec (vline); - cmd_stat = ret; switch (ret) { @@ -383,23 +381,10 @@ vtysh_execute_func (const char *line, int pager) if (cmd_stat) { line = "end"; - vline = cmd_make_strvec (line); - if (vline == NULL) - { - if (pager && vtysh_pager_name && fp && closepager) - { - if (pclose (fp) == -1) - { - perror ("pclose failed for pager"); - } - fp = NULL; - } - return CMD_SUCCESS; - } + vty->buf = line ; - ret = cmd_execute_command (vline, vty, &cmd, NULL, NULL, 1); - cmd_free_strvec (vline); + ret = cmd_execute_command (vty, cmd_parse_completion, &cmd); if (ret != CMD_SUCCESS_DAEMON) break; } @@ -456,22 +441,15 @@ int vtysh_config_from_file (struct vty *vty, FILE *fp) { int ret; - vector vline; struct cmd_element *cmd; - while (fgets (vty->buf, VTY_BUFSIZ, fp)) - { - if (vty->buf[0] == '!' || vty->buf[1] == '#') - continue; - - vline = cmd_make_strvec (vty->buf); + /* TODO: (1) allocate buffer for vty->buf (2) what about CMD_QUEUED ?? */ - /* In case of comment line. */ - if (vline == NULL) - continue; + while (fgets (vty->buf, VTY_BUFSIZ, fp)) + { /* Execute configuration command : this is strict match. */ - ret = cmd_execute_command_strict (vline, vty, &cmd); + ret = cmd_execute_command(vty, cmd_parse_strict, &cmd); /* Try again with setting node to CONFIG_NODE. */ if (ret != CMD_SUCCESS @@ -482,7 +460,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) { vty->node = KEYCHAIN_NODE; vtysh_exit_ripd_only (); - ret = cmd_execute_command_strict (vline, vty, &cmd); + ret = cmd_execute_command(vty, cmd_parse_strict, &cmd); if (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON @@ -490,7 +468,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) { vtysh_exit_ripd_only (); vty->node = CONFIG_NODE; - ret = cmd_execute_command_strict (vline, vty, &cmd); + ret = cmd_execute_command(vty, cmd_parse_strict, &cmd); } } else @@ -498,7 +476,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp) vtysh_execute ("end"); vtysh_execute ("configure terminal"); vty->node = CONFIG_NODE; - ret = cmd_execute_command_strict (vline, vty, &cmd); + ret = cmd_execute_command(vty, cmd_parse_strict, &cmd); } } @@ -2227,7 +2205,7 @@ void vtysh_init_vty (void) { /* Make vty structure. */ - vty = vty_new (0, VTY_SHELL); + vty = vty_open(VTY_SHELL); vty->node = VIEW_NODE; /* Initialize commands. */ diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index bfcf8e64..15938f43 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -101,7 +101,7 @@ config_get (int index, const char *line) master->cmp = (int (*)(void *, void *)) config_cmp; vector_set_index (configvec, index, master); } - + for (ALL_LIST_ELEMENTS (master, node, nnode, config_loop)) { if (strcmp (config_loop->name, line) == 0) @@ -265,7 +265,7 @@ vtysh_config_parse (char *line) { char *begin; char *pnt; - + begin = pnt = line; while (*pnt != '\0') @@ -353,9 +353,9 @@ vtysh_read_file (FILE *confp) int ret; struct vty *vty; - vty = vty_new (0, VTY_TERM); /* stdout */ + vty = vty_open(VTY_STDOUT); /* stdout */ vty->node = CONFIG_NODE; - + vtysh_execute_no_pager ("enable"); vtysh_execute_no_pager ("configure terminal"); @@ -367,7 +367,7 @@ vtysh_read_file (FILE *confp) vty_close (vty); - if (ret != CMD_SUCCESS) + if (ret != CMD_SUCCESS) { switch (ret) { @@ -378,7 +378,7 @@ vtysh_read_file (FILE *confp) fprintf (stderr, "There is no such command.\n"); break; } - fprintf (stderr, "Error occured during reading below line.\n%s\n", + fprintf (stderr, "Error occured during reading below line.\n%s\n", vty->buf); exit (1); } |