summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp.h14
-rw-r--r--bgpd/bgp_advertise.h4
-rw-r--r--bgpd/bgp_aspath.h7
-rw-r--r--bgpd/bgp_btoa.c10
-rw-r--r--bgpd/bgp_clist.c8
-rw-r--r--bgpd/bgp_common.c1
-rw-r--r--bgpd/bgp_common.h7
-rw-r--r--bgpd/bgp_connection.c9
-rw-r--r--bgpd/bgp_connection.h6
-rw-r--r--bgpd/bgp_debug.c38
-rw-r--r--bgpd/bgp_dump.c24
-rw-r--r--bgpd/bgp_engine.h52
-rw-r--r--bgpd/bgp_main.c92
-rw-r--r--bgpd/bgp_msg_write.h3
-rw-r--r--bgpd/bgp_notification.c2
-rw-r--r--bgpd/bgp_notification.h6
-rw-r--r--bgpd/bgp_open_state.c18
-rw-r--r--bgpd/bgp_open_state.h10
-rw-r--r--bgpd/bgp_packet.c10
-rw-r--r--bgpd/bgp_peer.c2
-rw-r--r--bgpd/bgp_peer_index.c23
-rw-r--r--bgpd/bgp_route.c6
-rw-r--r--bgpd/bgp_route_refresh.c7
-rw-r--r--bgpd/bgp_route_refresh.h12
-rw-r--r--bgpd/bgp_routemap.c25
-rw-r--r--bgpd/bgp_session.c9
-rw-r--r--bgpd/bgp_session.h10
-rw-r--r--bgpd/bgp_snmp.c38
-rw-r--r--bgpd/bgp_vty.c272
-rw-r--r--bgpd/bgpd.c3
-rw-r--r--bgpd/bgpd.h8
-rwxr-xr-xconfigure.ac60
-rw-r--r--isisd/dict.c36
-rw-r--r--isisd/dict.h2
-rw-r--r--isisd/isis_misc.c37
-rw-r--r--isisd/isis_spf.c40
-rw-r--r--isisd/topology/spgrid.c3
-rw-r--r--lib/Makefile.am60
-rw-r--r--lib/command.c4310
-rw-r--r--lib/command.h304
-rw-r--r--lib/command_common.h290
-rw-r--r--lib/command_execute.c568
-rw-r--r--lib/command_execute.h133
-rw-r--r--lib/command_local.h203
-rw-r--r--lib/command_parse.c4881
-rw-r--r--lib/command_parse.h814
-rw-r--r--lib/command_queue.c586
-rw-r--r--lib/command_queue.h8
-rw-r--r--lib/elstring.c282
-rw-r--r--lib/elstring.h297
-rw-r--r--lib/errno_names.c2
-rw-r--r--lib/getopt.c4
-rw-r--r--lib/getopt1.c4
-rw-r--r--lib/heap.c93
-rw-r--r--lib/heap.h45
-rw-r--r--lib/if.c13
-rw-r--r--lib/if.h14
-rw-r--r--lib/keychain.c59
-rw-r--r--lib/keystroke.c1139
-rw-r--r--lib/keystroke.h79
-rw-r--r--lib/list_util.c79
-rw-r--r--lib/list_util.h230
-rw-r--r--lib/log.c1491
-rw-r--r--lib/log.h280
-rw-r--r--lib/log_common.h79
-rw-r--r--lib/log_local.h168
-rw-r--r--lib/mem_tracker.c590
-rw-r--r--lib/memory.c3506
-rw-r--r--lib/memory.h193
-rw-r--r--lib/memtypes.awk13
-rw-r--r--lib/memtypes.c11
-rw-r--r--lib/misc.h166
-rw-r--r--lib/miyagi.h4
-rw-r--r--lib/mqueue.c126
-rw-r--r--lib/mqueue.h100
-rw-r--r--lib/network.c248
-rw-r--r--lib/network.h7
-rw-r--r--lib/node_type.h81
-rw-r--r--lib/plist.c45
-rw-r--r--lib/plist.h2
-rw-r--r--lib/prefix.c47
-rw-r--r--lib/prefix.h5
-rw-r--r--lib/privs.c71
-rw-r--r--lib/pthread_safe.c246
-rw-r--r--lib/pthread_safe.h20
-rw-r--r--lib/qafi_safi.h2
-rw-r--r--lib/qdebug_nb.h61
-rw-r--r--lib/qfstring.c448
-rw-r--r--lib/qfstring.h246
-rw-r--r--lib/qiovec.c302
-rw-r--r--lib/qiovec.h182
-rw-r--r--lib/qlib_init.c90
-rw-r--r--lib/qlib_init.h14
-rw-r--r--lib/qpath.c1528
-rw-r--r--lib/qpath.h217
-rw-r--r--lib/qpnexus.c114
-rw-r--r--lib/qpnexus.h64
-rw-r--r--lib/qpselect.c185
-rw-r--r--lib/qpselect.h33
-rw-r--r--lib/qpthreads.c212
-rw-r--r--lib/qpthreads.h201
-rw-r--r--lib/qrand.c48
-rw-r--r--lib/qrand.h52
-rw-r--r--lib/qstring.c793
-rw-r--r--lib/qstring.h823
-rw-r--r--lib/qtime.c253
-rw-r--r--lib/qtime.h155
-rw-r--r--lib/qtimers.c41
-rw-r--r--lib/qtimers.h41
-rw-r--r--lib/regex.c51
-rw-r--r--lib/routemap.c19
-rw-r--r--lib/routemap.h2
-rw-r--r--lib/sigevent.c1006
-rw-r--r--lib/sigevent.h34
-rw-r--r--lib/sockopt.c2
-rw-r--r--lib/sockunion.c1
-rw-r--r--lib/stream.c2
-rw-r--r--lib/symtab.c69
-rw-r--r--lib/symtab.h83
-rw-r--r--lib/temp.c1349
-rw-r--r--lib/thread.c12
-rw-r--r--lib/thread.h4
-rw-r--r--lib/tstring.h139
-rw-r--r--lib/uty.h239
-rw-r--r--lib/vargs.h34
-rw-r--r--lib/vector.c483
-rw-r--r--lib/vector.h172
-rw-r--r--lib/version.h.in2
-rw-r--r--lib/vio_fifo.c1825
-rw-r--r--lib/vio_fifo.h287
-rw-r--r--lib/vio_lines.c594
-rw-r--r--lib/vio_lines.h158
-rw-r--r--lib/vty.c1467
-rw-r--r--lib/vty.h181
-rw-r--r--lib/vty_cli.c3473
-rw-r--r--lib/vty_cli.h202
-rw-r--r--lib/vty_command.c2202
-rw-r--r--lib/vty_command.h70
-rw-r--r--lib/vty_common.h144
-rw-r--r--lib/vty_io.c3637
-rw-r--r--lib/vty_io.h635
-rw-r--r--lib/vty_io_basic.c1110
-rw-r--r--lib/vty_io_basic.h232
-rw-r--r--lib/vty_io_file.c2761
-rw-r--r--lib/vty_io_file.h70
-rw-r--r--lib/vty_io_shell.c369
-rw-r--r--lib/vty_io_shell.h55
-rw-r--r--lib/vty_io_term.c1607
-rw-r--r--lib/vty_io_term.h79
-rw-r--r--lib/vty_io_vsh.c369
-rw-r--r--lib/vty_io_vsh.h55
-rw-r--r--lib/vty_local.h231
-rw-r--r--lib/vty_log.c323
-rw-r--r--lib/vty_log.h42
-rw-r--r--lib/vty_pipe.c496
-rw-r--r--lib/workqueue.h8
-rw-r--r--lib/zassert.h10
-rw-r--r--lib/zconfig.h42
-rw-r--r--lib/zebra.h6
-rw-r--r--ospf6d/ospf6_lsa.c14
-rw-r--r--ospfd/ospf_spf.c168
-rw-r--r--ripngd/ripngd.c5
-rw-r--r--tests/Makefile.am6
-rw-r--r--tests/test-list_util.c29
-rw-r--r--tests/test-qpath.c490
-rw-r--r--tests/test-symtab.c23
-rw-r--r--tests/test-vector.c103
-rw-r--r--vtysh/vtysh.c82
-rw-r--r--vtysh/vtysh.h4
-rw-r--r--vtysh/vtysh_config.c43
-rw-r--r--vtysh/vtysh_main.c15
-rw-r--r--vtysh/vtysh_user.c25
-rw-r--r--vtysh/vtysh_user.h3
173 files changed, 41129 insertions, 15339 deletions
diff --git a/bgpd/bgp.h b/bgpd/bgp.h
index 6019b33c..0470c5ae 100644
--- a/bgpd/bgp.h
+++ b/bgpd/bgp.h
@@ -30,7 +30,7 @@
#define _GMCH_BGP_H "19-Dec-2009"
-#include <stdint.h>
+#include "misc.h"
#include "confirm.h"
/*##############################################################################
@@ -815,7 +815,8 @@ typedef U8 BGP_ATT_ORIGIN_T ; /* one byte of data ! */
VALUE(BGP_ATT_ORIGIN_L = sizeof(BGP_ATT_ORIGIN_T)) ;
-enum BGP_ATT_ORG {
+enum BGP_ATT_ORG
+{
BGP_ATT_ORG_MIN = 0,
BGP_ATT_ORG_IGP = 0, /* NLRI is interior to originating AS */
@@ -851,7 +852,8 @@ typedef U32 BGP_ATT_ASPS_AS4_T ;
/* AS Path Segment Types......................................................*/
-enum BGP_AS_SEG {
+enum BGP_AS_SEG
+{
BGP_AS_SET = 1,
BGP_AS_SEQUENCE = 2,
BGP_AS_CONFED_SEQUENCE = 3, /* RFC5065 */
@@ -862,7 +864,8 @@ enum BGP_AS_SEG {
#define AS4(h, l) (((h) << 16) + (l))
-enum BGP_ASN {
+enum BGP_ASN
+{
BGP_ASN_NULL = 0, /* Reserved */
BGP_ASN_RES1_S = 64496, /* Start of Reservation 1 (0xFBF0) */
@@ -962,7 +965,8 @@ enum
BGP_ATT_COM_MS_RES2 = 0xFFFF /* 0xFFFF_0000..0xFFFF_FFFF are reserved */
} ;
-enum BGP_COMM {
+enum BGP_COMM
+{
BGP_ATT_COM_RES1_S = 0x00000000,
BGP_ATT_COM_INTERNET = 0x00000000,
diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h
index ca92238a..afa812f6 100644
--- a/bgpd/bgp_advertise.h
+++ b/bgpd/bgp_advertise.h
@@ -21,9 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _QUAGGA_BGP_ADVERTISE_H
#define _QUAGGA_BGP_ADVERTISE_H
-#ifndef Inline
-#define Inline static inline
-#endif
+#include "lib/misc.h"
/* BGP advertise FIFO. */
typedef struct bgp_advertise* bgp_advertise ;
diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h
index 74eb775c..49a3a5be 100644
--- a/bgpd/bgp_aspath.h
+++ b/bgpd/bgp_aspath.h
@@ -21,12 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _QUAGGA_BGP_ASPATH_H
#define _QUAGGA_BGP_ASPATH_H
-#include <stdbool.h>
-
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
+#include "lib/misc.h"
/* AS path segment type. */
#define AS_SET 1
diff --git a/bgpd/bgp_btoa.c b/bgpd/bgp_btoa.c
index 7c708814..42cd0da2 100644
--- a/bgpd/bgp_btoa.c
+++ b/bgpd/bgp_btoa.c
@@ -18,8 +18,6 @@ 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 "zebra.h"
#include "stream.h"
#include "log.h"
@@ -91,11 +89,11 @@ attr_parse (struct stream *s, u_int16_t len)
aspath.str = aspath_make_str_count (&aspath);
printf ("ASPATH: %s\n", aspath.str);
free (aspath.str);
-
+
stream_forward (s, length);
}
break;
- case BGP_ATTR_NEXT_HOP:
+ case BGP_ATTR_NEXT_HOP:
{
struct in_addr nexthop;
nexthop.s_addr = stream_get_ipv4 (s);
@@ -144,7 +142,7 @@ main (int argc, char **argv)
perror ("fopen");
exit (1);
}
-
+
while (1)
{
stream_reset (s);
@@ -279,7 +277,7 @@ main (int argc, char **argv)
sip.s_addr = stream_get_ipv4 (s);
dip.s_addr = stream_get_ipv4 (s);
-
+
printf ("saddr: %s\n", inet_ntoa (sip));
printf ("daddr: %s\n", inet_ntoa (dip));
diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c
index 9c57cb65..ecf1a526 100644
--- a/bgpd/bgp_clist.c
+++ b/bgpd/bgp_clist.c
@@ -127,7 +127,7 @@ community_list_lookup (struct community_list_handler *ch,
if (!table)
return NULL;
- return symbol_get_value(symbol_seek(table, name)) ;
+ return symbol_get_value(symbol_lookup(table, name, no_add)) ;
}
static struct community_list *
@@ -145,7 +145,7 @@ community_list_get (struct community_list_handler *ch,
if (!table)
return NULL;
- sym = symbol_find(table, name) ;
+ sym = symbol_lookup(table, name, add) ;
list = symbol_get_value(sym) ;
if (!list)
{
@@ -768,10 +768,10 @@ community_list_terminate (struct community_list_handler *ch)
{
struct community_list *list ;
- while ((list = symbol_table_ream_keep(&ch->community_list)))
+ while ((list = symbol_table_ream(&ch->community_list, keep_it)))
community_list_delete(list) ;
- while ((list = symbol_table_ream_keep(&ch->extcommunity_list)))
+ while ((list = symbol_table_ream(&ch->extcommunity_list, keep_it)))
community_list_delete(list) ;
XFREE (MTYPE_COMMUNITY_LIST_HANDLER, ch);
diff --git a/bgpd/bgp_common.c b/bgpd/bgp_common.c
index cfee5ca2..8a3a57ff 100644
--- a/bgpd/bgp_common.c
+++ b/bgpd/bgp_common.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include "bgpd/bgp_common.h"
#include "lib/zassert.h"
diff --git a/bgpd/bgp_common.h b/bgpd/bgp_common.h
index a1f4df42..e115bfd5 100644
--- a/bgpd/bgp_common.h
+++ b/bgpd/bgp_common.h
@@ -22,18 +22,13 @@
#ifndef _QUAGGA_BGP_COMMON_H
#define _QUAGGA_BGP_COMMON_H
-#include <stdint.h>
-#include <stdbool.h>
+#include "misc.h"
#include <sys/socket.h>
#include "bgpd/bgp.h"
#include "qafi_safi.h"
#include "lib/zassert.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Here are a number of "incomplete" declarations, which allow a number of
* bgpd structures to refer to each other.
diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c
index d7997dba..24c86230 100644
--- a/bgpd/bgp_connection.c
+++ b/bgpd/bgp_connection.c
@@ -20,8 +20,9 @@
*/
#include <zebra.h>
-#include "bgpd/bgp_connection.h"
+#include "misc.h"
+#include "bgpd/bgp_connection.h"
#include "bgpd/bgpd.h"
#include "bgpd/bgp_fsm.h"
#include "bgpd/bgp_engine.h"
@@ -154,7 +155,7 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session,
/* Link back to session, point at its mutex and point session here */
connection->session = session ;
- connection->p_mutex = &session->mutex ;
+ connection->p_mutex = session->mutex ;
connection->lock_count = 0 ; /* no question about it */
connection->ordinal = ordinal ;
@@ -347,7 +348,7 @@ bgp_connection_free(bgp_connection 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) ;
+ connection->keepalive_timer = qtimer_free(connection->keepalive_timer) ;
bgp_notify_unset(&connection->notification) ;
bgp_open_state_unset(&connection->open_recv) ;
@@ -678,7 +679,7 @@ bgp_connection_stop(bgp_connection connection, bool stop_writer)
connection->notification_pending = 0 ;
/* Empty out the pending queue and remove from connection queue */
- mqueue_local_reset_keep(&connection->pending_queue) ;
+ mqueue_local_reset(&connection->pending_queue, keep_it) ;
bgp_connection_queue_del(connection) ;
/* If required: set write buffer *unwritable* (and empty). */
diff --git a/bgpd/bgp_connection.h b/bgpd/bgp_connection.h
index ead222f9..4df1fbca 100644
--- a/bgpd/bgp_connection.h
+++ b/bgpd/bgp_connection.h
@@ -22,7 +22,7 @@
#ifndef _QUAGGA_BGP_CONNECTION_H
#define _QUAGGA_BGP_CONNECTION_H
-#include <stdbool.h>
+#include "lib/misc.h"
#include "lib/mqueue.h"
#include "lib/qpthreads.h"
@@ -40,10 +40,6 @@
#include "bgpd/bgp_notification.h"
#include "bgpd/bgp_msg_read.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* The BGP Finite State Machine: states and events
*
diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c
index eda146dd..83e6400a 100644
--- a/bgpd/bgp_debug.c
+++ b/bgpd/bgp_debug.c
@@ -367,7 +367,7 @@ DEFUN (debug_bgp_as4,
BGP_STR
"BGP AS4 actions\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (as4, AS4);
else
{
@@ -385,7 +385,7 @@ DEFUN (no_debug_bgp_as4,
BGP_STR
"BGP AS4 actions\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (as4, AS4);
else
{
@@ -410,7 +410,7 @@ DEFUN (debug_bgp_as4_segment,
"BGP AS4 actions\n"
"BGP AS4 aspath segment handling\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (as4, AS4_SEGMENT);
else
{
@@ -429,7 +429,7 @@ DEFUN (no_debug_bgp_as4_segment,
"BGP AS4 actions\n"
"BGP AS4 aspath segment handling\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (as4, AS4_SEGMENT);
else
{
@@ -454,7 +454,7 @@ DEFUN (debug_bgp_fsm,
BGP_STR
"BGP Finite State Machine\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (fsm, FSM);
else
{
@@ -472,7 +472,7 @@ DEFUN (no_debug_bgp_fsm,
BGP_STR
"Finite State Machine\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (fsm, FSM);
else
{
@@ -496,7 +496,7 @@ DEFUN (debug_bgp_events,
BGP_STR
"BGP events\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (events, EVENTS);
else
{
@@ -514,7 +514,7 @@ DEFUN (no_debug_bgp_events,
BGP_STR
"BGP events\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (events, EVENTS);
else
{
@@ -538,7 +538,7 @@ DEFUN (debug_bgp_filter,
BGP_STR
"BGP filters\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (filter, FILTER);
else
{
@@ -556,7 +556,7 @@ DEFUN (no_debug_bgp_filter,
BGP_STR
"BGP filters\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (filter, FILTER);
else
{
@@ -580,7 +580,7 @@ DEFUN (debug_bgp_keepalive,
BGP_STR
"BGP keepalives\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (keepalive, KEEPALIVE);
else
{
@@ -598,7 +598,7 @@ DEFUN (no_debug_bgp_keepalive,
BGP_STR
"BGP keepalives\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (keepalive, KEEPALIVE);
else
{
@@ -622,7 +622,7 @@ DEFUN (debug_bgp_update,
BGP_STR
"BGP updates\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
{
DEBUG_ON (update, UPDATE_IN);
DEBUG_ON (update, UPDATE_OUT);
@@ -645,7 +645,7 @@ DEFUN (debug_bgp_update_direct,
"Inbound updates\n"
"Outbound updates\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
{
if (strncmp ("i", argv[0], 1) == 0)
{
@@ -684,7 +684,7 @@ DEFUN (no_debug_bgp_update,
BGP_STR
"BGP updates\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
{
DEBUG_OFF (update, UPDATE_IN);
DEBUG_OFF (update, UPDATE_OUT);
@@ -711,7 +711,7 @@ DEFUN (debug_bgp_normal,
DEBUG_STR
BGP_STR)
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (normal, NORMAL);
else
{
@@ -728,7 +728,7 @@ DEFUN (no_debug_bgp_normal,
DEBUG_STR
BGP_STR)
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (normal, NORMAL);
else
{
@@ -751,7 +751,7 @@ DEFUN (debug_bgp_zebra,
BGP_STR
"BGP Zebra messages\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_ON (zebra, ZEBRA);
else
{
@@ -769,7 +769,7 @@ DEFUN (no_debug_bgp_zebra,
BGP_STR
"BGP Zebra messages\n")
{
- if (vty_get_node(vty) == CONFIG_NODE)
+ if (vty->node == CONFIG_NODE)
DEBUG_OFF (zebra, ZEBRA);
else
{
diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c
index d76b5699..7c36825c 100644
--- a/bgpd/bgp_dump.c
+++ b/bgpd/bgp_dump.c
@@ -21,6 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include <zebra.h>
#include "log.h"
+#include "vty.h"
#include "stream.h"
#include "sockunion.h"
#include "command.h"
@@ -28,6 +29,8 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include "thread.h"
#include "linklist.h"
#include "bgpd/bgp_table.h"
+#include "qpath.h"
+#include "qstring.h"
#include "bgpd/bgpd.h"
#include "bgpd/bgp_route.h"
@@ -97,8 +100,8 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
int ret;
time_t clock;
struct tm tm;
- char fullpath[MAXPATHLEN];
- char realpath[MAXPATHLEN];
+ qpath path ;
+ qstring name ;
mode_t oldumask;
time (&clock);
@@ -106,11 +109,17 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
if (bgp_dump->filename[0] != DIRECTORY_SEP)
{
- sprintf (fullpath, "%s/%s", vty_get_cwd (), bgp_dump->filename);
- ret = strftime (realpath, MAXPATHLEN, fullpath, &tm);
+ path = vty_getcwd(NULL) ;
+ qpath_append_str(path, bgp_dump->filename) ;
}
else
- ret = strftime (realpath, MAXPATHLEN, bgp_dump->filename, &tm);
+ path = qpath_set(NULL, bgp_dump->filename) ;
+
+ name = qs_new_size(NULL, qpath_len(path) + 60) ;
+
+ ret = strftime (qs_char_nn(name), qs_len_nn(name), qpath_string(path), &tm);
+
+ qpath_free(path) ;
if (ret == 0)
{
@@ -123,11 +132,12 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
oldumask = umask(0777 & ~LOGFILE_MASK);
- bgp_dump->fp = fopen (realpath, "w");
+ bgp_dump->fp = fopen (qs_char_nn(name), "w");
if (bgp_dump->fp == NULL)
{
- zlog_warn("bgp_dump_open_file: %s: %s", realpath, errtoa(errno, 0).str);
+ zlog_warn("bgp_dump_open_file: %s: %s", qs_char_nn(name),
+ errtoa(errno, 0).str);
umask(oldumask);
return NULL;
}
diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h
index ceec1b2f..821d8127 100644
--- a/bgpd/bgp_engine.h
+++ b/bgpd/bgp_engine.h
@@ -22,23 +22,45 @@
#ifndef _QUAGGA_BGP_ENGINE_H
#define _QUAGGA_BGP_ENGINE_H
+#include "lib/misc.h"
+
#include "bgpd/bgpd.h"
#include "lib/mqueue.h"
#include "lib/qpnexus.h"
#include "lib/log.h"
-#ifndef Inline
-#define Inline static inline
+/*==============================================================================
+ * BGP_ENGINE_DEBUG setting
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if BGP_ENGINE_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set BGP_ENGINE_DEBUG == 0 to turn off debug
+ * * or set BGP_ENGINE_DEBUG != 0 to turn on debug
+ * * or set BGP_ENGINE_NO_DEBUG != to force debug off
+ */
+#ifdef BGP_ENGINE_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(BGP_ENGINE_DEBUG)
+# undef BGP_ENGINE_DEBUG
+# define BGP_ENGINE_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define BGP_ENGINE_DEBUG QDEBUG
#endif
-enum { qdebug =
-#ifdef QDEBUG
- 1
-#else
- 0
+#ifdef BGP_ENGINE_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(BGP_ENGINE_NO_DEBUG)
+# undef BGP_ENGINE_DEBUG
+# define BGP_ENGINE_DEBUG 0
+# endif
#endif
-};
+
+enum { bgp_engine_debug = BGP_ENGINE_DEBUG } ;
/*==============================================================================
*
@@ -70,7 +92,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
++stats->count ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
if (mq->count > stats->max)
stats->max = mq->count ;
@@ -81,7 +103,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
if (stats->count < 1000)
{
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
return ;
} ;
@@ -96,7 +118,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
assert(my_count == mq->count) ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
average = stats->total * 1000 ;
average = (average / stats->count) + 5 ;
@@ -119,10 +141,10 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
/* Send given message to the BGP Engine -- priority/ordinary
*/
Inline void
-bgp_to_bgp_engine(mqueue_block mqb, enum mqb_rank priority)
+bgp_to_bgp_engine(mqueue_block mqb, mqb_rank_b priority)
{
mqueue_enqueue(bgp_nexus->queue, mqb, priority) ;
- if (qdebug)
+ if (bgp_engine_debug)
bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ;
} ;
@@ -133,10 +155,10 @@ bgp_to_bgp_engine(mqueue_block mqb, enum mqb_rank priority)
/* Send given message to the Routing Engine -- priority/ordinary
*/
Inline void
-bgp_to_routing_engine(mqueue_block mqb, enum mqb_rank priority)
+bgp_to_routing_engine(mqueue_block mqb, mqb_rank_b priority)
{
mqueue_enqueue(routing_nexus->queue, mqb, priority) ;
- if (qdebug)
+ if (bgp_engine_debug)
bgp_queue_logging("Routing Engine", routing_nexus->queue,
&routing_engine_queue_stats) ;
} ;
diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c
index 60b66533..38fbb7e4 100644
--- a/bgpd/bgp_main.c
+++ b/bgpd/bgp_main.c
@@ -18,15 +18,15 @@ 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 <stdbool.h>
+#include "zebra.h"
+#include "misc.h"
#include "vector.h"
#include "vty.h"
#include "command.h"
#include "getopt.h"
#include "thread.h"
-#include <lib/version.h>
+#include "lib/version.h"
#include "memory.h"
#include "prefix.h"
#include "log.h"
@@ -118,13 +118,13 @@ static zebra_capabilities_t _caps_p [] =
struct zebra_privs_t bgpd_privs =
{
#if defined(QUAGGA_USER) && defined(QUAGGA_GROUP)
- .user = QUAGGA_USER,
- .group = QUAGGA_GROUP,
+ .user = QUAGGA_USER,
+ .group = QUAGGA_GROUP,
#endif
#ifdef VTY_GROUP
.vty_group = VTY_GROUP,
#endif
- .caps_p = _caps_p,
+ .caps_p = _caps_p,
.cap_num_p = sizeof(_caps_p)/sizeof(_caps_p[0]),
.cap_num_i = 0,
};
@@ -184,7 +184,7 @@ void sigusr2 (void);
/* prototypes */
static void bgp_exit (int);
-static void init_second_stage(int pthreads);
+static void init_second_stage(bool pthreads);
static void bgp_in_thread_init(void);
static void routing_start(void) ;
static void routing_finish(void) ;
@@ -206,10 +206,6 @@ static struct quagga_signal_t bgp_signals[] =
.handler = &sigusr1,
},
{
- .signal = SIGUSR2,
- .handler = &sigusr2,
- },
- {
.signal = SIGINT,
.handler = &sigint,
},
@@ -240,14 +236,17 @@ sighup (void)
}
-/* SIGINT handler. */
+/* SIGINT and SIGTERM handler. */
void
sigint (void)
{
zlog_notice ("Terminating on signal");
+ vty_reset_because("Terminating");
+
/* tell the routing engine to send notifies to peers and wait
- * for all sessions to be disabled */
+ * for all sessions to be disabled, then terminate.
+ */
sigterm_enqueue();
}
@@ -258,17 +257,6 @@ sigusr1 (void)
zlog_rotate (NULL);
}
-/* SIGUSR2 handler. */
-void
-sigusr2 (void)
-{
- /* Used to signal message queues */
- if (qpthreads_enabled)
- return;
- else
- exit(1);
-}
-
/*------------------------------------------------------------------------------
* Final exit code...
*
@@ -301,6 +289,7 @@ bgp_exit (int status)
/* reverse bgp_zebra_init/if_init */
if (retain_mode)
if_add_hook (IF_DELETE_HOOK, NULL);
+
for (ALL_LIST_ELEMENTS (iflist, node, nnode, ifp))
{
struct listnode *c_node, *c_nnode;
@@ -351,6 +340,7 @@ bgp_exit (int status)
cmd_terminate ();
vty_terminate ();
+
if (zclient)
zclient_free (zclient);
if (zlookup)
@@ -366,10 +356,10 @@ bgp_exit (int status)
if (qpthreads_enabled)
{
- qpn_reset_free(routing_nexus);
- qpn_reset_free(bgp_nexus);
+ qpn_reset(routing_nexus, free_it);
+ qpn_reset(bgp_nexus, free_it);
} ;
- cli_nexus = qpn_reset_free(cli_nexus);
+ cli_nexus = qpn_reset(cli_nexus, free_it);
if (CONF_BGP_DEBUG (normal, NORMAL))
log_memstats_stderr ("bgpd");
@@ -388,8 +378,8 @@ bgp_exit (int status)
*
* 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.
+ * 2. if it's not there, invoke the command but with a *negative* count 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
@@ -404,8 +394,8 @@ DEFUN_HID_CALL (threaded,
"threaded",
"Use pthreads\n")
{
- if (argv != NULL)
- config_threaded = 1 ; /* Explicit command => turn on threading */
+ if (argc == 0)
+ config_threaded = true ; /* Explicit command => turn on threading */
if (!done_2nd_stage_init)
init_second_stage(config_threaded) ;
@@ -421,7 +411,7 @@ DEFUN_HID_CALL (threaded,
* the message queues are available for the configuration data.
*/
static void
-init_second_stage(int pthreads)
+init_second_stage(bool pthreads)
{
assert(!done_2nd_stage_init) ;
@@ -431,13 +421,13 @@ init_second_stage(int pthreads)
bgp_peer_index_mutex_init();
/* Make nexus for main thread, always needed */
- cli_nexus = qpn_init_new(cli_nexus, 1); /* main thread */
+ cli_nexus = qpn_init_new(cli_nexus, true); /* main thread */
/* if using pthreads create additional nexus */
if (qpthreads_enabled)
{
- bgp_nexus = qpn_init_new(bgp_nexus, 0);
- routing_nexus = qpn_init_new(routing_nexus, 0);
+ bgp_nexus = qpn_init_new(bgp_nexus, false);
+ routing_nexus = qpn_init_new(routing_nexus, false);
}
else
{
@@ -495,9 +485,8 @@ main (int argc, char **argv)
/* Set umask before anything for security */
umask (0027);
-#ifdef QDEBUG
- fprintf(stderr, "%s\n", debug_banner);
-#endif
+ if (qdebug)
+ fprintf(stderr, "%s\n", debug_banner);
qlib_init_first_stage();
@@ -599,7 +588,11 @@ main (int argc, char **argv)
/* Initializations. */
srand (time (NULL));
signal_init (master, Q_SIGC(bgp_signals), bgp_signals);
- zprivs_init (&bgpd_privs);
+
+ cmd_getcwd() ; /* while have privilege */
+
+ zprivs_init (&bgpd_privs); /* lowers privileges */
+
cmd_init (1);
install_element (CONFIG_NODE, &threaded_cmd);
vty_init (master);
@@ -648,9 +641,9 @@ main (int argc, char **argv)
vty_start(vty_addr, vty_port, BGP_VTYSH_PATH);
/* Print banner. */
-#ifdef QDEBUG
- zlog_notice("%s", debug_banner);
-#endif
+ if (qdebug)
+ zlog_notice("%s", debug_banner);
+
zlog_notice ("BGPd %s%s starting: vty@%d, bgp@%s:%d",
QUAGGA_VERSION,
(qpthreads_enabled ? " pthreaded" : ""),
@@ -725,8 +718,6 @@ routing_background(void)
/*------------------------------------------------------------------------------
* SIGHUP: message sent to Routeing engine and the action it then takes.
- *
- * TODO: should SIGHUP be a priority message (!)
*/
static void
sighup_enqueue(void)
@@ -744,16 +735,17 @@ sighup_action(mqueue_block mqb, mqb_flag_t flag)
{
zlog_info ("bgpd restarting!");
- bgp_terminate (0, 0); /* send notifies */
+ bgp_terminate (false, false); /* send notifies */
bgp_reset ();
- /* Reload config file. */
- vty_read_config (config_file, config_default);
+ /* Reload config file -- no special first command, now */
+ vty_read_config_first_cmd_special(config_file, config_default,
+ NULL, config_ignore_warnings) ;
- /* Create VTY's socket */
+ /* Create VTY's socket */
vty_restart(vty_addr, vty_port, BGP_VTYSH_PATH);
- /* Try to return to normal operation. */
+ /* Try to return to normal operation. */
}
mqb_free(mqb);
@@ -807,7 +799,7 @@ sigterm_action(mqueue_block mqb, mqb_flag_t flag)
*/
program_terminating = true ;
- bgp_terminate(1, retain_mode);
+ bgp_terminate(true, retain_mode);
qpn_add_hook_function(&routing_nexus->foreground,
program_terminate_if_all_peers_deleted) ;
diff --git a/bgpd/bgp_msg_write.h b/bgpd/bgp_msg_write.h
index 77bfc1f2..8867e06f 100644
--- a/bgpd/bgp_msg_write.h
+++ b/bgpd/bgp_msg_write.h
@@ -24,8 +24,7 @@
#ifndef _QUAGGA_BGP_MSG_WRITE_H
#define _QUAGGA_BGP_MSG_WRITE_H
-#include <stdint.h>
-#include <stdbool.h>
+#include "misc.h"
#include "bgpd/bgp_common.h"
#include "bgpd/bgp_connection.h"
diff --git a/bgpd/bgp_notification.c b/bgpd/bgp_notification.c
index 7dd68c63..d70491c9 100644
--- a/bgpd/bgp_notification.c
+++ b/bgpd/bgp_notification.c
@@ -20,8 +20,8 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include <string.h>
#include <netinet/in.h>
#include "lib/zassert.h"
diff --git a/bgpd/bgp_notification.h b/bgpd/bgp_notification.h
index 20e96063..755f431e 100644
--- a/bgpd/bgp_notification.h
+++ b/bgpd/bgp_notification.h
@@ -24,13 +24,9 @@
#ifndef _QUAGGA_BGP_NOTIFY_H
#define _QUAGGA_BGP_NOTIFY_H
-#include <stddef.h>
+#include "lib/misc.h"
#include "bgpd/bgp_common.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* BGP NOTIFICATION message codes.
*/
diff --git a/bgpd/bgp_open_state.c b/bgpd/bgp_open_state.c
index 9ca9617e..e1dbaeaf 100644
--- a/bgpd/bgp_open_state.c
+++ b/bgpd/bgp_open_state.c
@@ -48,7 +48,7 @@ bgp_open_state_init_new(bgp_open_state state)
else
memset(state, 0, sizeof(struct bgp_open_state)) ;
- vector_init_new(&state->unknowns, 0) ;
+ vector_init_new(state->unknowns, 0) ;
return state ;
}
@@ -66,10 +66,10 @@ bgp_open_state_free(bgp_open_state state)
if (state != NULL)
{
- while ((unknown = vector_ream_keep(&state->unknowns)) != NULL)
+ while ((unknown = vector_ream(state->unknowns, keep_it)) != NULL)
XFREE(MTYPE_TMP, unknown) ;
- while ((afi_safi = vector_ream_keep(&state->afi_safi)) != NULL)
+ while ((afi_safi = vector_ream(state->afi_safi, keep_it)) != NULL)
XFREE(MTYPE_TMP, afi_safi) ;
XFREE(MTYPE_BGP_OPEN_STATE, state) ;
@@ -222,7 +222,7 @@ bgp_open_state_unknown_add(bgp_open_state state, uint8_t code,
if (length != 0)
memcpy(unknown->value, value, length) ;
- vector_push_item(&state->unknowns, unknown) ;
+ vector_push_item(state->unknowns, unknown) ;
} ;
/*------------------------------------------------------------------------------
@@ -231,7 +231,7 @@ bgp_open_state_unknown_add(bgp_open_state state, uint8_t code,
extern int
bgp_open_state_unknown_count(bgp_open_state state)
{
- return vector_end(&state->unknowns) ;
+ return vector_end(state->unknowns) ;
} ;
/*------------------------------------------------------------------------------
@@ -240,7 +240,7 @@ bgp_open_state_unknown_count(bgp_open_state state)
extern bgp_cap_unknown
bgp_open_state_unknown_cap(bgp_open_state state, unsigned index)
{
- return vector_get_item(&state->unknowns, index) ;
+ return vector_get_item(state->unknowns, index) ;
} ;
/*==============================================================================
@@ -264,7 +264,7 @@ bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi,
afi_safi->safi = safi ;
afi_safi->cap_code = cap_code ;
- vector_push_item(&state->afi_safi, afi_safi) ;
+ vector_push_item(state->afi_safi, afi_safi) ;
return afi_safi ;
} ;
@@ -275,7 +275,7 @@ bgp_open_state_afi_safi_add(bgp_open_state state, iAFI_t afi, iSAFI_t safi,
extern int
bgp_open_state_afi_safi_count(bgp_open_state state)
{
- return vector_end(&state->afi_safi) ;
+ return vector_end(state->afi_safi) ;
} ;
/*------------------------------------------------------------------------------
@@ -284,7 +284,7 @@ bgp_open_state_afi_safi_count(bgp_open_state state)
extern bgp_cap_afi_safi
bgp_open_state_afi_safi_cap(bgp_open_state state, unsigned index)
{
- return vector_get_item(&state->afi_safi, index) ;
+ return vector_get_item(state->afi_safi, index) ;
} ;
/*==============================================================================
diff --git a/bgpd/bgp_open_state.h b/bgpd/bgp_open_state.h
index 8c30712b..e88deda0 100644
--- a/bgpd/bgp_open_state.h
+++ b/bgpd/bgp_open_state.h
@@ -22,16 +22,12 @@
#ifndef _QUAGGA_BGP_OPEN_STATE_H
#define _QUAGGA_BGP_OPEN_STATE_H
-#include <stdint.h>
+#include "misc.h"
#include "bgpd/bgp.h"
#include "bgpd/bgp_common.h"
#include "lib/vector.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* BGP Open State.
*
@@ -114,8 +110,8 @@ struct bgp_open_state
bool has_restarted ; /* Restart State flag */
unsigned restart_time ; /* Restart Time in seconds */
- struct vector unknowns ; /* list of bgp_cap_unknown */
- struct vector afi_safi ; /* various afi/safi capabilities */
+ vector_t unknowns ; /* list of bgp_cap_unknown */
+ vector_t afi_safi ; /* various afi/safi capabilities */
} ;
/*==============================================================================
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index b21cec4e..3052eeba 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -780,7 +780,7 @@ bgp_route_refresh_send (struct peer *peer, afi_t afi, safi_t safi,
bgp_orf_entry orfpe = NULL;
struct prefix_list *plist = NULL;
struct orf_prefix orfp;
- vector_index i;
+ vector_index_t i;
int orf_refresh = 0;
enum prefix_list_type pe_type;
@@ -1838,7 +1838,7 @@ bgp_route_refresh_recv(bgp_peer peer, bgp_route_refresh rr)
{
afi_t afi;
safi_t safi;
- vector_index i;
+ vector_index_t i, e;
char name[BUFSIZ];
int ret;
@@ -1853,11 +1853,11 @@ bgp_route_refresh_recv(bgp_peer peer, bgp_route_refresh rr)
ret = snprintf (name, BUFSIZ, "%s.%d.%d", peer->host, afi, safi);
assert(ret < BUFSIZ);
- if (rr->entries.end > 0)
+ if ((e = bgp_orf_get_count(rr)) > 0)
{
- for (i = 0; i < rr->entries.end; ++i)
+ for (i = 0; i < e; ++i)
{
- bgp_orf_entry orfep = vector_slot(&rr->entries, i);
+ bgp_orf_entry orfep = vector_slot(rr->entries, i);
/* ignore unknown */
if (orfep->unknown)
diff --git a/bgpd/bgp_peer.c b/bgpd/bgp_peer.c
index 45d4ca0e..196f0d54 100644
--- a/bgpd/bgp_peer.c
+++ b/bgpd/bgp_peer.c
@@ -464,7 +464,7 @@ bgp_session_has_disabled(bgp_session session)
session->state = bgp_session_sDisabled ;
/* Immediately discard any other messages for this session. */
- mqueue_revoke(routing_nexus->queue, session) ;
+ mqueue_revoke(routing_nexus->queue, session, 0) ;
/* If the session is marked "delete_me", do that.
*
diff --git a/bgpd/bgp_peer_index.c b/bgpd/bgp_peer_index.c
index 518a22bc..d3c90598 100644
--- a/bgpd/bgp_peer_index.c
+++ b/bgpd/bgp_peer_index.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include "lib/zassert.h"
@@ -59,7 +60,7 @@
*/
static struct symbol_table bgp_peer_index ; /* lookup by 'name' */
-static struct vector bgp_peer_id_index ; /* lookup by peer-id */
+static vector_t bgp_peer_id_index ; /* lookup by peer-id */
static qpt_mutex bgp_peer_index_mutex = NULL ;
@@ -112,7 +113,7 @@ bgp_peer_index_init(void* parent)
sockunion_symbol_hash, /* "name" is an IP Address */
NULL) ; /* no value change call-back */
- vector_init_new(&bgp_peer_id_index, bgp_peer_id_unit) ;
+ vector_init_new(bgp_peer_id_index, bgp_peer_id_unit) ;
/* Initialise table entirely empty */
bgp_peer_id_table = NULL ;
@@ -149,11 +150,11 @@ bgp_peer_index_reset(void)
bgp_peer_id_table_chunk chunk ;
/* Ream out the peer id vector -- checking that all entries are empty */
- while ((entry = vector_ream_keep(&bgp_peer_id_index)) != NULL)
+ while ((entry = vector_ream(bgp_peer_id_index, keep_it)) != NULL)
passert((entry->peer == NULL) && (entry->next_free != entry)) ;
/* Discard body of symbol table -- must be empty ! */
- symbol_table_reset_keep(&bgp_peer_index) ;
+ symbol_table_reset(&bgp_peer_index, keep_it) ;
/* Discard the empty chunks of entries */
while (bgp_peer_id_table != NULL)
@@ -204,7 +205,7 @@ bgp_peer_index_register(bgp_peer peer, union sockunion* su)
entry = bgp_peer_id_free_head ;
bgp_peer_id_free_head = entry->next_free ;
- assert(vector_get_item(&bgp_peer_id_index, entry->id) == entry) ;
+ assert(vector_get_item(bgp_peer_id_index, entry->id) == entry) ;
/* Initialise the entry -- the id is already set */
entry->peer = peer ;
@@ -213,7 +214,7 @@ bgp_peer_index_register(bgp_peer peer, union sockunion* su)
peer->index_entry = entry;
/* Insert the new entry into the symbol table. */
- entry = symbol_set_value(symbol_find(&bgp_peer_index, su), entry) ;
+ entry = symbol_set_value(symbol_lookup(&bgp_peer_index, su, add), entry) ;
BGP_PEER_INDEX_UNLOCK() ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
@@ -241,7 +242,7 @@ bgp_peer_index_deregister(bgp_peer peer, union sockunion* su)
BGP_PEER_INDEX_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
- sym = symbol_seek(&bgp_peer_index, su) ;
+ sym = symbol_lookup(&bgp_peer_index, su, no_add) ;
passert(sym != NULL) ;
entry = symbol_delete(sym) ;
@@ -288,7 +289,7 @@ bgp_peer_index_seek_entry(union sockunion* su)
/* Only the Routing Engine can add/delete entries -- so no lock required */
- entry = symbol_get_value(symbol_seek(&bgp_peer_index, su)) ;
+ entry = symbol_get_value(symbol_lookup(&bgp_peer_index, su, no_add)) ;
if (entry != NULL)
assert((entry->peer != NULL) && (entry->next_free = entry)) ;
@@ -340,7 +341,7 @@ bgp_peer_index_seek_accept(union sockunion* su, bool* p_found)
BGP_PEER_INDEX_LOCK() ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
- entry = symbol_get_value(symbol_seek(&bgp_peer_index, su)) ;
+ entry = symbol_get_value(symbol_lookup(&bgp_peer_index, su, no_add)) ;
if (entry != NULL)
{
@@ -375,7 +376,7 @@ static void
bgp_peer_id_table_free_entry(bgp_peer_index_entry entry)
{
assert((entry != NULL) && (entry->id < bgp_peer_id_count)) ;
- assert(vector_get_item(&bgp_peer_id_index, entry->id) == entry) ;
+ assert(vector_get_item(bgp_peer_id_index, entry->id) == entry) ;
if (bgp_peer_id_free_head == NULL)
bgp_peer_id_free_head = entry ;
@@ -424,7 +425,7 @@ bgp_peer_id_table_make_ids(void)
while (id_new < bgp_peer_id_count)
{
- vector_set_item(&bgp_peer_id_index, id_new, entry) ;
+ vector_set_item(bgp_peer_id_index, id_new, entry) ;
entry->id = id_new ;
bgp_peer_id_table_free_entry(entry) ;
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index bcf23db5..34d580b8 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -11564,7 +11564,7 @@ DEFUN (show_ip_bgp_view_rsclient_prefix,
if (argc == 3)
peer = peer_lookup_in_view (vty, argv[0], argv[1]);
else
- peer = peer_lookup_in_view (vty, NULL, argv[0]);
+ peer = peer_lookup_in_view (vty, NULL, argv[0]);
if (! peer)
return CMD_WARNING;
@@ -11653,11 +11653,11 @@ DEFUN (show_bgp_view_ipv4_safi_rsclient_prefix,
vty_out (vty, "%% Activate the neighbor for the address family first%s",
VTY_NEWLINE);
return CMD_WARNING;
-}
+ }
if ( ! CHECK_FLAG (peer->af_flags[AFI_IP][safi],
PEER_FLAG_RSERVER_CLIENT))
-{
+ {
vty_out (vty, "%% Neighbor is not a Route-Server client%s",
VTY_NEWLINE);
return CMD_WARNING;
diff --git a/bgpd/bgp_route_refresh.c b/bgpd/bgp_route_refresh.c
index 5c2b6e5c..252183e4 100644
--- a/bgpd/bgp_route_refresh.c
+++ b/bgpd/bgp_route_refresh.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include <string.h>
@@ -60,7 +61,7 @@ bgp_route_refresh_new(iAFI_t afi, iSAFI_t safi, unsigned count)
rr->afi = afi ;
rr->safi = safi ;
- vector_init_new(&rr->entries, count) ;
+ vector_init_new(rr->entries, count) ;
/* rest of bgp_route_refresh zeroised -- not relevant when vector empty */
@@ -74,7 +75,7 @@ extern void
bgp_route_refresh_free(bgp_route_refresh rr)
{
bgp_orf_entry entry ;
- while((entry = vector_ream_keep(&rr->entries)) != NULL)
+ while((entry = vector_ream(rr->entries, keep_it)) != NULL)
XFREE(MTYPE_BGP_ORF_ENTRY, entry) ;
XFREE(MTYPE_BGP_ROUTE_REFRESH, rr) ;
@@ -130,7 +131,7 @@ bgp_orf_entry_new(bgp_route_refresh rr, uint8_t orf_type, bgp_form_t form,
orfe->form = form ;
orfe->unknown = (unknown_size != 0) ;
- vector_push_item(&rr->entries, orfe) ;
+ vector_push_item(rr->entries, orfe) ;
return orfe ;
} ;
diff --git a/bgpd/bgp_route_refresh.h b/bgpd/bgp_route_refresh.h
index b44ca9e0..78324ff4 100644
--- a/bgpd/bgp_route_refresh.h
+++ b/bgpd/bgp_route_refresh.h
@@ -22,16 +22,12 @@
#ifndef _QUAGGA_BGP_ROUTE_REFRESH_H
#define _QUAGGA_BGP_ROUTE_REFRESH_H
-#include <stddef.h>
+#include "lib/misc.h"
#include "bgpd/bgp_common.h"
#include "lib/prefix.h"
#include "lib/plist.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Structures to hold ROUTE-REFRESH and ORF
*/
@@ -81,7 +77,7 @@ struct bgp_route_refresh
iAFI_t afi ; /* NB: Internet AFI/SAFI */
iSAFI_t safi ;
- struct vector entries ; /* empty => simple ROUTE-REFRESH */
+ vector_t entries ; /* empty => simple ROUTE-REFRESH */
bool defer ; /* otherwise: immediate */
@@ -120,13 +116,13 @@ bgp_orf_add_unknown(bgp_route_refresh rr, uint8_t orf_type, bgp_size_t length,
Inline unsigned
bgp_orf_get_count(bgp_route_refresh rr)
{
- return vector_end(&rr->entries) ;
+ return vector_end(rr->entries) ;
} ;
Inline bgp_orf_entry
bgp_orf_get_entry(bgp_route_refresh rr, unsigned index)
{
- return vector_get_item(&rr->entries, index) ;
+ return vector_get_item(rr->entries, index) ;
} ;
#endif /* _QUAGGA_BGP_ROUTE_REFRESH_H */
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index 33e2c9cd..f9bf2844 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -1461,11 +1461,11 @@ route_set_community_delete (void *rule, struct prefix *prefix,
community_free (merge);
/* HACK: if the old community is not intern'd,
- * we should free it here, or all reference to it may be lost.
- * Really need to cleanup attribute caching sometime.
+ * we should free it here, or all references to it may be lost.
+ * Really need to clean up attribute caching sometime.
*/
if (old->refcnt == 0)
- community_free (old);
+ community_free(old) ;
if (new->size == 0)
{
@@ -1605,9 +1605,9 @@ route_set_ecommunity_soo (void *rule, struct prefix *prefix,
if (! ecom)
return RMAP_OKAY;
-
+
old_ecom = (bgp_attr_extra_get (bgp_info->attr))->ecommunity;
-
+
if (old_ecom)
new_ecom = ecommunity_merge (ecommunity_dup (old_ecom), ecom);
else
@@ -1632,7 +1632,7 @@ route_set_ecommunity_soo_compile (const char *arg)
ecom = ecommunity_str2com (arg, ECOMMUNITY_SITE_ORIGIN, 0);
if (! ecom)
return NULL;
-
+
return ecommunity_intern (ecom);
}
@@ -2991,7 +2991,7 @@ DEFUN (set_metric,
ALIAS (set_metric,
set_metric_addsub_cmd,
- "set metric <+/-metric>",
+ "set metric <-2147483647-+2147483647>",
SET_STR
"Metric value for destination routing protocol\n"
"Add or subtract metric\n")
@@ -3081,7 +3081,7 @@ ALIAS (no_set_weight,
DEFUN (set_aspath_prepend,
set_aspath_prepend_cmd,
- "set as-path prepend ." CMD_AS_RANGE,
+ "set as-path prepend .ASNs",
SET_STR
"Transform BGP AS_PATH attribute\n"
"Prepend to the as-path\n"
@@ -3119,7 +3119,7 @@ DEFUN (no_set_aspath_prepend,
ALIAS (no_set_aspath_prepend,
no_set_aspath_prepend_val_cmd,
- "no set as-path prepend ." CMD_AS_RANGE,
+ "no set as-path prepend .ASns",
NO_STR
SET_STR
"Transform BGP AS_PATH attribute\n"
@@ -3128,7 +3128,7 @@ ALIAS (no_set_aspath_prepend,
DEFUN (set_aspath_exclude,
set_aspath_exclude_cmd,
- "set as-path exclude ." CMD_AS_RANGE,
+ "set as-path exclude .ASNs",
SET_STR
"Transform BGP AS-path attribute\n"
"Exclude from the as-path\n"
@@ -3165,7 +3165,7 @@ DEFUN (no_set_aspath_exclude,
ALIAS (no_set_aspath_exclude,
no_set_aspath_exclude_val_cmd,
- "no set as-path exclude ." CMD_AS_RANGE,
+ "no set as-path exclude .ASNs",
NO_STR
SET_STR
"Transform BGP AS_PATH attribute\n"
@@ -3177,7 +3177,8 @@ DEFUN (set_community,
"set community .AA:NN",
SET_STR
"BGP community attribute\n"
- "Community number in aa:nn format or local-AS|no-advertise|no-export|internet or additive\n")
+ "Community number in aa:nn format or """
+ "local-AS|no-advertise|no-export|internet or additive\n")
{
int i;
int first = 0;
diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c
index 99077907..7e04e216 100644
--- a/bgpd/bgp_session.c
+++ b/bgpd/bgp_session.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include "bgpd/bgp_session.h"
#include "bgpd/bgp_common.h"
@@ -124,7 +125,7 @@ bgp_session_init_new(bgp_peer peer)
session = XCALLOC(MTYPE_BGP_SESSION, sizeof(struct bgp_session)) ;
- qpt_mutex_init_new(&session->mutex, qpt_mutex_recursive) ;
+ qpt_mutex_init_new(session->mutex, qpt_mutex_recursive) ;
session->peer = peer ;
bgp_peer_lock(peer) ; /* Account for the session->peer pointer */
@@ -240,7 +241,7 @@ bgp_session_delete(bgp_peer peer)
BGP_SESSION_UNLOCK(session) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
- qpt_mutex_destroy(&session->mutex, 0) ;
+ qpt_mutex_destroy(session->mutex, 0) ;
/* Proceed to dismantle the session. */
@@ -462,7 +463,7 @@ bgp_session_disable(bgp_peer peer, bgp_notify notification)
/* Can revoke whatever may be queued already. Will revoke again when the
* disable is acknowledged to finally clear the session out of the queue.
*/
- mqueue_revoke(routing_nexus->queue, session) ;
+ mqueue_revoke(routing_nexus->queue, session, 0) ;
/* Now change to limping state */
session->state = bgp_session_sLimping;
@@ -518,7 +519,7 @@ bgp_session_do_disable(mqueue_block mqb, mqb_flag_t flag)
if (flag == mqb_action)
{
/* Immediately discard any other messages for this session. */
- mqueue_revoke(bgp_nexus->queue, session) ;
+ mqueue_revoke(bgp_nexus->queue, session, 0) ;
/* 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 24bb5cd6..f8cfa547 100644
--- a/bgpd/bgp_session.h
+++ b/bgpd/bgp_session.h
@@ -22,8 +22,8 @@
#ifndef _QUAGGA_BGP_SESSION_H
#define _QUAGGA_BGP_SESSION_H
-#include <stdbool.h>
#include <zebra.h>
+#include "lib/misc.h"
#include "bgpd/bgp_common.h"
#include "bgpd/bgp_engine.h"
@@ -37,10 +37,6 @@
#include "lib/sockunion.h"
#include "lib/mqueue.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* BGP Session data structure.
*
@@ -310,12 +306,12 @@ MQB_ARGS_SIZE_OK(bgp_session_ttl_args) ;
inline static void BGP_SESSION_LOCK(bgp_session session)
{
- qpt_mutex_lock(&session->mutex) ;
+ qpt_mutex_lock(session->mutex) ;
} ;
inline static void BGP_SESSION_UNLOCK(bgp_session session)
{
- qpt_mutex_unlock(&session->mutex) ;
+ qpt_mutex_unlock(session->mutex) ;
} ;
/*==============================================================================
diff --git a/bgpd/bgp_snmp.c b/bgpd/bgp_snmp.c
index eaa41e6c..3f56fb25 100644
--- a/bgpd/bgp_snmp.c
+++ b/bgpd/bgp_snmp.c
@@ -44,13 +44,13 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include "bgpd/bgp_route.h"
#include "bgpd/bgp_fsm.h"
#include "bgpd/bgp_snmp.h"
-
+
/* BGP4-MIB described in RFC1657. */
#define BGP4MIB 1,3,6,1,2,1,15
/* BGP TRAP. */
#define BGPESTABLISHED 1
-#define BGPBACKWARDTRANSITION 2
+#define BGPBACKWARDTRANSITION 2
/* BGP MIB bgpVersion. */
#define BGPVERSION 0
@@ -118,7 +118,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#define OCTET_STRING ASN_OCTET_STR
#define IPADDRESS ASN_IPADDRESS
#define GAUGE32 ASN_UNSIGNED
-
+
/* Declare static local variables for convenience. */
SNMP_LOCAL_VARIABLES
@@ -143,7 +143,7 @@ static u_char *bgp4PathAttrTable (struct variable *, oid [], size_t *,
int, size_t *, WriteMethod **);
/* static u_char *bgpTraps (); */
-struct variable bgp_variables[] =
+struct variable bgp_variables[] =
{
/* BGP version. */
{BGPVERSION, OCTET_STRING, RONLY, bgpVersion,
@@ -247,7 +247,7 @@ struct variable bgp_variables[] =
3, {6, 1, 14}},
};
-
+
static u_char *
bgpVersion (struct variable *v, oid name[], size_t *length, int exact,
size_t *var_len, WriteMethod **write_method)
@@ -343,7 +343,7 @@ bgp_peer_lookup_next (struct in_addr *src)
}
static struct peer *
-bgpPeerTable_lookup (struct variable *v, oid name[], size_t *length,
+bgpPeerTable_lookup (struct variable *v, oid name[], size_t *length,
struct in_addr *addr, int exact)
{
struct peer *peer = NULL;
@@ -364,9 +364,9 @@ bgpPeerTable_lookup (struct variable *v, oid name[], size_t *length,
{
len = *length - v->namelen;
if (len > 4) len = 4;
-
+
oid2in_addr (name + v->namelen, len, addr);
-
+
peer = bgp_peer_lookup_next (addr);
if (peer == NULL)
@@ -391,12 +391,12 @@ write_bgpPeerTable (int action, u_char *var_val,
struct peer *peer;
long intval;
size_t bigsize = SNMP_MAX_LEN;
-
- if (var_val_type != ASN_INTEGER)
+
+ if (var_val_type != ASN_INTEGER)
{
return SNMP_ERR_WRONGTYPE;
}
- if (var_val_len != sizeof (long))
+ if (var_val_len != sizeof (long))
{
return SNMP_ERR_WRONGLENGTH;
}
@@ -597,7 +597,7 @@ bgpPeerTable (struct variable *v, oid name[], size_t *length,
default:
return NULL;
break;
- }
+ }
return NULL;
}
@@ -664,7 +664,7 @@ bgp4PathAttrLookup (struct variable *v, oid name[], size_t *length,
oid2in_addr (offset, IN_ADDR_SIZE, &su.sin.sin_addr);
/* Lookup node. */
- rn = bgp_node_lookup (bgp->rib[AFI_IP][SAFI_UNICAST],
+ rn = bgp_node_lookup (bgp->rib[AFI_IP][SAFI_UNICAST],
(struct prefix *) addr);
if (rn)
{
@@ -687,7 +687,7 @@ bgp4PathAttrLookup (struct variable *v, oid name[], size_t *length,
{
if (len > IN_ADDR_SIZE)
len = IN_ADDR_SIZE;
-
+
oid2in_addr (offset, len, &addr->prefix);
offset += IN_ADDR_SIZE;
@@ -726,12 +726,12 @@ bgp4PathAttrLookup (struct variable *v, oid name[], size_t *length,
for (binfo = rn->info; binfo; binfo = binfo->info_next)
{
if (binfo->peer->su.sin.sin_family == AF_INET
- && ntohl (paddr.s_addr)
+ && ntohl (paddr.s_addr)
< ntohl (binfo->peer->su.sin.sin_addr.s_addr))
{
if (min)
{
- if (ntohl (binfo->peer->su.sin.sin_addr.s_addr)
+ if (ntohl (binfo->peer->su.sin.sin_addr.s_addr)
< ntohl (min->peer->su.sin.sin_addr.s_addr))
min = binfo;
}
@@ -749,7 +749,7 @@ bgp4PathAttrLookup (struct variable *v, oid name[], size_t *length,
offset += IN_ADDR_SIZE;
*offset = rn->p.prefixlen;
offset++;
- oid_copy_addr (offset, &min->peer->su.sin.sin_addr,
+ oid_copy_addr (offset, &min->peer->su.sin.sin_addr,
IN_ADDR_SIZE);
addr->prefix = rn->p.u.prefix4;
addr->prefixlen = rn->p.prefixlen;
@@ -773,7 +773,7 @@ bgp4PathAttrTable (struct variable *v, oid name[], size_t *length,
struct bgp *bgp;
struct bgp_info *binfo;
struct prefix_ipv4 addr;
-
+
bgp = bgp_get_default ();
if (! bgp)
return NULL;
@@ -843,7 +843,7 @@ bgp4PathAttrTable (struct variable *v, oid name[], size_t *length,
}
return NULL;
}
-
+
/* BGP Traps. */
struct trap_object bgpTrapList[] =
{
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index 48d8ecbe..40efd6b8 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -57,9 +57,7 @@ extern struct in_addr router_id_zebra;
afi_t
bgp_node_afi (struct vty *vty)
{
- enum node_type node = vty_get_node(vty) ;
-
- if (node == BGP_IPV6_NODE || node == BGP_IPV6M_NODE)
+ if (vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE)
return AFI_IP6;
return AFI_IP;
}
@@ -69,11 +67,9 @@ bgp_node_afi (struct vty *vty)
safi_t
bgp_node_safi (struct vty *vty)
{
- enum node_type node = vty_get_node(vty) ;
-
- if (node == BGP_VPNV4_NODE)
+ if (vty->node == BGP_VPNV4_NODE)
return SAFI_MPLS_VPN;
- if (node == BGP_IPV4M_NODE || node == BGP_IPV6M_NODE)
+ if (vty->node == BGP_IPV4M_NODE || vty->node == BGP_IPV6M_NODE)
return SAFI_MULTICAST;
return SAFI_UNICAST;
}
@@ -323,12 +319,13 @@ DEFUN_DEPRECATED (neighbor_version,
}
/* "router bgp" commands. */
-DEFUN (router_bgp,
- router_bgp_cmd,
- "router bgp " CMD_AS_RANGE,
- ROUTER_STR
- BGP_STR
- AS_STR)
+DEFUN_ATTR (router_bgp,
+ router_bgp_cmd,
+ "router bgp " CMD_AS_RANGE,
+ ROUTER_STR
+ BGP_STR
+ AS_STR,
+ CMD_ATTR_NODE + BGP_NODE)
{
int ret;
as_t as;
@@ -347,9 +344,11 @@ DEFUN (router_bgp,
vty_out (vty, "Please specify 'bgp multiple-instance' first%s",
VTY_NEWLINE);
return CMD_WARNING;
+
case BGP_ERR_AS_MISMATCH:
vty_out (vty, "BGP is already running; AS is %u%s", as, VTY_NEWLINE);
return CMD_WARNING;
+
case BGP_ERR_INSTANCE_MISMATCH:
vty_out (vty, "BGP view name and AS number mismatch%s", VTY_NEWLINE);
vty_out (vty, "BGP instance is already running; AS is %u%s",
@@ -357,20 +356,21 @@ DEFUN (router_bgp,
return CMD_WARNING;
}
- vty_set_node(vty, BGP_NODE) ;
+ vty->node = BGP_NODE ;
vty->index = bgp;
return CMD_SUCCESS;
}
-ALIAS (router_bgp,
- router_bgp_view_cmd,
- "router bgp " CMD_AS_RANGE " view WORD",
- ROUTER_STR
- BGP_STR
- AS_STR
- "BGP view\n"
- "view name\n")
+ALIAS_ATTR (router_bgp,
+ router_bgp_view_cmd,
+ "router bgp " CMD_AS_RANGE " view WORD",
+ ROUTER_STR
+ BGP_STR
+ AS_STR
+ "BGP view\n"
+ "view name\n",
+ CMD_ATTR_NODE + BGP_NODE)
/* "no router bgp" commands. */
DEFUN (no_router_bgp,
@@ -606,7 +606,7 @@ ALIAS (no_bgp_confederation_identifier,
DEFUN (bgp_confederation_peers,
bgp_confederation_peers_cmd,
- "bgp confederation peers ." CMD_AS_RANGE,
+ "bgp confederation peers .ASs",
"BGP specific commands\n"
"AS confederation parameters\n"
"Peer ASs in BGP confederation\n"
@@ -636,7 +636,7 @@ DEFUN (bgp_confederation_peers,
DEFUN (no_bgp_confederation_peers,
no_bgp_confederation_peers_cmd,
- "no bgp confederation peers ." CMD_AS_RANGE,
+ "no bgp confederation peers .ASs",
NO_STR
"BGP specific commands\n"
"AS confederation parameters\n"
@@ -4050,90 +4050,118 @@ DEFUN (no_neighbor_ttl_security,
}
/* Address family configuration. */
-DEFUN (address_family_ipv4,
- address_family_ipv4_cmd,
- "address-family ipv4",
- "Enter Address Family command mode\n"
- "Address family\n")
-{
- vty_set_node(vty, BGP_IPV4_NODE) ;
+DEFUN_ATTR (address_family_ipv4,
+ address_family_ipv4_cmd,
+ "address-family ipv4",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_IPV4_NODE)
+{
+ vty->node = BGP_IPV4_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv4_safi,
- address_family_ipv4_safi_cmd,
- "address-family ipv4 (unicast|multicast)",
- "Enter Address Family command mode\n"
- "Address family\n"
- "Address Family modifier\n"
- "Address Family modifier\n")
+DEFUN_ATTR (address_family_ipv4_safi_unicast,
+ address_family_ipv4_safi_unicast_cmd,
+ "address-family ipv4 unicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV4_NODE)
{
- if (strncmp (argv[0], "m", 1) == 0)
- vty_set_node(vty, BGP_IPV4M_NODE) ;
- else
- vty_set_node(vty, BGP_IPV4_NODE) ;
+ vty->node = BGP_IPV4_NODE ;
+ return CMD_SUCCESS;
+}
+DEFUN_ATTR (address_family_ipv4_safi_multicast,
+ address_family_ipv4_safi_multicast_cmd,
+ "address-family ipv4 multicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV4M_NODE)
+{
+ vty->node = BGP_IPV4M_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv6,
- address_family_ipv6_cmd,
- "address-family ipv6",
- "Enter Address Family command mode\n"
- "Address family\n")
+DEFUN_ATTR (address_family_ipv6,
+ address_family_ipv6_cmd,
+ "address-family ipv6",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_IPV6_NODE)
{
- vty_set_node(vty, BGP_IPV6_NODE) ;
+ vty->node = BGP_IPV6_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv6_safi,
- address_family_ipv6_safi_cmd,
- "address-family ipv6 (unicast|multicast)",
- "Enter Address Family command mode\n"
- "Address family\n"
- "Address Family modifier\n"
- "Address Family modifier\n")
+DEFUN_ATTR (address_family_ipv6_safi_unicast,
+ address_family_ipv6_safi_unicast_cmd,
+ "address-family ipv6 unicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV6_NODE)
{
- if (strncmp (argv[0], "m", 1) == 0)
- vty_set_node(vty, BGP_IPV6M_NODE) ;
- else
- vty_set_node(vty, BGP_IPV6_NODE) ;
+ vty->node = BGP_IPV6_NODE ;
+ return CMD_SUCCESS;
+}
+DEFUN_ATTR (address_family_ipv6_safi_multicast,
+ address_family_ipv6_safi_multicast_cmd,
+ "address-family ipv6 multicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV6M_NODE)
+{
+ vty->node = BGP_IPV6M_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_vpnv4,
- address_family_vpnv4_cmd,
- "address-family vpnv4",
- "Enter Address Family command mode\n"
- "Address family\n")
+DEFUN_ATTR (address_family_vpnv4,
+ address_family_vpnv4_cmd,
+ "address-family vpnv4",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_VPNV4_NODE)
{
- vty_set_node(vty, BGP_VPNV4_NODE) ;
+ vty->node = BGP_VPNV4_NODE ;
return CMD_SUCCESS;
}
-ALIAS (address_family_vpnv4,
+ALIAS_ATTR (address_family_vpnv4,
address_family_vpnv4_unicast_cmd,
"address-family vpnv4 unicast",
"Enter Address Family command mode\n"
"Address family\n"
- "Address Family Modifier\n")
+ "Address Family Modifier\n",
+ CMD_ATTR_NODE + BGP_VPNV4_NODE)
-DEFUN (exit_address_family,
- exit_address_family_cmd,
- "exit-address-family",
- "Exit from Address Family configuration mode\n")
+DEFUN_ATTR (exit_address_family,
+ exit_address_family_cmd,
+ "exit-address-family",
+ "Exit from Address Family configuration mode\n",
+ CMD_ATTR_NODE + BGP_NODE)
{
- enum node_type node = vty_get_node(vty) ;
+ node_type_t node = vty->node ;
- if (node == BGP_IPV4_NODE
+ if ( node == BGP_IPV4_NODE
|| node == BGP_IPV4M_NODE
|| node == BGP_VPNV4_NODE
|| node == BGP_IPV6_NODE
|| node == BGP_IPV6M_NODE)
- vty_set_node(vty, BGP_NODE);
- return CMD_SUCCESS;
-}
+ {
+ vty->node = BGP_NODE ;
+ return CMD_SUCCESS ;
+ }
+ else
+ {
+ vty_out(vty, "%% No address family to leave\n") ;
+ return CMD_WARNING ;
+ } ;
+} ;
/* BGP clear sort. */
typedef enum
@@ -6651,52 +6679,55 @@ DEFUN (show_bgp_memory,
BGP_STR
"Global BGP memory statistics\n")
{
+ mem_stats_t mst[1] ;
char memstrbuf[MTYPE_MEMSTR_LEN];
unsigned long count;
+ mem_get_stats(mst) ;
+
/* RIB related usage stats */
- count = mtype_stats_alloc (MTYPE_BGP_NODE);
+ count = mem_get_alloc(mst, MTYPE_BGP_NODE);
vty_out (vty, "%ld RIB nodes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_node)),
VTY_NEWLINE);
- count = mtype_stats_alloc (MTYPE_BGP_ROUTE);
+ count = mem_get_alloc(mst, MTYPE_BGP_ROUTE);
vty_out (vty, "%ld BGP routes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_info)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_ROUTE_EXTRA)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ROUTE_EXTRA)))
vty_out (vty, "%ld BGP route ancillaries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_info_extra)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_STATIC)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_STATIC)))
vty_out (vty, "%ld Static routes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_static)),
VTY_NEWLINE);
/* Adj-In/Out */
- if ((count = mtype_stats_alloc (MTYPE_BGP_ADJ_IN)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ADJ_IN)))
vty_out (vty, "%ld Adj-In entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_adj_in)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_ADJ_OUT)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ADJ_OUT)))
vty_out (vty, "%ld Adj-Out entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_adj_out)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_NEXTHOP_CACHE)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_NEXTHOP_CACHE)))
vty_out (vty, "%ld Nexthop cache entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_nexthop_cache)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_DAMP_INFO)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_DAMP_INFO)))
vty_out (vty, "%ld Dampening entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_damp_info)),
@@ -6708,7 +6739,7 @@ DEFUN (show_bgp_memory,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof(struct attr)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_ATTR_EXTRA)))
+ if ((count = mem_get_alloc(mst, MTYPE_ATTR_EXTRA)))
vty_out (vty, "%ld BGP extra attributes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof(struct attr_extra)),
@@ -6724,7 +6755,7 @@ DEFUN (show_bgp_memory,
count * sizeof (struct aspath)),
VTY_NEWLINE);
- count = mtype_stats_alloc (MTYPE_AS_SEG);
+ count = mem_get_alloc(mst, MTYPE_AS_SEG);
vty_out (vty, "%ld BGP AS-PATH segments, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct assegment)),
@@ -6736,43 +6767,43 @@ DEFUN (show_bgp_memory,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct community)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_ECOMMUNITY)))
+ if ((count = mem_get_alloc(mst, MTYPE_ECOMMUNITY)))
vty_out (vty, "%ld BGP community entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct ecommunity)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_CLUSTER)))
+ if ((count = mem_get_alloc(mst, MTYPE_CLUSTER)))
vty_out (vty, "%ld Cluster lists, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct cluster_list)),
VTY_NEWLINE);
/* Peer related usage */
- count = mtype_stats_alloc (MTYPE_BGP_PEER);
+ count = mem_get_alloc(mst, MTYPE_BGP_PEER);
vty_out (vty, "%ld peers, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct peer)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_PEER_GROUP)))
+ if ((count = mem_get_alloc(mst, MTYPE_PEER_GROUP)))
vty_out (vty, "%ld peer groups, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct peer_group)),
VTY_NEWLINE);
/* Other */
- if ((count = mtype_stats_alloc (MTYPE_HASH)))
+ if ((count = mem_get_alloc(mst, MTYPE_HASH)))
vty_out (vty, "%ld hash tables, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct hash)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_HASH_BACKET)))
+ if ((count = mem_get_alloc(mst, MTYPE_HASH_BACKET)))
vty_out (vty, "%ld hash buckets, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct hash_backet)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_REGEXP)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_REGEXP)))
vty_out (vty, "%ld compiled regexes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (regex_t)),
@@ -9040,37 +9071,50 @@ bgp_config_write_redistribute (struct vty *vty, struct bgp *bgp, afi_t afi,
/* BGP node structure. */
static struct cmd_node bgp_node =
{
- BGP_NODE,
- "%s(config-router)# ",
- 1,
+ .node = BGP_NODE,
+ .prompt = "%s(config-router)# ",
+
+ .config_to_vtysh = true,
};
static struct cmd_node bgp_ipv4_unicast_node =
{
- BGP_IPV4_NODE,
- "%s(config-router-af)# ",
- 1,
+ .node = BGP_IPV4_NODE,
+ .prompt = "%s(config-router-af)# ",
+
+ .parent = BGP_NODE,
+
+ .config_to_vtysh = true,
};
static struct cmd_node bgp_ipv4_multicast_node =
{
- BGP_IPV4M_NODE,
- "%s(config-router-af)# ",
- 1,
+ .node = BGP_IPV4M_NODE,
+ .prompt = "%s(config-router-af)# ",
+
+ .parent = BGP_NODE,
+
+ .config_to_vtysh = true,
};
static struct cmd_node bgp_ipv6_unicast_node =
{
- BGP_IPV6_NODE,
- "%s(config-router-af)# ",
- 1,
+ .node = BGP_IPV6_NODE,
+ .prompt = "%s(config-router-af)# ",
+
+ .parent = BGP_NODE,
+
+ .config_to_vtysh = true,
};
static struct cmd_node bgp_ipv6_multicast_node =
{
- BGP_IPV6M_NODE,
- "%s(config-router-af)# ",
- 1,
+ .node = BGP_IPV6M_NODE,
+ .prompt = "%s(config-router-af)# ",
+
+ .parent = BGP_NODE,
+
+ .config_to_vtysh = true,
};
static struct cmd_node bgp_vpnv4_node =
@@ -9788,10 +9832,12 @@ bgp_vty_init (void)
/* address-family commands. */
install_element (BGP_NODE, &address_family_ipv4_cmd);
- install_element (BGP_NODE, &address_family_ipv4_safi_cmd);
+ install_element (BGP_NODE, &address_family_ipv4_safi_unicast_cmd);
+ install_element (BGP_NODE, &address_family_ipv4_safi_multicast_cmd);
#ifdef HAVE_IPV6
install_element (BGP_NODE, &address_family_ipv6_cmd);
- install_element (BGP_NODE, &address_family_ipv6_safi_cmd);
+ install_element (BGP_NODE, &address_family_ipv6_safi_unicast_cmd);
+ install_element (BGP_NODE, &address_family_ipv6_safi_multicast_cmd);
#endif /* HAVE_IPV6 */
install_element (BGP_NODE, &address_family_vpnv4_cmd);
install_element (BGP_NODE, &address_family_vpnv4_unicast_cmd);
@@ -10564,7 +10610,7 @@ DEFUN (show_ip_community_list,
{
struct symbol_table* table;
vector extract ;
- vector_index i ;
+ vector_index_t i ;
struct symbol* sym ;
struct community_list *list;
@@ -10923,7 +10969,7 @@ DEFUN (show_ip_extcommunity_list,
{
struct symbol_table* table;
vector extract ;
- vector_index i ;
+ vector_index_t i ;
struct symbol* sym ;
struct community_list *list;
@@ -10994,7 +11040,7 @@ community_list_config_write_list(struct vty* vty, int what)
struct community_list *list;
struct community_entry *entry;
vector extract ;
- vector_index i ;
+ vector_index_t i ;
struct symbol* sym ;
int write = 0;
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index ff601ba1..5a5eab83 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -19,6 +19,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#include <zebra.h>
+#include "misc.h"
#include "prefix.h"
#include "thread.h"
@@ -4864,7 +4865,7 @@ bgp_init (void)
*
*/
void
-bgp_terminate (int terminating, int retain_mode)
+bgp_terminate (bool terminating, bool retain_mode)
{
struct bgp *bgp;
struct peer *peer;
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index dad3360e..f20d8315 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -21,7 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _QUAGGA_BGPD_H
#define _QUAGGA_BGPD_H
-#include "stdbool.h"
+#include "misc.h"
#include "bgpd/bgp_common.h"
#include "bgpd/bgp_notification.h"
@@ -445,15 +445,15 @@ bgp_clock(void)
* Clock time calculated now may differ from any logged Wall Clock times !!
*/
Inline time_t
-bgp_wall_clock(time_t bgp_time)
+bgp_wall_clock(time_t mono)
{
- return time(NULL) + (bgp_time - bgp_clock()) ;
+ return time(NULL) - (bgp_clock() - mono) ;
} ;
/*------------------------------------------------------------------------------
* Prototypes.
*/
-extern void bgp_terminate (int, int);
+extern void bgp_terminate (bool, bool);
extern void bgp_reset (void);
extern void bgp_zclient_reset (void); /* See bgp_zebra ! */
diff --git a/configure.ac b/configure.ac
index 6e0c5d5b..01dc36bb 100755
--- a/configure.ac
+++ b/configure.ac
@@ -8,7 +8,7 @@
## $Id$
AC_PREREQ(2.53)
-AC_INIT(Quagga, 0.99.18ex17, [http://bugzilla.quagga.net])
+AC_INIT(Quagga, 0.99.18ex18p, [http://bugzilla.quagga.net])
AC_CONFIG_SRCDIR(lib/zebra.h)
AC_CONFIG_MACRO_DIR([m4])
@@ -53,13 +53,24 @@ dnl ------------
dnl Check CFLAGS
dnl ------------
AC_ARG_WITH(cflags,
-[ --with-cflags Set CFLAGS for use in compilation.])
+[ --with-cflags=FLAGS Set CFLAGS for use in compilation.])
if test "x$with_cflags" != "x" ; then
CFLAGS="$with_cflags" ; cflags_specified=yes ;
elif test -n "$CFLAGS" ; then
cflags_specified=yes ;
fi
+dnl -------------
+dnl Check DEFINES
+dnl -------------
+AC_ARG_WITH(defines,
+[ --with-defines=DEFS Set DEFINES for use in compilation.])
+if test "x$with_defines" != "x" ; then
+ DEFINES="$with_defines" ; defines_specified=yes ;
+elif test -n "$DEFINES" ; then
+ defines_specified=yes ;
+fi
+
dnl --------------------
dnl Check CC and friends
dnl --------------------
@@ -96,7 +107,7 @@ else
fi
dnl ---------------------------------------------
-dnl If CLFAGS doesn\'t exist set default value
+dnl If CFLAGS doesn\'t exist set default value
dnl AC_PROG_CC will have set minimal default
dnl already, eg "-O2 -g" for gcc, "-g" for others
dnl (Wall is gcc specific... have to make sure
@@ -128,7 +139,7 @@ if test "x${cflags_specified}" = "x" ; then
CFLAGS="${CFLAGS} -Wbad-function-cast -Wwrite-strings"
CFLAGS="${CFLAGS} -Wmissing-prototypes -Wmissing-declarations"
CFLAGS="${CFLAGS} -Wchar-subscripts -Wcast-qual"
- CFLAGS="${CFLAGS} -pthread -rdynamic"
+ CFLAGS="${CFLAGS} -pthread"
# TODO: conditionally addd -Wpacked if handled
AC_MSG_RESULT([gcc default])
;;
@@ -145,6 +156,27 @@ else
fi
dnl --------------
+dnl Append DEFINES
+dnl --------------
+
+AC_MSG_CHECKING([whether to append DEFINES])
+if test "x${defines_specified}" != "x" ; then
+ CFLAGS="${CFLAGS} ${DEFINES}"
+ AC_MSG_RESULT([appended ${DEFINES}])
+else
+ AC_MSG_RESULT([no])
+fi
+
+dnl -------------------
+dnl With MEMORY_TRACKER
+dnl -------------------
+AC_ARG_WITH(memory_tracker,
+[ --with-memory-tracker Set -DMEMORY_TRACKER])
+if test "x$with_memory_tracker" != "x" ; then
+ CFLAGS="$CFLAGS -DMEMORY_TRACKER" ;
+fi
+
+dnl --------------
dnl Check programs
dnl --------------
AC_PROG_INSTALL
@@ -252,13 +284,15 @@ AC_ARG_ENABLE(capabilities,
AC_ARG_ENABLE(gcc_ultra_verbose,
[ --enable-gcc-ultra-verbose enable ultra verbose GCC warnings])
AC_ARG_ENABLE(linux24_tcp_md5,
-[ --enable-linux24-tcp-md5 enable support for old, Linux-2.4 RFC2385 patch])
+[ --enable-linux24-tcp-md5 enable support for old, Linux-2.4 RFC2385 patch])
AC_ARG_ENABLE(gcc-rdynamic,
-[ --enable-gcc-rdynamic enable gcc linking with -rdynamic for better backtraces])
+[ --enable-gcc-rdynamic enable gcc linking with -rdynamic for better backtraces])
+AC_ARG_ENABLE(gcc-debug,
+[ --enable-gcc-debug enable gcc debug (-O0 -g3)])
AC_ARG_ENABLE(time-check,
[ --disable-time-check disable slow thread warning messages])
AC_ARG_ENABLE(pcreposix,
-[ --enable-pcreposix enable using PCRE Posix libs for regex functions])
+[ --enable-pcreposix enable using PCRE Posix libs for regex functions])
if test x"${enable_gcc_ultra_verbose}" = x"yes" ; then
CFLAGS="${CFLAGS} -W -Wcast-qual -Wstrict-prototypes"
@@ -267,6 +301,10 @@ if test x"${enable_gcc_ultra_verbose}" = x"yes" ; then
CFLAGS="${CFLAGS} -Wpacked -Wpadded"
fi
+if test x"${enable_gcc_debug}" = x"yes" ; then
+ CFLAGS="${CFLAGS} -O0 -g3"
+fi
+
if test x"${enable_gcc_rdynamic}" = x"yes" ; then
LDFLAGS="${LDFLAGS} -rdynamic"
fi
@@ -1548,7 +1586,7 @@ echo "
Quagga configuration
--------------------
quagga version : ${PACKAGE_VERSION}
-host operating system : ${host_os}
+host operating system : ${host_os}
source code location : ${srcdir}
compiler : ${CC}
compiler flags : ${CFLAGS}
@@ -1558,9 +1596,9 @@ linker flags : ${LDFLAGS} ${LIBS} ${LIBCAP} ${LIBREADLINE} ${LIBM}
state file directory : ${quagga_statedir}
config file directory : `eval echo \`echo ${sysconfdir}\``
example directory : `eval echo \`echo ${exampledir}\``
-user to run as : ${enable_user}
-group to run as : ${enable_group}
-group for vty sockets : ${enable_vty_group}
+user to run as : ${enable_user}
+group to run as : ${enable_group}
+group for vty sockets : ${enable_vty_group}
config file mask : ${enable_configfile_mask}
log file mask : ${enable_logfile_mask}
diff --git a/isisd/dict.c b/isisd/dict.c
index 6c3e1e7f..4d2400f6 100644
--- a/isisd/dict.c
+++ b/isisd/dict.c
@@ -18,9 +18,9 @@
* $Name$
*/
+#include "zebra.h"
#include <stdlib.h>
#include <stddef.h>
-#include "zebra.h"
#include "zassert.h"
#define DICT_IMPLEMENTATION
#include "dict.h"
@@ -32,7 +32,7 @@ static const char rcsid[] = "Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz";
/*
* These macros provide short convenient names for structure members,
* which are embellished with dict_ prefixes so that they are
- * properly confined to the documented namespace. It's legal for a
+ * properly confined to the documented namespace. It's legal for a
* program which uses dict to define, for instance, a macro called ``parent''.
* Such a macro would interfere with the dnode_t struct definition.
* In general, highly portable and reusable C modules which expose their
@@ -143,7 +143,7 @@ static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil)
* dict_next() successor function, verifying that the key of each node is
* strictly lower than that of its successor, if duplicates are not allowed,
* or lower or equal if duplicates are allowed. This function is used for
- * debugging purposes.
+ * debugging purposes.
*/
static int verify_bintree(dict_t *dict)
@@ -203,7 +203,7 @@ static unsigned int verify_redblack(dnode_t *nil, dnode_t *root)
if (root->color != dnode_black)
return 0;
return height_left + 1;
- }
+ }
return 1;
}
@@ -336,7 +336,7 @@ dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp)
return dict;
}
-/*
+/*
* Initialize a dictionary in the likeness of another dictionary
*/
@@ -376,7 +376,7 @@ static void dict_clear(dict_t *dict)
* debugging purposes, and should be placed in assert statements. Just because
* this function succeeds doesn't mean that the tree is not corrupt. Certain
* corruptions in the tree may simply cause undefined behavior.
- */
+ */
int dict_verify(dict_t *dict)
{
@@ -430,7 +430,7 @@ int dict_similar(const dict_t *left, const dict_t *right)
/*
* Locate a node in the dictionary having the given key.
- * If the node is not found, a null a pointer is returned (rather than
+ * If the node is not found, a null a pointer is returned (rather than
* a pointer that dictionary's nil sentinel node), otherwise a pointer to the
* located node is returned.
*/
@@ -494,9 +494,9 @@ dnode_t *dict_lower_bound(dict_t *dict, const void *key)
tentative = root;
root = root->left;
}
- }
+ }
}
-
+
return tentative;
}
@@ -526,9 +526,9 @@ dnode_t *dict_upper_bound(dict_t *dict, const void *key)
tentative = root;
root = root->right;
}
- }
+ }
}
-
+
return tentative;
}
@@ -708,10 +708,10 @@ dnode_t *dict_delete(dict_t *dict, dnode_t *delete)
child = (delete->left != nil) ? delete->left : delete->right;
- child->parent = delparent = delete->parent;
+ child->parent = delparent = delete->parent;
if (delete == delparent->left) {
- delparent->left = child;
+ delparent->left = child;
} else {
assert (delete == delparent->right);
delparent->right = child;
@@ -1035,7 +1035,7 @@ void dict_load_next(dict_load_t *load, dnode_t *newnode, const void *key)
{
dict_t *dict = load->dictptr;
dnode_t *nil = &load->nilnode;
-
+
assert (!dnode_is_in_a_dict(newnode));
assert (dict->nodecount < dict->maxcount);
@@ -1141,7 +1141,7 @@ void dict_merge(dict_t *dest, dict_t *source)
dict_load_t load;
dnode_t *leftnode = dict_first(dest), *rightnode = dict_first(source);
- assert (dict_similar(dest, source));
+ assert (dict_similar(dest, source));
if (source == dest)
return;
@@ -1174,7 +1174,7 @@ void dict_merge(dict_t *dest, dict_t *source)
leftnode = next;
continue;
}
-
+
copyright:
{
dnode_t *next = dict_next(source, rightnode);
@@ -1202,7 +1202,7 @@ typedef char input_t[256];
static int tokenize(char *string, ...)
{
- char **tokptr;
+ char **tokptr;
va_list arglist;
int tokcount = 0;
@@ -1266,7 +1266,7 @@ static void construct(dict_t *d)
dnode_t *dn;
char *tok1, *tok2, *val;
const char *key;
- char *help =
+ char *help =
"p turn prompt on\n"
"q finish construction\n"
"a <key> <val> add new entry\n";
diff --git a/isisd/dict.h b/isisd/dict.h
index 9395d1c0..e381256f 100644
--- a/isisd/dict.h
+++ b/isisd/dict.h
@@ -21,7 +21,7 @@
#ifndef DICT_H
#define DICT_H
-#include <limits.h>
+#include "misc.h"
#ifdef KAZLIB_SIDEEFFECT_DEBUG
#include "sfx.h"
#endif
diff --git a/isisd/isis_misc.c b/isisd/isis_misc.c
index fd910835..d45d7aa0 100644
--- a/isisd/isis_misc.c
+++ b/isisd/isis_misc.c
@@ -3,21 +3,21 @@
* Miscellanous routines
*
* Copyright (C) 2001,2002 Sampo Saaristo
- * Tampere University of Technology
+ * Tampere University of Technology
* Institute of Communications Engineering
*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public Licenseas published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public Licenseas published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
- * This program 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
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
@@ -96,7 +96,7 @@ isonet_print (u_char * from, int len)
/*
* Returns 0 on error, length of buff on ok
- * extract dot from the dotted str, and insert all the number in a buff
+ * extract dot from the dotted str, and insert all the number in a buff
*/
int
dotformat2buff (u_char * buff, const u_char * dotted)
@@ -464,7 +464,7 @@ isis_jitter (unsigned long timer, unsigned long jitter)
if (timer == 1)
return timer;
- /*
+ /*
* randomizing just the percent value provides
* no good random numbers - hence the spread
* to RANDOM_SPREAD (100000), which is ok as
@@ -496,16 +496,5 @@ newprefix2inaddr (u_char * prefix_start, u_char prefix_masklen)
const char *
unix_hostname (void)
{
- static struct utsname names;
- const char *hostname;
- extern struct host host;
-
- hostname = host.name;
- if (!hostname)
- {
- uname (&names);
- hostname = names.nodename;
- }
-
- return hostname;
-}
+ return cmd_host_name(true) ; /* if required, get fresh system hostname */
+} ;
diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c
index 5d7e9da4..c4433e49 100644
--- a/isisd/isis_spf.c
+++ b/isisd/isis_spf.c
@@ -3,21 +3,21 @@
* The SPT algorithm
*
* Copyright (C) 2001,2002 Sampo Saaristo
- * Tampere University of Technology
+ * Tampere University of Technology
* Institute of Communications Engineering
*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public Licenseas published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public Licenseas published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
- * This program 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
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
@@ -64,7 +64,7 @@ remove_excess_adjs (struct list *adjs)
struct isis_adjacency *adj, *candidate = NULL;
int comp;
- for (ALL_LIST_ELEMENTS_RO (adjs, node, adj))
+ for (ALL_LIST_ELEMENTS_RO (adjs, node, adj))
{
if (excess == NULL)
excess = node;
@@ -229,7 +229,7 @@ isis_spftree_del (struct isis_spftree *spftree)
return;
}
-#endif
+#endif
void
spftree_area_init (struct isis_area *area)
@@ -241,7 +241,7 @@ spftree_area_init (struct isis_area *area)
area->spftree6[0] = isis_spftree_new ();
#endif
- /* thread_add_timer (master, isis_run_spf_l1, area,
+ /* thread_add_timer (master, isis_run_spf_l1, area,
isis_jitter (PERIODIC_SPF_INTERVAL, 10)); */
}
@@ -251,7 +251,7 @@ spftree_area_init (struct isis_area *area)
#ifdef HAVE_IPV6
area->spftree6[1] = isis_spftree_new ();
#endif
- /* thread_add_timer (master, isis_run_spf_l2, area,
+ /* thread_add_timer (master, isis_run_spf_l2, area,
isis_jitter (PERIODIC_SPF_INTERVAL, 10)); */
}
@@ -416,7 +416,7 @@ isis_spf_add2tent (struct isis_spftree *spftree, enum vertextype vtype,
listnode_add (spftree->tents, vertex);
return vertex;
}
-
+
/* XXX: This cant use the standard ALL_LIST_ELEMENT macro */
for (node = listhead (spftree->tents); node; node = listnextnode (node))
{
@@ -613,7 +613,7 @@ lspfragloop:
if (family == AF_INET && lsp->tlv_data.ipv4_int_reachs)
{
prefix.family = AF_INET;
- for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv4_int_reachs,
+ for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv4_int_reachs,
node, ipreach))
{
dist = cost + ipreach->metrics.metric_default;
@@ -658,7 +658,7 @@ lspfragloop:
if (family == AF_INET6 && lsp->tlv_data.ipv6_reachs)
{
prefix.family = AF_INET6;
- for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv6_reachs,
+ for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv6_reachs,
node, ip6reach))
{
dist = cost + ip6reach->metric;
@@ -789,7 +789,7 @@ isis_spf_preload_tent (struct isis_spftree *spftree,
if (family == AF_INET6 && !circuit->ipv6_router)
continue;
#endif /* HAVE_IPV6 */
- /*
+ /*
* Add IP(v6) addresses of this circuit
*/
if (family == AF_INET)
@@ -872,7 +872,7 @@ isis_spf_preload_tent (struct isis_spftree *spftree,
}
list_delete (adj_list);
/*
- * Add the pseudonode
+ * Add the pseudonode
*/
if (level == 1)
memcpy (lsp_id, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1);
@@ -1272,7 +1272,7 @@ isis_spf_schedule6 (struct isis_area *area, int level)
spftree->pending = 1;
return retval;
}
-
+
THREAD_TIMER_OFF (spftree->t_spf);
if (diff < MINIMUM_SPF_INTERVAL)
@@ -1323,7 +1323,7 @@ isis_print_paths (struct vty *vty, struct list *paths)
continue;
if (memcmp (vertex->N.id, isis->sysid, ISIS_SYS_ID_LEN) == 0)
{
- vty_out (vty, "%s --%s", host.name?host.name:"",
+ vty_out (vty, "%s --%s", cmd_host_name(true),
VTY_NEWLINE);
}
else
diff --git a/isisd/topology/spgrid.c b/isisd/topology/spgrid.c
index 5197beb1..2aec9841 100644
--- a/isisd/topology/spgrid.c
+++ b/isisd/topology/spgrid.c
@@ -1,10 +1,11 @@
+#include <zebra.h>
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "random.c"
-#include <zebra.h>
#include "thread.h"
#include "vty.h"
diff --git a/lib/Makefile.am b/lib/Makefile.am
index e88c5998..0f965b07 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -4,20 +4,24 @@ INCLUDES = @INCLUDES@ -I.. -I$(top_srcdir) -I$(top_srcdir)/lib @SNMP_INCLUDES@
DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\"
lib_LTLIBRARIES = libzebra.la
-libzebra_la_LDFLAGS = -version-info 0:0:0
+libzebra_la_LDFLAGS = -version-info 0:0:0
libzebra_la_SOURCES = \
- network.c pid_output.c getopt.c getopt1.c daemon.c \
- checksum.c vector.c linklist.c vty.c command.c \
- sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \
- filter.c routemap.c distribute.c stream.c str.c log.c plist.c \
- zclient.c sockopt.c smux.c md5.c if_rmap.c keychain.c privs.c \
- sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \
- qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \
- command_queue.c qlib_init.c pthread_safe.c list_util.c \
- vty_io.c vty_cli.c keystroke.c qstring.c vio_fifo.c vio_lines.c \
- qiovec.c qfstring.c errno_names.c
-
+ buffer.c checksum.c command.c command_execute.c \
+ command_parse.c command_queue.c daemon.c distribute.c \
+ elstring.c errno_names.c filter.c getopt.c getopt1.c \
+ hash.c heap.c if.c if_rmap.c jhash.c keychain.c keystroke.c \
+ linklist.c list_util.c log.c md5.c memory.c memtypes.c mqueue.c \
+ network.c pid_output.c plist.c pqueue.c prefix.c privs.c \
+ pthread_safe.c qfstring.c qiovec.c qlib_init.c qpath.c \
+ qpnexus.c qpselect.c qpthreads.c qrand.c qstring.c \
+ qtime.c qtimers.c routemap.c sigevent.c smux.c \
+ sockopt.c sockunion.c str.c stream.c symtab.c \
+ table.c thread.c vector.c vio_fifo.c vio_lines.c \
+ vty.c vty_cli.c vty_command.c vty_io.c vty_io_basic.c \
+ vty_io_file.c vty_io_shell.c vty_io_term.c vty_io_vsh.c \
+ vty_log.c workqueue.c zclient.c
+
BUILT_SOURCES = memtypes.h route_types.h
libzebra_la_DEPENDENCIES = @LIB_REGEX@
@@ -25,20 +29,24 @@ libzebra_la_DEPENDENCIES = @LIB_REGEX@
libzebra_la_LIBADD = @LIB_REGEX@
pkginclude_HEADERS = \
- buffer.h checksum.h command.h filter.h getopt.h hash.h \
- if.h linklist.h log.h \
- memory.h network.h prefix.h routemap.h distribute.h sockunion.h \
- str.h stream.h table.h thread.h vector.h version.h vty.h zebra.h \
- plist.h zclient.h sockopt.h smux.h md5.h if_rmap.h keychain.h \
- privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \
- workqueue.h route_types.h symtab.h heap.h \
- qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \
- command_queue.h qlib_init.h qafi_safi.h \
- confirm.h miyagi.h pthread_safe.h list_util.h node_type.h uty.h \
- vty_io.h vty_cli.h keystroke.h qstring.h vio_fifo.h vio_lines.h \
- qiovec.h qfstring.h errno_names.h \
- route_types.h command_execute.h
-
+ command.h command_common.h command_execute.h \
+ command_local.h command_parse.h command_queue.h confirm.h \
+ distribute.h elstring.h errno_names.h filter.h getopt.h \
+ hash.h heap.h if.h if_rmap.h jhash.h keychain.h \
+ keystroke.h linklist.h list_util.h log.h log_common.h log_local.h \
+ md5.h memory.h memtypes.h misc.h miyagi.h mqueue.h \
+ network.h plist.h pqueue.h prefix.h privs.h \
+ pthread_safe.h qafi_safi.h qdebug_nb.h qfstring.h qiovec.h \
+ qlib_init.h qpath.h qpnexus.h qpselect.h qpthreads.h \
+ qrand.h qstring.h qtime.h qtimers.h route_types.h \
+ routemap.h sigevent.h smux.h sockopt.h sockunion.h str.h \
+ stream.h symtab.h table.h thread.h tstring.h vargs.h \
+ vector.h version.h vio_fifo.h vio_lines.h vty.h vty_cli.h \
+ vty_command.h vty_common.h vty_io.h vty_io_basic.h \
+ vty_io_file.h vty_io_shell.h vty_io_term.h vty_io_vsh.h \
+ vty_local.h vty_log.h workqueue.h zassert.h zclient.h \
+ zconfig.h zebra.h
+
EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt
memtypes.h: $(srcdir)/memtypes.c $(srcdir)/memtypes.awk
diff --git a/lib/command.c b/lib/command.c
index 67a9ab04..ee855360 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -21,83 +21,286 @@ 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 "misc.h"
+#include "version.h"
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
#include "memory.h"
-#include "log.h"
-#include <lib/version.h>
#include "thread.h"
#include "vector.h"
-#include "vty.h"
-#include "uty.h"
#include "qstring.h"
+#include "qtime.h"
+#include "workqueue.h"
#include "command.h"
+#include "command_local.h"
+#include "command_parse.h"
#include "command_execute.h"
-#include "workqueue.h"
#include "command_queue.h"
+#include "log_local.h"
+#include "vty_local.h"
+#include "vty_command.h"
+#include "vty_io.h"
+#include "vty_log.h"
+#include "network.h"
+
+/* Vector of cmd_node, one for each known node, built during daemon
+ * initialisation.
+ *
+ * Declared extern in command_local.h, so it can get at it.
+ */
+vector node_vector = NULL ;
+
+/*==============================================================================
+ * Default motd string and debug hello message.
+ */
+static const char* default_motd =
+"\n"
+"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n"
+ QUAGGA_COPYRIGHT "\n"
+"\n" ;
+
+const char* debug_banner =
+ QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG_NAME " "
+ __DATE__ " " __TIME__ ;
+
+/*==============================================================================
+ * Host information structure -- shared across command/vty
+ *
+ * Must have VTY_LOCK() or not be running multiple pthreads to access this !
+ */
+struct host host =
+{
+ /* Host name of this router. */
+ .name = NULL, /* set by cmd_init */
+ .name_set = false,
+ .name_gen = 0, /* set by cmd_init */
+
+ /* Password for vty interface. */
+ .password = NULL,
+ .password_encrypted = false,
+
+ /* Enable password */
+ .enable = NULL,
+ .enable_encrypted = false,
+
+ /* System wide terminal lines. */
+ .lines = -1, /* unset */
+
+ /* Log filename. */
+ .logfile = NULL,
+
+ /* config file name of this host */
+ .config_file = NULL,
+ .config_dir = NULL,
+
+ /* Flags for services */
+ .advanced = false,
+ .encrypt = false,
+
+ /* Banner configuration. */
+ .motd = NULL,
+ .motdfile = NULL,
+
+ /* Nobody has the config symbol of power */
+ .config = false,
+ .config_brand = 0,
+
+ /* allow VTY to start without password */
+ .no_password_check = false,
+
+ /* if VTY starts without password, use RESTRICTED_NODE */
+ .restricted_mode = false,
+
+ /* Vty timeout value -- see "exec timeout" command */
+ .vty_timeout_val = VTY_TIMEOUT_DEFAULT,
+
+ /* Vty access-class command */
+ .vty_accesslist_name = NULL,
+
+ /* Vty access-class for IPv6. */
+ .vty_ipv6_accesslist_name = NULL,
+
+ /* Current directory -- initialised in cmd_getcwd() */
+ .cwd = NULL,
+} ;
+
+/*==============================================================================
+ * host.name handling.
+ *
+ * If the host.name is set explicitly by command then host.name_set is true,
+ * and things are simple.
+ *
+ * Otherwise, need to ask the system. Does that once at start up, and if the
+ * host.name is unset by command -- so there should always be a valid host.name.
+ *
+ * However, it is conceivable that the host name changes (!). So, when asking
+ * for cmd_host_name(), can ask for the system to be asked afresh (if the name
+ * is not explicitly set).
+ *
+ * The VTY watch-dog timer refreshes the host.name on a regular basis,
+ * cmd_check_host_name(), so need not ask for a fresh host.name, unless wish
+ * to guarantee to be absolutely up to date.
+ *
+ * The VTY prompts need the current host name, but that is debounced using the
+ * host.name_gen value. host.name_gen is incremented each time the host.name
+ * actually changes. It is thought unlikely that this will ever wrap round,
+ * but it is guaranteed not to be zero.
+ *
+ * The fact that the host.name can change is reflected in the need to hold
+ * the VTY_LOCK while have the host.name in hand.
+ */
+
+static void cmd_get_sys_host_name(void) ;
+static void cmd_new_host_name(const char* name) ;
+
+/*------------------------------------------------------------------------------
+ * Get the host name: (a) from an explicit host name command
+ * or: (b) from the last time the system was asked.
+ *
+ * Note that the system is asked regularly by the watch dog.
+ *
+ * NB: needs to be VTY_LOCK() *or* not running qpthreads.
+ */
+extern const char*
+cmd_host_name(bool fresh)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (!host.name_set && fresh)
+ cmd_get_sys_host_name() ;
+
+ return host.name ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set (or unset) the host name from an explicit host name command.
+ *
+ * If unsets, immediately asks the system for the system host name (which may
+ * be the same !).
+ */
+static cmd_return_code_t
+cmd_set_host_name(const char* name)
+{
+ VTY_LOCK() ;
+
+ host.name_set = (name != NULL) ;
+ if (host.name_set)
+ cmd_new_host_name(name) ;
+ else
+ cmd_get_sys_host_name() ;
+
+ VTY_UNLOCK() ;
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the host name from the system and set host.name to that.
+ *
+ * NB: result will not be NULL, whatever happens will get at least an empty
+ * '\0' terminated string.
+ *
+ * NB: called early in the morning to initialise host.name and to set
+ * host.name_gen != 0.
+ */
+static void
+cmd_get_sys_host_name(void)
+{
+ struct utsname info ;
-/* Command vector which includes some level of command lists. Normally
- each daemon maintains each own cmdvec. */
-vector cmdvec = NULL;
+ VTY_ASSERT_LOCKED() ;
-struct desc desc_cr;
-char *command_cr = NULL;
+ uname (&info) ;
+ cmd_new_host_name(info.nodename) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set host.name to (possibly) new value.
+ *
+ * Does nothing if new name == old name, otherwise increments name_gen.
+ */
+static void
+cmd_new_host_name(const char* name)
+{
+ VTY_ASSERT_LOCKED() ;
-/* Host information structure. */
-struct host host;
+ if ((host.name == NULL) || (strcmp(host.name, name) != 0))
+ {
+ XFREE(MTYPE_HOST, host.name) ;
+ host.name = XSTRDUP(MTYPE_HOST, name) ;
+ do ++host.name_gen ; while (host.name_gen == 0) ;
+ } ;
+} ;
-/* Standard command node structures. */
+/*==============================================================================
+ * node structures for the basic nodes.
+ *
+ * Covers everything up to and including the CONFIG_NODE.
+ */
static struct cmd_node auth_node =
{
- AUTH_NODE,
- "Password: ",
+ .node = AUTH_NODE,
+ .prompt = "Password: ",
+
+ .parent = AUTH_NODE, /* self => no parent */
+ .exit_to = NULL_NODE, /* close ! */
+ .end_to = AUTH_NODE, /* self => no end */
};
static struct cmd_node view_node =
{
- VIEW_NODE,
- "%s> ",
+ .node = VIEW_NODE,
+ .prompt = "%s> ",
+
+ .parent = VIEW_NODE, /* self => no parent */
+ .exit_to = NULL_NODE, /* close ! */
+ .end_to = VIEW_NODE, /* self => no end */
};
static struct cmd_node restricted_node =
{
- RESTRICTED_NODE,
- "%s$ ",
+ .node = RESTRICTED_NODE,
+ .prompt = "%s$ ",
+
+ .parent = RESTRICTED_NODE, /* self => no parent */
+ .exit_to = NULL_NODE, /* close ! */
+ .end_to = RESTRICTED_NODE, /* self => no end */
};
static struct cmd_node auth_enable_node =
{
- AUTH_ENABLE_NODE,
- "Password: ",
+ .node = AUTH_ENABLE_NODE,
+ .prompt = "Password: ",
+
+ .parent = AUTH_ENABLE_NODE, /* self => no parent */
+ .exit_to = VIEW_NODE, /* fall back */
+ .end_to = AUTH_ENABLE_NODE, /* self => no end */
};
static struct cmd_node enable_node =
{
- ENABLE_NODE,
- "%s# ",
+ .node = ENABLE_NODE,
+ .prompt = "%s# ",
+
+ .parent = ENABLE_NODE, /* self => no parent */
+ .exit_to = NULL_NODE, /* close ! */
+ .end_to = ENABLE_NODE, /* self => no end */
};
static struct cmd_node config_node =
{
- CONFIG_NODE,
- "%s(config)# ",
- 1
-};
-
-/* Default motd string. */
-static const char *default_motd =
-"\r\n\
-Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ").\r\n\
-" QUAGGA_COPYRIGHT "\r\n\
-\r\n";
+ .node = CONFIG_NODE,
+ .prompt = "%s(config)# ",
+ .parent = ENABLE_NODE, /* more or less */
+ .exit_to = ENABLE_NODE, /* exit == end for CONFIG_NODE */
+ .end_to = ENABLE_NODE, /* standard end action */
-#ifdef QDEBUG
-const char* debug_banner =
- QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG_NAME " "
- __DATE__ " " __TIME__ ;
-#endif
+ .config_to_vtysh = true
+};
static const struct facility_map {
int facility;
@@ -105,16 +308,16 @@ static const struct facility_map {
size_t match;
} syslog_facilities[] =
{
- { LOG_KERN, "kern", 1 },
- { LOG_USER, "user", 2 },
- { LOG_MAIL, "mail", 1 },
+ { LOG_KERN, "kern", 1 },
+ { LOG_USER, "user", 2 },
+ { LOG_MAIL, "mail", 1 },
{ LOG_DAEMON, "daemon", 1 },
- { LOG_AUTH, "auth", 1 },
+ { LOG_AUTH, "auth", 1 },
{ LOG_SYSLOG, "syslog", 1 },
- { LOG_LPR, "lpr", 2 },
- { LOG_NEWS, "news", 1 },
- { LOG_UUCP, "uucp", 2 },
- { LOG_CRON, "cron", 1 },
+ { LOG_LPR, "lpr", 2 },
+ { LOG_NEWS, "news", 1 },
+ { LOG_UUCP, "uucp", 2 },
+ { LOG_CRON, "cron", 1 },
#ifdef LOG_FTP
{ LOG_FTP, "ftp", 1 },
#endif
@@ -129,6 +332,31 @@ static const struct facility_map {
{ 0, NULL, 0 },
};
+#if 0
+static enum cmd_return_code
+cmd_pipe_func(cmd_command 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)
+{
+ return CMD_SUCCESS ;
+} ;
+
+static struct cmd_command cmd_pipe_element =
+{
+ .string = "< or <|", /* Dummy */
+ .func = cmd_pipe_func,
+ .doc = "Pipe input to command processor",
+ .daemon = 0,
+ .items = NULL,
+ .cmdsize = 0,
+ .config = NULL,
+ .subconfig = NULL,
+ .attr = CMD_ATTR_SIMPLE,
+} ;
+#endif
+
+
static const char *
facility_name(int facility)
{
@@ -171,9 +399,12 @@ print_version (const char *progname)
}
-/* Utility function to concatenate argv argument into a single string
- with inserting ' ' character between each argument. */
-char *
+/*------------------------------------------------------------------------------
+ * Concatenate argv argument into a single string, inserting ' ' between each
+ * argument.
+ *
+ */
+extern char *
argv_concat (const char* const* argv, int argc, int shift)
{
int i;
@@ -184,8 +415,10 @@ argv_concat (const char* const* argv, int argc, int shift)
len = 0;
for (i = shift; i < argc; i++)
len += strlen(argv[i])+1;
+
if (!len)
return NULL;
+
p = str = XMALLOC(MTYPE_TMP, len);
for (i = shift; i < argc; i++)
{
@@ -203,8 +436,8 @@ argv_concat (const char* const* argv, int argc, int shift)
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;
+ const struct cmd_command *a = *(struct cmd_command * const *)p;
+ const struct cmd_command *b = *(struct cmd_command * const *)q;
return strcmp (a->string, b->string);
}
@@ -220,209 +453,172 @@ cmp_desc (const void *p, const void *q)
#endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
-/* Install top node of command vector. */
-void
-install_node (struct cmd_node *node,
- int (*func) (struct vty *))
-{
- vector_set_index (cmdvec, node->node, node);
- node->func = func;
- node->cmd_vector = vector_init (0);
-}
-
-/* Compare two command's string. Used in sort_node (). */
-static int
-cmp_node (const struct cmd_element **a, const struct cmd_element **b)
-{
- return strcmp ((*a)->string, (*b)->string);
-}
-
-static int
-cmp_desc (const struct desc **a, const struct desc **b)
-{
- return strcmp ((*a)->cmd, (*b)->cmd);
-}
-
-/* Sort each node's command element according to command string. */
-void
-sort_node ()
-{
- unsigned int i ;
-
- for (i = 0; i < vector_length(cmdvec); i++)
- {
- struct cmd_node *cnode;
- vector cmd_vector ;
- unsigned int j;
-
- 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.
+ * Install top node of command vector.
*
- * Discards leading and trailing white-space.
+ * Initialised as follows:
*
- * Treats lines that start with '!' or '#' (after any leading white-space)
- * as empty -- these are comment lines.
+ * .node -- must be set before node is installed
+ * .prompt -- must be set before node is installed
+ * .config_to_vtysh -- must be set true/false before node is installed
*
- * Tokens are non-whitespace separated by one or more white-space.
+ * .parent -- may be set, or if 0 (node_null) will be set to default
+ * .exit_to -- may be set, or if 0 (node_null) will be set to default
+ * .end_to -- may be set, or if 0 (node_null) will be set to default
*
- * White-space is anything that isspace() thinks is a space. (Which in the
- * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.)
+ * .config_write -- is set from parameter
*
- * Returns: NULL => empty line (after white-space trimming) or comment line.
- * otherwise: is vector containing one or more tokens.
+ * .cmd_vector -- initialised empty
+ *
+ * Default parent node:
+ *
+ * * all nodes > NODE_CONFIG have NODE_CONFIG as parent
+ * * node == NODE_CONFIG has ENABLE_NODE as parent
+ * * all nodes < NODE_CONFIG are their own parents (including ENABLE_NODE)
*
- * ....
+ * Default exit_to:
*
+ * * all nodes > NODE_CONFIG exit_to their parent
+ * * node == NODE_CONFIG exit_to ENABLE_NODE (its parent)
+ * * all nodes < NODE_CONFIG exit_to close
*
- * Note: all the tokens in the vector have at least one character, and no
- * entries are NULL.
+ * Default end_to:
+ *
+ * * all nodes >= NODE_CONFIG end_to ENABLE_NODE
+ * * all nodes < NODE_CONFIG end_to themselves
*
- * NB: it is the caller's responsibility to release the vector and its contents,
- * see cmd_free_strvec().
*/
-static vector
-cmd_make_vline(vector vline, qstring qs, const char *string)
+extern void
+install_node (struct cmd_node *node,
+ int (*config_write) (struct vty *))
{
- char *token, *tp ;
- const char *cp, *sp, *ep, *op ;
+ confirm(NULL_NODE == 0) ; /* unset value for .parent, .end_to & .exit_to */
- /* Reset any existing vline, and empty the qstring if given. */
- if (vline != NULL)
- vector_set_length(vline, 0) ;
+ if (node->parent == NULL_NODE)
+ {
+ if (node->node > CONFIG_NODE)
+ node->parent = CONFIG_NODE ;
+ else if (node->node == CONFIG_NODE)
+ node->parent = ENABLE_NODE ;
+ else
+ node->parent = node->node ;
+ } ;
- qs_clear(qs) ;
+ if (node->end_to == NULL_NODE)
+ {
+ if (node->node >= CONFIG_NODE)
+ node->end_to = ENABLE_NODE ;
+ else
+ node->end_to = node->node ;
+ } ;
- /* Strip leading and trailing white-space and deal with empty or effectively
- * empty lines -- comment lines are treated as effectively empty.
- */
- cp = string;
+ if (node->exit_to == NULL_NODE)
+ {
+ if (node->node >= CONFIG_NODE)
+ node->exit_to = node->parent ;
+ else
+ node->exit_to = NULL_NODE ;
+ } ;
- if (string == NULL)
- return NULL;
+ node->config_write = config_write ;
- while (isspace((int) *cp))
- cp++;
+ vector_init_new(node->cmd_vector, 0) ; /* embedded */
- ep = cp + strlen(cp) ;
+ vector_set_index (node_vector, node->node, node);
+} ;
- while ((ep > cp) && (isspace((int)*(ep - 1))))
- --ep ;
+/*------------------------------------------------------------------------------
+ * Return address of cmd_node -- asserts is not NULL !
+ */
+static cmd_node
+cmd_cmd_node(node_type_t node)
+{
+ cmd_node cn ;
- if ((cp == ep) || (*cp == '!') || (*cp == '#'))
- return NULL;
+ cn = vector_get_item(node_vector, node) ;
+ if (cn != NULL)
+ return cn ;
- /* Prepare return vector -- expect some reasonable number of tokens. */
- if (vline == NULL)
- vline = vector_init(10) ;
+ zabort("invalid node") ;
+} ;
- /* If writing the words to a qstring, copy the body of the original (less
- * any leading/trailing whitespace) to the qstring and '\0' terminate.
- */
- if (qs != NULL)
- {
- qs_set_n(qs, cp, ep - cp) ;
- tp = (char*)qs->body ; /* start at the beginning */
- }
- else
- tp = NULL ; /* not used, but not undefined */
+/*------------------------------------------------------------------------------
+ * Return parent node
+ */
+extern node_type_t
+cmd_node_parent(node_type_t node)
+{
+ return (cmd_cmd_node(node))->parent ;
+} ;
- op = cp ; /* original start position */
+/*------------------------------------------------------------------------------
+ * Return exit_to node
+ */
+extern node_type_t
+cmd_node_exit_to(node_type_t node)
+{
+ return (cmd_cmd_node(node))->exit_to ;
+} ;
- /* 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)
+/*------------------------------------------------------------------------------
+ * Return end_to node
+ */
+extern node_type_t
+cmd_node_end_to(node_type_t node)
+{
+ return (cmd_cmd_node(node))->end_to ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Sorting of all node cmd_vectors.
+ */
+
+/* Compare two command's string. Used in sort_node (). */
+static int
+cmp_node (const struct cmd_command **a, const struct cmd_command **b)
+{
+ return strcmp ((*a)->string, (*b)->string);
+}
+
+/* Sort each node's command element according to command string. */
+extern void
+sort_node ()
+{
+ unsigned int i ;
+
+ for (i = 0; i < vector_length(node_vector); i++)
{
- while (isspace((int) *cp))
- cp++ ; /* skip white-space */
+ struct cmd_node *cn;
+ vector cmd_vector ;
- sp = cp ;
- while ((cp < ep) && !isspace((int) *cp))
- cp++ ; /* eat token characters */
+ cn = vector_get_item(node_vector, i) ;
- if (qs == NULL)
- {
- /* creating array of MTYPE_STRVEC */
- size_t len ;
+ if (cn == NULL)
+ continue ;
- 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 */
- } ;
+ cmd_vector = cn->cmd_vector;
+ if (cmd_vector == NULL)
+ continue ;
- vector_push_item(vline, token);
+ vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ;
} ;
+} ;
- return vline ;
-}
-
+#if 0
/*------------------------------------------------------------------------------
- * Take string and break it into tokens.
- *
- * Discards leading and trailing white-space.
- *
- * Treats lines that start with '!' or '#' (after any leading white-space)
- * as empty -- these are comment lines.
- *
- * Tokens are non-whitespace separated by one or more white-space.
- *
- * White-space is anything that isspace() thinks is a space. (Which in the
- * 'C' locale is ' ', '\t', '\r, '\n', '\f' and '\v'.)
+ * Take string and break it into tokens -- see cmd_make_tokens().
*
* Returns: NULL => empty line (after white-space trimming) or comment line.
- * otherwise: is vector containing one or more tokens.
- *
- * Note: all the tokens in the vector have at least one character, and no
- * entries are NULL.
- *
- * NB: it is the caller's responsibility to release the vector and its contents,
- * see cmd_free_strvec().
+ * otherwise: is vector containing one or more tokens in qstrings.
*/
extern vector
cmd_make_strvec (const char *string)
{
- return cmd_make_vline(NULL, NULL, string) ;
+#if 0
+ return cmd_tokenize(NULL, string) ;
+#error sort this one out
+#endif
+ return NULL ;
} ;
/*------------------------------------------------------------------------------
@@ -431,2722 +627,569 @@ cmd_make_strvec (const char *string)
* Create vector if required.
*/
extern vector
-cmd_add_to_strvec (vector strvec, const char* str)
+cmd_add_to_strvec (vector items, const char* str)
{
- if (strvec == NULL)
- strvec = vector_init(1) ;
+ if (items == NULL)
+ items = vector_init(1) ;
- vector_push_item(strvec, XSTRDUP(MTYPE_STRVEC, str));
+ vector_push_item(items, XSTRDUP(MTYPE_STRVEC, str));
- return strvec ;
+ return items ;
} ;
/*------------------------------------------------------------------------------
* Free allocated string vector (if any) and all its contents.
*
- * Note that this is perfectly happy with strvec == NULL.
+ * Note that this is perfectly happy with items == NULL.
*/
extern void
-cmd_free_strvec (vector strvec)
+cmd_free_strvec (vector items)
{
char *cp;
- /* Note that vector_ream_free() returns NULL if strvec == NULL */
- while((cp = vector_ream_free(strvec)) != NULL)
+ /* Note that vector_ream_free() returns NULL if items == NULL */
+ while((cp = vector_ream(items, free_it)) != NULL)
XFREE (MTYPE_STRVEC, cp);
} ;
-/*----------------------------------------------------------------------------*/
-
-/* Fetch next description. Used in cmd_make_descvec(). */
-static char *
-cmd_desc_str (const char **string)
-{
- const char *cp, *start;
- char *token;
- int strlen;
-
- cp = *string;
-
- if (cp == NULL)
- return NULL;
-
- /* Skip white spaces. */
- while (isspace ((int) *cp) && *cp != '\0')
- cp++;
-
- /* Return if there is only white spaces */
- if (*cp == '\0')
- return NULL;
-
- start = cp;
-
- while (!(*cp == '\r' || *cp == '\n') && *cp != '\0')
- cp++;
-
- strlen = cp - start;
- token = XMALLOC (MTYPE_STRVEC, strlen + 1);
- memcpy (token, start, strlen);
- *(token + strlen) = '\0';
-
- *string = cp;
-
- return token;
-}
-
-/* New string vector. */
-static vector
-cmd_make_descvec (const char *string, const char *descstr)
-{
- int multiple = 0;
- const char *sp;
- char *token;
- int len;
- const char *cp;
- const char *dp;
- vector allvec;
- vector strvec = NULL;
- struct desc *desc;
-
- cp = string;
- dp = descstr;
-
- if (cp == NULL)
- return NULL;
-
- allvec = vector_init (0);
-
- while (1)
- {
- while (isspace ((int) *cp) && *cp != '\0')
- cp++;
-
- if (*cp == '(')
- {
- multiple = 1;
- cp++;
- }
- if (*cp == ')')
- {
- multiple = 0;
- cp++;
- }
- if (*cp == '|')
- {
- if (! multiple)
- {
- fprintf (stderr, "Command parse error!: %s\n", string);
- exit (1);
- }
- cp++;
- }
-
- while (isspace ((int) *cp) && *cp != '\0')
- cp++;
-
- if (*cp == '(')
- {
- multiple = 1;
- cp++;
- }
-
- if (*cp == '\0')
- return allvec;
-
- sp = cp;
-
- while (! (isspace ((int) *cp) || *cp == '\r' || *cp == '\n' || *cp == ')' || *cp == '|') && *cp != '\0')
- cp++;
-
- len = cp - sp;
-
- token = XMALLOC (MTYPE_STRVEC, len + 1);
- memcpy (token, sp, len);
- *(token + len) = '\0';
-
- desc = XCALLOC (MTYPE_DESC, sizeof (struct desc));
- desc->cmd = token;
- desc->str = cmd_desc_str (&dp);
-
- if (multiple)
- {
- if (multiple == 1)
- {
- strvec = vector_init (0);
- vector_set (allvec, strvec);
- }
- multiple++;
- }
- else
- {
- strvec = vector_init (0);
- vector_set (allvec, strvec);
- }
- vector_set (strvec, desc);
- }
-}
-
-/* Count mandantory string vector size. This is to determine inputed
- command has enough command length. */
-static int
-cmd_cmdsize (vector strvec)
-{
- unsigned int i;
- int size = 0;
-
- for (i = 0; i < vector_length(strvec); i++)
- {
- 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;
-}
+#endif
-/* Return prompt character of specified node. */
-const char *
-cmd_prompt (enum node_type node)
+/*------------------------------------------------------------------------------
+ * Return prompt string for the specified node.
+ */
+extern const char *
+cmd_prompt(node_type_t node)
{
- struct cmd_node *cnode;
+ struct cmd_node *cn ;
- assert(cmdvec != NULL) ;
- assert(cmdvec->p_items != NULL) ;
+ assert(node_vector != NULL) ;
+ assert(node_vector->p_items != NULL) ;
- cnode = NULL ;
- if (node < cmdvec->limit)
- cnode = vector_get_item (cmdvec, node);
+ cn = NULL ;
+ if (node < node_vector->limit)
+ cn = vector_get_item (node_vector, node);
- if (cnode == NULL)
+ if (cn == NULL)
{
zlog_err("Could not find prompt for node %d for", node) ;
return NULL ;
} ;
- return cnode->prompt;
+ return cn->prompt;
}
-/* Install a command into a node. */
-void
-install_element (enum node_type ntype, struct cmd_element *cmd)
+/*------------------------------------------------------------------------------
+ * Install a command into a node.
+ *
+ */
+extern void
+install_element(node_type_t ntype, cmd_command cmd)
{
- struct cmd_node *cnode;
-
- /* cmd_init hasn't been called */
- if (!cmdvec)
- return;
+ cmd_node cn ;
- cnode = vector_get_item (cmdvec, ntype);
+ cn = vector_get_item (node_vector, ntype);
- if (cnode == NULL)
+ if (cn == NULL)
{
fprintf (stderr, "Command node %d doesn't exist, please check it\n",
ntype);
exit (1);
}
- vector_set (cnode->cmd_vector, cmd);
+ vector_set (cn->cmd_vector, cmd);
- if (cmd->strvec == NULL)
- cmd->strvec = cmd_make_descvec (cmd->string, cmd->doc);
+ /* A cmd_command may appear in a number of cmd_vectors, but the cmd->items
+ * etc. need only be set up once.
+ *
+ * It is assumed that once a cmd_command has been installed it will never be
+ * changed !
+ *
+ * Need now to "compile" the command if not already compiled.
+ */
+ if (cmd->items == NULL)
+ cmd_compile(cmd);
- cmd->cmdsize = cmd_cmdsize (cmd->strvec);
-}
+ /* Post compilation check for reasonable cmd_command ! */
+ cmd_compile_check(cmd) ;
+} ;
+/*==============================================================================
+ * Password encryption
+ */
static const unsigned char itoa64[] =
-"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-static void
-to64(char *s, long v, int n)
-{
- while (--n >= 0)
- {
- *s++ = itoa64[v&0x3f];
- v >>= 6;
- }
-}
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-static char *
+/* Uses the usual crypt() function.
+ *
+ * Note that crypt() is not thread safe !
+ */
+static const char *
zencrypt (const char *passwd)
{
- char salt[6];
- struct timeval tv;
- char *crypt (const char *, const char *);
+ uint32_t r ;
+ char salt[3];
+
+ extern char *crypt (const char *, const char *) ;
- gettimeofday(&tv,0);
+ r = qt_random(*passwd) ;
- to64(&salt[0], random(), 3);
- to64(&salt[3], tv.tv_usec, 3);
- salt[5] = '\0';
+ salt[0] = itoa64[(r >> (32 - 5)) & 0x3F] ; /* ms 5 */
+ salt[1] = itoa64[(r >> (32 - 10)) & 0x3F] ; /* next ms 5 */
+ salt[2] = '\0';
- return crypt (passwd, salt);
+ return crypt(passwd, salt) ;
}
/* This function write configuration of this host. */
static int
-config_write_host (struct vty *vty)
+config_write_host (vty vty)
{
+ vty_io vio ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ;
+
if (qpthreads_enabled)
- vty_out (vty, "threaded%s", VTY_NEWLINE);
+ uty_out (vio, "threaded\n");
- if (host.name)
- vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE);
+ if (host.name_set)
+ uty_out (vio, "hostname %s\n", host.name);
- if (host.encrypt)
+ if (host.password != NULL)
{
- if (host.password_encrypt)
- vty_out (vty, "password 8 %s%s", host.password_encrypt, VTY_NEWLINE);
- if (host.enable_encrypt)
- vty_out (vty, "enable password 8 %s%s", host.enable_encrypt, VTY_NEWLINE);
- }
- else
+ if (host.password_encrypted)
+ uty_out (vio, "password 8 %s\n", host.password);
+ else
+ uty_out (vio, "password %s\n", host.password);
+ } ;
+
+ if (host.enable != NULL)
{
- if (host.password)
- vty_out (vty, "password %s%s", host.password, VTY_NEWLINE);
- if (host.enable)
- vty_out (vty, "enable password %s%s", host.enable, VTY_NEWLINE);
- }
+ if (host.enable_encrypted)
+ uty_out (vio, "enable password 8 %s\n", host.enable);
+ else
+ uty_out (vio, "enable password %s\n", host.enable);
+ } ;
if (zlog_get_default_lvl(NULL) != LOG_DEBUG)
{
- vty_out (vty, "! N.B. The 'log trap' command is deprecated.%s",
- VTY_NEWLINE);
- vty_out (vty, "log trap %s%s",
- zlog_priority[zlog_get_default_lvl(NULL)], VTY_NEWLINE);
+ uty_out (vio, "! N.B. The 'log trap' command is deprecated.\n");
+ uty_out (vio, "log trap %s\n", zlog_priority[zlog_get_default_lvl(NULL)]);
}
if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED))
{
- vty_out (vty, "log file %s", host.logfile);
+ uty_out (vio, "log file %s", qpath_string(host.logfile));
if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED)
{
- vty_out (vty, "log stdout");
+ uty_out (vio, "log stdout");
if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED)
- vty_out(vty,"no log monitor%s",VTY_NEWLINE);
+ uty_out(vio,"no log monitor%s",VTY_NEWLINE);
else if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) != zlog_get_default_lvl(NULL))
- vty_out(vty,"log monitor %s%s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)],VTY_NEWLINE);
+ uty_out(vio,"log monitor %s\n",
+ zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]);
if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED)
{
- vty_out (vty, "log syslog");
+ uty_out (vio, "log syslog");
if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_facility(NULL) != LOG_DAEMON)
- vty_out (vty, "log facility %s%s",
- facility_name(zlog_get_facility(NULL)), VTY_NEWLINE);
+ uty_out (vio, "log facility %s\n", facility_name(zlog_get_facility(NULL)));
if (zlog_get_record_priority(NULL) == 1)
- vty_out (vty, "log record-priority%s", VTY_NEWLINE);
+ uty_out (vio, "log record-priority\n");
if (zlog_get_timestamp_precision(NULL) > 0)
- vty_out (vty, "log timestamp precision %d%s",
- zlog_get_timestamp_precision(NULL), VTY_NEWLINE);
+ uty_out (vio, "log timestamp precision %d\n",
+ zlog_get_timestamp_precision(NULL));
if (host.advanced)
- vty_out (vty, "service advanced-vty%s", VTY_NEWLINE);
+ uty_out (vio, "service advanced-vty\n");
if (host.encrypt)
- vty_out (vty, "service password-encryption%s", VTY_NEWLINE);
+ uty_out (vio, "service password-encryption\n");
if (host.lines >= 0)
- vty_out (vty, "service terminal-length %d%s", host.lines,
- VTY_NEWLINE);
+ uty_out (vio, "service terminal-length %d\n", host.lines);
- if (host.motdfile)
- vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE);
+ if (host.motdfile)
+ uty_out (vio, "banner motd file %s\n", qpath_string(host.motdfile));
else if (! host.motd)
- vty_out (vty, "no banner motd%s", VTY_NEWLINE);
+ uty_out (vio, "no banner motd\n");
- return 1;
-}
-
-/* Utility function for getting command vector. */
-static vector
-cmd_node_vector (vector v, enum node_type ntype)
-{
- struct cmd_node *cnode = vector_get_item (v, ntype);
- return cnode->cmd_vector;
-}
-
-#if 0
-/* Filter command vector by symbol. This function is not actually used;
- * should it be deleted? */
-static int
-cmd_filter_by_symbol (char *command, char *symbol)
-{
- int i, lim;
+ VTY_UNLOCK() ;
- if (strcmp (symbol, "IPV4_ADDRESS") == 0)
- {
- i = 0;
- lim = strlen (command);
- while (i < lim)
- {
- if (! (isdigit ((int) command[i]) || command[i] == '.' || command[i] == '/'))
- return 1;
- i++;
- }
- return 0;
- }
- if (strcmp (symbol, "STRING") == 0)
- {
- i = 0;
- lim = strlen (command);
- while (i < lim)
- {
- if (! (isalpha ((int) command[i]) || command[i] == '_' || command[i] == '-'))
- return 1;
- i++;
- }
- return 0;
- }
- if (strcmp (symbol, "IFNAME") == 0)
- {
- i = 0;
- lim = strlen (command);
- while (i < lim)
- {
- if (! isalnum ((int) command[i]))
- return 1;
- i++;
- }
- return 0;
- }
- return 0;
+ return 1;
}
-#endif
/*==============================================================================
- * Match functions.
+ * Commands and other stuff related to:
*
- * Is the given string a, possibly incomplete, value of the required kind ?
- */
-
-/* Completion match types. */
-enum match_type
-{
- no_match, /* nope */
- extend_match,
-
- ipv4_prefix_match,
- ipv4_match,
- ipv6_prefix_match,
- ipv6_match,
- range_match,
- vararg_match,
-
- partly_match, /* OK as far as it went */
- exact_match /* Syntactically complete */
-};
-
-/*------------------------------------------------------------------------------
- * Is this an IPv4 Address:
+ * * end (and ^Z) -- go to ENABLE_NODE (aka Privileged Exec) if above that,
+ * otherwise do nothing.
*
- * 999.999.999.999 -- where no part may be > 255
+ * This is installed in all nodes.
*
- * TODO: cmd_ipv4_match() seems to accept leading '.' ?
- * TODO: cmd_ipv4_match() seems to accept leading zeros ?
+ * * exit -- go to parent node, if in CONFIG_NODE or any of its
+ * sub-nodes.
*
- * Returns: no_match -- improperly formed
- * partly_match -- accepts empty string
- * exact_match -- syntactically complete
- */
-static enum match_type
-cmd_ipv4_match (const char *str)
-{
- const char *sp;
- int dots = 0, nums = 0;
- char buf[4];
-
- if (str == NULL)
- return partly_match;
-
- for (;;)
- {
- memset (buf, 0, sizeof (buf));
- sp = str;
- while (*str != '\0')
- {
- if (*str == '.')
- {
- if (dots >= 3)
- return no_match;
-
- if (*(str + 1) == '.')
- return no_match;
-
- if (*(str + 1) == '\0')
- return partly_match;
-
- dots++;
- break;
- }
- if (!isdigit ((int) *str))
- return no_match;
-
- str++;
- }
-
- if (str - sp > 3)
- return no_match;
-
- strncpy (buf, sp, str - sp);
- if (atoi (buf) > 255)
- return no_match;
-
- nums++;
-
- if (*str == '\0')
- break;
-
- str++;
- }
-
- if (nums < 4)
- return partly_match;
-
- return exact_match;
-}
-
-/*------------------------------------------------------------------------------
- * Is this an IPv4 Prefix:
+ * Parent of CONFIG_NODE is ENABLE_NODE.
*
- * 999.999.999.999/99 -- where no part may be > 255,
- * and prefix length may not be > 32
+ * For all other nodes, this is EOF (which for the
+ * terminal is "close").
*
- * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ?
- * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ?
+ * This is installed in all nodes.
*
- * Returns: no_match -- improperly formed
- * partly_match -- accepts empty string
- * exact_match -- syntactically complete
+ * * enable -- go to ENABLE_NODE, if can.
*
- * NB: partly_match is returned for anything valid before the '/', but which
- * has no '/' or no number after the '/'.
- */
-static enum match_type
-cmd_ipv4_prefix_match (const char *str)
-{
- const char *sp;
- int dots = 0;
- char buf[4];
-
- if (str == NULL)
- return partly_match;
-
- for (;;)
- {
- memset (buf, 0, sizeof (buf));
- sp = str;
- while (*str != '\0' && *str != '/')
- {
- if (*str == '.')
- {
- if (dots == 3)
- return no_match;
-
- if (*(str + 1) == '.' || *(str + 1) == '/')
- return no_match;
-
- if (*(str + 1) == '\0')
- return partly_match;
-
- dots++;
- break;
- }
-
- if (!isdigit ((int) *str))
- return no_match;
-
- str++;
- }
-
- if (str - sp > 3)
- return no_match;
-
- strncpy (buf, sp, str - sp);
- if (atoi (buf) > 255)
- return no_match;
-
- if (dots == 3)
- {
- if (*str == '/')
- {
- if (*(str + 1) == '\0')
- return partly_match;
-
- str++;
- break;
- }
- else if (*str == '\0')
- return partly_match;
- }
-
- if (*str == '\0')
- return partly_match;
-
- str++;
- }
-
- sp = str;
- while (*str != '\0')
- {
- if (!isdigit ((int) *str))
- return no_match;
-
- str++;
- }
-
- if (atoi (sp) > 32)
- return no_match;
-
- return exact_match;
-}
-
-/*------------------------------------------------------------------------------
- * Is this an IPv6 Address:
+ * This is installed in VIEW_NODE and RESTRICTED_NODE.
*
- * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ?
+ * It is also installed in ENABLE_NODE (and hence is
+ * available anywhere), where it is a synonym for 'end' !
*
- * Returns: no_match -- improperly formed
- * partly_match -- accepts empty string
- * exact_match -- syntactically complete
- */
-
-#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%"
-#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/"
-#define STATE_START 1
-#define STATE_COLON 2
-#define STATE_DOUBLE 3
-#define STATE_ADDR 4
-#define STATE_DOT 5
-#define STATE_SLASH 6
-#define STATE_MASK 7
-
-#ifdef HAVE_IPV6
-
-static enum match_type
-cmd_ipv6_match (const char *str)
-{
- int state = STATE_START;
- int colons = 0, nums = 0, double_colon = 0;
- const char *sp = NULL;
- struct sockaddr_in6 sin6_dummy;
- int ret;
-
- if (str == NULL)
- return partly_match;
-
- if (strspn (str, IPV6_ADDR_STR) != strlen (str))
- return no_match;
-
- /* use inet_pton that has a better support,
- * for example inet_pton can support the automatic addresses:
- * ::1.2.3.4
- */
- ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
-
- if (ret == 1)
- return exact_match;
-
- while (*str != '\0')
- {
- switch (state)
- {
- case STATE_START:
- if (*str == ':')
- {
- if (*(str + 1) != ':' && *(str + 1) != '\0')
- return no_match;
- colons--;
- state = STATE_COLON;
- }
- else
- {
- sp = str;
- state = STATE_ADDR;
- }
-
- continue;
- case STATE_COLON:
- colons++;
- if (*(str + 1) == ':')
- state = STATE_DOUBLE;
- else
- {
- sp = str + 1;
- state = STATE_ADDR;
- }
- break;
- case STATE_DOUBLE:
- if (double_colon)
- return no_match;
-
- if (*(str + 1) == ':')
- return no_match;
- else
- {
- if (*(str + 1) != '\0')
- colons++;
- sp = str + 1;
- state = STATE_ADDR;
- }
-
- double_colon++;
- nums++;
- break;
- case STATE_ADDR:
- if (*(str + 1) == ':' || *(str + 1) == '\0')
- {
- if (str - sp > 3)
- return no_match;
-
- nums++;
- state = STATE_COLON;
- }
- if (*(str + 1) == '.')
- state = STATE_DOT;
- break;
- case STATE_DOT:
- state = STATE_ADDR;
- break;
- default:
- break;
- }
-
- if (nums > 8)
- return no_match;
-
- if (colons > 7)
- return no_match;
-
- str++;
- }
-
-#if 0
- if (nums < 11)
- return partly_match;
-#endif /* 0 */
-
- return exact_match;
-}
-
-/*------------------------------------------------------------------------------
- * Is this an IPv6 Prefix:
+ * For configuration reading (VTY_CONFIG_READ) and for
+ * VTY_SHELL_SERVER, no password is required.
*
- * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ?
- * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ?
+ * For VTY_TERMINAL, must already have authenticated
+ * once, or must be able to enter AUTH_ENABLE_NODE.
*
- * Returns: no_match -- improperly formed
- * partly_match -- accepts empty string
- * exact_match -- syntactically complete
- *
- * NB: partly_match is returned for anything valid before the '/', but which
- * has no '/' or no number after the '/'.
- */
-static enum match_type
-cmd_ipv6_prefix_match (const char *str)
-{
- int state = STATE_START;
- int colons = 0, nums = 0, double_colon = 0;
- int mask;
- const char *sp = NULL;
- char *endptr = NULL;
-
- if (str == NULL)
- return partly_match;
-
- if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
- return no_match;
-
- while (*str != '\0' && state != STATE_MASK)
- {
- switch (state)
- {
- case STATE_START:
- if (*str == ':')
- {
- if (*(str + 1) != ':' && *(str + 1) != '\0')
- return no_match;
- colons--;
- state = STATE_COLON;
- }
- else
- {
- sp = str;
- state = STATE_ADDR;
- }
-
- continue;
- case STATE_COLON:
- colons++;
- if (*(str + 1) == '/')
- return no_match;
- else if (*(str + 1) == ':')
- state = STATE_DOUBLE;
- else
- {
- sp = str + 1;
- state = STATE_ADDR;
- }
- break;
- case STATE_DOUBLE:
- if (double_colon)
- return no_match;
-
- if (*(str + 1) == ':')
- return no_match;
- else
- {
- if (*(str + 1) != '\0' && *(str + 1) != '/')
- colons++;
- sp = str + 1;
-
- if (*(str + 1) == '/')
- state = STATE_SLASH;
- else
- state = STATE_ADDR;
- }
-
- double_colon++;
- nums += 1;
- break;
- case STATE_ADDR:
- if (*(str + 1) == ':' || *(str + 1) == '.'
- || *(str + 1) == '\0' || *(str + 1) == '/')
- {
- if (str - sp > 3)
- return no_match;
-
- for (; sp <= str; sp++)
- if (*sp == '/')
- return no_match;
-
- nums++;
-
- if (*(str + 1) == ':')
- state = STATE_COLON;
- else if (*(str + 1) == '.')
- state = STATE_DOT;
- else if (*(str + 1) == '/')
- state = STATE_SLASH;
- }
- break;
- case STATE_DOT:
- state = STATE_ADDR;
- break;
- case STATE_SLASH:
- if (*(str + 1) == '\0')
- return partly_match;
-
- state = STATE_MASK;
- break;
- default:
- break;
- }
-
- if (nums > 11)
- return no_match;
-
- if (colons > 7)
- return no_match;
-
- str++;
- }
-
- if (state < STATE_MASK)
- return partly_match;
-
- mask = strtol (str, &endptr, 10);
- if (*endptr != '\0')
- return no_match;
-
- if (mask < 0 || mask > 128)
- return no_match;
-
-/* I don't know why mask < 13 makes command match partly.
- Forgive me to make this comments. I Want to set static default route
- because of lack of function to originate default in ospf6d; sorry
- yasu
- if (mask < 13)
- return partly_match;
-*/
-
- return exact_match;
-}
-
-#endif /* HAVE_IPV6 */
-
-/*------------------------------------------------------------------------------
- * Is this a decimal number in the allowed range:
- *
- * Returns: 1 => OK -- *including* empty string
- * 0 => not a valid number, or not in required range
- * (or invalid range !!)
- */
-
-#define DECIMAL_STRLEN_MAX 10
-
-static int
-cmd_range_match (const char *range, const char *str)
-{
- char *p;
- char buf[DECIMAL_STRLEN_MAX + 1];
- char *endptr = NULL;
- unsigned long min, max, val;
-
- if (str == NULL)
- return 1;
-
- val = strtoul (str, &endptr, 10);
- if (*endptr != '\0')
- return 0;
-
- range++;
- p = strchr (range, '-');
- if (p == NULL)
- return 0;
- if (p - range > DECIMAL_STRLEN_MAX)
- return 0;
- strncpy (buf, range, p - range);
- buf[p - range] = '\0';
- min = strtoul (buf, &endptr, 10);
- if (*endptr != '\0')
- return 0;
-
- range = p + 1;
- p = strchr (range, '>');
- if (p == NULL)
- return 0;
- if (p - range > DECIMAL_STRLEN_MAX)
- return 0;
- strncpy (buf, range, p - range);
- buf[p - range] = '\0';
- max = strtoul (buf, &endptr, 10);
- if (*endptr != '\0')
- return 0;
-
- if (val < min || val > max)
- return 0;
-
- return 1;
-}
-
-/*==============================================================================
- * Command "filtering".
- *
- * The command parsing process starts with a (shallow) copy of the cmd_vector
- * entry for the current "node".
- *
- * So cmd_v contains pointers to struct cmd_element values. When match fails,
- * the pointer is set NULL -- so parsing is a process of reducing the cmd_v
- * down to just the entries that match.
- *
- * Each cmd_element has a vector "strvec", which contains an entry for each
- * "token" position. That entry is a vector containing the possible values at
- * that position.
+ * * disable -- go to VIEW_NODE (aka User Exec).
*
+ * This is installed in ENABLE_NODE *only*.
*
+ * Note, however, that all ENABLE_NODE commands are
+ * available at ENABLE_NODE and above !
*/
/*------------------------------------------------------------------------------
- * Make completion match and return match type flag.
- *
- * Takes: command -- address of candidate token
- * cmd_v -- vector of commands that is being reduced/filtered
- * index -- index of token (position in line -- 0 == first)
- *
- * Returns: any of the enum match_type values:
+ * Enter CONFIG_NODE, possibly via password check.
*
- * no_match => no match of any kind
+ * If the parser established that can enter CONFIG_NODE directly, that's what
+ * happens.
*
- * extend_match => saw an optional token
- * ipv4_prefix_match )
- * ipv4_match )
- * ipv6_prefix_match ) saw full or partial match for this
- * ipv6_match )
- * range_match )
- * vararg_match )
+ * If the parser established that must authenticate, then may fail here if
+ * we are not in the right state to run the authentication.
*
- * partly_match => saw partial match for a keyword
- * exact_match => saw exact match for a keyword
+ * The authentication itself may fail...
*
- * Note that these return values are in ascending order of preference. So,
- * if there are multiple possibilities at this position, will return the one
- * furthest down this list.
+ * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE.
*/
-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, remove from vector */
- k = 0 ;
- for (i = 0; i < vector_length (cmd_v); i++)
- {
- const char *str;
- struct cmd_element *cmd_element;
- vector descvec;
- struct desc *desc;
- 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 == NULL)
- continue ;
-
- /* Discard cmd_v entry that has no token at the current position */
- descvec = vector_get_item(cmd_element->strvec, index) ;
- if (descvec == NULL)
- continue ;
-
- /* See if get any sort of match at current position */
- 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))
- {
- if (match_type < ipv6_match)
- match_type = ipv6_match;
-
- matched++;
- }
- }
- else if (CMD_IPV6_PREFIX (str))
- {
- if (cmd_ipv6_prefix_match (command))
- {
- if (match_type < ipv6_prefix_match)
- match_type = ipv6_prefix_match;
-
- matched++;
- }
- }
-#endif /* HAVE_IPV6 */
- else if (CMD_IPV4 (str))
- {
- if (cmd_ipv4_match (command))
- {
- if (match_type < ipv4_match)
- match_type = ipv4_match;
-
- 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++;
- } ;
- } ;
-
- /* 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;
+DEFUN_ATTR (config_terminal,
+ config_terminal_cmd,
+ "configure terminal",
+ "Configuration from vty interface\n"
+ "Configuration terminal\n",
+ CMD_ATTR_DIRECT + cmd_sp_configure)
+{
+ if (vty->exec->parsed->nnode == CONFIG_NODE)
+ return vty_cmd_config_lock(vty) ; ;
+
+ /* Otherwise, must authenticate to enter CONFIG_NODE. */
+ return vty_cmd_can_auth_enable(vty) ;
} ;
-/*------------------------------------------------------------------------------
- * Filter vector by command character with index.
- *
- * This appears to be identical to cmd_filter_by_completion(), except that
- * when matching keywords, requires an exact match.
- *
- * TODO: see if can merge cmd_filter_by_completion() & cmd_filter_by_string()
- */
-static enum match_type
-cmd_filter_by_string (char *command, vector cmd_v, unsigned int index)
-{
- unsigned int i ;
- unsigned int k ;
- enum match_type match_type;
-
- match_type = no_match;
-
- /* If command and cmd_element string do match, keep in vector */
- k = 0 ;
- for (i = 0; i < vector_length(cmd_v); i++)
- {
- unsigned int j;
- int matched ;
- const char *str;
- struct cmd_element *cmd_element;
- vector descvec;
- struct desc *desc;
-
- cmd_element = vector_get_item(cmd_v, i) ;
-
- /* Skip past NULL cmd_v entries (just in case) */
- if (cmd_element == NULL)
- continue ;
-
- /* Discard cmd_v entry that has no token at the current position */
- descvec = vector_get_item (cmd_element->strvec, index) ;
- if (descvec == NULL)
- continue ;
-
- /* See if have a match against any of the current possibilities */
- matched = 0 ;
- for (j = 0; j < vector_length(descvec); j++)
- {
- desc = vector_get_item (descvec, j) ;
- if (desc == NULL)
- continue ;
-
- str = desc->cmd;
-
- if (CMD_VARARG (str))
- {
- if (match_type < vararg_match)
- match_type = vararg_match;
- matched++;
- }
- else if (CMD_RANGE (str))
- {
- if (cmd_range_match (str, command))
- {
- if (match_type < range_match)
- match_type = range_match;
- matched++;
- }
- }
-#ifdef HAVE_IPV6
- else if (CMD_IPV6 (str))
- {
- if (cmd_ipv6_match (command) == exact_match)
- {
- if (match_type < ipv6_match)
- match_type = ipv6_match;
- matched++;
- }
- }
- else if (CMD_IPV6_PREFIX (str))
- {
- if (cmd_ipv6_prefix_match (command) == exact_match)
- {
- if (match_type < ipv6_prefix_match)
- match_type = ipv6_prefix_match;
- matched++;
- }
- }
-#endif /* HAVE_IPV6 */
- else if (CMD_IPV4 (str))
- {
- if (cmd_ipv4_match (command) == exact_match)
- {
- if (match_type < ipv4_match)
- match_type = ipv4_match;
- matched++;
- }
- }
- else if (CMD_IPV4_PREFIX (str))
- {
- if (cmd_ipv4_prefix_match (command) == exact_match)
- {
- 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;
-}
+ALIAS_ATTR (config_terminal,
+ config_enable_configure_cmd,
+ "enable configure",
+ "Turn on privileged mode command\n"
+ "Configuration terminal\n",
+ CMD_ATTR_DIRECT + cmd_sp_configure)
/*------------------------------------------------------------------------------
- * Check for ambiguous match
- *
- * Given the best that cmd_filter_by_completion() or cmd_filter_by_string()
- * found, check as follows:
- *
- * 1. discard all commands for which do not have the type of match selected.
+ * Enter ENABLE_NODE, possibly via password check.
*
- * See above for the ranking of matches.
+ * If the parser established that can enter ENABLE_NODE directly, that's what
+ * happens.
*
- * 2. for "partial match", look out for matching more than one keyword, and
- * return 1 if finds that.
+ * If the parser established that must authenticate, then may fail here if
+ * we are not in the right state to run the authentication.
*
- * 3. for "range match", look out for matching more than one range, and
- * return 1 if finds that.
+ * The authentication itself may fail...
*
- * 4. for ipv4_prefix_match and ipv6_prefix_match, if get a "partial match",
- * return 2.
- *
- * This appears to catch things which are supposed to be prefixes, but
- * do not have a '/' or do not have any digits after the '/'.
- *
- * Takes: command -- address of candidate token
- * cmd_v -- vector of commands that is being reduced/filtered
- * index -- index of token (position in line -- 0 == first)
- * type -- as returned by cmd_filter_by_completion()
- * or cmd_filter_by_string()
- *
- * Returns: 0 => not ambiguous
- * 1 => ambiguous -- the candidate token matches more than one
- * keyword, or the candidate number matches more
- * than one number range.
- * 2 => partial match for ipv4_prefix or ipv6_prefix
- * (missing '/' or no digits after '/').
- *
- * NB: it is assumed that cannot find both 1 and 2 states. But in any case,
- * returns 1 in preference.
+ * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE.
*/
-static int
-is_cmd_ambiguous (char *command, vector cmd_v, int index, enum match_type type)
+DEFUN_ATTR (enable,
+ config_enable_cmd,
+ "enable",
+ "Turn on privileged mode command\n",
+ CMD_ATTR_DIRECT + cmd_sp_enable)
{
- unsigned int i;
- unsigned int k;
- int ret ;
-
- ret = 0 ; /* all's well so far */
- k = 0 ; /* nothing kept, yet */
-
- 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 ;
-
- cmd_element = vector_get_item (cmd_v, i) ;
-
- /* 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;
-
-#ifdef HAVE_IPV6
- case ipv6_match:
- if (CMD_IPV6 (str))
- matched++;
- 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))
- 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. */
+ if (vty->exec->parsed->nnode == ENABLE_NODE)
+ return CMD_SUCCESS ;
- 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 ;
+ /* Otherwise, must authenticate to enter ENABLE_NODE. */
+ return vty_cmd_can_auth_enable(vty) ;
} ;
/*------------------------------------------------------------------------------
- * If src matches dst return dst string, otherwise return NULL
- *
- * Returns NULL if dst is an option, variable of vararg.
+ * disable command: end enabled state -> VIEW_NODE.
*
- * NULL or empty src are deemed to match.
+ * NB: although only installed in ENABLE_NODE, it will be implicitly available
+ * in all higher nodes -- as a quick way of crashing out to VIEW_NODE !
*/
-static const char *
-cmd_entry_function (const char *src, const char *dst)
+DEFUN_ATTR(disable,
+ config_disable_cmd,
+ "disable",
+ "Turn off privileged mode command\n",
+ CMD_ATTR_DIRECT + CMD_ATTR_NODE + VIEW_NODE)
{
- if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst))
- return NULL;
-
- if ((src == NULL) || (*src == '\0'))
- return dst;
-
- if (strncmp (src, dst, strlen (src)) == 0)
- return dst;
-
- return NULL;
-}
-
-/* If src matches dst return dst string, otherwise return NULL */
-/* This version will return the dst string always if it is
- CMD_VARIABLE for '?' key processing */
-static const char *
-cmd_entry_function_desc (const char *src, const char *dst)
-{
- if (CMD_VARARG (dst))
- return dst;
-
- if (CMD_RANGE (dst))
- {
- if (cmd_range_match (dst, src))
- return dst;
- else
- return NULL;
- }
-
-#ifdef HAVE_IPV6
- if (CMD_IPV6 (dst))
- {
- if (cmd_ipv6_match (src))
- return dst;
- else
- return NULL;
- }
-
- if (CMD_IPV6_PREFIX (dst))
- {
- if (cmd_ipv6_prefix_match (src))
- return dst;
- else
- return NULL;
- }
-#endif /* HAVE_IPV6 */
-
- if (CMD_IPV4 (dst))
- {
- if (cmd_ipv4_match (src))
- return dst;
- else
- return NULL;
- }
-
- if (CMD_IPV4_PREFIX (dst))
- {
- if (cmd_ipv4_prefix_match (src))
- return dst;
- else
- return NULL;
- }
-
- /* Optional or variable commands always match on '?' */
- if (CMD_OPTION (dst) || CMD_VARIABLE (dst))
- return dst;
-
- /* In case of 'command \t', given src is NULL string. */
- if (src == NULL)
- return dst;
-
- if (strncmp (src, dst, strlen (src)) == 0)
- return dst;
- else
- return NULL;
+ return CMD_SUCCESS ; /* will disable to parsed->nnode */
}
/*------------------------------------------------------------------------------
- * Check same string element existence.
- *
- * Returns: 0 => found same string in the vector
- * 1 => NOT found same string in the vector
+ * exit command: down one node level, including exit command processor.
*/
-static bool
-cmd_unique_string (vector v, const char *str)
-{
- unsigned int i;
- char *match;
-
- 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;
-}
-
-/* Compare string to description vector. If there is same string
- return 1 else return 0. */
-static bool
-desc_unique_string (vector v, const char *str)
+DEFUN_ATTR(config_exit,
+ config_exit_cmd,
+ "exit",
+ "Exit current mode and down to previous mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_exit)
{
- unsigned int i;
- struct desc *desc;
-
- 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 bool
-cmd_try_do_shortcut (enum node_type node, char* first_word) {
- return (node >= MIN_DO_SHORTCUT_NODE)
- && (first_word != NULL)
- && (strcmp( "do", first_word) == 0) ? 1 : 0 ;
+ return CMD_SUCCESS ; /* will exit to parsed->nnode */
}
-/* '?' describe command support. */
-static vector
-cmd_describe_command_real (vector vline, int node, int *status)
-{
- unsigned int i;
- vector cmd_vector;
-#define INIT_MATCHVEC_SIZE 10
- vector matchvec;
- struct cmd_element *cmd_element;
- unsigned int index;
- int ret;
- enum match_type match;
- char *command;
-
- /* Set index. */
- if (vector_length (vline) == 0)
- {
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
- else
- index = vector_length (vline) - 1;
-
- /* Make copy vector of current node's command vector. */
- cmd_vector = vector_copy (cmd_node_vector (cmdvec, node));
-
- /* Prepare match vector */
- matchvec = vector_init (INIT_MATCHVEC_SIZE);
-
- /* Filter commands. */
- /* Only words precedes current word will be checked in this loop. */
- for (i = 0; i < index; i++)
- if ((command = vector_get_item (vline, i)))
- {
- match = cmd_filter_by_completion (command, cmd_vector, i);
-
- if (match == vararg_match)
- {
- struct cmd_element *cmd_element;
- vector descvec;
- unsigned int j, k;
-
- for (j = 0; j < vector_length (cmd_vector); j++)
- if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL
- && (vector_length (cmd_element->strvec)))
- {
- descvec = vector_get_item (cmd_element->strvec,
- vector_length (cmd_element->strvec) - 1);
- for (k = 0; k < vector_length (descvec); k++)
- {
- struct desc *desc = vector_get_item (descvec, k);
- vector_set (matchvec, desc);
- }
- }
-
- vector_set (matchvec, &desc_cr);
- vector_free (cmd_vector);
-
- return matchvec;
- }
-
- if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1)
- {
- vector_free (cmd_vector);
- vector_free (matchvec);
- *status = CMD_ERR_AMBIGUOUS;
- return NULL;
- }
- else if (ret == 2)
- {
- vector_free (cmd_vector);
- vector_free (matchvec);
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
- }
-
- /* Prepare match vector */
- /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */
-
- /* Make sure that cmd_vector is filtered based on current word */
- command = vector_get_item (vline, index);
- if (command)
- match = cmd_filter_by_completion (command, cmd_vector, index);
-
- /* Make description vector. */
- for (i = 0; i < vector_length (cmd_vector); i++)
- {
- vector strvec ;
-
- cmd_element = vector_get_item (cmd_vector, i) ;
- if (cmd_element == NULL)
- continue ;
-
- /* Ignore cmd_element if no tokens at index position.
- *
- * Deal with special case of possible <cr> completion.
- */
- strvec = cmd_element->strvec;
- if (index >= vector_length (strvec))
- {
- if (command == NULL && index == vector_length (strvec))
- {
- 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);
- }
- } ;
- } ;
-
- vector_free (cmd_vector);
-
- if (vector_length(matchvec) == 0)
- {
- vector_free (matchvec);
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
-
- *status = CMD_SUCCESS;
- return matchvec;
-}
+/* quit is alias of exit. */
+ALIAS_ATTR (config_exit,
+ config_quit_cmd,
+ "quit",
+ "Exit current mode and down to previous mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_exit) ;
/*------------------------------------------------------------------------------
- * Get description of current (partial) command
- *
- * Returns: NULL => no description available
- *
- * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS
- *
- * or: address of vector of "struct desc" values available.
- *
- * NB: when a vector is returned it is the caller's responsibility to
- * vector_free() it. (The contents are all effectively const, so do not
- * themselves need to be freed.)
+ * end command: down to enable mode.
*/
-vector
-cmd_describe_command (vector vline, int node, int *status)
+DEFUN_ATTR (config_end,
+ config_end_cmd,
+ "end",
+ "End current mode and change to enable mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_end)
{
- vector ret;
-
- if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) )
- {
- vector shifted_vline;
- unsigned int index;
-
- /* We can try it on enable node, cos' the vty is authenticated */
-
- shifted_vline = vector_init (vector_count(vline));
- /* use memcpy? */
- for (index = 1; index < vector_length (vline); index++)
- {
- vector_set_index (shifted_vline, index-1, vector_lookup(vline, index));
- }
-
- ret = cmd_describe_command_real (shifted_vline, ENABLE_NODE, status);
-
- vector_free(shifted_vline);
- return ret;
- }
-
- return cmd_describe_command_real (vline, node, status);
+ return CMD_SUCCESS ; /* will end to parsed->nnode */
}
-/*------------------------------------------------------------------------------
- * Check LCD of matched command.
- *
- * Scan list of matched keywords, and by comparing them pair-wise, find the
- * longest common leading substring.
- *
- * Returns: 0 if zero or one matched keywords
- * length of longest common leading substring, otherwise.
- */
-static int
-cmd_lcd (vector matchvec)
-{
- int n ;
- int i ;
- int lcd ;
- char *sp, *sq, *ss ;
-
- n = vector_end(matchvec) ;
- if (n < 2)
- return 0 ;
-
- ss = vector_get_item(matchvec, 0) ;
- lcd = strlen(ss) ;
-
- for (i = 1 ; i < n ; i++)
- {
- sq = ss ;
- ss = vector_get_item(matchvec, i) ;
- sp = ss ;
-
- while ((*sp == *sq) && (*sp != '\0'))
- {
- ++sp ;
- ++sq ;
- } ;
-
- if (lcd > (sp - ss))
- lcd = (sp - ss) ;
- }
- return lcd;
-}
-
-/*------------------------------------------------------------------------------
- * Command line completion support.
- */
-static vector
-cmd_complete_command_real (vector vline, int node, int *status)
-{
- unsigned int i;
- unsigned int ivl ;
- unsigned int last_ivl ;
- vector cmd_v ;
-#define INIT_MATCHVEC_SIZE 10
- vector matchvec;
- struct cmd_element *cmd_element;
- unsigned int index;
- struct desc *desc;
- vector descvec;
- char *token;
- int n ;
-
- /* Stop immediately if the vline is empty. */
- if (vector_length (vline) == 0)
- {
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
-
- /* Take (shallow) copy of cmdvec for given node. */
- cmd_v = vector_copy (cmd_node_vector (cmdvec, node));
-
- /* First, filter upto, but excluding last token */
- last_ivl = vector_length (vline) - 1;
-
- for (ivl = 0; ivl < last_ivl; ivl++)
- {
- enum match_type match;
- int ret;
-
- /* TODO: does this test make any sense ? */
- if ((token = vector_get_item (vline, ivl)) == NULL)
- continue ;
-
- /* First try completion match, return best kind of match */
- index = ivl ;
- match = cmd_filter_by_completion (token, cmd_v, index) ;
-
- /* Eliminate all but the selected kind of match */
- ret = is_cmd_ambiguous (token, cmd_v, index, match) ;
-
- if (ret == 1)
- {
- /* ret == 1 => either token matches more than one keyword
- * or token matches more than one number range
- */
- vector_free (cmd_v);
- *status = CMD_ERR_AMBIGUOUS;
- return NULL;
- }
-#if 0
- /* For command completion purposes do not appear to care about
- * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after).
- */
- else if (ret == 2)
- {
- vector_free (cmd_v);
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
-#endif
- }
-
- /* Prepare match vector. */
- matchvec = vector_init (INIT_MATCHVEC_SIZE);
-
- /* Now we got into completion */
- index = last_ivl ;
- token = vector_get_item(vline, last_ivl) ; /* is now the last token */
-
- for (i = 0; i < vector_length (cmd_v); i++)
- {
- unsigned int j;
- const char *string;
-
- if ((cmd_element = vector_get_item (cmd_v, i)) == NULL)
- continue ;
-
- descvec = vector_get_item (cmd_element->strvec, index);
- if (descvec == NULL)
- continue ;
-
- for (j = 0; j < vector_length (descvec); j++)
- {
- desc = vector_get_item (descvec, j) ;
- if (desc == NULL)
- continue ;
-
- string = cmd_entry_function(token, desc->cmd) ;
- if ((string != NULL) && cmd_unique_string(matchvec, string))
- cmd_add_to_strvec (matchvec, string) ;
- } ;
- } ;
-
- n = vector_length(matchvec) ; /* number of entries in the matchvec */
-
- /* We don't need cmd_v any more. */
- vector_free (cmd_v);
-
- /* No matched command */
- if (n == 0)
- {
- vector_free (matchvec);
-
- /* In case of 'command \t' pattern. Do you need '?' command at
- the end of the line. */
- if (*token == '\0')
- *status = CMD_COMPLETE_ALREADY;
- else
- *status = CMD_ERR_NO_MATCH;
- return NULL;
- }
-
- /* Only one matched */
- if (n == 1)
- {
- *status = CMD_COMPLETE_FULL_MATCH;
- return matchvec ;
- }
-
- /* Check LCD of matched strings. */
- if (token != NULL)
- {
- unsigned lcd = cmd_lcd (matchvec) ;
-
- if (lcd != 0)
- {
- if (strlen(token) < lcd)
- {
- char *lcdstr;
-
- lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1);
- memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ;
- lcdstr[lcd] = '\0';
-
- cmd_free_strvec(matchvec) ; /* discard the match vector */
-
- matchvec = vector_init (1);
- vector_push_item(matchvec, lcdstr) ;
-
- *status = CMD_COMPLETE_MATCH;
- return matchvec ;
- }
- }
- }
-
- *status = CMD_COMPLETE_LIST_MATCH;
- return matchvec ;
-}
-
-/*------------------------------------------------------------------------------
- * Can the current command be completed ?
- */
-extern vector
-cmd_complete_command (vector vline, int node, int *status)
-{
- vector ret;
-
- if ( cmd_try_do_shortcut(node, vector_get_item(vline, 0) ) )
- {
- vector shifted_vline;
- unsigned int index;
-
- /* We can try it on enable node, cos' the vty is authenticated */
-
- shifted_vline = vector_init (vector_count(vline));
- /* use memcpy? */
- for (index = 1; index < vector_length (vline); index++)
- {
- vector_set_index (shifted_vline, index-1, vector_lookup(vline, index));
- }
-
- ret = cmd_complete_command_real (shifted_vline, ENABLE_NODE, status);
-
- vector_free(shifted_vline);
- return ret;
- }
-
- return cmd_complete_command_real (vline, node, status);
-}
-
-/*------------------------------------------------------------------------------
- * Return parent node
- *
- * All nodes > CONFIG_NODE are descended from CONFIG_NODE
- */
-enum node_type
-node_parent ( enum node_type node )
-{
- assert (node > CONFIG_NODE);
-
- switch (node)
- {
- case BGP_VPNV4_NODE:
- case BGP_IPV4_NODE:
- case BGP_IPV4M_NODE:
- case BGP_IPV6_NODE:
- case BGP_IPV6M_NODE:
- return BGP_NODE;
-
- case KEYCHAIN_KEY_NODE:
- return KEYCHAIN_NODE;
-
- default:
- return CONFIG_NODE;
- }
-}
-
-/*------------------------------------------------------------------------------
- * Initialise a new struct cmd_parsed, allocating if required
- */
-extern cmd_parsed
-cmd_parse_init_new(cmd_parsed parsed)
-{
- if (parsed == NULL)
- parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ;
- else
- memset(parsed, 0, sizeof(*parsed)) ;
-
- /* Zeroising the structure has set:
- *
- * cmd = NULL -- no command parsed, yet
- * cnode -- no node set, yet
- *
- * do_shortcut -- false
- * onode -- not material (do_shortcut is false)
- *
- * line = zeroised qstring -- empty
- * words = zeroised qstring -- empty
- *
- * vline = zeroised vector -- empty
- *
- * so nothing else to do
- */
-
- return parsed ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Initialise a new struct cmd_parsed, allocating if required
- */
-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 '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.
- *
- * 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 )
- *
- * 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 command line MUST be preserved until the parsed command is no
- * longer required -- no copy is made.
- *
- * 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.
- *
- * See elsewhere for description of parsed structure.
- */
-extern enum cmd_return_code
-cmd_parse_command(struct vty* vty, enum cmd_parse_type type)
-{
- enum cmd_return_code ret ;
- enum cmd_return_code first_ret ;
- cmd_parsed parsed ;
-
- /* 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;
- enum match_type match = 0;
- int varflag;
- char *command;
-
- /* Need length of vline, discounting the first entry if required */
- first = parsed->do_shortcut ? 1 : 0 ;
-
- 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 */
- for (index = 0 ; index < ivl; index++)
- {
- int ret ;
-
- command = vector_get_item(&parsed->vline, index + first) ;
- if (command == NULL)
- continue ;
-
- match = strict ? cmd_filter_by_string(command, cmd_v, index)
- : cmd_filter_by_completion(command, cmd_v, index) ;
-
- if (match == vararg_match)
- break;
-
- ret = is_cmd_ambiguous (command, cmd_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_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);
-#endif
- matched_count++;
- }
- else
- {
- incomplete_count++;
- }
- } ;
-
- /* Finished with cmd_v. */
- vector_free (cmd_v);
-
- /* To execute command, matched_count must be 1. */
- if (matched_count != 1)
- {
- if (matched_count == 0)
- return (incomplete_count) ? CMD_ERR_INCOMPLETE : CMD_ERR_NO_MATCH ;
- else
- return CMD_ERR_AMBIGUOUS ;
- } ;
-
- /* Found command -- process the arguments ready for execution */
- varflag = 0 ;
- argc = 0 ;
-
- for (index = 0; index < ivl ; index++)
- {
- int take = varflag ;
-
- if (!varflag)
- {
- vector descvec = vector_get_item (matched_element->strvec, index);
-
- if (vector_length (descvec) == 1)
- {
- struct desc *desc = vector_get_item (descvec, 0);
-
- if (CMD_VARARG (desc->cmd))
- take = varflag = 1 ;
- else
- take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ;
- }
- else
- take = 1 ;
- }
-
- if (take)
- vector_assign_item(&parsed->vline, argc++, index + first) ;
- } ;
-
- vector_set_length(&parsed->vline, argc) ; /* set to new length */
-
- /* Everything checks out... ready to execute command */
- parsed->cmd = matched_element ;
-
- return CMD_SUCCESS ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Dispatch a parsed command.
- *
- * Returns: command return code. NB: may be CMD_QUEUED (unless no_queue).
- *
- * 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 enum cmd_return_code
-cmd_dispatch(struct vty* vty, bool no_queue)
-{
- cmd_parsed parsed = vty->parsed ;
- enum cmd_return_code ret ;
-
- if (parsed->cmd == NULL)
- return CMD_SUCCESS ; /* NULL commands are easy */
-
- vty->node = parsed->cnode ;
-
- if (no_queue || !vty_cli_nexus)
- {
- ret = cmd_dispatch_call(vty) ;
- cmd_post_command(vty, ret) ;
- }
- else
- {
- /* 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 ret ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Tidy up after executing command.
- *
- * This is separated out so that can be called when queued command completes.
- *
- * If have just processed a "do" shortcut command, and it has not set the
- * vty->node to something other than ENABLE_NODE, then restore to the original
- * state.
- *
- * Arguments: ret = CMD_XXXX -- NB: CMD_QUEUED => command revoked
- */
-extern void
-cmd_post_command(struct vty* vty, int ret)
-{
- if (vty->parsed->do_shortcut)
- {
- if (vty->node == ENABLE_NODE)
- vty->node = vty->parsed->onode ;
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Parse and execute a command.
- *
- * The command is given by vty->buf and vty->node.
- *
- * Uses vty->parsed.
- *
- * -- use strict/completion parsing, as required.
- *
- * -- 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, 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 enum cmd_return_code
-cmd_execute_command(struct vty *vty,
- enum cmd_parse_type type, struct cmd_element **cmd)
-{
- enum cmd_return_code ret ;
-
- /* Try to parse in vty->node or, if required, ancestors thereof. */
- ret = cmd_parse_command(vty, type) ;
-
- if (cmd != NULL)
- *cmd = vty->parsed->cmd ; /* for vtysh */
-
- if (ret == CMD_SUCCESS)
- ret = cmd_dispatch(vty, 0) ;
- else if (ret == CMD_EMPTY)
- ret = CMD_SUCCESS ;
-
- return ret ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Read configuration from file.
- *
- * In the qpthreads world this assumes that it is running with the vty
- * locked, and that all commands are to be executed directly.
- *
- * 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.
- *
- * 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)
- *
- * If the file is empty, will return CMD_SUCCESS.
- *
- * Never returns CMD_EMPTY -- that counts as CMD_SUCCESS.
- *
- * If
- *
- * If return code is not CMD_SUCCESS, the the output buffering contains the
- * output from the last command attempted.
- */
-extern enum cmd_return_code
-config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd,
- qstring buf, bool ignore_warning)
-{
- enum cmd_return_code ret;
-
- vty->buf = buf->body ;
- vty->lineno = 0 ;
-
- ret = CMD_SUCCESS ; /* in case file is empty */
- vty_out_clear(vty) ;
-
- while (fgets (buf->body, buf->size, fp))
- {
- ++vty->lineno ;
-
- /* Execute configuration command : this is strict match */
- ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ;
-
- if (ret == CMD_EMPTY)
- continue ; /* skip empty/comment */
-
- if (ret != CMD_SUCCESS)
- break ; /* stop on *any* parsing issue */
-
- /* special handling before of first command */
- if (first_cmd != NULL)
- {
- if (first_cmd != vty->parsed->cmd)
- {
- ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ;
- if (ret != CMD_SUCCESS)
- break ; /* stop on *any* issue with "default" */
- } ;
- first_cmd = NULL ;
- }
-
- /* 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 */
- } ;
-
- vty_out_clear(vty) ;
- } ;
-
- if (ret == CMD_EMPTY)
- ret = CMD_SUCCESS ; /* OK if end on empty line */
-
- return ret ;
-} ;
-
-/*----------------------------------------------------------------------------*/
-
-/* Configration from terminal */
-DEFUN_CALL (config_terminal,
- config_terminal_cmd,
- "configure terminal",
- "Configuration from vty interface\n"
- "Configuration terminal\n")
-{
- if (vty_config_lock (vty, CONFIG_NODE))
- return CMD_SUCCESS;
-
- vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE);
- return CMD_WARNING;
-}
-
-/* Enable command */
-DEFUN_CALL (enable,
- config_enable_cmd,
- "enable",
- "Turn on privileged mode command\n")
-{
- /* If enable password is NULL, change to ENABLE_NODE */
- if ((host.enable == NULL && host.enable_encrypt == NULL) ||
- vty_shell_serv(vty))
- vty_set_node(vty, ENABLE_NODE);
- else
- vty_set_node(vty, AUTH_ENABLE_NODE);
-
- return CMD_SUCCESS;
-}
-
-/* Disable command */
-DEFUN_CALL (disable,
- config_disable_cmd,
- "disable",
- "Turn off privileged mode command\n")
-{
- if (vty_get_node(vty) == ENABLE_NODE)
- vty_set_node(vty, VIEW_NODE);
- return CMD_SUCCESS;
-}
-
-/* Down vty node level. */
-DEFUN_CALL (config_exit,
- config_exit_cmd,
- "exit",
- "Exit current mode and down to previous mode\n")
+/* Show version. */
+DEFUN_CALL (show_version,
+ show_version_cmd,
+ "show version",
+ SHOW_STR
+ "Displays zebra version\n")
{
- return vty_cmd_exit(vty) ;
-}
+ VTY_LOCK() ;
-/* quit is alias of exit. */
-ALIAS_CALL (config_exit,
- config_quit_cmd,
- "quit",
- "Exit current mode and down to previous mode\n")
-
-/* End of configuration. */
-DEFUN_CALL (config_end,
- config_end_cmd,
- "end",
- "End current mode and change to enable mode.")
-{
- return vty_cmd_end(vty) ;
-}
+ uty_out (vty->vio, "Quagga %s (%s).\n", QUAGGA_VERSION,
+ (host.name != NULL) ? host.name : "") ;
+ uty_out (vty->vio, "%s\n", QUAGGA_COPYRIGHT);
-/* Show version. */
-DEFUN_CALL (show_version,
- show_version_cmd,
- "show version",
- SHOW_STR
- "Displays zebra version\n")
-{
- vty_out (vty, "Quagga %s (%s).%s", QUAGGA_VERSION, host.name?host.name:"",
- VTY_NEWLINE);
- vty_out (vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE);
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
-/* Help display function for all node. */
+/* Help display function for all node. */
DEFUN_CALL (config_help,
config_help_cmd,
"help",
"Description of the interactive help system\n")
{
vty_out (vty,
- "Quagga VTY provides advanced help feature. When you need help,%s\
-anytime at the command line please press '?'.%s\
-%s\
-If nothing matches, the help list will be empty and you must backup%s\
- until entering a '?' shows the available options.%s\
-Two styles of help are provided:%s\
-1. Full help is available when you are ready to enter a%s\
-command argument (e.g. 'show ?') and describes each possible%s\
-argument.%s\
-2. Partial help is provided when an abbreviated argument is entered%s\
- and you want to know what arguments match the input%s\
- (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ "Quagga VTY provides advanced help feature. When you need help,\n"
+ "anytime at the command line please press '?'.\n"
+ "\n"
+ "If nothing matches, the help list will be empty and you must backup\n"
+ "until entering a '?' shows the available options.\n"
+ "Two styles of help are provided:\n"
+ " 1. Full help is available when you are ready to enter a\n"
+ " command argument (e.g. 'show ?') and describes each possible\n"
+ " argument.\n"
+ " 2. Partial help is provided when an abbreviated argument is entered\n"
+ " and you want to know what arguments match the input\n"
+ " (e.g. 'show me?'.)\n"
+ "\n") ;
return CMD_SUCCESS;
}
-/* Help display function for all node. */
+/* Help display function for all node. */
DEFUN_CALL (config_list,
- config_list_cmd,
- "list",
- "Print command list\n")
+ config_list_cmd,
+ "list",
+ "Print command list\n")
{
unsigned int i;
- struct cmd_node *cnode = vector_get_item (cmdvec, vty_get_node(vty));
- struct cmd_element *cmd;
-
- 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);
+ struct cmd_node *cn ;
+ struct cmd_command *cmd;
+
+ cn = vector_get_item (node_vector, vty->node);
+
+ for (i = 0; i < vector_length (cn->cmd_vector); i++)
+ if ( ((cmd = vector_get_item (cn->cmd_vector, i)) != NULL)
+ && ((cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)) == 0) )
+ vty_out (vty, " %s\n", cmd->string);
+
return CMD_SUCCESS;
}
-/* Write current configuration into file. */
+/* Write current configuration into file. */
DEFUN (config_write_file,
config_write_file_cmd,
"write file",
"Write running configuration to memory, network, or terminal\n"
"Write to configuration file\n")
{
- 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;
-
- /* Check and see if we are operating under vtysh configuration */
- if (host.config == NULL)
+ qpath path ;
+ qpath temp ;
+ qpath save ;
+ cmd_return_code_t ret, retw ;
+ unsigned int i ;
+ int fd, err ;
+ struct cmd_node *cn;
+ const char *config_name ;
+ const char *save_name ;
+ char* temp_name ;
+
+ err = 0 ; /* so far, so good */
+
+ VTY_LOCK() ;
+ path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ;
+ VTY_UNLOCK() ;
+
+ /* Check and see if we are operating under vtysh configuration */
+ if (path == NULL)
{
- vty_out (vty, "Can't save to configuration file, using vtysh.%s",
- VTY_NEWLINE);
+ vty_out (vty, "%% Cannot save to configuration file, using vtysh.\n");
return CMD_WARNING;
}
- /* Get filename. */
- config_file = host.config;
-
- config_file_sav =
- XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1);
- strcpy (config_file_sav, config_file);
- strcat (config_file_sav, CONF_BACKUP_EXT);
+ /* Set up the file names. */
+ config_name = qpath_string(path) ;
+ save = qpath_dup(path) ;
+ qpath_extend_str(save, CONF_BACKUP_EXT) ;
+ save_name = qpath_string(save) ;
- config_file_tmp = XMALLOC (MTYPE_TMP, strlen (config_file) + 8);
- sprintf (config_file_tmp, "%s.XXXXXX", config_file);
+ temp = qpath_dup(path) ;
+ qpath_extend_str(temp, ".XXXXXX") ;
+ temp_name = qpath_char_string(temp) ;
- /* Open file to configuration write. */
- fd = mkstemp (config_file_tmp);
+ /* Open file to configuration write. */
+ fd = mkstemp (temp_name);
if (fd < 0)
{
- vty_out (vty, "Can't open configuration file %s.%s", config_file_tmp,
- VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't open configuration file %s", temp_name) ;
goto finished;
}
- /* Make vty for configuration file. */
- vty_open_config_write(vty, fd) ;
+ /* Make vty for configuration file. */
+ vty_config_write_open(vty, fd) ;
- /* Config file header print. */
vty_out (vty, "!\n! Zebra configuration saved from vty\n! ");
vty_time_print (vty, 1);
vty_out (vty, "!\n");
- for (i = 0; i < vector_length (cmdvec); i++)
- if ((node = vector_get_item (cmdvec, i)) && node->func)
- {
- if ((*node->func) (vty))
- vty_out (vty, "!\n");
- }
+ retw = CMD_SUCCESS ;
+
+ for (i = 0; i < vector_length (node_vector); i++)
+ {
+ if ((cn = vector_get_item (node_vector, i)) && cn->config_write)
+ {
+ if ((*cn->config_write) (vty))
+ vty_out (vty, "!\n");
- err = vty_close_config_write(vty) ;
- close(fd) ;
+ retw = vty_cmd_out_push(vty) ; /* Push stuff so far */
- if (err != 0)
+ if (retw != CMD_SUCCESS)
+ break ;
+ } ;
+ } ;
+
+ ret = vty_config_write_close(vty) ;
+
+ if ((ret != CMD_SUCCESS) || (retw != CMD_SUCCESS))
{
- vty_out (vty, "Failed while writing configuration file %s.%s",
- config_file_tmp, VTY_NEWLINE);
+ vty_out (vty, "%% Failed while writing configuration file %s.\n",
+ temp_name) ;
goto finished;
}
- if (unlink (config_file_sav) != 0)
+ /* Now move files around to make .sav and the real file */
+ ret = CMD_WARNING ;
+
+ if (unlink (save_name) != 0)
if (errno != ENOENT)
{
- vty_out (vty, "Can't unlink backup configuration file %s.%s",
- config_file_sav, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't unlink backup configuration file %s",
+ save_name) ;
goto finished;
} ;
- if (link (config_file, config_file_sav) != 0)
+ if (link (config_name, save_name) != 0)
{
- vty_out (vty, "Can't backup old configuration file %s.%s",
- config_file_sav, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't backup old configuration file %s", config_name) ;
goto finished;
} ;
sync () ;
- if (unlink (config_file) != 0)
+ if (unlink (config_name) != 0)
{
- vty_out (vty, "Can't unlink configuration file %s.%s",
- config_file, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't unlink configuration file %s", config_name);
goto finished;
} ;
- if (link (config_file_tmp, config_file) != 0)
+ if (link (temp_name, config_name) != 0)
{
- vty_out (vty, "Can't save configuration file %s.%s",
- config_file, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't save configuration file %s", config_name);
goto finished;
} ;
sync ();
- if (chmod (config_file, CONFIGFILE_MASK) != 0)
+ if (chmod (config_name, CONFIGFILE_MASK) != 0)
{
- vty_out (vty, "Can't chmod configuration file %s: %s (%s).\n",
- config_file, errtostr(errno, 0).str, errtoname(errno, 0).str);
+ err = errno ;
+ vty_out (vty, "%% Can't chmod configuration file %s", config_name) ;
goto finished;
}
- vty_out (vty, "Configuration saved to %s\n", config_file);
+ vty_out (vty, "Configuration saved to %s\n", config_name) ;
ret = CMD_SUCCESS;
finished:
- unlink (config_file_tmp);
- XFREE (MTYPE_TMP, config_file_tmp);
- XFREE (MTYPE_TMP, config_file_sav);
+ if (err != 0)
+ vty_out(vty, ": %s (%s).\n", errtostr(errno, 0).str,
+ errtoname(errno, 0).str) ;
+ if (fd >= 0)
+ unlink (temp_name);
+
+ qpath_free(temp) ;
+ qpath_free(save) ;
+ qpath_free(path) ;
+
return ret;
-}
+} ;
ALIAS (config_write_file,
config_write_cmd,
@@ -3166,7 +1209,7 @@ ALIAS (config_write_file,
"Copy running config to... \n"
"Copy running config to startup config (same as write file)\n")
-/* Write current configuration into the terminal. */
+/* Write current configuration into the terminal. */
DEFUN (config_write_terminal,
config_write_terminal_cmd,
"write terminal",
@@ -3174,76 +1217,71 @@ DEFUN (config_write_terminal,
"Write to terminal\n")
{
unsigned int i;
- struct cmd_node *node;
+ bool vtysh_config ;
- if (vty_shell_serv(vty))
- {
- 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);
- }
- }
- else
+ vtysh_config = (vty->type == VTY_SHELL_SERVER) ;
+
+ if (!vtysh_config)
+ vty_out (vty, "\n"
+ "Current configuration:\n"
+ "!\n") ;
+
+ for (i = 0 ; i < vector_length(node_vector) ; i++)
{
- vty_out (vty, "%sCurrent configuration:%s", VTY_NEWLINE,
- VTY_NEWLINE);
- vty_out (vty, "!%s", VTY_NEWLINE);
-
- 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);
- }
- vty_out (vty, "end%s",VTY_NEWLINE);
- }
+ cmd_node node ;
+
+ node = vector_get_item(node_vector, i) ;
+
+ if (node == NULL)
+ continue ;
+
+ if (vtysh_config && !node->config_to_vtysh)
+ continue ;
+
+ if ((*node->config_write != NULL) && ((*node->config_write)(vty) != 0))
+ vty_out (vty, "!\n") ;
+ } ;
+
+ if (!vtysh_config)
+ vty_out (vty, "end\n") ;
+
return CMD_SUCCESS;
}
-/* Write current configuration into the terminal. */
+/* Write current configuration into the terminal. */
ALIAS (config_write_terminal,
show_running_config_cmd,
"show running-config",
SHOW_STR
"running configuration\n")
-/* Write startup configuration into the terminal. */
+/* Write startup configuration into the terminal. */
DEFUN (show_startup_config,
show_startup_config_cmd,
"show startup-config",
SHOW_STR
- "Contentes of startup configuration\n")
+ "Contents of startup configuration\n")
{
- char buf[BUFSIZ];
- FILE *confp;
+ cmd_return_code_t ret ;
+ qpath path ;
- confp = fopen (host.config, "r");
- if (confp == NULL)
- {
- vty_out (vty, "Can't open configuration file [%s]%s",
- host.config, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ VTY_LOCK() ;
+ path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ;
+ VTY_UNLOCK() ;
- while (fgets (buf, BUFSIZ, confp))
+ if (path == NULL)
{
- char *cp = buf;
-
- while (*cp != '\r' && *cp != '\n' && *cp != '\0')
- cp++;
- *cp = '\0';
-
- vty_out (vty, "%s%s", buf, VTY_NEWLINE);
- }
+ vty_out (vty, "%% Cannot show configuration file, using vtysh.\n");
+ return CMD_WARNING;
+ } ;
- fclose (confp);
+ ret = vty_cat_file(vty, path, "configuration file") ;
+ qpath_free(path) ;
- return CMD_SUCCESS;
+ return ret ;
}
-/* Hostname configuration */
+/* Hostname configuration */
DEFUN_CALL (config_hostname,
hostname_cmd,
"hostname WORD",
@@ -3252,21 +1290,11 @@ DEFUN_CALL (config_hostname,
{
if (!isalpha((int) *argv[0]))
{
- vty_out (vty, "Please specify string starting with alphabet%s", VTY_NEWLINE);
+ vty_out (vty, "Please specify string starting with alphabet\n");
return CMD_WARNING;
}
- VTY_LOCK() ;
-
- if (host.name)
- XFREE (MTYPE_HOST, host.name);
-
- host.name = XSTRDUP (MTYPE_HOST, argv[0]);
- uty_set_host_name(host.name) ;
-
- VTY_UNLOCK() ;
-
- return CMD_SUCCESS;
+ return cmd_set_host_name(argv[0]) ;
}
DEFUN_CALL (config_no_hostname,
@@ -3276,26 +1304,18 @@ DEFUN_CALL (config_no_hostname,
"Reset system's network name\n"
"Host name of this router\n")
{
- VTY_LOCK() ;
-
- if (host.name)
- XFREE (MTYPE_HOST, host.name);
- host.name = NULL;
- uty_set_host_name(host.name) ;
-
- VTY_UNLOCK() ;
-
- return CMD_SUCCESS;
+ return cmd_set_host_name(NULL) ;
}
-/* VTY interface password set. */
-DEFUN_CALL (config_password, password_cmd,
- "password (8|) WORD",
- "Assign the terminal connection password\n"
- "Specifies a HIDDEN password will follow\n"
- "dummy string \n"
- "The HIDDEN line password string\n")
+/*------------------------------------------------------------------------------
+ * Password setting function -- common for password and enable password.
+ */
+static cmd_return_code_t
+do_set_password(vty vty, int argc, argv_t argv, char** p_password,
+ bool* p_encrypted)
{
+ cmd_return_code_t ret ;
+
/* Argument check. */
if (argc == 0)
{
@@ -3303,47 +1323,63 @@ DEFUN_CALL (config_password, password_cmd,
return CMD_WARNING;
}
+ VTY_LOCK() ;
+ ret = CMD_SUCCESS ;
+
if (argc == 2)
{
+ /* Encrypted password argument */
+
if (*argv[0] == '8')
- {
- if (host.password)
- XFREE (MTYPE_HOST, host.password);
- host.password = NULL;
- if (host.password_encrypt)
- XFREE (MTYPE_HOST, host.password_encrypt);
- host.password_encrypt = XSTRDUP (MTYPE_HOST, argv[1]);
- return CMD_SUCCESS;
- }
+ {
+ XFREE(MTYPE_HOST, *p_password);
+ *p_encrypted = true ;
+ *p_password = XSTRDUP(MTYPE_HOST, argv[1]);
+ }
else
- {
- vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ {
+ vty_out(vty, "Unknown encryption type.\n");
+ ret = CMD_WARNING;
+ }
}
-
- if (!isalnum ((int) *argv[0]))
+ else
{
- vty_out (vty,
- "Please specify string starting with alphanumeric%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ /* Plaintext password argument */
- if (host.password)
- XFREE (MTYPE_HOST, host.password);
- host.password = NULL;
+ if (!isalnum ((int) *argv[0]))
+ {
+ vty_out(vty, "Please specify string starting with alphanumeric\n");
+ ret = CMD_WARNING ;
+ }
+ else
+ {
+ /* If host.encrypt, only keeps the encrypted password. */
- if (host.encrypt)
- {
- if (host.password_encrypt)
- XFREE (MTYPE_HOST, host.password_encrypt);
- host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0]));
- }
- else
- host.password = XSTRDUP (MTYPE_HOST, argv[0]);
+ XFREE (MTYPE_HOST, *p_password);
- return CMD_SUCCESS;
-}
+ *p_encrypted = host.encrypt ;
+ if (*p_encrypted)
+ *p_password = XSTRDUP (MTYPE_HOST, zencrypt (argv[0]));
+ else
+ *p_password = XSTRDUP (MTYPE_HOST, argv[0]);
+ } ;
+ } ;
+
+ VTY_UNLOCK() ;
+ return ret ;
+} ;
+
+/* VTY interface password set. */
+DEFUN_CALL (config_password, password_cmd,
+ "password (8) WORD",
+ "Assign the terminal connection password\n"
+ "Specifies a HIDDEN password will follow\n"
+ "dummy string \n"
+ "The HIDDEN line password string\n")
+{
+ return do_set_password(vty, argc, argv, &host.password,
+ &host.password_encrypted) ;
+} ;
ALIAS_CALL (config_password, password_text_cmd,
"password LINE",
@@ -3352,65 +1388,16 @@ ALIAS_CALL (config_password, password_text_cmd,
/* VTY enable password set. */
DEFUN_CALL (config_enable_password, enable_password_cmd,
- "enable password (8|) WORD",
+ "enable password (8) WORD",
"Modify enable password parameters\n"
"Assign the privileged level password\n"
"Specifies a HIDDEN password will follow\n"
"dummy string \n"
"The HIDDEN 'enable' password string\n")
{
- /* Argument check. */
- if (argc == 0)
- {
- vty_out (vty, "Please specify password.%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- /* Crypt type is specified. */
- if (argc == 2)
- {
- if (*argv[0] == '8')
- {
- if (host.enable)
- XFREE (MTYPE_HOST, host.enable);
- host.enable = NULL;
-
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- host.enable_encrypt = XSTRDUP (MTYPE_HOST, argv[1]);
-
- return CMD_SUCCESS;
- }
- else
- {
- vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- }
-
- if (!isalnum ((int) *argv[0]))
- {
- vty_out (vty,
- "Please specify string starting with alphanumeric%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- if (host.enable)
- XFREE (MTYPE_HOST, host.enable);
- host.enable = NULL;
-
- /* Plain password input. */
- if (host.encrypt)
- {
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0]));
- }
- else
- host.enable = XSTRDUP (MTYPE_HOST, argv[0]);
-
- return CMD_SUCCESS;
-}
+ return do_set_password(vty, argc, argv, &host.enable,
+ &host.enable_encrypted) ;
+} ;
ALIAS_CALL (config_enable_password,
enable_password_text_cmd,
@@ -3426,14 +1413,12 @@ DEFUN_CALL (no_config_enable_password, no_enable_password_cmd,
"Modify enable password parameters\n"
"Assign the privileged level password\n")
{
- if (host.enable)
- XFREE (MTYPE_HOST, host.enable);
- host.enable = NULL;
+ VTY_LOCK() ;
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- host.enable_encrypt = NULL;
+ host.enable_encrypted = false ;
+ XFREE (MTYPE_HOST, host.enable);
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -3443,24 +1428,30 @@ DEFUN_CALL (service_password_encrypt,
"Set up miscellaneous service\n"
"Enable encrypted passwords\n")
{
- if (host.encrypt)
- return CMD_SUCCESS;
+ VTY_LOCK() ;
- host.encrypt = 1;
+ host.encrypt = true ;
- if (host.password)
+ /* If we have an unencrypted password in hand, convert that now.
+ * If not, retain any already encrypted password.
+ */
+ if (!host.password_encrypted && (host.password != NULL))
{
- if (host.password_encrypt)
- XFREE (MTYPE_HOST, host.password_encrypt);
- host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.password));
- }
- if (host.enable)
+ char* plain = host.password ;
+ host.password = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ;
+ XFREE(MTYPE_HOST, plain) ;
+ host.password_encrypted = true ;
+ } ;
+
+ if (!host.enable_encrypted && (host.enable != NULL))
{
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.enable));
- }
+ char* plain = host.enable ;
+ host.enable = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ;
+ XFREE(MTYPE_HOST, plain) ;
+ host.enable_encrypted = true ;
+ } ;
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -3471,19 +1462,13 @@ DEFUN_CALL (no_service_password_encrypt,
"Set up miscellaneous service\n"
"Enable encrypted passwords\n")
{
- if (! host.encrypt)
- return CMD_SUCCESS;
-
- host.encrypt = 0;
+ VTY_LOCK() ;
- if (host.password_encrypt)
- XFREE (MTYPE_HOST, host.password_encrypt);
- host.password_encrypt = NULL;
+ /* Keep any existing passwords, encrypted or not. */
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- host.enable_encrypt = NULL;
+ host.encrypt = false ;
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -3517,6 +1502,17 @@ DEFUN_CALL (config_terminal_no_length, config_terminal_no_length_cmd,
return CMD_SUCCESS;
}
+static cmd_return_code_t
+set_host_lines(int lines)
+{
+ VTY_LOCK() ;
+
+ host.lines = lines ;
+
+ VTY_UNLOCK() ;
+ return CMD_SUCCESS ;
+} ;
+
DEFUN_CALL (service_terminal_length, service_terminal_length_cmd,
"service terminal-length <0-512>",
"Set up miscellaneous service\n"
@@ -3532,10 +1528,9 @@ DEFUN_CALL (service_terminal_length, service_terminal_length_cmd,
vty_out (vty, "length is malformed%s", VTY_NEWLINE);
return CMD_WARNING;
}
- host.lines = lines;
- return CMD_SUCCESS;
-}
+ return set_host_lines(lines) ;
+} ;
DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd,
"no service terminal-length [<0-512>]",
@@ -3544,8 +1539,7 @@ DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd,
"System wide terminal length configuration\n"
"Number of lines of VTY (0 means no line control)\n")
{
- host.lines = -1;
- return CMD_SUCCESS;
+ return set_host_lines(-1) ;
}
DEFUN_HID_CALL (do_echo,
@@ -3556,13 +1550,25 @@ DEFUN_HID_CALL (do_echo,
{
char *message;
- vty_out (vty, "%s%s", ((message = argv_concat(argv, argc, 0)) ? message : ""),
- VTY_NEWLINE);
- if (message)
- XFREE(MTYPE_TMP, message);
+ message = argv_concat(argv, argc, 0) ;
+ vty_out (vty, "%s\n", message ? message : "") ;
+ XFREE(MTYPE_TMP, message);
+
return CMD_SUCCESS;
}
+/*==============================================================================
+ * Logging configuration.
+ *
+ * Each VTY has its own logging level for monitor logging, and its own
+ * enable/disable. When logging is enabled, the current monitor logging
+ * level is set for the VTY.
+ *
+ * The monitor logging level is a bit special -- setting this level affects
+ * the current VTY (if it is a VTY_TERMINAL) and any future VTY. It also
+ * affects the level which will be written away to any configuration file.
+ */
+
DEFUN_CALL (config_logmsg,
config_logmsg_cmd,
"logmsg "LOG_LEVELS" .MESSAGE",
@@ -3578,8 +1584,7 @@ DEFUN_CALL (config_logmsg,
message = argv_concat(argv, argc, 1);
zlog(NULL, level, "%s", (message ? message : ""));
- if (message)
- XFREE(MTYPE_TMP, message);
+ XFREE(MTYPE_TMP, message);
return CMD_SUCCESS;
}
@@ -3589,51 +1594,43 @@ DEFUN_CALL (show_logging,
SHOW_STR
"Show current logging configuration\n")
{
+ int lvl ;
+
vty_out (vty, "Syslog logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s, facility %s, ident %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)],
- facility_name(zlog_get_facility(NULL)), zlog_get_ident(NULL));
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s, facility %s, ident %s\n", zlog_priority[lvl],
+ facility_name(zlog_get_facility(NULL)), zlog_get_ident(NULL)) ;
vty_out (vty, "Stdout logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s\n", zlog_priority[lvl]) ;
vty_out (vty, "Monitor logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s\n", zlog_priority[lvl]);
vty_out (vty, "File logging: ");
- if ((zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) == ZLOG_DISABLED) ||
- !zlog_is_file(NULL))
- vty_out (vty, "disabled");
+ if (((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)) == ZLOG_DISABLED) ||
+ !zlog_is_file(NULL))
+ vty_out (vty, "disabled\n");
else
{
char * filename = zlog_get_filename(NULL);
- vty_out (vty, "level %s, filename %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)],
- filename);
+ vty_out (vty, "level %s, filename %s\n", zlog_priority[lvl], filename) ;
free(filename);
}
- vty_out (vty, "%s", VTY_NEWLINE);
- vty_out (vty, "Protocol name: %s%s",
- zlog_get_proto_name(NULL), VTY_NEWLINE);
- vty_out (vty, "Record priority: %s%s",
- (zlog_get_record_priority(NULL) ? "enabled" : "disabled"), VTY_NEWLINE);
- vty_out (vty, "Timestamp precision: %d%s",
- zlog_get_timestamp_precision(NULL), VTY_NEWLINE);
+ vty_out (vty, "Protocol name: %s\n", zlog_get_proto_name(NULL));
+ vty_out (vty, "Record priority: %s\n",
+ (zlog_get_record_priority(NULL) ? "enabled" : "disabled")) ;
+ vty_out (vty, "Timestamp precision: %d\n",
+ zlog_get_timestamp_precision(NULL)) ;
return CMD_SUCCESS;
}
@@ -3681,7 +1678,9 @@ DEFUN_CALL (config_log_monitor,
"Logging control\n"
"Set terminal line (monitor) logging level\n")
{
- zlog_set_level (NULL, ZLOG_DEST_MONITOR, zlog_get_default_lvl(NULL));
+ int level = zlog_get_default_lvl(NULL) ;
+ zlog_set_level (NULL, ZLOG_DEST_MONITOR, level) ;
+ vty_set_monitor_level(vty, level) ;
return CMD_SUCCESS;
}
@@ -3696,7 +1695,9 @@ DEFUN_CALL (config_log_monitor_level,
if ((level = level_match(argv[0])) == ZLOG_DISABLED)
return CMD_ERR_NO_MATCH;
+
zlog_set_level (NULL, ZLOG_DEST_MONITOR, level);
+ vty_set_monitor_level(vty, level) ;
return CMD_SUCCESS;
}
@@ -3709,57 +1710,38 @@ DEFUN_CALL (no_config_log_monitor,
"Logging level\n")
{
zlog_set_level (NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED);
+ vty_set_monitor_level(vty, ZLOG_DISABLED) ;
return CMD_SUCCESS;
}
+/*------------------------------------------------------------------------------
+ * Set new logging file and level -- "log file FILENAME [LEVEL]"
+ *
+ * Note that even if fail to open the new log file, will set host.logfile.
+ *
+ * Failure here is an error.
+ */
static int
set_log_file(struct vty *vty, const char *fname, int loglevel)
{
- int ret;
- char *p = NULL;
- const char *fullpath;
-
- /* Path detection. */
- if (! IS_DIRECTORY_SEP (*fname))
- {
- char cwd[MAXPATHLEN+1];
- cwd[MAXPATHLEN] = '\0';
-
- if (getcwd (cwd, MAXPATHLEN) == NULL)
- {
- zlog_err ("config_log_file: Unable to alloc mem!");
- return CMD_WARNING;
- }
-
- if ( (p = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (fname) + 2))
- == NULL)
- {
- zlog_err ("config_log_file: Unable to alloc mem!");
- return CMD_WARNING;
- }
- sprintf (p, "%s/%s", cwd, fname);
- fullpath = p;
- }
- else
- fullpath = fname;
-
- ret = zlog_set_file (NULL, fullpath, loglevel);
+ int err ;
- if (p)
- XFREE (MTYPE_TMP, p);
+ VTY_LOCK() ;
- if (!ret)
- {
- vty_out (vty, "can't open logfile %s\n", fname);
- return CMD_WARNING;
- }
+ host.logfile = uty_cmd_path_name_complete(host.logfile, fname,
+ vty->exec->context) ;
+ err = zlog_set_file (NULL, qpath_string(host.logfile), loglevel) ;
- if (host.logfile)
- XFREE (MTYPE_HOST, host.logfile);
+ VTY_UNLOCK() ;
- host.logfile = XSTRDUP (MTYPE_HOST, fname);
+ if (err == 0)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS;
+ vty_out(vty, "%% failed to open log file %s: %s (%s)\n",
+ qpath_string(host.logfile),
+ errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ return CMD_WARNING ;
}
DEFUN_CALL (config_log_file,
@@ -3774,7 +1756,7 @@ DEFUN_CALL (config_log_file,
DEFUN_CALL (config_log_file_level,
config_log_file_level_cmd,
- "log file FILENAME "LOG_LEVELS,
+ "log file FILENAME " LOG_LEVELS,
"Logging control\n"
"Logging to file\n"
"Logging filename\n"
@@ -3784,6 +1766,7 @@ DEFUN_CALL (config_log_file_level,
if ((level = level_match(argv[1])) == ZLOG_DISABLED)
return CMD_ERR_NO_MATCH;
+
return set_log_file(vty, argv[0], level);
}
@@ -3795,13 +1778,13 @@ DEFUN_CALL (no_config_log_file,
"Cancel logging to file\n"
"Logging file name\n")
{
- zlog_reset_file (NULL);
+ VTY_LOCK() ;
- if (host.logfile)
- XFREE (MTYPE_HOST, host.logfile);
+ zlog_reset_file (NULL);
- host.logfile = NULL;
+ host.logfile = qpath_free(host.logfile) ;
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -3989,20 +1972,43 @@ DEFUN_CALL (no_config_log_timestamp_precision,
return CMD_SUCCESS;
}
+/*==============================================================================
+ * MOTD commands and set up.
+ *
+ * Note that can set a MOTD file that does not exist at the time. A friendly
+ * message warns about this, but it is not an error. The message will not be
+ * seen while reading the configuration file -- but it is not worth stopping
+ * the configuration file reader for this !
+ */
DEFUN_CALL (banner_motd_file,
banner_motd_file_cmd,
- "banner motd file [FILE]",
+ "banner motd file FILE",
"Set banner\n"
"Banner for motd\n"
"Banner from a file\n"
"Filename\n")
{
- if (host.motdfile)
- XFREE (MTYPE_HOST, host.motdfile);
- host.motdfile = XSTRDUP (MTYPE_HOST, argv[0]);
+ int err ;
- return CMD_SUCCESS;
-}
+ VTY_LOCK() ;
+
+ host.motdfile = uty_cmd_path_name_complete(host.motdfile,
+ argv[0], vty->exec->context) ;
+ err = qpath_stat_is_file(host.motdfile) ;
+
+ if (err != 0)
+ {
+ vty_out(vty, "NB: '%s': ", qpath_string(host.motdfile)) ;
+ if (err < 0)
+ vty_out(vty, "is not a file\n") ;
+ else
+ vty_out(vty, "%s (%s)\n", errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ } ;
+
+ VTY_UNLOCK() ;
+ return CMD_SUCCESS ;
+} ;
DEFUN_CALL (banner_motd_default,
banner_motd_default_cmd,
@@ -4011,7 +2017,11 @@ DEFUN_CALL (banner_motd_default,
"Strings for motd\n"
"Default string\n")
{
- host.motd = default_motd;
+ VTY_LOCK() ;
+
+ host.motd = default_motd ;
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -4022,23 +2032,149 @@ DEFUN_CALL (no_banner_motd,
"Set banner string\n"
"Strings for motd\n")
{
- host.motd = NULL;
- if (host.motdfile)
- XFREE (MTYPE_HOST, host.motdfile);
- host.motdfile = NULL;
+ VTY_LOCK() ;
+
+ host.motd = NULL ;
+ host.motdfile = qpath_free(host.motdfile) ;
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
-/* Set config filename. Called from vty.c */
-void
-host_config_set (char *filename)
+/*------------------------------------------------------------------------------
+ * Set current directory
+ */
+DEFUN_CALL (do_chdir,
+ chdir_cmd,
+ "chdir DIR",
+ "Set current directory\n"
+ "Directory to set\n")
+{
+ cmd_return_code_t ret ;
+ qpath path ;
+ int err ;
+
+ ret = CMD_SUCCESS ;
+
+ path = uty_cmd_path_name_complete(NULL, argv[0], vty->exec->context) ;
+ err = qpath_stat_is_directory(path) ;
+
+ if (err == 0)
+ qpath_copy(vty->exec->context->dir_cd, path) ;
+ else
+ {
+ vty_out(vty, "%% chdir %s: ", qpath_string(path)) ;
+ if (err < 0)
+ vty_out(vty, "is not a directory\n") ;
+ else
+ vty_out(vty, "%s (%s)\n", errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ ret = CMD_WARNING ;
+ } ;
+
+ qpath_free(path) ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show given directory path
+ */
+DEFUN_CALL (do_shdir,
+ shdir_cmd,
+ "shdir DIR",
+ "Show directory\n"
+ "Directory to show\n")
{
- if (host.config)
- XFREE (MTYPE_HOST, host.config);
- host.config = XSTRDUP (MTYPE_HOST, filename);
+ cmd_return_code_t ret ;
+ qpath path ;
+ int err ;
+
+ ret = CMD_SUCCESS ;
+
+ path = uty_cmd_path_name_complete(NULL, argv[0], vty->exec->context) ;
+ err = qpath_stat_is_directory(path) ;
+
+ if (err == 0)
+ vty_out(vty, "%s\n", qpath_string(path)) ;
+ else
+ {
+ vty_out(vty, "%% %s: ", qpath_string(path)) ;
+ if (err < 0)
+ vty_out(vty, "is not a directory\n") ;
+ else
+ vty_out(vty, "%s (%s)\n", errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ ret = CMD_WARNING ;
+ } ;
+
+ qpath_free(path) ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get cwd.
+ *
+ * This is done very early in the morning, before lowering privileges, to
+ * minimise chance of not being able to get the cwd. If cwd itself is not
+ * accessible in lowered privilege state, that will later become clear.
+ *
+ * Sets host.cwd, which is torn down in cmd_terminate().
+ *
+ */
+extern void
+cmd_getcwd(void)
+{
+ host.cwd = qpath_getcwd(NULL) ;
+
+ if (host.cwd == NULL)
+ {
+ fprintf(stderr, "Cannot getcwd()\n") ;
+ exit(1) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set host.config_file and host.config_dir.
+ */
+extern void
+cmd_host_config_set (qpath config_file)
+{
+ VTY_LOCK() ;
+
+ host.config_file = qpath_copy(host.config_file, config_file) ;
+ host.config_dir = qpath_copy(host.config_dir, config_file) ;
+
+ qpath_shave(host.config_dir) ;
+
+ VTY_UNLOCK() ;
}
-void
+/*------------------------------------------------------------------------------
+ * Set the lexical level for further command processing.
+ */
+DEFUN_CALL (lexical_level,
+ lexical_level_cmd,
+ "lexical-level <0-1>",
+ "Set lexical level\n"
+ "The required lexical level\n")
+{
+ int level ;
+
+ level = strtol(argv[0], NULL, 0) ;
+
+ vty->exec->context->full_lex = (level != 0) ;
+
+ return CMD_SUCCESS;
+}
+
+/*==============================================================================
+ * Command handling initialisation and termination.
+ */
+
+/*------------------------------------------------------------------------------
+ * Install copy of the default commands in the given node.
+ */
+extern void
install_default (enum node_type node)
{
install_element (node, &config_exit_cmd);
@@ -4054,26 +2190,33 @@ install_default (enum node_type node)
install_element (node, &show_running_config_cmd);
}
-/* Initialize command interface. Install basic nodes and commands. */
-void
-cmd_init (int terminal)
+/*------------------------------------------------------------------------------
+ * Initialise command handling.
+ *
+ * Install basic nodes and commands. Initialise the host structure.
+ *
+ * Sets srand(time(NULL))
+ */
+extern void
+cmd_init (bool terminal)
{
- command_cr = XSTRDUP(MTYPE_STRVEC, "<cr>");
- desc_cr.cmd = command_cr;
- desc_cr.str = XSTRDUP(MTYPE_STRVEC, "");
-
- /* Allocate initial top vector of commands. */
- cmdvec = vector_init (0);
-
- /* Default host value settings. */
- host.name = NULL;
- host.password = NULL;
- host.enable = NULL;
- host.logfile = NULL;
- host.config = NULL;
- host.lines = -1;
- host.motd = default_motd;
- host.motdfile = NULL;
+ srand(time(NULL)) ;
+
+ cmd_parser_init() ;
+
+ if (host.cwd == NULL) /* in case cmd_cwd() not called, yet */
+ cmd_getcwd() ;
+
+ /* Allocate initial top vector of commands. */
+ node_vector = vector_init(0);
+
+ /* Set default motd */
+ host.motd = default_motd ;
+ host.config_brand = rand() ;
+
+ /* Default host value settings are already set, see above */
+
+ cmd_get_sys_host_name() ; /* start with system name & name_gen == 1 */
/* Install top nodes. */
install_node (&view_node, NULL);
@@ -4092,30 +2235,44 @@ cmd_init (int terminal)
install_element (VIEW_NODE, &config_quit_cmd);
install_element (VIEW_NODE, &config_help_cmd);
install_element (VIEW_NODE, &config_enable_cmd);
+ install_element (VIEW_NODE, &config_enable_configure_cmd);
+ install_element (VIEW_NODE, &config_terminal_cmd);
install_element (VIEW_NODE, &config_terminal_length_cmd);
install_element (VIEW_NODE, &config_terminal_no_length_cmd);
install_element (VIEW_NODE, &show_logging_cmd);
install_element (VIEW_NODE, &echo_cmd);
+ install_element (VIEW_NODE, &chdir_cmd);
+ install_element (VIEW_NODE, &shdir_cmd);
install_element (RESTRICTED_NODE, &config_list_cmd);
install_element (RESTRICTED_NODE, &config_exit_cmd);
install_element (RESTRICTED_NODE, &config_quit_cmd);
install_element (RESTRICTED_NODE, &config_help_cmd);
install_element (RESTRICTED_NODE, &config_enable_cmd);
+ install_element (RESTRICTED_NODE, &config_enable_configure_cmd);
+ install_element (RESTRICTED_NODE, &config_terminal_cmd);
install_element (RESTRICTED_NODE, &config_terminal_length_cmd);
install_element (RESTRICTED_NODE, &config_terminal_no_length_cmd);
install_element (RESTRICTED_NODE, &echo_cmd);
+ install_element (RESTRICTED_NODE, &chdir_cmd);
+ install_element (RESTRICTED_NODE, &shdir_cmd);
}
if (terminal)
{
install_default (ENABLE_NODE);
install_element (ENABLE_NODE, &config_disable_cmd);
+ install_element (ENABLE_NODE, &config_enable_cmd);
+ install_element (ENABLE_NODE, &config_enable_configure_cmd);
install_element (ENABLE_NODE, &config_terminal_cmd);
install_element (ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
}
+
install_element (ENABLE_NODE, &show_startup_config_cmd);
install_element (ENABLE_NODE, &show_version_cmd);
+ install_element (ENABLE_NODE, &lexical_level_cmd);
+ install_element (ENABLE_NODE, &chdir_cmd);
+ install_element (ENABLE_NODE, &shdir_cmd);
if (terminal)
{
@@ -4130,9 +2287,12 @@ cmd_init (int terminal)
install_element (CONFIG_NODE, &hostname_cmd);
install_element (CONFIG_NODE, &no_hostname_cmd);
+ install_element (CONFIG_NODE, &lexical_level_cmd);
if (terminal)
{
+ install_element (CONFIG_NODE, &echo_cmd);
+
install_element (CONFIG_NODE, &password_cmd);
install_element (CONFIG_NODE, &password_text_cmd);
install_element (CONFIG_NODE, &enable_password_cmd);
@@ -4170,97 +2330,71 @@ cmd_init (int terminal)
install_element (CONFIG_NODE, &service_terminal_length_cmd);
install_element (CONFIG_NODE, &no_service_terminal_length_cmd);
+ install_element (RESTRICTED_NODE, &show_thread_cpu_cmd);
install_element (VIEW_NODE, &show_thread_cpu_cmd);
install_element (ENABLE_NODE, &show_thread_cpu_cmd);
- install_element (RESTRICTED_NODE, &show_thread_cpu_cmd);
-
+
install_element (ENABLE_NODE, &clear_thread_cpu_cmd);
install_element (VIEW_NODE, &show_work_queues_cmd);
install_element (ENABLE_NODE, &show_work_queues_cmd);
- }
- srand(time(NULL));
-}
+ } ;
+} ;
+/*------------------------------------------------------------------------------
+ * Close down command interface.
+ *
+ * Dismantle the node_vector and all commands.
+ *
+ * Clear out the host structure.
+ */
void
cmd_terminate ()
{
- unsigned int i, j, k, l;
- struct cmd_node *cmd_node;
- struct cmd_element *cmd_element;
- struct desc *desc;
- vector cmd_node_v, cmd_element_v, desc_v;
+ cmd_node cmd_node;
+ cmd_command cmd ;
- if (cmdvec)
+ /* Ream out the vector of command nodes. */
+ while ((cmd_node = vector_ream(node_vector, free_it)) != NULL)
{
- for (i = 0; i < vector_length (cmdvec); i++)
+ /* Ream out the (embedded) vector of commands per node. */
+ while ((cmd = vector_ream(cmd_node->cmd_vector, keep_it)) != NULL)
{
- cmd_node = vector_get_item (cmdvec, i) ;
- if (cmd_node == NULL)
- continue ;
-
- cmd_node_v = cmd_node->cmd_vector;
+ /* Ream out the vector of items for each command.
+ *
+ * Note that each cmd is a static structure, which may appear in
+ * more than one cmd_vector -- but the "compiled" portions are
+ * dynamically allocated.
+ */
+ cmd_item next_item ;
- for (j = 0; j < vector_length (cmd_node_v); j++)
+ while ((next_item = vector_ream(cmd->items, free_it)) != NULL)
{
- 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++)
+ do
{
- 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);
+ cmd_item item ;
+ item = next_item ;
+ next_item = item->next ;
+ XFREE(MTYPE_CMD_ITEM, item);
+ }
+ while (next_item != NULL) ;
} ;
- vector_free (cmd_node_v);
- } ;
+ cmd->items = NULL ; /* gone */
- vector_free (cmdvec);
- cmdvec = NULL;
+ XFREE (MTYPE_CMD_STRING, cmd->r_doc) ;
+ } ;
}
- if (command_cr)
- XFREE(MTYPE_STRVEC, command_cr);
- if (desc_cr.str)
- XFREE(MTYPE_STRVEC, desc_cr.str);
- if (host.name)
- XFREE (MTYPE_HOST, host.name);
- if (host.password)
- XFREE (MTYPE_HOST, host.password);
- if (host.password_encrypt)
- XFREE (MTYPE_HOST, host.password_encrypt);
- if (host.enable)
- XFREE (MTYPE_HOST, host.enable);
- if (host.enable_encrypt)
- XFREE (MTYPE_HOST, host.enable_encrypt);
- if (host.logfile)
- XFREE (MTYPE_HOST, host.logfile);
- if (host.motdfile)
- XFREE (MTYPE_HOST, host.motdfile);
- if (host.config)
- XFREE (MTYPE_HOST, host.config);
-}
+ node_vector = NULL ;
+
+ XFREE(MTYPE_HOST, host.name);
+ XFREE(MTYPE_HOST, host.password);
+ XFREE(MTYPE_HOST, host.enable);
+ host.logfile = qpath_free(host.logfile) ;
+ host.motdfile = qpath_free(host.motdfile) ;
+ host.config_file = qpath_free(host.config_file) ;
+ host.config_dir = qpath_free(host.config_dir) ;
+ XFREE(MTYPE_HOST, host.vty_accesslist_name);
+ XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name);
+ host.cwd = qpath_free(host.cwd) ;
+} ;
diff --git a/lib/command.h b/lib/command.h
index ba7c4bb2..bb8b0bd9 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -23,204 +23,43 @@
#ifndef _ZEBRA_COMMAND_H
#define _ZEBRA_COMMAND_H
-#include <stdbool.h>
+#include "misc.h"
+
+#include "command_common.h"
+#include "vty.h"
-#include "node_type.h"
#include "vector.h"
#include "qstring.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
-struct vty ; /* in case command.h expanded first */
-
-/* Host configuration variable */
-struct host
-{
- /* Host name of this router. */
- char *name;
-
- /* Password for vty interface. */
- char *password;
- char *password_encrypt;
-
- /* Enable password */
- char *enable;
- char *enable_encrypt;
-
- /* System wide terminal lines. */
- int lines;
-
- /* Log filename. */
- char *logfile;
-
- /* config file name of this host */
- char *config;
-
- /* Flags for services */
- int advanced;
- int encrypt;
-
- /* Banner configuration. */
- const char *motd;
- char *motdfile;
-};
-
-/* Node which has some commands and prompt string and configuration
- function pointer . */
-struct cmd_node
-{
- /* Node index. */
- enum node_type node;
-
- /* Prompt character at vty interface. */
- const char *prompt;
-
- /* Is this node's configuration goes to vtysh ? */
- int vtysh;
-
- /* Node's configuration write function */
- int (*func) (struct vty *);
-
- /* Vector of this node's command list. */
- vector cmd_vector;
-};
-
-enum
-{
- /* bit significant */
- CMD_ATTR_DEPRECATED = 0x01,
- 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.
+/*==============================================================================
+ * This contains everything required for command handling from outside the
+ * library.
*/
-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. */
- cmd_function* func ;
- const char *doc; /* Documentation of this command. */
- int daemon; /* Daemon to which this command belong. */
- vector strvec; /* Pointing out each description vector. */
- unsigned int cmdsize; /* Command index count. */
- char *config; /* Configuration string */
- vector subconfig; /* Sub configuration string */
- u_char attr; /* Command attributes */
-};
-
-/* Command description structure. */
-struct desc
-{
- char *cmd; /* Command string. */
- char *str; /* Command's description. */
-};
-
-/* Command parsing options */
-enum cmd_parse_type /* bit significant */
-{
- cmd_parse_completion = 0x00,
- cmd_parse_strict = 0x01,
-
- cmd_parse_do = 0x02,
- cmd_parse_tree = 0x04,
-} ;
-
-/* Parsed command */
-typedef struct cmd_parsed* cmd_parsed ;
-struct cmd_parsed
-{
- struct cmd_element *cmd ; /* NULL if empty command
- or fails to parse */
-
- enum node_type cnode ; /* node command is in */
- enum node_type onode ; /* node the parser started in */
-
- bool do_shortcut ; /* true => is "do" command */
-
- qstring_t words ; /* the words, '\0' separated */
-
- vector_t vline ; /* pointers to the words */
-} ;
-
-
-/* Command dispatch options */
-enum {
- cmd_no_queue = true,
- cmd_may_queue = false,
-} ;
-
/*------------------------------------------------------------------------------
- * Can now include these
+ * Turn off these macros when using cpp with extract.pl
*/
-
-#include "vty.h"
-#include "uty.h"
-
-/*----------------------------------------------------------------------------*/
-/* Turn off these macros when uisng cpp with extract.pl */
#ifndef VTYSH_EXTRACT_PL
-/* helper defines for end-user DEFUN* macros */
-#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
- struct cmd_element cmdname = \
+/* helper defines for end-user DEFUN* macros */
+#define DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+ struct cmd_command cmdname = \
{ \
- .string = cmdstr, \
- .func = funcname, \
- .doc = helpstr, \
- .attr = attrs, \
- .daemon = dnum, \
- };
+ .string = cmdstr, \
+ .func = funcname, \
+ .doc = helpstr, \
+ .attr = attrs, \
+ .daemon = dnum, \
+ \
+ .items = NULL, \
+ .nt_min = 0, \
+ .nt = 0, \
+ .nt_max = 0, \
+ .vararg = NULL, \
+ .r_doc = NULL, \
+ } ;
+
+/* Legacy name for cmd_command */
+#define cmd_element cmd_command
#define DEFUN_CMD_FUNC_DECL(funcname) \
static cmd_function funcname;
@@ -228,50 +67,52 @@ enum {
#define DEFUN_CMD_FUNC_TEXT(funcname) \
static DEFUN_CMD_FUNCTION(funcname)
-/* DEFUN for vty command interafce. Little bit hacky ;-). */
+/* DEFUN for vty command interface. Little bit hacky ;-). */
#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
DEFUN_CMD_FUNC_DECL(funcname) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0) \
DEFUN_CMD_FUNC_TEXT(funcname)
#define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
DEFUN_CMD_FUNC_DECL(funcname) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0) \
DEFUN_CMD_FUNC_TEXT(funcname)
+#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT)
+
#define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
#define DEFUN_HID_CALL(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_HIDDEN))
+ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \
+ (CMD_ATTR_DIRECT | CMD_ATTR_HIDDEN))
#define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED)
#define DEFUN_DEP_CALL(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_DEPRECATED))
-
-#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL)
+ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \
+ (CMD_ATTR_DIRECT | CMD_ATTR_DEPRECATED))
-/* DEFUN_NOSH for commands that vtysh should ignore */
+/* DEFUN_NOSH for commands that vtysh should ignore */
#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \
DEFUN(funcname, cmdname, cmdstr, helpstr)
-/* DEFSH for vtysh. */
+/* DEFSH for vtysh. */
#define DEFSH(daemon, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon)
+ DEFUN_CMD_COMMAND(NULL, cmdname, cmdstr, helpstr, 0, daemon)
-/* DEFUN + DEFSH */
+/* DEFUN + DEFSH */
#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \
DEFUN_CMD_FUNC_DECL(funcname) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon) \
DEFUN_CMD_FUNC_TEXT(funcname)
-/* DEFUN + DEFSH with attributes */
+/* DEFUN + DEFSH with attributes */
#define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \
DEFUN_CMD_FUNC_DECL(funcname) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, daemon) \
DEFUN_CMD_FUNC_TEXT(funcname)
#define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
@@ -280,44 +121,34 @@ enum {
#define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED)
-/* ALIAS macro which define existing command's alias. */
+/* ALIAS macro which define existing command's alias. */
#define ALIAS(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0)
#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0)
+
+#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT, 0)
#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0)
#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0)
-
-#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL, 0)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0)
#define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon)
#define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon)
#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
- DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon)
+ DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED,\
+ daemon)
#endif /* VTYSH_EXTRACT_PL */
-/* Some macroes */
-#define CMD_OPTION(S) ((S[0]) == '[')
-#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<'))
-#define CMD_VARARG(S) ((S[0]) == '.')
-#define CMD_RANGE(S) ((S[0] == '<'))
-
-#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0))
-#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0))
-#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0))
-#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0))
-
/* Common descriptions. */
#define SHOW_STR "Show running system information\n"
#define IP_STR "IP information\n"
@@ -375,26 +206,35 @@ enum {
#endif /* HAVE_IPV6 */
/* Prototypes. */
-extern void cmd_init (int);
+extern void cmd_getcwd(void) ;
+extern void cmd_init (bool);
extern void cmd_terminate (void);
extern void print_version (const char *);
extern void install_node (struct cmd_node *, int (*) (struct vty *));
-extern void install_default (enum node_type);
-extern void install_element (enum node_type, struct cmd_element *);
+extern void install_default (node_type_t);
+extern void install_element (node_type_t, struct cmd_command *);
extern void sort_node (void);
+extern const char* cmd_host_name(bool fresh) ;
+
+extern node_type_t cmd_node_parent(node_type_t node) ;
+extern node_type_t cmd_node_exit_to(node_type_t node) ;
+extern node_type_t cmd_node_end_to(node_type_t node) ;
+
/* 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* const* argv, int argc, int shift);
-/* struct host global, ick */
-extern struct host host;
+/* Export typical functions. */
+extern struct cmd_command config_end_cmd;
+extern struct cmd_command config_exit_cmd;
+extern struct cmd_command config_quit_cmd;
+extern struct cmd_command config_help_cmd;
+extern struct cmd_command config_list_cmd;
-#ifdef QDEBUG
extern const char *debug_banner ;
-#endif
#endif /* _ZEBRA_COMMAND_H */
diff --git a/lib/command_common.h b/lib/command_common.h
new file mode 100644
index 00000000..4dd26753
--- /dev/null
+++ b/lib/command_common.h
@@ -0,0 +1,290 @@
+/* Command handler node_type stuff -- header
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_COMMAND_COMMON_H
+#define _ZEBRA_COMMAND_COMMON_H
+
+#include "misc.h"
+#include "vector.h"
+
+/*------------------------------------------------------------------------------
+ * These are the command levels/contexts.
+ *
+ * NB: this is the order in which configuration is written to the
+ * configuration file.
+ */
+enum node_type
+{
+ NULL_NODE = 0, /* For when need "not a node" */
+
+ AUTH_NODE, /* VTY login -> VIEW_NODE */
+ RESTRICTED_NODE, /* if no login required, may use this node */
+ VIEW_NODE, /* aka user EXEC */
+ AUTH_ENABLE_NODE, /* enable login -> ENABLE_NODE/CONFIG_NODE */
+ ENABLE_NODE, /* aka privileged EXEC */
+
+ MIN_DO_SHORTCUT_NODE = ENABLE_NODE,
+ /* May not "do xx" at any node lower */
+ MAX_NON_CONFIG_NODE = ENABLE_NODE,
+ /* May not be higher than this without owning
+ * the configuration symbol of power */
+
+ CONFIG_NODE, /* aka global configuration mode */
+
+ MIN_CONFIG_NODE = CONFIG_NODE,
+ /* May not change context to any node lower */
+
+ SERVICE_NODE, /* unused ! */
+ DEBUG_NODE, /* debug config write only */
+ AAA_NODE, /* unused ! */
+ KEYCHAIN_NODE, /* see: keychain.c */
+ KEYCHAIN_KEY_NODE, /* see: keychain.c -- child of KEYCHAIN_NODE */
+ INTERFACE_NODE, /* interface commands */
+ ZEBRA_NODE, /* router zebra commands */
+ TABLE_NODE, /* rtm_table config write -- see zserv.c */
+ RIP_NODE, /* router rip commands */
+ RIPNG_NODE, /* router ripng commands */
+ BGP_NODE, /* router bgp commands */
+ BGP_VPNV4_NODE, /* address-family vpnv4 -- child of BGP_NODE */
+ BGP_IPV4_NODE, /* address-family ipv4 (unicast) -- child of BGP_NODE */
+ BGP_IPV4M_NODE, /* address-family ipv4 multicast -- child of BGP_NODE */
+ BGP_IPV6_NODE, /* address-family ipv6 (unicast) -- child of BGP_NODE */
+ BGP_IPV6M_NODE, /* address-family ipv6 multicast -- child of BGP_NODE */
+ OSPF_NODE, /* router ospf commands */
+ OSPF6_NODE, /* router ospf6 commands */
+ ISIS_NODE, /* router isis commands */
+ MASC_NODE, /* unused ! RFC 2909 Multicast Address-Set Claim */
+ IRDP_NODE, /* unused ! ICMP Router Discovery Protocol */
+ IP_NODE, /* zebra_ip_config write only -- see zebra_vty.c */
+ ACCESS_NODE, /* access list config write only -- see filter.c */
+ PREFIX_NODE, /* prefix list config write only -- see plist.c */
+ ACCESS_IPV6_NODE, /* access list config write only -- see filter.c */
+ PREFIX_IPV6_NODE, /* prefix list config write only -- see plist.c */
+ AS_LIST_NODE, /* AS list config write only -- see bgp_filter.c */
+ COMMUNITY_LIST_NODE, /* Community list config write only -- see bgp_vty.c */
+ RMAP_NODE, /* route-map commands */
+ SMUX_NODE, /* SNMP config write only -- see smux.c */
+ DUMP_NODE, /* BGP dump config write only -- see bgp_dump.c */
+ FORWARDING_NODE, /* forwarding config write -- see zserv.c */
+ PROTOCOL_NODE, /* protocol config write -- see zebra_vty.c */
+ VTY_NODE, /* line vty commands */
+
+ MAX_PLUS_1_NODE,
+ MAX_NODE = MAX_PLUS_1_NODE - 1
+} ;
+typedef enum node_type node_type_t ;
+
+/*------------------------------------------------------------------------------
+ * Return values for command handling.
+ *
+ * NB: when a command is executed it may return CMD_SUCCESS, CMD_WARNING
+ * or CMD_ERROR.
+ *
+ * In all cases any output required (including any warning or error
+ * messages) must already have been output.
+ *
+ * CMD_WARNING will stop configuration reader, unless ignore warning
+ * option is set.
+ *
+ * CMD_ERROR will always stop the configuration reader.
+ *
+ * If there is no output and either CMD_WARNING or CMD_ERROR, then will
+ * output a general warning message.
+ *
+ * All other return codes are for use within the command handler.
+ */
+enum cmd_return_code
+{
+ CMD_SUCCESS = 0, /* used generally */
+
+ /* Return codes suitable for command execution functions */
+
+ CMD_WARNING = 1, /* command: not 100% successful */
+ CMD_ERROR, /* command: failed badly */
+
+ CMD_CLOSE, /* command: finish up and close vty */
+
+ /* Return codes from the command parser */
+
+ CMD_EMPTY, /* parser: nothing to execute */
+
+ CMD_ERR_PARSING, /* parser: general parser error */
+ CMD_ERR_NO_MATCH, /* parser: command/argument not recognised */
+ CMD_ERR_AMBIGUOUS, /* parser: more than on command matches */
+ CMD_ERR_INCOMPLETE,
+
+ /* Return codes used in command loop */
+
+ CMD_HIATUS, /* loop: something requires attention */
+ CMD_STOP, /* loop: stop and close vty (final) */
+
+ CMD_CANCEL, /* loop: stop and close down to base
+ * vin/vout and discard output. */
+
+ /* Return codes from I/O layers */
+
+ CMD_WAITING, /* I/O: waiting for more input */
+ CMD_EOF, /* I/O: nothing more to come */
+ CMD_IO_ERROR, /* I/O: error or time-out */
+
+ /* For the chop ???? */
+
+//CMD_COMPLETE_FULL_MATCH, /* cmd_completion returns */
+//CMD_COMPLETE_MATCH,
+//CMD_COMPLETE_LIST_MATCH,
+//CMD_COMPLETE_ALREADY,
+
+
+ CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */
+} ;
+
+typedef enum cmd_return_code cmd_return_code_t ;
+
+/*------------------------------------------------------------------------------
+ * Structure for each node -- root of all commands for the node.
+ *
+ * See install_node().
+ */
+struct vty ; /* Forward reference */
+
+struct cmd_node
+{
+ node_type_t node ; /* who we are */
+
+ const char* prompt ; /* prompt string for vty */
+
+ bool config_to_vtysh ; /* configuration goes to vtysh ? */
+
+ node_type_t parent ; /* parent when parsing commands */
+ node_type_t exit_to ; /* where to go on "exit" */
+ node_type_t end_to ; /* where to go on "end", "^C" or "^Z" */
+
+ int (*config_write) (struct vty*) ; /* configuration write function */
+
+ vector_t cmd_vector; /* Vector of this node's commands. */
+} ;
+
+typedef struct cmd_node cmd_node_t ;
+typedef struct cmd_node* cmd_node ;
+
+/*------------------------------------------------------------------------------
+ * Commands -- contents of the nodes' cmd_vector(s).
+ *
+ * A cmd_command is a static structure, which contains dynamic elements
+ * which are set when a command is installed. Note that is not uncommon for
+ * one cmd_command to appear in more than one node.
+ *
+ * The command attributes affect:
+ *
+ * * the parsing of the command -- in particular how the next_node is
+ * established.
+ *
+ * * whether the command is shown in help (or some forms of help if
+ * deprecated.
+ *
+ * * whether the command can be executed directly in the cli thread
+ * (avoiding having to wait for the cmd thread's attention -- this may be
+ * less useful now that all commands are treated a "priority" messages
+ * going into the cmd thread).
+ *
+ * If the command is marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will
+ * extract the node_type_t that the command will set, if CMD_SUCCESS. This
+ * means that can parse commands without executing them.
+ *
+ * If the command is not marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will
+ * extract the cmd_special_t value for the command -- which will be
+ * interesting if it isn't cmd_sp_simple.
+ */
+enum cmd_attr
+{
+ CMD_ATTR_SIMPLE = 0, /* bit significant */
+
+ CMD_ATTR_NODE = BIT(7), /* sets given node */
+ CMD_ATTR_MASK = CMD_ATTR_NODE - 1,
+
+ CMD_ATTR_DEPRECATED = BIT(12),
+ CMD_ATTR_HIDDEN = BIT(13), /* not shown in help */
+ CMD_ATTR_DIRECT = BIT(14), /* can run in cli thread */
+};
+typedef enum cmd_attr cmd_attr_t ;
+
+CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)MAX_NODE) ;
+
+/* Special commands, which require extra processing at parse time. */
+enum cmd_special
+{
+ cmd_sp_simple = 0,
+
+ cmd_sp_end,
+ cmd_sp_exit,
+ cmd_sp_enable,
+ cmd_sp_configure,
+
+ cmd_sp_max_plus_1,
+ cmd_sp_max = cmd_sp_max_plus_1 - 1
+} ;
+typedef enum cmd_special cmd_special_t ;
+
+CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)cmd_sp_max) ;
+
+/* Command functions and macros to define same */
+
+struct cmd_command ;
+typedef struct cmd_command* cmd_command ;
+
+typedef const char* const argv_t[] ;
+
+#define DEFUN_CMD_ARG_UNUSED __attribute__ ((unused))
+#define DEFUN_CMD_FUNCTION(name) \
+ enum cmd_return_code name (cmd_command 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)) ;
+
+/* The cmd_command structure itself */
+
+struct cmd_item ; /* Defined in command_parse.h */
+
+struct cmd_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. */
+ cmd_attr_t attr ; /* Command attributes */
+
+ vector items ; /* Vector of pointers to cmd_item(s) */
+
+ uint nt_min ; /* excluding [option](s) */
+ uint nt ; /* count of all items */
+ uint nt_max ; /* "infinite" if .vararg */
+
+ struct cmd_item* vararg ; /* if there is a vararg item */
+
+ char* r_doc ; /* rendered documentation */
+
+//char* config ; /* Configuration string */
+//vector subconfig ; /* Sub configuration string */
+};
+
+#endif /* _ZEBRA_COMMAND_COMMON_H */
diff --git a/lib/command_execute.c b/lib/command_execute.c
new file mode 100644
index 00000000..cf04bb31
--- /dev/null
+++ b/lib/command_execute.c
@@ -0,0 +1,568 @@
+/* Command Line Execution
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "misc.h"
+
+#include "command_local.h"
+#include "command_parse.h"
+#include "command_execute.h"
+#include "command_queue.h"
+#include "vty_common.h"
+#include "vty_command.h"
+#include "memory.h"
+
+/*==============================================================================
+ * Construct and destroy cmd_exec object.
+ *
+ *
+ */
+
+/*------------------------------------------------------------------------------
+ * Construct cmd_exec object and initialise.
+ *
+ * This is done when a command loop is entered.
+ *
+ * The initial cmd_context reflects the vty->type and initial vty->node.
+ */
+extern cmd_exec
+cmd_exec_new(vty vty)
+{
+ cmd_exec exec ;
+ cmd_context context ;
+
+ exec = XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_exec)) ;
+
+ /* Zeroising has set:
+ *
+ * vty = X -- set below
+ *
+ * action = all zeros
+ *
+ * context = NULL -- see below
+ *
+ * parsed = NULL -- see below
+ *
+ * password_failures = 0 -- none, yet
+ *
+ * state = exec_null
+ * locus = NULL -- not significant in exec_null
+ * ret = CMD_SUCCESS
+ *
+ * cq = NULL -- no mqb (qpthreads)
+ * -- no thread (legacy thread)
+ */
+ confirm(CMD_ACTION_ALL_ZEROS) ; /* action */
+ confirm(exec_null == 0) ; /* state */
+ confirm(CMD_SUCCESS == 0) ; /* ret */
+
+ exec->vty = vty ;
+
+ exec->parsed = cmd_parsed_new() ;
+
+ /* Initialise the context */
+ VTY_ASSERT_LOCKED() ;
+
+ exec->context = context = cmd_context_new() ;
+
+ context->node = vty->node ;
+
+ context->parse_execution = true ;
+ context->parse_only = false ;
+ context->reflect_enabled = false ;
+
+ context->onode = NULL_NODE ;
+
+ context->dir_cd = qpath_dup(host.cwd) ;
+ context->dir_home = qpath_dup(host.config_dir) ;
+ context->dir_here = qpath_dup(host.config_dir) ;
+
+ switch (vty->type)
+ {
+ case VTY_TERMINAL:
+ context->full_lex = true ;
+
+ context->parse_strict = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = context->node >= ENABLE_NODE ;
+ context->can_auth_enable = true ;
+
+ break ;
+
+ case VTY_SHELL_SERVER:
+ zabort("missing VTY_SHELL_SERVER context") ;
+
+ context->full_lex = true ;
+
+ context->parse_strict = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = true ;
+ context->can_auth_enable = false ;
+
+ break ;
+
+ case VTY_CONFIG_READ:
+ context->full_lex = false ;
+
+ context->parse_strict = true ;
+ context->parse_no_do = true ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = true ;
+ context->can_auth_enable = false ;
+
+ break ;
+
+ default:
+ zabort("vty type unknown to cmd_exec_new()") ;
+ } ;
+
+ return exec ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make a new context -- returns a completely empty context object.
+ */
+extern cmd_context
+cmd_context_new(void)
+{
+ return XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_context)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Save current context and update for new, child vin.
+ *
+ * - updates the dir_here if required
+ *
+ * - cannot inherit can_enable unless is ENABLE_NODE or better
+ *
+ * - cannot inherit can_auth_enable, no how
+ */
+extern cmd_context
+cmd_context_new_save(cmd_context context, qpath file_here)
+{
+ cmd_context saved ;
+
+ saved = cmd_context_new() ;
+
+ *saved = *context ; /* copy as is */
+
+ /* The saved copy of the context now owns the current paths, so now need
+ * to duplicate (or set new) paths.
+ */
+ context->dir_cd = qpath_dup(saved->dir_cd) ;
+ context->dir_home = qpath_dup(saved->dir_home) ;
+
+ if (file_here == NULL)
+ context->dir_here = qpath_dup(saved->dir_here) ;
+ else
+ {
+ context->dir_here = qpath_dup(file_here) ;
+ qpath_shave(context->dir_here) ;
+ } ;
+
+ /* The inheritance of can_enable is tricky. Will not bequeath can_enable
+ * is is not currently in ENABLE_NODE or better !
+ */
+ if (context->node <= ENABLE_NODE)
+ context->can_enable = false ;
+
+ context->can_auth_enable = false ;
+
+ return saved ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Restore given context -- frees the copy restored from.
+ *
+ * Has to free the directories in the context being restored to.
+ *
+ * Note that can restore any node it likes: if it was enabled, that's fine; if
+ * it was in some config mode, will still have the symbol of power because
+ * only at vin_depth <= 1 is the symbol of power actually released.
+ *
+ * Returns NULL.
+ */
+extern cmd_context
+cmd_context_restore(cmd_context dst, cmd_context src)
+{
+ assert(src != NULL) ;
+
+ qpath_free(dst->dir_cd) ;
+ qpath_free(dst->dir_home) ;
+ qpath_free(dst->dir_here) ;
+
+ *dst = *src ; /* copy as is */
+
+ return cmd_context_free(src, true) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free the given context.
+ *
+ * If the context is a copy of an existing context, then must not free the
+ * directories -- they will be freed when that existing context is freed.
+ */
+extern cmd_context
+cmd_context_free(cmd_context context, bool copy)
+{
+ if (context != NULL)
+ {
+ if (!copy)
+ {
+ qpath_free(context->dir_cd) ;
+ qpath_free(context->dir_home) ;
+ qpath_free(context->dir_here) ;
+ } ;
+
+ XFREE(MTYPE_CMD_EXEC, context) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Destroy cmd_exec object.
+ */
+extern cmd_exec
+cmd_exec_free(cmd_exec exec)
+{
+ if (exec != NULL)
+ {
+ exec->vty = NULL ; /* no longer required. */
+
+ exec->context = cmd_context_free(exec->context, false) ; /* not a copy */
+ cmd_action_clear(exec->action) ;
+ exec->parsed = cmd_parsed_free(exec->parsed) ;
+
+ if (vty_nexus)
+ exec->cq.mqb = mqb_free(exec->cq.mqb) ;
+ else if (exec->cq.thread != NULL)
+ {
+ thread_cancel(exec->cq.thread) ;
+ exec->cq.thread = NULL ;
+ } ;
+
+ XFREE(MTYPE_CMD_EXEC, exec) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*==============================================================================
+ *
+ */
+/*------------------------------------------------------------------------------
+ * Read configuration from file.
+ *
+ * In the qpthreads world this assumes that it is running with the vty
+ * locked, and that all commands are to be executed directly.
+ *
+ * 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.
+ *
+ * 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 or I/O operation
+ *
+ * when returns, the entire vin/vout stack will have been closed.
+ *
+ * If reaches EOF on the config file, returns CMD_SUCCESS. If anything else
+ * happens, will generate an error message and exits the command loop.
+ */
+extern cmd_return_code_t
+cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
+{
+ cmd_exec exec = vty->exec ;
+ cmd_parsed parsed = exec->parsed ;
+ cmd_return_code_t ret, hret ;
+
+ ret = CMD_SUCCESS ; /* so far, so good */
+
+ while (1)
+ {
+ /* Deal with anything which is not success !
+ */
+ if ((ret != CMD_SUCCESS) && (ret != CMD_EMPTY))
+ {
+ /* Will drop straight out of the loop if have anything other
+ * than CMD_HIATUS, CMD_EOF or CMD_CLOSE, which are all signals
+ * that some adjustment to the vin/vout stacks is required,
+ * or that we are all done here.
+ *
+ * Everything else is deemed to be an error that stops the
+ * command loop.
+ */
+ if ((ret != CMD_HIATUS) && (ret != CMD_EOF) && (ret != CMD_CLOSE))
+ break ;
+
+ ret = vty_cmd_hiatus(vty, ret) ;
+
+ if (ret == CMD_STOP)
+ return CMD_SUCCESS ; /* the expected outcome ! */
+
+ if (ret != CMD_SUCCESS)
+ break ;
+ } ;
+
+ /* If all is well, need another command line */
+
+ ret = vty_cmd_fetch_line(vty) ; /* sets exec->action */
+ if (ret != CMD_SUCCESS)
+ continue ;
+
+ /* Parse the command line we now have */
+ assert(exec->action->to_do == cmd_do_command) ;
+
+ cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ;
+ ret = cmd_parse_command(parsed, exec->context) ;
+
+ if (ret != CMD_SUCCESS)
+ continue ;
+
+ /* Special handling before first active line. */
+ if (first_cmd != NULL)
+ {
+ if (first_cmd != parsed->cmd)
+ {
+ ret = (*first_cmd->func)(first_cmd, vty, -1, NULL) ;
+ if (ret != CMD_SUCCESS)
+ continue ;
+ } ;
+ first_cmd = NULL ;
+ } ;
+
+ /* reflection now..... */
+ if (exec->reflect)
+ {
+ ret = vty_cmd_reflect_line(vty) ;
+ if (ret != CMD_SUCCESS)
+ continue ;
+ } ;
+
+ /* Pipe work, if any */
+ if ((parsed->parts & cmd_parts_pipe) != 0)
+ {
+ ret = cmd_open_pipes(vty) ;
+ if (ret != CMD_SUCCESS)
+ continue ;
+ } ;
+
+ /* Command execution, if any */
+ if ((parsed->parts & cmd_part_command) != 0)
+ ret = cmd_execute(vty) ;
+
+ /* Deal with success (or suppressed warning). */
+ if ((ret == CMD_SUCCESS) || ((ret == CMD_WARNING) && ignore_warning))
+ ret = vty_cmd_success(vty) ;
+ } ;
+
+ /* Arrives here if:
+ *
+ * - vty_cmd_fetch_line() returns anything except CMD_SUCCESS, CMD_EOF or
+ * CMD_HIATUS -- which are not errors.
+ *
+ * - any other operation returns anything except CMD_SUCCESS
+ * (or CMD_WARNING, if they are being ignored), CMD_EOF or CMD_CLOSE.
+ *
+ * Deal with any errors -- generate suitable error messages and close back
+ * to (but excluding) vout_base.
+ *
+ * CMD_SUCCESS and CMD_EMPTY are impossible at this point -- they should
+ * have been dealt with in the loop.
+ *
+ * CMD_EOF is also impossible -- vty_cmd_fetch_line() or vty_cmd_hiatus()
+ * can return that, but that will have been dealt with.
+ *
+ * CMD_CLOSE is also impossible -- will have been given to vty_cmd_hiatus(),
+ * which never returns it.
+ *
+ * CMD_WAITING is not valid for blocking vio !
+ */
+ qassert(ret != CMD_SUCCESS) ;
+ qassert(ret != CMD_EMPTY) ;
+ qassert(ret != CMD_EOF) ;
+ qassert(ret != CMD_CLOSE) ;
+ qassert(ret != CMD_WAITING) ;
+
+ hret = ret ;
+ do
+ hret = vty_cmd_hiatus(vty, hret) ;
+ while (hret == CMD_IO_ERROR) ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ */
+
+/*------------------------------------------------------------------------------
+ * Open in and/or out pipes
+ *
+ * * Returns:
+ *
+ * - OK -- CMD_SUCCESS
+ * - error -- CMD_ERROR, etc
+ */
+extern cmd_return_code_t
+cmd_open_pipes(vty vty)
+{
+ cmd_exec exec = vty->exec ;
+ cmd_parsed parsed = exec->parsed ;
+ cmd_return_code_t ret ;
+ vty_io vio ;
+ bool after ;
+ VTY_LOCK() ;
+ vio = vty->vio ;
+
+ ret = CMD_SUCCESS ;
+
+ after = false ; /* assuming no in pipe */
+
+ /* Deal with any in pipe stuff */
+ if ((parsed->parts & cmd_part_in_pipe) != 0)
+ {
+ qstring args ;
+
+ args = cmd_tokens_concat(parsed, parsed->first_in_pipe,
+ parsed->num_in_pipe) ;
+
+ if ((parsed->in_pipe & cmd_pipe_file) != 0)
+ ret = uty_cmd_open_in_pipe_file(vio, exec->context, args,
+ parsed->in_pipe) ;
+ else if ((parsed->in_pipe & cmd_pipe_shell) != 0)
+ ret = uty_cmd_open_in_pipe_shell(vio, exec->context, args,
+ parsed->in_pipe) ;
+ else
+ zabort("invalid in pipe state") ;
+
+ qs_free(args) ;
+
+ after = true ;
+ } ;
+
+ /* Deal with any out pipe stuff */
+ if (((parsed->parts & cmd_part_out_pipe) != 0) && (ret == CMD_SUCCESS))
+ {
+ qstring args ;
+
+ args = cmd_tokens_concat(parsed, parsed->first_out_pipe,
+ parsed->num_out_pipe) ;
+
+ if ((parsed->out_pipe & cmd_pipe_file) != 0)
+ ret = uty_cmd_open_out_pipe_file(vio, exec->context, args,
+ parsed->out_pipe, after) ;
+ else if ((parsed->out_pipe & cmd_pipe_shell) != 0)
+ ret = uty_cmd_open_out_pipe_shell(vio, exec->context, args,
+ parsed->out_pipe, after) ;
+ else if ((parsed->out_pipe & cmd_pipe_dev_null) != 0)
+ ret = uty_cmd_open_out_dev_null(vio, after) ;
+ else
+ zabort("invalid out pipe state") ;
+
+ qs_free(args) ;
+ } ;
+
+ VTY_UNLOCK() ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command Execution.
+ *
+ * If !parse_only, set vty->node and dispatch the command.
+ *
+ * Returns: CMD_SUCCESS -- it all want very well
+ * CMD_WARNING -- not so good: warning message sent by vty_out()
+ * CMD_ERROR -- not so good: warning message sent by vty_out()
+ * CMD_EOF -- close the current input
+ *
+ * NB: the distinction between CMD_WARNING and CMD_ERROR is that CMD_WARNING
+ * may be ignored when reading a configuration file.
+ *
+ * NB: no other returns are acceptable !
+ */
+extern cmd_return_code_t
+cmd_execute(vty vty)
+{
+ cmd_parsed parsed = vty->exec->parsed ;
+ cmd_context context = vty->exec->context ;
+ cmd_command cmd = parsed->cmd ;
+
+ cmd_return_code_t ret ;
+
+ vty->node = parsed->cnode ;
+
+ if (context->parse_only)
+ ret = CMD_SUCCESS ;
+ else
+ ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed),
+ cmd_arg_vector_argv(parsed)) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ /* If the node is changed by the command, do that now and make sure
+ * that the configuration symbol of power is straight.
+ *
+ * If the new node is >= CONFIG_NODE, then MUST already have acquired
+ * the symbol of power (otherwise the command would have failed !)
+ *
+ * If the new node is < CONFIG_NODE, then we will here release the
+ * symbol of power iff we are at the vin_base !
+ *
+ * If the new node is NULL_NODE, then treat as CMD_EOF.
+ */
+ if (context->node != parsed->nnode)
+ {
+ context->node = parsed->nnode ;
+ vty_cmd_config_lock_check(vty, context->node) ;
+
+ if (context->node == NULL_NODE)
+ ret = CMD_EOF ;
+ } ;
+
+ /* The command should (no longer) change the vty->node, but if it does,
+ * it had better be to the same as what the parser expected -- for if
+ * not, that will break "parse_only" and generally cause confusion.
+ */
+ qassert((vty->node == parsed->cnode) || (vty->node == parsed->nnode)) ;
+ }
+ else
+ {
+ /* Enforce restrictions on return codes. */
+ assert((ret == CMD_WARNING) || (ret == CMD_ERROR)
+ || (ret == CMD_EOF)) ;
+ } ;
+
+ return ret ;
+} ;
diff --git a/lib/command_execute.h b/lib/command_execute.h
index a5a807c8..97033b6e 100644
--- a/lib/command_execute.h
+++ b/lib/command_execute.h
@@ -23,57 +23,102 @@
#ifndef _ZEBRA_COMMAND_EXECUTE_H
#define _ZEBRA_COMMAND_EXECUTE_H
-#include "command.h"
-
-extern vector cmd_make_strvec (const char *);
-extern vector cmd_add_to_strvec (vector v, const char* str) ;
-extern void cmd_free_strvec (vector);
-extern vector cmd_describe_command (vector, int, int *status);
-extern vector cmd_complete_command (vector, int, int *status);
-extern const char *cmd_prompt (enum node_type);
-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 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)
+#include "command_local.h"
+#include "command_parse.h"
+#include "vty_common.h"
+#include "qstring.h"
+#include "mqueue.h"
+#include "qpnexus.h"
+#include "thread.h"
+
+/*==============================================================================
+ * This is stuff which is used to parse and then execute commands.
+ */
+
+/* State of the execution loop
+ */
+enum cmd_exec_state
{
- cmd_parsed parsed = vty->parsed ;
- return (*(parsed->cmd->func))(parsed->cmd, vty,
- vector_length(&parsed->vline),
- (const char * const*)vector_body(&parsed->vline)) ;
+ exec_null = 0, /* not started, yet */
+ exec_fetch, /* fetch command line */
+ exec_open_pipes, /* open pipes on command line */
+ exec_execute, /* execute standard command */
+ exec_special, /* execute special command */
+ exec_done_cmd, /* command has completed */
+ exec_hiatus, /* while issues are dealt with */
+ exec_stopped, /* command loop has stopped */
} ;
+typedef enum cmd_exec_state cmd_exec_state_t ;
+
+typedef struct cmd_exec* cmd_exec ;
+
+struct cmd_exec
+{
+ vty vty ; /* parent */
+
+ cmd_action_t action ; /* to do + line */
-#define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0)
-#define cmd_parse_reset_free(parsed) cmd_parse_reset(parsed, 1)
+ cmd_context context ; /* how to parse/execute */
-extern void config_replace_string (struct cmd_element *, char *, ...);
+ bool out_suppress ; /* for configuration reading */
+ bool reflect ; /* actually reflect */
-/* Export typical functions. */
-extern struct cmd_element config_end_cmd;
-extern struct cmd_element config_exit_cmd;
-extern struct cmd_element config_quit_cmd;
-extern struct cmd_element config_help_cmd;
-extern struct cmd_element config_list_cmd;
-extern char *host_config_file (void);
-extern void host_config_set (char *);
+ cmd_parsed parsed ; /* parsing and its result */
-/* "<cr>" global */
-extern char *command_cr;
+ uint password_failures ; /* AUTH_NODE & AUTH_ENABLE_NODE */
+
+ cmd_exec_state_t state ; /* for cq_process */
+ qpn_nexus locus ; /* for cq_process */
+
+ cmd_return_code_t ret ; /* for cq_process */
+
+ union
+ {
+ mqueue_block mqb ; /* for cq_process */
+ struct thread* thread ;
+ } cq ;
+} ;
+
+/*==============================================================================
+ * Functions
+ *
+ */
+
+extern cmd_exec cmd_exec_new(vty vty) ;
+extern cmd_exec cmd_exec_free(cmd_exec exec) ;
+
+extern cmd_return_code_t cmd_read_config(vty vty, cmd_command first_cmd,
+ bool ignore_warning) ;
+extern cmd_return_code_t cmd_open_pipes(vty vty) ;
+
+extern cmd_return_code_t cmd_execute(vty vty) ;
+
+extern cmd_context cmd_context_new(void) ;
+extern cmd_context cmd_context_new_save(cmd_context src, qpath file_here) ;
+extern cmd_context cmd_context_restore(cmd_context dst, cmd_context src) ;
+extern cmd_context cmd_context_free(cmd_context context, bool copy) ;
+
+
+#if 0
+
+extern enum cmd_return_code cmd_execute_command (vty vty,
+ cmd_parse_type_t type, struct cmd_command **cmd) ;
+extern enum cmd_return_code cmd_execute_command_strict (vty vty,
+ enum cmd_parse_type type, struct cmd_command **cmd) ;
+
+
+extern void config_replace_string (cmd_command, char *, ...);
-#ifdef QDEBUG
-extern const char *debug_banner;
#endif
+/*==============================================================================
+ * Inlines
+ */
+
+Inline bool
+cmd_is_direct(cmd_parsed parsed)
+{
+ return ((parsed->cmd->attr & CMD_ATTR_DIRECT) != 0) ;
+} ;
+
#endif /* _ZEBRA_COMMAND_EXECUTE_H */
diff --git a/lib/command_local.h b/lib/command_local.h
new file mode 100644
index 00000000..0925930d
--- /dev/null
+++ b/lib/command_local.h
@@ -0,0 +1,203 @@
+/* Command handler -- header for stuff used within command/vty
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_COMMAND_LOCAL_H
+#define _ZEBRA_COMMAND_LOCAL_H
+
+#include "command_common.h" /* First and foremost ! */
+
+#include "vty_local.h"
+#include "vector.h"
+#include "qpath.h"
+
+/*==============================================================================
+ * This is for access to some things in command.c which are not required
+ * by external users, who use command.h.
+ *
+ * This is for use within the command/vty family.
+ *
+ * Should not be used with command.h ! (Except in command.c itself.)
+ *
+ * This may duplicate things published in command.h, but also includes things
+ * which are not intended for "external" use.
+ */
+
+/*------------------------------------------------------------------------------
+ * Host configuration variable
+ */
+typedef unsigned long int name_gen_t ;
+
+struct host
+{
+ /* Host name of this router. */
+ char* name ;
+ bool name_set ; /* set by command */
+ name_gen_t name_gen ; /* incremented each time name changes */
+
+ /* Password for vty interface. */
+ char* password;
+ bool password_encrypted ;
+
+ /* Enable password */
+ char* enable;
+ bool enable_encrypted ;
+
+ /* System wide terminal lines default */
+ int lines;
+
+ /* Log filename. */
+ qpath logfile;
+
+ /* config file name of this host */
+ qpath config_file ;
+ qpath config_dir ;
+
+ /* Flags for services */
+ bool advanced;
+ bool encrypt;
+
+ /* Banner configuration. */
+ const char* motd ;
+ qpath motdfile;
+
+ /* Someone has the config symbol of power */
+ bool config ;
+ ulong config_brand ;
+
+ /* Allow vty to start without password */
+ bool no_password_check ;
+
+ /* Restrict unauthenticated logins? */
+ bool restricted_mode ;
+
+ /* vty timeout value -- see "exec timeout" command */
+ unsigned long vty_timeout_val ;
+
+ /* vty access-class command */
+ char* vty_accesslist_name ;
+
+ /* vty access-class for IPv6. */
+ char* vty_ipv6_accesslist_name ;
+
+ /* Current directory -- initialised cmd_cwd() */
+ qpath cwd ;
+} ;
+
+enum
+{
+ restricted_mode_default = false,
+} ;
+
+/* Structure declared in command.c */
+extern struct host host ;
+
+/*------------------------------------------------------------------------------
+ * Command qualifiers
+ */
+enum cmd_do
+{
+ cmd_do_nothing = 0, /* no action required */
+
+ cmd_do_command = 1, /* dispatch the current command line */
+
+ cmd_do_ctrl_c, /* received ^C */
+ cmd_do_ctrl_d, /* received ^D */
+ cmd_do_ctrl_z, /* received ^Z */
+
+ cmd_do_eof, /* hit "EOF" */
+ cmd_do_timed_out, /* terminal timed out */
+
+ cmd_do_count, /* number of different cli_do_xxx */
+
+ cmd_do_mask = 0x0F,
+
+ cmd_do_auth = 0x10,
+
+ cmd_do_keystroke = 0xFF, /* special for keystroke reader */
+} ;
+
+CONFIRM(cmd_do_count <= (cmd_do_mask + 1)) ;
+
+typedef enum cmd_do cmd_do_t ;
+
+/*------------------------------------------------------------------------------
+ * Command action -- qualifier + line
+ */
+struct cmd_action
+{
+ cmd_do_t to_do ;
+ qstring line ;
+} ;
+typedef struct cmd_action cmd_action_t[1] ;
+typedef struct cmd_action* cmd_action ;
+
+enum { CMD_ACTION_ALL_ZEROS = (cmd_do_nothing == 0) } ;
+
+/*------------------------------------------------------------------------------
+ * Vector of nodes -- defined in command.c, declared here so the parser can
+ * reach it.
+ */
+extern vector node_vector ;
+
+/*----------------------------------------------------------------------------*/
+
+#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"
+
+/*==============================================================================
+ * Functions in command.c
+ */
+
+extern const char* cmd_host_name(bool fresh) ;
+extern void cmd_host_config_set(qpath config_file);
+
+extern const char* cmd_prompt(node_type_t node) ;
+
+extern node_type_t cmd_node_parent(node_type_t node) ;
+extern node_type_t cmd_node_exit_to(node_type_t node) ;
+extern node_type_t cmd_node_end_to(node_type_t node) ;
+
+/*==============================================================================
+ *
+ */
+Inline void
+cmd_action_clear(cmd_action act)
+{
+ act->to_do = cmd_do_nothing ;
+ act->line = NULL ; /* not essential, but tidy */
+} ;
+
+Inline void
+cmd_action_set(cmd_action act, cmd_do_t to_do, qstring line)
+{
+ act->to_do = to_do ;
+ act->line = line ;
+} ;
+
+Inline void
+cmd_action_take(cmd_action dst, cmd_action src)
+{
+ *dst = *src ;
+ cmd_action_clear(src) ;
+} ;
+
+#endif /* _ZEBRA_COMMAND_LOCAL_H */
diff --git a/lib/command_parse.c b/lib/command_parse.c
new file mode 100644
index 00000000..9204db70
--- /dev/null
+++ b/lib/command_parse.c
@@ -0,0 +1,4881 @@
+/* Quagga command line parsing
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "misc.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#include "command_local.h"
+#include "command_parse.h"
+#include "memory.h"
+#include "list_util.h"
+#include "elstring.h"
+
+/*==============================================================================
+ * Command Description objects.
+ *
+ */
+static void cmd_fail_item(cmd_command cmd, const char* msg) ;
+static char* cmd_item_brackets(cmd_command cmd, char* cp) ;
+static cmd_item cmd_make_item(cmd_command cmd, char* cp, char* dp) ;
+static void cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) ;
+static char* cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) ;
+static long cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) ;
+static int cmd_cmp_item(const cmd_item* a, const cmd_item* b) ;
+static int cmd_cmp_range_items(const cmd_item a, const cmd_item b) ;
+static bool cmd_item_is_option(cmd_item_type_t it) ;
+static bool cmd_item_is_vararg(cmd_item_type_t it) ;
+static void cmd_set_str(cmd_item n, const char* str) ;
+
+/*------------------------------------------------------------------------------
+ * Table of known "words" -- so that a word can be represented by its
+ * address in this table and its length.
+ *
+ * See: cmd_set_str()
+ */
+enum { word_lump_length = 500 * 8 } ; /* plenty ? */
+enum { command_specification_length = 500 } ; /* plenty !! */
+
+typedef struct word_lump* word_lump ;
+
+struct word_lump
+{
+ word_lump next ;
+
+ char* end ; /* points at the terminating '\0' */
+ char words[word_lump_length] ;
+} ;
+
+static struct dl_base_pair(word_lump) word_lumps = INIT_DL_BASE_PAIR ;
+
+/*------------------------------------------------------------------------------
+ * Dummy eol_item -- completed in cmd_parse_init()
+ */
+static struct cmd_item eol_item =
+{
+ .str = ELSTRING_INIT, /* see cmd_parse_init() */
+ .doc = "",
+
+ .next = NULL,
+
+ .type = item_eol,
+ .arg = false,
+
+ .range_sign_allowed = false,
+ .range_sign_required = false,
+ .range_min = 0,
+ .range_max = 0
+} ;
+
+/*------------------------------------------------------------------------------
+ * Parse cmd_command string and doc to create the items for the cmd_command,
+ * and fill in:
+ *
+ * cmd->items -- vector of cmd_item(s).
+ *
+ * Where a given item may have more than one possible
+ * value, thet are arranged as a list.
+ *
+ * cmd->nt_min -- count of items up to first [option]
+ * where (...) counts as 1
+ * and .vararg counts as 1
+ *
+ * Is the minimum number of tokens required to match to
+ * this command.
+ *
+ * cmd->nt -- count of all items
+ * where (...) counts as 1
+ * and [option] counts as 1
+ * and .vararg counts as 1
+ *
+ * cmd->nt_max -- count of all items as nt_var,
+ * except .vararg forces to UINT_MAX
+ *
+ * Is the maximum number of tokens which can be matched
+ * to this command.
+ *
+ * cmd->r_string -- copy of cmd->string, chopped up and referred to by
+ * the cmd_items.
+ *
+ * cmd->d_string -- copy of the cmd->doc, chopped up and referred to by
+ * the cmd_items.
+ *
+ * Note that the cmd_items point into the r_string and the d_string, and
+ * do not have further copies of their fragments of the original.
+ *
+ * Note that the t_string and d_string have all extraneous spaces, tabs and
+ * control characters removed.
+ *
+ * Stops dead if not valid !
+ *
+ * Accepts: items separated by one or more spaces. Early on in the process,
+ * will discard spaces within a bracketed item (and checks for balanced
+ * brackets). Rejects any control character other than '\t', which is
+ * converted to ' '. Multiple spaces are reduced to single spaces.
+ *
+ * - single item is one of:
+ *
+ * - keyword -- anything starting a-z or 0-9, followed by any
+ * alphanumeric, '-', '_' or ':'.
+ * or *
+ * - <0-9> -- decimal range
+ * - WORD -- anything at least starting A-Z, followed by any
+ * alphanumeric, '-', '_' or ':'.
+ * - A.B.C.D -- ipv4 address
+ * - A.B.C.D/M -- ipv4 prefix
+ * - X:X::X:X -- ipv6 address
+ * - X:X::X:X/M -- ipv6 prefix
+ * - .vararg -- anything starting '.', followed by any alphanumeric,
+ * '-', '_' or ':'.
+ * - [item] -- optional item any of the above TODO
+ *
+ * - multiple item is: '(' item '|' item .... ')'
+ *
+ * where spaces around items are discarded. The items may be any of the
+ * above, except:
+ *
+ * - must all be different types of item, or for keywords and ranges,
+ * different values.
+ *
+ * - cannot have [item]
+ *
+ * - cannot have .var
+ *
+ * may have a single item -- whose value is sent out as an argument.
+ *
+ * An [item] may only be followed by other [item](s). An [item] matches a
+ * token or end of line.
+ *
+ * A .vararg must be the last item. A .vararg matches one or more tokens.
+ *
+ *
+ *
+ */
+extern void
+cmd_compile(cmd_command cmd)
+{
+ vector multvec ;
+ const char* p ;
+ char* cp ;
+ char* qp ;
+ char* dp ;
+ bool opt ;
+ bool vararg ;
+
+ char spec[command_specification_length + 1] ;
+
+ /* Initialise the compiled version of the command */
+
+ assert(cmd->r_doc == NULL) ;
+
+ cmd->items = vector_init_new(NULL, 10) ; /* plenty ! */
+ cmd->nt_min = 0 ;
+ cmd->nt = 0 ;
+ cmd->nt_max = 0 ;
+ cmd->vararg = NULL ;
+ cmd->r_doc = XSTRDUP(MTYPE_CMD_STRING, cmd->doc) ; /* NULL => "" */
+
+ if (strlen(cmd->string) > command_specification_length)
+ cmd_fail_item(cmd, "command specification *too* long") ;
+
+ /* Simplify the command line string by replacing TABs by spaces, and barfing
+ * on control characters. Strip leading and trailing spaces and any spaces
+ * between brackets -- checking for matching brackets -- and squeeze out
+ * multiple spaces.
+ */
+ qp = spec ;
+ p = cmd->string ;
+
+ while ((*p == ' ') || (*p == '\t'))
+ ++p ; /* squeeze out leading spaces */
+
+ while (*p != '\0') /* starts with not ' ' and not '\t' */
+ {
+ if (!iscntrl(*p))
+ *qp = *p ;
+ else if (*p == '\t')
+ *qp = ' ' ;
+ else
+ cmd_fail_item(cmd, "improper control character in string") ;
+
+ if ((*qp != ' ') || (*(qp - 1) != ' '))
+ ++qp ; /* squeeze out multiple spaces */
+
+ ++p ;
+ } ;
+
+ while ((qp > spec) && (*(qp - 1) == ' '))
+ --qp ; /* squeeze out trailing spaces */
+
+ *qp = '\0' ;
+
+ cp = spec ;
+ qp = spec ;
+ while (*cp != '\0')
+ {
+ if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{'))
+ {
+ /* Check for balanced brackets and remove any spaces between.
+ *
+ * Checks for enclosed brackets being balanced as well.
+ *
+ * Leaves cp pointing at the trailing bracket.
+ */
+ char* sp = cp ;
+ cp = cmd_item_brackets(cmd, cp) ;
+ while (sp < cp)
+ {
+ if (*sp != ' ')
+ *qp++ = *sp++ ;
+ else
+ ++sp ;
+ } ;
+ } ;
+
+ *qp++ = *cp++ ;
+ } ;
+
+ *qp = '\0' ; /* terminate reduced string */
+
+ /* Simplify the documentation string by replacing TABs by spaces, and barfing
+ * on control characters other than '\n'.
+ *
+ * Strips leading spaces and any spaces before or after '\n'.
+ */
+
+ qp = dp = cmd->r_doc ;
+ while (*dp != '\0')
+ {
+ /* Strip leading */
+ while (*dp == ' ')
+ ++dp ;
+
+ /* Eat documentation section. */
+ while ((*dp != '\n') && (*dp != '\0'))
+ {
+ if (!iscntrl(*dp))
+ *qp++ = *dp++ ;
+ else if (*dp == '\t')
+ {
+ *qp++ = ' ' ;
+ ++dp ;
+ }
+ else
+ cmd_fail_item(cmd, "improper control character in documentation") ;
+ } ;
+
+ /* Get here with *dp == '\n' or '\0'
+ *
+ * Strip trailing spaces (any before '\n' or '\0'
+ */
+ while ((qp != cmd->r_doc) && (*(qp - 1) == ' '))
+ --qp ;
+
+ /* copy '\n', if required. */
+ if (*dp == '\n')
+ *qp++ = *dp++ ;
+ } ;
+
+ *qp = '\0' ; /* terminate reduced string */
+
+ /* Processing loop */
+
+ cp = spec ;
+ dp = cmd->r_doc ;
+
+ opt = false ;
+ vararg = false ;
+
+ multvec = NULL ;
+
+ while (*cp != '\0')
+ {
+ uint multiple ;
+
+ /* Deal with single or multiple item. */
+ multiple = 0 ;
+ do
+ {
+ cmd_item n ;
+ char* c_sp ;
+ char* d_sp ;
+
+ /* step to the next documentation section */
+
+ d_sp = dp ; /* start of documentation */
+
+ while (*dp != '\0')
+ {
+ if (*dp == '\n')
+ {
+ *dp++ = '\0' ;
+ break ;
+ } ;
+ ++dp ;
+ } ;
+
+ /* Deal with '(' if we have one. */
+
+ if (*cp == '(') /* change up to multiple */
+ {
+ if (multiple != 0)
+ cmd_fail_item(cmd, "unexpected '('") ;
+
+ multiple = 1 ; /* seen '(' */
+ ++cp ; /* step past it */
+
+ multvec = vector_re_init(multvec, 10) ; /* plenty ! */
+ } ;
+
+ /* Find end of current item & '\0' terminate it. */
+ c_sp = cp ;
+ while (1)
+ {
+ if (*cp == '|') /* eat '|' */
+ {
+ if ((c_sp == cp) || (multiple < 1))
+ cmd_fail_item(cmd, "unexpected '|'") ;
+ *cp++ = '\0' ;
+ break ;
+ } ;
+
+ if (*cp == ')') /* eat ')' */
+ {
+ if ((c_sp == cp) || (multiple < 1))
+ cmd_fail_item(cmd, "unexpected ')'") ;
+ *cp++ = '\0' ;
+ multiple = 2 ;
+
+ if ((*cp != ' ') && (*cp != '\0'))
+ cmd_fail_item(cmd, "expect ' ' or nothing after ')'") ;
+ } ;
+
+ if (*cp == ' ')
+ {
+ *cp++ = '\0' ;
+ break ;
+ } ;
+
+ if (*cp == '\0')
+ break ;
+
+ ++cp ;
+ } ;
+
+ /* Create the next item and push */
+
+ n = cmd_make_item(cmd, c_sp, d_sp) ;
+
+ if (multiple == 0)
+ vector_push_item(cmd->items, n) ;
+ else
+ vector_push_item(multvec, n) ;
+
+ /* Extra checks for multiple item. */
+ if (multiple > 0)
+ {
+ n->arg = true ; /* always */
+
+ if (cmd_item_is_option(n->type))
+ cmd_fail_item(cmd, "cannot have [option] inside (..)") ;
+
+ /* could lift this restriction, but need to check that
+ * do not have a WORD|.VAR together, because that is tautologous.
+ */
+
+ if (cmd_item_is_vararg(n->type))
+ cmd_fail_item(cmd, "cannot have .vararg inside (..)") ;
+ } ;
+
+ /* Check optional item state -- can only be trailing */
+ if (cmd_item_is_option(n->type))
+ opt = true ;
+ else if (opt)
+ cmd_fail_item(cmd, "can only have [option] after [option]") ;
+
+ /* Check vararg item state -- can only be trailing */
+ if (vararg)
+ cmd_fail_item(cmd, "cannot have anything after .vararg") ;
+ else if (cmd_item_is_vararg(n->type))
+ {
+ vararg = true ;
+ cmd->vararg = n ; /* remember for parsing */
+ } ;
+
+ } while (multiple == 1) ;
+
+ /* count the item */
+ if (!opt)
+ ++cmd->nt_min ;
+ ++cmd->nt ;
+ if (!vararg)
+ ++cmd->nt_max ;
+ else
+ cmd->nt_max = UINT_MAX ;
+
+ /* Complete the multiple item.
+ *
+ * Sort the items so that are always used and presented in the same
+ * order. Check that the items are unique.
+ *
+ * We must have at least one item.
+ */
+ if (multiple == 2)
+ {
+ cmd_item n, p ;
+ uint i ;
+
+ assert(vector_length(multvec) >= 1) ;
+
+ vector_sort(multvec, (vector_sort_cmp*)cmd_cmp_item) ;
+
+ n = vector_get_item(multvec, 0) ;
+ vector_push_item(cmd->items, n) ;
+
+ for (i = 1 ; i < vector_length(multvec) ; ++i)
+ {
+ p = n ;
+ n = vector_get_item(multvec, i) ;
+
+ p->next = n ;
+ n->next = NULL ;
+
+ if (p->type == n->type)
+ {
+ bool repeat ;
+
+ if (n->type == item_keyword)
+ repeat = (els_body_nn(n->str) == els_body_nn(p->str))
+ && (els_len_nn(n->str) == els_len_nn(p->str)) ;
+ else if (n->type == item_range)
+ repeat = cmd_cmp_range_items(n, p) == 0 ;
+ else
+ repeat = true ;
+
+ if (repeat)
+ cmd_fail_item(cmd, "repeated items in (...)") ;
+ } ;
+ } ;
+ }
+ else
+ assert(multiple == 0) ;
+ } ;
+
+ vector_reset(multvec, free_it) ;
+
+ /* Reduce the vector to the minimum size required */
+ vector_decant(cmd->items) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Validate a compiled item
+ *
+ * Checks that the contents of the cmd_command are consistent with the
+ * contents of the srcvec.
+ *
+ */
+extern void
+cmd_compile_check(cmd_command cmd)
+{
+ bool ok ;
+
+ uint nt_min = 0 ;
+ uint nt = 0 ;
+ uint nt_max = 0 ;
+ cmd_item vararg = NULL ;
+
+ ok = true ;
+
+ /* Require the following to be set to something */
+ if ( (cmd->string == NULL)
+ || (cmd->func == NULL)
+ || (cmd->items == NULL)
+ || (cmd->r_doc == NULL) )
+ ok = false ;
+
+ /* cmd->nt must match the vector_length and be non-zero. */
+ ok = ok && ((nt = vector_length(cmd->items)) == cmd->nt) ;
+ ok = ok && (nt != 0) ;
+
+ /* Walk the vector of items, and check that those are OK. */
+ if (ok)
+ {
+ uint ii = 0 ;
+ bool opt = false ;
+
+ for (ii = 0 ; ok && (ii < nt) ; ++ii)
+ {
+ cmd_item item ;
+ cmd_item first_item ;
+
+ item = vector_get_item(cmd->items, ii) ;
+ if (item == NULL)
+ {
+ ok = false ;
+ break ;
+ } ;
+
+ if (vararg != NULL) /* nothing after vararg */
+ {
+ ok = false ;
+ break ;
+ } ;
+
+ first_item = item ;
+ while (ok && (item != NULL))
+ {
+ /* If this is an option, may only be a single item
+ *
+ * Otherwise, after an option must all be options.
+ */
+ if (cmd_item_is_option(item->type))
+ {
+ /* option must be a single item. */
+ opt = true ;
+ if ((item != first_item) || (item->next != NULL))
+ {
+ ok = false ;
+ break ;
+ } ;
+ }
+ else if (opt)
+ {
+ /* once we have an option, must all be options */
+ ok = false ;
+ break ;
+ } ;
+
+ /* If this is a vararg, must be the last of this item.
+ *
+ * Note that allow for [.varg] and (...., .varg) -- the second
+ * should be sorted to the back !
+ */
+ if (cmd_item_is_vararg(item->type))
+ {
+ /* vararg must be last item & only vararg */
+ if ((item->next != NULL) || (vararg != NULL))
+ {
+ ok = false ;
+ break ;
+ } ;
+
+ vararg = item ;
+ } ;
+
+
+ /* If there is a next, this and the next MUST be arg */
+ if (item->next != NULL)
+ {
+ if (!((item->arg) && (item->next->arg)))
+ {
+ ok = false ;
+ break ;
+ } ;
+ } ;
+
+ item = item->next ;
+ } ;
+
+ /* Advance the nt_min and nt_max as required. */
+ if (!opt)
+ ++nt_min ;
+
+ if (vararg == NULL)
+ ++nt_max ;
+ else
+ nt_max = UINT_MAX ;
+ } ;
+ } ;
+
+ /* Final checks */
+
+ ok = ok && (cmd->nt_min == nt_min)
+ && (cmd->nt == nt)
+ && (cmd->nt_max == nt_max)
+ && (cmd->vararg == vararg) ;
+
+ if (!ok)
+ cmd_fail_item(cmd, "some compile error") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reject the cmd_item string or doc.
+ */
+static void
+cmd_fail_item(cmd_command cmd, const char* msg)
+{
+ fprintf (stderr, "Command parse error!: %s\n", msg) ;
+ fprintf (stderr, " in command: '%s'\n", cmd->string) ;
+ exit(2) ;
+}
+
+/*------------------------------------------------------------------------------
+ * Advance to matching bracket -- fail if not found. Recurse as required.
+ *
+ * Returns: address of matching bracket.
+ */
+static char*
+cmd_item_brackets(cmd_command cmd, char* cp)
+{
+ char seek ;
+
+ switch (*cp)
+ {
+ case '(':
+ seek = ')' ;
+ break ;
+
+ case '[':
+ seek = ']' ;
+ break ;
+
+ case '<':
+ seek = '>' ;
+ break ;
+
+ case '{':
+ seek = '}' ;
+ break ;
+
+ default:
+ return cp ;
+ } ;
+
+ do
+ {
+ ++cp ;
+
+ if (*cp == seek)
+ return cp ;
+ else if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{'))
+ cp = cmd_item_brackets(cmd, cp) ;
+ }
+ while (*cp != '\0') ;
+
+ cmd_fail_item(cmd, "unbalanced brackets of some sort") ;
+
+ return cp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make descriptor for current item.
+ *
+ * cp points at start of '\0' terminated item, which has no spaces and no
+ * control characters in or around it. Also, if there are brackets, they are
+ * balanced.
+ *
+ * Returns: new descriptor, filled in as required.
+ */
+
+static cmd_item
+cmd_make_item(cmd_command cmd, char* cp, char* dp)
+{
+ char* inner ;
+ cmd_item n ;
+
+ n = XCALLOC(MTYPE_CMD_ITEM, sizeof(struct cmd_item)) ;
+
+ /* Zeroising has set:
+ *
+ * * str = NULL -- set below
+ * * str_len = 0 -- set below
+ * * doc = NULL -- set below
+ *
+ * * next = NULL -- set elsewhere if multiple.
+ *
+ * * type = item_null
+ * * arg = false -- set if required, below
+ *
+ * * range_sign_allowed )
+ * * range_sign_required ) -- set elsewhere if required
+ * * range_min )
+ * * range_max )
+ */
+ confirm(item_null == 0) ;
+
+ cmd_set_str(n, cp) ;
+ n->doc = dp ;
+
+ /* Worry about option state */
+ inner = NULL ;
+ if (*cp == '[')
+ {
+ n->arg = true ; /* always true for option */
+
+ inner = XSTRDUP(MTYPE_TMP, cp) ;
+ cp = inner + 1 ; /* strip leading '[' */
+ *(cp + strlen(cp) - 1) = '\0' ; /* strip trailing ']' */
+
+ if (*cp == '\0')
+ cmd_fail_item(cmd, "empty [option]") ;
+ } ;
+
+ /* Deal with the inner item */
+ cmd_make_item_inner(cmd, n, cp) ;
+
+ /* Worry about the option state, again. */
+ if (inner != NULL)
+ {
+ XFREE(MTYPE_TMP, inner) ;
+
+ if (n->type == item_vararg)
+ cmd_fail_item(cmd, "cannot have [.vararg]") ;
+
+ n->type = item_option_word ; /* TODO other option types ? */
+ } ;
+
+ /* return newly minted cmd_item item */
+ assert(n->type != item_null) ;
+
+ return n ;
+}
+
+/*------------------------------------------------------------------------------
+ * Make inner part of cmd_item -- so can have [...] anything (in principle).
+ *
+ * Require '\0' terminated inner part.
+ */
+static void
+cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp)
+{
+ bool eat_name_chars ; /* alphanumeric + '-', '_', '.' and ':' */
+
+ eat_name_chars = false ;
+
+ if (islower(*cp) /* 'a'..'z' */
+ || isdigit(*cp)) /* '0'..'9' */
+ {
+ /* item_keyword -- lowercase alpha numeric + '_' and '-' */
+ n->type = item_keyword ;
+ eat_name_chars = true ;
+ }
+ else if (*cp == '*') /* '*' */
+ {
+ /* special item_keyword '*' */
+ n->type = item_keyword ;
+ ++cp ;
+ }
+ else if (isupper(*cp)) /* 'A'..'Z' */
+ {
+ n->arg = true ;
+
+ /* WORD or other variable */
+ if (strcmp(cp, "A.B.C.D") == 0)
+ n->type = item_ipv4_address ;
+ else if (strcmp(cp, "A.B.C.D/M") == 0)
+ n->type = item_ipv4_prefix ;
+ else if (strcmp(cp, "X:X::X:X") == 0)
+ n->type = item_ipv6_address ;
+ else if (strcmp(cp, "X:X::X:X/M") == 0)
+ n->type = item_ipv6_prefix ;
+ else
+ {
+ n->type = item_word ;
+ eat_name_chars = true ;
+ } ;
+
+ if (n->type != item_word)
+ cp += strlen(cp) ; /* step past "A.B.C.D" et al */
+ }
+ else if (*cp == '.') /* '.' */
+ {
+ n->arg = true ;
+ n->type = item_vararg ;
+ eat_name_chars = true ;
+ }
+ else if (*cp == '<') /* '<' */
+ {
+ n->arg = true ;
+
+ cp = cmd_make_item_numeric(cmd, n, ++cp) ;
+
+ if (*cp != '>')
+ cmd_fail_item(cmd, "badly formed <...>") ;
+ else
+ ++cp ;
+ }
+ else if (*cp == '\0')
+ cmd_fail_item(cmd, "cannot have an empty item") ;
+
+ if (eat_name_chars)
+ {
+ do ++cp ; while ( isalnum(*cp)
+ || (*cp == '-')
+ || (*cp == '_')
+ || (*cp == ':')
+ || (*cp == '.') ) ;
+
+ } ;
+
+ if (*cp != '\0')
+ cmd_fail_item(cmd, "invalid item") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make <...> types
+ *
+ * Require '\0' terminated <...>, pointing after the '<'.
+ *
+ * Assumes the '>' is present.
+ *
+ * Returns: where processed up to -- pointing at '>' iff OK.
+ *
+ * Supports ranges:
+ *
+ * 9-10 => unsigned values in 32b range -- NO sign
+ *
+ * +9-10 => unsigned value, where '+' is optional
+ * 9-+10 => unsigned value, where '+' is *required*
+ * +9-+10 => same as above
+ *
+ * -9-10 => signed value, where '+' is optional
+ * -9-+10 => signed value, where '+' is *required*
+ *
+ * -9--8 => signed value, where '-' is required (!)
+ *
+ *
+ * +/-9 => -9-+9 -- sign is required.
+ *
+ * In place of decimal number can use 9b -- giving 2^9 - 1.
+ */
+static char*
+cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp)
+{
+ if (isdigit(*cp) || (*cp == '+') || (*cp == '-'))
+ {
+ long m ;
+ bool pm ;
+
+ confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ;
+
+ n->type = item_range ;
+ n->range_sign_allowed = false ;
+ n->range_sign_required = false ;
+
+ if (strncmp(cp, "+/-", 3) == 0)
+ {
+ pm = true ;
+ n->range_sign_required = true ;
+ cp += 2 ; /* step to '-' to get -ve range_min. */
+ }
+ else
+ {
+ pm = false ;
+ n->range_sign_allowed = (*cp == '+') ;
+ } ;
+
+ m = cmd_make_item_number(cmd, n, &cp) ;
+ n->range_min = m ;
+
+ if (pm)
+ m = -m ; /* for range_max */
+
+ else if (*cp == '-')
+ {
+ ++cp ; /* past the '-' */
+
+ n->range_sign_required = (*cp == '+') ;
+
+ m = cmd_make_item_number(cmd, n, &cp) ;
+ }
+
+ else
+ cmd_fail_item(cmd, "badly formed <0-1>") ;
+
+ n->range_max = m ;
+
+ if (n->range_min > n->range_max)
+ cmd_fail_item(cmd, "badly formed <0-1> min > max !") ;
+
+ if ((n->range_sign_required) || (n->range_min < 0))
+ n->range_sign_allowed = true ; /* allowed if required ! */
+ } ;
+
+ return cp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get signed or unsigned value -- process the '9b' form.
+ *
+ */
+static long
+cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp)
+{
+ long m ;
+ char* cp ;
+
+ cp = *p_cp ;
+ m = strtol(cp, p_cp, 10) ;
+
+ if ((*p_cp == cp) || (m > item_max_number))
+ cmd_fail_item(cmd, "badly formed or out of range number in <...>") ;
+
+ if (**p_cp == 'b')
+ {
+ long s ;
+
+ ++(*p_cp) ; /* step past 'b' */
+ s = m ;
+ m = labs(m) ;
+ if ((m == 0) || (m > 32))
+ cmd_fail_item(cmd, "out of range number in 9b form in <...>") ;
+
+ m = ((long)1 << m) - 1 ;
+ if (s < 0)
+ m = -m ;
+ } ;
+
+ return m ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare cmd_item items
+ *
+ * Note that command types sort with the larger type value before the smaller.
+ */
+static int
+cmd_cmp_item(const cmd_item* a, const cmd_item* b)
+{
+ if ((*a)->type != (*b)->type)
+ return ((*a)->type > (*b)->type) ? -1 : +1 ; /* descending order */
+ else
+ {
+ if ((*a)->type == item_range)
+ return cmd_cmp_range_items(*a, *b) ;
+ else
+ return els_cmp((*a)->str, (*b)->str) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare cmd_item item_range items
+ */
+static int
+cmd_cmp_range_items(const cmd_item a, const cmd_item b)
+{
+ int as, bs ;
+
+ if (a->range_min != b->range_min)
+ return (a->range_min < b->range_min) ? -1 : +1 ;
+
+ if (a->range_max != b->range_max)
+ return (a->range_max < b->range_max) ? -1 : +1 ;
+
+ as = a->range_sign_required ? 2 : (a->range_sign_allowed ? 1 : 0) ;
+ bs = b->range_sign_required ? 2 : (b->range_sign_allowed ? 1 : 0) ;
+
+ if (as != bs)
+ return (as < bs) ? -1 : +1 ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the str and str_len entries for a given item.
+ *
+ * The str entry of a cmd_item is set to point at a pool of known values.
+ * This means that where two str entries are the same, they will have the
+ * same address !
+ */
+static void
+cmd_set_str(cmd_item n, const char* str)
+{
+ uint len ;
+ word_lump lump ;
+ char* word ;
+
+ len = strlen(str) ;
+
+ lump = dsl_head(word_lumps) ;
+ while (1)
+ {
+ if (lump == NULL)
+ {
+ lump = XCALLOC(MTYPE_CMD_STRING, sizeof(struct word_lump)) ;
+ lump->end = lump->words ;
+
+ dsl_append(word_lumps, lump, next) ;
+ } ;
+
+ word = strstr(lump->words, str) ;
+ if (word != NULL)
+ break ;
+
+ if (dsl_next(lump, next) != NULL)
+ {
+ lump = dsl_next(lump, next) ;
+ continue ;
+ } ;
+
+ if (len >= (&lump->words[word_lump_length] - lump->end))
+ {
+ lump = NULL ;
+ continue ;
+ } ;
+
+ word = lump->end ;
+ memcpy(word, str, len) ;
+ lump->end += len ;
+ *lump->end = '\0' ;
+ break ;
+ } ;
+
+ els_set_n_nn(n->str, word, len) ;
+} ;
+
+/*==============================================================================
+ * Token objects
+ */
+
+/*------------------------------------------------------------------------------
+ * Create a new, empty token_vector with room for a dozen arguments (initially).
+ */
+static token_vector
+cmd_token_vector_new(void)
+{
+ token_vector tv ;
+
+ tv = XCALLOC(MTYPE_CMD_PARSED, sizeof(token_vector_t)) ;
+
+ vector_init_new(tv->body, 12) ;
+
+ return tv ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty token_vector and release all memory if required.
+ */
+static token_vector
+cmd_token_vector_free(token_vector tv)
+{
+ if (tv != NULL)
+ {
+ cmd_token t ;
+
+ /* Give back all the token objects and release vector body */
+ while ((t = vector_ream(tv->body, keep_it)) != NULL)
+ {
+ qs_reset(t->qs, keep_it) ; /* discard body of qstring */
+ XFREE(MTYPE_TOKEN, t) ;
+ } ;
+
+ XFREE(MTYPE_CMD_PARSED, tv) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get string value of given token.
+ *
+ * Returns an empty (not NULL) string if token NULL or no string.
+ */
+inline static char*
+cmd_token_make_string(cmd_token t)
+{
+ if (t->term)
+ return qs_char_nn(t->qs) ;
+ else
+ {
+ t->term = true ;
+ return qs_make_string(t->qs) ;
+ }
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get i'th token from given token vector -- zero origin
+ */
+Inline cmd_token
+cmd_token_get(token_vector tv, vector_index_t i)
+{
+ return vector_get_item(tv->body, i) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set i'th token from given token vector -- zero origin
+ */
+inline static void
+cmd_token_set(token_vector tv, vector_index_t i,
+ cmd_token_type_t type, const char* p, usize len, usize tp)
+{
+ cmd_token t = cmd_token_get(tv, i) ;
+
+ if (t == NULL)
+ {
+ /* Make a brand new token object */
+ t = XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ;
+
+ /* Zeroising the new structure sets:
+ *
+ * type = 0 -- cmd_tok_eol
+ * qs = zeroised qstring -- empty string
+ * complete = 0 -- false
+ *
+ * tp = 0
+ * lp = zeroised elstring -- empty string
+ */
+ confirm(cmd_tok_eol == 0) ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
+ confirm(ELSTRING_INIT_ALL_ZEROS) ;
+
+ vector_set_item(tv->body, i, t) ;
+ } ;
+
+ t->type = type ;
+ t->term = false ;
+ t->tp = tp ;
+
+ qs_set_alias_n(t->qs, p, len) ;
+ qs_els_copy_nn(t->ot, t->qs) ;
+
+ t->w_len = 0 ; /* no words matched to, yet */
+ t->seen = 0 ; /* no matches attempted, yet */
+} ;
+
+
+/*------------------------------------------------------------------------------
+ * Get one or more original token values, concatenated with space between each.
+ *
+ * Returns a brand new qstring that must be discarded after use.
+ */
+extern qstring
+cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt)
+{
+ cmd_token t ;
+ qstring qs ;
+
+ assert(nt >= 2) ;
+
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs = qs_set_els(NULL, t->ot) ;
+
+ while (--nt >= 2)
+ {
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs_append_str(qs, " ") ;
+ qs_append_els(qs, t->ot) ;
+ } ;
+
+ return qs ;
+} ;
+
+/*==============================================================================
+ * Argument vector
+ */
+
+/*------------------------------------------------------------------------------
+ * Create a new, empty arg_vector with room for a dozen arguments (initially).
+ */
+static arg_vector
+cmd_arg_vector_new(void)
+{
+ arg_vector args ;
+
+ args = XCALLOC(MTYPE_CMD_PARSED, sizeof(arg_vector_t)) ;
+
+ vector_init_new(args->body, 12) ;
+
+ return args ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty arg_vector and release all memory if required.
+ */
+static arg_vector
+cmd_arg_vector_free(arg_vector args)
+{
+ if (args != NULL)
+ {
+ vector_reset(args->body, keep_it) ;
+ XFREE(MTYPE_CMD_PARSED, args) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty the body of the arg_vector object in cmd_parsed.
+ */
+Inline void
+cmd_arg_vector_empty(cmd_parsed parsed)
+{
+ vector_set_length(parsed->args->body, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push argument to the argument vector.
+ */
+Inline void
+cmd_arg_vector_push(cmd_parsed parsed, char* arg)
+{
+ vector_push_item(parsed->args->body, arg) ;
+} ;
+
+/*==============================================================================
+ * Parser object
+ */
+
+/*------------------------------------------------------------------------------
+ * Allocate and initialise a new cmd_parsed object
+ */
+extern cmd_parsed
+cmd_parsed_new(void)
+{
+ cmd_parsed parsed ;
+
+ parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(cmd_parsed_t)) ;
+
+ /* Zeroising the structure has set:
+ *
+ * parts = 0 -- cleared by cmd_tokenize()
+ * tok_total = 0 -- set by cmd_tokenize()
+ *
+ * elen = 0 -- set by cmd_tokenize()
+ * tsp = 0 -- set by cmd_tokenize()
+ *
+ * cmd = NULL -- no command yet
+ * cnode = 0 -- not set
+ * nnode = 0 -- not set
+ *
+ * num_tokens = 0 -- set by cmd_tokenize()
+ * tokens = NULL -- see below
+ *
+ * args = NULL -- see below
+ *
+ * emess = NULL -- no error yet
+ * eloc = 0 -- no error location
+ *
+ * in_pipe = cmd_pipe_none
+ * out_pipe = cmd_pipe_none
+ *
+ * first_in_pipe )
+ * num_in_pipe )
+ * first_do )
+ * num_do )
+ * first_command ) none
+ * num_command )
+ * first_out_pipe )
+ * num_out_pipe )
+ * first_comment )
+ * num_comment )
+ *
+ * cti ) set by cmd_token_position()
+ * rp )
+ *
+ * cmd_v = NULL -- no vector of filtered commands
+ * item_v = NULL -- no vector of filtered items
+ *
+ * strongest )
+ * best_complete ) set by cmd_filter_prepare()
+ * min_strength )
+ * strict )
+ */
+ confirm(cmd_pipe_none == 0) ;
+
+ parsed->tokens = cmd_token_vector_new() ;
+ parsed->args = cmd_arg_vector_new() ;
+
+ return parsed ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty out and free a cmd_parsed object
+ */
+extern cmd_parsed
+cmd_parsed_free(cmd_parsed parsed)
+{
+ if (parsed != NULL)
+ {
+ parsed->tokens = cmd_token_vector_free(parsed->tokens) ;
+ parsed->args = cmd_arg_vector_free(parsed->args) ;
+
+ parsed->cmd_v = vector_reset(parsed->cmd_v, free_it) ;
+ parsed->item_v = vector_reset(parsed->item_v, free_it) ;
+
+ parsed->emess = qs_reset(parsed->emess, free_it) ;
+
+ XFREE(MTYPE_CMD_PARSED, parsed) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*==============================================================================
+ * Parsing error handling.
+ *
+ *
+ */
+static cmd_return_code_t
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...) PRINTF_ATTRIBUTE(4, 5) ;
+
+/*------------------------------------------------------------------------------
+ * Register a parsing error.
+ *
+ * Takes token in which parsing error was detected, and an offset from the
+ * start of that, for the location of the error. If the offset is not zero,
+ * it must be an offset in the original token (!).
+ *
+ * The message is a simple constant string (!).
+ *
+ * The message will be output as: ..........^ pointing to the location
+ * followed by: % <mess>\n
+ *
+ * (The mess does not need to include the '%' or the '\n'.)
+ *
+ * Sets: parsed->emess
+ * parsed->eloc
+ *
+ * Returns: CMD_ERR_PARSING -- which MUST only be returned if p
+ */
+static cmd_return_code_t
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...)
+{
+ va_list args ;
+
+ qs_clear(parsed->emess) ;
+
+ va_start (args, format);
+ parsed->emess = qs_vprintf(parsed->emess, format, args);
+ va_end (args) ;
+
+ if (t != NULL)
+ parsed->eloc = t->tp + off ;
+ else
+ parsed->eloc = -1 ;
+
+ return CMD_ERR_PARSING ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Register a parsing error.
+ *
+ * Takes token in which parsing error was detected, and an offset from the
+ * start of that, for the location of the error. If the offset is not zero,
+ * it must be an offset in the original token (!).
+ *
+ * The message is a simple constant string (!).
+ *
+ * The message will be output as: ..........^ pointing to the location
+ * followed by: % <mess>\n
+ *
+ * (The mess does not need to include the '%' or the '\n'.)
+ *
+ * Sets: parsed->emess
+ * parsed->eloc
+ *
+ * Returns: CMD_ERR_PARSING -- which MUST only be returned if p
+ */
+extern void
+cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent)
+{
+ if (parsed->eloc >= 0)
+ {
+ qstring here ;
+
+ here = qs_set_fill(NULL, indent + parsed->eloc, "....") ;
+ vio_fifo_put_bytes(ebuf, qs_body_nn(here), qs_len_nn(here)) ;
+ qs_reset(here, free_it) ;
+ } ;
+
+ vio_fifo_printf(ebuf, "^\n%% %s\n", qs_make_string(parsed->emess)) ;
+} ;
+
+/*==============================================================================
+ * Lexical level stuff
+ */
+
+/*------------------------------------------------------------------------------
+ * Take qstring and see if it is empty -- only whitespace and/or comment
+ */
+extern bool
+cmd_is_empty(qstring line)
+{
+ cpp_t lp ;
+
+ qs_cpp(lp, line) ; /* NULL -> NULL */
+
+ while (lp->p < lp->e)
+ {
+ if ((*lp->p == ' ') || (*lp->p == '\t'))
+ ++lp->p ;
+ else
+ return ((*lp->p == '!') || (*lp->p == '#')) ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reserved characters in a pipe token.
+ *
+ * The characters reserved allow for a large number of possible options to
+ * be attached to the pipe token.
+ *
+ * Wish to allow the pipe token to be followed by file name or command name
+ * without requiring whitespace separation, also do not want to intrude into
+ * quoted or escaped stuff. So limit the characters that are reserved.
+ */
+static bool
+cmd_pipe_reserved_char(char ch)
+{
+ return strchr("<|>%&*+-=?", ch) != NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Take qstring and break it into tokens.
+ *
+ * Expects string to have been preprocessed, if required, to ensure that any
+ * unwanted control characters have been removed. This code only recognises
+ * '\t' and treats it as whitespace (so any other control characters will
+ * end up as part of a token).
+ *
+ * Ignores leading and trailing ' ' or '\t' (whitespace).
+ *
+ * Have "full_lex" flag -- to distinguish old and new.
+ *
+ * If not full_lex, we have this simple tokenization:
+ *
+ * * tokens are separated by one or more whitespace characters.
+ *
+ * * if the first non-whitespace character is '!', this is a comment line.
+ *
+ * For the "full_lex" we more or less follow the usual shell conventions,
+ * except:
+ *
+ * * can have '!' as well as '#' to start comments
+ *
+ * * may only have '|' at the start of a line. '>|' must be used for
+ * piping command output to shell command. So '|' is, effectively, a
+ * "shell command prefix".
+ *
+ * Limiting '|' in this way removes problems with regular expressions.
+ *
+ * Also allows a <| shell to use pipes !!
+ *
+ * So the "full_lex" will:
+ *
+ * * ignore anything between '....' -- as per shell convention, '\' is
+ * ignored and there is no way to include "'" in a single quoted string.
+ *
+ * * ignore anything immediately preceded by '\' -- including space
+ * (or tab converted to a space) and double quote characters.
+ *
+ * * ignore anything between "...." -- including '\"' escapes.
+ *
+ * * unbalanced "'" or '"' are treated as if eol was a "'" or '"'.
+ *
+ * Except when ignored by the rules above, the "full lex" will recognise
+ * the following:
+ *
+ * * tokens are separated by whitespace -- one ' ' or '\t' characters.
+ * Whitespace before, after or between tokens is not part of the tokens.
+ *
+ * * the character '|' is significant at the start of a line (after any
+ * leading whitespace) only. It is then the start of an out_pipe
+ * token -- so self terminates after any pipe_reserved_chars.
+ *
+ * * the characters '!', '#' are significant only at the start of a token,
+ * and then from there to end of line is comment.
+ *
+ * Note that this means that comment after a token must be separated by
+ * at least one space from that token.
+ *
+ * * the characters '<' and '>' are separators -- they terminate any
+ * preceding token. They are in_pipe or out_pipe tokens, and self
+ * terminate after any pipe_reserved_chars.
+ *
+ * The tokenization does not remove any " ' or \ characters, that is left for
+ * a later stage, where context may affect the handling.
+ *
+ * The tokens returned contain all the original characters of the line, except
+ * for the removal of ' ' and '\t' between tokens and at the end of the line.
+ *
+ * NB: the elstring containing the line to be tokenized MUST NOT change
+ * until the parsed object is finished with.
+ *
+ * Returns: the types of all tokens or'd together.
+ * returns cmd_tok_null if the line is empty (apart from ' ' and '\t')
+ * or if the elstring was or contained NULL.
+ *
+ * Initialises the parsed object, ready for further parsing:
+ *
+ * Sets: parsed->parts = cmd_parts_none
+ * parsed->num_tokens )
+ * parsed->elen ) per the results
+ * parsed->tsp )
+ * parsed->tokens )
+ *
+ * Note that the num_tokens does not include the cmd_tok_eol on the end.
+ */
+extern void
+cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex)
+{
+ cpp_t lp ;
+ const char *cp, *tp ;
+ cmd_token_type_t total ;
+ uint nt ;
+
+ total = 0 ; /* nothing yet */
+ nt = 0 ;
+
+ qs_cpp(lp, line) ; /* NULL -> NULL */
+
+ cp = lp->p ;
+ tp = cp ;
+ while (cp < lp->e) /* process to end */
+ {
+ const char* sp ;
+ bool end ;
+ cmd_token_type_t type ;
+
+ if ((*cp == ' ') || (*cp == '\t'))
+ {
+ /* skip white-space */
+ do
+ {
+ end = (++cp == lp->e) ;
+ } while (!end && ((*cp == ' ') || (*cp == '\t'))) ;
+
+ if (end)
+ break ;
+ } ;
+
+ end = false ;
+ sp = cp ;
+ type = cmd_tok_simple ;
+ do
+ {
+ switch (*cp)
+ {
+ case '\t': /* whitespace at end of token */
+ case ' ':
+ end = true ;
+ break ;
+
+ case '\'': /* proceed to matching ' or end */
+ ++cp ;
+ if (full_lex)
+ {
+ type |= cmd_tok_sq ;
+ while (cp < lp->e)
+ {
+ if (*cp++ == '\'')
+ break ;
+ } ;
+ } ;
+ break ;
+
+ case '\\': /* step past escaped character, if any */
+ ++cp ;
+ if (full_lex)
+ {
+ type |= cmd_tok_esc ;
+ if (cp < lp->e)
+ ++cp ;
+ } ;
+ break ;
+
+ case '"': /* proceed to matching " or end... */
+ ++cp ;
+ if (full_lex)
+ {
+ type |= cmd_tok_dq ;
+ while (cp < lp->e) /* NB: do not register \ separately */
+ {
+ if (*cp++ == '"')
+ if (*(cp - 2) != '\\') /* ignore escaped " */
+ break ;
+ } ;
+ } ;
+ break ;
+
+ case '|': /* only at start of line */
+ if (full_lex && (nt == 0) && (cp == sp))
+ {
+ type = cmd_tok_out_shell ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ end = true ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ case '>': /* '>' is a separator */
+ if (full_lex)
+ {
+ if (cp == sp)
+ {
+ type = cmd_tok_out_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
+ end = true ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ case '<': /* '<' is a separator */
+ if (full_lex)
+ {
+ if (cp == sp)
+ {
+ type = cmd_tok_in_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
+ end = true ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ case '#':
+ if (!full_lex)
+ {
+ ++cp ;
+ break ;
+ } ;
+ fall_through ; /* treat as '!'. */
+
+ case '!': /* '!' and '#' special at token start */
+ if ((cp == sp) && (full_lex || (nt == 0)))
+ {
+ type = cmd_tok_comment ;
+ cp = lp->e ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ default:
+ ++cp ;
+ break ;
+ } ;
+ } while (!end && (cp < lp->e)) ;
+
+ cmd_token_set(parsed->tokens, nt, type, sp, cp - sp, sp - lp->p) ;
+ ++nt ;
+ total |= type ;
+
+ tp = cp ;
+ } ;
+
+ /* When we get here, tp points just after last character of last token,
+ * or at start of line if the line is blank (other than whitespace).
+ *
+ * If line is empty (apart from whitespace):
+ *
+ * - the effective length is zero.
+ * - the trailing space count is the number of (leading) spaces.
+ *
+ * If line is not empty:
+ *
+ * - the start position of the first token is the number of leading spaces.
+ * - the trailing space count is the number of spaces after the last
+ * token. (If the last token is comment, this will be zero.)
+ */
+ parsed->parts = cmd_parts_none ;
+ parsed->tok_total = total ;
+ parsed->num_tokens = nt ;
+ parsed->elen = tp - lp->p ;
+ parsed->tsp = lp->e - tp ;
+
+ /* Append an empty end of line token. */
+ cmd_token_set(parsed->tokens, parsed->num_tokens, cmd_tok_eol,
+ lp->e, 0, lp->e - lp->p) ;
+} ;
+
+
+
+
+
+/*==============================================================================
+ * VTY Command Line Input Pipe
+ *
+ * Here are the mechanics which support the:
+ *
+ * < file_name
+ *
+ * <| shell_command
+ *
+ * and the:
+ *
+ * > file_name
+ * >> file_name
+ * | shell_command
+ *
+ *==============================================================================
+ * The file_name handling
+ *
+ * Two directories are supported:
+ *
+ * "home" -- being the root for configuration files
+ *
+ * "cd" -- being the root for relative filenames, in the usual way
+ *
+ * "here" -- being the directory for the enclosing "< filename"
+ * see below for more detailed semantics
+ *
+ * There are the global values:
+ *
+ * config home -- defaults to directory for configuration file.
+ * may be set by command.
+ *
+ * config cd -- defaults to cwd at start up
+ *
+ * There are the local values, within a given CLI instance (ie VTY):
+ *
+ * cli home -- set to config home when CLI instance starts, or when
+ * config home is set within the CLI.
+ *
+ * cli cd -- similarly
+ *
+ * cli here -- outside < is same as cli home, unless explicitly set
+ * - set by cli here
+ * - pushed each time executes <, and set to directory for
+ * the < filename.
+ * - pushed each time executes <| and left unchanged
+ * - popped each time exits < or <|
+ * - set to parent by "no cli here" inside <, or to
+ * default state outside <
+ *
+ * And then the filename syntax:
+ *
+ * /path -- absolute path -- in the usual way
+ *
+ * path -- path wrt "cli cd" -- in the usual way
+ *
+ * ~/path -- path in "cli home" -- so "config" stuff
+ *
+ * ~./path -- path in "here" -- so wrt to enclosing < file
+ *
+ *==============================================================================
+ * The Input Pipe Commands
+ *
+ * These are "universal commands".
+ *
+ * They are:
+ *
+ * < filename -- filename as above
+ *
+ * treat contents of given file as command lines.
+ * (That may include further < commands.)
+ *
+ * See notes on cli here for pushing/popping the here
+ * directory.
+ *
+ * TODO: Filename and quotes ??
+ *
+ * <| shell_command -- the shell command is executed "as is" by system().
+ *
+ * the stdout and stderr are collected.
+ *
+ * treats stdout as command lines.
+ * (That may include further < commands.)
+ *
+ * anything from stderr is sent to the VTY output.
+ *
+ * As far as the top level CLI is concerned, these are discrete commands.
+ * That is to say:
+ *
+ * -- except where blocked while reading the "pipe", all commands are
+ * executed one after another, in one Routing Engine operation.
+ *
+ * -- in any event, all output is gathered in the VTY buffering, and will
+ * be sent to the console (or where ever) only when the outermost command
+ * completes.
+ *
+ * There are three options associated with the output from a < operation:
+ *
+ * -- suppress command line reflect
+ *
+ * whether to suppress reflect of commands to the VTY before they are
+ * dispatched.
+ *
+ * The default is not to suppress command line reflect.
+ *
+ * -- suppress command results
+ *
+ * whether to suppress any output generated by each command.
+ *
+ * The default is not to suppress command results.
+ *
+ * -- suppress "more"
+ *
+ * whether to do "--more--", if currently applies, when finally
+ * outputting all the command results.
+ *
+ * The default is not to suppress "more".
+ *
+ * This option can only be set for the outermost < operation.
+ *
+ * Remembering that all output occurs in one go when the outermost < operation
+ * completes.
+ *
+ * The default is to show everything and implement "--more--", pretty much as
+ * if the commands had been typed in. Except that "--more--" applies to
+ * everything (including the command lines) together, rather than to the output
+ * of each command individually.
+ *
+ * These options may be changed by flags attached to the <, as follows:
+ *
+ * ! suppress command line reflect
+ *
+ * ? suppress "--more--"
+ *
+ * * suppress command output
+ *
+ * TODO: cli xxx commands for reflect and output suppression and notes ....
+ *
+ * TODO: Error handling....
+ *
+ * TODO: Closing the CLI....
+ *
+ * TODO: Time out and the CLI....
+ *
+ *
+ */
+
+
+
+
+/*------------------------------------------------------------------------------
+ * Get next cpp char & step -- unless at end of cpp.
+ */
+inline static char
+cpp_getch(cpp p)
+{
+ if (p->p < p->e)
+ return *p->p++ ;
+ else
+ return '\0' ;
+}
+
+/*------------------------------------------------------------------------------
+ * Process in-pipe token and set the required bits in the pipe type word
+ *
+ * Known tokens are: < <|
+ * Known options are: +
+ */
+static cmd_return_code_t
+cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t)
+{
+ cpp_t p ;
+ bool ok ;
+
+ els_cpp(p, t->ot) ;
+
+ ok = true ;
+
+ switch (cpp_getch(p))
+ {
+ case '<':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
+
+ case '\0':
+ parsed->in_pipe = cmd_pipe_file ;
+ break ;
+
+ case '|':
+ parsed->in_pipe = cmd_pipe_shell ;
+ break ;
+ } ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
+
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ case '+':
+ parsed->in_pipe |= cmd_pipe_reflect ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
+ } ;
+
+ if (ok)
+ return CMD_SUCCESS ;
+
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe in'") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Process out-pipe token and set the required bits in the pipe type word
+ *
+ * Known tokens are: | > >> >| >*
+ * Known options are: none
+ */
+static cmd_return_code_t
+cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
+{
+ cpp_t p ;
+ bool ok ;
+
+ els_cpp(p, t->ot) ;
+
+ ok = true ;
+
+ switch (cpp_getch(p))
+ {
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_cmd ;
+ break ;
+
+ case '>':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
+
+ case '\0':
+ parsed->out_pipe = cmd_pipe_file ;
+ break ;
+
+ case '>':
+ parsed->out_pipe = cmd_pipe_file | cmd_pipe_append ;
+ break ;
+
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell ;
+ break ;
+
+ case '*':
+ parsed->out_pipe = cmd_pipe_dev_null ;
+ break ;
+ } ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
+
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ default:
+ ok = false ;
+ break ;
+ } ;
+ } ;
+
+ if (ok)
+ return CMD_SUCCESS ;
+
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe out'") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If token is incomplete make a copy of it and '\0' terminate.
+ *
+ * If if contains quotes or escapes process those down.
+ *
+ * For Quagga purposes, the following is done:
+ *
+ * inside '...': all characters stand for themselves, except '\t' -> ' ',
+ * and, of course, the terminating '.
+ *
+ * This is just like the shell.
+ *
+ * inside "...": all characters stand for themselves, except '\t' -> ' ',
+ * and, of course, the terminating ", plus the following
+ * \x escapes are processed:
+ *
+ * \" -> " -- so can have " in "...."
+ * \\ -> \ -- so can have \ in "...."
+ * \$ -> $ -- so can have $ in "...."
+ *
+ * outside quotes: all characters stand for themselves, except:
+ *
+ * \sp -> sp -- so can escape the odd space (cf shell)
+ * \tab -> sp -- ditto of tab, but map to space
+ * \? -> ? )
+ * \' -> ' )
+ * \" -> " ) so can escape the odd meta character
+ * \< -> < ) where required or to taste.
+ * \> -> > )
+ * \! -> ! )
+ * \# -> \# )
+ *
+ * NB: with the exception of $ inside of "..." this carefully avoids the
+ * regex meta characters: .*+?^$_ and (|)[-]
+ * and the regex escaped forms of those and the back reference \1 etc.
+ *
+ * $ in "..." is reserved for future use as a variable or other
+ * substitution start.
+ *
+ * Returns: CMD_SUCCESS <=> OK
+ * CMD_ERR_PARSE <=> invalid escape or incomplete quotes
+ *
+ * NB: if fails, returns token completed as far as possible.
+ */
+static cmd_return_code_t
+cmd_token_complete(cmd_parsed parsed, cmd_token t)
+{
+ cpp_t p ;
+ pp_t q ;
+ bool dq ;
+ cmd_return_code_t ret ;
+
+ if ((t->type & cmd_tok_incomplete) == 0)
+ return CMD_SUCCESS ; /* Quick out if nothing to do */
+
+ /* To process quotes etc works from the elstring that points to
+ * the original line, to the qstring.
+ *
+ * For quotes and escapes, the result is always no longer than the
+ * original.
+ */
+ els_cpp_nn(p, t->ot) ; /* original token */
+
+ qs_new_size(t->qs, p->e - p->p) ; /* discard alias & set qs to be
+ big enough for original */
+ qs_pp_nn(q, t->qs) ; /* where to complete token to */
+
+ ret = CMD_SUCCESS ;
+ dq = false ;
+ while (p->p < p->e)
+ {
+ switch (*p->p)
+ {
+ case '\t':
+ *q->p++ = ' ' ; /* '\t' -> ' ' */
+ ++p->p ;
+ break ;
+
+ case '\'':
+ ++p->p ; /* skip leading ' */
+ while (1)
+ {
+ if (p->p == p->e)
+ {
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing '") ;
+ break ;
+ } ;
+
+ if (*p->p == '\'')
+ {
+ ++p->p ; /* skip trailing ' */
+ break ; /* done '....' */
+ } ;
+
+ if (*p->p == '\t')
+ {
+ *q->p++ = ' ' ; /* '\t' -> ' ' */
+ ++p->p ;
+ }
+ else
+ *q->p++ = *p->p++ ; /* rest as is */
+ } ;
+ break ;
+
+ case '"':
+ ++p->p ; /* skip " */
+ dq = !dq ; /* switch state */
+ break ;
+
+ case '\\':
+ *q->p++ = *p->p++ ; /* copy the \ */
+ if (p->p == p->e)
+ ret = cmd_set_parse_error(parsed, t, 0, "trailing \\") ;
+ else
+ {
+ if (dq)
+ {
+ /* inside "...": \", \$ and \\ only */
+ if ((*p->p == '"') || (*p->p == '$') || (*p->p == '\\'))
+ --q->p ; /* strip the \ */
+ }
+ else
+ {
+ /* outside quotes: \sp \tab \< \> \! \# */
+ if ( (*p->p == '\t') || (*p->p == ' ') ||
+ (*p->p == '\'') || (*p->p == '"') ||
+ (*p->p == '<') || (*p->p == '>') ||
+ (*p->p == '!') || (*p->p == '#') )
+ --q->p ; /* strip the \ */
+ } ;
+
+ if (*p->p != '\t')
+ *q->p++ = *p->p++ ;
+ else
+ {
+ *q->p++ = ' ' ;
+ ++p->p ;
+ } ;
+ } ;
+ break ;
+
+ default:
+ *q->p++ = *p->p++ ;
+ break ;
+ } ;
+ } ;
+
+ if (dq)
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing \"") ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ *q->p = '\0' ; /* '\0' terminate */
+ qs_set_len_nn(t->qs, q->p - qs_char_nn(t->qs)) ;
+ t->term = true ;
+ }
+ else
+ {
+ qs_set_alias_els(t->qs, t->ot) ;
+ } ;
+
+ return ret ;
+}
+
+/*------------------------------------------------------------------------------
+ * Tokenize the given line and work out where cursor is wrt tokens.
+ *
+ * Note that this is implicitly "full lex".
+ *
+ * Looks for first token whose end is at or beyond the cursor position. Note
+ * that:
+ *
+ * * where the cursor is just after the last character of a token, it is at
+ * the end of that token.
+ *
+ * * where there is more than one space between tokens, and the cursor is
+ * after the first space, then it is deemed to be "on" the second token.
+ *
+ * - if there are spaces at the start of the line and the cursor is on
+ * one of those spaces, the it is "on" the first token.
+ *
+ * - if the line is blank, with zero or more spaces, the cursor is "on"
+ * the eol token.
+ *
+ * Note that where a line ends in a comment, there are no trailing spaces.
+ *
+ * Returns: true <=> "in a special place"
+ *
+ * Is "in a special place" if the cursor is:
+ *
+ * a. in a quoted string of any type
+ * b. in an escape (so immediately after a '\')
+ * c. after the '!' or '#' on a line which consists only of a comment
+ * d. after the first '<' or '>' of the first pipe token on the line
+ *
+ * If NOT "in a special place", will set:
+ *
+ * * parsed->cti -- cursor token index
+ * * parsed->rp -- cursor position relative to the start of token
+ *
+ * Note that the cursor token may be a comment or pipe token, or the eol token
+ * at the end of the line.
+ */
+extern bool
+cmd_token_position(cmd_parsed parsed, qstring line)
+{
+ cmd_token t ;
+ uint ti ;
+ uint cp, ep ;
+
+ cpp_t p ;
+ const char* q ;
+ const char* e ;
+ bool sq, dq, bs ;
+
+ /* Re-initialise parsed object and tokenize the given line */
+ cmd_tokenize(parsed, line, true) ;
+
+ /* Get the cursor position */
+ cp = qs_cp_nn(line) ;
+
+ /* Look for the last token whose end is <= cp
+ *
+ * Will position on last, "eol" token -- which is not counted in
+ * parsed->num_tokens -- if is beyond the last real token on the line.
+ */
+ t = NULL ;
+ ti = 0 ;
+ while (1)
+ {
+ t = cmd_token_get(parsed->tokens, ti) ;
+
+ if (cp > t->tp)
+ {
+ /* As soon as we are past '|', '<', '>', '!' or '#'
+ * return "special"
+ */
+ if ((t->type & ( cmd_tok_in_pipe
+ | cmd_tok_out_pipe
+ | cmd_tok_out_shell
+ | cmd_tok_comment) ) != 0)
+ return true ;
+ } ;
+
+ ep = t->tp + els_len_nn(t->ot) ;
+
+ if ((cp <= ep) || (ti == parsed->num_tokens))
+ break ; /* stop when found token (or at eol) */
+
+ ++ti ;
+ } ;
+
+ parsed->cti = ti ;
+ parsed->ctl = els_len_nn(t->ot) ;
+ parsed->rp = (int)cp - (int)t->tp ;
+
+ /* Arrive with t = token in which cp belongs
+ *
+ * If the token is incomplete, then need to check for "ins" -- unless is
+ * already "ins".
+ */
+ if ((t->type & cmd_tok_incomplete) == 0)
+ return false ;
+
+ /* Scan to see if in '...' or "..." or after '\'. */
+
+ els_cpp_nn(p, t->ot) ; /* original token */
+
+ q = p->p + (cp - t->tp) ; /* position interested in */
+ assert(q > p->p) ;
+
+ dq = false ;
+ sq = false ;
+ bs = false ;
+
+ e = (q <= p->e) ? q : p->e ; /* stop at q or end of token */
+
+ while (p->p < e)
+ {
+ switch (*p->p)
+ {
+ case '\'':
+ if (!dq && !bs) /* ignore ' inside "..." & after \ */
+ sq = !sq ;
+ break ;
+
+ case '\"': /* ignore " inside '...' & after \ */
+ if (!sq && !bs)
+ dq = !dq ;
+ break ;
+
+ case '\\':
+ if (!sq) /* ignore \ inside '...' */
+ bs = !bs ; /* cope with \\ */
+ break ;
+
+ default:
+ break ;
+ } ;
+
+ ++p->p ;
+ } ;
+
+ /* Have scanned to but excluding current character or end of token, whichever
+ * came first.
+ *
+ * If in '...' or "...", then is "ins"
+ * If is immediately after \, then is "ins".
+ */
+ return sq || dq || (bs && (p->e <= q)) ;
+} ;
+
+/*==============================================================================
+ * Match functions.
+ *
+ * Is the given string a, possibly incomplete, value of the required kind ?
+ */
+
+static match_strength_t cmd_ipv4_match(const char* cp, uint prefix) ;
+static match_strength_t cmd_prefix_match(const char* cp, uint prefix) ;
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv4 Address
+ *
+ * 999.999.999.999 -- each 0..255, no leading zeros, decimal only.
+ *
+ * Returns: mt_no_match -- improperly formed
+ * mt_ipv4_address_partial -- OK as far as it goes (or empty)
+ * mt_ipv4_address_complete -- syntactically complete
+ */
+static match_type_t
+cmd_ipv4_address_match(cmd_token t)
+{
+ match_strength_t ms ;
+ match_type_t mt ;
+
+ if ((t->seen & item_ipv4_address_bit) != 0)
+ return t->match[item_ipv4_address] ;
+
+ ms = cmd_ipv4_match(cmd_token_make_string(t), 0) ;
+
+ if (ms == ms_var_complete)
+ mt = mt_ipv4_address_complete ;
+ else if (ms == ms_partial)
+ mt = mt_ipv4_address_partial ;
+ else
+ mt = mt_no_match ;
+
+ t->seen |= item_ipv4_address_bit ;
+
+ return t->match[item_ipv4_address] = mt ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv4 Prefix
+ *
+ * 999.999.999.999/99 -- each 0..255, no leading zeros, decimal only.
+ * and prefix length must be <= 32
+ *
+ * Returns: mt_no_match -- improperly formed
+ * mt_ipv4_prefix_partial -- OK as far as it goes (or empty)
+ * mt_ipv4_prefix_complete -- syntactically complete
+ *
+ * NB: partly_match is returned for anything valid before the '/', but which
+ * has no '/' or no number after the '/'.
+ */
+static match_type_t
+cmd_ipv4_prefix_match(cmd_token t)
+{
+ match_strength_t ms ;
+ match_type_t mt ;
+
+ if ((t->seen & item_ipv4_prefix_bit) != 0)
+ return t->match[item_ipv4_prefix] ;
+
+ ms = cmd_ipv4_match(cmd_token_make_string(t), 32) ;
+
+ if (ms == ms_var_complete)
+ mt = mt_ipv4_prefix_complete ;
+ else if (ms == ms_partial)
+ mt = mt_ipv4_prefix_partial ;
+ else
+ mt = mt_no_match ;
+
+ t->seen |= item_ipv4_prefix_bit ;
+
+ return t->match[item_ipv4_prefix] = mt ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv4 Address or Prefix:
+ *
+ * 999.999.999.999[/99] -- each 0..255, no leading zeros, decimal only.
+ * and prefix length must be <= n
+ *
+ * Returns: ms_no_match -- improperly formed
+ * ms_partial -- OK as far as it goes (or empty)
+ * ms_var_complete -- syntactically complete
+ *
+ * NB: partly_match is returned for anything valid before the '/', but which
+ * has no '/' or no number after the '/'.
+ */
+static match_strength_t
+cmd_ipv4_match(const char* cp, uint prefix)
+{
+ uint nums ;
+
+ for (nums = 0 ; nums < 4 ; ++nums)
+ {
+ if (*cp == '.') /* need a '.' except at start */
+ {
+ if (nums == 0)
+ return ms_no_match ;
+
+ ++cp ; /* step past '.' */
+ }
+ else
+ {
+ if (nums != 0)
+ return (*cp == '\0') ? ms_partial : ms_no_match ;
+ } ;
+
+ /* Collect a decimal number 0..255, no leading zeros.
+ *
+ * Rejects anything other than digits -- including '/' and '.'.
+ *
+ * Accepts '\0' as partial -- which accepts empty strings.
+ */
+ if (*cp == '0')
+ {
+ ++cp ;
+ if (isdigit(*cp))
+ return ms_no_match ; /* reject leading zeros */
+ }
+ else
+ {
+ char* ep ;
+
+ if (isdigit(*cp))
+ {
+ if (strtoul(cp, &ep, 10) <= 255)
+ cp = ep ;
+ else
+ return ms_no_match ; /* reject invalid number */
+ }
+ else
+ return (*cp == '\0') ? ms_partial : ms_no_match ;
+ } ;
+ } ;
+
+ /* Arrive here with 4 numbers */
+
+ if (prefix == 0)
+ return (*cp == '\0') ? ms_var_complete : ms_no_match ;
+ else
+ return cmd_prefix_match(cp, prefix) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is this a Prefix:
+ *
+ * /99 no leading zeros, decimal only, value must be <= n.
+ *
+ * Arrives here with *cp pointing at where there should be a '/'.
+ *
+ * Returns: ms_no_match -- improperly formed
+ * ms_partial -- OK as far as it goes (or empty).
+ * ms_var_complete -- syntactically complete
+ */
+static match_strength_t
+cmd_prefix_match(const char* cp, uint prefix)
+{
+ if (*cp != '/')
+ return (*cp == '\0') ? ms_partial : ms_no_match ;
+
+ /* OK have '/' and a prefix is now expected. */
+
+ ++cp ; /* step past '/' */
+
+ if (*cp == '\0')
+ return ms_partial ; /* if nothing after '/' */
+
+ if (*cp == '0')
+ ++cp ;
+ else
+ {
+ char* ep ;
+ if (isdigit(*cp) && (strtoul(cp, &ep, 10) <= prefix))
+ cp = ep ;
+ else
+ return ms_no_match ; /* reject invalid number */
+ } ;
+
+ if (*cp != '\0')
+ return ms_no_match ; /* something other than digits after the '/',
+ or leading zero, or number too big */
+
+ return ms_var_complete ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * IPv6 Address and Prefix matching.
+ */
+
+#ifdef HAVE_IPV6
+
+static match_strength_t cmd_ipv6_match(const char* cp, uint prefix) ;
+
+
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv6 Address
+ *
+ * h:h:... -- RFC 4291 rules & prefix length must be <= n
+ *
+ * Returns: mt_no_match -- improperly formed
+ * mt_ipv6_address_partial -- OK as far as it goes (or empty)
+ * mt_ipv6_address_complete -- syntactically complete
+ *
+ * NB: partly_match is returned for anything valid before the '/', but which
+ * has no '/' or no number after the '/'.
+ */
+static match_type_t
+cmd_ipv6_address_match (cmd_token t)
+{
+ match_strength_t ms ;
+ match_type_t mt ;
+
+ if ((t->seen & item_ipv6_address_bit) != 0)
+ return t->match[item_ipv6_address] ;
+
+ ms = cmd_ipv6_match(cmd_token_make_string(t), 0) ;
+
+ if (ms == ms_var_complete)
+ mt = mt_ipv6_address_complete ;
+ else if (ms == ms_partial)
+ mt = mt_ipv6_address_partial ;
+ else
+ mt = mt_no_match ;
+
+ t->seen |= item_ipv6_address_bit ;
+
+ return t->match[item_ipv6_address] = mt ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv6 Prefix
+ *
+ * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= 128
+ *
+ * Returns: mt_no_match -- improperly formed
+ * mt_ipv6_prefix_partial -- OK as far as it goes (or empty)
+ * mt_ipv6_prefix_complete -- syntactically complete
+ *
+ * NB: partly_match is returned for anything valid before the '/', but which
+ * has no '/' or no number after the '/'.
+ */
+static match_type_t
+cmd_ipv6_prefix_match(cmd_token t)
+{
+ match_strength_t ms ;
+ match_type_t mt ;
+
+ if ((t->seen & item_ipv6_prefix_bit) != 0)
+ return t->match[item_ipv6_prefix] ;
+
+ ms = cmd_ipv6_match(cmd_token_make_string(t), 128) ;
+
+ if (ms == ms_var_complete)
+ mt = mt_ipv6_prefix_complete ;
+ else if (ms == ms_partial)
+ mt = mt_ipv6_prefix_partial ;
+ else
+ mt = mt_no_match ;
+
+ t->seen |= item_ipv6_prefix_bit ;
+
+ return t->match[item_ipv6_prefix] = mt ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is this an IPv6 Address or Prefix:
+ *
+ * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= n
+ *
+ * Returns: ms_no_match -- improperly formed
+ * ms_partial -- OK as far as it goes (or empty)
+ * ms_var_complete -- syntactically complete
+ *
+ * NB: partly_match is returned for anything valid before the '/', but which
+ * has no '/' or no number after the '/'.
+ */
+static match_strength_t
+cmd_ipv6_match(const char* cp, uint prefix)
+{
+ bool double_colon ;
+ uint nums ;
+
+ double_colon = false ;
+ nums = 0 ;
+
+ /* At start, the first time around the loop... */
+
+ for (nums = 0 ; nums < 8 ; ++nums)
+ {
+ const char* sp, * ep ;
+
+ /* After number (nums != 0), or at start.
+ *
+ * Deal with (a) ':', '::' and '::/'.
+ * (b) '/' -- valid only if had '::'.
+ * (c) '\0' -- partial unless have had '::'
+ * (d) if not at start, must have one of the above.
+ */
+ if (*cp == ':')
+ {
+ /* (a) ':', '::' and '::/'.
+ *
+ * At start can accept '::', but not ':' (unless '\0' follows).
+ *
+ * If not at start, accept ':' and accept '::' if not already seen.
+ *
+ * After '::' can have the full complement of numbers, or '/' or
+ * '\0' which bring the number part to an end.
+ *
+ * After ':' we accept '\0' but explicitly reject '/' (and we
+ * reject a ':' at the start if not followed by '\0').
+ */
+ ++cp ; /* step past ':' */
+
+ if (*cp == ':')
+ {
+ /* '::' -- counts as number, can be followed by '/' */
+
+ if (double_colon)
+ return ms_no_match ; /* at most one */
+
+ ++cp ; /* step past '::' */
+
+ double_colon = true ;
+ ++nums ; /* counts as a number */
+
+ if ((nums == 8) || (*cp == '/') || (*cp == '\0'))
+ break ; /* no more numbers */
+ }
+ else if (*cp == '\0')
+ return ms_partial ; /* accepts bare ':', inter alia */
+
+ else if ((*cp == '/') || (nums == 0))
+ return ms_no_match ;
+ }
+ else if (*cp == '/')
+ {
+ /* (b) '/' -- valid only if had '::'. */
+ if (double_colon)
+ break ;
+ else
+ return ms_no_match ;
+ }
+ else if (*cp == '\0')
+ {
+ /* (c) '\0' -- partial unless have had '::' */
+ if (double_colon)
+ break ;
+ else
+ return ms_partial ; /* accept empty string, inter alia */
+ }
+ else if (nums != 0)
+ /* (d) if not at start, must have one of the above. */
+ return ms_no_match ;
+
+ assert(*cp != '\0') ;
+
+ /* Is now at start, or after ':' and is not '\0'.
+ *
+ * Require 1..4 hex digits -- will also accept 1..3 decimals !
+ *
+ * Rejects anything else, including '/' at this stage.
+ */
+ sp = cp ;
+ ep = cp + 4 ;
+ do
+ {
+ if (((*cp >= '0') && (*cp <= '9')) || ((*cp >= 'A') && (*cp <= 'F'))
+ || ((*cp >= 'a') && (*cp <= 'f')))
+ ++cp ;
+ else
+ {
+ if (cp > sp)
+ break ;
+ else
+ return ms_no_match ; /* no digits */
+ }
+ }
+ while (cp < ep) ;
+
+ /* Watch out for '.' ! */
+
+ if (*cp == '.')
+ {
+ /* Can have IPv4 trailing part, if that would account for the
+ * last two number parts of the IPv6.
+ *
+ * Note that a '.' after something which is not simple decimal
+ * 0..255 will be rejected by cmd_ipv4_match().
+ *
+ * Note also that we pass through the prefix requirement.
+ */
+ if ((nums == 6) || (double_colon && (nums < 6)))
+ return cmd_ipv4_match(sp, prefix) ;
+ else
+ return ms_no_match ;
+ } ;
+ } ;
+
+ /* Arrives here either because nums == 8, or met '/' or '\0' after '::
+ *
+ * So only get here if have a valid end of the digits part of the IPv6
+ */
+
+ assert((nums == 8) || double_colon) ;
+
+ if (prefix == 0)
+ return (*cp == '\0') ? ms_var_complete : ms_no_match ;
+ else
+ return cmd_prefix_match(cp, prefix) ;
+} ;
+
+
+#endif /* HAVE_IPV6 */
+
+/*------------------------------------------------------------------------------
+ * Is this a decimal number in the allowed range:
+ *
+ * Returns: mt_no_match -- improperly formed or empty
+ * mt_range_partial -- OK as far as it went (or empty string)
+ * mt_range_complete -- syntactically complete
+ */
+static match_type_t
+cmd_range_match (cmd_item item, cmd_token t)
+{
+ const char* cp, * dp ;
+ char *ep ;
+ int base ;
+ long val;
+
+ confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ;
+
+ cp = cmd_token_make_string(t) ;
+
+ /* Worry about any sign */
+
+ dp = cp ;
+
+ if ((*cp == '-') || (*cp == '+'))
+ {
+ if (!item->range_sign_allowed)
+ return mt_no_match ; /* reject '-' or '+' */
+
+ ++dp ; /* step to digit */
+ }
+ else
+ {
+ if (item->range_sign_required)
+ return mt_no_match ;
+ } ;
+
+ /* Worry about leading digits and hex, and no digits at all */
+
+ base = 10 ; /* by default. */
+
+ if (*dp == '\0')
+ return mt_range_partial ; /* accepts empty string, inter alia */
+
+ else if (*dp == '0')
+ {
+ ++dp ; /* step past zero */
+
+ if (*dp != '\0')
+ {
+ /* No leading zeros and no stinking octal -- but allow hex */
+ if ((*dp != 'x') && (*dp != 'X'))
+ return mt_no_match ;
+
+ ++dp ; /* step past 'x' or 'X' */
+ base = 16 ;
+
+ if (*dp == '\0')
+ return mt_range_partial ;
+ } ;
+ }
+
+ else if (!isdigit(*dp))
+ return mt_no_match ;
+
+ /* The string starts with digit, possibly preceded by sign, and possibly
+ * an 'x' or 'X' with at least 1 further character.
+ */
+ val = strtol(cp, &ep, base) ;
+ if (*ep != '\0')
+ return mt_no_match ;
+
+ /* Is the result in range ? */
+
+ if (val >= item->range_min && val <= item->range_max)
+ return mt_range_complete ; /* on the money */
+
+ /* Want to return mt_range_partial iff adding digits might make
+ * an in range value.
+ *
+ * If val is < 0, then adding digits makes it smaller.
+ * If val is == 0, not allowed to add digits.
+ * If val is > 0, then adding digits makes it bigger.
+ */
+ if (val < item->range_min)
+ {
+ /* Is less than minimum, so partial match if can get bigger. */
+ return (val > 0) ? mt_range_partial : mt_no_match ;
+ }
+ else
+ {
+ /* Is more than maximum, so partial match if can get smaller. */
+ return (val < 0) ? mt_range_partial : mt_no_match ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Command "filtering".
+ *
+ * The command parsing process starts with a (shallow) copy of the cmd_vector
+ * entry for the current "node".
+ *
+ * So cmd_v contains pointers to struct cmd_command values. When match fails,
+ * the pointer is set NULL -- so parsing is a process of reducing the cmd_v
+ * down to just the entries that match.
+ *
+ * Each cmd_command has a vector "items", which contains an entry for each
+ * "token" position. That entry is a vector containing the possible values at
+ * that position.
+ */
+
+static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ;
+
+/*------------------------------------------------------------------------------
+ * Prepare to filter commands in the node being parsed in.
+ *
+ * The execute option turns off all partial matching -- so will not match, say,
+ * 222 as a possible IP address ! This means that the result of the filter
+ * operation will be executable command(s), only.
+ *
+ * The execute option also pre-filters the command vector to discard all
+ * commands which are too short or too long to match the current line.
+ *
+ * The strict option turns off partial matching of keywords, so only complete
+ * keywords will do. This is used for the configuration file, so that new
+ * commands can be added !
+ *
+ * Returns: number of commands which may match to.
+ */
+static uint
+cmd_filter_prepare(cmd_parsed parsed, cmd_context context)
+{
+ vector src_v ;
+ uint ci ;
+ uint nct ;
+
+ /* get commands for the current node */
+ src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode))->cmd_vector ;
+
+ assert(src_v != NULL) ; /* invalid parsed->cnode ?? */
+
+ /* Empty the working commands vector, making sure big enough for the current
+ * node's commands -- creates vector if required.
+ *
+ * Note that the cmd_v lives for as long as the parsed object, so will
+ * grow over time to accommodate what is required.
+ */
+ parsed->cmd_v = vector_re_init(parsed->cmd_v, vector_length(src_v)) ;
+
+ /* Filter out commands which are too short.
+ *
+ * For execution, can filter out commands which are too long.
+ */
+ nct = parsed->num_command ;
+
+ for (ci = 0 ; ci < vector_length(src_v) ; ++ci)
+ {
+ cmd_command cmd ;
+
+ cmd = vector_get_item(src_v, ci) ;
+
+ if ( cmd->nt_max < nct)
+ continue ; /* ignore if too short */
+
+ if ((cmd->nt_min > nct) && context->parse_execution)
+ continue ; /* ignore if too long */
+
+ vector_push_item(parsed->cmd_v, cmd) ;
+ } ;
+
+ /* Other preparation for filtering
+ *
+ * The min_strength is set according to whether is for execution. The
+ * strongest match is set from that. This means that any match which does
+ * not meet the minimum criterion is automatically excluded.
+ */
+
+ parsed->min_strength = context->parse_execution ? ms_min_execute
+ : ms_min_parse ;
+ parsed->strict = context->parse_strict ;
+
+ parsed->strongest = parsed->min_strength ;
+ parsed->best_complete = mt_no_match ;
+
+ return vector_length(parsed->cmd_v) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Filter set commands by matching items against the given token.
+ *
+ * Takes: parsed -- cmd_parsed object, previously prepared by
+ * cmd_prepare_fiter().
+ * ii -- item index (0 == first)
+ * keep_items -- how to filter
+ *
+ * The item index is the index wrt the start of the commands being matched,
+ * not the index of the token in the command line.
+ *
+ * Keywords must match strictly or partly, depending on the filtering state
+ * in the parsed object.
+ *
+ * Variables must match completely if filtering for execution, otherwise may
+ * match partially.
+ *
+ * Returns: the number of items matched.
+ *
+ * NB: when filtering for execution, will not accept any partial matches for
+ * variables (but may accept partial matches for keywords). So, once the
+ * last token has been filtered against, the number of items matched gives
+ * the number of unambiguous commands, with complete values, that the line
+ * matches.
+ */
+static uint
+cmd_filter(cmd_parsed parsed, uint ii, bool keep_items)
+{
+ uint ci ; /* command index -- in cmd_v */
+ uint ti ; /* token index in command line */
+ uint c_keep ;
+ uint i_keep ;
+ cmd_token t ;
+
+ /* Reset the filtering state */
+
+ parsed->strongest = parsed->min_strength ;
+ parsed->best_complete = mt_no_match ;
+
+ /* Decide which token we are trying to match. */
+ if (ii < parsed->num_command)
+ ti = parsed->first_command + ii ; /* map to token index */
+ else
+ {
+ /* special case of filtering against the empty token at the end of
+ * the command line -- this is for command line help stuff.
+ *
+ * Note that there may be out_pipe or even comment tokens in between,
+ * so we here set the ti to the trailing eol token.
+ */
+ assert(ii == parsed->num_command) ;
+ ti = parsed->num_tokens ;
+ }
+ t = cmd_token_get(parsed->tokens, ti) ;
+
+ /* If we are keeping the items which are matched, set the item_v
+ * empty and guess that may get one item per command -- creates the item_v
+ * vector if required..
+ *
+ * Note that the item_v lives for as long as the parsed object, so will
+ * grow over time to accommodate what is required.
+ */
+ if (keep_items)
+ parsed->item_v = vector_re_init(parsed->item_v,
+ vector_length(parsed->cmd_v)) ;
+
+ /* Work down the cmd_v, attempting to match cmd_items against the cmd_token.
+ *
+ * Keep in the cmd_v the commands for which we get a match.
+ *
+ * At the same time, if required, keep in the item_v all the items which have
+ * matched.
+ */
+ c_keep = 0 ;
+ i_keep = 0 ;
+
+ for (ci = 0; ci < vector_length(parsed->cmd_v); ci++)
+ {
+ cmd_command cmd ;
+ cmd_item item ;
+ int best ;
+
+ cmd = vector_get_item(parsed->cmd_v, ci) ;
+
+ if (ii < cmd->nt)
+ item = vector_get_item(cmd->items, ii) ;
+
+ else if (ii == cmd->nt_max)
+ item = &eol_item ; /* match at end of line */
+
+ else if (ii > cmd->nt_max)
+ continue ; /* discard commands we are beyond, now */
+
+ else /* cmd->nt < cmd->nt_max <=> vararg. */
+ {
+ /* It is elsewhere arranged that a vararg is always the last
+ * item for a given command.
+ *
+ * We cope with all tokens from the first vararg onwards by matching
+ * them all against the vararg. Inter alia this allows for a
+ * vararg to check each argument in turn -- iff feel like doing
+ * that.
+ */
+ item = cmd->vararg ;
+
+ /* Must have something and must now only check against the varag.
+ * (Not anything else in the (...) if vararg was in one !)
+ */
+ assert((item != NULL) && (item->next == NULL)) ;
+ } ;
+
+ /* See if get any sort of match at current position */
+ best = -1 ;
+ while (item != NULL)
+ {
+ int ret ;
+ ret = cmd_item_filter(parsed, item, t) ;
+
+ if (ret >= 0)
+ {
+ if (ret > 0)
+ i_keep = 0 ;
+
+ if (keep_items)
+ vector_set_item(parsed->item_v, i_keep++, item) ;
+ else
+ ++i_keep ;
+ } ;
+
+ if (ret > best)
+ best = ret ;
+
+ item = item->next ;
+ } ;
+
+ /* Keep if had a match */
+ if (best >= 0)
+ {
+ if (best > 0)
+ c_keep = 0 ; /* better than all the rest */
+
+ vector_set_item(parsed->cmd_v, c_keep++, cmd) ;
+ } ;
+ } ;
+
+ if (keep_items)
+ vector_set_length(parsed->item_v, i_keep) ; /* discard what did not keep */
+
+ vector_set_length(parsed->cmd_v, c_keep) ; /* discard what did not keep */
+
+ return i_keep ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Filter given item, in given parsed state, attempting to match given token.
+ *
+ * Update the parsed state if get a better match.
+ *
+ * Returns: < 0 => is not as good )
+ * == 0 => equally good ) compared to best match to date.
+ * > 0 => better than )
+ *
+ * NB: the matching functions will partially match an empty string.
+ *
+ * The ms_anything types of item, will match an empty string.
+ */
+static int
+cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t)
+{
+ match_strength_t ms ;
+ match_type_t mt ;
+ int cw ;
+
+ /* Ignore item if the best we can hope for is not as strong as what we
+ * have already.
+ */
+ mt = item_best_match(item->type) ;
+ ms = match_match_strength(mt) ;
+
+ if ((mt < parsed->best_complete) || (ms < parsed->strongest))
+ return -1 ; /* cannot even be as good */
+
+ /* Bang the rocks together to get match type
+ *
+ * TODO: do we need mt_xxx_partial etc ?
+ */
+ if (t->type == cmd_tok_eol)
+ {
+ mt = (item->type == item_eol) ? mt_eol : mt_eol_partial ;
+ mt = mt_eol_partial ;
+ }
+ else
+ {
+ switch (item->type)
+ {
+ case item_null:
+ zabort("invalid item_null") ;
+ break ;
+
+ case item_eol:
+ break ;
+
+ case item_keyword:
+ if ((t->word == els_body_nn(item->str))
+ && (t->w_len == els_len_nn(item->str)))
+ cw = t->cw ;
+ else
+ {
+ cw = els_cmp_word(t->ot, item->str) ;
+ t->word = els_body_nn(item->str) ;
+ t->w_len = els_len_nn(item->str) ;
+ t->cw = cw ;
+ } ;
+
+ if (cw > 0) /* nope */
+ mt = mt_no_match ;
+ else if (cw == 0) /* exact match */
+ mt = mt_keyword_complete ;
+ else /* partial match */
+ mt = parsed->strict ? mt_no_match
+ : mt_keyword_incomplete ;
+ break ;
+
+ case item_range:
+ mt = cmd_range_match(item, t) ;
+ break ;
+
+ case item_ipv4_address:
+ mt = cmd_ipv4_address_match(t) ;
+ break ;
+
+ case item_ipv4_prefix:
+ mt = cmd_ipv4_prefix_match(t) ;
+ break ;
+
+ case item_ipv6_address:
+ #ifdef HAVE_IPV6
+ mt = cmd_ipv6_address_match(t) ;
+ #endif /* HAVE_IPV6 */
+ break ;
+
+ case item_ipv6_prefix:
+ #ifdef HAVE_IPV6
+ mt = cmd_ipv6_prefix_match(t) ;
+ #endif /* HAVE_IPV6 */
+ break ;
+
+ case item_word:
+ mt = mt_word_match ;
+ break ;
+
+ case item_vararg:
+ mt = mt_vararg_match ;
+ break ;
+
+ case item_option_word:
+ mt = mt_option_word_match ;
+ break ;
+
+ default:
+ zabort("unknown item type") ;
+ } ;
+ } ;
+
+ /* Easy if did not match at all */
+ if (mt == mt_no_match)
+ return -1 ;
+
+ /* Is what we got worse, as good or better ?
+ *
+ * Update parsed to suit and return the news.
+ *
+ * Note that parsed->best_complete will be ms_no_match until parsed->strongest
+ * is set to ms_var_complete.
+ */
+ ms = match_match_strength(mt) ;
+
+ if (ms < parsed->strongest)
+ return -1 ;
+
+ if (ms == parsed->strongest)
+ return 0 ;
+
+ parsed->strongest = ms ;
+ return +1 ;
+} ;
+
+/*==============================================================================
+ * Parsing of command lines
+ */
+
+/*------------------------------------------------------------------------------
+ * Parse a command in the given "node", if possible, ready for execution.
+ *
+ * If 'exact': use cmd_filter_by_string()
+ * otherwise: use cmd_filter_by_completion()
+ *
+ * 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 cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed,
+ cmd_context context) ;
+static cmd_return_code_t cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) ;
+static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed,
+ cmd_context context) ;
+static cmd_return_code_t cmd_parse_specials(cmd_parsed parsed,
+ cmd_context context) ;
+static node_type_t cmd_auth_specials(cmd_context context, node_type_t target) ;
+
+/*------------------------------------------------------------------------------
+ * Parse a command in the given "node", or (if required) any of its ancestors.
+ *
+ * Requires command line to have been tokenized.
+ *
+ * Returns: CMD_SUCCESS => successfully parsed command, and the result is
+ * in the given parsed structure, ready for execution.
+ *
+ * - parsed->cnode may not be the incoming node.
+ *
+ * - parsed->nnode is set for when command succeeds.
+ *
+ * - parsed->parts is what was found
+ *
+ * - parsed->cmd->daemon => daemon
+ *
+ * CMD_EMPTY => line is empty, except perhaps for comment
+ * (iff parsing for execution)
+ *
+ * CMD_ERR_INCOMPLETE => "do" and nothing more
+ * (iff parsing for execution)
+ *
+ * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ??
+ *
+ * CMD_ERR_NO_MATCH => could find nothing to match the command
+ * line.
+ *
+ * CMD_ERR_AMBIGUOUS => found 2 or more possible matches.
+ * Or, if not parsing for execution, there
+ * were no command tokens.
+ *
+ * CMD_ERR_PARSING => something wrong ! See parsed->emess.
+ *
+ * NB: unless cmd_parse_no_tree, may have tried any ancestor nodes. Returns
+ * with parsed->cnode with node last tried.
+ *
+ * NB: unless cmd_parse_no_do, will have taken a leading "do", and pushed the
+ * parsed->cnode to ENABLE_NODE (if in MIN_DO_SHORTCUT_NODE or higher).
+ *
+ * NB: the command line MUST be preserved until the parsed command is no
+ * longer required -- no copy is made.
+ *
+ * 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.
+ *
+ * See elsewhere for description of parsed structure.
+ */
+extern cmd_return_code_t
+cmd_parse_command(cmd_parsed parsed, cmd_context context)
+{
+ cmd_return_code_t ret ;
+
+ /* Level 1 parsing
+ *
+ * Break down the tokens into:
+ *
+ * 1) in-pipe or command (with or without "do")
+ * 2) possible out-pipe
+ * 3) possible comment
+ *
+ * Complete any tokens which contain quotes and/or escapes.
+ *
+ * Unless CMD_ERR_PARSING:
+ *
+ * - sort out any "do" and cut from start of command.
+ * - set parsed->cnode (from given node or according to "do")
+ * - set parsed->nnode (from given node)
+ * - set parsed->cmd = NULL
+ * - empty the argv vector
+ *
+ * Note that cmd_parse_phase_one only returns CMD_SUCCESS or CMD_ERR_PARSING.
+ */
+ ret = cmd_parse_phase_one(parsed, context) ;
+ if (ret != CMD_SUCCESS)
+ {
+ assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */
+ return ret ;
+ } ;
+
+ /* If no command tokens, and is parsing for execution, then we are done...
+ * but watch out for bare "do"
+ */
+ if (((parsed->parts & cmd_part_command) == 0) && context->parse_execution)
+ {
+ if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none)
+ return CMD_EMPTY ; /* accept empty */
+
+ if ((parsed->parts & cmd_part_do) != 0)
+ return CMD_ERR_INCOMPLETE ; /* reject "do" alone */
+
+ return CMD_SUCCESS ; /* accept pipes */
+ } ;
+
+ /* Level 2 parsing
+ *
+ * Try in the current node and then in parent nodes, if allowed.
+ *
+ * Will stop moving up the tree when hits a node which is its own parent.
+ * All nodes from NODE_CONFIG up have a parent. All nodes from NODE_ENABLE
+ * down are their own parent.
+ *
+ * Note that when not parsing for execution, may get here with no command
+ * tokens at all -- in which case cmd_parse_phase_two() will return
+ * CMD_ERR_AMBIGUOUS.
+ *
+ * Note that cmd_parse_phase_two only returns CMD_SUCCESS, CMD_ERR_NO_MATCH
+ * or CMD_ERR_AMBIGUOUS.
+ */
+ while (1)
+ {
+ node_type_t pnode ;
+
+ ret = cmd_parse_phase_two(parsed, context) ;
+
+ if (ret == CMD_SUCCESS)
+ break ;
+
+ if ((ret != CMD_ERR_NO_MATCH) || context->parse_no_tree)
+ return ret ;
+
+ pnode = cmd_node_parent(parsed->cnode) ;
+
+ if (pnode == parsed->cnode)
+ return CMD_ERR_NO_MATCH ; /* done if no parent node. */
+
+ parsed->cnode = pnode ;
+ } ;
+
+ /* Parsed successfully -- worry about parsed->nnode.
+ *
+ * Currently parsed->nnode is set to the node we came in on. If the
+ * parsed->cnode is not the same, then we have two choices:
+ *
+ * (a) parsed->cnode is ENABLE_NODE -- either because have 'do' or by tree
+ * walk -- in which case we leave parsed->nnode;
+ *
+ * (b) parsed->cnode is not ENABLE_NODE -- so this command, if successful
+ * will change context, in which cas we need to set parsed->nnode.
+ */
+ if ((parsed->nnode != parsed->cnode) && (parsed->cnode != ENABLE_NODE))
+ parsed->nnode = parsed->cnode ;
+
+ /* Worry about "special" commands and those that set the next node. */
+ if ((parsed->cmd->attr & (CMD_ATTR_NODE | CMD_ATTR_MASK)) != 0)
+ {
+ if ((parsed->cmd->attr & CMD_ATTR_NODE) != 0)
+ parsed->nnode = parsed->cmd->attr & CMD_ATTR_MASK ;
+ else
+ {
+ /* This is a "special" command, which may have some (limited)
+ * semantic restrictions.
+ *
+ * The main thing we are interested in is commands which have
+ * special effects on parsed->nnode (in particular exit and end).
+ */
+ ret = cmd_parse_specials(parsed, context) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+ } ;
+
+ /* If for execution, fill the arg_vector
+ *
+ * The arg_vector is an array of pointers to '\0' terminated strings, which
+ * are pointers to the relevant tokens' qstring bodies.
+ */
+ if (context->parse_execution)
+ {
+ uint ti ;
+
+ cmd_arg_vector_empty(parsed) ;
+
+ for (ti = 0; ti < parsed->num_command ; ti++)
+ {
+ bool take ;
+
+ if (ti < parsed->cmd->nt_min)
+ {
+ cmd_item item = vector_get_item (parsed->cmd->items, ti);
+
+ take = item->arg ; /* follow item */
+ }
+ else
+ take = true ; /* option or vararg token */
+
+ if (take)
+ {
+ cmd_token t ;
+ t = cmd_token_get(parsed->tokens, parsed->first_command + ti) ;
+ cmd_arg_vector_push(parsed, cmd_token_make_string(t)) ;
+ } ;
+ } ;
+ } ;
+
+ /* Return appropriate form of success */
+ return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON
+ : CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Phase 1 of command parsing -- get ready to search the command system.
+ *
+ * Scan the tokens to break them up into the sections:
+ *
+ * - in-pipe -- '<' etc up to '>' or comment
+ * - do -- leading 'do' on the command (empty if in-pipe)
+ * - command -- actual command (empty if in-pipe)
+ * - out-pipe -- '>' etc up to comment
+ * - comment -- '!' or '#' onwards
+ *
+ * Sets parsed->cnode to the given node, or to ENABLE_NODE if have 'do' (and
+ * the original node allows it).
+ *
+ * Sets parsed->nnode to the given node.
+ *
+ * Requires line to have been tokenized -- cmd_tokenize().
+ *
+ * Returns: CMD_SUCCESS -- all is well
+ * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
+ * -- malformed quotes/escapes
+ */
+static cmd_return_code_t
+cmd_parse_phase_one(cmd_parsed parsed, cmd_context context)
+{
+ uint nt = parsed->num_tokens ;
+
+ /* Set command and parsing entries */
+ parsed->nnode = parsed->cnode = context->node ;
+ parsed->cmd = NULL ;
+
+ /* pick off any comment */
+ if ((parsed->tok_total & cmd_tok_comment) != 0)
+ {
+ parsed->num_comment = 1 ;
+ parsed->first_comment = --nt ; /* implicitly the last */
+ parsed->tok_total ^= cmd_tok_comment ;
+ parsed->parts |= cmd_part_comment ;
+ } ;
+
+ /* If this is not a simple line need to do some extra work:
+ *
+ * - identify and check any pipe items
+ * -
+ */
+ if (parsed->tok_total == cmd_tok_simple)
+ {
+ /* All tokens are simple and there is at least one */
+ parsed->first_command = 0 ;
+ parsed->num_command = nt ;
+ parsed->parts |= cmd_part_command ;
+ }
+ else if (parsed->tok_total != 0)
+ {
+ /* There is at least one not-simple cmd_token. */
+ cmd_return_code_t ret ;
+ ret = cmd_parse_phase_one_b(parsed, nt) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ /* If have a have a 'do' at the front, account for it */
+ if ((parsed->parts & cmd_part_command) != 0)
+ {
+ /* Have a command -- worry about "do" if allowed */
+ if (!context->parse_no_do && (context->node >= MIN_DO_SHORTCUT_NODE))
+ {
+ cmd_token t ;
+ t = cmd_token_get(parsed->tokens, parsed->first_command) ;
+ if (els_len_nn(t->ot) == 2)
+ {
+ const char* p = els_body_nn(t->ot) ;
+ if ((*p == 'd') && (*(p+1) == 'o'))
+ {
+ parsed->cnode = ENABLE_NODE ; /* change to this node */
+
+ parsed->num_do = 1 ;
+ parsed->first_do = parsed->first_command ;
+ --parsed->num_command ;
+ ++parsed->first_command ;
+ parsed->parts |= cmd_part_do ;
+ /* If have *only* the "do", we don't have a command */
+ if (parsed->num_command == 0)
+ parsed->parts ^= cmd_part_command ;
+ } ;
+ } ;
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Phase 1b of command parsing
+ *
+ * tokenizer found at least one of:
+ *
+ * - in pipe token
+ * - out pipe token
+ * - token with quotes or escapes
+ *
+ * Deal with all of those, verifying the syntax of any pipes and completing
+ * any tokens with quotes etc.
+ *
+ * Update the "parts" and the relevant first/num values.
+ *
+ * Note that the number of tokens (nt) *excludes* any comment token.
+ *
+ * Returns: CMD_SUCCESS -- all is well
+ * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
+ * -- malformed quotes/escapes
+ */
+static cmd_return_code_t
+cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
+{
+ cmd_return_code_t ret ;
+ cmd_token t ;
+ cmd_parts_t parts ;
+ uint i, n ;
+ uint* pn ;
+
+ parts = cmd_parts_none ; /* no parts yet */
+ n = 0 ; /* no tokens in current part */
+ pn = NULL ; /* no current part */
+
+ /* Walk the tokens, establishing the start and end of:
+ *
+ * - command part )
+ * - in pipe part ) exclusive
+ * - out shell part )
+ * - out pipe part which may only follow command or in pipe
+ *
+ *
+ */
+ for (i = 0 ; i < nt ; ++i)
+ {
+ t = cmd_token_get(parsed->tokens, i) ;
+
+ /* Simple tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_command.
+ */
+ if ((t->type & cmd_tok_simple) != 0)
+ {
+ if (parts == cmd_parts_none)
+ {
+ parts = cmd_part_command ;
+ parsed->first_command = i ;
+ pn = &parsed->num_command ;
+ n = 0 ;
+ } ;
+ }
+
+ /* '<' types of token can appear anywhere, except in a cmd_part_command.
+ *
+ * If this is the first token, start a cmd_part_in_pipe.
+ */
+ else if ((t->type & cmd_tok_in_pipe) != 0)
+ {
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe in'") ;
+
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_in_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ parts = cmd_part_in_pipe ;
+ parsed->first_in_pipe = i ;
+ pn = &parsed->num_in_pipe ;
+ n = 0 ;
+ }
+ }
+
+ /* '|' tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_shell) != 0)
+ {
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'shell pipe'") ;
+
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ parts = cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
+ }
+
+ /* '>' types of token can appear anywhere, except on an empty line.
+ *
+ * If not already in cmd_part_out_pipe, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_pipe) != 0)
+ {
+ if (parts == cmd_parts_none)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe out'") ;
+
+ if ((parts & cmd_part_out_pipe) == 0)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ *pn = n ; /* set number of in-pipe/cmd */
+
+ parts |= cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
+ } ;
+
+ assert(parts != cmd_parts_none) ; /* dealt with all token types */
+
+ /* Now deal with any "incompleteness" */
+
+ if ((t->type & cmd_tok_incomplete) != 0)
+ {
+ ret = cmd_token_complete(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ ++n ; /* count up tokens */
+ } ;
+
+ if (pn != NULL)
+ *pn = n ; /* number in last phase */
+
+ /* If have an in-pipe or an out-pipe, worry about the number of
+ * arguments
+ */
+ if ((parts & cmd_parts_pipe) != 0)
+ {
+ const char* msg = NULL ;
+ uint i ;
+ bool e ;
+
+ /* If there is an in_pipe part, check number of arguments */
+ if ((msg == NULL) && ((parts & cmd_part_in_pipe) != 0))
+ {
+ assert(parsed->num_in_pipe > 0) ;
+
+ if (((parsed->in_pipe & cmd_pipe_file) != 0)
+ && (parsed->num_in_pipe != 2))
+ {
+ if (parsed->num_in_pipe == 1)
+ {
+ i = parsed->first_in_pipe ;
+ e = true ;
+ msg = "requires file" ;
+ }
+ else
+ {
+ i = parsed->first_in_pipe + 2 ;
+ e = false ;
+ msg = "expects file only" ;
+ } ;
+ } ;
+
+ if (((parsed->in_pipe & cmd_pipe_shell) != 0)
+ && (parsed->num_in_pipe < 2))
+ {
+ i = parsed->first_in_pipe ;
+ e = true ;
+ msg = "requires shell command" ;
+ } ;
+ } ;
+
+ /* If there is an out_pipe part, check the number of arguments */
+ if ((msg == NULL) && ((parts & cmd_part_out_pipe) != 0))
+ {
+ assert(parsed->num_out_pipe > 0) ;
+
+ if (((parsed->out_pipe & cmd_pipe_file) != 0)
+ && (parsed->num_out_pipe != 2))
+ {
+ if (parsed->num_out_pipe == 1)
+ {
+ i = parsed->first_out_pipe ;
+ e = true ;
+ msg = "requires file" ;
+ }
+ else
+ {
+ i = parsed->first_out_pipe + 2 ;
+ e = false ;
+ msg = "expects file only" ;
+ } ;
+ } ;
+
+ if (((parsed->out_pipe & cmd_pipe_shell) != 0)
+ && (parsed->num_out_pipe < 2))
+ {
+ assert(parsed->num_out_pipe > 0) ;
+
+ i = parsed->first_out_pipe ;
+ e = true ;
+ msg = "requires shell command" ;
+ } ;
+ } ;
+
+ if (msg != NULL)
+ {
+ t = cmd_token_get(parsed->tokens, i) ;
+ return cmd_set_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ;
+ } ;
+ } ;
+
+ /* It's OK -- so record the parts found and return success */
+ parsed->parts |= parts ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Phase 2 of command parsing -- parsing for execution.
+ *
+ * Assumes phase 1 completed successfully.
+ *
+ * Note that if parsed->num_command == 0, will have constructed a cmd_v, with
+ * all possible commands in it (depending on cmd_parse_execution).
+ *
+ * Returns: CMD_SUCCESS -- parsed successfully
+ * CMD_ERR_NO_MATCH -- could find nothing that matches
+ * CMD_ERR_AMBIGUOUS -- found more than one match
+ * or parsed->num_command == 0
+ */
+static cmd_return_code_t
+cmd_parse_phase_two(cmd_parsed parsed, cmd_context context)
+{
+ uint ii ;
+ uint match ;
+
+ /* Prepare to filter commands */
+
+ cmd_filter_prepare(parsed, context) ;
+
+ match = 2 ; /* in case parsed->num_command == 0 ! */
+
+ for (ii = 0 ; ii < parsed->num_command ; ii++)
+ match = cmd_filter(parsed, ii, false) ;
+
+ /* Should end up with one command to execute. */
+ if (match == 0)
+ return CMD_ERR_NO_MATCH ;
+
+ if (match > 1)
+ return CMD_ERR_AMBIGUOUS ;
+
+ parsed->cmd = vector_get_item(parsed->cmd_v, 0) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Perform the special handling required for the command that has parsed
+ * successfully so far.
+ *
+ * This mechanism is used to deal with commands which have a context sensitive
+ * effect on parsed->nnode.
+ *
+ * Can be used to do any other semantic checks, subject to the information
+ * being available.
+ *
+ * Returns: CMD_SUCCESS -- OK
+ * CMD_ERR_PARSING -- failed some check, see parsed->emess
+ *
+ * Dealt with here are:
+ *
+ * * "exit" and "quit"
+ *
+ * The next node depends on the current node.
+ *
+ * * "end"
+ *
+ * The next node depends on the current node.
+ *
+ * * "enable"
+ *
+ * This command is really only intended for interactive use, but need to
+ * do something with it in other contexts.
+ *
+ * Can go to ENABLE_NODE directly if can_enable is set in the context.
+ *
+ * The can_enable is set when the vty starts if it does not require any
+ * authentication to enter ENABLE_NODE (eg VTY_CONFIG_READ) or because
+ * it started in ENABLE_NODE or greater (eg VTY_TERMINAL with no password
+ * and advanced mode !).
+ *
+ * Once can_enable is set it is not unset. So getting to enable once is
+ * sufficient for a given VTY.
+ *
+ * A pipe will inherit can_enable, provided that the parent is in
+ * ENABLE_NODE or better.
+ *
+ * A pipe cannot inherit can_auth_enable -- this means that a pipe
+ * can either immediately enable, or cannot enable at all.
+ *
+ * The effect of all this is that "enable" is straightforward, except for
+ * VTY_TERMINAL. For VTY_TERMINAL:
+ *
+ * - if the VTY starts in any node >= ENABLE_NODE, then can_enable
+ * is set from the beginning !
+ *
+ * If has ever reached ENABLE_NODE, then can_enable will be set.
+ *
+ * - otherwise: when enable command is seen, must authenticate.
+ *
+ * - if there is an enable password, then must get and accept the
+ * password, which can only happen at vin_depth == vout_depth == 0
+ * -- see vty_cmd_can_auth_enable().
+ *
+ * - if there is no enable password, then is implicitly authenticated
+ * if is in VIEW_NODE.
+ *
+ * Note that will not accept enable with no password if is in
+ * RESTRICTED_NODE. Can only be in RESTRICTED_NODE if started with
+ * no password, but host.restricted_mode is set. Doesn't seem much
+ * point having a restricted_mode if you can go straight to
+ * ENABLE_NODE just because a password has not been set !
+ */
+static cmd_return_code_t
+cmd_parse_specials(cmd_parsed parsed, cmd_context context)
+{
+ cmd_return_code_t ret ;
+
+ ret = CMD_SUCCESS ;
+
+ switch (parsed->cmd->attr & CMD_ATTR_MASK)
+ {
+ case cmd_sp_simple:
+ zabort("invalid cmd_sp_simple") ;
+ break ;
+
+ case cmd_sp_end:
+ parsed->nnode = cmd_node_end_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_exit: /* NULL_NODE <=> CMD_EOF */
+ parsed->nnode = cmd_node_exit_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_enable:
+ parsed->nnode = cmd_auth_specials(context, ENABLE_NODE) ;
+ break ;
+
+ case cmd_sp_configure:
+ parsed->nnode = cmd_auth_specials(context, CONFIG_NODE) ;
+ break ;
+
+ default:
+ zabort("unknown cmd_sp_xxx") ;
+ break ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Deal with commands which may require AUTH_ENABLE_NODE authentication.
+ *
+ * The rule is that the parser must set what node a given command *will* change
+ * to iff it succeeds.
+ *
+ * So if we can go directly to the target node, must establish that now.
+ *
+ * If cannot go to directly, we set that should go to AUTH_ENABLE_NODE. That
+ * may fail if not in a suitable state to do that -- and issue suitable
+ * message.
+ */
+static node_type_t
+cmd_auth_specials(cmd_context context, node_type_t target)
+{
+ if (!context->can_enable)
+ {
+ /* Can enable if is already ENABLE_NODE or better (this should
+ * not be required -- but does no harm).
+ *
+ * If we are allowed to authenticate, then the authentication
+ * is trivial if there is no password set and we are in
+ * VIEW_NODE !
+ *
+ * Note that if we are in RESTRICTED_NODE, then must authenticate,
+ * which will later be refused if no password is set at the time !
+ */
+ if (context->node >= ENABLE_NODE)
+ context->can_enable = true ;
+ else if (context->can_auth_enable)
+ {
+ /* Can do a*/
+ bool no_pw ;
+
+ VTY_LOCK() ;
+ no_pw = (host.enable == NULL) ;
+ VTY_UNLOCK() ;
+
+ context->can_enable = no_pw && (context->node == VIEW_NODE) ;
+ } ;
+ } ;
+
+ context->onode = context->node ; /* see vty_cmd_auth() */
+ context->tnode = target ; /* see vty_cmd_auth() */
+
+ return context->can_enable ? target : AUTH_ENABLE_NODE ;
+} ;
+
+#if 0
+/*------------------------------------------------------------------------------
+ * If src matches dst return dst string, otherwise return NULL
+ *
+ * Returns NULL if dst is an option, variable of vararg.
+ *
+ * NULL or empty src are deemed to match.
+ */
+static const char *
+cmd_entry_function (const char *src, const char *dst)
+{
+ if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst))
+ return NULL;
+
+ if ((src == NULL) || (*src == '\0'))
+ return dst;
+
+ if (strncmp (src, dst, strlen (src)) == 0)
+ return dst;
+
+ return NULL;
+}
+
+/* If src matches dst return dst string, otherwise return NULL */
+/* This version will return the dst string always if it is
+ CMD_VARIABLE for '?' key processing */
+static const char *
+cmd_entry_function_item (const char *src, const char *dst)
+{
+ if (CMD_VARARG (dst))
+ return dst;
+
+ if (CMD_RANGE (dst))
+ {
+ if (cmd_range_match (dst, src))
+ return dst;
+ else
+ return NULL;
+ }
+
+#ifdef HAVE_IPV6
+ if (CMD_IPV6 (dst))
+ {
+ if (cmd_ipv6_match (src))
+ return dst;
+ else
+ return NULL;
+ }
+
+ if (CMD_IPV6_PREFIX (dst))
+ {
+ if (cmd_ipv6_prefix_match (src))
+ return dst;
+ else
+ return NULL;
+ }
+#endif /* HAVE_IPV6 */
+
+ if (CMD_IPV4 (dst))
+ {
+ if (cmd_ipv4_match (src))
+ return dst;
+ else
+ return NULL;
+ }
+
+ if (CMD_IPV4_PREFIX (dst))
+ {
+ if (cmd_ipv4_prefix_match (src))
+ return dst;
+ else
+ return NULL;
+ }
+
+ /* Optional or variable commands always match on '?' */
+ if (CMD_OPTION (dst) || CMD_VARIABLE (dst))
+ return dst;
+
+ /* In case of 'command \t', given src is NULL string. */
+ if (src == NULL)
+ return dst;
+
+ if (strncmp (src, dst, strlen (src)) == 0)
+ return dst;
+ else
+ return NULL;
+}
+
+/*------------------------------------------------------------------------------
+ * Check same string element existence.
+ *
+ * Returns: 0 => found same string in the vector
+ * 1 => NOT found same string in the vector
+ */
+static bool
+cmd_unique_string (vector v, const char *str)
+{
+ unsigned int i;
+ char *match;
+
+ 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;
+}
+
+/* Compare string to description vector. If there is same string
+ return 1 else return 0. */
+static bool
+item_unique_string (vector v, const char *str)
+{
+ unsigned int i;
+ struct cmd_item *item;
+
+ for (i = 0; i < vector_length (v); i++)
+ if ((item = vector_get_item (v, i)) != NULL)
+ if (strcmp (item->cmd, str) == 0)
+ return 1;
+ return 0;
+}
+#endif
+/*==============================================================================
+ * '?' describe command support.
+ */
+
+/*------------------------------------------------------------------------------
+ * Get description of current (partial) command
+ *
+ * Returns: NULL => no description available
+ *
+ * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS
+ *
+ * or: address of vector of "struct cmd_item" values available.
+ *
+ * NB: when a vector is returned it is the caller's responsibility to
+ * vector_free() it. (The contents are all effectively const, so do not
+ * themselves need to be freed.)
+ */
+extern vector
+cmd_describe_command (const char* line, node_type_t node,
+ cmd_return_code_t* status)
+{
+#if 0
+ vector ret ;
+ struct cmd_parsed parsed_s ;
+ cmd_parsed parsed ;
+ cmd_token_type_t tok_total ;
+
+ /* Set up a parser object and tokenize the command line */
+ parsed = cmd_parse_init_new(&parsed_s) ;
+ tok_total = cmd_tokenize(parsed, line, node) ;
+
+
+
+
+
+
+
+ /* Level 1 parsing
+ *
+ * Strip quotes and escapes from all the tokens.
+ */
+ if (tok_total != cmd_tok_simple)
+ {
+ ret = cmd_parse_phase_one(parsed) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ /* If allowed to 'do', see if there.
+ *
+ * 'do' forces command to be parsed in ENABLE_NODE (if allowed)
+ */
+ if ((type & cmd_parse_no_do) == 0)
+ cmd_try_do_shortcut(parsed) ;
+
+
+
+
+ return cmd_describe_command_real (tokens, node, status);
+
+
+
+ static vector
+ cmd_describe_command_real (vector tokens, int node, int *status)
+ {
+ unsigned int i;
+ vector cmd_vector;
+ #define INIT_MATCHVEC_SIZE 10
+ vector matchvec;
+ struct cmd_command *cmd_command;
+ unsigned int index;
+ int ret;
+ enum match_type match;
+ char *command;
+
+ /* Set index. */
+ if (vector_length (tokens) == 0)
+ {
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+ else
+ index = vector_length (tokens) - 1;
+
+ /* Make copy vector of current node's command vector. */
+ cmd_vector = vector_copy (cmd_node_vector (cmdvec, node));
+
+ /* Prepare match vector */
+ matchvec = vector_init (INIT_MATCHVEC_SIZE);
+
+ /* Filter commands. */
+ /* Only words precedes current word will be checked in this loop. */
+ for (i = 0; i < index; i++)
+ if ((command = vector_get_item (tokens, i)))
+ {
+ match = cmd_filter(command, cmd_vector, i, any_match) ;
+
+ if (match == vararg_match)
+ {
+ struct cmd_command *cmd_command;
+ vector descvec;
+ unsigned int j, k;
+
+ for (j = 0; j < vector_length (cmd_vector); j++)
+ if ((cmd_command = vector_get_item (cmd_vector, j)) != NULL
+ && (vector_length (cmd_command->items)))
+ {
+ descvec = vector_get_item (cmd_command->items,
+ vector_length (cmd_command->items) - 1);
+ for (k = 0; k < vector_length (descvec); k++)
+ {
+ struct cmd_item *item = vector_get_item (descvec, k);
+ vector_set (matchvec, item);
+ }
+ }
+
+ vector_set (matchvec, &item_cr);
+ vector_free (cmd_vector);
+
+ return matchvec;
+ } ;
+
+ ret = is_cmd_ambiguous (command, cmd_vector, i, match) ;
+ if (ret != 0)
+ {
+ vector_free (cmd_vector);
+ vector_free (matchvec);
+ *status = (ret == 1) ? CMD_ERR_AMBIGUOUS
+ : CMD_ERR_NO_MATCH ;
+ return NULL ;
+ } ;
+ }
+
+ /* Prepare match vector */
+ /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */
+
+ /* Make sure that cmd_vector is filtered based on current word */
+ command = vector_get_item (tokens, index);
+ if (command)
+ match = cmd_filter(command, cmd_vector, index, any_match);
+
+ /* Make description vector. */
+ for (i = 0; i < vector_length (cmd_vector); i++)
+ {
+ vector items ;
+
+ cmd_command = vector_get_item (cmd_vector, i) ;
+ if (cmd_command == NULL)
+ continue ;
+
+ /* Ignore cmd_command if no tokens at index position.
+ *
+ * Deal with special case of possible <cr> completion.
+ */
+ items = cmd_command->items;
+ if (index >= vector_length (items))
+ {
+ if (command == NULL && index == vector_length (items))
+ {
+ if (!item_unique_string (matchvec, cr_string))
+ vector_push_item(matchvec, &item_cr);
+ }
+ continue ;
+ } ;
+
+ /* Check if command is completed. */
+ unsigned int j;
+ vector descvec = vector_get_item (items, index);
+ struct cmd_item *item;
+
+ for (j = 0; j < vector_length (descvec); j++)
+ if ((item = vector_get_item (descvec, j)))
+ {
+ const char *string;
+
+ string = cmd_entry_function_desc (command, item->cmd);
+ if (string)
+ {
+ /* Uniqueness check */
+ if (!item_unique_string (matchvec, string))
+ vector_push_item(matchvec, item);
+ }
+ } ;
+ } ;
+
+ vector_free (cmd_vector);
+
+ if (vector_length(matchvec) == 0)
+ {
+ vector_free (matchvec);
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+
+ *status = CMD_SUCCESS;
+ return matchvec;
+ }
+
+ cmd_parse_reset(parsed, false) ;
+
+ return
+#endif
+
+ return NULL ;
+} ;
+
+#if 0
+/*------------------------------------------------------------------------------
+ * Check LCD of matched command.
+ *
+ * Scan list of matched keywords, and by comparing them pair-wise, find the
+ * longest common leading substring.
+ *
+ * Returns: 0 if zero or one matched keywords
+ * length of longest common leading substring, otherwise.
+ */
+static int
+cmd_lcd (vector matchvec)
+{
+ int n ;
+ int i ;
+ int lcd ;
+ char *sp, *sq, *ss ;
+
+ n = vector_end(matchvec) ;
+ if (n < 2)
+ return 0 ;
+
+ ss = vector_get_item(matchvec, 0) ;
+ lcd = strlen(ss) ;
+
+ for (i = 1 ; i < n ; i++)
+ {
+ sq = ss ;
+ ss = vector_get_item(matchvec, i) ;
+ sp = ss ;
+
+ while ((*sp == *sq) && (*sp != '\0'))
+ {
+ ++sp ;
+ ++sq ;
+ } ;
+
+ if (lcd > (sp - ss))
+ lcd = (sp - ss) ;
+ }
+ return lcd;
+}
+
+#endif
+
+/*==============================================================================
+ * Command line help support.
+ *
+ *
+ */
+
+static bool cmd_post_last(cmd_parsed parsed) ;
+
+
+/*------------------------------------------------------------------------------
+ * Look out for the following special cases:
+ *
+ * a) before or on '#' or '!'
+ *
+ * '#' and '!' are only recognised as the start of a comment at the
+ * start of a line... So can insert something in front, and what
+ * currently looks like a comment, suddenly needs to be reconsidered
+ * as tokens... the first of which happens to start with '!' or '#'.
+ *
+ * To save work, and leave open the possibility of trailing comments,
+ * we treat this case as an "error".
+ *
+ * b) before or on '<'
+ *
+ * currently, pipe-in must be the first token on the command line.
+ *
+ * So if there is anything prior to the '<', treat that as an error.
+ * If is just spaces before the '<', say that nothing may be placed
+ * before the '<' (cf nothing may be placed before '!' or '#').
+ *
+ * in these cases no help is available -- return message explaining.
+ *
+ * NB: assumes that have already done cmd_token_position(), and therefore that
+ * if there is a '#' or '<' that cannot be positioned *after* it, or
+ * indeed that can be positioned after a '>'.
+ */
+extern const char*
+cmd_help_preflight(cmd_parsed parsed)
+{
+ if ((parsed->tok_total & cmd_tok_comment) != 0)
+ {
+ return "cannot have a command on a comment line";
+ } ;
+
+ if ((parsed->tok_total & cmd_tok_in_pipe) != 0)
+ {
+ return "cannot have a command and an '<' pipe together" ;
+ } ;
+
+ return NULL ; /* OK ! */
+} ;
+
+
+
+
+/*------------------------------------------------------------------------------
+ * See if current command line can be completed, and if so, how.
+ *
+ * NB: must already have done cmd_token_position().
+ *
+ * Must not be called if cmd_token_position() reported a "special".
+ *
+ * Returns: CMD_ERR_PARSING -- the parser could not make sense of the line.
+ *
+ */
+extern cmd_return_code_t
+cmd_completion(cmd_parsed parsed, cmd_context context)
+{
+ cmd_return_code_t ret ;
+
+ /* Parse the line -- allow completion, allow do, allow backing up the tree,
+ * but do not parse for execution.
+ */
+ context->can_enable = true ;
+ context->full_lex = true ;
+ context->parse_execution = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+ context->parse_strict = false ;
+
+ ret = cmd_parse_command(parsed, context) ;
+
+ if (ret == CMD_ERR_PARSING)
+ return ret ; /* nothing more possible */
+
+ /* Expect now to have a cmd_v set with the result of the filtering,
+ * with 0, 1 or more possible commands in it.
+ *
+ * Now establish what the alternatives are at the current token position.
+ *
+ * We do this by filtering again, on the current token position, and
+ * asking the filter to collect all the possible items. Note that if the
+ * current token position is beyond the last actual command token, then
+ * will be filtering on the empty eol token -- which we arrange to match
+ * anything, including eol -- which gives the dummy item_cr of type
+ * item_eol.
+ */
+ assert(parsed->cti >= parsed->first_command) ;
+ assert(parsed->cti <= parsed->first_command + parsed->num_command) ;
+
+ cmd_filter(parsed, parsed->cti - parsed->first_command, true) ;
+
+ /* Reduce the list of items available at the current position to
+ * eliminate duplicates and have the possibilities sorted by type and
+ * by value.
+ */
+ if (vector_length(parsed->item_v) > 1)
+ {
+ cmd_item prev ;
+ uint ii ;
+ uint i_keep ;
+
+ vector_sort(parsed->item_v, (vector_sort_cmp*)cmd_cmp_item) ;
+
+ i_keep = 1 ;
+
+ prev = vector_get_item(parsed->item_v, 0) ;
+ for (ii = 1 ; ii < vector_length(parsed->item_v) ; ++ii)
+ {
+ cmd_item item ;
+
+ item = vector_get_item(parsed->item_v, ii) ;
+ if (cmd_cmp_item(&item, &prev) != 0)
+ {
+ vector_set_item(parsed->item_v, i_keep++, item) ;
+ prev = item ;
+ } ;
+ } ;
+
+ vector_set_length(parsed->item_v, i_keep) ;
+ /* discard what did not keep */
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * How to insert a newly completed keyword ?
+ *
+ * There are a number of cases:
+ *
+ * 1) the cti is for the token to be completed.
+ *
+ * May be positioned one or more spaces in front of the keyword.
+ *
+ * Will replace any leading spaces and the keyword by the new keyword.
+ *
+ * 2) the cti is for the empty eol token
+ *
+ * Will replace any leading spaces to the eol keyword, by the new
+ * keyword.
+ *
+ * 3) the cti is for a '>' token, or any other !cmd_tok_simple.
+ *
+ * If cti is not exactly on the token, then insert the keyword exactly
+ * where it is -- no spaces need to be removed or added.
+ *
+ * If the cti is on the token, insert the keyword and one space.
+ *
+ * The second issue is where to leave the cursor... if the command is now
+ * complete, wish to leave the cursor at the end of the newly completed
+ * keyword. Otherwise:
+ *
+ * 1) if the next token is a cmd_tok_simple, move to it.
+ *
+ * 2) otherwise if there is already a space after the newly completed
+ * keyword, move past it.
+ *
+ * Otherwise, insert a space.
+ *
+ * Returns: n = number of characters to move the cursor before
+ * starting to replace characters (may be -ve)
+ * *rep = number of characters to replace
+ * *ins = number of spaces to insert : 0..2
+ * *mov = number of spaces to move afterwards (may be -ve)
+ */
+extern void
+cmd_complete_keyword(cmd_parsed parsed, int* pre, int* rep, int* ins, int* mov)
+{
+ cmd_token t, nt ;
+ bool last ;
+ int gap ;
+
+ /* Is this the last token possible on this command line ? */
+ last = cmd_post_last(parsed) ;
+
+ /* Get the token we are operating and the following one (if any).
+ *
+ * Calculate gap between end of token to be replaced and start of next,
+ * if any.
+ *
+ * gap == 0 => is at eol or on !cmd_tok_simple.
+ */
+ t = cmd_token_get(parsed->tokens, parsed->cti) ;
+ if (parsed->cti < parsed->num_tokens)
+ {
+ nt = cmd_token_get(parsed->tokens, parsed->cti + 1) ;
+ gap = nt->tp - (t->tp + els_len_nn(t->ot)) ;
+ }
+ else
+ {
+ nt = NULL ;
+ gap = (parsed->rp < 0) ? -parsed->rp : 0 ;
+ } ;
+
+ /* Now work out what we need to do. */
+
+ *pre = 0 ; /* preset values */
+ *rep = 0 ;
+ *ins = 0 ;
+ *mov = 0 ;
+
+ if ((t->type & cmd_tok_simple) != 0)
+ {
+ /* Replacing an existing simple token ------------------------------
+ *
+ * Move to start of token and replace it.
+ */
+ *pre = -parsed->rp ;
+ *rep = parsed->ctl ;
+
+ /* now what do we do after the token ? */
+ assert(nt != NULL) ;
+
+ if ((nt->type & cmd_tok_simple) != 0)
+ {
+ /* Next token is simple -- step to it */
+ assert(!last) ;
+
+ *mov = gap ;
+ }
+ else if (nt->type == cmd_tok_eol)
+ {
+ /* Next token is eol
+ *
+ * gap is the number of spaces there will be after the
+ * newly replaced token.
+ */
+ if (!last)
+ {
+ if (gap == 0)
+ *ins = 1 ; /* need a trailing space */
+ else
+ *mov = 1 ; /* step over existing space */
+ } ;
+ }
+ else
+ {
+ /* Next token is something special.
+ *
+ * gap is the number of spaces there will be after the
+ * newly replaced token.
+ */
+ if (gap == 0)
+ {
+ *ins = last ? 1 : 2 ;
+ *mov = -1 ;
+ }
+ else if (gap == 1)
+ {
+ *ins = last ? 0 : 1 ;
+ }
+ else
+ {
+ *mov = last ? 0 : 1 ;
+ }
+ } ;
+ }
+
+ else if (t->type == cmd_tok_eol)
+ {
+ /* Inserting at or before eol --------------------------------------
+ *
+ * Insert exactly where we are -- *pre == 0
+ */
+
+ /* If last, replace any spaces between cursor and end of line,
+ * otherwise, keep one of them.
+ */
+ *rep = last ? gap : (gap > 0) ? gap - 1 : gap ;
+
+ /* now what do we do after the token ? */
+ assert(nt == NULL) ;
+
+ if (!last)
+ {
+ if (gap == 0)
+ *ins = 1 ; /* need space after */
+ else
+ *mov = 1 ; /* step over one */
+ } ;
+ }
+ else
+ {
+ /* Inserting at or before something special ------------------------
+ *
+ * Insert exactly where we are -- *pre == 0
+ * replace nothing -- *rep == 0
+ */
+ if (gap == 0)
+ {
+ *ins = last ? 1 : 2 ;
+ *mov = -1 ;
+ }
+ else if (gap == 1)
+ {
+ *ins = last ? 0 : 1 ;
+ }
+ else
+ {
+ *mov = last ? 0 : 1 ;
+ }
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do we have just one command left, and have we just filtered on the last
+ * possible command item & token ?
+ *
+ * If so, then there is no point filtering any further, and there is nothing
+ * more that could be added to this command line.
+ */
+static bool
+cmd_post_last(cmd_parsed parsed)
+{
+ if (vector_length(parsed->cmd_v) == 1)
+ {
+ cmd_command cmd ;
+ cmd = vector_get_item(parsed->cmd_v, 0) ;
+
+ return ((parsed->cti - parsed->first_command + 1) == cmd->nt_max) ;
+ } ;
+
+ return false ;
+} ;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#if 0
+static vector
+cmd_complete_command_real (vector tokens, int node, int *status)
+{
+ unsigned int i;
+ unsigned int ivl ;
+ unsigned int last_ivl ;
+ vector cmd_v ;
+#define INIT_MATCHVEC_SIZE 10
+ vector matchvec;
+ struct cmd_command *cmd_command;
+ unsigned int index;
+ struct cmd_item *cmd_item;
+ vector descvec;
+ char *token;
+ int n ;
+
+ /* Stop immediately if the tokens is empty. */
+ if (vector_length (tokens) == 0)
+ {
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+
+ /* Take (shallow) copy of cmdvec for given node. */
+ cmd_v = vector_copy (cmd_node_vector (cmdvec, node));
+
+ /* First, filter upto, but excluding last token */
+ last_ivl = vector_length (tokens) - 1;
+
+ for (ivl = 0; ivl < last_ivl; ivl++)
+ {
+ enum match_type match;
+ int ret;
+
+ /* TODO: does this test make any sense ? */
+ if ((token = vector_get_item (tokens, ivl)) == NULL)
+ continue ;
+
+ /* First try completion match, return best kind of match */
+ index = ivl ;
+ match = cmd_filter_by_completion (token, cmd_v, index) ;
+
+ /* Eliminate all but the selected kind of match */
+ ret = is_cmd_ambiguous (token, cmd_v, index, match) ;
+
+ if (ret == 1)
+ {
+ /* ret == 1 => either token matches more than one keyword
+ * or token matches more than one number range
+ */
+ vector_free (cmd_v);
+ *status = CMD_ERR_AMBIGUOUS;
+ return NULL;
+ }
+#if 0
+ /* For command completion purposes do not appear to care about
+ * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after).
+ */
+ else if (ret == 2)
+ {
+ vector_free (cmd_v);
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+#endif
+ }
+
+ /* Prepare match vector. */
+ matchvec = vector_init (INIT_MATCHVEC_SIZE);
+
+ /* Now we got into completion */
+ index = last_ivl ;
+ token = vector_get_item(tokens, last_ivl) ; /* is now the last token */
+
+ for (i = 0; i < vector_length (cmd_v); i++)
+ {
+ unsigned int j;
+ const char *string;
+
+ if ((cmd_command = vector_get_item (cmd_v, i)) == NULL)
+ continue ;
+
+ descvec = vector_get_item (cmd_command->items, index);
+ if (descvec == NULL)
+ continue ;
+
+ for (j = 0; j < vector_length (descvec); j++)
+ {
+ item = vector_get_item (descvec, j) ;
+ if (item == NULL)
+ continue ;
+
+ string = cmd_entry_function(token, item->str) ;
+ if ((string != NULL) && cmd_unique_string(matchvec, string))
+ cmd_add_to_strvec (matchvec, string) ;
+ } ;
+ } ;
+
+ n = vector_length(matchvec) ; /* number of entries in the matchvec */
+
+ /* We don't need cmd_v any more. */
+ vector_free (cmd_v);
+
+ /* No matched command */
+ if (n == 0)
+ {
+ vector_free (matchvec);
+
+ /* In case of 'command \t' pattern. Do you need '?' command at
+ the end of the line. */
+ if (*token == '\0')
+ *status = CMD_COMPLETE_ALREADY;
+ else
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+
+ /* Only one matched */
+ if (n == 1)
+ {
+ *status = CMD_COMPLETE_FULL_MATCH;
+ return matchvec ;
+ }
+
+ /* Check LCD of matched strings. */
+ if (token != NULL)
+ {
+ unsigned lcd = cmd_lcd (matchvec) ;
+
+ if (lcd != 0)
+ {
+ if (strlen(token) < lcd)
+ {
+ char *lcdstr;
+
+ lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1);
+ memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ;
+ lcdstr[lcd] = '\0';
+
+ cmd_free_strvec(matchvec) ; /* discard the match vector */
+
+ matchvec = vector_init (1);
+ vector_push_item(matchvec, lcdstr) ;
+
+ *status = CMD_COMPLETE_MATCH;
+ return matchvec ;
+ }
+ }
+ }
+
+ *status = CMD_COMPLETE_LIST_MATCH;
+ return matchvec ;
+}
+#endif
+
+/*------------------------------------------------------------------------------
+ * Can the current command be completed ?
+ */
+extern vector
+cmd_complete_command (vector tokens, int node, int *status)
+{
+#if 0
+ vector ret;
+
+ if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) )
+ {
+ vector shifted_tokens;
+ unsigned int index;
+
+ /* We can try it on enable node, cos' the vty is authenticated */
+
+ shifted_tokens = vector_init (vector_count(tokens));
+ /* use memcpy? */
+ for (index = 1; index < vector_length (tokens); index++)
+ {
+ vector_set_index (shifted_tokens, index-1,
+ vector_lookup(tokens, index)) ;
+ }
+
+ ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status);
+
+ vector_free(shifted_tokens);
+ return ret;
+ }
+
+ return cmd_complete_command_real (tokens, node, status);
+#endif
+
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+} ;
+
+/*==============================================================================
+ * Initialise command parsing -- done before first command is installed.
+ *
+ * Complete the (much used) eol_item.
+ */
+extern void
+cmd_parser_init(void)
+{
+ dsl_init(word_lumps) ;
+ cmd_set_str(&eol_item, "<cr>") ;
+} ;
diff --git a/lib/command_parse.h b/lib/command_parse.h
new file mode 100644
index 00000000..ee219f84
--- /dev/null
+++ b/lib/command_parse.h
@@ -0,0 +1,814 @@
+/* Quagga command line parsing -- header
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_COMMAND_PARSE_H
+#define _ZEBRA_COMMAND_PARSE_H
+
+#include "misc.h"
+
+#include "command_common.h"
+#include "vector.h"
+#include "vio_fifo.h"
+#include "qstring.h"
+#include "elstring.h"
+#include "qpath.h"
+#include "elstring.h"
+#include "memory.h"
+
+#if 0
+/*==============================================================================
+ * Parsing of tokens
+ */
+
+enum cmd_token_spec
+{
+ cmd_ts_simple = 0,
+
+ cmd_ts_keyword,
+ cmd_ts_number,
+ cmd_ts_word,
+ cmd_ts_option,
+
+ cmd_ts_ipv4_addr,
+ cmd_ts_ipv4_prefix,
+
+ cmd_ts_ipv6_addr,
+ cmd_ts_ipv6_prefix,
+
+ cmd_ts_vararg,
+} ;
+typedef enum cmd_token_spec cmd_token_spec_t ;
+#endif
+
+/*==============================================================================
+ * Sexing of token types in command descriptions
+ */
+#define CMD_OPTION(S) ((S[0]) == '[')
+#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<'))
+#define CMD_VARARG(S) ((S[0]) == '.')
+#define CMD_RANGE(S) ((S[0] == '<'))
+
+#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0))
+#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0))
+#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0))
+#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0))
+
+/*==============================================================================
+ * Command Items.
+ *
+ * A command is compiled into a vector of lists of command items -- the
+ * cmd_command->items (so called).
+ *
+ */
+
+/* Command item types
+ *
+ * NB: the command items sort according to this -- highest first.
+ *
+ * NB: this is in the same order as the match_type -- and should remain so.
+ * See match_type below for further discussion of the hierarchy.
+ */
+enum cmd_item_type
+{
+ item_null,
+
+ item_eol,
+
+ item_option_word,
+
+ item_vararg, /* rest of the line */
+ item_word,
+
+ item_ipv6_prefix,
+ item_ipv6_address,
+ item_ipv4_prefix,
+ item_ipv4_address,
+
+ item_range,
+
+ item_keyword,
+
+ item_type_count, /* number of types */
+} ;
+typedef enum cmd_item_type cmd_item_type_t ;
+
+enum {
+ item_max_number = 0xFFFFFFFF /* can be +/- this */
+} ;
+
+CONFIRM(LONG_MAX >= item_max_number) ;
+
+enum cmd_item_type_bit
+{
+ item_null_bit = BIT(item_null),
+ item_eol_bit = BIT(item_eol),
+ item_option_word_bit = BIT(item_option_word),
+ item_vararg_bit = BIT(item_vararg),
+ item_word_bit = BIT(item_word),
+ item_ipv6_prefix_bit = BIT(item_ipv6_prefix),
+ item_ipv6_address_bit = BIT(item_ipv6_address),
+ item_ipv4_prefix_bit = BIT(item_ipv4_prefix),
+ item_ipv4_address_bit = BIT(item_ipv4_address),
+ item_range_bit = BIT(item_range),
+ item_keyword_bit = BIT(item_keyword),
+} ;
+typedef enum cmd_item_type_bit cmd_item_type_bit_t ;
+
+/*------------------------------------------------------------------------------
+ * Sex cmd_item_type and return whether it is an "option" type, or not.
+ *
+ * NB: tolerates item_null -- others may well not.
+ */
+Inline bool
+cmd_item_is_option(cmd_item_type_t itt)
+{
+ const static bool is_option[item_type_count] =
+ {
+ [item_null] = false,
+
+ [item_eol] = false,
+
+ [item_option_word] = true,
+
+ [item_vararg] = false,
+ [item_word] = false,
+
+ [item_ipv6_prefix] = false,
+ [item_ipv6_address] = false,
+ [item_ipv4_prefix] = false,
+ [item_ipv4_address] = false,
+
+ [item_range] = false,
+
+ [item_keyword] = false,
+ } ;
+
+ assert((itt >= 0) && (itt < item_type_count)) ;
+
+ return is_option[itt] ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Sex cmd_item_type and return whether it is an "vararg" type, or not.
+ *
+ * NB: tolerates item_null -- others may well not.
+ */
+Inline bool
+cmd_item_is_vararg(cmd_item_type_t itt)
+{
+ const static bool is_vararg[item_type_count] =
+ {
+ [item_null] = false,
+
+ [item_eol] = false,
+
+ [item_option_word] = false,
+
+ [item_vararg] = true,
+ [item_word] = false,
+
+ [item_ipv6_prefix] = false,
+ [item_ipv6_address] = false,
+ [item_ipv4_prefix] = false,
+ [item_ipv4_address] = false,
+
+ [item_range] = false,
+
+ [item_keyword] = false,
+ } ;
+
+ assert((itt >= 0) && (itt < item_type_count)) ;
+
+ return is_vararg[itt] ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * The command item structure.
+ */
+typedef struct cmd_item* cmd_item ;
+struct cmd_item
+{
+ elstring_t str ; /* in some word_lump */
+
+ const char* doc ; /* in r_doc -- description text */
+
+ cmd_item next ; /* Next possibility (if any) */
+
+ cmd_item_type_t type ;
+ bool arg ; /* include in argv */
+
+ /* For item_range values */
+ bool range_sign_allowed ;
+ bool range_sign_required ;
+ long range_min ;
+ long range_max ;
+} ;
+
+/*==============================================================================
+ * Match strengths types and filter settings.
+ *
+ * When matching a token, may have a number of competing items, possibly of
+ * different types.
+ *
+ * For execution a token must match completely -- or, for keyword items,
+ * match partially at most one possible keyword.
+ */
+
+/* Match strength
+ *
+ * When matching a token against an item, the following are the possible
+ * results.
+ */
+enum match_strength
+{
+ ms_no_match = 0, /* match failed: token is definitely NOT
+ * the item in question.
+ */
+
+ ms_min_parse = ms_no_match + 1,
+ /* for parsing must match somehow ! */
+
+ ms_partial, /* match OK up to the end of the token, but
+ * more is required to complete it.
+ * This is used by the variable matches.
+ */
+
+ ms_min_execute = ms_partial + 1,
+ /* for execution must be at least this */
+
+ ms_anything, /* matches because will match anything.
+ * This is used for WORD and such like items.
+ */
+
+ ms_kwd_incomplete, /* keyword match OK, but not complete.
+ */
+
+ ms_var_complete, /* match succeeded: token is a complete and
+ * valid instance of the item in question
+ */
+
+ ms_kwd_complete /* match succeeded: token is a complete and
+ * valid instance of the item in question
+ */
+} ;
+typedef enum match_strength match_strength_t ;
+
+
+/* The match type indicates what has been matched and the strength of the
+ * match.
+ *
+ * NB: the order of these is significant, higher numbered match types are
+ * preferred over lower numbered ones.
+ *
+ * NB: this is in the same order as the cmd_item_type -- and should remain so.
+ *
+ * During the command filtering, when a token and an item match, and the
+ * match type is better than the match type to date, then all previous matches
+ * are discarded.
+ *
+ * The hierarchy means that, for example, '22' will match an IPv4 prefix
+ * partially, but that will be forgotten if it later matches a WORD, and that
+ * will be forgotten if it later matches a number range.
+ *
+ * The IPv6 items have a lower priority so that a simple decimal will prefer
+ * the simpler IPv4 address form. As soon as there is an 'a'..'f' or a ':',
+ * the IPv4 will no longer match.
+ *
+ * The partial keyword match is preferred over a partial anything else, but
+ * cannot mask a complete value !
+ *
+ * The relative ranking of: mt_option_word_match, mt_vararg_match, and
+ * mt_word_match, is more or less arbitrary -- if these are ever candidates
+ * at the same time, the commands are ambiguous.
+ *
+ */
+enum match_type
+{
+ mt_no_match,
+
+ mt_eol_partial, /* a partial match ! */
+
+ mt_ipv6_address_partial,
+ mt_ipv6_prefix_partial,
+ mt_ipv4_address_partial,
+ mt_ipv4_prefix_partial,
+
+ mt_range_partial,
+
+ mt_eol, /* an ms_anything match */
+
+ mt_option_word_match, /* anything can match a [WORD] */
+
+ mt_vararg_match, /* anything can match a .vararg */
+ mt_word_match, /* anything can match a WORD */
+
+ mt_keyword_incomplete,
+
+ mt_ipv6_prefix_complete,
+ mt_ipv6_address_complete,
+ mt_ipv4_prefix_complete,
+ mt_ipv4_address_complete,
+
+ mt_range_complete,
+
+ mt_keyword_complete,
+
+ match_type_count /* Number of match types */
+} ;
+typedef enum match_type match_type_t ;
+
+/*------------------------------------------------------------------------------
+ * Map match_type -> cmd_item_type
+ *
+ * From a match type extract the type of item which has been match, partially
+ * or completely.
+ */
+Inline cmd_item_type_t
+match_item_type(match_type_t mt)
+{
+ static cmd_item_type_t match_item_type[match_type_count] =
+ {
+ [mt_no_match] = item_null,
+
+ [mt_eol_partial] = item_eol,
+
+ [mt_ipv4_prefix_partial] = item_ipv4_prefix,
+ [mt_ipv4_address_partial] = item_ipv4_address,
+ [mt_ipv6_prefix_partial] = item_ipv6_prefix,
+ [mt_ipv6_address_partial] = item_ipv6_address,
+
+ [mt_range_partial] = item_range,
+
+ [mt_eol] = item_eol,
+
+ [mt_option_word_match] = item_option_word,
+
+ [mt_vararg_match] = item_vararg,
+ [mt_word_match] = item_word,
+
+ [mt_keyword_incomplete] = item_keyword,
+
+ [mt_ipv6_prefix_complete] = item_ipv6_prefix,
+ [mt_ipv6_address_complete] = item_ipv6_address,
+ [mt_ipv4_prefix_complete] = item_ipv4_prefix,
+ [mt_ipv4_address_complete] = item_ipv4_address,
+
+ [mt_range_complete] = item_range,
+
+ [mt_keyword_complete] = item_keyword,
+ } ;
+
+ assert((mt >= 0) && (mt < match_type_count)) ;
+
+ return match_item_type[mt] ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map match_type -> match_strength.
+ *
+ * From a match type extract the strength of the match.
+ */
+Inline match_strength_t
+match_match_strength(match_type_t mt)
+{
+ const static match_strength_t match_match_strength[match_type_count] =
+ {
+ [mt_no_match] = ms_no_match,
+
+ [mt_eol_partial] = ms_partial,
+
+ [mt_ipv6_prefix_partial] = ms_partial,
+ [mt_ipv6_address_partial] = ms_partial,
+ [mt_ipv4_prefix_partial] = ms_partial,
+ [mt_ipv4_address_partial] = ms_partial,
+
+ [mt_range_partial] = ms_partial,
+
+ [mt_eol] = ms_anything,
+
+ [mt_option_word_match] = ms_anything,
+
+ [mt_vararg_match] = ms_anything,
+ [mt_word_match] = ms_anything,
+
+ [mt_keyword_incomplete] = ms_kwd_incomplete,
+
+ [mt_ipv6_prefix_complete] = ms_var_complete,
+ [mt_ipv6_address_complete] = ms_var_complete,
+ [mt_ipv4_prefix_complete] = ms_var_complete,
+ [mt_ipv4_address_complete] = ms_var_complete,
+
+ [mt_range_complete] = ms_var_complete,
+
+ [mt_keyword_complete] = ms_kwd_complete,
+ } ;
+
+ assert((mt >= 0) && (mt < match_type_count)) ;
+
+ return match_match_strength[mt] ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map cmd_item_type -> best possible match type.
+ *
+ * This gives the most optimistic outcome of an attempt to match to the given
+ * type of item.
+ *
+ * NB: tolerates item_null -- others may not
+ */
+Inline match_type_t
+item_best_match(cmd_item_type_t it)
+{
+ const static match_type_t item_best_match[item_type_count] =
+ {
+ [item_null] = mt_no_match,
+
+ [item_eol] = mt_eol,
+
+ [item_option_word] = mt_option_word_match,
+
+ [item_vararg] = mt_vararg_match,
+ [item_word] = mt_word_match,
+
+ [item_ipv6_prefix] = mt_ipv6_prefix_complete,
+ [item_ipv6_address] = mt_ipv6_address_complete,
+ [item_ipv4_prefix] = mt_ipv4_prefix_complete,
+ [item_ipv4_address] = mt_ipv4_address_complete,
+
+ [item_range] = mt_range_complete,
+
+ [item_keyword] = mt_keyword_complete,
+ } ;
+
+ assert((it >= 0) && (it < item_type_count)) ;
+
+ return item_best_match[it] ;
+} ;
+
+/*==============================================================================
+ *
+ */
+
+/* Pipe types */
+enum cmd_pipe_type /* bit significant */
+{
+ cmd_pipe_none = 0,
+
+ cmd_pipe_file = BIT(0),
+ cmd_pipe_shell = BIT(1),
+ cmd_pipe_dev_null = BIT(2), /* out pipe only -- black hole */
+
+ /* For in pipes */
+ cmd_pipe_reflect = BIT(4), /* + option */
+
+ /* For out file pipes */
+ cmd_pipe_append = BIT(4), /* >> */
+
+ /* For out shell pipes */
+ cmd_pipe_shell_cmd = BIT(4), /* | at start of line */
+} ;
+typedef enum cmd_pipe_type cmd_pipe_type_t ;
+
+/* Parsed parts */
+enum cmd_parts /* bit significant */
+{
+ cmd_parts_none = 0,
+
+ cmd_part_do = BIT(0),
+ cmd_part_command = BIT(1),
+
+ cmd_part_in_pipe = BIT(2),
+ cmd_part_out_pipe = BIT(3),
+
+ cmd_parts_pipe = (cmd_part_in_pipe | cmd_part_out_pipe),
+
+ cmd_part_comment = BIT(4),
+} ;
+typedef enum cmd_parts cmd_parts_t ;
+
+
+/*------------------------------------------------------------------------------
+ * Token object -- a qstring and some other properties.
+ */
+enum cmd_token_type /* *bit* significant */
+{
+ cmd_tok_eol = 0, /* all lines have one */
+
+ cmd_tok_simple = BIT( 0),
+
+ cmd_tok_sq = BIT( 4),
+ cmd_tok_dq = BIT( 5), /* '\\' within "..." are not
+ registered separately. */
+ cmd_tok_esc = BIT( 6),
+
+ cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc),
+
+ cmd_tok_in_pipe = BIT( 8), /* token starting '<' */
+ cmd_tok_out_pipe = BIT( 9), /* token starting '>' */
+ cmd_tok_out_shell = BIT(10), /* token starting '|' */
+
+ cmd_tok_comment = BIT(14), /* token starting '!' or '#" */
+} ;
+typedef enum cmd_token_type cmd_token_type_t ;
+
+struct cmd_token
+{
+ cmd_token_type_t type ;
+ usize tp ; /* location of token in the line */
+
+ bool term ; /* token has been '\0' terminated */
+
+ qstring_t qs ; /* token string */
+ elstring_t ot ; /* original token in the line */
+
+ const void* word ; /* set when a word match is done */
+ ulen w_len ; /* length of word match */
+ int cw ; /* result of the word match */
+
+ cmd_item_type_bit_t seen ; /* bit set when match done */
+ match_type_t match[item_type_count] ;
+ /* result of match seen */
+} ;
+
+typedef struct cmd_token* cmd_token ;
+
+/*------------------------------------------------------------------------------
+ * Token vector -- a vector of token objects
+ */
+struct token_vector
+{
+ vector_t body ;
+} ;
+
+typedef struct token_vector token_vector_t[1] ;
+typedef struct token_vector* token_vector ;
+
+enum {
+ TOKEN_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS
+} ;
+
+/*------------------------------------------------------------------------------
+ * Argument vector -- a vector of const char*
+ */
+struct arg_vector
+{
+ vector_t body ;
+} ;
+typedef struct arg_vector arg_vector_t[1] ;
+typedef struct arg_vector* arg_vector ;
+
+enum {
+ ARG_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS
+} ;
+
+/*------------------------------------------------------------------------------
+ * Parsed command line
+ *
+ * The current command line is only valid while command is being parsed and
+ * executed -- in between it is nonsense. The pointer can be overwritten at
+ * any time -- responsibility for the memory lies elsewhere.
+ *
+ * The args vector is a set of pointers to the strings in the relevant tokens.
+ * This vector is only valid while command is being parsed and executed -- in
+ * between it too is nonsense. The pointers can be overwritten or discarded at
+ * any time -- responsibility for the memory lies elsewhere.
+ *
+ * The token vectors contain the tokens for the current command being parsed
+ * and executed. After the command has completed these vectors can be
+ * emptied -- see cmd_empty_parsed_tokens() -- but the next command to be
+ * parsed will tidy up before proceeding.
+ */
+struct cmd_parsed
+{
+ cmd_parts_t parts ; /* What parts are present */
+
+ cmd_token_type_t tok_total ; /* What token types are present */
+
+ usize elen ; /* effective length (less trailing spaces) */
+ usize tsp ; /* number of trailing spaces */
+
+ uint num_tokens ; /* number of tokens parsed */
+
+ token_vector tokens ; /* vector of token objects */
+
+ /* NB: the following are significant only if there is a command part
+ * or a do part
+ */
+ struct cmd_command *cmd ; /* NULL if empty command
+ or fails to parse */
+ node_type_t cnode ; /* node command is in */
+ node_type_t nnode ; /* node to set if command succeeds */
+
+ arg_vector args ; /* vector of arguments */
+
+ /* NB: the following are significant only if an error is returned */
+
+ qstring emess ; /* parse error */
+ ssize eloc ; /* error location */
+
+ /* NB: the following are significant only if respective part is
+ * present.
+ */
+ cmd_pipe_type_t in_pipe ; /* if any */
+ cmd_pipe_type_t out_pipe ; /* if any */
+
+ uint first_in_pipe ;
+ uint num_in_pipe ;
+
+ uint first_do ;
+ uint num_do ;
+
+ uint first_command ;
+ uint num_command ;
+
+ uint first_out_pipe ;
+ uint num_out_pipe ;
+
+ uint first_comment ;
+ uint num_comment ;
+
+ /* The following are significant after cmd_token_position() */
+
+ uint cti ; /* cursor token index -- may be eol token */
+ uint ctl ; /* cursor token length -- 0 <=> eol token */
+ int rp ; /* cursor relative to start of cursor token */
+
+ /* The following are used while filtering commands */
+
+ vector cmd_v ; /* working vector */
+ vector item_v ; /* working vector */
+
+ match_strength_t strongest ;
+ match_type_t best_complete ;
+
+ match_strength_t min_strength ; /* for execution */
+ bool strict ; /* for strict keyword match */
+} ;
+
+enum
+{
+ CMD_PARSED_INIT_ALL_ZEROS = (cmd_pipe_none == 0)
+} ;
+
+typedef struct cmd_parsed cmd_parsed_t[1] ;
+typedef struct cmd_parsed* cmd_parsed ;
+
+/*==============================================================================
+ * This is the stuff that defines the context in which commands are parsed,
+ * and then executed.
+ *
+ * The context lives in the cmd_exec. The CLI has a copy of the context,
+ * which it uses for the prompt and for command line help handling.
+ *
+ * Each time a new vin is pushed, the current context is copied to the current
+ * TOS (before the push).
+ *
+ * Easch time a vin is popped, the context is restored.
+ *
+ * Each time a vin or vout is pushed or popped the context the cmd_exec
+ * out_suppress and reflect flags must be updated.
+ */
+struct cmd_context
+{
+ /* The node between commands. */
+
+ node_type_t node ; /* updated on CMD_SUCCESS */
+
+ /* These properties affect the parsing of command lines. */
+
+ bool full_lex ; /* as required */
+
+ bool parse_execution ; /* parsing to execute */
+
+ bool parse_only ; /* do not execute */
+
+ bool parse_strict ; /* no incomplete keywords */
+ bool parse_no_do ; /* no 'do' commands */
+ bool parse_no_tree ; /* no tree walking */
+
+ bool can_auth_enable ; /* if required */
+ bool can_enable ; /* no (further) password needed */
+
+ /* These properties affect the execution of parsed commands. */
+
+ bool reflect_enabled ; /* per the pipe */
+
+ /* Special for AUTH_ENABLE_NODE -- going from/to */
+
+ node_type_t onode ; /* VIEW_NODE or RESTRICTED_NODE */
+ node_type_t tnode ; /* ENABLE_NODE or CONFIG_NODE */
+
+ /* The current directories. */
+
+ qpath dir_cd ; /* chdir directory */
+ qpath dir_home ; /* "~/" directory */
+ qpath dir_here ; /* "~./" directory */
+} ;
+
+typedef struct cmd_context cmd_context_t[1] ;
+typedef struct cmd_context* cmd_context ;
+
+/*==============================================================================
+ * Prototypes
+ */
+extern void cmd_compile(cmd_command cmd) ;
+extern void cmd_compile_check(cmd_command cmd) ;
+
+extern cmd_parsed cmd_parsed_new(void) ;
+extern cmd_parsed cmd_parsed_free(cmd_parsed parsed) ;
+
+extern bool cmd_is_empty(qstring line) ;
+extern void cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex) ;
+extern qstring cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt) ;
+
+extern cmd_return_code_t cmd_parse_command(cmd_parsed parsed,
+ cmd_context context) ;
+
+extern bool cmd_token_position(cmd_parsed parsed, qstring line) ;
+extern const char* cmd_help_preflight(cmd_parsed parsed) ;
+extern cmd_return_code_t cmd_completion(cmd_parsed parsed, cmd_context context);
+
+extern void cmd_complete_keyword(cmd_parsed parsed,
+ int* pre, int* rep, int* ins, int* mov) ;
+
+extern void cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent) ;
+
+extern void cmd_parser_init(void) ;
+
+
+
+//extern vector cmd_make_strvec (const char *);
+//extern vector cmd_add_to_strvec (vector v, const char* str) ;
+//extern void cmd_free_strvec (vector);
+extern vector cmd_describe_command (const char* line, node_type_t node,
+ cmd_return_code_t* status) ;
+extern vector cmd_complete_command (vector, int, int *status);
+
+
+
+//extern cmd_return_code_t cmd_parse_error(cmd_parsed parsed, cmd_token t,
+// usize off, const char* mess) ;
+
+//Inline const char* cmd_token_string(cmd_token t) ;
+//Inline char* cmd_token_make_string(cmd_token t) ;
+//Inline int cmd_token_count(token_vector tv) ;
+//Inline cmd_token cmd_token_get(token_vector tv, vector_index_t i) ;
+//Inline void cmd_token_set(token_vector tv, vector_index_t i,
+// cmd_token_type_t type, const char* p, usize len, usize tp) ;
+
+//extern cmd_return_code_t cmd_token_complete(cmd_parsed parsed, cmd_token t) ;
+//Inline const char* cmd_token_string(cmd_token t) ;
+//extern cmd_return_code_t cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) ;
+//extern cmd_return_code_t cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) ;
+
+//extern match_type_t cmd_ipv4_match (const char *str) ;
+//extern match_type_t cmd_ipv4_prefix_match (const char *str) ;
+#if HAVE_IPV6
+//extern match_type_t cmd_ipv6_match (const char *str) ;
+//extern match_type_t cmd_ipv6_prefix_match (const char *str) ;
+#endif
+//extern bool cmd_range_match (const char *range, const char *str) ;
+
+/*==============================================================================
+ * Inlines
+ */
+
+/*------------------------------------------------------------------------------
+ * Get the body of the argument vector.
+ */
+Inline const char * const*
+cmd_arg_vector_argv(cmd_parsed parsed)
+{
+ return (const char* const*)vector_body(parsed->args->body) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get length of the body of the argument vector.
+ */
+Inline unsigned
+cmd_arg_vector_argc(cmd_parsed parsed)
+{
+ return vector_length(parsed->args->body) ;
+} ;
+
+#endif /* _ZEBRA_COMMAND_PARSE_H */
diff --git a/lib/command_queue.c b/lib/command_queue.c
index 5f14abae..b80b2c9f 100644
--- a/lib/command_queue.c
+++ b/lib/command_queue.c
@@ -18,137 +18,571 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include <zebra.h>
-
-#include "mqueue.h"
#include "qpnexus.h"
-#include "memory.h"
+#include "mqueue.h"
+#include "thread.h"
+
#include "command_queue.h"
#include "command_execute.h"
-#include "vty.h"
-#include "uty.h"
-#include "vector.h"
-#include "qstring.h"
+#include "vty_command.h"
+#include "vty_io.h"
+#include "log.h"
+
+/*==============================================================================
+ * This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER.
+ *
+ * The command loop is a form of co-routine, which is "called" by sending a
+ * message (or by legacy threads events). In the multi-pthread world, the
+ * command loop may run in either the vty_cli_nexus or the vty_cmd_nexus, and
+ * may be passed from one to the other by sending a message.
+ *
+ * The command loop is usually in one of three states:
+ *
+ * vc_running -- the command loop is running, doing things.
+ *
+ * In this state the vty and the vty->exec are in the hands
+ * of the command loop.
+ *
+ * The vty->vio is always accessed under VTY_LOCK().
+ *
+ * Note that one of the things the command loop may be
+ * doing is waiting for a message to be picked up (or
+ * a legacy thread event to be dispatched).
+ *
+ * vc_waiting -- the command loop is waiting for something to happen
+ * (typically some I/O), and will stay there until a
+ * message is sent (or a legacy threads event).
+ *
+ * To be waiting the command loop must have entered "hiatus"
+ * and then exited.
+ *
+ * When a command loop is or has been stopped, to goes to vc_stopped state.
+ *
+ * There are further issues:
+ *
+ * * In the mult-pthreaded world, commands may be parsed in either pthread,
+ * but most are executed in the Routing thread. So may switch pthreads
+ * after parsing a command -- by sending a message.
+ *
+ * * opening pipes is done in the vty_cli_nexus, in case of any possible
+ * blocking and because the pselect() stuff is all done in the
+ * vty_cli_nexus.
+ *
+ * * all output is via fifo buffers -- when output is pushed, as much as
+ * possible is done immediately, but if necessary the pselect() process
+ * (in the vty_cli_nexus) is left to keep things moving.
+ *
+ * In vty_io_basic() it is possible to set read/write ready and associated
+ * timeouts while running in the vty_cmd_nexus, but note that this is done
+ * by sending messages to the CLI thread (if multi-pthreaded).
+ *
+ * The smooth running of the command handling depends on the continued
+ * running of the CLI thread.
+ *
+ * To close a VTY must (eventually) arrange for the state to not be vc_running.
+ * While a vty is vc_running, the command loop may be in either nexus, and
+ * may be:
+ *
+ * - on the vty_cli_nexus queue (or the combined queue)
+ * - executing cq_process() in the vty_cli_nexus (or the single "both" nexus)
+ * - on the vty_cmd_nexus queue (if different)
+ * - executing cq_process() in the vty_cmd_nexus (if different)
+ *
+ * where being on the queue means that there is a message waiting to be
+ * picked up on that queue, which will end up calling cq_process().
+ *
+ * Or, in the legacy threads world:
+ *
+ * - on the event queue -- waiting for event handler to call cq_process()
+ * - executing cq_process()
+ *
+ * To bring the command loop to a stop will call cq_revoke to revoke any
+ * pending message or pending legacy event. If that succeeds, then the
+ * command loop can be stopped immediately. If not, then vc_running implies
+ * it is executing in cq_process() in one of the nexuses.
+ */
/*------------------------------------------------------------------------------
- * Form of message passed with command to be executed
+ * Prototypes
*/
+static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state,
+ cmd_return_code_t ret) ;
+static void cq_action(mqueue_block mqb, mqb_flag_t flag);
+static int cq_thread(struct thread* thread) ;
+static void cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) ;
-struct cq_command_args
+/*------------------------------------------------------------------------------
+ * Enqueue vty to enter the command loop -- must be exec_null
+ *
+ * Expects the vty start up process to have output some cheerful messages,
+ * which is treated as a dummy "login" command. So the loop is entered at
+ * "exec_done_cmd", which will push out the output, deal with the return code,
+ * and loop round to fetch the first command line (if required).
+ */
+extern void
+cq_loop_enter(vty vty, cmd_return_code_t ret)
{
- enum cmd_return_code ret ; /* return code from command */
+ VTY_ASSERT_CLI_THREAD() ;
+
+ qassert(vty->exec->state == exec_null) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ;
} ;
-MQB_ARGS_SIZE_OK(cq_command_args) ;
/*------------------------------------------------------------------------------
- * Prototypes
+ * Continue (resume) at hiatus -- must be exec_hiatus
*/
-static void cq_action(mqueue_block mqb, mqb_flag_t flag);
-static void cq_return(mqueue_block mqb, mqb_flag_t flag);
+extern void
+cq_continue(vty vty, cmd_return_code_t ret)
+{
+ VTY_ASSERT_CLI_THREAD() ;
+
+ qassert(vty->exec->state == exec_hiatus) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+} ;
/*------------------------------------------------------------------------------
- * Enqueue vty and argv[] for execution in given nexus.
+ * Enqueue vty for execution in given nexus or issue thread event.
+ *
+ * Will execute in the current exec->state, passing in the given return
+ * code.
+ *
+ * Note that the vio->state *must* be vc_running.
*/
-void
-cq_enqueue(struct vty *vty, qpn_nexus dst)
+static void
+cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
+ cmd_return_code_t ret)
{
- struct cq_command_args* args ;
- mqueue_block mqb ;
+ cmd_exec exec = vty->exec ;
+
+ qassert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
- assert(vty_cli_nexus) ; /* must be running qnexus-wise */
+ exec->locus = dst ;
+ exec->state = state ;
+ exec->ret = ret ;
- mqb = mqb_init_new(NULL, cq_action, vty) ;
- args = mqb_get_args(mqb) ;
+ if (vty_nexus)
+ {
+ mqueue_block mqb ;
- args->ret = CMD_QUEUED ;
+ if ((mqb = exec->cq.mqb) == NULL)
+ mqb = exec->cq.mqb = mqb_init_new(NULL, cq_action, vty) ;
- mqueue_enqueue(dst->queue, mqb, 0) ;
-}
+ mqueue_enqueue(dst->queue, mqb, (dst == vty_cmd_nexus) ? mqb_priority
+ : mqb_ordinary) ;
+ }
+ else
+ {
+ qassert(vty_cli_nexus == vty_cmd_nexus) ;
+ exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ;
+ } ;
+} ;
/*------------------------------------------------------------------------------
- * Dispatch a command from the message queue block
+ * Deal with command message -- in the qpthreads world.
*
- * 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.
+ * Note that if the message is revoked, then the state is set vc_stopped.
+ * We revoke when stopping a command loop -- see cq_revoke, below. If the
+ * message is revoked at any other time then the command loop is, indeed,
+ * stopped -- the I/O side may plow on until buffers empty, but the
+ * command loop will not cq_continue(). At
*/
static void
cq_action(mqueue_block mqb, mqb_flag_t flag)
{
- struct vty *vty;
- struct cq_command_args* args ;
+ vty vty;
- assert(vty_cli_nexus) ; /* must be running qnexus-wise */
+ assert(vty_nexus) ; /* must be running qnexus-wise */
vty = mqb_get_arg0(mqb);
- args = mqb_get_args(mqb) ;
+
+ qassert(vty->exec->cq.mqb == mqb) ;
if (flag == mqb_action)
- {
- args->ret = cmd_dispatch_call(vty) ;
- assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */
- }
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* do not touch vty on way out */
else
- args->ret = CMD_QUEUED ;
+ {
+ /* Revoke action. */
+ mqb_free(mqb) ;
+ vty->exec->cq.mqb = NULL ;
+
+ vty_cmd_set_stopped(vty) ; /* enforced stop of loop */
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Deal with command message -- in the legacy threads world.
+ */
+static int
+cq_thread(struct thread* thread)
+{
+ vty vty = THREAD_ARG(thread) ;
+
+ qassert(vty->exec->cq.thread == thread) ;
+
+ vty->exec->cq.thread = NULL ;
- mqb_set_action(mqb, cq_return) ;
- mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ;
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* do not touch vty on way out */
+ return 0 ;
} ;
/*------------------------------------------------------------------------------
- * Accept return from command which has completed.
+ * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER.
*
- * The command line processing for the vty may be stalled (with read mode
- * disabled) waiting for the return from the command.
+ * This is essentially a coroutine, where the state is in the vty, noting that:
*
- * 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.
+ * vty->exec->state is the "return address"
+ * vty->exec->ret is the "return code"
*
- * 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.
+ * The command loop runs at the mqueue level in the qpnexus world, or is
+ * driven by "events" in the legacy threads world. The two ways into the
+ * loop are:
+ *
+ * cq_loop_enter() -- called once and once only to start the loop.
+ *
+ * cq_continue() -- called when some I/O process has completed and
+ * the loop is in hiatus, waiting for I/O.
+ *
+ * The one way out of the loop is when it hits a hiatus, which is triggered
+ * by any operation not returning CMD_SUCCESS. In hiatus, vty_cmd_hiatus()
+ * is called and given the current return code to deal with. It will
+ * return:
+ *
+ * CMD_SUCCESS => can proceed to fetch the next command
+ * CMD_WAITING => must leave the command loop, waiting for I/O
+ * CMD_STOP => close the vty and leave command loop
+ * CMD_IO_ERROR => loop back and deal with the error
+ *
+ * The cq_loop_enter() and cq_continue() operations send a message (to the
+ * cli thread, in the mult-pthreaded world), whose action routine is to call
+ * cq_process().
+ *
+ * In the multi-pthreaded world, opening and closing files MUST be done in the
+ * cli thread. Most commands MUST be executed in the cmd thread. So the
+ * command loop is passed between the threads in a number of places. (To keep
+ * life simple, switches back to the cli thread if ends up waiting for I/O.)
+ *
+ * The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read
+ * and/or write ready state -- which may generate messages to the
+ * vty_cli_nexus.
+ *
+ * NB: on exit from cq_process the VTY may have been closed down and released,
+ * so do NOT depend on its existence.
*/
static void
-cq_return(mqueue_block mqb, mqb_flag_t flag)
+cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
{
- struct vty *vty ;
- struct cq_command_args* args ;
+ cmd_exec exec = vty->exec ;
+ cmd_parsed parsed = exec->parsed ;
+
+ /* Have switch wrapped in a while(1) so that can change state by setting
+ * exec->state and doing "continue".
+ *
+ * Breaking out of the switch forces the exec state to exec_hiatus,
+ * with the current ret (which will switch to the CLI thread).
+ *
+ * The exec locus is either vty_cli_nexus or vty_cmd_nexus. If these
+ * are equal (including both NULL, in the legacy threads world), then is
+ * running single threaded -- otherwise is running multi-threaded.
+ */
+ while (1)
+ {
+ switch(state)
+ {
+ /*--------------------------------------------------------------------
+ * Should not get here in exec_null state.
+ */
+ case exec_null:
+ zabort("exec state == exec_null") ;
+ break ;
+
+ /*--------------------------------------------------------------------
+ * Need another command to execute => in_pipe !
+ *
+ * If multi-threaded: may be in either thread. If vty_cmd_fetch_line()
+ * cannot, for any reason, return a command line, will return something
+ * other than CMD_SUCCESS -- which enters the exec_hiatus.
+ */
+ case exec_fetch:
+ ret = vty_cmd_fetch_line(vty) ;
+
+ if (ret != CMD_SUCCESS)
+ break ;
+
+ if (exec->action->to_do != cmd_do_command)
+ {
+ state = exec_special ;
+ continue ;
+ } ;
+
+ fall_through ; /* with ret == CMD_SUCCESS */
+
+ /*--------------------------------------------------------------------
+ * Parse command in hand
+ *
+ * If multi-threaded: may be in either thread.
+ */
+ cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ;
+
+ ret = cmd_parse_command(parsed, exec->context) ;
+ if (ret != CMD_SUCCESS)
+ {
+ if (ret != CMD_EMPTY)
+ break ; /* stop on *any* parsing issue */
+
+ /* Empty lines from in_pipes we simply ignore.
+ *
+ * Don't expect to see them otherwise, but if we do then need
+ * to complete the command execution process.
+ */
+ ret = CMD_SUCCESS ;
+
+ state = exec_done_cmd ;
+ continue ;
+ } ;
+
+ /* reflection now */
+ if (exec->reflect)
+ {
+ ret = vty_cmd_reflect_line(vty) ;
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ; /* CMD_IO_ERROR or CMD_HIATUS */
+ } ;
+
+ fall_through ;
+
+ /*--------------------------------------------------------------------
+ * Pipe work if any
+ *
+ * Will receive CMD_EOF if the VTY has been closed.
+ *
+ * If multi-threaded: must be in vty_cli_nexus to proceed -- so may
+ * generate message to transfer to vty_cli_nexus.
+ */
+ case exec_open_pipes:
+ if ((parsed->parts & cmd_parts_pipe) != 0)
+ {
+ /* If running pthreaded, do open pipes in vty_cli_nexus */
+ if (exec->locus != vty_cli_nexus)
+ return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ;
+
+ /* Now in vty_cli_nexus */
+ ret = cmd_open_pipes(vty) ;
+ if (ret != CMD_SUCCESS)
+ break ; /* quit if open fails */
+ } ;
+
+ fall_through ; /* with ret == CMD_SUCCESS */
+
+ /*--------------------------------------------------------------------
+ * Execute command in hand (if any)
+ *
+ * If multi-threaded: some commands can run in either thread, most must
+ * run in the vty_cmd_nexus -- so may generate message to transfer to
+ * the vty_cmd_nexus.
+ */
+ case exec_execute:
+ if ((parsed->parts & cmd_part_command) != 0)
+ {
+ /* If running pthreaded, do most commands in vty_cmd_nexus */
+ if ((exec->locus != vty_cmd_nexus) && (!cmd_is_direct(parsed)))
+ return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ;
+
+ /* Standard command handling */
+#ifdef CONSUMED_TIME_CHECK
+ {
+ RUSAGE_T before;
+ RUSAGE_T after;
+ unsigned long realtime, cputime;
+
+ GETRUSAGE(&before);
+#endif /* CONSUMED_TIME_CHECK */
+
+ ret = cmd_execute(vty) ;
+
+#ifdef CONSUMED_TIME_CHECK
+ GETRUSAGE(&after);
+ realtime = thread_consumed_time(&after, &before, &cputime) ;
+ if (realtime > CONSUMED_TIME_CHECK)
+ /* Warn about CPU hog that must be fixed. */
+ zlog(NULL, LOG_WARNING,
+ "SLOW COMMAND: command took %lums (cpu time %lums): %s",
+ realtime/1000, cputime/1000,
+ qs_make_string(exec->action->line)) ;
+ } ;
+#endif /* CONSUMED_TIME_CHECK */
+ } ;
+
+ fall_through ;
+
+ /*--------------------------------------------------------------------
+ * Command has completed -- if successful, push output and loop back
+ * to fetch another command.
+ *
+ * Break if not successful, or push fails or must wait.
+ *
+ * If multi-threaded: may be in either thread:
+ *
+ * vty_cmd_success() may set write ready -- so in vty_cmd_nexus may
+ * generate message to vty_cli_nexus.
+ */
+ case exec_done_cmd:
+ if (ret == CMD_SUCCESS)
+ ret = vty_cmd_success(vty) ;
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ; /* stop */
- assert(vty_cli_nexus) ; /* must be running qnexus-wise */
+ state = exec_fetch ;
+ continue ;
- vty = mqb_get_arg0(mqb) ;
- args = mqb_get_args(mqb) ;
+ /*--------------------------------------------------------------------
+ * Deal with the "special" commands
+ */
+ case exec_special:
+ if (exec->locus != vty_cli_nexus)
+ return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ;
- /* signal end of command */
- cmd_post_command(vty, args->ret) ;
- vty_queued_result(vty, args->ret) ;
+ ret = vty_cmd_special(vty) ;
+ if (ret != CMD_SUCCESS)
+ break ;
-//if (qpthreads_enabled)
-// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE);
+ state = exec_done_cmd ;
+ continue ;
- mqb_free(mqb);
-}
+ /*--------------------------------------------------------------------
+ * Hiatus state -- some return code to be dealt with !
+ *
+ * If we are not in the vty_cli_nexus, then must pass the problem
+ * to the vty_cli_nexus. If the return code is CMD_STOP, or there
+ * is a CMD_STOP signal, then drop the config symbol of power -- see
+ * uty_close().
+ */
+ case exec_hiatus:
+ if (exec->locus != vty_cli_nexus)
+ {
+ vty_cmd_check_stop(vty, ret) ;
+ return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+ } ;
+
+ while (1)
+ {
+ /* Let vty_cmd_hiatus() deal with the return code and/or any
+ * stop/error/etc trap, and adjust the stack as required.
+ */
+ ret = vty_cmd_hiatus(vty, ret) ;
+
+ if (ret == CMD_SUCCESS)
+ break ; /* back to exec_fetch */
+
+ if (ret == CMD_WAITING)
+ {
+ exec->state = exec_hiatus ;
+ return ; /* <<< DONE, pro tem */
+ } ;
+
+ if (ret == CMD_IO_ERROR)
+ continue ; /* give error back to hiatus */
+
+ if (ret == CMD_STOP)
+ {
+ exec->state = exec_stopped ;
+ vty_cmd_loop_exit(vty) ;
+
+ return ; /* <<< DONE, permanently */
+ } ;
+
+ zabort("invalid return from vty_cmd_hiatus()") ;
+ } ;
+
+ state = exec_fetch ;
+ continue ; /* can fetch, again */
+
+ /*--------------------------------------------------------------------
+ * Should not get here in exec_stopped state.
+ */
+ case exec_stopped:
+ zabort("exec state == exec_exit") ;
+ break ;
+
+ /*----------------------------------------------------------------------
+ * Unknown exec->state !
+ */
+ default:
+ zabort("unknown exec->state") ;
+ break ;
+ } ;
+
+ /* Have broken out of the switch() => exec_hiatus */
+ state = exec_hiatus ;
+ } ;
+} ;
/*------------------------------------------------------------------------------
- * Revoke any messages related to the given VTY -- if running qnexus-wise.
+ * Revoke any message associated with the given vty from the command queue.
+ *
+ * This is used when a VTY_TERMINAL or a VTY_SHELL_SERVER is being closed.
+ *
+ * See cq_process above for discussion of what messages there may be. At any
+ * time there is at most one message in flight.
+ *
+ * In the single-threaded world (including legacy threads), messages or events
+ * are used to enter or resume the command loop. If a message or event can
+ * be revoked, then the command loop is effectively stopped.
*
- * Revokes in vty_cmd_nexus -- so before command is started
- * and in vty_cli_nexus -- so after command has completed
+ * In the multi-threaded world, messages are used to enter or resume the
+ * command loop, and to transfer it to and from the cli and cmd threads. If
+ * a message can be revoked, the command loop is effectively stopped.
*
- * Can do nothing about any command actually being executed in the
- * vty_cmd_nexus.
+ * Note that the revoke does not affect any vty_cli_nexus messages associated
+ * with the vty_io_basic operations.
+ *
+ * If succeeds in revoking, then will have set the state to vc_stopped, and
+ * will have released the message block or legacy thread object.
+ *
+ * Returns: true <=> have revoked a pending message/event
+ *
+ * NB: if no command loop has ever been started for this vty, then will not
+ * find anything to revoke.
*/
-void
-cq_revoke(struct vty *vty)
+extern bool
+cq_revoke(vty vty)
{
- if (vty_cli_nexus)
+ int ret ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (vty_nexus)
{
- mqueue_revoke(vty_cmd_nexus->queue, vty) ;
- mqueue_revoke(vty_cli_nexus->queue, vty) ;
+ ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ;
+
+ if ((ret == 0) && vty_multi_nexus)
+ ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
+ }
+ else
+ {
+ ret = thread_cancel_event(vty_master, vty) ;
+
+ if (ret != 0)
+ {
+ /* Make sure in same state as after mqueue_revoke. */
+ vty_cmd_set_stopped(vty) ;
+ vty->exec->cq.thread = NULL ;
+ } ;
} ;
-}
+
+ /* If revoked message/event, the command loop is stopped. */
+ if (ret != 0)
+ vty->exec->state = exec_stopped ;
+
+ return (ret != 0) ;
+} ;
+
+
diff --git a/lib/command_queue.h b/lib/command_queue.h
index 804dfa1f..0f4863d6 100644
--- a/lib/command_queue.h
+++ b/lib/command_queue.h
@@ -22,10 +22,12 @@
#ifndef COMMAND_QUEUE_H_
#define COMMAND_QUEUE_H_
-#include "command.h"
+#include "vty_local.h"
+#include "command_execute.h"
#include "qpnexus.h"
-extern void cq_enqueue(struct vty *vty, qpn_nexus dst) ;
-extern void cq_revoke(struct vty* vty) ;
+extern void cq_loop_enter(vty vty, cmd_return_code_t ret) ;
+extern void cq_continue(vty vty, cmd_return_code_t ret) ;
+extern bool cq_revoke(vty vty) ;
#endif /* COMMAND_QUEUE_H_ */
diff --git a/lib/elstring.c b/lib/elstring.c
new file mode 100644
index 00000000..51089cdd
--- /dev/null
+++ b/lib/elstring.c
@@ -0,0 +1,282 @@
+/* Length/String string handling
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "misc.h"
+#include <ctype.h>
+
+#include "elstring.h"
+#include "memory.h"
+
+/*==============================================================================
+ * Create and free
+ */
+
+/*------------------------------------------------------------------------------
+ * Create a new elstring
+ */
+extern elstring
+els_new(void)
+{
+ return XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ;
+
+ confirm(ELSTRING_INIT_ALL_ZEROS) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Initialise or create a new elstring
+ */
+extern elstring
+els_init_new(elstring els)
+{
+ if (els == NULL)
+ els = els_new() ;
+ else
+ memset(els, 0, sizeof(elstring_t)) ;
+
+ confirm(ELSTRING_INIT_ALL_ZEROS) ;
+
+ return els ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Release dynamically allocated elstring.
+ *
+ * Returns NULL.
+ *
+ * NB: it is the callers responsibility to free the contents of the elstring.
+ * if that is required, before freeing the elstring itself.
+ */
+extern elstring
+els_free(elstring els)
+{
+ if (els != NULL)
+ XFREE(MTYPE_TMP, els) ;
+
+ return NULL ;
+} ;
+
+/*==============================================================================
+ * Various comparisons
+ */
+
+/*------------------------------------------------------------------------------
+ * Basic string/length vs string/length comparison
+ */
+inline static int
+str_nn_cmp(const uchar* ap, ulen al, const uchar* bp, ulen bl)
+{
+ ulen n ;
+
+ n = (al <= bl) ? al : bl ; /* min(al, bl) */
+
+ while (n)
+ {
+ int d = *ap++ - *bp++ ;
+ if (d != 0)
+ return d ;
+ --n ;
+ } ;
+
+ return al < bl ? -1 : (al == bl) ? 0 : +1 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare two elstrings -- returns the usual -ve, 0, +ve cmp result.
+ *
+ * NULL elstring is treated as empty.
+ */
+extern int
+els_cmp(elstring a, elstring b)
+{
+ return str_nn_cmp(els_body(a), els_len(a), els_body(b), els_len(b)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare two string/length values -- returns the usual -ve, 0, +ve cmp result
+ *
+ * (Not really an elstring function, but the infrastructure is here.)
+ */
+extern int
+els_nn_cmp(const void* ap, ulen al, const void* bp, ulen bl)
+{
+ return str_nn_cmp(ap, al, bp, bl) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare elstring against word elstring.
+ *
+ * Returns: -1 => elstring and word match to end of elstring
+ * 0 => elstring and word match completely
+ * +1 => elstring and word do not match
+ *
+ * NULLs are treated as empty strings.
+ *
+ * An empty elstring will completely match an empty word.
+ * An empty elstring will partly match any non-empty word.
+ */
+extern int
+els_cmp_word(elstring a, elstring w)
+{
+ const uchar* ap, * ep, * wp ;
+ ulen al, wl ;
+
+ al = els_len(a) ; /* zero if a is NULL */
+ wl = els_len(w) ; /* zero if w is NULL */
+
+ if (al > wl)
+ return +1 ; /* no match if longer */
+
+ if (al == 0)
+ return (wl == 0) ? 0 : -1 ; /* exact or partial if empty */
+
+ ap = els_body_nn(a) ;
+ wp = els_body_nn(w) ;
+
+ /* Neither string is empty */
+
+ if (*ap != *wp)
+ return +1 ; /* quick out if no match for 1st char */
+
+ ep = ap + al - 1 ;
+ while (ap < ep)
+ {
+ if (*++ap != *++wp)
+ return 1 ;
+ } ;
+
+ return al == wl ? 0 : -1 ; /* full or partial match */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare significant parts of two qstrings -- returns the usual -ve, 0, +ve
+ * cmp result.
+ *
+ * By significant, mean excluding leading/trailing isspace() and treating
+ * multiple isspace() as single isspace().
+ *
+ * Compares the 'len' portions of the strings.
+ *
+ * NULL elstring is treated as empty.
+ */
+extern int
+els_cmp_sig(elstring a, elstring b)
+{
+ cpp_t ap ;
+ cpp_t bp ;
+
+ /* Set up pointers and dispense with leading and trailing isspace()
+ *
+ * Dummy up if NULL
+ */
+ els_cpp(ap, a) ; /* NULL if a is NULL */
+
+ while ((ap->p < ap->e) && isspace(*ap->p))
+ ++ap->p ;
+ while ((ap->p < ap->e) && isspace(*(ap->e - 1)))
+ --ap->e ;
+
+ els_cpp(bp, b) ;
+
+ while ((bp->p < bp->e) && isspace(*bp->p))
+ ++bp->p ;
+ while ((bp->p < bp->e) && isspace(*(bp->e - 1)))
+ --bp->e ;
+
+ /* Now set about finding the first difference */
+ while ((ap->p != ap->e) && (bp->p != bp->e))
+ {
+ if (isspace(*ap->p) && isspace(*bp->p))
+ {
+ do { ++ap->p ; } while (isspace(*ap->p)) ;
+ do { ++bp->p ; } while (isspace(*bp->p)) ;
+ } ;
+
+ if (*ap->p != *bp->p)
+ return (*ap->p < *bp->p) ? -1 : +1 ;
+
+ ++ap->p ;
+ ++bp->p ;
+ } ;
+
+ /* No difference before ran out of one or both */
+ if (ap->p != ap->e)
+ return +1 ;
+ else if (bp->p != bp->e)
+ return -1 ;
+ else
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Are two elstrings equal ? -- returns true if strings equal.
+ */
+extern bool
+els_equal(elstring a, elstring b)
+{
+ const uchar* ap ;
+ const uchar* bp ;
+ ulen n ;
+
+ n = els_len(b) ; /* zero if b is NULL */
+ if (n != els_len(a))
+ return false ;
+
+ ap = els_body(a) ; /* NULL if a is NULL */
+ bp = els_body(b) ;
+
+ while (n)
+ {
+ if (*ap++ != *bp++)
+ return false ;
+ --n ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is 'b' a leading substring of 'a' ? -- returns true if it is.
+ *
+ * If 'b' is empty it is always a leading substring.
+ */
+extern bool
+els_substring(elstring a, elstring b)
+{
+ const uchar* ap ;
+ const uchar* bp ;
+ ulen n ;
+
+ n = els_len(b) ; /* zero if b is NULL */
+ if (n > els_len(a))
+ return false ;
+
+ ap = els_body(a) ;
+ bp = els_body(b) ;
+
+ while (n)
+ {
+ if (*ap++ != *bp++)
+ return false ;
+ --n ;
+ } ;
+
+ return true ;
+} ;
+
diff --git a/lib/elstring.h b/lib/elstring.h
new file mode 100644
index 00000000..2b8e77fc
--- /dev/null
+++ b/lib/elstring.h
@@ -0,0 +1,297 @@
+/* Length/String string handling -- header
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_ELSTRING_H
+#define _ZEBRA_ELSTRING_H
+
+#include "misc.h"
+
+/*==============================================================================
+ * This is some very simple support for strings which are Length/Body
+ * objects.
+ *
+ * NB: this object knows NOTHING about memory allocation etc. The objective
+ * is the simplest possible encapsulation of strings which are NOT '\0'
+ * terminated.
+ *
+ * NB: this object knows NOTHING about whether there is a '\0' beyond the
+ * 'len' of the string.
+ */
+struct elstring
+{
+ union
+ {
+ void* v ; /* may be NULL iff len == 0 */
+ const void* cv ;
+ } body ;
+
+ ulen len ;
+} ;
+
+typedef struct elstring elstring_t[1] ;
+typedef struct elstring* elstring ;
+
+/* Setting an elstring object to all zeros is enough to initialise it to
+ * an empty string.
+ */
+enum { ELSTRING_INIT_ALL_ZEROS = true } ;
+
+#define ELSTRING_INIT { { { NULL }, 0 } }
+
+/*==============================================================================
+ * Pointer pair and unsigned pointer pair and const versions.
+ */
+struct pp
+{
+ char* p ;
+ char* e ;
+} ;
+typedef struct pp pp_t[1] ;
+typedef struct pp* pp ;
+
+struct cpp
+{
+ const char* p ;
+ const char* e ;
+} ;
+typedef struct cpp cpp_t[1] ;
+typedef struct cpp* cpp ;
+
+/*==============================================================================
+ * NULLs for all types of pp and for els
+ */
+Inline void pp_null(pp p) { p->p = p->e = NULL ; } ;
+Inline void cpp_null(cpp p) { p->p = p->e = NULL ; } ;
+
+Inline void els_null(elstring els) { els->body.v = NULL ; els->len = 0 ; } ;
+
+/*==============================================================================
+ * Access functions.
+ */
+
+Inline void* els_body(elstring els) ;
+Inline void* els_body_nn(elstring els) ;
+Inline ulen els_len(elstring els) ;
+Inline ulen els_len_nn(elstring els) ;
+Inline void* els_end(elstring els) ;
+Inline void* els_end_nn(elstring els) ;
+
+Inline void els_pp(pp p, elstring els) ;
+Inline void els_pp_nn(pp p, elstring els) ;
+Inline void els_cpp(cpp p, elstring els) ;
+Inline void els_cpp_nn(cpp p, elstring els) ;
+
+Inline void*
+els_body(elstring els)
+{
+ return (els != NULL) ? els_body_nn(els) : NULL ;
+} ;
+
+Inline void*
+els_body_nn(elstring els)
+{
+ return els->body.v ;
+} ;
+
+Inline ulen
+els_len(elstring els)
+{
+ return (els != NULL) ? els_len_nn(els) : 0 ;
+} ;
+
+Inline ulen
+els_len_nn(elstring els)
+{
+ return els->len ;
+} ;
+
+Inline void*
+els_end(elstring els)
+{
+ return (els != NULL) ? els_end_nn(els) : NULL ;
+} ;
+
+Inline void*
+els_end_nn(elstring els)
+{
+ return (void*)((char*)els->body.v + els->len);
+} ;
+
+Inline void
+els_pp(pp p, elstring els)
+{
+ if (els != NULL)
+ els_pp_nn(p, els) ;
+ else
+ pp_null(p) ;
+} ;
+
+Inline void
+els_pp_nn(pp p, elstring els)
+{
+ p->p = els->body.v ;
+ p->e = p->p + els->len ;
+} ;
+
+Inline void
+els_cpp(cpp p, elstring els)
+{
+ if (els != NULL)
+ els_cpp_nn(p, els) ;
+ else
+ cpp_null(p) ;
+} ;
+
+Inline void
+els_cpp_nn(cpp p, elstring els)
+{
+ p->p = els->body.cv ;
+ p->e = p->p + els->len ;
+} ;
+
+/*==============================================================================
+ * All so simple that most is implemented as Inline
+ */
+
+extern elstring els_init_new(elstring els) ;
+extern elstring els_new(void) ;
+extern elstring els_free(elstring els) ;
+
+extern int els_cmp(elstring a, elstring b) ;
+extern int els_nn_cmp(const void* ap, ulen al, const void* bp, ulen bl) ;
+extern int els_cmp_word(elstring a, elstring w) ;
+extern int els_cmp_sig(elstring a, elstring b) ;
+extern bool els_equal(elstring a, elstring b) ;
+extern bool els_substring(elstring a, elstring b) ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring value from ordinary string.
+ *
+ * NB: elstring MUST NOT be NULL.
+ *
+ * NB: treats str == NULL as a zero length string.
+ *
+ * NB: sets "term" unless str == NULL.
+ */
+Inline void
+els_set_nn(elstring els, const void* str)
+{
+ els->body.cv = str ;
+ els->len = (str != NULL) ? strlen(str) : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring value from ordinary string.
+ *
+ * Creates elstring object if required (ie if els == NULL).
+ *
+ * See: els_set_nn.
+ */
+Inline elstring
+els_set(elstring els, const void* str)
+{
+ if (els == NULL)
+ els = els_new() ;
+
+ els_set_nn(els, str) ;
+
+ return els ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring value from body + length.
+ *
+ * NB: sets term = false.
+ *
+ * NB: elstring MUST NOT be NULL.
+ *
+ * NB: treats str == NULL as a zero length string.
+ */
+Inline void
+els_set_n_nn(elstring els, const void* body, ulen len)
+{
+ els->body.cv = body ;
+ els->len = len ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring value from body + length.
+ *
+ * Creates elstring object if required (ie if els == NULL).
+ *
+ * See els_set_n_nn.
+ */
+Inline elstring
+els_set_n(elstring els, const void* body, ulen len)
+{
+ if (els == NULL)
+ els = els_new() ;
+
+ els_set_n_nn(els, body, len) ;
+
+ return els ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear contents of an elstring (if any)
+ *
+ * NB: it is the callers responsibility to free the contents of the elstring.
+ * if that is required, before freeing the elstring itself.
+ */
+Inline void
+els_clear(elstring els)
+{
+ if (els != NULL)
+ {
+ els->body.v = NULL ;
+ els->len = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring 'len'.
+ *
+ * NB: it is the caller's responsibility to set a valid body !!
+ *
+ * NB: elstring MUST NOT be NULL.
+ */
+Inline void
+els_set_len_nn(elstring els, ulen len)
+{
+ els->len = len ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set elstring body.
+ *
+ * NB: it is the caller's responsibility to set a valid body !!
+ *
+ * NB: it is the caller's responsibility to set a valid 'len'.
+ *
+ * NB: elstring MUST NOT be NULL.
+ */
+Inline void
+els_set_body_nn(elstring els, const void* body)
+{
+ els->body.cv = body ;
+} ;
+
+#endif /* _ZEBRA_ELSTRING_H */
diff --git a/lib/errno_names.c b/lib/errno_names.c
index 7238f1a7..3c1ff23c 100644
--- a/lib/errno_names.c
+++ b/lib/errno_names.c
@@ -18,8 +18,8 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include <stddef.h>
#include <errno.h>
#include <netdb.h>
diff --git a/lib/getopt.c b/lib/getopt.c
index ec98feac..75cf7c8b 100644
--- a/lib/getopt.c
+++ b/lib/getopt.c
@@ -30,10 +30,6 @@
# define _NO_PROTO
#endif
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
#include "zebra.h"
#include <stdio.h>
diff --git a/lib/getopt1.c b/lib/getopt1.c
index dc36fd0e..d5514c33 100644
--- a/lib/getopt1.c
+++ b/lib/getopt1.c
@@ -20,10 +20,6 @@
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301,
USA. */
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
#include "zebra.h"
#include "getopt.h"
diff --git a/lib/heap.c b/lib/heap.c
index 3a7ed360..708e88d0 100644
--- a/lib/heap.c
+++ b/lib/heap.c
@@ -19,9 +19,7 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
-
-#include "zassert.h"
+#include "misc.h"
#include "heap.h"
#include "memory.h"
@@ -79,7 +77,7 @@
*/
static heap
-heap_setup(heap h, int new_vector, vector_index size, heap_cmp* cmp,
+heap_setup(heap h, int new_vector, vector_length_t size, heap_cmp* cmp,
int with_backlink, unsigned int backlink_offset) ;
/* Initialize heap -- allocating heap structure if required.
@@ -166,12 +164,13 @@ heap_re_init(heap h, unsigned int size, heap_cmp* cmp,
* *before* doing this.
*/
heap
-heap_reset(heap h, int free_structure)
+heap_reset(heap h, free_keep_b free_structure)
{
- vector_reset_keep(&h->v) ; /* vector structure is embedded in the heap */
+ vector_reset(h->v, keep_it) ; /* vector structure is embedded in the heap */
+ confirm(free_it == true) ;
if (free_structure)
- XFREE(MTYPE_VECTOR, h) ; /* sets h = NULL */
+ XFREE(MTYPE_VECTOR, h) ; /* sets h = NULL */
return h ;
} ;
@@ -189,9 +188,9 @@ heap_setup(heap h, int new_vector, unsigned int size, heap_cmp* cmp,
h->backlink_offset = backlink_offset ;
if (new_vector)
- vector_init_new(&h->v, size) ;
+ vector_init_new(h->v, size) ;
else
- vector_re_init(&h->v, size) ;
+ vector_re_init(h->v, size) ;
return h ;
} ;
@@ -220,14 +219,14 @@ heap_setup(heap h, int new_vector, unsigned int size, heap_cmp* cmp,
* NB: items are reamed out in no defined order.
*/
p_vector_item
-heap_ream(heap h, int free_structure)
+heap_ream(heap h, free_keep_b free_structure)
{
p_vector_item p_v ;
if (h == NULL)
return NULL ;
- if ((p_v = vector_ream_keep(&h->v)) == NULL)
+ if ((p_v = vector_ream(h->v, keep_it)) == NULL)
heap_reset(h, free_structure) ;
return p_v ;
@@ -247,11 +246,11 @@ heap_pop_item(heap h)
p_vector_item p_v ;
p_vector_item p_x ;
- p_v = vector_pop_item(&h->v) ; /* extract last item, if any */
- if ((p_v == NULL) || (h->v.end == 0))
+ p_v = vector_pop_item(h->v) ; /* extract last item, if any */
+ if ((p_v == NULL) || (h->v->end == 0))
return p_v ; /* done if empty or last was also first */
- p_x = h->v.p_items[0] ; /* this is what we are popping */
+ p_x = h->v->p_items[0] ; /* this is what we are popping */
heap_bubble_down(h, 0, p_v) ; /* reposition what was the last item */
/* updating any backlink */
@@ -296,13 +295,13 @@ void
heap_delete_item(heap h, p_vector_item p_v)
{
p_vector_item p_x ;
- vector_index i ;
+ vector_index_t i ;
i = heap_find_item(h, p_v) ; /* index of item to be deleted */
- p_x = vector_pop_item(&h->v) ; /* extract last item, if any */
+ p_x = vector_pop_item(h->v) ; /* extract last item, if any */
- if (i < h->v.end) /* if not deleting the last item... */
+ if (i < h->v->end) /* if not deleting the last item... */
heap_bubble(h, i, p_x) ; /* ...reinsert what was last, at the delete */
/* position, updating any backlink */
} ;
@@ -322,25 +321,25 @@ heap_delete_item(heap h, p_vector_item p_v)
void
heap_push_vector(heap h, vector v, int move_vector)
{
- vector_index i = h->v.end ;
- vector_index e ;
- vector_index n = v->end ;
+ vector_index_t i = h->v->end ;
+ vector_index_t e ;
+ vector_length_t n = v->end ;
p_vector_item p_v ;
if (move_vector)
- vector_move_append(&h->v, v) ;
+ vector_move_append(h->v, v) ;
else
- vector_copy_append(&h->v, v) ;
+ vector_copy_append(h->v, v) ;
e = i ; /* old end of the heap. */
while (n--) {
- p_v = h->v.p_items[i++] ;
+ p_v = h->v->p_items[i++] ;
if (p_v != NULL)
heap_bubble_up(h, e++, p_v) ; /* move new item into position in heap */
/* setting any backlink */
} ;
- h->v.end = e ; /* new end of heap */
+ h->v->end = e ; /* new end of heap */
} ;
/* Pop given heap to vector -- creating vector if required (v == NULL).
@@ -363,8 +362,8 @@ heap_push_vector(heap h, vector v, int move_vector)
vector
heap_pop_vector(vector v, heap h, int move_heap)
{
- vector_index n = h->v.end ;
- vector_index i ;
+ vector_length_t n = h->v->end ;
+ vector_index_t i ;
v = vector_re_init(v, n) ; /* guarantees >= 'n' items in vector */
v->end = n ;
@@ -373,7 +372,7 @@ heap_pop_vector(vector v, heap h, int move_heap)
v->p_items[i] = heap_pop_item(h) ;
if (!move_heap)
- vector_copy_here(&h->v, v) ; /* fully sorted is also heap ordered ! */
+ vector_copy_here(h->v, v) ; /* fully sorted is also heap ordered ! */
return v ;
} ;
@@ -401,11 +400,11 @@ heap_pop_vector(vector v, heap h, int move_heap)
*
* Note that this sets the backlink on the given item.
*/
-private void
-heap_bubble(heap h, vector_index i, p_vector_item p_v)
+Private void
+heap_bubble(heap h, vector_index_t i, p_vector_item p_v)
{
/* If this is < parent, we bubble upwards. */
- if ((i != 0) && (h->cmp(&p_v, &h->v.p_items[HEAP_UP(i)]) < 0))
+ if ((i != 0) && (h->cmp(&p_v, h->v->p_items[HEAP_UP(i)]) < 0))
heap_bubble_up(h, i, p_v) ;
/* Otherwise we try bubbling downwards. */
else
@@ -421,11 +420,11 @@ heap_bubble(heap h, vector_index i, p_vector_item p_v)
* v.end at all. So this can be used to work along a vector and bring
* items into heap order.
*/
-private void
-heap_bubble_up(heap h, vector_index i, p_vector_item p_v)
+Private void
+heap_bubble_up(heap h, vector_index_t i, p_vector_item p_v)
{
- p_vector_item* ha = h->v.p_items ; /* underlying array */
- vector_index ip ; /* index of parent */
+ p_vector_item* ha = h->v->p_items ; /* underlying array */
+ vector_index_t ip ; /* index of parent */
p_vector_item p_p ; /* pointer to parent item */
dassert(ha != NULL) ;
@@ -453,16 +452,16 @@ heap_bubble_up(heap h, vector_index i, p_vector_item p_v)
*
* Note that this sets the backlink on the given item.
*/
-private void
-heap_bubble_down(heap h, vector_index i, p_vector_item p_v)
+Private void
+heap_bubble_down(heap h, vector_index_t i, p_vector_item p_v)
{
- vector_index e = h->v.end ; /* end of heap */
- vector_index ic ; /* index of child */
- vector_index is ; /* index of sibling */
- p_vector_item p_c ; /* pointer to child */
- p_vector_item p_s ; /* pointer to sibling */
+ vector_length_t e = h->v->end ; /* end of heap */
+ vector_index_t ic ; /* index of child */
+ vector_index_t is ; /* index of sibling */
+ p_vector_item p_c ; /* pointer to child */
+ p_vector_item p_s ; /* pointer to sibling */
- p_vector_item* ha = h->v.p_items ; /* underlying array */
+ p_vector_item* ha = h->v->p_items ; /* underlying array */
dassert(ha != NULL) ;
while (1)
@@ -497,21 +496,21 @@ heap_bubble_down(heap h, vector_index i, p_vector_item p_v)
} ;
/* Find index of given item in the given heap. */
-private vector_index
+Private vector_index_t
heap_find_item(heap h, p_vector_item p_v)
{
- vector_index i ;
+ vector_index_t i ;
if (h->state & Heap_Has_Backlink)
i = HEAP_BACKLINK(h, p_v) ;
else
{
- for (i = 0 ; i < h->v.end ; ++i)
- if (h->v.p_items[i] == p_v)
+ for (i = 0 ; i < h->v->end ; ++i)
+ if (h->v->p_items[i] == p_v)
return i ;
} ;
- assert((i < h->v.end) && (h->v.p_items[i] == p_v)) ;
+ assert((i < h->v->end) && (h->v->p_items[i] == p_v)) ;
return i ;
} ;
diff --git a/lib/heap.h b/lib/heap.h
index bd984398..9f981c8d 100644
--- a/lib/heap.h
+++ b/lib/heap.h
@@ -17,13 +17,9 @@
#ifndef _ZEBRA_HEAP_H
#define _ZEBRA_HEAP_H
+#include "misc.h"
#include "vector.h"
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
-
/*==============================================================================
* Data structures etc.
*/
@@ -34,7 +30,7 @@ enum heap_state {
Heap_Has_Backlink = 0x01, /* Set if backlink set */
} ;
-typedef vector_index heap_backlink_t ;
+typedef vector_index_t heap_backlink_t ;
typedef struct heap* heap ;
@@ -45,7 +41,7 @@ struct heap
enum heap_state state ;
unsigned int backlink_offset ;
- struct vector v ;
+ vector_t v ;
} ;
/*==============================================================================
@@ -66,17 +62,8 @@ extern heap heap_re_init(heap h, unsigned int size, heap_cmp* cmp,
#define heap_re_init_backlinked(h, size, cmp, offset) \
heap_re_init(h, size, cmp, 1, offset)
-extern heap heap_reset(heap h, int free_structure) ;
-extern p_vector_item heap_ream(heap h, int free_structure) ;
-
-/* Reset heap and free the heap structure. */
-#define heap_reset_free(h) heap_reset(h, 1)
-/* Reset heap but keep the heap structure. */
-#define heap_reset_keep(h) heap_reset(h, 0)
-/* Ream out heap and free the heap structure. */
-#define heap_ream_free(h) heap_ream(h, 1)
-/* Ream out heap but keep the heap structure. */
-#define heap_ream_keep(h) heap_ream(h, 0)
+extern heap heap_reset(heap h, free_keep_b free_structure) ;
+extern p_vector_item heap_ream(heap h, free_keep_b free_structure) ;
Inline void heap_push_item(heap h, p_vector_item p_v) ;
extern p_vector_item heap_pop_item(heap h) ;
@@ -102,20 +89,16 @@ extern vector heap_pop_vector(vector v, heap h, int move_heap) ;
* This are extern only for use in Inline and other friends
*/
-#ifndef private
- #define private extern
-#endif
-
-private void
-heap_bubble(heap h, vector_index i, p_vector_item p_v) ;
+Private void
+heap_bubble(heap h, vector_index_t i, p_vector_item p_v) ;
-private void
-heap_bubble_up(heap h, vector_index i, p_vector_item p_v) ;
+Private void
+heap_bubble_up(heap h, vector_index_t i, p_vector_item p_v) ;
-private void
-heap_bubble_down(heap h, vector_index i, p_vector_item p_v) ;
+Private void
+heap_bubble_down(heap h, vector_index_t i, p_vector_item p_v) ;
-private vector_index
+Private vector_index_t
heap_find_item(heap h, p_vector_item p_v) ;
/*==============================================================================
@@ -128,7 +111,7 @@ Inline void
heap_push_item(heap h, p_vector_item p_v)
{
dassert(p_v != NULL) ; /* no NULLs, thank you. */
- heap_bubble_up(h, vector_extend_by_1(&h->v), p_v) ;
+ heap_bubble_up(h, vector_extend_by_1(h->v), p_v) ;
} ;
/* Get copy of top heap item (does not pop).
@@ -138,7 +121,7 @@ heap_push_item(heap h, p_vector_item p_v)
Inline p_vector_item
heap_top_item(heap h)
{
- return vector_get_first_item(&h->v) ; /* if any */
+ return vector_get_first_item(h->v) ; /* if any */
} ;
/* Update heap to reflect new value of top item.
diff --git a/lib/if.c b/lib/if.c
index 048ee1fb..23d35054 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -532,11 +532,12 @@ if_sunwzebra_get (const char *name, size_t nlen)
}
#endif /* SUNOS_5 */
-DEFUN (interface,
- interface_cmd,
- "interface IFNAME",
- "Select an interface to configure\n"
- "Interface's name\n")
+DEFUN_ATTR (interface,
+ interface_cmd,
+ "interface IFNAME",
+ "Select an interface to configure\n"
+ "Interface's name\n",
+ CMD_ATTR_NODE + INTERFACE_NODE)
{
struct interface *ifp;
size_t sl;
@@ -556,7 +557,7 @@ DEFUN (interface,
#endif /* SUNOS_5 */
vty->index = ifp;
- vty_set_node(vty, INTERFACE_NODE) ;
+ vty->node = INTERFACE_NODE ;
return CMD_SUCCESS;
}
diff --git a/lib/if.h b/lib/if.h
index 841ce51e..f89f488b 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -301,12 +301,12 @@ extern char *if_indextoname (unsigned int, char *);
/* Exported variables. */
extern struct list *iflist;
-extern struct cmd_element interface_desc_cmd;
-extern struct cmd_element no_interface_desc_cmd;
-extern struct cmd_element interface_cmd;
-extern struct cmd_element no_interface_cmd;
-extern struct cmd_element interface_pseudo_cmd;
-extern struct cmd_element no_interface_pseudo_cmd;
-extern struct cmd_element show_address_cmd;
+extern struct cmd_command interface_desc_cmd;
+extern struct cmd_command no_interface_desc_cmd;
+extern struct cmd_command interface_cmd;
+extern struct cmd_command no_interface_cmd;
+extern struct cmd_command interface_pseudo_cmd;
+extern struct cmd_command no_interface_pseudo_cmd;
+extern struct cmd_command show_address_cmd;
#endif /* _ZEBRA_IF_H */
diff --git a/lib/keychain.c b/lib/keychain.c
index 2f8a0b77..a7929751 100644
--- a/lib/keychain.c
+++ b/lib/keychain.c
@@ -227,18 +227,19 @@ key_delete (struct keychain *keychain, struct key *key)
key_free (key);
}
-DEFUN (key_chain,
- key_chain_cmd,
- "key chain WORD",
- "Authentication key management\n"
- "Key-chain management\n"
- "Key-chain name\n")
+DEFUN_ATTR (key_chain,
+ key_chain_cmd,
+ "key chain WORD",
+ "Authentication key management\n"
+ "Key-chain management\n"
+ "Key-chain name\n",
+ CMD_ATTR_NODE + KEYCHAIN_NODE)
{
struct keychain *keychain;
keychain = keychain_get (argv[0]);
vty->index = keychain;
- vty_set_node(vty, KEYCHAIN_NODE) ;
+ vty->node = KEYCHAIN_NODE ;
return CMD_SUCCESS;
}
@@ -266,11 +267,12 @@ DEFUN (no_key_chain,
return CMD_SUCCESS;
}
-DEFUN (key,
- key_cmd,
- "key <0-2147483647>",
- "Configure a key\n"
- "Key identifier number\n")
+DEFUN_ATTR (key,
+ key_cmd,
+ "key <0-2147483647>",
+ "Configure a key\n"
+ "Key identifier number\n",
+ CMD_ATTR_NODE + KEYCHAIN_KEY_NODE)
{
struct keychain *keychain;
struct key *key;
@@ -281,17 +283,18 @@ DEFUN (key,
VTY_GET_INTEGER ("key identifier", index, argv[0]);
key = key_get (keychain, index);
vty->index_sub = key;
- vty_set_node(vty, KEYCHAIN_KEY_NODE) ;
+ vty->node = KEYCHAIN_KEY_NODE ;
return CMD_SUCCESS;
}
-DEFUN (no_key,
- no_key_cmd,
- "no key <0-2147483647>",
- NO_STR
- "Delete a key\n"
- "Key identifier number\n")
+DEFUN_ATTR (no_key,
+ no_key_cmd,
+ "no key <0-2147483647>",
+ NO_STR
+ "Delete a key\n"
+ "Key identifier number\n",
+ CMD_ATTR_NODE + KEYCHAIN_NODE)
{
struct keychain *keychain;
struct key *key;
@@ -309,7 +312,7 @@ DEFUN (no_key,
key_delete (keychain, key);
- vty_set_node(vty, KEYCHAIN_NODE) ;
+ vty->node = KEYCHAIN_NODE ;
return CMD_SUCCESS;
}
@@ -850,16 +853,20 @@ DEFUN (send_lifetime_duration_month_day,
static struct cmd_node keychain_node =
{
- KEYCHAIN_NODE,
- "%s(config-keychain)# ",
- 1
+ .node = KEYCHAIN_NODE,
+ .prompt ="%s(config-keychain)# ",
+
+ .config_to_vtysh = true
};
static struct cmd_node keychain_key_node =
{
- KEYCHAIN_KEY_NODE,
- "%s(config-keychain-key)# ",
- 1
+ .node = KEYCHAIN_KEY_NODE,
+ .prompt = "%s(config-keychain-key)# ",
+
+ .parent = KEYCHAIN_NODE,
+
+ .config_to_vtysh = true
};
static int
diff --git a/lib/keystroke.c b/lib/keystroke.c
index c6eb811e..e4dc2540 100644
--- a/lib/keystroke.c
+++ b/lib/keystroke.c
@@ -19,8 +19,7 @@
* Boston, MA 02111-1307, USA.
*/
-#include <stdbool.h>
-#include <string.h>
+#include "misc.h"
#include "keystroke.h"
@@ -28,7 +27,6 @@
#include "memory.h"
#include "mqueue.h"
-#include "zassert.h"
/*==============================================================================
*/
@@ -252,43 +250,59 @@
enum stream_state
{
- kst_null, /* nothing special (but see iac) */
+ kst_between, /* between keystrokes */
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 */
+ kst_iac, /* seen an IAC -- previous state pushed. */
kst_iac_option, /* waiting for option (just seen IAC X) */
kst_iac_sub, /* waiting for IAC SE */
+ kst_iac_sub_iac, /* seen an IAC in iac_sub state */
} ;
+typedef enum stream_state stream_state_t ;
struct keystroke_state
{
- enum stream_state state ;
- unsigned len ;
- uint8_t raw[keystroke_max_len] ;
+ stream_state_t state ;
+ uint len ;
+ uint8_t raw[keystroke_max_len] ;
} ;
+typedef struct keystroke_state keystroke_state_t ;
+
+typedef enum
+{
+ steal_none = 0, /* not stealing */
+ steal_next, /* steal next complete kestroke */
+ steal_this, /* steal this keystroke when complete */
+ steal_done /* have a stolen keystroke in hand */
+} steal_state_t ;
+
+enum { max_cntrl = 0x1F } ;
struct keystroke_stream
{
- vio_fifo_t fifo ; /* the keystrokes */
+ vio_fifo fifo ; /* the keystrokes */
- keystroke_callback* iac_callback ;
- void* iac_callback_context ;
+ keystroke_callback* callback ;
+ void* callback_context ;
uint8_t CSI ; /* CSI character value (if any) */
bool eof_met ; /* nothing more to come */
+ bool timed_out ; /* eof because timed out */
- bool steal_this ; /* steal current keystroke when complete */
-
- bool iac ; /* last character was an IAC */
+ steal_state_t steal ; /* whether/how/if stealing */
+ keystroke_t stolen ; /* what has been stolen */
- struct keystroke_state in ; /* current keystroke being collected */
+ keystroke_state_t in ; /* current keystroke being collected */
- struct keystroke_state pushed_in ;
+ keystroke_state_t pushed_in ;
/* keystroke interrupted by IAC */
+
+ bool interrupt[max_cntrl + 1] ;
} ;
/* Buffering of keystrokes */
@@ -298,27 +312,26 @@ enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void keystroke_in_push(keystroke_stream stream, uint8_t u) ;
+static void keystroke_in_push(keystroke_stream stream) ;
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) ;
+inline static void keystroke_set_null(keystroke stroke,
+ keystroke_stream stream) ;
+inline static void keystroke_set_char(keystroke stroke, uint8_t u) ;
+inline static void keystroke_set_esc(keystroke stroke, keystroke_type_t type,
+ keystroke_stream stream) ;
+
+
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) ;
-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,
- uint8_t u) ;
-static void keystroke_steal_csi(keystroke steal, keystroke_stream stream,
- uint8_t u) ;
-static void keystroke_put(keystroke_stream stream, enum keystroke_type type,
- bool broken, uint8_t* bytes, int len) ;
+
+static void keystroke_put_char(keystroke_stream stream, uint8_t u) ;
+static void keystroke_put_esc(keystroke_stream stream, uint8_t u,
+ keystroke_flags_t flags) ;
+static void keystroke_put_iac(keystroke_stream stream, keystroke_flags_t flags);
+
+static void keystroke_put(keystroke_stream stream,
+ keystroke_type_t type, keystroke_flags_t flags,
+ uint8_t* bytes, uint len) ;
/*==============================================================================
* Creating and freeing keystroke streams and keystroke stream buffers.
@@ -329,20 +342,23 @@ static void keystroke_put(keystroke_stream stream, enum keystroke_type type,
*
* Can set CSI character value. '\0' => none. (As does '\x1B' !)
*
- * The callback function is called when an IAC sequence is seen, the callback
- * is:
+ * Can set any control character (other than '\0', '\r', '\n', ESC or CSI)
+ * to be an "interrupt" character.
+ *
+ * The callback function is called when an IAC sequence or an "interrupt"
+ * control character 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
+ * The callback must return true iff the keystroke has been dealt with, and
* should NOT be stored for later processing.
*/
extern keystroke_stream
-keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
- void* iac_callback_context)
+keystroke_stream_new(uint8_t csi_char, const char* interrupts,
+ keystroke_callback* callback, void* callback_context)
{
keystroke_stream stream ;
@@ -350,28 +366,51 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
/* Zeroising the structure sets:
*
- * iac_callback = NULL -- none
- * iac_callback_context = NULL -- none
+ * fifo = NULL -- see below
+ *
+ * callback = NULL -- see below
+ * callback_context = NULL -- see below
+ *
+ * CSI = '\0' -- see below
+ *
+ * eof_met = false -- no EOF yet
+ * timed_out = false -- not timed out yet
*
- * eof_met = false -- no EOF yet
- * steal = false -- no stealing set
- * iac = false -- last character was not an IAC
+ * steal = steal_none -- no stealing going on
+ * stolen = all zeros not significant until steal_done
*
- * in.state = kst_null
- * in.len = 0 -- nothing in the buffer
+ * in.state = kst_between
+ * in.len = 0 -- nothing in the buffer
*
* pushed_in.state ) ditto
* pushed_in.len )
+ *
+ * interrupt = all false
*/
- confirm(kst_null == 0) ;
+ confirm(kst_between == 0) ;
+ confirm(steal_none == 0) ;
- stream->iac_callback = iac_callback ;
- stream->iac_callback_context = iac_callback_context ;
+ stream->callback = callback ;
+ stream->callback_context = callback_context ;
- vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ;
+ stream->fifo = vio_fifo_new(keystroke_buffer_len) ;
stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ;
+ if (interrupts != NULL)
+ {
+ while (*interrupts != '\0')
+ {
+ if ((*interrupts > max_cntrl) || (*interrupts == '\r')
+ || (*interrupts == '\n')
+ || (*interrupts == '\x1B')
+ || (*interrupts == stream->CSI))
+ zabort("invalid 'interrupt' character") ;
+
+ stream->interrupt[(uint8_t)*interrupts++] = true ;
+ } ;
+ } ;
+
return stream ;
} ;
@@ -385,7 +424,7 @@ keystroke_stream_free(keystroke_stream stream)
{
if (stream != NULL)
{
- vio_fifo_reset_keep(&stream->fifo) ;
+ stream->fifo = vio_fifo_free(stream->fifo) ;
XFREE(MTYPE_KEY_STREAM, stream) ;
} ;
@@ -400,6 +439,7 @@ keystroke_stream_free(keystroke_stream stream)
/*------------------------------------------------------------------------------
* See if given keystroke stream is empty
*
+ * case kst_iac_option:
* May or may not be at "EOF", see below.
*
* Returns: true <=> is empty
@@ -407,7 +447,7 @@ keystroke_stream_free(keystroke_stream stream)
extern bool
keystroke_stream_empty(keystroke_stream stream)
{
- return (stream == NULL) || vio_fifo_empty(&stream->fifo) ;
+ return (stream == NULL) || vio_fifo_empty(stream->fifo) ;
} ;
/*------------------------------------------------------------------------------
@@ -415,20 +455,33 @@ keystroke_stream_empty(keystroke_stream stream)
*
* * keystroke stream is empty
*
- * * there is no partial keystroke in construction
- *
* * EOF has been signalled by a suitable call of keystroke_input().
*
- * Returns: true <=> is at EOF
+ * Returns: true <=> is at EOF (or is NULL !)
*/
extern bool
-keystroke_stream_eof(keystroke_stream stream)
+keystroke_stream_at_eof(keystroke_stream stream)
{
/* Note that when EOF is signalled, any partial keystroke in construction
* is converted to a broken keystroke and placed in the stream.
* (So eof_met => no partial keystroke.)
*/
- return (stream == NULL) || (vio_fifo_empty(&stream->fifo) && stream->eof_met);
+ return (stream == NULL) || (vio_fifo_empty(stream->fifo) && stream->eof_met) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * See if given keystroke stream has seen "EOF" yet, that is:
+ *
+ * * EOF has been signalled by a suitable call of keystroke_input().
+ *
+ * NB: the keystroke stream may not be empty.
+ *
+ * Returns: true <=> is at EOF (or is NULL !)
+ */
+extern bool
+keystroke_stream_met_eof(keystroke_stream stream)
+{
+ return (stream == NULL) || stream->eof_met ;
} ;
/*------------------------------------------------------------------------------
@@ -436,51 +489,194 @@ keystroke_stream_eof(keystroke_stream stream)
*
* * discard contents of the stream, including any partial keystroke.
*
- * * set the stream "eof_met".
+ * * set the stream "eof_met" with or without timed_out
*/
extern void
-keystroke_stream_set_eof(keystroke_stream stream)
+keystroke_stream_set_eof(keystroke_stream stream, bool timed_out)
{
- vio_fifo_reset_keep(&stream->fifo) ;
+ vio_fifo_clear(stream->fifo, true) ; /* and clear marks */
stream->eof_met = true ; /* essential information */
+ stream->timed_out = timed_out ; /* variant of eof */
- stream->steal_this = false ; /* keep tidy */
- stream->iac = false ;
- stream->in.state = kst_null ;
- stream->pushed_in.state = kst_null ;
+ stream->steal = steal_none ; /* keep tidy */
+ stream->in.state = kst_between ;
+ stream->pushed_in.state = kst_between ;
} ;
/*==============================================================================
- * Input raw bytes to given keyboard stream.
- *
- * To steal the next keystroke, pass 'steal' = address of a keystroke structure.
- * Otherwise, pass NULL.
- *
- * Note: when trying to steal, will complete any partial keystroke before
- * stealing the next one. May exit from here:
- *
- * a. without having completed the partial keystroke.
+ * Getting and stealing keystrokes.
+ */
+
+/*------------------------------------------------------------------------------
+ * Get next keystroke from keystroke stream
*
- * b. without having completed the keystroke to be stolen.
+ * Returns: true => have a stroke type != ks_null
+ * false => stroke type is ks_null (may be EOF).
+ */
+extern bool
+keystroke_get(keystroke_stream stream, keystroke stroke)
+{
+ int b ;
+
+ /* Get first byte and deal with FIFO empty response */
+ b = vio_fifo_get_byte(stream->fifo) ;
+
+ if (b < 0)
+ {
+ keystroke_set_null(stroke, stream) ;
+ return false ;
+ } ;
+
+ /* Fetch first byte and deal with the simple character case */
+ if ((b & kf_compound) == 0) /* Simple character ? */
+ {
+ keystroke_set_char(stroke, b) ;
+ return true ;
+ } ;
+
+ /* Sex the compound keystroke */
+
+ stroke->type = b & kf_type_mask ;
+ stroke->value = 0 ;
+ stroke->flags = b & kf_flag_mask ;
+ stroke->len = vio_fifo_get_byte(stream->fifo) ; /* -1 <=> end */
+
+ /* Fetch what we need to the stroke buffer */
+ if (stroke->len > 0)
+ {
+ uint get ;
+
+ assert(stroke->len <= keystroke_max_len) ;
+
+ get = vio_fifo_get_bytes(stream->fifo, stroke->buf, stroke->len) ;
+
+ assert(get == stroke->len) ;
+ }
+ else
+ assert(stroke->len == 0) ;
+
+ /* Complete the process, depending on the type */
+ switch (stroke->type)
+ {
+ case ks_null:
+ zabort("ks_null found in FIFO") ;
+
+ case ks_char:
+ /* If character is well formed, set its value */
+ if (stroke->flags == 0)
+ {
+ uint i ;
+ assert((stroke->len > 0) && (stroke->len <= 4)) ;
+ for (i = 0 ; i < stroke->len ; ++i)
+ stroke->value = (stroke->value << 8) + stroke->buf[i] ;
+
+ /* NB: to do UTF-8 would need to create UTF form here */
+
+ } ;
+ break ;
+
+ case ks_esc:
+ /* If have ESC X, set value = X */
+ if (stroke->len == 1)
+ stroke->value = stroke->buf[0] ;
+ else
+ assert(stroke->len == 0) ;
+ break ;
+
+ case ks_csi:
+ /* If have the final X, set value = X */
+ /* Null terminate the parameters */
+ if (stroke->len != 0)
+ {
+ --stroke->len ;
+ stroke->value = stroke->buf[stroke->len] ;
+ } ;
+ stroke->buf[stroke->len] = '\0' ;
+ break ;
+
+ case ks_iac:
+ /* If have the command byte after IAC, set value */
+ if (stroke->len > 0)
+ stroke->value = stroke->buf[0] ;
+ break ;
+
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Steal keystroke from keystroke stream
*
- * State (b) is remembered by the keystroke_stream.
+ * The first time this is called, sets the stream into "steal_next" state. As
+ * keystroke input is read -- see keystroke_input() -- the next keystroke to
+ * arrive will be stolen. The first time that this is called after a keystroke
+ * has been stolen, the stolen keystroke will be returned, and the stealing
+ * state reset.
*
- * Caller may have to call several times with steal != NULL to get a
- * keystroke.
+ * Returns: true => have a stolen keystroke type (may be EOF)
+ * false => nothing yet
+ */
+extern bool
+keystroke_steal(keystroke_stream stream, keystroke stroke)
+{
+ /* At EOF we immediately "steal" an eof or timed out ks_null.
+ */
+ if (stream->eof_met)
+ {
+ keystroke_set_null(stroke, stream) ;
+ return true ;
+ } ;
+
+ /* If have a stolen keystroke in hand -- return that and clear
+ * stealing state.
+ */
+ if (stream->steal == steal_done)
+ {
+ *stroke = *stream->stolen ;
+ stream->steal = steal_none ;
+ return true ;
+ } ;
+
+ /* Otherwise, if not already stealing, set stealing state.
+ *
+ * In any event, do not have a stolen keystroke, yet.
+ */
+ if (stream->steal == steal_none)
+ stream->steal = steal_next ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear any keystroke stealing state.
*
- * If steal != NULL the keystroke will be set to the stolen keystroke. That
- * will be type ks_null if nothing was available, and may be knull_eof.
+ * If keystroke has been stolen, it will be lost.
+ */
+extern void
+keystroke_steal_clear(keystroke_stream stream)
+{
+ stream->steal = steal_none ;
+} ;
+
+/*==============================================================================
+ * Reading of bytes into keystroke stream.
+ */
+
+/*------------------------------------------------------------------------------
+ * Input raw bytes to given keystroke stream.
*
- * Note that never steals broken or truncated keystrokes.
+ * Note that never steals broken or truncated keystrokes, or IAC.
*
- * Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream.
+ * Passing len < 0 signals EOF to the keystroke_stream.
*
* Updates the stream and returns updated raw
*/
extern void
-keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
- keystroke steal)
+keystroke_input(keystroke_stream stream, uint8_t* ptr, int len)
{
uint8_t* end ;
@@ -489,13 +685,12 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* Any partial keystroke is converted into a broken keystroke and placed
* at the end of the stream.
*
- * Note that this occurs before any attempt to steal a keystroke -- so can
- * never steal a broken keystroke.
+ * Note that neither steals no calls back broken keystrokes.
*/
- if ((len == 0) && (ptr == NULL))
+ if (len < 0)
{
stream->eof_met = true ;
- stream->steal_this = false ;
+ stream->timed_out = false ;
/* Loop to deal with any pending IAC and partial keystroke.
*
@@ -505,85 +700,49 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* A partial IAC sequence may have pushed a partial real keystroke
* sequence -- so loop until have dealt with that.
*/
- do
+ while (stream->in.state != kst_between)
{
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 ;
+ case kst_cr: /* expecting something after CR */
keystroke_add_raw(stream, '\r') ;
- keystroke_put(stream, ks_char, true,
+ keystroke_put(stream, ks_char, kf_broken,
stream->in.raw, stream->in.len) ;
break ;
- case kst_esc: /* expecting rest of escape */
- keystroke_clear_iac(stream) ;
-
- keystroke_put_esc(stream, '\0', 0) ;
- break ;
-
+ case kst_esc: /* expecting rest of escape */
case kst_csi:
- keystroke_clear_iac(stream) ;
-
- keystroke_put_csi(stream, '\0') ;
+ keystroke_put_esc(stream, '\0', kf_broken) ;
break ;
- case kst_iac_option: /* expecting rest of IAC */
- assert(!stream->iac) ;
- /* fall through */
+ /* For incomplete IAC sequences, insert a broken sequence.
+ *
+ * Note that where have just seen an IAC itself, we insert that
+ * now at the end of the sequence.
+ */
+ case kst_iac: /* expecting something after IAC */
+ case kst_iac_sub_iac:
+ keystroke_add_raw(stream, tn_IAC) ;
+ fall_through ;
+
+ case kst_iac_option: /* expecting rest of IAC XX */
case kst_iac_sub:
- keystroke_put_iac_long(stream, true) ;
+ keystroke_put_iac(stream, kf_broken) ;
- /* 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_between: /* Cannot be this */
+ 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) ;
+ keystroke_in_pop(stream) ; /* pops kst_between, when all done */
+ } ;
} ;
- /* Update the stealing state
- *
- * steal != NULL => want to steal a keystroke
- *
- * If do not wish to steal now, must clear any
- * remembered steal_this state.
- *
- * stream->steal_this => steal the next keystroke to complete.
- *
- * If want to steal a keystroke, this is set if
- * currently "between" keystrokes, or later when
- * reach that condition.
- *
- * Once set, this is remembered across calls to
- * keystroke_input(), while still wish to steal.
- */
- if (steal == NULL)
- stream->steal_this = false; /* not now required */
- else
- stream->steal_this = (stream->in.state == kst_null) ;
- /* want to and can can steal the next
- keystroke that completes */
-
/* Once EOF has been signalled, do not expect to receive any further input.
*
* However, keystroke_stream_set_eof() can set the stream artificially at
@@ -607,192 +766,219 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
*
* 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 = false ; /* IAC IAC => single IAC byte value */
- else
- {
- stream->iac = true ; /* seen an IAC */
- continue ; /* wait for next character */
- } ;
- } ;
-
- /* If stream->iac, then need to worry about IAC XX
- *
- * Note that IAC sequences are entirely invisible to the general
- * stream of keystrokes. So... IAC sequences may appear in the middle
- * of multi-byte general keystroke objects.
- *
- * If this is not a simple 2 byte IAC, then must put whatever was
- * collecting to one side, and deal with the IAC.
- *
- * Note: not interested in stealing an IAC object.
+ * First IAC changes state as required to process the next byte in
+ * suitable (sub) state.
*/
- if (stream->iac)
+ if (u == tn_IAC)
{
- stream->iac = false ; /* expect will eat the IAC XX */
-
switch (stream->in.state)
{
- case kst_null:
+ /* An IAC may interrupt any ordinary keystroke sequence.
+ *
+ * Switch to kst_iac state and continue by fetching the next byte,
+ * if any.
+ */
+ case kst_between:
case kst_cr:
case kst_esc:
case kst_csi:
- if (u < tn_SB)
- /* This is a simple IAC XX, one byte IAC */
- keystroke_put_iac_one(stream, u) ;
- else
- /* 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 ;
+ keystroke_in_push(stream) ; /* enter kst_iac state */
+ continue ; /* loop back */
- case kst_iac_sub:
- assert(stream->in.raw[0] == tn_SB) ;
+ /* An IAC in kst_iac state is an escaped IAC value, so pop the
+ * state and proceed with the tn_IAC value.
+ */
+ case kst_iac:
+ keystroke_in_pop(stream) ;
+ break ;
- if (u != tn_SE)
- {
- --ptr ; /* put back the XX */
- stream->iac = true ; /* put back the IAC */
- } ;
+ /* An IAC in kst_iac_option state is actually an EXOPL, so
+ * proceed with the EXOPL value.
+ */
+ case kst_iac_option:
+ confirm((uint)tn_IAC == (uint)to_EXOPL) ;
+ break ;
- keystroke_put_iac_long(stream, (u != tn_SE)) ;
- keystroke_in_pop(stream) ;
+ /* An IAC in kst_iac_sub state may be IAC IAC or IAC SE below.
+ *
+ * Switch to kst_iac_sub_iac state and continue by fetching the
+ * next byte, if any.
+ */
+ case kst_iac_sub:
+ stream->in.state = kst_iac_sub_iac ;
+ continue ; /* loop back */
+
+ /* An IAC in kst_iac state is an escaped IAC value, so return
+ * to kst_iac_sub state and proceed with the tn_IAC value.
+ */
+ case kst_iac_sub_iac:
+ stream->in.state = kst_iac_sub ;
break ;
case kst_char: /* TBD */
- case kst_iac_option:
zabort("impossible keystroke stream state") ;
default:
zabort("unknown keystroke stream state") ;
} ;
-
- continue ;
} ;
- /* No IAC complications... proceed per current state */
+ /* Dealt with an IAC, proceed with byte per current state.
+ */
switch (stream->in.state)
{
- case kst_null: /* Expecting anything */
- stream->steal_this = (steal != NULL) ;
-
- 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 */
- {
- stream->in.len = 0 ;
- stream->in.state = kst_csi ;
- }
- else
+ /* In kst_between state we are between keystrokes, and may get anything.
+ *
+ * If waiting to steal the next keystroke, this is the (start of the)
+ * next keystroke.
+ */
+ case kst_between:
+ if (stream->steal == steal_next)
+ stream->steal = steal_this ;
+
+ if (u <= max_cntrl)
{
- /* Won't steal NUL */
- if (!stream->steal_this || (u == '\0'))
- keystroke_put_char(stream, u) ;
- else
+ stream->in.len = 0 ; /* make sure */
+
+ if (u == '\0') /* discard NUL */
+ break ;
+
+ if (u == '\r')
+ {
+ stream->in.state = kst_cr ;
+ break ;
+ } ;
+
+ if (u == 0x1B)
+ {
+ stream->in.state = kst_esc ;
+ break ;
+ } ;
+
+ if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */
{
- keystroke_steal_char(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
+ stream->in.state = kst_csi ;
+ break ;
} ;
- stream->in.state = kst_null ;
+ if (stream->interrupt[u] && (stream->callback != NULL))
+ {
+ keystroke_t stroke ;
+
+ keystroke_set_char(stroke, u) ;
+
+ if ((*stream->callback)(stream->callback_context, stroke))
+ break ;
+ } ;
} ;
+
+ /* Simple single byte character: put or steal -- no state change.
+ */
+ keystroke_put_char(stream, u) ;
+
break ;
- case kst_char: /* TBD */
+ /* Multi-byte character handling -- TBD
+ */
+ case kst_char:
zabort("impossible keystroke stream state") ;
+ /* Byte following a '\r'
+ */
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' ;
- } ;
+ stream->in.state = kst_between ;
- if (!stream->steal_this)
- keystroke_put_char(stream, u) ;
- else
+ switch (u)
{
- keystroke_steal_char(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
+ case '\0':
+ u = '\r' ; /* treat '\r''\0' as '\r' (as expected) */
+
+ case '\r': /* treat '\r''\r' as '\r' (why not) */
+ case '\n': /* treat '\r''\n' as '\n' */
+ break ;
+
+ default:
+ --ptr ; /* put back the duff XX */
+ if (u == tn_IAC)
+ keystroke_in_push(stream) ;
+ /* re-enter kst_iac state */
+ u = '\r' ; /* treat '\r'XX as '\r' */
} ;
- stream->in.state = kst_null ;
+ keystroke_put_char(stream, u) ; /* put or steal */
+
break ;
- case kst_esc: /* Expecting XX after ESC */
+ /* Byte following ESC
+ */
+ case kst_esc:
if (u == '[')
- {
- stream->in.len = 0 ;
- stream->in.state = kst_csi ;
- }
+ stream->in.state = kst_csi ;
else
{
- if (!stream->steal_this)
- keystroke_put_esc(stream, u, 1) ;
- else
- {
- keystroke_steal_esc(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
- } ;
-
- stream->in.state = kst_null ;
+ keystroke_put_esc(stream, u, kf_null) ;
+ stream->in.state = kst_between ;
} ;
break ;
- case kst_csi: /* Expecting ... after ESC [ or CSI */
+ /* Byte(s) following CSI or ESC '['
+ */
+ case kst_csi:
if ((u >= 0x20) && (u <= 0x3F))
keystroke_add_raw(stream, u) ;
else
{
- bool ok ;
+ keystroke_flags_t flags = kf_null ;
+
+ stream->in.state = kst_between ; /* done */
- ok = stream->in.len < keystroke_max_len ;
- /* have room for terminator */
+ if (stream->in.len >= keystroke_max_len)
+ {
+ flags |= kf_truncated ;
+ stream->in.len = keystroke_max_len - 1 ;
+ } ;
if ((u < 0x40) || (u > 0x7E))
{
--ptr ; /* put back the duff XX */
- stream->iac = (u == tn_IAC) ;
- /* re=escape if is IAC */
+ if (u == tn_IAC)
+ keystroke_in_push(stream) ;
+ /* re-enter kst_iac state */
u = '\0' ;
- ok = false ; /* broken */
+ flags |= kf_broken ;
} ;
- if (!stream->steal_this || !ok)
- keystroke_put_csi(stream, u) ;
- else
- {
- keystroke_steal_csi(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
- } ;
- stream->in.state = kst_null ;
+ keystroke_put_esc(stream, u, flags) ;
} ;
break ;
- case kst_iac_option: /* Expecting <option> after IAC XX */
+ /* Byte following IAC -- where that byte is not itself IAC
+ */
+ case kst_iac: /* IAC XX -- process the XX */
+ keystroke_add_raw(stream, u) ;
+
+ if (u < tn_SB)
+ {
+ /* This is a simple IAC XX, one byte IAC
+ */
+ keystroke_put_iac(stream, kf_null) ;
+ keystroke_in_pop(stream) ;
+ }
+ else
+ {
+ /* This is a multi-byte IAC, so set into kst_iac_option state.
+ */
+ stream->in.state = kst_iac_option ;
+ } ;
+ break ;
+
+ /* Byte following IAC XX -- expecting <option>
+ *
+ * If the XX was SB -> kst_iac_sub state: IAC SB <option> .... IAC SE
+ *
+ * Otherwise, this is a 3 byte IAC: IAC XX <option>
+ */
+ case kst_iac_option:
assert(stream->in.len == 1) ;
keystroke_add_raw(stream, u) ;
@@ -800,28 +986,55 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
stream->in.state = kst_iac_sub ;
else
{
- keystroke_put_iac_long(stream, false) ;
+ keystroke_put_iac(stream, kf_null) ;
keystroke_in_pop(stream) ;
} ;
break ;
- case kst_iac_sub: /* Expecting sub stuff */
- assert(stream->in.raw[0]== tn_SB) ;
+ /* Bytes following IAC SB <option> ...
+ *
+ * This IAC is terminated by IAC SE. If an IAC is seen, the next byte
+ * is dealt with in kst_iac_sub_iac state.
+ */
+ case kst_iac_sub:
+ assert(stream->in.raw[0] == tn_SB) ;
keystroke_add_raw(stream, u) ;
break ;
+ /* in kst_iac_sub_iac state, expect:
+ *
+ * IAC -- escaped 0xFF value -- dealt with already.
+ *
+ * SE -- end of kst_iac_sub state
+ *
+ * Nothing else.
+ *
+ * So here the IAC is terminated, broken if don't have an SE, and the
+ * stack popped.
+ *
+ * Then if was broken, put back the XX to be processed in kst_iac state.
+ */
+ case kst_iac_sub_iac:
+ assert(stream->in.raw[0] == tn_SB) ;
+
+ keystroke_put_iac(stream, ((u == tn_SE) ? kf_null : kf_broken)) ;
+ keystroke_in_pop(stream) ;
+
+ if (u != tn_SE)
+ {
+ --ptr ; /* put back the XX */
+ keystroke_in_push(stream) ; /* enter kst_iac state */
+ } ;
+ break ;
+
+
default:
zabort("unknown keystroke stream state") ;
}
} ;
- assert(ptr == end) ;
- /* If did not steal a keystroke, return a ks_null -- which may be
- * a knull_eof.
- */
- if (steal != NULL)
- keystroke_set_null(stream, steal) ;
+ assert(ptr == end) ;
} ;
/*------------------------------------------------------------------------------
@@ -829,165 +1042,95 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* sequences transparently.
*/
-/* Push current state and set new current state for start of IAC option
- * sequence.
+/* Push current state and set new current state to process byte after IAC.
*/
static void
-keystroke_in_push(keystroke_stream stream, uint8_t u)
+keystroke_in_push(keystroke_stream stream)
{
- assert(stream->pushed_in.state == kst_null) ;
-
- stream->pushed_in = stream->in ;
+ assert(stream->pushed_in.state == kst_between) ;
- stream->in.len = 1 ;
- stream->in.raw[0] = u ;
+ stream->pushed_in = stream->in ;
- stream->in.state = kst_iac_option ;
+ stream->in.state = kst_iac ;
+ stream->in.len = 0 ;
} ;
-/* Pop the pushed state and clear the pushed state to kst_null */
+/* Pop the pushed state and clear the pushed state to kst_between (so further
+ * pop will return that)
+ */
static void
keystroke_in_pop(keystroke_stream stream)
{
stream->in = stream->pushed_in ;
- stream->pushed_in.state = kst_null ;
+ stream->pushed_in.state = kst_between ;
stream->pushed_in.len = 0 ;
} ;
-/*==============================================================================
- * Fetch next keystroke from keystroke stream
- *
- * Returns: 1 => have a stroke type != ks_null
- * 0 => stroke type is ks_null (may be EOF).
+/*------------------------------------------------------------------------------
+ * Set given keystroke to ks_null -- and set to knull_eof if stream->eof_met
*/
-extern int
-keystroke_get(keystroke_stream stream, keystroke stroke)
+inline static void
+keystroke_set_null(keystroke stroke, keystroke_stream stream)
{
- int b ;
- uint8_t* p ;
- uint8_t* e ;
-
- /* Get first byte and deal with FIFO empty response */
- b = vio_fifo_get_byte(&stream->fifo) ;
-
- if (b < 0)
- return keystroke_set_null(stream, stroke) ;
-
- /* Fetch first byte and deal with the simple character case */
- if ((b & kf_compound) == 0) /* Simple character ? */
- {
- stroke->type = ks_char ;
- stroke->value = b ;
-
- stroke->flags = 0 ;
- stroke->len = 1 ;
- stroke->buf[0] = b ;
-
- return 1 ;
- } ;
-
- /* Sex the compound keystroke */
-
- stroke->type = b & kf_type_mask ;
- stroke->value = 0 ;
- stroke->flags = b & kf_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 ;
-
- while (p < e)
- *p++ = keystroke_get_byte(stream) ;
-
- p = stroke->buf ;
-
- /* Complete the process, depending on the type */
- switch (stroke->type)
- {
- case ks_null:
- zabort("ks_null found in FIFO") ;
-
- case ks_char:
- /* If character is well formed, set its value */
- if (stroke->flags == 0)
- {
- assert((stroke->len > 0) && (stroke->len <= 4)) ;
- while (p < e)
- stroke->value = (stroke->value << 8) + *p++ ;
-
- /* NB: to do UTF-8 would need to create UTF form here */
-
- } ;
- break ;
-
- case ks_esc:
- /* If have ESC X, set value = X */
- if (stroke->len == 1)
- stroke->value = *p ;
- else
- assert(stroke->len == 0) ;
- break ;
-
- case ks_csi:
- /* If have the final X, set value = X */
- /* Null terminate the parameters */
- if (stroke->len != 0)
- {
- --e ;
- stroke->value = *e ;
- --stroke->len ;
- } ;
- *e = '\0' ;
- break ;
-
- case ks_iac:
- /* If have the command byte after IAC, set value */
- if (stroke->len > 0)
- stroke->value = *p ;
- break ;
+ stroke->type = ks_null ;
+ stroke->value = stream->eof_met ? (stream->timed_out ? knull_timed_out
+ : knull_eof)
+ : knull_not_eof ;
+ stroke->flags = 0 ;
+ stroke->len = 0 ;
+} ;
- default:
- zabort("unknown keystroke type") ;
- } ;
+/*------------------------------------------------------------------------------
+ * Set given keystroke to simple character
+ */
+inline static void
+keystroke_set_char(keystroke stroke, uint8_t u)
+{
+ stroke->type = ks_char ;
+ stroke->value = u ;
- return 1 ;
+ stroke->flags = 0 ;
+ stroke->len = 1 ;
+ stroke->buf[0] = u ;
} ;
/*------------------------------------------------------------------------------
- * Set given keystroke to ks_null -- and set to knull_eof if stream->eof_met
- *
- * Returns: 0
+ * Set given keystroke to given escape
*/
-inline static int
-keystroke_set_null(keystroke_stream stream, keystroke stroke)
+inline static void
+keystroke_set_esc(keystroke stroke, keystroke_type_t type,
+ keystroke_stream stream)
{
- stroke->type = ks_null ;
- stroke->value = stream->eof_met ? knull_eof : knull_not_eof ;
+ int len ;
- stroke->flags = 0 ;
- stroke->len = 0 ;
+ len = stream->in.len - 1 ; /* exclude the escape terminator */
+ assert((len >= 0) && (len < keystroke_max_len)) ;
+
+ if (len > 0)
+ memcpy(stroke->buf, stream->in.raw, len) ;
+ stroke->buf[len] = '\0' ;
- return 0 ;
+ stroke->type = type ;
+ stroke->value = stream->in.raw[len] ; /* last character */
+ stroke->flags = 0 ;
+ stroke->len = len ;
} ;
/*------------------------------------------------------------------------------
- * Fetch 2nd or subsequent byte of keystroke.
- *
- * NB: it is impossible for partial keystrokes to be written, so this treats
- * buffer empty as a FATAL error.
+ * Set given keystroke to given escape
*/
-inline static uint8_t
-keystroke_get_byte(keystroke_stream stream)
+inline static void
+keystroke_set_iac(keystroke stroke, keystroke_stream stream)
{
- int b = vio_fifo_get_byte(&stream->fifo) ;
+ assert((stream->in.len >= 1) && (stream->in.len <= keystroke_max_len)) ;
- passert(b >= 0) ;
+ memcpy(stroke->buf, stream->in.raw, stream->in.len) ;
- return b ;
+ stroke->type = ks_iac ;
+ stroke->value = stream->in.raw[0] ;
+ stroke->flags = 0 ;
+ stroke->len = stream->in.len ;
} ;
/*==============================================================================
@@ -996,6 +1139,8 @@ keystroke_get_byte(keystroke_stream stream)
/*------------------------------------------------------------------------------
* If possible, add character to the stream->in.raw[] buffer
+ *
+ * Note that the stream->in.len continues counting beyond the end of the buffer.
*/
static inline void
keystroke_add_raw(keystroke_stream stream, uint8_t u)
@@ -1007,197 +1152,89 @@ keystroke_add_raw(keystroke_stream stream, uint8_t u)
} ;
/*------------------------------------------------------------------------------
- * Store simple character value
+ * Store simple 8 bit character value, or steal it
*/
static void
-keystroke_put_char(keystroke_stream stream, uint32_t u)
+keystroke_put_char(keystroke_stream stream, uint8_t u)
{
- if (u < 0x80)
- vio_fifo_put_byte(&stream->fifo, (uint8_t)u) ;
+ if (stream->steal == steal_this)
+ {
+ keystroke_set_char(stream->stolen, u) ;
+ stream->steal = steal_done ;
+ }
else
{
- uint8_t buf[4] ;
- uint8_t* p ;
-
- p = buf + 4 ;
-
- do
- {
- *(--p) = u & 0xFF ;
- u >>= 8 ;
- }
- while (u != 0) ;
-
- keystroke_put(stream, ks_char, 0, p, (buf + 4) - p) ;
+ if (u < kf_compound)
+ vio_fifo_put_byte(stream->fifo, u) ;
+ else
+ keystroke_put(stream, ks_char, kf_null, &u, 1) ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Store simple ESC. Is broken if length (after ESC) == 0 !
- */
-static void
-keystroke_put_esc(keystroke_stream stream, uint8_t u, int len)
-{
- keystroke_put(stream, ks_esc, (len == 0), &u, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Store CSI.
- *
- * Plants the last character of the CSI in the buffer, even if has to overwrite
- * the existing last character -- the sequence is truncated, but this way at
- * least the end of the sequence is preserved.
- *
- * Is broken if u == '\0'. May also be truncated !
+ * Store simple ESC or CSI, or steal it (but won't steal if broken/truncated)
*/
static void
-keystroke_put_csi(keystroke_stream stream, uint8_t u)
+keystroke_put_esc(keystroke_stream stream, uint8_t u, keystroke_flags_t flags)
{
- int l ;
-
- l = stream->in.len++ ;
+ keystroke_type_t type ;
- if (l >= keystroke_max_len)
- l = keystroke_max_len - 1 ;
+ keystroke_add_raw(stream, u) ;
- stream->in.raw[l] = u ; /* plant terminator */
+ type = (stream->in.state == kst_esc) ? ks_esc : ks_csi ;
- keystroke_put(stream, ks_csi, (u == '\0'), stream->in.raw, stream->in.len) ;
+ if ((stream->steal == steal_this)
+ && ((flags & (kf_broken | kf_truncated)) == 0))
+ {
+ keystroke_set_esc(stream->stolen, type, stream) ;
+ stream->steal = steal_done ;
+ }
+ else
+ keystroke_put(stream, type, flags, stream->in.raw, stream->in.len) ;
} ;
/*------------------------------------------------------------------------------
* Store IAC -- if not broken, send it via any call-back.
*/
static void
-keystroke_put_iac(keystroke_stream stream, bool broken, uint8_t* bytes, int len)
+keystroke_put_iac(keystroke_stream stream, keystroke_flags_t flags)
{
bool dealt_with = false ;
- if (!broken && (stream->iac_callback != NULL))
+ if ((stream->callback != NULL) && ((flags & (kf_broken | kf_truncated)) == 0))
{
- struct keystroke stroke ;
-
- assert((len >= 1) && (bytes != NULL) && (len <= keystroke_max_len)) ;
+ keystroke_t stroke ;
- stroke.type = ks_iac ;
- stroke.value = bytes[0] ;
- stroke.flags = 0 ;
- stroke.len = len ;
+ keystroke_set_iac(stroke, stream) ;
- memcpy(&stroke.buf, bytes, len) ;
-
- dealt_with = (*stream->iac_callback)(stream->iac_callback_context,
- &stroke) ;
+ dealt_with = (*stream->callback)(stream->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.
- */
-static void
-keystroke_put_iac_long(keystroke_stream stream, bool broken)
-{
- keystroke_put_iac(stream, broken, stream->in.raw, stream->in.len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * 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 ;
- } ;
+ keystroke_put(stream, ks_iac, flags, stream->in.raw, stream->in.len) ;
} ;
/*------------------------------------------------------------------------------
- * Store <first> <len> [<bytes>]
+ * Store <first> <len> [<bytes>] -- compound keystroke.
*
* If len == 0, bytes may be NULL
*/
static void
-keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken,
- uint8_t* bytes, int len)
+keystroke_put(keystroke_stream stream, keystroke_type_t type,
+ keystroke_flags_t flags,
+ uint8_t* bytes, uint len)
{
if (len > keystroke_max_len)
{
len = keystroke_max_len ;
- type |= kf_truncated ;
+ flags |= kf_truncated ;
} ;
- vio_fifo_put_byte(&stream->fifo,
- kf_compound | (broken ? kf_broken : 0) | type) ;
- vio_fifo_put_byte(&stream->fifo, len) ;
+ vio_fifo_put_byte(stream->fifo, kf_compound | flags | type) ;
+ vio_fifo_put_byte(stream->fifo, len) ;
if (len > 0)
- vio_fifo_put(&stream->fifo, (void*)bytes, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Steal character value -- cannot be broken
- */
-static void
-keystroke_steal_char(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- steal->type = ks_char ;
- steal->value = u ;
- steal->flags = 0 ;
- steal->len = 1 ;
- steal->buf[0] = u ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Steal simple escape -- cannot be broken
- */
-static void
-keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- steal->type = ks_esc ;
- steal->value = u ;
- steal->flags = 0 ;
- steal->len = 1 ;
- steal->buf[0] = u ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Steal CSI escape -- 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.
- */
-static void
-keystroke_steal_csi(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- int 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 ;
-
- memcpy(steal->buf, stream->in.raw, len) ;
- steal->buf[len] = '\0' ;
+ vio_fifo_put_bytes(stream->fifo, (void*)bytes, len) ;
} ;
/*==============================================================================
diff --git a/lib/keystroke.h b/lib/keystroke.h
index 76fba15e..b04a2e20 100644
--- a/lib/keystroke.h
+++ b/lib/keystroke.h
@@ -22,26 +22,20 @@
#ifndef _ZEBRA_KEYSTROKE_H
#define _ZEBRA_KEYSTROKE_H
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
+#include "misc.h"
#include <arpa/telnet.h>
#include "zassert.h"
#include "vio_fifo.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Keystroke buffering
*/
enum { keystroke_max_len = 100 } ;
-enum keystroke_type
+typedef enum keystroke_type
{
ks_null = 0, /* nothing, nada, bupkis... */
ks_char, /* character -- uint32_t */
@@ -52,17 +46,21 @@ enum keystroke_type
ks_type_count,
ks_type_reserved = 0x0F,
-} ;
+} keystroke_type_t ;
+
CONFIRM(ks_type_count <= ks_type_reserved) ;
-enum keystroke_null
+typedef enum keystroke_null
{
knull_not_eof,
- knull_eof
-};
+ knull_eof,
+ knull_timed_out,
+} keystroke_null_t ;
-enum keystroke_flags
+typedef enum keystroke_flags
{
+ kf_null = 0,
+
kf_compound = 0x80, /* marker on all compound characters */
kf_reserved = 0x40,
@@ -73,26 +71,33 @@ enum keystroke_flags
kf_flag_mask = 0x70, /* flags for the keystroke */
kf_type_mask = 0x0F, /* extraction of type */
-} ;
+} keystroke_flags_t ;
-CONFIRM(ks_type_reserved == (enum keystroke_type)kf_type_mask) ;
+/* The keystroke type and flags are designed so that they can be packed
+ * together as a single byte, the first of a "compound character".
+ */
+typedef uint8_t keystroke_compound_t ;
+
+CONFIRM(ks_type_reserved == (keystroke_type_t)kf_type_mask) ;
+CONFIRM((kf_compound | kf_flag_mask | kf_type_mask) <= 0xFF) ;
typedef struct keystroke* keystroke ;
typedef struct keystroke_stream* keystroke_stream ;
struct keystroke
{
- enum keystroke_type type ;
- uint8_t flags ; /* the kf_flag_mask flags */
+ keystroke_type_t type ;
+ keystroke_flags_t flags ; /* just the kf_flag_mask bits */
uint32_t value ;
unsigned len ;
uint8_t buf[keystroke_max_len] ;
} ;
+typedef struct keystroke keystroke_t[1] ;
-#define keystroke_iac_callback_args void* context, keystroke stroke
-typedef bool (keystroke_callback)(keystroke_iac_callback_args) ;
+#define keystroke_callback_args void* context, keystroke stroke
+typedef bool (keystroke_callback)(keystroke_callback_args) ;
/* Telnet commands/options */
enum tn_Command
@@ -170,25 +175,21 @@ enum tn_Option
/*==============================================================================
* Functions
*/
-extern keystroke_stream
-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 keystroke_stream
-keystroke_stream_free(keystroke_stream stream) ;
-
-extern bool
-keystroke_stream_empty(keystroke_stream stream) ;
-extern bool
-keystroke_stream_eof(keystroke_stream stream) ;
-
-extern void
-keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
- keystroke steal) ;
-extern int
-keystroke_get(keystroke_stream stream, keystroke stroke) ;
+extern keystroke_stream keystroke_stream_new(uint8_t csi_char,
+ const char* interrupts,
+ keystroke_callback* callback, void* callback_context) ;
+
+extern void keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) ;
+
+extern keystroke_stream keystroke_stream_free(keystroke_stream stream) ;
+
+extern bool keystroke_get(keystroke_stream stream, keystroke stroke) ;
+extern bool keystroke_steal(keystroke_stream stream, keystroke stroke) ;
+extern void keystroke_steal_clear(keystroke_stream stream) ;
+extern bool keystroke_stream_empty(keystroke_stream stream) ;
+extern bool keystroke_stream_at_eof(keystroke_stream stream) ;
+extern bool keystroke_stream_met_eof(keystroke_stream stream) ;
+
+extern void keystroke_input(keystroke_stream stream, uint8_t* ptr, int len) ;
#endif /* _ZEBRA_KEYSTROKE_H */
diff --git a/lib/list_util.c b/lib/list_util.c
index 720b8ca7..09ddb6b5 100644
--- a/lib/list_util.c
+++ b/lib/list_util.c
@@ -18,9 +18,8 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
-
-#include <list_util.h>
-
+#include "misc.h"
+#include "list_util.h"
/*==============================================================================
* Single Base, Single Link
@@ -50,31 +49,85 @@
*
* Note again the cast to (void**).
*
- * Returns: 0 => OK -- removed item from list (OR item == NULL)
- * -1 => item not found on list
+ * Returns: true => removed item from list
+ * false => item not found on list (or item == NULL)
*/
-extern int
-ssl_del_func(void* p_this, void* item, size_t link_offset)
+extern bool
+ssl_del_func(void** p_this, void* item, size_t link_offset)
{
void* this ;
if (item == NULL)
- return 0 ;
+ return false ;
- while ((this = *(void**)p_this) != item)
+ while ((this = *p_this) != item)
{
if (this == NULL)
- return -1 ;
+ return false ;
p_this = _sl_p_next(this, link_offset) ;
} ;
- *(void**)p_this = _sl_next(item, link_offset) ;
+ *p_this = _sl_next(item, link_offset) ;
- return 0 ;
+ return true ;
} ;
/*==============================================================================
- * Single Base, Double Link
+ * Double Base, Single Link
+ */
+
+/*------------------------------------------------------------------------------
+ * Deleting item
+ *
+ * Have to chase down list to find item.
+ *
+ * Note that p_this:
+ *
+ * * starts as pointer to the base pointer, so should really be void**,
+ * but that causes all sorts of problems with strict-aliasing.
+ *
+ * So: have to cast to (void**) before dereferencing to get the address
+ * of the first item on the list.
+ *
+ * * as steps along the list p_this points to the "next pointer" in the
+ * previous item.
+ *
+ * The _sl_p_next() macro adds the offset of the "next pointer" to the
+ * address of the this item.
+ *
+ * * at the end, assigns the item's "next pointer" to the "next pointer"
+ * field pointed at by p_this.
+ *
+ * Note again the cast to (void**).
+ *
+ * Returns: true => removed item from list
+ * false => item not found on list (or item == NULL)
*/
+extern bool
+dsl_del_func(struct dl_void_base_pair* p_base, void* item, size_t link_offset)
+{
+ void* this ;
+ void** p_this ;
+
+ if (item == NULL)
+ return false ;
+
+ p_this = &p_base->head ;
+
+ while ((this = *p_this) != item)
+ {
+ if (this == NULL)
+ return false ;
+
+ p_this = _sl_p_next(this, link_offset) ;
+ } ;
+
+ *p_this = _sl_next(item, link_offset) ;
+
+ if (item == p_base->tail)
+ p_base->tail = *p_this ;
+
+ return true ;
+} ;
diff --git a/lib/list_util.h b/lib/list_util.h
index 876b7b11..0ce3e6fa 100644
--- a/lib/list_util.h
+++ b/lib/list_util.h
@@ -22,12 +22,7 @@
#ifndef _ZEBRA_LIST_UTIL_H
#define _ZEBRA_LIST_UTIL_H
-#include <stddef.h>
-
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
+#include "misc.h"
/*------------------------------------------------------------------------------
* Note that the following fell foul of "strict-aliasing":
@@ -47,7 +42,7 @@
* } ;
*
* the assignment to *p_base is, apparently, unacceptable. This works
- * perfectly well as am ordinary function. Using a GNUC extension it is
+ * perfectly well as an ordinary function. Using a GNUC extension it is
* possible to avoid the function call... hence the ugly skips.
*/
#ifdef __GNUC__
@@ -114,15 +109,17 @@
#define dl_base_pair(ptr_t) { ptr_t head ; ptr_t tail ; }
-struct dl_void_list_pair list_pair(void*) ;
-struct dl_void_base_pair base_pair(void*) ;
+#define INIT_DL_BASE_PAIR { NULL, NULL }
+
+struct dl_void_list_pair dl_list_pair(void*) ;
+struct dl_void_base_pair dl_base_pair(void*) ;
#define _lu_off(obj, field) ((char*)&((obj)->field) - (char*)(obj))
/*==============================================================================
* Single Base, Single Link
*
- * To delete entry must chase down list to find it.
+ * To delete entry must chase down list to find it. Cannot insert at tail.
*
* Supports:
*
@@ -138,10 +135,10 @@ struct dl_void_base_pair base_pair(void*) ;
*
* ssl_del(base, item, next) -- delete from list
*
- * Treat as function returning int. Does nothing if the item is NULL.
+ * Treat as function returning bool. Does nothing if the item is NULL.
*
- * Returns: 0 => OK -- removed item from list (OR item == NULL)
- * -1 => item not found on list
+ * Returns: true => removed item from list
+ * false => item not found on list (or item was NULL)
*
* ssl_del_head(base, next) -- delete head of list
*
@@ -242,11 +239,11 @@ struct dl_void_base_pair base_pair(void*) ;
(base) = item ; \
} while (0)
-extern int ssl_del_func(void* p_this, void* obj, size_t link_offset)
+Private bool ssl_del_func(void** p_this, void* obj, size_t link_offset)
__attribute__((noinline)) ;
#define ssl_del(base, item, next) \
- ssl_del_func((void*)(&base), item, _lu_off(item, next))
+ ssl_del_func((void**)(&base), item, _lu_off(item, next))
#define ssl_del_head(base, next) \
do { if ((base) != NULL) \
@@ -266,10 +263,10 @@ extern int ssl_del_func(void* p_this, void* obj, size_t link_offset)
*/
#define _sl_p_next(item, off) \
- ( (char*)(item) + (off) )
+ ((void**)( (char*)(item) + (off) ))
#define _sl_next(item, off) \
- *(void**)_sl_p_next(item, off)
+ *_sl_p_next(item, off)
/*==============================================================================
* Single Base, Double Link
@@ -511,7 +508,7 @@ extern int ssl_del_func(void* p_this, void* obj, size_t link_offset)
*
* Treat as function returning void*.
*
- * Returns old head in dst and as return from "function".
+ * Returns old tail in dst and as return from "function".
*
* Returns NULL and sets dst == NULL if list is empty.
*
@@ -726,4 +723,201 @@ extern int ssl_del_func(void* p_this, void* obj, size_t link_offset)
#define ddl_prev(item, list) \
((item) != NULL ? (item)->list.prev : NULL)
+ /*==============================================================================
+ * Double Base, Single Link
+ *
+ * To delete entry must chase down list to find it. Can insert at tail, but
+ * not remove (except by chasing down list).
+ *
+ * Supports:
+ *
+ * dsl_init(base) -- initialise base
+ *
+ * An empty list has *both* head and tail pointers NULL.
+ *
+ * NB: confusion will arise if only one of these pointers is NULL.
+ *
+ * dsl_push(base, item, next) -- insert at head of list
+ *
+ * Treat as void function. The item may *not* be NULL.
+ *
+ * Undefined if item is already on any list (including this one).
+ *
+ * dsl_append(base, item, next) -- insert at tail of list
+ *
+ * Treat as void function. The item may *not* be NULL.
+ *
+ * Undefined if item is already on any list (including this one).
+ *
+ * dsl_in_after(after, base, item, next) -- insert after
+ *
+ * Treat as void function. The after & item may *not* be NULL.
+ *
+ * Undefined if item is already on any list (including this one), or if
+ * after is not on the list.
+ *
+ * dsl_pop(&dst, base, next) -- pop head of list, if any
+ *
+ * Treat as function returning void*.
+ *
+ * Returns old head in dst and as return from "function".
+ *
+ * Returns NULL and sets dst == NULL if list is empty.
+ *
+ * dsl_del(base, item, next) -- delete from list
+ *
+ * Treat as void function. Does nothing if the item is NULL.
+ *
+ * Undefined if item is not on the list.
+ *
+ * dsl_del_head(base, next) -- delete head of list
+ *
+ * Treat as void function. Does nothing if the list is empty.
+ *
+ * dsl_del_tail(base, next) -- delete tail of list
+ *
+ * Treat as void function. Does nothing if the list is empty.
+ *
+ * dsl_head(base) -- return head of list
+ *
+ * Treat as function returning void*.
+ *
+ * dsl_tail(base) -- return tail of list
+ *
+ * Treat as function returning void*.
+ *
+ * dsl_next(item, next) -- step to next item, if any
+ *
+ * Treat as function returning void*. Returns NULL if the item is NULL.
+ *
+ * Note that dsl_del() and dsl_pop() do NOT affect the item->next pointer.
+ *
+ * Where:
+ *
+ * "base" to be an r-value of type: struct base_pair(struct item*)*
+ *
+ * That is... a variable or field which is a pointer to
+ *
+ * "item" to be an l-value of type struct item*
+ *
+ * "dst" to be an r-value of type struct item*
+ *
+ * "next" to be the name of a field in struct item of type struct item*
+ *
+ *------------------------------------------------------------------------------
+ * For example:
+ *
+ * struct item // definition for list items
+ * {
+ * ...
+ * struct item* bar_next ;
+ * ...
+ * } ;
+ *
+ * static struct base_pair(struct item*) bar_base ;
+ * // declaration of the list base
+ *
+ * // create item and add to list (adds at front)
+ * struct item* q = calloc(1, sizeof(struct item)) ;
+ * dsl_push(bar_base, q, bar_next) ;
+ *
+ * // remove item from list
+ * dsl_del(bar_base, q, bar_next) ;
+ *
+ * // walk a list
+ * struct item* t = dsl_head(bar_base) ;
+ * while (t != NULL)
+ * {
+ * ....
+ * t = dsl_next(t, bar_next) ;
+ * }
+ *
+ * // walk and empty out a list -- removing item before processing
+ * struct item* t ;
+ * while (dsl_pop(&t, bar_base, bar_next) != NULL)
+ * {
+ * .... // t points to old head of list
+ * }
+ *
+ * // walk and empty out a list -- removing after processing
+ * struct item* t ;
+ * while ((t = dsl_head(bar_base) != NULL)
+ * {
+ * ....
+ * dsl_del_head(bar_base, bar_next) ;
+ * }
+ *
+ * And for example:
+ *
+ * struct parent_item // parent structure containing list
+ * {
+ * ....
+ * struct base_pair(struct item*) bar_base ;
+ * ....
+ * }
+ *
+ * void footle(struct parent_item* parent, struct item* item)
+ * {
+ * ....
+ * dsl_push(parent->bar_base, item, bar_next) ;
+ * ....
+ * }
+ */
+
+ #define dsl_init(base) \
+ ((base).head = (base).tail = NULL)
+
+ #define dsl_push(base, item, next) \
+ do { (item)->next = (base).head ; \
+ if ((base).tail == NULL) \
+ (base).tail = (item) ; \
+ (base).head = (item) ; \
+ } while (0)
+
+ #define dsl_append(base, item, next) \
+ do { (item)->next = NULL ; \
+ if ((base).tail != NULL) \
+ (base).tail->next = (item) ; \
+ else \
+ (base).head = (item) ; \
+ (base).tail = (item) ; \
+ } while (0)
+
+ #define dsl_in_after(after, base, item, next) \
+ do { (item)->next = (after)->next ; \
+ (after)->next = (item) ; \
+ if ((base).tail == (after)) \
+ (base).tail = (item) ; \
+ } while (0)
+
+ Private bool dsl_del_func(struct dl_void_base_pair* p_base,
+ void* obj, size_t link_offset)
+ __attribute__((noinline)) ;
+ #define dsl_del(base, item, list) \
+ dsl_del_func((struct dl_void_base_pair*)(&base), item, \
+ _lu_off(item, next))
+
+ #define dsl_del_head(base, list) \
+ do { if ((base).head != NULL) \
+ { \
+ (base).head = (base).head->next ; \
+ if ((base).head == NULL) \
+ (base).tail = NULL ; \
+ } \
+ } while (0)
+
+ #define dsl_pop(dst, base, list) \
+ ((*(dst) = (base).head) != NULL \
+ ? ( ((base).head = (base).head->next) == NULL \
+ ? ( (base).tail = NULL, *(dst) ) \
+ : *(dst) ) \
+ : NULL)
+
+ #define dsl_head(base) ((base).head)
+
+ #define dsl_tail(base) ((base).tail)
+
+ #define dsl_next(item, list) \
+ ((item) != NULL ? (item)->next : NULL)
+
#endif /* _ZEBRA_LIST_UTIL_H */
diff --git a/lib/log.c b/lib/log.c
index 18878094..d4feb75c 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -25,14 +25,14 @@
#include <zebra.h>
#include "log.h"
-#include "vty.h"
-#include "uty.h"
+#include "log_local.h"
+#include "vty_log.h"
#include "memory.h"
-#include "command.h"
+
#ifndef SUNOS_5
#include <sys/un.h>
#endif
-/* for printstack on solaris */
+/* for printstack on solaris */
#ifdef HAVE_UCONTEXT_H
#include <ucontext.h>
#endif
@@ -40,64 +40,410 @@
#include "qfstring.h"
#include "sigevent.h"
-/* log is protected by the same mutext as vty, see comments in vty.c */
+/*------------------------------------------------------------------------------
+ * Logging is managed using struct zlog, which represents a set of logging
+ * destinations, which are output to in parallel, each with its own log
+ * priority.
+ *
+ * Notionally, there may be more than one strct zlog, but in practice there is
+ * only one and that is the one pointed at by zlog_default.
+ */
+struct zlog
+{
+ struct zlog* next ; /* To support multiple logging streams */
+
+ const char *ident; /* daemon name (first arg to openlog) */
+ zlog_proto_t protocol ;
-/* prototypes */
-static int uzlog_reset_file (struct zlog *zl);
-static void zlog_abort (const char *mess) __attribute__ ((noreturn));
-static void vzlog (struct zlog *zl, int priority, const char *format, va_list args);
-static void uzlog_backtrace(int priority);
-static void uvzlog (struct zlog *zl, int priority, const char *format, va_list args);
+ int maxlvl[ZLOG_DEST_COUNT]; /* maximum priority set */
+ int monitor_lvl ; /* last monitor level specified */
+ int default_lvl ; /* maxlvl to use if none is specified */
+
+ int emaxlvl[ZLOG_DEST_COUNT]; /* effective maximum priority */
-static int logfile_fd = -1; /* Used in signal handler. */
+ bool syslog ; /* have active syslog */
+ int file_fd ; /* fd for ZLOG_DEST_FILE (if any) */
+ int stdout_fd ; /* fd for ZLOG_DEST_STDOUT */
-struct zlog *zlog_default = NULL;
+ char *filename; /* for ZLOG_DEST_FILE */
+ int facility; /* as per syslog facility */
+ int syslog_options; /* 2nd arg to openlog */
+
+ int timestamp_precision; /* # of digits of subsecond precision */
+ bool record_priority; /* should messages logged through stdio
+ include the priority of the message? */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tables of protocol and log priority names.
+ */
const char *zlog_proto_names[] =
{
- "NONE",
- "DEFAULT",
- "ZEBRA",
- "RIP",
- "BGP",
- "OSPF",
- "RIPNG",
- "OSPF6",
- "ISIS",
- "MASC",
+ [ZLOG_NONE] = "NONE",
+ [ZLOG_DEFAULT] = "DEFAULT",
+ [ZLOG_ZEBRA] = "ZEBRA",
+ [ZLOG_RIP] = "RIP",
+ [ZLOG_BGP] = "BGP",
+ [ZLOG_OSPF] = "OSPF",
+ [ZLOG_RIPNG] = "RIPNG",
+ [ZLOG_OSPF6] = "OSPF6",
+ [ZLOG_ISIS] = "ISIS",
+ [ZLOG_MASC] = "MASC",
NULL,
-};
+} ;
const char *zlog_priority[] =
{
- "emergencies",
- "alerts",
- "critical",
- "errors",
- "warnings",
- "notifications",
- "informational",
- "debugging",
+ [LOG_EMERG] = "emergencies",
+ [LOG_ALERT] = "alerts",
+ [LOG_CRIT] = "critical",
+ [LOG_ERR] = "errors",
+ [LOG_WARNING] = "warnings",
+ [LOG_NOTICE] = "notifications",
+ [LOG_INFO] = "informational",
+ [LOG_DEBUG] = "debugging",
NULL,
};
+/*------------------------------------------------------------------------------
+ * Static variables
+ */
+struct zlog* zlog_default = NULL;
+
+struct zlog* zlog_list = NULL ;
+
+static volatile sig_atomic_t max_maxlvl = ZLOG_DISABLED ;
+
+qpt_mutex_t log_mutex ;
+
+int log_lock_count = 0 ;
+int log_assert_fail = 0 ;
+
+/*------------------------------------------------------------------------------
+ * Prototypes
+ */
+static void zlog_abort (const char *mess) __attribute__ ((noreturn)) ;
+static void uzlog_backtrace(int priority);
+
/*==============================================================================
- * Time stamp handling -- gettimeofday(), localtime() and strftime().
+ * Log locking and relationship with VTY.
*
- * Maintains a cached form of the current time (under the vty/log mutex), so
- * that can avoid multiple calls of localtime() and strftime() per second.
+ * For pthreads purposes there is a LOG_LOCK() mutex.
*
- * The value from gettimeofday() is in micro-seconds. Can provide timestamp
- * with any number of decimal digits, but at most 6 will be significant.
+ * To support interaction with the VTY:
+ *
+ * a. setting logging configuration.
+ *
+ * b. issuing log messages within the VTY
+ *
+ * the LOG_LOCK() may be collected while VTY_LOCK() -- BUT NOT vice versa !
+ * This is not too difficult, because the logging has no need to call VTY
+ * functions... except for "vty monitor" VTY, so, for that purpose:
+ *
+ * a. for each monitor VTY there is a "monitor_fifo", into which log messages
+ * are place if required. This buffer and some related flags must be
+ * handled under the LOG_LOCK().
+ *
+ * The vty_log() function takes care of this, and takes care of prompting
+ * the vty(s) to output the contents of the fifo.
+ *
+ * This will be called under LOG_LOCK(). It will NOT VTY_LOCK(). It
+ * will return promptly.
+ *
+ * b. once any logging has been handed to the VTY, the logging can forget
+ * about it.
+ *
+ * c. the interface from logging to VTY is in vty_log.h
+ *
+ * The logging system supports some logging once a signal (in particular,
+ * the hard exceptions, such as SIGSEGV) has gone off and the system is being
+ * brought to a dead stop. A separate mechanism is provided for outputting
+ * directly to any vty monitor VTY -- at a file descriptor level !
+ *
+ * The VTY may call logging functions in the same was as any other part of the
+ * system. When interacting with configuration etc, may need to acquire the
+ * LOG_LOCK, etc. The interface from VTY to logging is in log_local.h.
+ */
+
+/*------------------------------------------------------------------------------
+ * Further initialisation for qpthreads.
+ *
+ * This is done during "second stage" initialisation, when all nexuses have
+ * been set up and the qpthread_enabled state established.
+ *
+ * Initialise mutex.
+ *
+ * NB: may be called once and once only.
+ */
+extern void
+log_init_r(void)
+{
+ qpt_mutex_init(log_mutex, qpt_mutex_recursive);
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close down for qpthreads.
+ */
+extern void
+log_finish(void)
+{
+ qpt_mutex_destroy(log_mutex, 0);
+} ;
+
+/*==============================================================================
+ * The actual logging function and creation of the logging lines.
*/
+/* Structure used to hold line for log output -- so that need be generated
+ * just once even if output to multiple destinations.
+ *
+ * Note that the buffer length is a hard limit (including terminating "\n")
+ * Do not wish to malloc any larger buffer while logging.
+ */
+enum { logline_buffer_len = 1008 } ;
+
+struct logline {
+ size_t len ; /* length including either '\r''\n' or '\n' */
+
+ char line[logline_buffer_len]; /* buffer */
+} ;
+typedef struct logline logline_t[1] ;
+typedef struct logline* logline ;
+
+static void uvzlog_line(logline ll, struct zlog *zl, int priority,
+ const char *format, va_list va) ;
static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ;
+/*==============================================================================
+ * The main logging function.
+ *
+ * This writes the logging information directly to all logging destinations,
+ * except the vty_log(), so that it is externally visible as soon as possible.
+ * This assumes that syslog and file output is essentially instantaneous, and
+ * will not block.
+ *
+ * Does not attempt to pick up or report any I/O errors.
+ *
+ * For vty_log(), any logging is buffered and dealt with by the VTY handler.
+ *
+ * So, having acquired the LOG_LOCK() the logging will proceed without
+ * requiring any further locks. Indeed, apart from vty_log() and
+ * uquagga_timestamp() (TODO) the process is async_signal_safe, even.
+ */
+extern void
+zlog (struct zlog *zl, int priority, const char *format, ...)
+{
+ logline_t ll ;
+
+ /* Decide whether any logging at all is required.
+ *
+ * NB: the max_maxlvl is established every time any change is made to
+ * logging facilities. Those changes are made under LOG_LOCK(), so
+ * only one thread will write to max_max_lvl at any time, and that
+ * will be consistent with the state of all known logging at the
+ * time.
+ *
+ * The max_maxlvl is sig_atomic_t and volatile, and we assume that:
+ *
+ * writing to max_maxlvl is atomic wrt all forms of interrupt,
+ * and wrt any processor cache invalidation.
+ *
+ * That is:
+ *
+ * the variable cannot be read in a partly written state (with,
+ * say, some bytes of the old value and some of the new).
+ *
+ * Hence, reading max_maxlvl will either collect the state before some
+ * change to the logging levels, or after.
+ *
+ * If the given priority > max_maxlvl, this function exits, otherwise it
+ * immediately acquires the LOG_LOCK().
+ *
+ * So, if the logging facilities are being changed, then:
+ *
+ * a) if the level is being increased, so the current priority would
+ * would pass, then that change is just too late for this logging
+ * operation.
+ *
+ * b) if the level is about to be reduced, then will get past the
+ * initial test, but after acquiring the LOG_LOCK(), will find that
+ * there is no logging to be done after all.
+ *
+ * NB: max_maxlvl is statically initialised to ZLOG_DISABLED.
+ */
+ if (priority > max_maxlvl)
+ return ;
+
+ /* Decide where we are logging to. */
+
+ LOG_LOCK() ;
+
+ if (zl == NULL)
+ {
+ zl = zlog_default ;
+
+ if (zl == NULL)
+ {
+ /* Have to get up very early in the morning to get to here, because
+ * zlog_default should be initialised -- by openzlog() -- very early
+ * indeed.
+ *
+ * So... "log" to stderr.
+ */
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+
+ write(fileno(stderr), ll->line, ll->len) ;
+
+ return ;
+ } ;
+ } ;
+
+ /* Step through the logging destinations and log as required. */
+
+ ll->len = 0 ; /* Nothing generated, yet */
+
+ /* Syslog output */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_SYSLOG])
+ {
+ va_list va;
+ va_start(va, format);
+ vsyslog (priority|zlog_default->facility, format, va);
+ va_end(va);
+ }
+
+ /* File output. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_FILE])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ write(zl->file_fd, ll->line, ll->len) ;
+ }
+
+ /* stdout output. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_STDOUT])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ write(zl->stdout_fd, ll->line, ll->len) ;
+ }
+
+ /* Terminal monitor. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_MONITOR])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ vty_log(priority, ll->line, ll->len - 1) ; /* less the '\n' */
+ } ;
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Preparation of line to send to logging: file, stdout or "monitor" terminals.
+ *
+ * Line ends in '\n', but no terminating '\0'.
+ *
+ * TODO: apart from uquagga_timestamp, this is all async_signal_safe.
+ *
+ */
+static void
+uvzlog_line(logline ll, struct zlog *zl, int priority,
+ const char *format, va_list va)
+{
+ const char* q ;
+ qf_str_t qfs ;
+
+ qfs_init(qfs, ll->line, sizeof(ll->line)) ;
+ /* "<time stamp>" */
+ uquagga_timestamp(qfs, (zl != NULL) ? zl->timestamp_precision : 0) ;
+
+ qfs_append_n(qfs, " ", 1) ;
+
+ /* "<priority>: " if required */
+ if ((zl != NULL) && zl->record_priority)
+ {
+ qfs_append(qfs, zlog_priority[priority]) ;
+ qfs_append(qfs, ": ") ;
+ } ;
+
+ /* "<protocol>: " or "unknown: " */
+ if (zl != NULL)
+ q = zlog_proto_names[zl->protocol] ;
+ else
+ q = "unknown" ;
+
+ qfs_append(qfs, q) ;
+ qfs_append(qfs, ": ") ;
+
+ /* Now the log line itself (uses a *copy* of the va_list) */
+ qfs_vprintf(qfs, format, va) ;
+
+ /* Stick '\n' on the end */
+ qfs_append_n(qfs, "\n", 1) ;
+
+ /* Worry about overflow of message */
+ if (qfs_overflow(qfs) != 0)
+ qfs_term_string(qfs, "...\n", sizeof("...\n") - 1) ;
+
+ ll->len = qfs_len(qfs) ;
+} ;
+
/*------------------------------------------------------------------------------
* Fill buffer with current time, to given number of decimal digits.
*
* If given buffer is too small, provides as many characters as possible.
+
+ * Time stamp handling -- gettimeofday(), localtime() and strftime().
+ *
+ * Maintains a cached form of the current time (under the vty/log mutex), so
+ * that can avoid multiple calls of localtime() and strftime() per second.
+ *
+ * The value from gettimeofday() is in micro-seconds. Can provide timestamp
+ * with any number of decimal digits, but at most 6 will be significant.
+ *
+ * Puts a current timestamp in buf and returns the number of characters
+ * written (not including the terminating NUL). The purpose of
+ * this function is to avoid calls to localtime appearing all over the code.
+ * It caches the most recent localtime result and can therefore
+ * avoid multiple calls within the same second.
+ *
+ * The buflen MUST be > 1 and the buffer address MUST NOT be NULL.
+ *
+ * If buflen is too small, writes buflen-1 characters followed by '\0'.
+ *
+ * Time stamp is rendered in the form:
+ */
+#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S"
+/*
+ * This has a fixed length (leading zeros are included) of 19 characters
+ * (unless this code is still in use beyond the year 9999 !)
+ *
+ * Which may be followed by "." and a number of decimal digits, usually 1..6.
*
+ * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and
+ * rounding up for good measure -- buffer size = 32.
+ */
+CONFIRM(timestamp_buffer_len >= 32) ;
+/*
* Returns: number of characters in buffer, not including trailing '\0'.
*
* NB: does no rounding.
@@ -109,18 +455,21 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen)
{
qf_str_t qfs ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- qfs_init(&qfs, buf, buflen) ;
- uquagga_timestamp(&qfs, timestamp_precision) ;
+ qfs_init(qfs, buf, buflen) ;
+ uquagga_timestamp(qfs, timestamp_precision) ;
- VTY_UNLOCK() ;
- return qfs_len(&qfs) ;
+ LOG_UNLOCK() ;
+ return qfs_len(qfs) ;
}
/*------------------------------------------------------------------------------
* unprotected version for when mutex already held
*/
+
+// used in uvzlog_line
+
static void
uquagga_timestamp(qf_str qfs, int timestamp_precision)
{
@@ -135,7 +484,7 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision)
/* would it be sufficient to use global 'recent_time' here? I fear not... */
gettimeofday(&clock, NULL);
- /* first, we update the cache if the time has changed */
+ /* first, we update the cache if the seconds time has changed */
if (cache.last != clock.tv_sec)
{
struct tm tm;
@@ -174,137 +523,54 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision)
} ;
/*==============================================================================
- * va_list version of zlog
+ *
*/
-static void
-vzlog (struct zlog *zl, int priority, const char *format, va_list args)
+
+void
+_zlog_assert_failed (const char *assertion, const char *file,
+ unsigned int line, const char *function)
{
- VTY_LOCK() ;
- uvzlog(zl, priority, format, args);
- VTY_UNLOCK() ;
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size,
+ "Assertion `%s' failed in file %s, line %u, function %s",
+ assertion, file, line, (function ? function : "?"));
+ zlog_abort(buff);
}
-/* va_list version of zlog. Unprotected assumes mutex already held*/
-static void
-uvzlog (struct zlog *zl, int priority, const char *format, va_list va)
+/* Abort with message */
+void
+_zlog_abort_mess (const char *mess, const char *file,
+ unsigned int line, const char *function)
{
- struct logline ll ; /* prepares line for output, here */
-
- VTY_ASSERT_LOCKED() ;
-
- ll.p_nl = NULL ; /* Nothing generated, yet */
-
- /* If zlog is not specified, use default one. */
- if (zl == NULL)
- zl = zlog_default ;
-
- /* When zlog_default is also NULL, use stderr for logging. */
- if (zl == NULL)
- {
- uvzlog_line(&ll, zl, priority, format, va, llt_lf) ;
- write(fileno(stderr), ll.line, ll.len) ;
- }
- else
- {
- /* Syslog output */
- if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG])
- {
- va_list ac;
- va_copy(ac, va);
- vsyslog (priority|zlog_default->facility, format, ac);
- va_end(ac);
- }
-
- /* File output. */
- if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp)
- {
- 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, llt_lf) ;
- write(fileno(zl->fp), ll.line, ll.len) ;
- }
-
- /* Terminal monitor. */
- if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR])
- uty_log(&ll, zl, priority, format, va) ;
- }
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size, "%s, in file %s, line %u, function %s",
+ mess, file, line, (function ? function : "?"));
+ zlog_abort(buff);
}
-/*------------------------------------------------------------------------------
- * Preparation of line to send to logging: file, stdout or "monitor" terminals.
- *
- * Takes copy of va_list before using it, so the va_list is unchanged.
- */
-extern void
-uvzlog_line(struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va, enum ll_term term)
+/* Abort with message and errno and strerror() thereof */
+void
+_zlog_abort_errno (const char *mess, const char *file,
+ unsigned int line, const char *function)
{
- 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 (term == ll->term)
- return ; /* exit here if all set */
- }
- else
- {
- /* must construct the line */
- qf_str_t qfs ;
- va_list vac ;
-
- 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) ;
-
- qfs_append_n(&qfs, " ", 1) ;
-
- /* "<priority>: " if required */
- if ((zl != NULL) && (zl->record_priority))
- {
- qfs_append(&qfs, zlog_priority[priority]) ;
- qfs_append(&qfs, ": ") ;
- } ;
-
- /* "<protocol>: " or "unknown: " */
- if (zl != NULL)
- q = zlog_proto_names[zl->protocol] ;
- else
- q = "unknown" ;
-
- qfs_append(&qfs, q) ;
- qfs_append(&qfs, ": ") ;
-
- /* Now the log line itself */
- va_copy(vac, va);
- qfs_vprintf(&qfs, format, vac) ;
- va_end(vac);
-
- /* Set pointer to where the '\0' is. */
- p = ll->p_nl = qfs_ptr(&qfs) ;
- } ;
-
- /* finish off with '\r''\n''\0' or '\n''\0' as required */
- if (term == llt_crlf)
- *p++ = '\r' ;
-
- if (term != llt_nul)
- *p++ = '\n' ;
-
- *p = '\0' ;
+ _zlog_abort_err(mess, errno, file, line, function);
+}
- ll->len = p - ll->line ;
- ll->term = term ;
-} ;
+/* Abort with message and given error and strerror() thereof */
+void
+_zlog_abort_err (const char *mess, int err, const char *file,
+ unsigned int line, const char *function)
+{
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size,
+ "%s, in file %s, line %u, function %s, %s",
+ mess, file, line, (function ? function : "?"),
+ errtoa(err, 0).str);
+ zlog_abort(buff);
+}
/*============================================================================*/
@@ -445,18 +711,18 @@ open_crashlog(void)
#undef CRASHLOG_PREFIX
}
-/* Note: the goal here is to use only async-signal-safe functions. */
-void
-zlog_signal(int signo, const char *action
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *program_counter
-#endif
- )
+/*------------------------------------------------------------------------------
+ * Note: the goal here is to use only async-signal-safe functions.
+ */
+extern void
+zlog_signal(int signo, const char *action, siginfo_t *siginfo,
+ void *program_counter)
{
time_t now;
char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...")+100];
char *s = buf;
char *msgstart = buf;
+ int file_fd ;
#define LOC s,buf+sizeof(buf)-s
time(&now);
@@ -490,10 +756,16 @@ zlog_signal(int signo, const char *action
/* N.B. implicit priority is most severe */
#define PRI LOG_CRIT
-#define DUMP(FD) write(FD, buf, s-buf);
+#define DUMP(FD) write(FD, buf, s - buf);
+
/* If no file logging configured, try to write to fallback log file. */
- if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0))
- DUMP(logfile_fd)
+ file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ;
+ if (file_fd < 0)
+ file_fd = open_crashlog() ;
+
+ if (file_fd >= 0)
+ DUMP(file_fd)
+
if (!zlog_default)
DUMP(STDERR_FILENO)
else
@@ -503,7 +775,7 @@ zlog_signal(int signo, const char *action
/* Remove trailing '\n' for monitor and syslog */
*--s = '\0';
if (PRI <= zlog_default->maxlvl[ZLOG_DEST_MONITOR])
- vty_log_fixed(buf,s-buf);
+ vty_log_fixed(buf, s - buf);
if (PRI <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG])
syslog_sigsafe(PRI|zlog_default->facility,msgstart,s-msgstart);
}
@@ -538,6 +810,7 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
int size;
char buf[100];
char *s, **bt = NULL;
+ int file_fd ;
#define LOC s,buf+sizeof(buf)-s
#ifdef HAVE_GLIBC_BACKTRACE
@@ -568,8 +841,13 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
s = num_append(LOC,size);
s = str_append(LOC," stack frames:\n");
- if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0))
- DUMP(logfile_fd)
+ file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ;
+ if (file_fd < 0)
+ file_fd = open_crashlog() ;
+
+ if (file_fd >= 0)
+ DUMP(file_fd)
+
if (!zlog_default)
DUMP(STDERR_FILENO)
else
@@ -617,16 +895,16 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
void
zlog_backtrace(int priority)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
uzlog_backtrace(priority);
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
static void
uzlog_backtrace(int priority)
{
#ifndef HAVE_GLIBC_BACKTRACE
- uzlog(NULL, priority, "No backtrace available on this platform.");
+ zlog(NULL, priority, "No backtrace available on this platform.");
#else
void *array[20];
int size, i;
@@ -635,161 +913,79 @@ uzlog_backtrace(int priority)
if (((size = backtrace(array,sizeof(array)/sizeof(array[0]))) <= 0) ||
((size_t)size > sizeof(array)/sizeof(array[0])))
{
- uzlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d "
+ zlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d "
"(valid range is between 1 and %lu)",
size, (unsigned long)(sizeof(array)/sizeof(array[0])));
return;
}
- uzlog(NULL, priority, "Backtrace for %d stack frames:", size);
+ zlog(NULL, priority, "Backtrace for %d stack frames:", size);
if (!(strings = backtrace_symbols(array, size)))
{
- uzlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)");
+ zlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)");
for (i = 0; i < size; i++)
- uzlog(NULL, priority, "[bt %d] %p",i,array[i]);
+ zlog(NULL, priority, "[bt %d] %p",i,array[i]);
}
else
{
for (i = 0; i < size; i++)
- uzlog(NULL, priority, "[bt %d] %s",i,strings[i]);
+ zlog(NULL, priority, "[bt %d] %s",i,strings[i]);
free(strings);
}
#endif /* HAVE_GLIBC_BACKTRACE */
}
-/* unlocked version */
-void
-uzlog (struct zlog *zl, int priority, const char *format, ...)
-{
- va_list args;
-
- va_start(args, format);
- uvzlog (zl, priority, format, args);
- va_end (args);
-}
-
-void
-zlog (struct zlog *zl, int priority, const char *format, ...)
-{
- va_list args;
-
- va_start(args, format);
- vzlog (zl, priority, format, args);
- va_end (args);
-}
-
-#define ZLOG_FUNC(FUNCNAME,PRIORITY) \
-void \
-FUNCNAME(const char *format, ...) \
-{ \
- va_list args; \
- va_start(args, format); \
- vzlog (NULL, PRIORITY, format, args); \
- va_end(args); \
-}
-
-ZLOG_FUNC(zlog_err, LOG_ERR)
-
-ZLOG_FUNC(zlog_warn, LOG_WARNING)
-
-ZLOG_FUNC(zlog_info, LOG_INFO)
-
-ZLOG_FUNC(zlog_notice, LOG_NOTICE)
-
-ZLOG_FUNC(zlog_debug, LOG_DEBUG)
-
-#undef ZLOG_FUNC
-
-#define PLOG_FUNC(FUNCNAME,PRIORITY) \
-void \
-FUNCNAME(struct zlog *zl, const char *format, ...) \
-{ \
- va_list args; \
- va_start(args, format); \
- vzlog (zl, PRIORITY, format, args); \
- va_end(args); \
-}
-
-PLOG_FUNC(plog_err, LOG_ERR)
-
-PLOG_FUNC(plog_warn, LOG_WARNING)
-
-PLOG_FUNC(plog_info, LOG_INFO)
-
-PLOG_FUNC(plog_notice, LOG_NOTICE)
-
-PLOG_FUNC(plog_debug, LOG_DEBUG)
-
-#undef PLOG_FUNC
-
-void
-_zlog_assert_failed (const char *assertion, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size,
- "Assertion `%s' failed in file %s, line %u, function %s",
- assertion, file, line, (function ? function : "?"));
- zlog_abort(buff);
-}
-
-/* Abort with message */
-void
-_zlog_abort_mess (const char *mess, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size, "%s, in file %s, line %u, function %s",
- mess, file, line, (function ? function : "?"));
- zlog_abort(buff);
-}
-
-/* Abort with message and errno and strerror() thereof */
-void
-_zlog_abort_errno (const char *mess, const char *file,
- unsigned int line, const char *function)
-{
- _zlog_abort_err(mess, errno, file, line, function);
-}
-
-/* Abort with message and given error and strerror() thereof */
-void
-_zlog_abort_err (const char *mess, int err, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size,
- "%s, in file %s, line %u, function %s, %s",
- mess, file, line, (function ? function : "?"),
- errtoa(err, 0).str);
- zlog_abort(buff);
-}
+static void uzlog_set_effective_level(struct zlog* zl) ;
static void
zlog_abort (const char *mess)
{
-#if VTY_DEBUG
+#if LOG_DEBUG
/* May not be locked -- but that doesn't matter any more */
- ++vty_lock_count ;
+ ++log_lock_count ;
#endif
- /* Force fallback file logging? */
- if (zlog_default && !zlog_default->fp &&
- ((logfile_fd = open_crashlog()) >= 0) &&
- ((zlog_default->fp = fdopen(logfile_fd, "w")) != NULL))
- zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR;
+ if (zlog_default != NULL)
+ {
+ if (zlog_default->file_fd < 0)
+ zlog_default->file_fd = open_crashlog() ;
- uzlog(NULL, LOG_CRIT, "%s", mess);
+ if (zlog_default->file_fd >= 0)
+ {
+ zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR;
+ uzlog_set_effective_level(zlog_default) ;
+ } ;
+ } ;
+
+ zlog(NULL, LOG_CRIT, "%s", mess);
uzlog_backtrace(LOG_CRIT);
zabort_abort();
}
+/*==============================================================================
+ * Opening, closing, setting log levels etc.
+ *
+ */
+static int uzlog_set_file(struct zlog *zl, const char *filename, int level) ;
+static int uzlog_reset_file(struct zlog *zl) ;
+static void uzlog_set_effective_level(struct zlog* zl) ;
-/* Open log stream */
-struct zlog *
+/*------------------------------------------------------------------------------
+ * Get the effective zlog stream.
+ */
+static inline struct zlog* zlog_actual(struct zlog* zl)
+{
+ return zl != NULL ? zl : zlog_default ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open logging -- create and initialise a struct zlog object.
+ *
+ * Opens a connection to syslog.
+ *
+ * This must be done very early in the morning, before any pthreading starts.
+ */
+extern struct zlog *
openzlog (const char *progname, zlog_proto_t protocol,
int syslog_flags, int syslog_facility)
{
@@ -798,441 +994,552 @@ openzlog (const char *progname, zlog_proto_t protocol,
zl = XCALLOC(MTYPE_ZLOG, sizeof (struct zlog));
- zl->ident = progname;
- zl->protocol = protocol;
- zl->facility = syslog_facility;
+ zl->ident = progname;
+ zl->protocol = protocol;
+ zl->facility = syslog_facility;
zl->syslog_options = syslog_flags;
- /* Set default logging levels. */
- for (i = 0; i < sizeof(zl->maxlvl)/sizeof(zl->maxlvl[0]); i++)
- zl->maxlvl[i] = ZLOG_DISABLED;
- zl->maxlvl[ZLOG_DEST_MONITOR] = LOG_DEBUG;
- zl->default_lvl = LOG_DEBUG;
+ /* Set initial logging levels. */
+ for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i)
+ zl->maxlvl[i] = ZLOG_DISABLED ;
+
+ zl->monitor_lvl = LOG_DEBUG ;
+ zl->default_lvl = LOG_DEBUG ;
openlog (progname, syslog_flags, zl->facility);
+ zl->syslog = true ; /* have syslog */
+ zl->stdout_fd = fileno(stdout) ; /* assume have stdout */
+ zl->file_fd = -1 ; /* no file, yet */
+
+ assert(zlog_list == NULL) ; /* can do this once ! */
+ zlog_list = zl ;
+
+ uzlog_set_effective_level(zl) ;
+
return zl;
-}
+} ;
-void
+/*------------------------------------------------------------------------------
+ * Close logging -- destroy struct zlog object.
+ *
+ * Closes connection to syslog and any log file.
+ */
+extern void
closezlog (struct zlog *zl)
{
- closelog();
+ assert((zl == zlog_list) && (zl->next == NULL)) ; /* pro tem */
- if (zl->fp != NULL)
- fclose (zl->fp);
+ LOG_LOCK() ;
+
+ closelog();
+ if (zl->file_fd >= 0)
+ close (zl->file_fd) ;
if (zl->filename != NULL)
- free (zl->filename);
+ free (zl->filename) ;
+
+ zl->syslog = false ;
+ zl->file_fd = -1 ;
+ zl->stdout_fd = -1 ;
+
+ uzlog_set_effective_level(zl) ;
XFREE (MTYPE_ZLOG, zl);
+
+ LOG_UNLOCK() ;
}
-/* Called from command.c. */
-void
-zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level)
+/*------------------------------------------------------------------------------
+ * Set new logging level for the given destination.
+ *
+ * If the log_level is ZLOG_DISABLED, then the destination is, effectively,
+ * disabled.
+ *
+ * Note that for file logging need to use zlog_set_file() to set a file in
+ * the first place and zlog_reset_file() to actually close a file destination.
+ *
+ * Update the effective maxlvl for this zlog, and the max_maxlvl for all zlog.
+ *
+ * Note that for monitor the sets the separate zl->monitor_lvl. The entry
+ * in the zl->maxlvl[] vector is the maximum of all *active* monitors, not
+ * the current configured level.
+ */
+extern void
+zlog_set_level (struct zlog *zl, zlog_dest_t dest, int level)
{
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- zl->maxlvl[dest] = log_level;
+ if (dest != ZLOG_DEST_MONITOR)
+ {
+ zl->maxlvl[dest] = level ;
+ uzlog_set_effective_level(zl) ;
+ }
+ else
+ zl->monitor_lvl = level ;
}
- VTY_UNLOCK() ;
-}
+ LOG_UNLOCK() ;
+} ;
-int
-zlog_set_file (struct zlog *zl, const char *filename, int log_level)
+/*------------------------------------------------------------------------------
+ * Set new log file: name and level.
+ *
+ * Note that this closes any existing file
+ *
+ * Returns: 0 => OK
+ * errno otherwise.
+ */
+extern int
+zlog_set_file(struct zlog *zl, const char *filename, int level)
{
- FILE *fp;
- mode_t oldumask;
- int result = 1;
+ int err ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- /* There is opend file. */
- uzlog_reset_file (zl);
+ err = uzlog_set_file(zl, filename, level) ;
- /* Set default zl. */
- if (zl == NULL)
- zl = zlog_default;
+ LOG_UNLOCK() ;
+ return err ;
+}
- if (zl != NULL)
+static int
+uzlog_set_file(struct zlog *zl, const char *filename, int level)
+{
+ int err ;
+
+ LOG_ASSERT_LOCKED() ;
+
+ err = 0 ;
+ if ((zl = zlog_actual(zl)) != NULL)
{
- /* Open file. */
+ mode_t oldumask;
+ int fd ;
+
+ /* Close any existing file */
+ uzlog_reset_file(zl);
+
+ /* Open file making damn sure we get the mode we want ! */
oldumask = umask (0777 & ~LOGFILE_MASK);
- fp = fopen (filename, "a");
- umask(oldumask);
- if (fp == NULL)
- result = 0;
+ fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, LOGFILE_MASK);
+ if (fd < 0)
+ err = errno ;
else
{
- /* Set flags. */
- zl->filename = strdup (filename);
- zl->maxlvl[ZLOG_DEST_FILE] = log_level;
- zl->fp = fp;
- logfile_fd = fileno(fp);
- }
- }
+ /* Set flags. */
+ zl->filename = strdup (filename) ;
+ zl->file_fd = fd ;
+ zl->maxlvl[ZLOG_DEST_FILE] = level;
+
+ uzlog_set_effective_level(zl) ;
+ } ;
- VTY_UNLOCK() ;
- return result;
+ umask(oldumask);
+ } ;
+
+ return err ;
}
-/* Reset opend file. */
-int
-zlog_reset_file (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Close any existing log file. Set level to ZLOG_DISABLED.
+ *
+ * Note that this closes any existing file
+ *
+ * Returns: 1
+ */
+extern int
+zlog_reset_file(struct zlog *zl)
{
- int result;
- VTY_LOCK() ;
- result = uzlog_reset_file(zl);
- VTY_UNLOCK() ;
- return result;
+ int ret ;
+ LOG_LOCK() ;
+
+ ret = uzlog_reset_file(zl) ;
+
+ LOG_UNLOCK() ;
+ return ret ;
}
static int
-uzlog_reset_file (struct zlog *zl)
- {
- if (zl == NULL)
- zl = zlog_default;
+uzlog_reset_file(struct zlog *zl)
+{
+ LOG_ASSERT_LOCKED() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- if (zl->fp)
- fclose (zl->fp);
- zl->fp = NULL;
- logfile_fd = -1;
+ if (zl->file_fd >= 0)
+ close (zl->file_fd) ;
+
+ zl->file_fd = -1 ;
zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED;
- if (zl->filename)
- free (zl->filename);
- zl->filename = NULL;
- }
+ if (zl->filename != NULL)
+ free (zl->filename) ;
+
+ zl->filename = NULL ;
+
+ uzlog_set_effective_level(zl) ;
+ } ;
return 1;
}
-/* Reopen log file. */
-int
+/*------------------------------------------------------------------------------
+ * Close and reopen log file -- TODO and the point ??
+ */
+extern int
zlog_rotate (struct zlog *zl)
{
- int level;
- int result = 1;
+ int err ;
+ char* filename ;
- VTY_LOCK() ;
+ filename = NULL ;
+ err = 0 ;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- if (zl->fp)
- fclose (zl->fp);
- zl->fp = NULL;
- logfile_fd = -1;
- level = zl->maxlvl[ZLOG_DEST_FILE];
- zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED;
-
- if (zl->filename)
+ if (zl->file_fd >= 0)
{
- mode_t oldumask;
- int save_errno;
-
- oldumask = umask (0777 & ~LOGFILE_MASK);
- zl->fp = fopen (zl->filename, "a");
- save_errno = errno;
- umask(oldumask);
- if (zl->fp == NULL)
- {
- /* can't call logging while locked */
- char *fname = strdup(zl->filename);
- uzlog(NULL, LOG_ERR,
- "Log rotate failed: cannot open file %s for append: %s",
- fname, errtoa(save_errno, 0).str);
- free(fname);
- result = -1;
- }
- else
- {
- logfile_fd = fileno(zl->fp);
- zl->maxlvl[ZLOG_DEST_FILE] = level;
- }
- }
- }
- VTY_UNLOCK() ;
- return result;
+ filename = zl->filename ;
+ zl->filename = NULL ; /* co-opt the name */
+
+ err = uzlog_set_file(zl, filename, zl->maxlvl[ZLOG_DEST_FILE]) ;
+ } ;
+ } ;
+
+ LOG_UNLOCK() ;
+
+ if (err != 0)
+ zlog(NULL, LOG_ERR,
+ "Log rotate failed: cannot open file %s for append: %s",
+ filename, errtoa(err, 0).str) ;
+ if (filename != NULL)
+ free(filename) ; /* discard old copy */
+
+ return (err == 0) ? 1 : -1 ;
}
-int
-zlog_get_default_lvl (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Set the current maximum monitor level and the effective level (the same in
+ * this case).
+ */
+extern void
+uzlog_set_monitor(struct zlog *zl, int level)
{
- int result = LOG_DEBUG;
+ if ((zl = zlog_actual(zl)) != NULL)
+ {
+ zl->maxlvl[ZLOG_DEST_MONITOR] = level ;
+ uzlog_set_effective_level(zl) ;
+ } ;
+} ;
- VTY_LOCK() ;
+/*------------------------------------------------------------------------------
+ * get the current default level
+ */
+extern int
+zlog_get_default_lvl (struct zlog *zl)
+{
+ int level ;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- result = zl->default_lvl;
- }
+ zl = zlog_actual(zl) ;
+ level = (zl != NULL) ? zl->default_lvl : LOG_DEBUG ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return level ;
}
-void
+/*------------------------------------------------------------------------------
+ * set the default level
+ */
+extern void
zlog_set_default_lvl (struct zlog *zl, int level)
{
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- zl->default_lvl = level;
- }
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->default_lvl = level;
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-/* Set logging level and default for all destinations */
-void
+/*------------------------------------------------------------------------------
+ * Set default logging level and the level for any destination not ZLOG_DISABLED
+ *
+ * Note that on openzlog(), all destinations are set ZLOG_DISABLED.
+ */
+extern void
zlog_set_default_lvl_dest (struct zlog *zl, int level)
{
- int i;
+ LOG_LOCK() ;
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
+ int i;
+
zl->default_lvl = level;
- for (i = 0; i < ZLOG_NUM_DESTS; i++)
+ for (i = 0; i < ZLOG_DEST_COUNT; i++)
if (zl->maxlvl[i] != ZLOG_DISABLED)
- zl->maxlvl[i] = level;
- }
+ zl->maxlvl[i] = level ;
- VTY_UNLOCK() ;
-}
+ uzlog_set_effective_level(zl) ;
+ } ;
-int
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the current level for the given destination.
+ *
+ * Note that for monitor the gets the separate zl->monitor_lvl. The entry
+ * in the zl->maxlvl[] vector is the maximum of all *active* monitors, not
+ * the current configured level.
+ */
+extern int
zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest)
{
- int result = ZLOG_DISABLED;
+ int level ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
+ zl = zlog_actual(zl) ;
if (zl == NULL)
- zl = zlog_default;
+ level = ZLOG_DISABLED ;
+ else
+ level = (dest != ZLOG_DEST_MONITOR) ? zl->maxlvl[dest] : zl->monitor_lvl ;
- if (zl != NULL)
- {
- result = zl->maxlvl[dest];
- }
+ LOG_UNLOCK() ;
+ return level;
+} ;
- VTY_UNLOCK() ;
- return result;
-}
+/*------------------------------------------------------------------------------
+ * Get the current monitor level
+ */
+extern int
+uzlog_get_monitor_lvl(struct zlog *zl)
+{
+ LOG_ASSERT_LOCKED() ;
-int
+ zl = zlog_actual(zl) ;
+ return (zl == NULL) ? ZLOG_DISABLED : zl->monitor_lvl ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the current facility setting for syslog
+ */
+extern int
zlog_get_facility (struct zlog *zl)
{
- int result = LOG_DAEMON;
-
- VTY_LOCK() ;
+ int facility ;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- result = zl->facility;
- }
+ zl = zlog_actual(zl) ;
+ facility = (zl != NULL) ? zl->facility : LOG_DAEMON ;
- VTY_UNLOCK() ;
- return result;
-}
+ LOG_UNLOCK() ;
+ return facility ;
+} ;
-void
+/*------------------------------------------------------------------------------
+ * Set the current facility setting for syslog
+ */
+extern void
zlog_set_facility (struct zlog *zl, int facility)
{
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- zl->facility = facility;
- }
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->facility = facility ;
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-int
+/*------------------------------------------------------------------------------
+ * Get the record priority setting
+ */
+extern bool
zlog_get_record_priority (struct zlog *zl)
{
- int result = 0;
+ bool priority ;
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- result = zl->record_priority;
- }
+ zl = zlog_actual(zl) ;
+ priority = (zl != NULL) ? zl->record_priority : false ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return priority ;
}
-void
-zlog_set_record_priority (struct zlog *zl, int record_priority)
+/*------------------------------------------------------------------------------
+ * Set the record priority setting
+ */
+extern void
+zlog_set_record_priority (struct zlog *zl, bool record_priority)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->record_priority = record_priority ;
- if (zl != NULL)
- {
- zl->record_priority = record_priority;
- }
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-int
+/*------------------------------------------------------------------------------
+ * Get the timestamp precision setting
+ */
+extern int
zlog_get_timestamp_precision (struct zlog *zl)
{
- int result = 0;
+ int precision = 0;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ zl = zlog_actual(zl) ;
+ precision = (zl != NULL) ? zl->timestamp_precision : 0 ;
- if (zl != NULL)
- {
- result = zl->timestamp_precision;
- }
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return precision ;
}
-void
+/*------------------------------------------------------------------------------
+ * Get the timestamp precision setting
+ */
+extern void
zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- zl->timestamp_precision = timestamp_precision;
- }
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->timestamp_precision = timestamp_precision;
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-/* returns name of ZLOG_NONE if no zlog given and no default set */
-const char *
+/*------------------------------------------------------------------------------
+ * Get name of the protocol -- name of ZLOG_NONE if no zlog set up yet.
+ */
+extern const char *
zlog_get_proto_name (struct zlog *zl)
{
- const char * result;
- VTY_LOCK() ;
- result = uzlog_get_proto_name(zl);
- VTY_UNLOCK() ;
- return result;
+ const char* name ;
+ LOG_LOCK() ;
+
+ zl = zlog_actual(zl) ;
+ name = zlog_proto_names[(zl != NULL) ? zl->protocol : ZLOG_NONE] ;
+
+ LOG_UNLOCK() ;
+ return name ;
}
-/* unprotected version, assumes mutex held */
-const char *
-uzlog_get_proto_name (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * caller must free result
+ */
+extern char *
+zlog_get_filename (struct zlog *zl)
{
- zlog_proto_t protocol = ZLOG_NONE;
+ char* name = NULL;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- protocol = zl->protocol;
- }
+ LOG_LOCK() ;
- return zlog_proto_names[protocol];
+ zl = zlog_actual(zl) ;
+ name = ((zl != NULL) && (zl->filename != NULL)) ? strdup(zl->filename)
+ : NULL ;
+ LOG_UNLOCK() ;
+ return name ;
}
-/* caller must free result */
-char *
-zlog_get_filename (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Get address of ident string
+ */
+extern const char *
+zlog_get_ident (struct zlog *zl)
{
- char * result = NULL;
+ const char* ident ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ zl = zlog_actual(zl) ;
+ ident = (zl != NULL) ? zl->ident : NULL ;
- if (zl != NULL && zl->filename != NULL)
- {
- result = strdup(zl->filename);
- }
-
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return ident ;
}
-const char *
-zlog_get_ident (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * logging to a file?
+ */
+extern bool
+zlog_is_file (struct zlog *zl)
{
- const char * result = NULL;
+ bool is_file ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- result = zl->ident;
- }
+ zl = zlog_actual(zl) ;
+ is_file = (zl != NULL) ? (zl->file_fd >= 0) : false ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;;
+ return is_file ;
}
-/* logging to a file? */
-int
-zlog_is_file (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Setting of emaxlvl for given destination.
+ */
+inline static void
+uzlog_set_emaxlvl(struct zlog *zl, zlog_dest_t dest, bool test)
{
- int result = 0;
+ zl->emaxlvl[dest] = test ? zl->maxlvl[dest] : ZLOG_DISABLED ;
+};
- VTY_LOCK() ;
+/*------------------------------------------------------------------------------
+ * Update effective logging level for the given destination, and max_maxlvl.
+ *
+ * The effective logging level takes into account whether the destination is
+ * enabled or not.
+ */
+static void
+uzlog_set_effective_level(struct zlog *zl)
+{
+ int emaxlvl ;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_ASSERT_LOCKED() ;
- if (zl != NULL)
+ /* Re-establish the emaxlvl for this logging stream. */
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_SYSLOG, zl->syslog ) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_FILE, zl->file_fd >= 0) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_STDOUT, zl->stdout_fd >= 0) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_MONITOR, true) ;
+ confirm(ZLOG_DEST_COUNT == 4) ;
+
+ /* Scan all known logging streams, and re-establish max_maxlvl.
+ *
+ * Do not expect there to be many of these, or that we will change them very
+ * often -- so nothing clever, here.
+ */
+ emaxlvl = ZLOG_DISABLED ;
+ zl = zlog_list ;
+ while (zl != NULL)
{
- result = (zl->fp != NULL);
- }
+ uint i ;
+ for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i)
+ if (emaxlvl < zl->emaxlvl[i])
+ emaxlvl = zl->emaxlvl[i] ;
+ zl = zl->next ;
+ } ;
- VTY_UNLOCK() ;;
- return result;
-}
+ max_maxlvl = emaxlvl ;
+} ;
+
+/*==============================================================================
+ *
+ */
/* Message lookup function. */
const char *
diff --git a/lib/log.h b/lib/log.h
index dda773ea..5fb2f163 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -25,7 +25,12 @@
#ifndef _ZEBRA_LOG_H
#define _ZEBRA_LOG_H
+#include "misc.h"
+#include <signal.h>
#include <syslog.h>
+#include <stdio.h>
+#include "log_common.h"
+#include "vargs.h"
#include "pthread_safe.h"
/* Here is some guidance on logging levels to use:
@@ -44,135 +49,59 @@
* please use LOG_ERR instead.
*/
-typedef enum
-{
- ZLOG_NONE,
- ZLOG_DEFAULT,
- ZLOG_ZEBRA,
- ZLOG_RIP,
- ZLOG_BGP,
- ZLOG_OSPF,
- ZLOG_RIPNG,
- ZLOG_OSPF6,
- ZLOG_ISIS,
- ZLOG_MASC
-} zlog_proto_t;
-
-/* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
- to that logging destination. */
-#define ZLOG_DISABLED (LOG_EMERG-1)
-
-typedef enum
-{
- ZLOG_DEST_SYSLOG = 0,
- ZLOG_DEST_STDOUT,
- ZLOG_DEST_MONITOR,
- ZLOG_DEST_FILE
-} zlog_dest_t;
-#define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1)
+/*------------------------------------------------------------------------------
+ * Pointer to default logging structure.
+ *
+ * Each daemon initialises this very early in the morning (before processing
+ * command line arguments) by:
+ *
+ * zlog_default = openzlog(....) ;
+ */
+extern struct zlog* zlog_default;
-struct zlog
-{
- const char *ident; /* daemon name (first arg to openlog) */
- zlog_proto_t protocol;
- int maxlvl[ZLOG_NUM_DESTS]; /* maximum priority to send to associated
- logging destination */
- int default_lvl; /* maxlvl to use if none is specified */
- FILE *fp;
- char *filename;
- int facility; /* as per syslog facility */
- int record_priority; /* should messages logged through stdio include the
- priority of the message? */
- int syslog_options; /* 2nd arg to openlog */
- int timestamp_precision; /* # of digits of subsecond precision */
-};
+/*------------------------------------------------------------------------------
+ * zlog opening etc.
+ */
+extern struct zlog* openzlog (const char *progname, zlog_proto_t protocol,
+ int syslog_options, int syslog_facility);
+extern void closezlog (struct zlog *zl);
+extern void zlog_set_level(struct zlog *zl, zlog_dest_t, int log_level);
+extern int zlog_rotate (struct zlog *);
-/* Message structure. */
-struct message
+/*------------------------------------------------------------------------------
+ * Functions and macros for logging.
+ */
+extern void zlog (struct zlog *zl, int priority, const char *format, ...)
+ PRINTF_ATTRIBUTE(3, 4);
+/* Handy zlog functions.
+ */
+#define zlog_thus(thus, ...) zlog(zlog_default, thus, __VA_ARGS__)
+#define zlog_err(...) zlog(zlog_default, LOG_ERR, __VA_ARGS__)
+#define zlog_warn(...) zlog(zlog_default, LOG_WARNING, __VA_ARGS__)
+#define zlog_info(...) zlog(zlog_default, LOG_INFO, __VA_ARGS__)
+#define zlog_notice(...) zlog(zlog_default, LOG_NOTICE, __VA_ARGS__)
+#define zlog_debug(...) zlog(zlog_default, LOG_DEBUG, __VA_ARGS__)
+
+/* For bgpd's peer oriented log.
+ */
+#define plog_thus(zl, thus, ...) zlog(zl, thus, __VA_ARGS__)
+#define plog_err(zl, ...) zlog(zl, LOG_ERR, __VA_ARGS__)
+#define plog_warn(zl, ...) zlog(zl, LOG_WARNING, __VA_ARGS__)
+#define plog_info(zl, ...) zlog(zl, LOG_INFO, __VA_ARGS__)
+#define plog_notice(zl, ...) zlog(zl, LOG_NOTICE, __VA_ARGS__)
+#define plog_debug(zl, ...) zlog(zl, LOG_DEBUG, __VA_ARGS__)
+
+/*------------------------------------------------------------------------------
+ * Message and other lookups.
+ */
+struct message /* For message lookup. */
{
int key;
const char *str;
};
-/* module initialization */
-extern void zlog_init_r(void);
-extern void zlog_destroy_r(void);
-
-/* Default logging structure. */
-extern struct zlog *zlog_default;
-
-/* Open zlog function */
-extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol,
- int syslog_options, int syslog_facility);
-
-/* Close zlog function. */
-extern void closezlog (struct zlog *zl);
-
-/* 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__ */
-
-/* Generic function for zlog. */
-extern void zlog (struct zlog *zl, int priority, const char *format, ...)
- PRINTF_ATTRIBUTE(3, 4);
-/* assumed locked version for close friends */
-extern void uzlog (struct zlog *zl, int priority, const char *format, ...)
- PRINTF_ATTRIBUTE(3, 4);
-
-/* Handy zlog functions. */
-extern void zlog_err (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_warn (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_info (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_notice (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_debug (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-
-/* For bgpd's peer oriented log. */
-extern void plog_err (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_warn (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_info (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_notice (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_debug (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-
-/* Set logging level for the given destination. If the log_level
- argument is ZLOG_DISABLED, then the destination is disabled.
- This function should not be used for file logging (use zlog_set_file
- or zlog_reset_file instead). */
-extern void zlog_set_level (struct zlog *zl, zlog_dest_t, int log_level);
-
-/* Set logging to the given filename at the specified level. */
-extern int zlog_set_file (struct zlog *zl, const char *filename, int log_level);
-/* Disable file logging. */
-extern int zlog_reset_file (struct zlog *zl);
-
-/* Rotate log. */
-extern int zlog_rotate (struct zlog *);
-
-/* getters & setters */
-extern int zlog_get_default_lvl (struct zlog *zl);
-extern void zlog_set_default_lvl (struct zlog *zl, int level);
-extern void zlog_set_default_lvl_dest (struct zlog *zl, int level);
-extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest);
-extern int zlog_get_facility (struct zlog *zl);
-extern void zlog_set_facility (struct zlog *zl, int facility);
-extern int zlog_get_record_priority (struct zlog *zl);
-extern void zlog_set_record_priority (struct zlog *zl, int record_priority);
-extern int zlog_get_timestamp_precision (struct zlog *zl);
-extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision);
-extern const char * zlog_get_ident (struct zlog *zl);
-extern char * zlog_get_filename (struct zlog *zl);
-extern int zlog_is_file (struct zlog *zl);
extern const char * zlog_get_proto_name (struct zlog *zl);
-extern const char * uzlog_get_proto_name (struct zlog *zl);
-/* For hackey massage lookup and check */
#define LOOKUP(x, y) mes_lookup(x, x ## _max, y, "(no item found)")
extern const char *lookup (const struct message *, int);
@@ -183,15 +112,15 @@ extern const char *mes_lookup (const struct message *meslist,
extern const char *zlog_priority[];
extern const char *zlog_proto_names[];
-/* Safe version of strerror -- never returns NULL. */
extern const char *safe_strerror(int errnum);
+/*------------------------------------------------------------------------------
+ *
+ */
+
/* To be called when a fatal signal is caught. */
-extern void zlog_signal(int signo, const char *action
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *program_counter
-#endif
- );
+extern void zlog_signal(int signo, const char *action, siginfo_t *siginfo,
+ void *program_counter) ;
/* Ring down the curtain -- turn of SIGABRT handler and abort() */
extern void zabort_abort(void) __attribute__ ((noreturn)) ;
@@ -205,65 +134,9 @@ extern void zlog_backtrace(int priority);
that is logged in addition to the current backtrace. */
extern void zlog_backtrace_sigsafe(int priority, void *program_counter);
-/* Puts a current timestamp in buf and returns the number of characters
- * written (not including the terminating NUL). The purpose of
- * this function is to avoid calls to localtime appearing all over the code.
- * It caches the most recent localtime result and can therefore
- * avoid multiple calls within the same second.
- *
- * The buflen MUST be > 1 and the buffer address MUST NOT be NULL.
- *
- * If buflen is too small, writes buflen-1 characters followed by '\0'.
- *
- * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S
- *
- * This has a fixed length (leading zeros are included) of 19 characters
- * (unless this code is still in use beyond the year 9999 !)
- *
- * Which may be followed by "." and a number of decimal digits, usually 1..6.
- *
- * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and
- * rounding up for good measure -- buffer size = 32.
- */
-#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S"
-
-enum { timestamp_buffer_len = 32 } ;
-
-extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */,
- char *buf, size_t buflen);
-
-/* Generate line to be logged
- *
- * Structure used to hold line for log output -- so that need be generated
- * just once even if output to multiple destinations.
- *
- * Note that the buffer length is a hard limit (including terminating '\n''\0'
- * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging.
+/*------------------------------------------------------------------------------
+ * Defines for use in command construction
*/
-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 terminator */
-
- enum ll_term term ; /* how line is terminated */
-
- size_t len ; /* length including either '\r''\n' or '\n' */
-
- char line[logline_buffer_len]; /* buffer */
-} ;
-
-extern void
-uvzlog_line(struct logline* ll, struct zlog *zl, int priority,
- 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)"
@@ -277,26 +150,27 @@ uvzlog_line(struct logline* ll, struct zlog *zl, int priority,
"Informational messages\n" \
"Debugging messages\n"
-#define LOG_FACILITIES "(kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7)"
+#define LOG_FACILITIES "(kern|user|mail|daemon|auth|syslog|lpr|news|uucp|" \
+ "cron|local0|local1|local2|local3|local4|local5|local6|local7)"
#define LOG_FACILITY_DESC \
- "Kernel\n" \
- "User process\n" \
- "Mail system\n" \
- "System daemons\n" \
- "Authorization system\n" \
- "Syslog itself\n" \
- "Line printer system\n" \
- "USENET news\n" \
- "Unix-to-Unix copy system\n" \
- "Cron/at facility\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n"
+ "Kernel\n" \
+ "User process\n" \
+ "Mail system\n" \
+ "System daemons\n" \
+ "Authorization system\n" \
+ "Syslog itself\n" \
+ "Line printer system\n" \
+ "USENET news\n" \
+ "Unix-to-Unix copy system\n" \
+ "Cron/at facility\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n"
#endif /* _ZEBRA_LOG_H */
diff --git a/lib/log_common.h b/lib/log_common.h
new file mode 100644
index 00000000..dc0463c5
--- /dev/null
+++ b/lib/log_common.h
@@ -0,0 +1,79 @@
+/* Logging definitions used in log.h and log_local.h
+ * Copyright (C) 2011 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_LOG_COMMON_H
+#define _ZEBRA_LOG_COMMON_H
+
+#include "misc.h"
+#include <syslog.h>
+
+/*==============================================================================
+ * This is for things which are required in log.h for external use, and in
+ * log_local.h for use within the log/command/vty family.
+ */
+
+/*------------------------------------------------------------------------------
+ * The protocols known to the logging system.
+ */
+typedef enum
+{
+ ZLOG_NONE,
+ ZLOG_DEFAULT,
+ ZLOG_ZEBRA,
+ ZLOG_RIP,
+ ZLOG_BGP,
+ ZLOG_OSPF,
+ ZLOG_RIPNG,
+ ZLOG_OSPF6,
+ ZLOG_ISIS,
+ ZLOG_MASC
+} zlog_proto_t;
+
+/*------------------------------------------------------------------------------
+ * If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
+ * to that logging destination.
+ *
+ * Note that logging levels with higher priority have lower numbers. So, this
+ * may (well) be -ve.
+ */
+enum { ZLOG_DISABLED = LOG_EMERG - 1 } ;
+
+/*------------------------------------------------------------------------------
+ * The logging destinations supported.
+ */
+typedef enum
+{
+ ZLOG_DEST_SYSLOG = 0,
+ ZLOG_DEST_FILE,
+ ZLOG_DEST_STDOUT,
+ ZLOG_DEST_MONITOR,
+
+ ZLOG_DEST_COUNT /* Number of destinations */
+} zlog_dest_t;
+
+/*------------------------------------------------------------------------------
+ *
+ */
+struct zlog ;
+
+enum { timestamp_buffer_len = 32 } ;
+
+#endif /* _ZEBRA_LOG_COMMON_H */
diff --git a/lib/log_local.h b/lib/log_local.h
new file mode 100644
index 00000000..d96f9f8b
--- /dev/null
+++ b/lib/log_local.h
@@ -0,0 +1,168 @@
+/* Logging locking and interface presented to vty et al
+ * Copyright (C) 2011 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_LOG_LOCAL_H
+#define _ZEBRA_LOG_LOCAL_H
+
+#include "misc.h"
+
+#include "log_common.h"
+#include "qpthreads.h"
+
+/*==============================================================================
+ * This is for access to some things in log.c which are not required
+ * by external users, who use log.h.
+ *
+ * This is for use within the log/command/vty family.
+ *
+ * Should not be used with log.h ! (Except in log.c itself.)
+ *
+ * This may duplicate things published in log.h, but also includes things
+ * which are not intended for "external" use.
+ */
+
+/*==============================================================================
+ * To make logging qpthread safe we use a single mutex.
+ *
+ * A normal mutex is used -- recursion is not expected or required.
+ *
+ * There are some "ulog" functions which assume the mutex is locked.
+ *
+ * It is assumed that while the log_mutex is owned, NO OTHER SIGNIFICANT lock
+ * will be acquired -- in particular, NOT the VTY_LOCK() !!
+ */
+
+extern qpt_mutex_t log_mutex ;
+
+/*------------------------------------------------------------------------------
+ * Sort out LOG_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if LOG_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set LOG_DEBUG == 0 to turn off debug
+ * * or set LOG_DEBUG != 0 to turn on debug
+ * * or set LOG_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef LOG_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(LOG_DEBUG)
+# undef LOG_DEBUG
+# define LOG_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define LOG_DEBUG QDEBUG
+#endif
+
+#ifdef LOG_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(LOG_NO_DEBUG)
+# undef LOG_DEBUG
+# define LOG_DEBUG 0
+# endif
+#endif
+
+enum { log_debug = LOG_DEBUG } ;
+
+/*------------------------------------------------------------------------------
+ * Locking and related functions.
+ */
+extern int log_lock_count ;
+
+Inline void
+LOG_LOCK(void) /* if is qpthreads_enabled, lock log_mutex */
+{
+ qpt_mutex_lock(log_mutex) ;
+ if (log_debug)
+ ++log_lock_count ;
+} ;
+
+Inline void
+LOG_UNLOCK(void) /* if is qpthreads_enabled, unlock log_mutex */
+{
+ if (log_debug)
+ --log_lock_count ;
+ qpt_mutex_unlock(log_mutex) ;
+} ;
+
+/* For debug (and documentation) purposes, will LOG_ASSERT_LOCKED where that
+ * is required.
+ *
+ * Note that both these checks will pass if !qpthreads_enabled. So can have
+ * code which is called before qpthreads are started up, or which will never
+ * run qpthreaded !
+ */
+
+extern int log_assert_fail ;
+
+Inline void
+LOG_ASSERT_FAILED(void)
+{
+ if (log_assert_fail == 0) ;
+ {
+ log_assert_fail = 1 ;
+ assert(0) ;
+ } ;
+} ;
+
+Inline void
+LOG_ASSERT_LOCKED(void)
+{
+ if (log_debug)
+ if ((log_lock_count == 0) && (qpthreads_enabled))
+ LOG_ASSERT_FAILED() ;
+} ;
+
+/*==============================================================================
+ * Functions in log.c -- used in any of the log/command/vty
+ */
+extern void log_init_r(void) ;
+extern void log_finish(void);
+
+
+extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */,
+ char *buf, size_t buflen);
+
+extern void uzlog_set_monitor(struct zlog *zl, int level) ;
+extern int uzlog_get_monitor_lvl(struct zlog *zl) ;
+
+extern int zlog_set_file(struct zlog *zl, const char *filename, int log_level);
+extern int zlog_reset_file(struct zlog *zl);
+
+extern int zlog_get_default_lvl (struct zlog *zl);
+extern void zlog_set_default_lvl (struct zlog *zl, int level);
+extern void zlog_set_default_lvl_dest (struct zlog *zl, int level);
+extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest);
+extern int zlog_get_facility (struct zlog *zl);
+extern void zlog_set_facility (struct zlog *zl, int facility);
+extern bool zlog_get_record_priority (struct zlog *zl);
+extern void zlog_set_record_priority (struct zlog *zl, bool record_priority);
+extern int zlog_get_timestamp_precision (struct zlog *zl);
+extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision);
+extern const char * zlog_get_ident (struct zlog *zl);
+extern char * zlog_get_filename (struct zlog *zl);
+extern bool zlog_is_file (struct zlog *zl);
+
+
+#endif /* _ZEBRA_LOG_LOCAL_H */
diff --git a/lib/mem_tracker.c b/lib/mem_tracker.c
deleted file mode 100644
index b468ad2a..00000000
--- a/lib/mem_tracker.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/* Memory Allocation Tracker
- * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
- *
- * This file is part of GNU Zebra.
- *
- * GNU Zebra is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * GNU Zebra is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the Free
- * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
- */
-
-#include "vty.h"
-
-/*==============================================================================
- * Memory Tracker
- */
-typedef struct mem_descriptor* mem_descriptor ;
-struct mem_descriptor
-{
- void* addr ;
- const char* name ;
-
- uint32_t next ; /* MS Type is encoded as MS 4 bits */
- uint32_t size ; /* LS Type is encoded as MS 4 bits */
-} ;
-
-typedef uint32_t md_index ;
-
-enum
-{
- md_next_bits = 28, /* up to 256M allocated objects */
- md_next_mask = (1 << md_next_bits) - 1,
-
- md_index_max = md_next_mask + 1,
-
- md_size_bits = 28, /* up to 256M individual item */
- md_size_mask = (1 << md_size_bits) - 1,
-
- md_size_max = md_size_mask,
-
- md_next_type_bits = 32 - md_next_bits,
- md_next_type_mask = (1 << md_next_type_bits) - 1,
- md_size_type_bits = 32 - md_size_bits,
- md_size_type_mask = (1 << md_size_type_bits) - 1,
-
- md_i_index_bits = 16,
- md_i_index_count = 1 << md_i_index_bits,
- md_i_index_mask = md_i_index_count - 1,
-
- md_page_bits = md_next_bits - md_i_index_bits,
- md_page_count = 1 << md_page_bits,
- md_page_mask = md_page_count - 1,
-} ;
-
-CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ;
-
-static struct mem_type_tracker
-{
- struct mem_tracker mt[MTYPE_MAX] ;
-} mem_type_tracker ;
-
-static mem_descriptor mem_page_table[md_page_count] ;
-
-static mem_descriptor mem_free_descriptors ;
-static md_index mem_next_index ;
-
-static struct mem_tracker mem ;
-
-uint32_t mem_base_count ;
-
-md_index* mem_bases ;
-
-inline static void
-mem_md_set_type(mem_descriptor md, enum MTYPE mtype)
-{
- uint32_t t_ms ;
- uint32_t t_ls ;
-
- t_ms = mtype >> md_size_type_bits ;
- t_ls = mtype ;
-
- t_ms = (t_ms & md_next_type_mask) << md_next_bits ;
- t_ls = (t_ls & md_size_type_mask) << md_size_bits ;
-
- md->next = (md->next & md_next_mask) | t_ms ;
- md->size = (md->size & md_size_mask) | t_ls ;
-} ;
-
-inline static void
-mem_md_set_next(mem_descriptor md, md_index next)
-{
- md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ;
-} ;
-
-inline static void
-mem_md_set_size(mem_descriptor md, size_t size)
-{
- md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ;
-} ;
-
-inline static uint8_t
-mem_md_type(mem_descriptor md)
-{
- return ( (md->next >> (md_next_bits - md_size_type_bits))
- & (md_next_type_mask << md_size_type_bits) )
- | ( (md->size >> md_size_bits) & md_size_type_mask ) ;
-} ;
-
-inline static md_index
-mem_md_next(mem_descriptor md)
-{
- return md->next & md_next_mask ;
-} ;
-
-inline static size_t
-mem_md_size(mem_descriptor md)
-{
- return md->size & md_size_mask ;
-} ;
-
-inline static mem_descriptor
-mem_md_ptr(md_index mdi)
-{
- mem_descriptor page ;
-
- if (mdi == 0)
- return NULL ;
-
- page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ;
- passert(page != NULL) ;
- return page + (mdi & md_i_index_mask) ;
-} ;
-
-static void mem_md_make_bases(void) ;
-
-inline static md_index*
-mem_md_base(void* address)
-{
- if (mem_bases == NULL)
- mem_md_make_bases() ;
-
- return mem_bases + ((uintptr_t)address % mem_base_count) ;
-} ;
-
-static void
-mem_md_make_bases(void)
-{
- md_index* bases_was = mem_bases ;
- uint32_t count_was = mem_base_count ;
-
- mem_base_count += 256 * 1024 ;
- mem_base_count |= 1 ;
- mem_bases = calloc(mem_base_count, sizeof(md_index)) ;
-
- passert(mem_bases != NULL) ;
-
- if (bases_was == NULL)
- passert(count_was == 0) ;
- else
- {
- md_index* base = bases_was ;
- md_index* new_base ;
- md_index this ;
- md_index next ;
- mem_descriptor md ;
-
- while (count_was)
- {
- next = *base++ ;
- while (next != 0)
- {
- this = next ;
- md = mem_md_ptr(this) ;
- next = mem_md_next(md) ;
-
- new_base = mem_md_base(md->addr) ;
- mem_md_set_next(md, *new_base) ;
- *new_base = this ;
- } ;
- --count_was ;
- } ;
-
- free(bases_was) ;
- } ;
-} ;
-
-static void
-mem_md_make_descriptors(void)
-{
- mem_descriptor md ;
- md_index mdi ;
-
- mdi = mem_next_index ;
- passert(mdi < md_index_max) ;
-
- mem_free_descriptors
- = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask]
- = calloc(md_i_index_count, sizeof(struct mem_descriptor)) ;
-
- passert(mem_free_descriptors != NULL) ;
-
- mem_next_index += md_i_index_count ;
-
- if (mdi == 0)
- {
- ++mem_free_descriptors ; /* don't use index == 0 */
- ++mdi ;
- } ;
-
- md = mem_free_descriptors ;
- while (mdi < mem_next_index)
- {
- md->addr = md + 1 ; /* point at next entry */
- md->next = mdi ; /* set to point at self */
- ++md ;
- ++mdi ;
- } ;
- (md-1)->addr = NULL ; /* set end of list */
-} ;
-
-inline static void
-mem_md_malloc(enum MTYPE mtype, void* address, size_t size, const char* name)
-{
- mem_tracker mtt ;
- md_index* base ;
- mem_descriptor md ;
- md_index mdi ;
-
- passert(size <= md_size_max) ;
-
- if (mem_free_descriptors == NULL)
- mem_md_make_descriptors() ;
-
- md = mem_free_descriptors ;
- mem_free_descriptors = md->addr ;
- mdi = md->next ;
-
- if (mem.tracked_count >= (mem_base_count * 4))
- mem_md_make_bases() ;
-
- base = mem_md_base(address) ;
-
- md->addr = address ;
- md->name = name ;
- md->size = size ;
- md->next = *base ;
- mem_md_set_type(md, mtype) ;
-
- *base = mdi ;
-
- ++mem.malloc_count ;
- ++mem.tracked_count ;
-
- mem.tracked_size += size ;
-
- if (mem.tracked_max_count < mem.tracked_count)
- mem.tracked_max_count = mem.tracked_count ;
-
- if (mem.tracked_max_size < mem.tracked_size)
- mem.tracked_max_size = mem.tracked_size ;
-
- mtt = &(mem_type_tracker.mt[mtype]) ;
-
- ++(mtt->malloc_count) ;
- ++(mtt->tracked_count) ;
- mtt->tracked_size += size ;
-
- if (mtt->tracked_max_count < mtt->tracked_count)
- mtt->tracked_max_count = mtt->tracked_count ;
-
- if (mtt->tracked_max_size < mtt->tracked_size)
- mtt->tracked_max_size = mtt->tracked_size ;
-} ;
-
-inline static void
-mem_md_free(enum MTYPE mtype, void* address)
-{
- mem_tracker mtt ;
- md_index* base ;
- mem_descriptor md, prev_md ;
- md_index this, next ;
-
- if (address == NULL)
- return ;
-
- base = mem_md_base(address) ;
-
- prev_md = NULL ;
- this = *base ;
- while (this != 0)
- {
- md = mem_md_ptr(this) ;
- next = mem_md_next(md) ;
-
- if (md->addr == address)
- {
- if (mem_md_type(md) != mtype)
- zabort("memory type mismatch in free") ;
-
- ++mem.free_count ;
- --mem.tracked_count ;
-
- mem.tracked_size -= mem_md_size(md) ;
-
- mtt = &(mem_type_tracker.mt[mtype]) ;
-
- ++(mtt->free_count) ;
- --(mtt->tracked_count) ;
- mtt->tracked_size -= mem_md_size(md) ;
-
- if (prev_md == NULL)
- *base = next ;
- else
- mem_md_set_next(prev_md, next) ;
-
- md->addr = mem_free_descriptors ;
- mem_free_descriptors = md ;
- md->next = this ;
-
- return ;
- }
- else
- {
- prev_md = md ;
- this = next ;
- } ;
- } ;
-
- zabort("Failed to find memory being freed") ;
-} ;
-
-inline static void
-mem_md_realloc(enum MTYPE mtype, void* old_address, void* new_address,
- size_t size, const char* name)
-{
- mem_tracker mtt ;
- md_index* base ;
- mem_descriptor md, prev_md ;
- md_index this, next ;
-
- if (old_address == NULL)
- {
- mem_md_malloc(mtype, new_address, size, name) ;
- return ;
- } ;
-
- passert(size <= md_size_max) ;
-
- base = mem_md_base(old_address) ;
-
- prev_md = NULL ;
- this = *base ;
- while (this != 0)
- {
- md = mem_md_ptr(this) ;
- next = mem_md_next(md) ;
-
- if (md->addr == old_address)
- {
- if (mem_md_type(md) != mtype)
- zabort("memory type mismatch in realloc") ;
-
- ++mem.realloc_count ;
-
- mem.tracked_size += size - mem_md_size(md) ;
-
- if (mem.tracked_max_size < mem.tracked_size)
- mem.tracked_max_size = mem.tracked_size ;
-
- mtt = &(mem_type_tracker.mt[mtype]) ;
-
- ++(mtt->realloc_count) ;
- mtt->tracked_size += size - mem_md_size(md) ;
-
- if (mtt->tracked_max_size < mtt->tracked_size)
- mtt->tracked_max_size = mtt->tracked_size ;
-
- md->name = name ;
- mem_md_set_size(md, size) ;
-
- if (old_address == new_address)
- return ;
-
- if (prev_md == NULL)
- *base = next ;
- else
- mem_md_set_next(prev_md, next) ;
-
- base = mem_md_base(new_address) ;
- mem_md_set_next(md, *base) ;
- *base = this ;
-
- md->addr = new_address ;
-
- return ;
- }
- else
- {
- prev_md = md ;
- this = next ;
- } ;
- } ;
-
- zabort("Failed to find memory being realloced") ;
-} ;
-
-/*==============================================================================
- * Memory Tracker Display
- */
-
-static const char* scale_d_tags [] =
-{
- [0] = " " ,
- [1] = "k",
- [2] = "m",
- [3] = "g",
-} ;
-
-static const char* scale_b_tags [] =
-{
- [0] = " " ,
- [1] = "KiB",
- [2] = "MiB",
- [3] = "GiB",
-} ;
-
-static char*
-mem_show_commas(char* buff, size_t size, uint64_t val, const char* tag)
-{
- char* p ;
- const char* q ;
- int n ;
-
- passert(size > 10) ;
-
- p = buff + size ;
- *(--p) = '\0' ;
-
- q = tag + strlen(tag) ;
- while ((p > buff) && (q > tag))
- *(--p) = *(--q) ;
-
- n = 3 ;
- while (p > buff)
- {
- *(--p) = '0' + (val % 10) ;
- val /= 10 ;
- if (val == 0)
- break ;
-
- if ((--n == 0) && (p > buff))
- {
- *(--p) = ',' ;
- n = 3 ;
- } ;
- } ;
-
- return p ;
-} ;
-
-static char*
-mem_show_count(char* buff, size_t size, uint64_t val, int scale)
-{
- int i, r ;
-
- i = 0 ;
- if (scale)
- {
- r = 0 ;
- while ((i < 3) && (val >= 10000))
- {
- r = (val % 1000) ;
- val /= 1000 ;
- ++i ;
- } ;
- if (r >= 500) {
- val += 1 ;
- if ((val == 10000) && (i < 3))
- {
- val /= 1000 ;
- ++i ;
- } ;
- } ;
- } ;
-
- return mem_show_commas(buff, size, val, scale_d_tags[i]) ;
-} ;
-
-static char*
-mem_show_byte_count(char* buff, size_t size, uint64_t val, int scale)
-{
- int i, r ;
-
- i = 0 ;
- if (scale)
- {
- r = 0 ;
- while ((i < 3) && (val >= 10000))
- {
- r = (val % 1024) ;
- val /= 1024 ;
- ++i ;
- } ;
- if (r >= 512) {
- val += 1 ;
- if ((val == 10000) && (i < 3))
- {
- val /= 1024 ;
- ++i ;
- } ;
- } ;
- } ;
-
- return mem_show_commas(buff, size, val, scale_b_tags[i]) ;
-} ;
-
-static int
-show_memory_tracker_summary(struct vty *vty)
-{
- struct mem_tracker mt ;
- enum { sbs = 100 } ;
- char buf[sbs];
- size_t overhead ;
-
- LOCK ;
- overhead = (sizeof(struct mem_descriptor) * mem_next_index)
- + (sizeof(md_index) * mem_base_count)
- + (sizeof(mem_descriptor) * md_page_count) ;
-
- mt = mem ; /* copy the overall memory information */
- UNLOCK ;
-
- vty_out (vty, "Memory Tracker Statistics:%s", VTY_NEWLINE);
- vty_out (vty, " Current memory allocated: %10s%s",
- mem_show_byte_count(buf, sbs, mt.tracked_size, 1),
- VTY_NEWLINE);
- vty_out (vty, " Current allocated objects: %8s%s",
- mem_show_count (buf, sbs, mt.tracked_count, 1),
- VTY_NEWLINE);
- vty_out (vty, " Maximum memory allocated: %10s%s",
- mem_show_byte_count(buf, sbs, mt.tracked_max_size, 1),
- VTY_NEWLINE);
- vty_out (vty, " Maximum allocated objects: %8s%s",
- mem_show_count (buf, sbs, mt.tracked_max_count, 1),
- VTY_NEWLINE);
- vty_out (vty, " malloc/calloc call count: %8s%s",
- mem_show_count (buf, sbs, mt.malloc_count, 1),
- VTY_NEWLINE);
- vty_out (vty, " realloc_call_count: %8s%s",
- mem_show_count (buf, sbs, mt.realloc_count, 1),
- VTY_NEWLINE);
- vty_out (vty, " free call count: %8s%s",
- mem_show_count (buf, sbs, mt.free_count, 1),
- VTY_NEWLINE);
- vty_out (vty, " Memory Tracker overhead: %10s%s",
- mem_show_byte_count(buf, sbs, overhead, 1),
- VTY_NEWLINE);
- return 1;
-} ;
-
-static int
-show_memory_tracker_detail(struct vty *vty, struct mem_tracker* mt,
- unsigned long alloc)
-{
- enum { sbs = 100 } ;
- char buf[sbs];
-
- vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_count, 1)) ;
- vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_size, 1)) ;
- vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->tracked_max_count, 1)) ;
- vty_out(vty, "%10s", mem_show_byte_count(buf, sbs, mt->tracked_max_size, 1)) ;
- vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->malloc_count, 1)) ;
- vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->realloc_count, 1)) ;
- vty_out(vty, "%8s", mem_show_count(buf, sbs, mt->free_count, 1)) ;
-
- if (alloc != mt->tracked_count)
- vty_out(vty, " %8s!!", mem_show_count(buf, sbs, alloc, 1)) ;
-
- return 1;
-} ;
diff --git a/lib/memory.c b/lib/memory.c
index 49b20c14..efd3a83f 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -26,68 +26,527 @@
#include <malloc.h>
#endif /* !HAVE_STDLIB_H || HAVE_MALLINFO */
+#include "qlib_init.h"
#include "log.h"
#include "memory.h"
#include "qpthreads.h"
-/* Needs to be qpthread safe. The system malloc etc are already
- * thread safe, but we need to protect the stats
+#include "vty.h"
+#include "command.h"
+#include "qfstring.h"
+
+/* HAVE_MMAP specifies that can do mmap() and mmunmap() -- below */
+#define HAVE_MMAP 1
+
+/* HAVE_MEM_REGIONS -- pro tem ! */
+#if defined(GNU_LINUX) && !defined(NO_MEM_REGIONS)
+#define HAVE_MEM_REGIONS
+#endif
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+#ifdef HAVE_MEM_REGIONS
+
+#undef HAVE_MEM_REGIONS
+#define HAVE_MEM_REGIONS 1
+
+ enum { memory_regions = HAVE_MEM_REGIONS } ;
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#else
+ enum { memory_regions = 0 } ;
+#endif
+
+
+/*==============================================================================
+ * Here we wrap the usual malloc()/calloc()/realloc()/free()/strdup() functions
+ * with various amounts of statics gathering.
+ *
+ * When memory is allocated/deallocated the type of that memory must be
+ * specified -- see memtypes.c.
+ *
+ * The basic statistics gathering counts, for each memory type, the number of
+ * objects which have been allocated and not yet freed -- so knows the current
+ * number of allocated objects. (The counts depend on realloc() and free()
+ * being given the same memory type as the original allocation.)
+ *
+ *------------------------------------------------------------------------------
+ * The "memory_tracker"
+ *
+ * The (debug) "memory_tracker" statistics keep the address, size and type
+ * for every allocated object, and the name of the function which allocated
+ * the object (or last realloc()'d) it. This adds 24..32 bytes per object,
+ * so is not cheap ! The number of alloc/realloc/free operations for each
+ * type of memory is also recorded, as are the peak number of objects and the
+ * peak memory allocation. These statistics allow:
+ *
+ * * tracking of size of allocated memory
+ *
+ * * the malloc() overhead for allocated memory
+ * (assuming the mem_tracker_start() function can establish this.
+ *
+ * * checking that realloc() and free()
+ *
+ * a. specify the address of a previously allocated object.
+ *
+ * b. specify the same type as the original allocation
+ *
+ * * reporting of the total activity -- alloc/realloc/free -- by memory type
+ *
+ * * reporting of peak object counts and size -- by memory type.
+ *
+ * The extra data saved by the memory_tracker is put (if at all possible) into
+ * lumps of memory separate from malloc() -- allocated by mmap().
+ *
+ *------------------------------------------------------------------------------
+ * The "memory_logger"
+ *
+ * The (debug) "memory_logger" outputs a logging message for every memory
+ * operation.
+ *
+ * The "memory_logger" overrides the "memory tracker".
+ *
+ *------------------------------------------------------------------------------
+ * mallinfo()
+ *
+ * This is a GNU extension in glibc. Sadly, the mallinfo data structure is
+ * defined in terms of "int", and there seem to be no plans to extend that to
+ * support larger amounts of memory -- beyond 4G.
+ *
+ *------------------------------------------------------------------------------
+ * mmap() and munmap()
+ *
+ * These are Posix facilities. Most implementations support (MAP_ANON or
+ * MAP_ANONYMOUS), though that is not Posix. The Posix posix_typed+mem_open()
+ * can be used if MAP_ANON or MAP_ANONYMOUS is not supported.
+ *
+ * Late model malloc() uses mmap(), so memory may be allocated across a number
+ * of distinct regions.
+ *
+ * The "memory_tracker" will use mmap() allocated regions, if at all possible,
+ * for all the tracked data.
+ *
+ *------------------------------------------------------------------------------
+ * Memory Regions
+ *
+ * With Linux /proc/self/maps provides a list of all regions of memory. Also,
+ * /proc/self/pagemap can be used to discover which pages in each region are
+ * in memory and which are swapped out (and which are neither).
+ *
+ * Using these it is possible to provide some information about total memory
+ * usage -- partly replacing mallinfo().
+ *
+ * With memory_tracker, can work out which regions form part of the heap, and
+ * how much of each region is in use, taken up by overhead, or currently
+ * free.
+ *
+ * Without memory_tracker there is no way to tell which regions are part of
+ * the heap, apart from the initial heap region. So the summary information
+ * may show a number of "Anon" regions, some of which are, in fact, heap.
+ *
+ * The extra data saved by the memory_regions is put (if at all possible) into
+ * lumps of memory separate from malloc() -- allocated by mmap().
+ *
+ *------------------------------------------------------------------------------
+ * malloc() overhead
+ *
+ * There is an attempt to establish the amount of redtape on each malloc()
+ * allocation, the minimum size of a malloc() block and the unit of allocation
+ * after that. This is used by the memory_tracker to identify the space in
+ * the heap which is "overhead".
+ */
+
+/*------------------------------------------------------------------------------
+ * Need to be qpthread safe. The system malloc etc are already thread safe,
+ * but we need to protect the statistics.
*/
static qpt_mutex_t memory_mutex;
-#define LOCK qpt_mutex_lock(&memory_mutex);
-#define UNLOCK qpt_mutex_unlock(&memory_mutex);
+#define LOCK qpt_mutex_lock(memory_mutex);
+#define UNLOCK qpt_mutex_unlock(memory_mutex);
static void log_memstats(int log_priority);
-static const struct message mstr [] =
+/*------------------------------------------------------------------------------
+ * Basic statistics
+ */
+static mem_stats_t mstats ; /* zeroised in memory_start() */
+
+static size_t mem_pagesize = 0 ; /* set in memory_start() */
+
+/*------------------------------------------------------------------------------
+ * Showing stuff
+ */
+QFB_T(num_str, 30) ;
+
+/*------------------------------------------------------------------------------
+ * Read in the mem_type_map array: MTYPE_XXX -> "MTYPE_XXX"
+ */
+#define MEM_MTYPE_MAP_REQUIRED 1
+#include "memtypes.h"
+CONFIRM((sizeof(mem_mtype_map) / sizeof(char*)) == MTYPE_MAX) ;
+
+static inline const char*
+mem_mtype_name(mtype_t mtype)
{
- { MTYPE_THREAD, "thread" },
- { MTYPE_THREAD_MASTER, "thread_master" },
- { MTYPE_VECTOR, "vector" },
- { MTYPE_VECTOR_BODY, "vector_index" },
- { MTYPE_IF, "interface" },
- { 0, NULL },
-};
+ return (mtype < MTYPE_MAX) ? mem_mtype_map[mtype] : "*unknown mtype*" ;
+}
-/* If using the mem_tracker, include it now. */
+/*------------------------------------------------------------------------------
+ * Include the optional memory tracker -- MEMORY_TRACKER.
+ *
+ * The memory tracker is always part of the source -- so kept up to date.
+ * We depend on dead code removal to eliminate overhead when the tracker is
+ * not used.
+ */
+typedef struct mem_tracker_item* mem_tracker_item ;
+typedef struct mem_tracker_item mem_tracker_item_t ;
-typedef struct mem_tracker* mem_tracker ;
-struct mem_tracker
+struct mem_tracker_item
{
- uint64_t malloc_count ;
- uint64_t realloc_count ;
- uint64_t free_count ;
+ ulong malloc_count ; /* number of mallocs */
+ ulong realloc_count ; /* number of reallocs */
+ ulong free_count ; /* number of frees */
- uint32_t tracked_count ;
- size_t tracked_size ;
+ ulong item_count ; /* number of existing items */
+ size_t total_size ; /* total size of existing items */
- uint32_t tracked_max_count ;
- size_t tracked_max_size ;
+ ulong peak_item_count ; /* peak count of items */
+ size_t peak_total_size ; /* peak size of items */
} ;
-static void
-mem_tracker_zeroise(struct mem_tracker* mem)
+/*------------------------------------------------------------------------------
+ * Memory Region Handling -- based on /proc/self/maps
+ * and /proc/self/pagemap
+ */
+enum mem_region_flags
{
- memset(mem, 0, sizeof(struct mem_tracker)) ;
+ mrf_read = BIT(0),
+ mrf_write = BIT(1),
+ mrf_execute = BIT(2),
+ mrf_private = BIT(3),
+ mrf_shared = BIT(4),
} ;
+typedef enum mem_region_flags mem_region_flags_t ;
-#ifdef MEMORY_TRACKER
-#include "mem_tracker.c"
-#endif
+enum mem_region_type
+{
+ mrt_anon,
+ mrt_named,
+ mrt_heap,
+ mrt_stack,
+ mrt_vdso,
+ mrt_vsyscall,
+ mrt_other,
+
+ mrt_type_count,
+} ;
+typedef enum mem_region_type mem_region_type_t ;
-/*==============================================================================
- * Keeping track of number of allocated objects of given type
+static const char mrf_type_chars[mrt_type_count] =
+{
+ [mrt_anon] = 'A',
+ [mrt_named] = '=',
+ [mrt_heap] = 'H',
+ [mrt_stack] = 'S',
+ [mrt_vdso] = 'v',
+ [mrt_vsyscall] = 'w',
+ [mrt_other] = '~',
+} ;
+
+enum mem_region_sex
+{
+ mrx_code = 0, /* mrt_named & mrf_read & !mrf_write & mrf_execute */
+ /* mrt_vdso & mrf_read & !mrf_write & mrf_execute */
+ /* mrt_vsyscall & mrf_read & !mrf_write & mrf_execute */
+ mrx_const, /* mrt_named & mrf_read & !mrf_write & !mrf_execute */
+ mrx_data, /* mrt_named & mrf_read & mrf_write & !mrf_execute */
+ mrx_reserved, /* mrt_name & !mrf_read & !mrf_write & !mrf_execute */
+ mrx_gap, /* mrt_anon & !mrf_read & !mrf_write & !mrf_execute */
+ mrx_exotic, /* anything else */
+
+ /* Note that the stack and anon regions immediately precede the heap stuff */
+
+ mrx_stack, /* mrt_stack & mrf_read & mrf_write & !mrf_execute */
+ mrx_anon, /* mrt_anon & mrf_read & mrf_write & !mrf_execute */
+
+ mrx_rest_total, /* subtotal of all not heap */
+
+ mrx_heap, /* start of heap information */
+
+ mrx_heap_main = mrx_heap,
+ /* mrt_heap & mrf_read & mrf_write & !mrf_execute */
+ mrx_heap_extra, /* mrx_anon & seen by memory_tracker */
+
+ mrx_heap_total, /* subtotal of heap_main and heap extra */
+
+ mrx_grand_total, /* total of everything */
+
+ mrx_sex_count, /* number of mrx items */
+
+ mrx_heap_count = mrx_heap_total - mrx_heap,
+ /* number of heap type items */
+} ;
+
+typedef enum mem_region_sex mem_region_sex_t ;
+
+static const char* mrx_sex_names[mrx_sex_count] =
+{
+ [mrx_heap_main] = "Heap ",
+ [mrx_heap_extra] = "Extra ",
+
+ [mrx_code] = "Code ",
+ [mrx_const] = "Const ",
+ [mrx_data] = "Data ",
+ [mrx_reserved] = "Resrvd",
+ [mrx_gap] = "Gap ",
+ [mrx_exotic] = "Exotic",
+ [mrx_stack] = "Stack ",
+ [mrx_anon] = "Anon ",
+
+ [mrx_heap_total] = "++++++",
+ [mrx_rest_total] = "++++++",
+ [mrx_grand_total] = " Total",
+} ;
+
+enum { mem_region_max_name_len = 127 } ;
+
+typedef struct mem_region mem_region_t ;
+typedef struct mem_region* mem_region ;
+typedef const struct mem_region* mem_region_c ;
+
+struct mem_region
+{
+ uintptr_t base ;
+ uintptr_t limit ;
+
+ mem_region_flags_t flags ;
+ mem_region_type_t type ;
+ mem_region_sex_t sex ;
+
+ size_t offset ;
+
+ uint dev_major ;
+ uint dev_minor ;
+ ullong inode ;
+
+ char name[mem_region_max_name_len + 1] ;
+
+ size_t present ;
+ size_t swapped ;
+
+ uintptr_t min_address ;
+ uintptr_t max_address ;
+
+ size_t count ;
+ size_t used ;
+ size_t overhead ;
+} ;
+
+typedef struct mem_region_summary mem_region_summary_t ;
+typedef struct mem_region_summary* mem_region_summary ;
+
+struct mem_region_summary
+{
+ mem_region_sex_t sex ;
+
+ size_t size ;
+ size_t present ;
+ size_t swapped ;
+
+ size_t count ;
+ size_t used ;
+ size_t overhead ;
+ size_t unused ;
+ size_t data ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker definitions
*/
+typedef struct mem_descriptor mem_descriptor_t ;
+typedef struct mem_descriptor* mem_descriptor ;
+struct mem_descriptor
+{
+ void* addr ;
+ const char* name ;
+
+ uint32_t next ; /* MS Type is encoded as MS 4 bits */
+ uint32_t size ; /* LS Type is encoded as MS 4 bits */
+} ;
+
+typedef uint32_t md_index_t ;
-static struct mstat
+enum
{
- struct
- {
- char *name ;
- long alloc ;
- } mt[MTYPE_MAX] ;
-} mstat ;
+ md_next_bits = 28, /* up to 256M allocated objects */
+ md_next_mask = (1 << md_next_bits) - 1,
+
+ md_index_max = md_next_mask + 1,
+
+ md_size_bits = 28, /* up to 256M individual item */
+ md_size_mask = (1 << md_size_bits) - 1,
+
+ md_size_max = md_size_mask,
+
+ md_next_type_bits = 32 - md_next_bits,
+ md_next_type_mask = (1 << md_next_type_bits) - 1,
+ md_size_type_bits = 32 - md_size_bits,
+ md_size_type_mask = (1 << md_size_type_bits) - 1,
+
+ md_i_index_bits = 16,
+ md_i_index_count = 1 << md_i_index_bits,
+ md_i_index_mask = md_i_index_count - 1,
+
+ md_page_bits = md_next_bits - md_i_index_bits,
+ md_page_count = 1 << md_page_bits,
+ md_page_mask = md_page_count - 1,
+} ;
+
+CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ;
+
+/* The structure in which all mem_tracker stuff is held.
+ *
+ * Note that for the total memory, does not bother to keep:
+ *
+ * malloc_count
+ * realloc_count
+ * free_count
+ *
+ * up to date on every alloc/free -- those can be produced by adding up the
+ * typ items.
+ *
+ * Does keep:
+ *
+ * item_count
+ * total_size
+ *
+ * up to date on every alloc/free, so that can maintain:
+ *
+ * peak_item_count
+ * peak_total_size
+ */
+typedef struct mem_tracker_data mem_tracker_data_t ;
+typedef struct mem_tracker_data* mem_tracker_data ;
+
+struct mem_tracker_data
+{
+ mem_tracker_item_t tot[1] ; /* Total memory */
+ mem_tracker_item_t typ[MTYPE_MAX] ; /* Per memory type */
+
+ size_t tracker_overhead ; /* Bytes used by tracker */
+} ;
+
+/* All the mem_tracker data is kept together to facilitate copying of same
+ * to take snapshot to output from.
+ */
+static mem_tracker_data_t mem_mt_data ;
+
+static mem_descriptor* mem_page_table = NULL ;
+static mem_descriptor mem_free_descriptors = NULL ;
+static md_index_t mem_next_index = 0 ;
+
+static uint32_t mem_base_count = 0 ;
+static md_index_t* mem_bases = NULL ;
+
+/*------------------------------------------------------------------------------
+ * In order to measure heap usage, we try to establish the overhead and
+ * padding in the blocks returned by malloc() -- see mem_alloc_discover().
+ *
+ * Have to make a number of assumptions:
+ *
+ * a. overhead comprises:
+ *
+ * * a fixed amount of "red-tape"
+ *
+ * * a minimum allocation
+ *
+ * * a fixed unit of allocation for anything above minimum
+ *
+ * Note that this assumes that malloc() does *not* for larger blocks
+ * allocate larger amounts of unused space at the end of a block.
+ * (Which would be a way of avoiding moving memory around on some
+ * realloc() requests -- but those are rare.)
+ *
+ * b. at start time:
+ *
+ * * malloc() will allocate blocks at the "edge" of the heap.
+ *
+ * * free() will release blocks, which will be immediately be
+ * reused.
+ *
+ * This appears reasonable, but... who can tell ? The code that tries to
+ * establish these parameters is quite careful to check that its conclusions
+ * are consistent -- but there is limited intelligence here.
+ */
+bool mem_alloc_known = false ; /* set true when think know what
+ the overhead & padding is. */
+
+uintptr_t mem_alloc_first = 0 ; /* address of first allocation */
+size_t mem_alloc_min_size = 0 ; /* minimum size block */
+size_t mem_alloc_redtape = 0 ; /* redtape per block */
+size_t mem_alloc_min_max = 0 ; /* maximum allocation in minimum
+ size block */
+size_t mem_alloc_unit = 0 ; /* unit beyond minimum size */
+
+size_t mem_alloc_add = 0 ; /* see mem_alloc_padding() */
+
+char* mem_alloc_trial_addr = NULL ; /* final trial results */
+size_t mem_alloc_trial_size = 0 ;
+size_t mem_alloc_trial_alloc = 0 ;
+
+enum
+{
+ ma_not_tried,
+
+ ma_not_a_before_b,
+ ma_not_increasing_size,
+ ma_unit_not_power_2,
+ ma_address_not_n_unit,
+ ma_size_min_not_n_unit,
+ ma_trial_failed,
+
+} mem_alloc_failed = ma_not_tried ;
+
+/*------------------------------------------------------------------------------
+ * Extra counting performed by optional logger -- MEMORY_LOGGER
+ *
+ * The memory logger is always part of the source -- so kept up to date.
+ * We depend on dead code removal to eliminate overhead when the tracker is
+ * not used.
+ */
+static struct /* TODO: establish what this is for ! */
+{
+ const char *name;
+ ulong alloc;
+ ulong t_malloc;
+ ulong c_malloc;
+ ulong t_calloc;
+ ulong c_calloc;
+ ulong t_realloc;
+ ulong t_free;
+ ulong c_strdup;
+} mlog_stat [MTYPE_MAX];
+
+/*==============================================================================
+ * Function definitions.
+ */
+inline static void mem_mt_malloc(mtype_t mtype, void* address,
+ size_t size, const char* name) ;
+inline static void mem_mt_free(mtype_t mtype, void* address) ;
+inline static void mem_mt_realloc(mtype_t mtype, void* old_address,
+ void* new_address,
+ size_t new_size, const char* name) ;
+static void mem_mt_get_stats(mem_tracker_data mtd, bool locked) ;
+
+static void mtype_log (const char *func, void *memory, mtype_t mtype,
+ const char *file, int line) ;
/*==============================================================================
* Memory allocation functions.
@@ -95,12 +554,15 @@ static struct mstat
* NB: failure to allocate is FATAL -- so no need to test return value.
*/
-/* Fatal memory allocation error occured. */
+/*------------------------------------------------------------------------------
+ * Fatal memory allocation error occured.
+ */
static void __attribute__ ((noreturn))
-zerror (const char *fname, int type, size_t size)
+zerror (const char *fname, mtype_t mtype, size_t size)
{
- zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n",
- fname, lookup (mstr, type), (int) size, errtoa(errno, 0).str);
+ zlog_err ("%s : can't allocate memory for '%s'(%d) size %zd: %s\n",
+ fname, mem_mtype_name(mtype), mtype, size, errtoa(errno, 0).str) ;
+
log_memstats(LOG_WARNING);
/* N.B. It might be preferable to call zlog_backtrace_sigsafe here, since
that function should definitely be safe in an OOM condition. But
@@ -108,7 +570,7 @@ zerror (const char *fname, int type, size_t size)
this time... */
zlog_backtrace(LOG_WARNING);
zabort_abort();
-}
+} ;
/*------------------------------------------------------------------------------
* Memory allocation.
@@ -119,8 +581,8 @@ zerror (const char *fname, int type, size_t size)
*
* NB: If memory cannot be allocated, aborts execution.
*/
-void *
-zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
+extern void *
+zmalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -135,23 +597,33 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
-#ifdef MEMORY_TRACKER
- mem_md_malloc(mtype, memory, size, name) ;
-#endif
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].c_malloc++;
+ mlog_stat[mtype].t_malloc++;
+ } ;
+
+ if (memory_tracker)
+ mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zmalloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
- return memory;
-}
+ return memory ;
+} ;
/*------------------------------------------------------------------------------
* Memory allocation zeroising the allocated area.
*
* As zmalloc, plus zeroises the allocated memory.
*/
-void *
-zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
+extern void *
+zcalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -166,11 +638,21 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
-#ifdef MEMORY_TRACKER
- mem_md_malloc(mtype, memory, size, name) ;
-#endif
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].c_calloc++;
+ mlog_stat[mtype].t_calloc++;
+ } ;
+
+ if (memory_tracker)
+ mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zcalloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return memory;
@@ -190,7 +672,7 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
* NB: If memory cannot be allocated, aborts execution.
*/
void *
-zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
+zrealloc (mtype_t mtype, void *ptr, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -205,11 +687,20 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
else
{
if (ptr == NULL)
- mstat.mt[mtype].alloc++;
-#ifdef MEMORY_TRACKER
- mem_md_realloc(mtype, ptr, memory, size, name) ;
-#endif
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].t_realloc++;
+ } ;
+
+ if (memory_tracker)
+ mem_mt_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zrealloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return memory;
@@ -224,22 +715,27 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
* Does nothing if the given pointer is NULL.
*/
void
-zfree (enum MTYPE mtype, void *ptr)
+zfree (mtype_t mtype, void *ptr MEMORY_EXTRA_ARGS)
{
if (ptr != NULL)
{
LOCK ;
- assert(mstat.mt[mtype].alloc > 0) ;
+ assert(mstats.alloc[mtype] > 0) ;
+ mstats.alloc[mtype]--;
- mstat.mt[mtype].alloc--;
-#ifdef MEMORY_TRACKER
- mem_md_free(mtype, ptr) ;
-#endif
+ if (memory_logger)
+ mlog_stat[mtype].t_free++;
+
+ if (memory_tracker)
+ mem_mt_free(mtype, ptr) ;
free (ptr);
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zfree", ptr, mtype, MEMORY_LOGGING_ARGS) ;
} ;
} ;
@@ -251,13 +747,13 @@ zfree (enum MTYPE mtype, void *ptr)
* NB: If memory cannot be allocated, aborts execution.
*/
char *
-zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME)
+zstrdup (mtype_t mtype, const char *str MEMORY_EXTRA_ARGS)
{
void *dup;
LOCK ;
- dup = strdup (str);
+ dup = strdup (str ? str : "");
if (dup == NULL)
{
UNLOCK ;
@@ -265,305 +761,1913 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
-#ifdef MEMORY_TRACKER
- mem_md_malloc(mtype, dup, strlen(str)+1, name) ;
-#endif
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ mlog_stat[mtype].c_strdup++;
+
+ if (memory_tracker)
+ mem_mt_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zstrdup", dup, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return dup;
}
/*==============================================================================
- * Memory allocation with built in logging
+ * Memory logging function -- called if MEMORY_LOGGER enabled
*/
+static void
+mtype_log (const char *func, void *memory, mtype_t mtype,
+ const char *file, int line)
+{
+ zlog_debug ("%s: %s(%d) %p %s %d", func, mem_mtype_name(mtype), mtype,
+ memory, file, line) ;
+} ;
-#ifdef MEMORY_LOG
+/*==============================================================================
+ * Support for memory reporting functions
+ */
-static struct
+/*------------------------------------------------------------------------------
+ * Pick up copy of the current basic statistics
+ *
+ * Done under lock in order to get a single, consistent snap shot.
+ */
+extern void
+mem_get_stats(mem_stats_t* mst)
{
- const char *name;
- long alloc;
- unsigned long t_malloc;
- unsigned long c_malloc;
- unsigned long t_calloc;
- unsigned long c_calloc;
- unsigned long t_realloc;
- unsigned long t_free;
- unsigned long c_strdup;
-} mlog_stat [MTYPE_MAX];
+ LOCK ;
+ *mst = mstats ;
+ UNLOCK ;
+} ;
-static void
-mtype_log (char *func, void *memory, const char *file, int line,
- enum MTYPE type)
+/*------------------------------------------------------------------------------
+ * Get value of alloc count for given mtype
+ *
+ * For use with a copy of statistics picjed up already.
+ */
+extern ulong
+mem_get_alloc(mem_stats_t* mst, mtype_t mtype)
{
- zlog_debug ("%s: %s %p %s %d", func, lookup (mstr, type), memory, file, line);
-}
+ return mst->alloc[mtype] ;
+} ;
-void *
-mtype_zmalloc (const char *file, int line, int type, size_t size)
+/*------------------------------------------------------------------------------
+ * Get individual allocation counter.
+ *
+ * To get all the counters at once, see mem_get_stats().
+ */
+extern ulong
+mtype_stats_alloc(mtype_t mtype)
{
- void *memory;
+ ulong alloc ;
+ LOCK ;
+ alloc = mstats.alloc[mtype] ;
+ UNLOCK ;
+ return alloc ;
+} ;
- LOCK
- mlog_stat[type].c_malloc++;
- mlog_stat[type].t_malloc++;
- UNLOCK
+/*------------------------------------------------------------------------------
+ * Convert given byte count to 4 figures + scale
+ *
+ * "0 bytes", "2048 bytes", "110KiB", "500MiB", "11GiB", etc.
+ *
+ * The pointer returned may be NULL (indicating an error)
+ * or point to the given buffer, or point to static storage.
+ */
+extern const char *
+mtype_memstr(char *buf, size_t len, ulong bytes)
+{
+ uint t, g, m, k;
- memory = zmalloc (type, size);
- mtype_log ("zmalloc", memory, file, line, type);
+ /* easy cases */
+ if (!bytes)
+ return "0 bytes";
+ if (bytes == 1)
+ return "1 byte";
- return memory;
-}
+ if (sizeof (ulong) >= 8)
+ /* Hacked to make it not warn on ILP32 machines
+ * Shift will always be 40 at runtime. See below too */
+ t = bytes >> (sizeof (ulong) >= 8 ? 40 : 0);
+ else
+ t = 0;
-void *
-mtype_zcalloc (const char *file, int line, enum MTYPE type, size_t size)
-{
- void *memory;
+ g = bytes >> 30;
+ m = bytes >> 20;
+ k = bytes >> 10;
+
+ if (t > 10)
+ {
+ /* The shift will always be 39 at runtime.
+ * Just hacked to make it not warn on 'smaller' machines.
+ * Static compiler analysis should mean no extra code
+ */
+ if (bytes & (1UL << (sizeof (ulong) >= 8 ? 39 : 0)))
+ t++;
+ snprintf (buf, len, "%4u TiB", t);
+ }
+ else if (g > 10)
+ {
+ if (bytes & (1 << 29))
+ g++;
+ snprintf (buf, len, "%u GiB", g);
+ }
+ else if (m > 10)
+ {
+ if (bytes & (1 << 19))
+ m++;
+ snprintf (buf, len, "%u MiB", m);
+ }
+ else if (k > 10)
+ {
+ if (bytes & (1 << 9))
+ k++;
+ snprintf (buf, len, "%d KiB", k);
+ }
+ else
+ snprintf (buf, len, "%lu bytes", bytes);
- LOCK
- mlog_stat[type].c_calloc++;
- mlog_stat[type].t_calloc++;
- UNLOCK
+ return buf;
+} ;
- memory = zcalloc (type, size);
- mtype_log ("xcalloc", memory, file, line, type);
+/*==============================================================================
+ * Establishing (if can) the malloc() parameters.
+ */
- return memory;
-}
+static qfb_gen_t mem_alloc_unknown_message(void) ;
-void *
-mtype_zrealloc (const char *file, int line, enum MTYPE type, void *ptr,
- size_t size)
+static inline size_t
+mem_alloc_padding(size_t s)
{
- void *memory;
+ size_t sp ;
+ sp = (s <= mem_alloc_min_max) ? 0 : ((s - mem_alloc_min_max + mem_alloc_add)
+ & ~mem_alloc_add) ;
+ return sp + mem_alloc_min_max - s ;
+} ;
- /* Realloc need before allocated pointer. */
- LOCK
- mlog_stat[type].t_realloc++;
- UNLOCK
+/*------------------------------------------------------------------------------
+ * This tries to find out how malloc() system manages memory, overhead-wise.
+ */
+static void
+mem_alloc_discover(void)
+{
+ enum { show = false } ;
+
+ size_t alloc ;
+ FILE* out = show ? stdout : NULL ;
+
+ /* Make sure the statics are initialised -- cleared down to zero */
+ mem_page_table = NULL ;
+ mem_free_descriptors = NULL ;
+ mem_next_index = 0 ;
+
+ mem_base_count = 0 ;
+ mem_bases = NULL ;
+
+ /* Try to establish the memory allocation parameters */
+ mem_alloc_known = false ;
+ mem_alloc_failed = ma_not_tried ;
+
+ mem_alloc_first = (uintptr_t)((char*)malloc(1)) ;
+ free((void*)mem_alloc_first) ;
+
+ mem_alloc_min_size = 0 ;
+ mem_alloc_redtape = 0 ;
+ mem_alloc_min_max = 0 ;
+ mem_alloc_unit = 0 ;
+
+ if (show)
+ fprintf(out, "first malloc(1):%p\n", (void*)mem_alloc_first) ;
+
+ /* In this loop we allocate blocks 'a' and then 'b' of the same size,
+ * increasing from 1 by 1 each time round the loop.
+ *
+ * We assume (at a minimum) that at the edge of the heap 'b' will immediately
+ * follow 'a'.
+ *
+ * So, we expect to find the actual size of each allocation (including
+ * redtape and padding) as 'b - a'. After allocating 1 byte, expect the
+ * size to remain the same until we allocate the minimum allocation + 1,
+ * at which point we expect the size to increase by the allocation unit.
+ *
+ * Note that we do not require 'a' to be allocated at the same address
+ * each time -- only that if one is allocated using reclaimed space, then
+ * both are. [A common approach is for released space to remain in the units
+ * it was originally allocated in -- so, not coalesce free space, at least
+ * not until there is pressure to do so. If malloc() does this, allocating
+ * 'a' and 'b' the same size means that 'a' and 'b' will fit in their
+ * first locations until the minimum allocation is exceeded, at which point
+ * they will neither of them fit there.]
+ *
+ * We leave this loop:
+ *
+ * * with mem_alloc_unit != 0 if all has gone well so far.
+ *
+ * or mem_alloc_unit == 0 if got something unexpected
+ * mem_alloc_failed == indication of what went wrong.
+ *
+ * * mem_alloc_min_size
+ *
+ * * mem_alloc_min_max
+ */
+ alloc = 0 ;
+
+ while (1)
+ {
+ char* a, * b ;
+ size_t size ;
+ uint i ;
- memory = zrealloc (type, ptr, size);
+ ++alloc ;
- mtype_log ("xrealloc", memory, file, line, type);
+ a = malloc(alloc) ;
+ b = malloc(alloc) ;
- return memory;
-}
+ if (b < a)
+ {
+ mem_alloc_failed = ma_not_a_before_b ;
+ break ;
+ } ;
-/* Important function. */
-void
-mtype_zfree (const char *file, int line, enum MTYPE type, void *ptr)
+ size = b - a ;
+ for (i = 0 ; i < alloc ; ++i)
+ *(a + i) = i ;
+
+ free(b) ;
+ free(a) ;
+
+ if (show)
+ {
+ fprintf(out, "alloc:%3zd a:%p b:%p size=%zd\n",
+ alloc, a, b, size) ;
+ a = (char*)mem_alloc_first ;
+ while (a < b)
+ {
+ fprintf(out, " %p: %016llX\n", a, (ullong)(*(uint64_t*)a)) ;
+ a += 8 ;
+ } ;
+ } ;
+
+ if (mem_alloc_min_size == 0)
+ mem_alloc_min_size = size ; /* capture first size */
+
+ if (mem_alloc_min_size == size)
+ mem_alloc_min_max = alloc ; /* increases until size changes */
+ else
+ {
+ /* As soon as the size changes we stop increasing the allocation
+ * request and exit the loop.
+ *
+ * If the size increased, we assume it has done so by the minimum
+ * allocation unit.
+ */
+ if (mem_alloc_min_size < size)
+ mem_alloc_unit = size - mem_alloc_min_size ;
+ else
+ mem_alloc_failed = ma_not_increasing_size ;
+ break ;
+ } ;
+ } ;
+
+ if (show)
+ fprintf(out, "size_min=%zd alloc_min=%zd alloc_unit=%zd\n",
+ mem_alloc_min_size, mem_alloc_min_max, mem_alloc_unit) ;
+
+ if (mem_alloc_unit != 0)
+ {
+ size_t x ;
+
+ /* Assume all is well. */
+ mem_alloc_redtape = mem_alloc_min_size - mem_alloc_min_max ;
+ mem_alloc_add = mem_alloc_unit - 1 ;
+
+ x = ((mem_alloc_unit ^ (mem_alloc_unit - 1)) >> 1) + 1 ;
+ if (x != mem_alloc_unit)
+ mem_alloc_failed = ma_unit_not_power_2 ;
+ else if ((mem_alloc_first % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_address_not_n_unit ;
+ else if ((mem_alloc_min_size % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_size_min_not_n_unit ;
+ else
+ {
+ char* b ;
+
+ mem_alloc_trial_alloc = 31415 ;
+ mem_alloc_trial_addr = malloc(mem_alloc_trial_alloc) ;
+ b = malloc(mem_alloc_trial_alloc) ;
+ free(b) ;
+ free(mem_alloc_trial_addr) ;
+
+ mem_alloc_trial_size = b - mem_alloc_trial_addr ;
+
+ if (mem_alloc_trial_size !=
+ (mem_alloc_redtape + mem_alloc_trial_alloc
+ + mem_alloc_padding(mem_alloc_trial_alloc)))
+ mem_alloc_failed = ma_trial_failed ;
+ else if (((uintptr_t)mem_alloc_trial_addr % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_trial_failed ;
+ else if ((mem_alloc_trial_size % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_trial_failed ;
+ else
+ mem_alloc_known = true ;
+ } ;
+ } ;
+
+ if (!mem_alloc_known)
+ mem_alloc_add = 0 ;
+
+ if (show)
+ {
+ if (mem_alloc_known)
+ fprintf(out,
+ "Allocation parameters: redtape=%zu min_max_alloc=%zu alloc unit=%zu\n",
+ mem_alloc_redtape, mem_alloc_min_max, mem_alloc_unit) ;
+ else
+ fprintf(out, "Allocation parameters not known: %s\n",
+ mem_alloc_unknown_message().str) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Message explaining why allocation parameters are not known
+ *
+ * Message expected to follow something like: "allocation parameters unknown: "
+ */
+static qfb_gen_t
+mem_alloc_unknown_message(void)
{
- LOCK
- mlog_stat[type].t_free++;
- UNLOCK
+ QFB_QFS(qfb_gen, qfs) ;
- mtype_log ("xfree", ptr, file, line, type);
+ switch (mem_alloc_failed)
+ {
+ case ma_not_tried:
+ qfs_append(qfs, "not yet tried to establish parameters") ;
+ break ;
+
+ case ma_not_a_before_b:
+ qfs_append(qfs, "malloc() allocated 'a' at higher address than 'b'") ;
+ break ;
+
+ case ma_not_increasing_size:
+ qfs_append(qfs,
+ "malloc() apparently allocated less space for larger request") ;
+ break ;
+
+ case ma_unit_not_power_2:
+ qfs_printf(qfs, "mem_alloc_unit=%zd is not power of 2\n",
+ mem_alloc_unit) ;
+ break ;
+
+ case ma_address_not_n_unit:
+ qfs_printf(qfs,
+ "first address in heap (%p) is not multiple of alloc_unit=%zd",
+ (void*)mem_alloc_first, mem_alloc_unit) ;
+ break ;
+
+ case ma_size_min_not_n_unit:
+ qfs_printf(qfs, "minimum size (%zd) is not multiple of alloc_unit=%zd",
+ mem_alloc_min_size, mem_alloc_unit) ;
+ break ;
+
+ case ma_trial_failed:
+ qfs_printf(qfs,
+ "parameters trial failed: malloc(%zu) gave %zu bytes at %p",
+ mem_alloc_trial_alloc, mem_alloc_trial_size, mem_alloc_trial_addr) ;
+ break ;
+
+ default:
+ qfs_printf(qfs, "for unknown reason %d", mem_alloc_failed) ;
+ break ;
+ } ;
- zfree (type, ptr);
-}
+ qfs_term(qfs) ;
-char *
-mtype_zstrdup (const char *file, int line, enum MTYPE type, const char *str)
+ return qfb_gen ;
+} ;
+
+/*==============================================================================
+ * Memory Region Mapping
+ *
+ * Uses a mmaped area to hold:
+ *
+ * 1. array of mem_region_t objects, one for each region in /proc/self/maps
+ *
+ * 2. array of mem_region pointers, for all regions in ascending address
+ * order.
+ *
+ * 3. array of mem_region pointers, for all "malloc" regions in ascending
+ * address order -- this is used when memory tracker maps current
+ * allocations to the memory map.
+ */
+static void* mem_region_map_base = NULL ; /* mmaped area base */
+static size_t mem_region_map_size = 0 ; /* mmaped area size */
+
+static uint mem_region_count = 0 ; /* current count */
+static uint mem_region_heap_count = 0 ; /* current count, heap */
+static uint mem_region_max_count = 0 ; /* current limit */
+
+static mem_region_t* mem_regions = NULL ; /* known regions */
+static mem_region* mem_region_v = NULL ; /* all regions */
+static mem_region* mem_region_h = NULL ; /* heap regions */
+
+static mem_region mem_seek_region(mem_region vec[], uint count, uintptr_t addr);
+static FILE* mem_open_maps(void) ;
+static bool mem_read_region(mem_region reg, FILE* maps) ;
+static int mem_open_pagemap(void) ;
+static uint mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v,
+ uint count) ;
+static qfb_gen_t mem_form_region(mem_region reg) ;
+
+static void mem_sort_regions(mem_region* base, uint count) ;
+static int mem_cmp_region(const cvp* a, const cvp* b) ;
+static int mem_in_region(cvp k, const cvp* v) ;
+
+/*------------------------------------------------------------------------------
+ * Get an up to date map of all regions -- assumes lock held.
+ *
+ * Reads the /proc/self/maps file and fills in the mem_regions array, if
+ * required, will create a new mmaped area if runs out of space.
+ *
+ * Fill in and sort the mem_region_v.
+ *
+ * Empty the mem_region_h.
+ */
+static void
+mem_map_regions(void)
{
- char *memory;
+ FILE* maps ;
+ pid_t euid ;
- LOCK
- mlog_stat[type].c_strdup++;
- UNLOCK
+ euid = geteuid() ;
+ seteuid(getuid()) ;
- memory = zstrdup (type, str);
+ maps = mem_open_maps() ;
- mtype_log ("xstrdup", memory, file, line, type);
+ mem_region_count = 0 ; /* current count */
+ mem_region_heap_count = 0 ; /* current count, heap */
- return memory;
-}
-#endif
+ if (maps != NULL)
+ {
+ bool alloc = (mem_region_max_count == 0) ;
-/*==============================================================================
- * Showing memory allocation
+ while (1)
+ {
+ size_t map_size, vec_size, tot_size ;
+ mem_region_t reg[1] ;
+
+ if (alloc)
+ {
+ if (mem_region_map_base != NULL)
+ mem_munmap(mem_region_map_base, mem_region_map_size) ;
+
+ mem_region_max_count = ((mem_region_max_count + 64) | 0xFF) + 1 ;
+ } ;
+
+ map_size = ((mem_region_max_count * sizeof(mem_region_t)) | 0xFF) + 1;
+ vec_size = ((mem_region_max_count * sizeof(mem_region)) | 0xFF) + 1;
+
+ tot_size = map_size + (vec_size * 2) ;
+
+ if (alloc)
+ {
+ mem_region_map_base = mem_mmap(tot_size) ; /* zero fills */
+ mem_region_map_size = tot_size ;
+ }
+ else
+ {
+ assert(mem_region_map_size == tot_size) ;
+
+ memset(mem_region_map_base, 0, mem_region_map_size) ;
+ } ;
+
+ mem_regions = (mem_region)mem_region_map_base ;
+ mem_region_v = (mem_region*)((char*)mem_region_map_base + map_size) ;
+ mem_region_h = (mem_region*)((char*)mem_region_map_base + map_size
+ + vec_size) ;
+
+ while (mem_read_region(reg, maps))
+ {
+ if (mem_region_count < mem_region_max_count)
+ {
+ mem_regions[mem_region_count] = *reg ;
+ mem_region_v[mem_region_count] =
+ &mem_regions[mem_region_count] ;
+ } ;
+ ++mem_region_count ;
+ } ;
+
+ /* OK if had enough memory for complete map */
+ if (mem_region_count <= mem_region_max_count)
+ break ;
+
+ /* Page map did not fit -- alloc new memory according to what we
+ * now know is required.
+ */
+ mem_region_max_count = mem_region_count ;
+ mem_region_count = 0 ; /* reset count */
+
+ fseek(maps, 0, SEEK_SET) ;
+ alloc = true ;
+ } ;
+
+ fclose(maps) ;
+ } ;
+
+ seteuid(euid) ;
+
+ mem_sort_regions(mem_region_v, mem_region_count) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Find how much is in memory and how much is swapped out -- assumes lock held.
+ *
+ * Assumes the regions have just been updated.
*/
+static void
+mem_weigh_regions(void)
+{
+ enum { show = false } ;
-/* Looking up memory status from vty interface. */
-#include "vector.h"
-#include "vty.h"
-#include "command.h"
+ int pagemap ;
+ pid_t euid ;
+
+ euid = geteuid() ;
+ seteuid(getuid()) ;
+
+ pagemap = mem_open_pagemap() ;
+
+ if (pagemap >= 0)
+ {
+ enum { pm_entries = 4096 / sizeof(uint64_t) } ;
+
+ uint64_t pm[pm_entries] ;
+ uint i ;
+
+ for (i = 0 ; i < mem_region_count ; ++i)
+ {
+ mem_region reg ;
+ uintptr_t v ;
+ uint n, j ;
+
+ reg = mem_region_v[i] ;
+
+ v = reg->base ;
+ assert((v % mem_pagesize) == 0) ;
+
+ reg->present = 0 ;
+ reg->swapped = 0 ;
+
+ n = 0 ;
+ j = 0 ;
+ while (v < reg->limit)
+ {
+ uint64_t pme ;
+
+ if (j == n)
+ {
+ n = mem_read_pagemap(pm, pagemap, v, pm_entries) ;
+ j = 0 ;
+ if (n == 0)
+ break ;
+ } ;
+
+ pme = pm[j] ;
+
+ if (((pme & (3llu << 62)) != 0))
+ {
+ assert(mem_pagesize == (1llu << ((pme >> 55) & 0x3F))) ;
+
+ if ((pme & (1llu << 63)) != 0)
+ reg->present += mem_pagesize ;
+ else
+ reg->swapped += mem_pagesize ;
+ } ;
+
+ j += 1 ;
+ v += mem_pagesize ;
+ } ;
+
+ if (show)
+ fputs(mem_form_region(reg).str, stdout) ;
+ } ;
+ close(pagemap) ;
+ } ;
+
+ seteuid(euid) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Account for tracked memory object in the map of regions -- assume lock held.
+ */
static void
-log_memstats(int pri)
+mem_account_region(uintptr_t address, size_t size)
{
- struct mstat mst ;
- struct mlist *ml;
+ mem_region reg ;
- LOCK ;
- mst = mstat ;
- UNLOCK ;
+ reg = mem_seek_region(mem_region_h, mem_region_heap_count, address) ;
- for (ml = mlists; ml->list; ml++)
+ if (reg == NULL)
{
- struct memory_list *m;
+ reg = mem_seek_region(mem_region_v, mem_region_count, address) ;
+ assert(reg != NULL) ;
- zlog (NULL, pri, "Memory utilization in module %s:", ml->name);
- for (m = ml->list; m->index >= 0; m++)
+ mem_region_h[mem_region_heap_count++] = reg ;
+ mem_sort_regions(mem_region_h, mem_region_heap_count) ;
+ } ;
+
+ reg->count += 1 ;
+ reg->used += size ;
+ reg->overhead += mem_alloc_redtape + mem_alloc_padding(size) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Count maximum number of summary regions
+ */
+static uint
+mem_count_summary_regions(void)
+{
+ uint i, h ;
+
+ h = 0 ;
+ for (i = 0 ; i < mem_region_heap_count ; ++i)
+ {
+ if (mem_region_h[i]->sex == mrx_anon)
{
- unsigned long alloc = mst.mt[m->index].alloc ;
- if (m->index && alloc)
- zlog (NULL, pri, " %-30s: %10ld", m->format, alloc);
- }
- }
-}
+ mem_region_h[i]->sex = mrx_heap_extra ;
+ ++h ;
+ } ;
+ } ;
-void
-log_memstats_stderr (const char *prefix)
+ for (i = 0 ; i < mem_region_count ; ++i)
+ {
+ if (mem_region_v[i]->sex == mrx_heap_main)
+ ++h ;
+ } ;
+
+ return (mrx_sex_count - mrx_heap_count) + h ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fill in the region summary
+ *
+ * Constructs an individual entry for each mrx_heap_main or mrx_heap_extra, and
+ * summary entries for each non-heap sex.
+ *
+ * If there is only one heap entry -- which is most likely if memory_tracker is
+ * not enabled -- then does not construct a heap subtotal entry.
+ *
+ * Note that for heap entries, the count is the count is the number of known
+ * items allocated in that heap region -- this is only known with mem_tracker.
+ *
+ * For all other entries it is the number of regions. For the grand total,
+ * the count is returned zero -- cannot add count of regions to count of
+ * items and get anything sensible.
+ */
+static void
+mem_summarize_regions(mem_region_summary_t sum[], uint n)
{
- struct mstat mst ;
- struct mlist *ml;
- struct memory_list *m;
- int i;
- int j = 0;
+ uint i, h, nh, it ;
- LOCK ;
- mst = mstat ;
- UNLOCK ;
+ memset(sum, 0, sizeof(mem_region_summary_t) * n) ;
+ nh = n - (mrx_sex_count - mrx_heap_count) ;
+ h = 0 ;
- for (ml = mlists; ml->list; ml++)
+ for (i = 0 ; i < mem_region_count ; ++i)
{
- i = 0;
- for (m = ml->list; m->index >= 0; m++)
+ mem_region reg ;
+ uint is, ist ;
+
+ reg = mem_region_v[i] ;
+
+ is = reg->sex ;
+
+ if ((is == mrx_heap_main) || (is == mrx_heap_extra))
{
- unsigned long alloc = mst.mt[m->index].alloc ;
- if (m->index && alloc)
+ assert(h < nh) ;
+
+ is = mrx_heap + h++ ;
+ ist = n - 2 ;
+ sum[ist].sex = mrx_heap_total ;
+
+ if (memory_tracker)
{
- if (!i)
- fprintf (stderr,
- "%s: memstats: Current memory utilization in module %s:\n",
- prefix,
- ml->name);
- fprintf (stderr,
- "%s: memstats: %-30s: %10ld%s\n",
- prefix,
- m->format,
- alloc,
- alloc < 0 ? " (REPORT THIS BUG!)" : "");
- i = j = 1;
- }
+ sum[is].count = reg->count ;
+ sum[is].used = reg->used ;
+ sum[is].overhead = reg->overhead ;
+ sum[is].unused = (reg->limit - reg->base)
+ - (reg->used + reg->overhead) ;
+
+ sum[ist].count += sum[is].count ;
+ sum[ist].used += sum[is].used;
+ sum[ist].overhead += sum[is].overhead ;
+ sum[ist].unused += sum[is].unused ;
+ } ;
+
+ if ((mem_alloc_first >= reg->base) && (mem_alloc_first <= reg->limit))
+ {
+ sum[is].data = mem_alloc_first - reg->base ;
+ sum[ist].data = sum[is].data ;
+ } ;
}
+ else
+ {
+ ist = mrx_rest_total ;
+ sum[ist].sex = mrx_rest_total ;
+
+ sum[is].count += 1 ;
+ sum[ist].count += 1 ;
+ } ;
+
+ sum[is].sex = reg->sex ;
+ sum[is].size += (reg->limit - reg->base) ;
+ sum[is].present += reg->present ;
+ sum[is].swapped += reg->swapped ;
+
+ sum[ist].size += (reg->limit - reg->base) ;
+ sum[ist].present += reg->present ;
+ sum[ist].swapped += reg->swapped ;
+ } ;
+
+ /* Construct the grand totals by copying in the heap subtotal and then
+ * adding the rest. Setting the count to be the count of regions.
+ */
+
+ it = n - 1 ;
+ sum[it] = sum[n - 2] ; /* copy heap subtotals to total */
+ sum[it].sex = mrx_grand_total ;
+
+ if (nh == 1) /* wipe out heap subtotals if only one */
+ memset(&sum[n - 2], 0, sizeof(mem_region_summary_t)) ;
+
+ sum[it].count = 0 ; /* grand total count is meaningless */
+ sum[it].size += sum[mrx_rest_total].size ;
+ sum[it].present += sum[mrx_rest_total].present ;
+ sum[it].swapped += sum[mrx_rest_total].swapped ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Seek region for given address amongst the known regions.
+ *
+ * Returns known region, or NULL.
+ */
+static mem_region
+mem_seek_region(mem_region vec[], uint count, uintptr_t addr)
+{
+ mem_region* p_reg ;
+
+ if (count <= 4)
+ {
+ uint i ;
+ for (i = 0 ; i < count ; ++i)
+ {
+ mem_region reg = vec[i] ;
+ if ((reg->base <= addr) && (addr < reg->limit))
+ return reg ;
+ } ;
+
+ return NULL ;
+ } ;
+
+ typedef int bsearch_cmp(const void*, const void*) ;
+ p_reg = bsearch(&addr, vec, count, sizeof(mem_region),
+ (bsearch_cmp*)mem_in_region) ;
+ return (p_reg != NULL) ? *p_reg : NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open /proc/self/maps if we can
+ */
+static FILE*
+mem_open_maps(void)
+{
+ FILE* maps = fopen("/proc/self/maps", "r") ;
+
+ if (maps == NULL)
+ zlog_err("%s: failed to open '/proc/self/maps': %s",
+ __func__, errtoa(errno, 0).str) ;
+ return maps ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read next entry from /proc/<pid>/maps
+ *
+ * Each entry takes the form:
+ *
+ * base limit perm offset dev inode name
+ * 00400000-0040b000 r-xp 00000000 08:03 262262 /bin/cat
+ *
+ * It is assumed that all values are hex except the inode.
+ *
+ * Zeroises the mem_region object and sets:
+ *
+ * base - base of region
+ * limit - last address + 1 of region
+ * flags - as permissions and the name of the region
+ * offset - offset within device (if any)
+ * dev_major
+ * dev_minor
+ * inode
+ * name - which may be [heap], [stack] etc. or path to mapped file.
+ */
+static bool
+mem_read_region(mem_region reg, FILE* maps)
+{
+ int ch, i ;
+ size_t a, b ;
+
+ memset(reg, 0, sizeof(mem_region_t)) ;
+
+ /* Chew up leading white-space -- spaces/tabs/newlines etc. */
+ while (isspace(ch = fgetc(maps))) ;
+ if (ch == EOF)
+ return false ;
+ ungetc(ch, maps) ;
+
+ /* Base and limit */
+ i = fscanf(maps, "%zx-%zx", &a, &b) ;
+
+ if ((i != 2) || (a > b))
+ {
+ zlog_err("%s: invalid base or limit (%d)", __func__, i) ;
+ return false ;
+ } ;
+
+ confirm(sizeof(size_t) >= sizeof(uintptr_t)) ;
+
+ reg->base = a ;
+ reg->limit = b ;
+
+ /* Flags */
+ while (isblank(ch = fgetc(maps))) ;
+
+ do
+ {
+ switch (ch)
+ {
+ case '-':
+ break ;
+
+ case 'r':
+ reg->flags |= mrf_read ;
+ break ;
+
+ case 'w':
+ reg->flags |= mrf_write ;
+ break ;
+
+ case 'x':
+ reg->flags |= mrf_execute ;
+ break ;
+
+ case 'p':
+ reg->flags |= mrf_private ;
+ break ;
+
+ case 's':
+ reg->flags |= mrf_shared ;
+ break ;
+
+ default:
+ zlog_err("%s: invalid permission '%c'", __func__, ch) ;
+ return false ;
+ } ;
}
+ while (!isblank(ch = fgetc(maps))) ;
- if (j)
- fprintf (stderr,
- "%s: memstats: NOTE: If configuration exists, utilization may be "
- "expected.\n",
- prefix);
+ /* offset, device and inode */
+ i = fscanf(maps, "%zx %x:%x %llu", &reg->offset,
+ &reg->dev_major, &reg->dev_minor,
+ &reg->inode) ;
+ if (i != 4)
+ {
+ zlog_err("%s: invalid offset, device or inode (%d)", __func__, i) ;
+ return false ;
+ } ;
+
+ /* Skip spaces and then pick up the name (if any) */
+ while (isblank(ch = fgetc(maps))) ;
+
+ i = 0 ;
+ while ((ch != '\n') && (ch != EOF))
+ {
+ if (i < (mem_region_max_name_len + 1))
+ reg->name[i++] = ch ;
+
+ ch = fgetc(maps) ;
+ } ;
+
+ if (i == (mem_region_max_name_len + 1))
+ {
+ i -= 4 ;
+ while (i < mem_region_max_name_len)
+ reg->name[i++] = '.' ;
+ } ;
+
+ reg->name[i] = '\0' ;
+
+ /* Establish type */
+ if (i == 0)
+ reg->type = mrt_anon ;
+ else if (reg->name[0] == '[')
+ {
+ if (strcmp(reg->name, "[heap]") == 0)
+ reg->type = mrt_heap ;
+ else if (strcmp(reg->name, "[stack]") == 0)
+ reg->type = mrt_stack ;
+ else if (strcmp(reg->name, "[vdso]") == 0)
+ reg->type = mrt_vdso ;
+ else if (strcmp(reg->name, "[vsyscall]") == 0)
+ reg->type = mrt_vsyscall ;
+ else
+ reg->type = mrt_other ;
+ }
else
- fprintf (stderr,
- "%s: memstats: No remaining tracked memory utilization.\n",
- prefix);
-}
+ reg->type = mrt_named ;
+
+ /* Establish sex
+ *
+ * Note that mrx_heap_extra is only discovered if/when the mem_tracker data
+ * is scanned to see if anything has been malloced in the region.
+ */
+ reg->sex = mrx_exotic ;
+
+ switch (reg->flags & (mrf_read | mrf_write | mrf_execute))
+ {
+ case mrf_read | 0 | mrf_execute:
+ if ((reg->type == mrt_named) || (reg->type == mrt_vdso)
+ || (reg->type == mrt_vsyscall))
+ reg->sex = mrx_code ;
+ break ;
+
+ case mrf_read:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_const ;
+ break ;
+
+ case mrf_read | mrf_write:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_data ;
+ else if (reg->type == mrt_heap)
+ reg->sex = mrx_heap_main ;
+ else if (reg->type == mrt_anon)
+ reg->sex = mrx_anon ;
+ else if (reg->type == mrt_stack)
+ reg->sex = mrx_stack ;
+ break ;
+
+ case 0:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_reserved ;
+ else if (reg->type == mrt_anon)
+ reg->sex = mrx_gap ;
+ break ;
+
+ default: /* mrf_read | mrf_write | mrf_execute
+ 0 | mrf_write | mrf_execute
+ 0 | mrf_write | 0
+ 0 | 0 | mrf_execute */
+ break ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open /proc/self/pagemap if we can
+ */
+static int
+mem_open_pagemap(void)
+{
+ int pagemap = open("/proc/self/pagemap", O_RDONLY) ;
+
+ if (pagemap < 0)
+ zlog_err("%s: failed to open '/proc/self/pagemap': %s",
+ __func__, errtoa(errno, 0).str) ;
+ return pagemap ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read set of entries from /proc/self/pagemap.
+ */
+static uint
+mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v, uint count)
+{
+ off_t off ;
+ int n ;
+
+ off = (v / mem_pagesize) * sizeof(uint64_t) ;
+ if (lseek(pagemap, off, SEEK_SET) < 0)
+ {
+ zlog_err("%s: failed to seek to %llX in '/proc/self/pagemap': %s",
+ __func__, (long long)off, errtoa(errno, 0).str) ;
+ return 0 ;
+ } ;
+
+ n = read(pagemap, pm, sizeof(uint64_t) * count) ;
+
+ if (n < 0)
+ {
+ zlog_err("%s: failed reading at %llX in '/proc/self/pagemap': %s",
+ __func__, (long long)off, errtoa(errno, 0).str) ;
+ n = 0 ;
+ } ;
+
+ assert((n % sizeof(uint64_t)) == 0) ;
+
+ return n / sizeof(uint64_t) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Construct string describing a memory region
+ */
+static qfb_gen_t
+mem_form_region(mem_region reg)
+{
+ QFB_QFS(qfb_gen, qfs) ;
+ size_t a, b, s ;
+ int ch ;
+
+ confirm(sizeof(size_t) >= sizeof(uintptr_t)) ;
+
+ a = reg->base >> 32 ;
+ b = reg->base & 0xFFFFFFFF ;
+ s = reg->limit - reg->base ;
+
+ qfs_printf(qfs, "%08zX_%08zX %010zX %010zX %010zX ", a, b, s,
+ reg->present, reg->swapped) ;
+
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_read) != 0) ? 'r' : '-') ;
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_write) != 0) ? 'w' : '-') ;
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_execute) != 0) ? 'x' : '-') ;
+
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_private) != 0)
+ ? ( ((reg->flags & mrf_shared) != 0) ? 'Q' : 'p' )
+ : ( ((reg->flags & mrf_shared) != 0) ? 's' : '-' )) ;
+
+ ch = (reg->type < mrt_type_count) ? mrf_type_chars[reg->type] : '\0' ;
+ if (ch == '\0')
+ ch = '?' ;
+
+ qfs_printf(qfs, " %08zX %02X:%02X %8lld %c %s\n", reg->offset,
+ reg->dev_major, reg->dev_minor,
+ reg->inode, ch, reg->name) ;
+ qfs_term(qfs) ;
+
+ return qfb_gen ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Sort given vector of pointers to regions, and then make sure they are
+ * in order (!) and that the limit >= base for each one.
+ */
static void
-show_memory_type_vty (struct vty *vty, const char* name,
- struct mem_tracker* mt, long int alloc, int sep)
+mem_sort_regions(mem_region* base, uint count)
{
- if (sep)
- vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ;
+ uint i ;
+ uintptr_t ll ;
- vty_out (vty, "%-30s:", name) ;
-#ifdef MEMORY_TRACKER
- show_memory_tracker_detail(vty, mt, alloc) ;
-#else
- vty_out (vty, " %10ld", alloc) ;
-#endif
- vty_out (vty, "%s", VTY_NEWLINE);
+ typedef int qsort_cmp(const void*, const void*) ;
+ qsort(base, count, sizeof(mem_region), (qsort_cmp*)mem_cmp_region) ;
+
+ ll = (uintptr_t)NULL ;
+ for (i = 0 ; i < count ; ++i)
+ {
+ assert(base[i]->base >= ll) ;
+ ll = base[i]->limit ;
+ assert(base[i]->base <= ll) ;
+ } ;
} ;
+/*------------------------------------------------------------------------------
+ * Compare two regions for qsort
+ *
+ * Don't think it likely, but if bases are equal, sorts by limit. Allows for
+ * empty region(s) ?
+ */
static int
-show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml,
- int needsep)
+mem_cmp_region(const cvp* a, const cvp* b)
{
- int notempty = 0 ;
+ mem_region_c ra = *(const mem_region_c*)a ;
+ mem_region_c rb = *(const mem_region_c*)b ;
- long int alloc ;
+ if (ra->base != rb->base)
+ return (ra->base < rb->base) ? -1 : +1 ;
- struct mstat mst ;
- struct mem_tracker mem_tot ;
- struct mem_tracker mem_one ;
- struct mem_tracker* mt ;
+ if (ra->limit != rb->limit)
+ return (ra->limit < rb->limit) ? -1 : +1 ;
-#ifdef MEMORY_TRACKER
- struct mem_type_tracker mem_tt ;
-#endif
+ return 0 ;
+} ;
- LOCK ;
- mst = mstat ;
-#ifdef MEMORY_TRACKER
- mem_tt = mem_type_tracker ;
-#endif
- UNLOCK ;
+/*------------------------------------------------------------------------------
+ * Compare two regions for bsearch
+ */
+static int
+mem_in_region(cvp k, const cvp* v)
+{
+ const uintptr_t kv = *(const uintptr_t*)k ;
+ const mem_region_c rv = *(const mem_region_c*)v ;
+
+ if (rv->base > kv)
+ return -1 ;
- mem_tracker_zeroise(&mem_tot) ;
- mem_tracker_zeroise(&mem_one) ;
+ if (kv >= rv->limit)
+ return +1 ;
- if ((m == NULL) && (ml != NULL))
- m = (ml++)->list ;
+ return 0 ;
+} ;
- while (m != NULL)
+/*==============================================================================
+ * Manipulation of the tracking data structures.
+ */
+static void mem_mt_make_bases(void) ;
+
+/*------------------------------------------------------------------------------
+ * Set memory type in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_type(mem_descriptor md, enum MTYPE mtype)
+{
+ uint32_t t_ms ;
+ uint32_t t_ls ;
+
+ t_ms = mtype >> md_size_type_bits ;
+ t_ls = mtype ;
+
+ t_ms = (t_ms & md_next_type_mask) << md_next_bits ;
+ t_ls = (t_ls & md_size_type_mask) << md_size_bits ;
+
+ md->next = (md->next & md_next_mask) | t_ms ;
+ md->size = (md->size & md_size_mask) | t_ls ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the next index in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_next(mem_descriptor md, md_index_t next)
+{
+ md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the item size in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_size(mem_descriptor md, size_t size)
+{
+ md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the item type from the given mem_descriptor
+ */
+inline static uint8_t
+mem_mt_type(mem_descriptor md)
+{
+ return ( (md->next >> (md_next_bits - md_size_type_bits))
+ & (md_next_type_mask << md_size_type_bits) )
+ | ( (md->size >> md_size_bits) & md_size_type_mask ) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the next index from the given mem_descriptor
+ */
+inline static md_index_t
+mem_mt_next(mem_descriptor md)
+{
+ return md->next & md_next_mask ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the item size from the given mem_descriptor
+ */
+inline static size_t
+mem_mt_size(mem_descriptor md)
+{
+ return md->size & md_size_mask ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map item index to mem_descriptor
+ */
+inline static mem_descriptor
+mem_mt_ptr(md_index_t mdi)
+{
+ mem_descriptor page ;
+
+ if (mdi == 0)
+ return NULL ;
+
+ page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ;
+ passert(page != NULL) ;
+ return page + (mdi & md_i_index_mask) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map item address to address of base entry in the hash table.
+ */
+inline static md_index_t*
+mem_mt_base(void* address)
+{
+ if (mem_bases == NULL)
+ mem_mt_make_bases() ;
+
+ return mem_bases + ((uintptr_t)address % mem_base_count) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make new set of bases for the hash table -- if a set already exists,
+ * rearrange the hash table and then discard the old bases.
+ */
+static void
+mem_mt_make_bases(void)
+{
+ md_index_t* bases_was = mem_bases ;
+ uint32_t count_was = mem_base_count ;
+
+ size_t size_was = count_was * sizeof(md_index_t) ;
+ size_t size_now ;
+
+ mem_base_count += 256 * 1024 ;
+ mem_base_count |= 1 ;
+
+ size_now = mem_base_count * sizeof(md_index_t) ;
+
+ mem_bases = mem_mmap(size_now) ; /* zero fills */
+
+ mem_mt_data.tracker_overhead += size_now - size_was ;
+
+ if (bases_was == NULL)
+ passert(count_was == 0) ;
+ else
{
- if (m->index <= 0)
+ md_index_t* base = bases_was ;
+ md_index_t* new_base ;
+ md_index_t this ;
+ md_index_t next ;
+ mem_descriptor md ;
+
+ while (count_was)
{
- needsep = notempty ;
- if (m->index < 0)
+ next = *base++ ;
+ while (next != 0)
{
- if (ml == NULL)
- m = NULL ;
- else
- m = (ml++)->list ;
- }
+ this = next ;
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ new_base = mem_mt_base(md->addr) ;
+ mem_mt_set_next(md, *new_base) ;
+ *new_base = this ;
+ } ;
+ --count_was ;
+ } ;
+
+ mem_munmap(bases_was, size_was) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make new page full of empty memory descriptors, and place them all on the
+ * free descriptors list.
+ */
+static void
+mem_mt_make_descriptors(void)
+{
+ mem_descriptor md ;
+ md_index_t mdi ;
+ size_t desc_size ;
+ size_t total_size ;
+
+ mdi = mem_next_index ;
+ passert(mdi < md_index_max) ;
+
+ desc_size = md_i_index_count * sizeof(mem_descriptor_t) ;
+ total_size = desc_size ;
+
+ if (mem_page_table == NULL)
+ {
+ desc_size = (desc_size | 0xFF) + 1 ;
+ total_size = desc_size + (md_page_count * sizeof(mem_descriptor)) ;
+ } ;
+
+ mem_mt_data.tracker_overhead += total_size ;
+
+ mem_free_descriptors = mem_mmap(total_size) ; /* zero fills */
+
+ if (mem_page_table == NULL)
+ mem_page_table = (mem_descriptor*)((char*)mem_free_descriptors + desc_size);
+
+ mem_page_table[(mdi >> md_i_index_bits) & md_page_mask]
+ = mem_free_descriptors ;
+ mem_next_index += md_i_index_count ;
+
+ if (mdi == 0)
+ {
+ ++mem_free_descriptors ; /* don't use index == 0 */
+ ++mdi ;
+ } ;
+
+ md = mem_free_descriptors ;
+ while (mdi < mem_next_index)
+ {
+ md->addr = md + 1 ; /* point at next entry */
+ md->next = mdi ; /* set to point at self */
+ ++md ;
+ ++mdi ;
+ } ;
+ (md-1)->addr = NULL ; /* set end of list */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Walk the memory_tracker descriptors, to account for all tracked memory
+ * by region -- assumes lock held.
+ */
+static void
+mem_mt_region_accounting(void)
+{
+ uint i ;
+
+ for (i = 0 ; i < mem_base_count ; ++i)
+ {
+ md_index_t this ;
+ this = mem_bases[i] ;
+ while (this != 0)
+ {
+ mem_descriptor md ;
+ md = mem_mt_ptr(this) ;
+ mem_account_region((uintptr_t)md->addr, mem_mt_size(md)) ;
+ this = mem_mt_next(md) ;
+ } ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Tracking of malloc/realloc/free -- calloc treated as malloc.
+ */
+
+/*------------------------------------------------------------------------------
+ * memory_tracker malloc() extension.
+ */
+inline static void
+mem_mt_malloc(mtype_t mtype, void* address, size_t size, const char* name)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md ;
+ md_index_t mdi ;
+
+ passert(size <= md_size_max) ;
+ passert(mtype < MTYPE_MAX) ;
+
+ if (mem_free_descriptors == NULL)
+ mem_mt_make_descriptors() ;
+
+ md = mem_free_descriptors ;
+ mem_free_descriptors = md->addr ;
+ mdi = md->next ;
+
+ if (mem_mt_data.tot->item_count >= (mem_base_count * 4))
+ mem_mt_make_bases() ;
+
+ base = mem_mt_base(address) ;
+
+ md->addr = address ;
+ md->name = name ;
+ md->size = size ;
+ md->next = *base ;
+ mem_mt_set_type(md, mtype) ;
+
+ *base = mdi ;
+
+ /* Keep track of total number of all items and the total size of same,
+ * and the peak value of those.
+ */
+ mem_mt_data.tot->item_count += 1 ;
+ mem_mt_data.tot->total_size += size ;
+
+ if (mem_mt_data.tot->peak_item_count < mem_mt_data.tot->item_count)
+ mem_mt_data.tot->peak_item_count = mem_mt_data.tot->item_count ;
+
+ if (mem_mt_data.tot->peak_total_size < mem_mt_data.tot->total_size)
+ mem_mt_data.tot->peak_total_size = mem_mt_data.tot->total_size ;
+
+ /* Now all data for items of this type.
+ */
+ typ = &(mem_mt_data.typ[mtype]) ;
+
+ typ->malloc_count += 1 ;
+ typ->item_count += 1 ;
+ typ->total_size += size ;
+
+ if (typ->peak_item_count < typ->item_count)
+ typ->peak_item_count = typ->item_count ;
+
+ if (typ->peak_total_size < typ->total_size)
+ typ->peak_total_size = typ->total_size ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker free() extension.
+ */
+inline static void
+mem_mt_free(mtype_t mtype, void* address)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md, prev_md ;
+ md_index_t this, next ;
+
+ passert(mtype < MTYPE_MAX) ;
+
+ if (address == NULL)
+ return ;
+
+ base = mem_mt_base(address) ;
+
+ prev_md = NULL ;
+ this = *base ;
+ while (this != 0)
+ {
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ if (md->addr == address)
+ {
+ size_t size = mem_mt_size(md) ;
+
+ if (mem_mt_type(md) != mtype)
+ zabort("memory type mismatch in free") ;
+
+ mem_mt_data.tot->item_count -= 1 ;
+ mem_mt_data.tot->total_size -= size ;
+
+ typ = &(mem_mt_data.typ[mtype]) ;
+
+ typ->free_count += 1 ;
+ typ->item_count -= 1 ;
+ typ->total_size -= size ;
+
+ if (prev_md == NULL)
+ *base = next ;
else
- ++m ;
+ mem_mt_set_next(prev_md, next) ;
+
+ md->addr = mem_free_descriptors ;
+ mem_free_descriptors = md ;
+ md->next = this ;
+
+ return ;
}
else
{
- alloc = mst.mt[m->index].alloc ;
-#ifdef MEMORY_TRACKER
- mt = &(mem_tt.mt[m->index]) ;
-#else
- mt = &mem_one ;
- mt->tracked_count = alloc ;
-#endif
+ prev_md = md ;
+ this = next ;
+ } ;
+ } ;
+
+ zabort("Failed to find memory being freed") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker realloc() extension.
+ */
+inline static void
+mem_mt_realloc(mtype_t mtype, void* old_address, void* new_address,
+ size_t new_size, const char* name)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md, prev_md ;
+ md_index_t this, next ;
+
+ if (old_address == NULL)
+ {
+ mem_mt_malloc(mtype, new_address, new_size, name) ;
+ return ;
+ } ;
+
+ passert(new_size <= md_size_max) ;
+ passert(mtype < MTYPE_MAX) ;
+
+ base = mem_mt_base(old_address) ;
+
+ prev_md = NULL ;
+ this = *base ;
+ while (this != 0)
+ {
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ if (md->addr == old_address)
+ {
+ size_t old_size = mem_mt_size(md) ;
+
+ if (mem_mt_type(md) != mtype)
+ zabort("memory type mismatch in realloc") ;
+
+ mem_mt_data.tot->total_size += new_size - old_size ;
+
+ if (mem_mt_data.tot->peak_total_size
+ < mem_mt_data.tot->total_size)
+ mem_mt_data.tot->peak_total_size
+ = mem_mt_data.tot->total_size ;
- mem_tot.malloc_count += mt->malloc_count ;
- mem_tot.free_count += mt->free_count ;
- mem_tot.realloc_count += mt->realloc_count ;
- mem_tot.tracked_count += mt->tracked_count ;
- mem_tot.tracked_max_count += mt->tracked_max_count ;
- mem_tot.tracked_size += mt->tracked_size ;
- mem_tot.tracked_max_size += mt->tracked_max_size ;
+ typ = &(mem_mt_data.typ[mtype]) ;
- if (alloc || mt->tracked_count)
+ typ->realloc_count += 1 ;
+ typ->total_size += new_size - old_size ;
+
+ if (typ->peak_total_size < typ->total_size)
+ typ->peak_total_size = typ->total_size ;
+
+ md->name = name ;
+ mem_mt_set_size(md, new_size) ;
+
+ if (old_address == new_address)
+ return ;
+
+ if (prev_md == NULL)
+ *base = next ;
+ else
+ mem_mt_set_next(prev_md, next) ;
+
+ base = mem_mt_base(new_address) ;
+ mem_mt_set_next(md, *base) ;
+ *base = this ;
+
+ md->addr = new_address ;
+
+ return ;
+ }
+ else
+ {
+ prev_md = md ;
+ this = next ;
+ } ;
+ } ;
+
+ zabort("Failed to find memory being realloced") ;
+} ;
+
+/*==============================================================================
+ * Memory Tracker Display
+ */
+
+/*------------------------------------------------------------------------------
+ * Get copy of current statistics in mem_tracker_data form.
+ *
+ * This acquires the lock (if required), copies the data and releases the
+ * lock.
+ *
+ * If not mem_tracker enabled, then takes the standard mstat stuff and
+ * creates a (largely empty) mem_tracker data set.
+ *
+ * If mem_tracker enabled, takes the mem_tracker_data and the mstat stuff.
+ * Then checks that the counts are the same, and constructs the totals for:
+ *
+ * malloc_count
+ * realloc_count
+ * free_count
+ */
+static void
+mem_mt_get_stats(mem_tracker_data mtd, bool locked)
+{
+ mem_stats_t mst ;
+ mtype_t mtype ;
+ ulong item_count ;
+ size_t total_size ;
+
+ if (!locked)
+ LOCK ;
+
+ mst = mstats ;
+ if (memory_tracker)
+ *mtd = mem_mt_data ;
+
+ UNLOCK ;
+
+ if (!memory_tracker)
+ {
+ memset(mtd, 0, sizeof(mem_tracker_data_t)) ;
+
+ item_count = 0 ;
+
+ for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype)
+ item_count += (mtd->typ[mtype].item_count = mst.alloc[mtype]) ;
+
+ mtd->tot->item_count = item_count ;
+ } ;
+
+ mtd->tot->malloc_count = 0 ;
+ mtd->tot->realloc_count = 0 ;
+ mtd->tot->free_count = 0 ;
+
+ item_count = 0 ;
+ total_size = 0 ;
+
+ for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype)
+ {
+ mtd->tot->malloc_count += mtd->typ[mtype].malloc_count ;
+ mtd->tot->realloc_count += mtd->typ[mtype].realloc_count ;
+ mtd->tot->free_count += mtd->typ[mtype].free_count ;
+
+ item_count += mtd->typ[mtype].item_count ;
+ total_size += mtd->typ[mtype].total_size ;
+ } ;
+
+ assert(item_count == mtd->tot->item_count) ;
+
+ if (memory_tracker)
+ {
+ assert(total_size == mtd->tot->total_size) ;
+ assert(item_count == mtd->tot->malloc_count - mtd->tot->free_count) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Creation of "friendly" counts and sizes display.
+ */
+
+enum { scale_max = 4 } ;
+
+static const char* scale_d_tags [] =
+{
+ [0] = " " ,
+ [1] = "'k",
+ [2] = "'m",
+ [3] = "'g",
+ [4] = "'t",
+} ;
+
+static const char* scale_b_tags [] =
+{
+ [0] = " " ,
+ [1] = "'K",
+ [2] = "'M",
+ [3] = "'G",
+ [4] = "'T",
+} ;
+
+static const ulong p10 [] =
+{
+ [ 0] = 1ul,
+ [ 1] = 10ul,
+ [ 2] = 100ul,
+ [ 3] = 1000ul,
+ [ 4] = 10000ul,
+ [ 5] = 100000ul,
+ [ 6] = 1000000ul,
+ [ 7] = 10000000ul,
+ [ 8] = 100000000ul,
+ [ 9] = 1000000000ul,
+ [10] = 10000000000ul,
+ [11] = 100000000000ul,
+ [12] = 1000000000000ul,
+ [13] = 10000000000000ul,
+ [14] = 100000000000000ul,
+ [15] = 1000000000000000ul /* 10^((4*3) + 3) */
+} ;
+
+static const ulong q10 [] =
+{
+ [ 0] = 1ul / 2,
+ [ 1] = 10ul / 2,
+ [ 2] = 100ul / 2,
+ [ 3] = 1000ul / 2,
+ [ 4] = 10000ul / 2,
+ [ 5] = 100000ul / 2,
+ [ 6] = 1000000ul / 2,
+ [ 7] = 10000000ul / 2,
+ [ 8] = 100000000ul / 2,
+ [ 9] = 1000000000ul / 2,
+ [10] = 10000000000ul / 2,
+ [11] = 100000000000ul / 2,
+ [12] = 1000000000000ul / 2,
+ [13] = 10000000000000ul / 2,
+ [14] = 100000000000000ul / 2,
+ [15] = 1000000000000000ul / 2, /* 10^((4*3) + 3) / 2 */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Form string for number, with commas and "d" decimal digits, followed by
+ * the given tag -- where d = 0..4
+ *
+ * So: val=1234567, d=2, tag="k" -> "12,345.67k".
+ * val=1234, d=0, tag="" -> "1,234"
+ */
+static num_str_t
+mem_form_commas(ulong v, const char* tag, uint d, uint f)
+{
+ QFB_QFS(num_str, qfs) ;
+
+ if (d == 0)
+ qfs_unsigned(qfs, v, pf_commas, 0, 0) ;
+ else
+ {
+ qfs_unsigned(qfs, v, 0, 0, 0) ;
+ qfs_append(qfs, ".") ;
+ qfs_unsigned(qfs, f, pf_zeros, d, 0) ;
+ } ;
+
+ qfs_append(qfs, tag) ;
+ qfs_term(qfs) ;
+
+ return num_str ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Form count with 4 significant digits, up to multiples of trillions.
+ */
+static num_str_t
+mem_form_count(ulong val)
+{
+ uint i, d ;
+ ldiv_t r ;
+
+ i = 0 ;
+ d = 0 ;
+
+ while ((val >= p10[i + 4]) && (i < (scale_max * 3)))
+ i += 3 ;
+
+ if (i == 0)
+ {
+ r.quot = val ;
+ r.rem = 0 ;
+ }
+ else
+ {
+ if (val < p10[i + 1])
+ d = 3 ;
+ else if (val < p10[i + 2])
+ d = 2 ;
+ else if (val < p10[i + 3])
+ d = 1 ;
+
+ /* Scale down to required number of decimals and round.
+ *
+ * If is thousands, then i = 3, if value = 10,000 (smallest possible)
+ * then d == 2. So divide by 5 (q10[3 - 2]) to make ls bit the rounding
+ * bit, add one and shift off the rounding bit.
+ *
+ * The result should be 1000..9999, unless value is greater than our
+ * ability to scale, or has rounded up one decade.
+ */
+ val = ((val / q10[i - d]) + 1) >> 1 ;
+
+ qassert(val >= 1000) ;
+
+ if (val > 9999)
+ {
+ if (d == 0)
{
- show_memory_type_vty(vty, m->format, mt, alloc, needsep) ;
- needsep = 0 ;
- notempty = 1 ;
+ if (i < (scale_max * 3))
+ {
+ qassert(val == 10000) ;
+
+ val = 1000 ;
+ d = 2 ;
+ i += 3 ;
+ } ;
+ }
+ else
+ {
+ qassert(val == 10000) ;
+
+ val = 1000 ;
+ d -= 1 ;
} ;
+ } ;
- ++m ;
- } ;
+ r = ldiv(val, p10[d]) ;
} ;
- show_memory_type_vty(vty, "Total", &mem_tot, mem_tot.tracked_count, notempty);
+ return mem_form_commas(r.quot, scale_d_tags[i / 3], d, r.rem) ;
+} ;
- return 1 ;
+/*------------------------------------------------------------------------------
+ * Form count with 4 significant digits, up to multiples of Terabytes.
+ *
+ * So: 1023
+ */
+static num_str_t
+mem_form_byte_count(ulong val)
+{
+ ulong v ;
+ uint i, d, f ;
+
+ i = 0 ;
+ d = 0 ;
+ v = val ;
+ f = 0 ;
+
+ while ((v >= 1024) && (i < scale_max))
+ {
+ v >>= 10 ; /* find power of 1024 scale */
+ i += 1 ;
+ } ;
+
+ if (i > 0)
+ {
+ if (v < 10)
+ d = 3 ; /* number of decimals expected */
+ else if (v < 100)
+ d = 2 ;
+ else if (v < 1000)
+ d = 1 ;
+
+ /* Scale up to the required number of decimals, shift down so that
+ * only ms bit of fraction is left, round and shift off rounding bit.
+ *
+ * If d != 0, then will scale up by 10, 100 or 1000. The largest value
+ * to be scaled up is 1000 * (2^(scale_max * 10)) - 1, which will be
+ * multiplied by 10. The maximum scaled value is, therefore, bound to
+ * be < 2^(((scale_max + 1) * 10) + 4)
+ */
+ confirm((sizeof(ulong) * 8) >= (((scale_max + 1) * 10) + 4)) ;
+
+ v = (((val * p10[d]) >> ((i * 10) - 1)) + 1) >> 1 ;
+
+ qassert(v >= 1000) ;
+
+ if (d == 0)
+ {
+ if ((v == 1024) && (i < scale_max))
+ {
+ v = 1000 ; /* rounded up to next power of 1024 */
+ d = 3 ;
+ i += 1 ;
+ }
+ }
+ else
+ {
+ if (v >= 10000)
+ {
+ qassert(v == 10000) ;
+
+ v = 1000 ; /* rounded up to one less decimals */
+ d -= 1 ;
+ } ;
+ } ;
+
+ f = v % p10[d] ;
+ v = v / p10[d] ;
+ } ;
+
+ return mem_form_commas(v, scale_b_tags[i], d, f) ;
} ;
+/*------------------------------------------------------------------------------
+ * Show summary of memory_tracker on vty.
+ */
+static void
+show_memory_tracker_summary(vty vty, mem_tracker_data mtd)
+{
+ vty_out (vty, "Memory Tracker Statistics:\n");
+ vty_out (vty, " Current memory allocated: %13s\n",
+ mem_form_byte_count(mtd->tot->total_size).str) ;
+ vty_out (vty, " Current allocated objects: %'11lu\n",
+ mtd->tot->item_count);
+ vty_out (vty, " Maximum memory allocated: %13s\n",
+ mem_form_byte_count(mtd->tot->peak_total_size).str) ;
+ vty_out (vty, " Maximum allocated objects: %'11lu\n",
+ mtd->tot->peak_item_count) ;
+ vty_out (vty, " malloc/calloc call count: %13s\n",
+ mem_form_count (mtd->tot->malloc_count).str);
+ vty_out (vty, " realloc_call_count: %13s\n",
+ mem_form_count (mtd->tot->realloc_count).str);
+ vty_out (vty, " free call count: %13s\n",
+ mem_form_count (mtd->tot->free_count).str);
+ vty_out (vty, " Memory Tracker overhead: %13s\n",
+ mem_form_byte_count(mtd->tracker_overhead).str);
+} ;
+
+/*==============================================================================
+ * Showing memory allocation
+ */
#ifdef HAVE_MALLINFO
-static int
-show_memory_mallinfo (struct vty *vty)
+static void
+show_memory_mallinfo (struct vty *vty, mem_tracker_data mtd)
{
- struct mallinfo minfo = mallinfo();
+ struct mallinfo minfo ;
char buf[MTYPE_MEMSTR_LEN];
+ LOCK ;
+
+ minfo = mallinfo() ;
+ mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data & UNLOCK */
+
+
vty_out (vty, "System allocator statistics:%s", VTY_NEWLINE);
vty_out (vty, " Total heap allocated: %s%s",
mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.arena),
@@ -583,22 +2687,353 @@ show_memory_mallinfo (struct vty *vty)
vty_out (vty, " Free ordinary blocks: %s%s",
mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.fordblks),
VTY_NEWLINE);
- vty_out (vty, " Ordinary blocks: %ld%s",
- (unsigned long)minfo.ordblks,
+ vty_out (vty, " Ordinary blocks: %lu%s",
+ (ulong)minfo.ordblks,
VTY_NEWLINE);
- vty_out (vty, " Small blocks: %ld%s",
- (unsigned long)minfo.smblks,
+ vty_out (vty, " Small blocks: %lu%s",
+ (ulong)minfo.smblks,
VTY_NEWLINE);
- vty_out (vty, " Holding blocks: %ld%s",
- (unsigned long)minfo.hblks,
+ vty_out (vty, " Holding blocks: %lu%s",
+ (ulong)minfo.hblks,
VTY_NEWLINE);
vty_out (vty, "(see system documentation for 'mallinfo' for meaning)%s",
VTY_NEWLINE);
+}
+#endif /* HAVE_MALLINFO */
+
+/*------------------------------------------------------------------------------
+ * Log the number of each type of memory currently thought to be allocated.
+ */
+static void
+log_memstats(int pri)
+{
+ mem_stats_t mst ;
+ struct mlist *ml;
+
+ mem_get_stats(&mst) ;
+
+ for (ml = mlists; ml->list; ml++)
+ {
+ struct memory_list *m;
+
+ zlog (NULL, pri, "Memory utilization in module %s:", ml->name);
+ for (m = ml->list; m->index >= 0; m++)
+ {
+ if ((m->index != 0) && (mst.alloc[m->index] != 0))
+ zlog (NULL, pri, " %-30s: %10lu", m->format, mst.alloc[m->index]) ;
+ } ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Output the number of each type of memory currently thought to be allocated,
+ * to stderr.
+ */
+extern void
+log_memstats_stderr (const char *prefix)
+{
+ mem_stats_t mst ;
+ struct mlist *ml;
+ struct memory_list *m;
+ bool seen_something ;
- return 1;
+ mem_get_stats(&mst) ;
+
+ seen_something = false ;
+ for (ml = mlists; ml->list; ml++)
+ {
+ bool first = true ;
+ for (m = ml->list; m->index >= 0; m++)
+ {
+ if ((m->index != 0) && (mst.alloc[m->index] != 0))
+ {
+ if (first)
+ {
+ fprintf (stderr,
+ "%s: memstats: Current memory utilization in module %s:\n",
+ prefix,
+ ml->name) ;
+ first = false ;
+ } ;
+ fprintf (stderr,
+ "%s: memstats: %-30s: %10lu\n",
+ prefix,
+ m->format,
+ mst.alloc[m->index]) ;
+ seen_something = true ;
+ } ;
+ } ;
+ } ;
+
+ if (seen_something)
+ fprintf (stderr,
+ "%s: memstats: NOTE: If configuration exists, utilization may be "
+ "expected.\n",
+ prefix);
+ else
+ fprintf (stderr,
+ "%s: memstats: No remaining tracked memory utilization.\n",
+ prefix);
}
+
+/*------------------------------------------------------------------------------
+ * Show all regions -- to vty.
+ *
+ * Acquires the lock while making the region map, and returns the other
+ * statistics, gathered while the lock is held.
+ */
+static void
+mem_show_region_info(vty vty, mem_tracker_data mtd)
+{
+ uint i, n ;
+
+ /* Start by making sure known regions are up to date, and then taking a
+ * local copy of them.
+ *
+ * This uses C99 variable length arrays !
+ */
+ LOCK ;
+
+ mem_map_regions() ;
+ mem_weigh_regions() ;
+
+ if (memory_tracker)
+ mem_mt_region_accounting() ;
+
+ n = mem_count_summary_regions() ;
+
+ mem_region_summary_t sum[n] ;
+ mem_summarize_regions(sum, n) ;
+
+ mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data and UNLOCK */
+
+ /* Now can show what we have
+ */
+ /* 12345678901234567890123456789012345678901234 */
+ vty_out(vty, "Region Size Present Swapped Count ") ;
+ if (memory_tracker)
+ /* 567890123456789012345678901 */
+ vty_out(vty, " Used Overhd Unused ") ;
+ /* 234567890 */
+ vty_out(vty, " Static\n") ;
+
+ for (i = 0 ; i < n ; i++)
+ {
+ mem_region_summary rs = &sum[i] ;
+ const char* name ;
+
+ if (rs->size == 0)
+ continue ; /* Ignore absent or trivial entries */
+
+ name = (rs->sex < mrx_sex_count) ? mrx_sex_names[rs->sex] : NULL ;
+ if (name == NULL)
+ name = "????" ;
+
+ /* 12--7:90-789-678-5 */
+ vty_out(vty, " %-6s: %8s %8s %8s",
+ name,
+ mem_form_byte_count(rs->size).str,
+ mem_form_byte_count(rs->present).str,
+ mem_form_byte_count(rs->swapped).str
+ ) ;
+
+ if (rs->sex != mrx_grand_total)
+ {
+ /* 67-4 */
+ vty_out(vty, " %8s", ((rs->count != 0) || memory_tracker)
+ ? mem_form_count(rs->count).str
+ : "- ") ;
+
+ if (memory_tracker && (rs->sex >= mrx_heap))
+ /* 56-345-234-1 */
+ vty_out (vty, " %8s %8s %8s",
+ mem_form_byte_count(rs->used).str,
+ mem_form_byte_count(rs->overhead).str,
+ mem_form_byte_count(rs->unused).str) ;
+
+ if (rs->data != 0)
+ /* 23-0 */
+ vty_out(vty, " %8s", mem_form_byte_count(rs->data).str) ;
+ } ;
+
+ vty_out(vty, "\n") ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show memory summary, if any is available:
+ *
+ * If have "memory_regions", show that. Otherwise, if HAVE_MALLINFO, show
+ * that (for whatever it is worth.
+ *
+ * If we have memory_tracker, show that.
+ *
+ * Show what we know about the memory overhead, if anything.
+ *
+ * Returns true if has output any form of summary.
+ *
+ * Copies the memory_tracker or standard statistics to the given
+ * mem_tracker_data structure -- so all information is gathered under the lock,
+ * in one go.
+ */
+static bool
+show_memory_summary_info(vty vty, mem_tracker_data mtd)
+{
+ bool needsep = false ;
+
+ if (memory_regions)
+ {
+ /* Show what we know about all memory regions & get other statistics */
+ mem_show_region_info(vty, mtd) ;
+ needsep = true ;
+ }
+ else
+ {
+ /* If we have mallinfo() show that & get other statistics.
+ *
+ * In any event, get other statistics.
+ */
+#ifdef HAVE_MALLINFO
+ show_memory_mallinfo(vty, mtd) ;
+ needsep = true ;
+#else
+ mem_mt_get_stats(mtd) ; /* Get a snapshot of all data */
#endif /* HAVE_MALLINFO */
+ } ;
+ if (memory_tracker)
+ {
+ show_memory_tracker_summary(vty, mtd) ;
+ needsep = true ;
+
+ if (mem_alloc_known)
+ vty_out(vty,
+ "malloc() minimum block %zu with %zu redtape and allocation unit %zu\n",
+ mem_alloc_min_size, mem_alloc_redtape, mem_alloc_unit) ;
+ else
+ vty_out(vty, "malloc() parameters unknown: %s\n",
+ mem_alloc_unknown_message().str) ;
+ } ;
+
+ return needsep ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show counts for for given type of memory.
+ *
+ * Shows basic allocation count or full memory_tracker statistics.
+ */
+static void
+show_memory_type_vty (vty vty, const char* name, mem_tracker_item mt, bool sep)
+{
+ if (sep)
+ {
+ /* 123456789012345678901234567890123456789012 */
+ vty_out (vty, "------------------------------:------Count") ;
+
+ if (memory_tracker)
+ {
+ /* 123456789012 */
+ vty_out(vty, "---Size--"
+ "--Peak-Count"
+ "-P-Size--"
+ "-malloc--"
+ "realloc--"
+ "---free--") ;
+ } ;
+
+ vty_out (vty, "\n") ;
+ } ;
+
+ vty_out (vty, "%-30s:%'11lu", name, mt->item_count) ;
+
+ if (memory_tracker)
+ {
+ vty_out(vty, "%9s", mem_form_byte_count(mt->total_size).str) ;
+ vty_out(vty, "%'12lu", mt->peak_item_count) ;
+ vty_out(vty, "%9s", mem_form_byte_count(mt->peak_total_size).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->malloc_count).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->realloc_count).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->free_count).str) ;
+
+ } ;
+
+ vty_out (vty, "\n") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show counts for all memory of types in the given memory_list.
+ *
+ * Shows everything for NULL memory_list, preceeded by summary.
+ *
+ * For basic statistics shows anything with non-zero count. For memory_tracker
+ * shows anything with non-zero count or peak count.
+ */
+static int
+show_memory_vty (vty vty, struct memory_list *ml)
+{
+ mem_tracker_data_t mtd[1] ;
+ mem_tracker_item mt ;
+
+ struct mlist* mll = NULL ;
+
+ bool needsep = false ;
+ bool notempty = false ;
+
+ /* ml == NULL => "show memory all" */
+ if (ml == NULL)
+ {
+ needsep = show_memory_summary_info(vty, mtd) ;
+
+ mll = mlists ;
+ }
+ else
+ mem_mt_get_stats(mtd, false) ; /* Get a snapshot of all data */
+
+ /* Work along the current list, when reach end, step to next or fin */
+ while (1)
+ {
+ if (ml == NULL) /* Step to next list */
+ {
+ if (mll != NULL)
+ ml = (mll++)->list ;
+ if (ml == NULL)
+ break ;
+ } ;
+
+ if (ml->index <= 0) /* Deal with separator/terminator */
+ {
+ needsep |= notempty ;
+ notempty = false ;
+
+ if (ml->index < 0)
+ ml = NULL ;
+ else
+ ++ml ;
+
+ continue ;
+ } ;
+
+ mt = &(mtd->typ[ml->index]) ;
+
+ if ((mt->item_count != 0) || (mt->peak_item_count != 0))
+ {
+ show_memory_type_vty(vty, ml->format, mt, needsep) ;
+ needsep = false ;
+ notempty = true ;
+ } ;
+
+ ++ml ;
+ } ;
+
+ show_memory_type_vty(vty, "Total", mtd->tot, needsep || notempty) ;
+
+ return 1 ;
+} ;
+
+/*==============================================================================
+ * The memory CLI commands
+ */
DEFUN_CALL (show_memory_summary,
show_memory_summary_cmd,
@@ -607,23 +3042,12 @@ DEFUN_CALL (show_memory_summary,
"Memory statistics\n"
"Summary memory statistics\n")
{
-#ifdef MEMORY_TRACKER
- show_memory_tracker_summary(vty) ;
-#else
- long alloc = 0 ;
- int mtype ;
+ mem_tracker_data_t mtd[1] ;
-# ifdef HAVE_MALLINFO
- show_memory_mallinfo (vty);
-# endif /* HAVE_MALLINFO */
-
- LOCK ;
- for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype)
- alloc += mstat.mt[mtype].alloc ;
- UNLOCK
- vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ;
+ show_memory_summary_info(vty, mtd) ;
-#endif /* MEMORY_TRACKER */
+ if (!memory_tracker)
+ vty_out(vty, "%'lu items allocated\n", mtd->tot->item_count) ;
return CMD_SUCCESS;
}
@@ -635,16 +3059,7 @@ DEFUN_CALL (show_memory_all,
"Memory statistics\n"
"All memory statistics\n")
{
- int needsep = 0;
-
-#ifdef HAVE_MALLINFO
- needsep |= show_memory_mallinfo (vty);
-#endif /* HAVE_MALLINFO */
-#ifdef MEMORY_TRACKER
- needsep |= show_memory_tracker_summary(vty) ;
-#endif
-
- show_memory_vty (vty, NULL, mlists, needsep);
+ show_memory_vty (vty, NULL) ;
return CMD_SUCCESS;
}
@@ -662,7 +3077,7 @@ DEFUN_CALL (show_memory_lib,
"Memory statistics\n"
"Library memory\n")
{
- show_memory_vty (vty, memory_list_lib, NULL, 0);
+ show_memory_vty (vty, memory_list_lib);
return CMD_SUCCESS;
}
@@ -673,7 +3088,7 @@ DEFUN_CALL (show_memory_zebra,
"Memory statistics\n"
"Zebra memory\n")
{
- show_memory_vty (vty, memory_list_zebra, NULL, 0);
+ show_memory_vty (vty, memory_list_zebra);
return CMD_SUCCESS;
}
@@ -684,7 +3099,7 @@ DEFUN_CALL (show_memory_rip,
"Memory statistics\n"
"RIP memory\n")
{
- show_memory_vty (vty, memory_list_rip, NULL, 0);
+ show_memory_vty (vty, memory_list_rip);
return CMD_SUCCESS;
}
@@ -695,7 +3110,7 @@ DEFUN_CALL (show_memory_ripng,
"Memory statistics\n"
"RIPng memory\n")
{
- show_memory_vty (vty, memory_list_ripng, NULL, 0);
+ show_memory_vty (vty, memory_list_ripng);
return CMD_SUCCESS;
}
@@ -706,7 +3121,7 @@ DEFUN_CALL (show_memory_bgp,
"Memory statistics\n"
"BGP memory\n")
{
- show_memory_vty (vty, memory_list_bgp, NULL, 0);
+ show_memory_vty (vty, memory_list_bgp);
return CMD_SUCCESS;
}
@@ -717,7 +3132,7 @@ DEFUN_CALL (show_memory_ospf,
"Memory statistics\n"
"OSPF memory\n")
{
- show_memory_vty (vty, memory_list_ospf, NULL, 0);
+ show_memory_vty (vty, memory_list_ospf);
return CMD_SUCCESS;
}
@@ -728,7 +3143,7 @@ DEFUN_CALL (show_memory_ospf6,
"Memory statistics\n"
"OSPF6 memory\n")
{
- show_memory_vty (vty, memory_list_ospf6, NULL, 0);
+ show_memory_vty (vty, memory_list_ospf6);
return CMD_SUCCESS;
}
@@ -739,24 +3154,60 @@ DEFUN_CALL (show_memory_isis,
"Memory statistics\n"
"ISIS memory\n")
{
- show_memory_vty (vty, memory_list_isis, NULL, 0);
+ show_memory_vty (vty, memory_list_isis);
return CMD_SUCCESS;
}
-/* Second state initialisation if qpthreaded */
-void
+/*==============================================================================
+ * Initialisation and close down
+ */
+
+static void bd_form_test(void) ;
+
+/*------------------------------------------------------------------------------
+ * First stage initialisation
+ */
+extern void
+memory_start(void)
+{
+#ifdef MCHECK
+ #include <mcheck.h>
+ mcheck(NULL) ; /* start now !! */
+#endif
+
+ memset(&mstats, 0, sizeof(mem_stats_t)) ;
+ memset(&mem_mt_data, 0, sizeof(mem_tracker_data_t)) ;
+ memset(mlog_stat, 0, sizeof(mlog_stat)) ;
+
+ mem_pagesize = qlib_pagesize ;
+
+ mem_alloc_discover() ;
+
+ if (false)
+ bd_form_test() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Second stage initialisation if qpthreaded
+ */
+extern void
memory_init_r (void)
{
- qpt_mutex_init(&memory_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(memory_mutex, qpt_mutex_quagga);
}
-/* Finished with module */
-void
+/*------------------------------------------------------------------------------
+ * Finished with module
+ */
+extern void
memory_finish (void)
{
- qpt_mutex_destroy(&memory_mutex, 0);
+ qpt_mutex_destroy(memory_mutex, 0);
}
+/*------------------------------------------------------------------------------
+ * Set up of memory reporting commands
+ */
void
memory_init (void)
{
@@ -795,78 +3246,377 @@ memory_init (void)
install_element (ENABLE_NODE, &show_memory_isis_cmd);
}
-/* Stats querying from users */
-/* Return a pointer to a human friendly string describing
- * the byte count passed in. E.g:
- * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc.
- * Up to 4 significant figures will be given.
- * The pointer returned may be NULL (indicating an error)
- * or point to the given buffer, or point to static storage.
+#undef UNLOCK
+#undef LOCK
+
+/*==============================================================================
+ * Support for mmap of anonymous memory regions
+ *
+ * These wrappers allow for the (perhaps obscure, now) absence of MAP_ANON/
+ * MAP_ANONYMOUS. They also check for failure returns, and crash with
+ * suitable messages.
+ *
+ * In the (extremely) unlikely event of there being no mmap, uses malloc().
+ *
+ * Note that the memory allocated is *not* included in the memory statistics.
*/
-const char *
-mtype_memstr (char *buf, size_t len, unsigned long bytes)
+
+/*------------------------------------------------------------------------------
+ * mmap allocation of memory.
+ *
+ * Rounds allocation up to multiple of page size (unless using malloc()) and
+ * zeroises.
+ */
+extern void*
+mem_mmap(size_t size)
{
- unsigned int t, g, m, k;
+ void* p ;
+ int err = 0 ;
- /* easy cases */
- if (!bytes)
- return "0 bytes";
- if (bytes == 1)
- return "1 byte";
+#if HAVE_MMAP
- if (sizeof (unsigned long) >= 8)
- /* Hacked to make it not warn on ILP32 machines
- * Shift will always be 40 at runtime. See below too */
- t = bytes >> (sizeof (unsigned long) >= 8 ? 40 : 0);
- else
- t = 0;
- g = bytes >> 30;
- m = bytes >> 20;
- k = bytes >> 10;
+ size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ;
- if (t > 10)
+# if (defined(MAP_ANON) || defined(MAP_ANONYMOUS)) && 0
+ p = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | (MAP_ANONYMOUS |0) | (MAP_ANON |0), -1, 0) ;
+ err = errno ;
+# else
+ int fd ;
+
+ fd = open("/dev/zero", O_RDWR) ;
+
+ if (fd >= 0)
{
- /* The shift will always be 39 at runtime.
- * Just hacked to make it not warn on 'smaller' machines.
- * Static compiler analysis should mean no extra code
- */
- if (bytes & (1UL << (sizeof (unsigned long) >= 8 ? 39 : 0)))
- t++;
- snprintf (buf, len, "%4d TiB", t);
+ p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0) ;
+ err = errno ;
}
- else if (g > 10)
+ else
{
- if (bytes & (1 << 29))
- g++;
- snprintf (buf, len, "%d GiB", g);
- }
- else if (m > 10)
+ p = MAP_FAILED ;
+ err = errno ;
+ } ;
+
+ close(fd) ;
+
+ if (p == MAP_FAILED)
{
- if (bytes & (1 << 19))
- m++;
- snprintf (buf, len, "%d MiB", m);
- }
- else if (k > 10)
+ zlog_err ("%s : failed to mmap %zd bytes: %s\n",
+ __func__, size, errtoa(err, 0).str) ;
+ zabort_abort();
+ } ;
+# endif
+
+#else
+ p = malloc(size) ;
+ err = errno ;
+
+ if (p == NULL)
{
- if (bytes & (1 << 9))
- k++;
- snprintf (buf, len, "%d KiB", k);
- }
+ zlog_err ("%s : failed to malloc %zd bytes: %s\n",
+ __func__, size, errtoa(err, 0).str) ;
+ zabort_abort();
+ } ;
+#endif
+
+ memset(p, 0, size) ;
+
+ return p ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * mmap allocation of memory.
+ *
+ * Rounds allocation up to multiple of page sizes and zeroises.
+ */
+extern void
+mem_munmap(void* p, size_t size)
+{
+#if HAVE_MMAP
+ int rc ;
+
+ size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ;
+ rc = munmap(p, size) ;
+
+ if (rc < 0)
+ {
+ zlog_err ("%s : failed to munmap %zd bytes @ %p: %s\n",
+ __func__, size, p, errtoa(errno, 0).str) ;
+ zabort_abort();
+ } ;
+#else
+ free(p) ;
+#endif
+} ;
+
+/*==============================================================================
+ * Testing the number formatting
+ */
+static ulong t_val(double v, ulong s) ;
+static void b_test(ulong v) ;
+static void d_test(ulong v) ;
+static void c_val(char* e, ulong u, uint d, const char* t) ;
+
+static void
+bd_form_test(void)
+{
+ ulong s ;
+ int g = 0 ;
+
+ fprintf(stderr, "Group %d\n", g) ;
+ b_test(0 ) ;
+ b_test(1 ) ;
+ b_test(9 ) ;
+ b_test(10 ) ;
+ b_test(99 ) ;
+ b_test(100 ) ;
+ b_test(999 ) ;
+ b_test(1023 ) ;
+ b_test(1024 ) ;
+ b_test(1025 ) ;
+ b_test(9999 ) ;
+ b_test(10000) ;
+ b_test(10007) ;
+
+ s = 1 ;
+ for (g = 1 ; g <= 5 ; ++g)
+ {
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1024ul ;
+ b_test(t_val(.31414, s) ) ;
+ b_test(t_val(.31415, s) ) ;
+ b_test(t_val(.31416, s) ) ;
+ b_test(t_val(.99995, s) - 1) ;
+ b_test(t_val(.99995, s) ) ;
+ b_test(t_val(.99995, s) + 1) ;
+ b_test(t_val(1.2345, s) ) ;
+ b_test(t_val(9.9995, s) - 1) ;
+ b_test(t_val(9.9995, s) ) ;
+ b_test(t_val(9.9995, s) + 1) ;
+ b_test(t_val(3.1415, s) - 1) ;
+ b_test(t_val(3.1415, s) ) ;
+ b_test(t_val(3.1416, s) ) ;
+ b_test(t_val(99.995, s) - 1) ;
+ b_test(t_val(99.995, s) ) ;
+ b_test(t_val(99.995, s) + 1) ;
+ b_test(t_val(314.15, s) - 1) ;
+ b_test(t_val(314.15, s) ) ;
+ b_test(t_val(314.16, s) ) ;
+ b_test(t_val(999.95, s) - 1) ;
+ b_test(t_val(999.95, s) ) ;
+ b_test(t_val(999.95, s) + 1) ;
+ b_test(t_val(1023.5, s) - 1) ;
+ b_test(t_val(1023.5, s) ) ;
+ b_test(t_val(1023.5, s) + 1) ;
+ b_test(t_val(3141.5, s) - 1) ;
+ b_test(t_val(3141.5, s) ) ;
+ b_test(t_val(3141.6, s) ) ;
+ b_test(t_val(9999.5, s) - 1) ;
+ b_test(t_val(9999.5, s) ) ;
+ b_test(t_val(9999.5, s) + 1) ;
+ } ;
+
+ g = 0 ;
+ fprintf(stderr, "Group %d\n", g) ;
+ d_test(0 ) ;
+ d_test(1 ) ;
+ d_test(9 ) ;
+ d_test(10 ) ;
+ d_test(11 ) ;
+ d_test(99 ) ;
+ d_test(100 ) ;
+ d_test(999 ) ;
+ d_test(1000 ) ;
+ d_test(1001 ) ;
+ d_test(9999 ) ;
+ d_test(10000) ;
+ d_test(10001) ;
+
+ s = 1 ;
+ for (g = 1 ; g <= 4 ; ++g)
+ {
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1000ul ;
+ d_test(t_val(1.2345, s) - 1) ;
+ d_test(t_val(1.2345, s) ) ;
+ d_test(t_val(9.9995, s) - 1) ;
+ d_test(t_val(9.9995, s) ) ;
+ d_test(t_val(21.235, s) - 1) ;
+ d_test(t_val(21.235, s) ) ;
+ d_test(t_val(99.995, s) - 1) ;
+ d_test(t_val(99.995, s) ) ;
+ d_test(t_val(312.35, s) - 1) ;
+ d_test(t_val(312.35, s) ) ;
+ d_test(t_val(999.95, s) - 1) ;
+ d_test(t_val(999.95, s) ) ;
+ d_test(t_val(4321.5, s) - 1) ;
+ d_test(t_val(4321.5, s) ) ;
+ d_test(t_val(9999.5, s) - 1) ;
+ d_test(t_val(9999.5, s) ) ;
+ } ;
+
+ g = 5 ;
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1000ul ;
+ d_test(t_val(1.2345, s) - 1) ;
+ d_test(t_val(1.2345, s) ) ;
+ d_test(t_val(9.9995, s) - 1) ;
+ d_test(t_val(9.9995, s) ) ;
+ d_test(t_val(21.2345, s) - 1) ;
+ d_test(t_val(21.2345, s) ) ;
+ d_test(t_val(99.9995, s) - 1) ;
+ d_test(t_val(99.9995, s) ) ;
+ d_test(t_val(312.6785, s) - 1) ;
+ d_test(t_val(312.6785, s) ) ;
+ d_test(t_val(999.9995, s) - 1) ;
+ d_test(t_val(999.9995, s) ) ;
+ d_test(t_val(4321.5675, s) - 1) ;
+ d_test(t_val(4321.5675, s) ) ;
+ d_test(t_val(9999.9995, s) - 1) ;
+ d_test(t_val(9999.9995, s) ) ;
+} ;
+
+static ulong
+t_val(double v, ulong s)
+{
+ return v * s ;
+} ;
+
+static void
+b_test(ulong v)
+{
+ num_str_t g ;
+ char e[50] ;
+ ulong u ;
+ uint i ;
+ uint d ;
+
+ d = 0 ;
+ u = v ;
+ i = 0 ;
+ if (u >= 1024)
+ {
+ uint n ;
+
+ while ((u >= 1024) && (i < scale_max))
+ {
+ u >>= 10 ;
+ i += 1 ;
+ }
+ n = i * 10 ;
+
+ u = v ;
+ while (u < (1000ul << n))
+ {
+ u *= 10 ;
+ d += 1 ;
+ } ;
+
+ u = ((u >> (n - 1)) + 1) >> 1 ;
+
+ if (d == 0)
+ {
+ if ((u == 1024) && (i < scale_max))
+ {
+ u = 1000 ;
+ i += 1 ;
+ d = 3 ;
+ } ;
+ }
+ else
+ {
+ assert(u <= 10000) ;
+ if (u == 10000)
+ {
+ u = 1000 ;
+ d -= 1 ;
+ } ;
+ } ;
+ } ;
+
+ c_val(e, u, d, scale_b_tags[i]) ;
+
+ g = mem_form_byte_count(v) ;
+
+ fprintf(stderr, "%16lx %'20lu -> '%7s'", v, v, g.str) ;
+
+ if (strcmp(e, g.str) == 0)
+ fputs(" -- OK\n", stderr) ;
else
- snprintf (buf, len, "%ld bytes", bytes);
+ fprintf(stderr, " *** expect: '%7s'\n", e) ;
+} ;
- return buf;
-}
+static void
+d_test(ulong v)
+{
+ num_str_t g ;
+ char e[50] ;
+ ulong u ;
+ uint i ;
+ uint n ;
+ uint d ;
+
+ d = 0 ;
+ i = 0 ;
+ u = v ;
+ n = (v == 0) ? 0 : 1 ;
+
+ while (u >= 10)
+ {
+ u /= 10 ;
+ n += 1 ;
+ } ;
+
+ u = v ;
+
+ if (n > 4)
+ {
+ if (n > ((scale_max * 3) + 3))
+ {
+ u = (u + q10[scale_max * 3]) / p10[scale_max * 3] ;
+ i = scale_max ;
+ }
+ else
+ {
+ u = (u + q10[n - 4]) / p10[n - 4] ;
+
+ if (u > 9999)
+ {
+ u /= 10 ;
+ n += 1 ;
+ } ;
+
+ i = (n - 2) / 3 ;
+ d = (i * 3) + 4 - n ;
+ } ;
+ } ;
+
+ c_val(e, u, d, scale_d_tags[i]) ;
+
+ g = mem_form_count(v) ;
+
+ fprintf(stderr, "%'20lu -> '%7s'", v, g.str) ;
+
+ if (strcmp(e, g.str) == 0)
+ fputs(" -- OK\n", stderr) ;
+ else
+ fprintf(stderr, " *** expect: '%7s'\n", e) ;
+} ;
-unsigned long
-mtype_stats_alloc (enum MTYPE type)
+static void
+c_val(char* e, ulong u, uint d, const char* t)
{
- unsigned long result;
- LOCK
- result = mstat.mt[type].alloc;
- UNLOCK
- return result;
-}
+ if (d != 0)
+ sprintf(e, "%lu.%0*lu%s", u / p10[d], (int)d, u % p10[d], t) ;
+ else
+ {
+ if (u < 1000)
+ sprintf(e, "%lu%s", u, t) ;
+ else if (u < 1000000)
+ sprintf(e, "%lu,%03lu%s", u / 1000, u % 1000, t) ;
+ else
+ sprintf(e, "%lu,%03lu,%03lu%s", u / 1000000, (u / 1000) % 1000,
+ u % 1000, t) ;
+ } ;
+} ;
-#undef UNLOCK
-#undef LOCK
diff --git a/lib/memory.h b/lib/memory.h
index 6c95d73a..5e5af698 100644
--- a/lib/memory.h
+++ b/lib/memory.h
@@ -21,101 +21,170 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _ZEBRA_MEMORY_H
#define _ZEBRA_MEMORY_H
-#include <stddef.h>
-
-/* For pretty printing of memory allocate information. */
-struct memory_list
+#include "misc.h"
+
+/*------------------------------------------------------------------------------
+ * The file "lib/memtypes.h is created automatically (in the make) from
+ * memtypes.c, and contains a large enum which defines all the memory type
+ * names.
+ *
+ * The file memtypes.c contains a number of arrays of struct memory_list, which
+ * map memory type names to a string, for printing. Those arrays are
+ * collected together in an array of struct mlist.
+ */
+struct memory_list /* one per memory type */
{
int index;
const char *format;
};
-struct mlist {
+struct mlist { /* one per class of memory types */
struct memory_list *list;
const char *name;
};
-extern struct mlist mlists[];
+extern struct mlist mlists[]; /* all classes of memory */
#include "lib/memtypes.h"
-/* #define MEMORY_LOG */
-#ifdef MEMORY_LOG
-#define XMALLOC(mtype, size) \
- mtype_zmalloc (__FILE__, __LINE__, (mtype), (size))
-#define XCALLOC(mtype, size) \
- mtype_zcalloc (__FILE__, __LINE__, (mtype), (size))
-#define XREALLOC(mtype, ptr, size) \
- mtype_zrealloc (__FILE__, __LINE__, (mtype), (ptr), (size))
-#define XFREE(mtype, ptr) \
- do { \
- mtype_zfree (__FILE__, __LINE__, (mtype), (ptr)); \
- ptr = NULL; } \
- while (0)
-#define XSTRDUP(mtype, str) \
- mtype_zstrdup (__FILE__, __LINE__, (mtype), (str))
+typedef enum MTYPE mtype_t ;
+
+/*------------------------------------------------------------------------------
+ * Basic counting -- count of allocated objects of each type of memory
+ *
+ */
+struct mem_stats /* wrap in struct so can copy trivially */
+{
+ ulong alloc[MTYPE_MAX] ;
+} ;
+
+typedef struct mem_stats mem_stats_t ;
+
+/*------------------------------------------------------------------------------
+ * Option for logging memory operations.
+ */
+#ifdef MEMORY_LOGGER /* Takes precedence over MEMORY_TRACKER */
+
+#undef MEMORY_LOGGER
+#define MEMORY_LOGGER 1
+
+#undef MEMORY_TRACKER
+#define MEMORY_TRACKER 0
+
#else
-#ifdef QDEBUG
-#define MEMORY_TRACKER 1
+#define MEMORY_LOGGER 0
+
+#endif
+
+/*------------------------------------------------------------------------------
+ * Sort out MEMORY_TRACKER -- option to keep track of memory allocation.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if NO_MEMORY_TRACKER is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set MEMORY_TRACKER == 0 to turn off debug
+ * * or set MEMORY_TRACKER != 0 to turn on debug
+ * * or set VTY_NO_DEBUG != to force debug off
+ */
+#ifdef MEMORY_TRACKER /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(MEMORY_TRACKER)
+# undef MEMORY_TRACKER
+# define MEMORY_TRACKER 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define MEMORY_TRACKER QDEBUG
+#endif
+
+#ifdef NO_MEMORY_TRACKER /* Override, if defined. */
+# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG)
+# undef MEMORY_TRACKER
+# define MEMORY_TRACKER 0
+# endif
#endif
-#ifdef MEMORY_TRACKER
-#define MEMORY_TRACKER_NAME , const char* name
-#define MEMORY_TRACKER_FUNC , __func__
+/*------------------------------------------------------------------------------
+ * Argument macros to make everything look much the same
+ */
+
+#if MEMORY_LOGGER
+#define MEMORY_EXTRA_DATA , __FILE__, __LINE__
+#define MEMORY_EXTRA_ARGS , const char* file, int line
+#define MEMORY_LOGGING_ARGS file, line
+#elif MEMORY_TRACKER
+#define MEMORY_EXTRA_DATA , __func__
+#define MEMORY_EXTRA_ARGS , const char* func
+#define MEMORY_TRACKER_ARG func
#else
-#define MEMORY_TRACKER_NAME
-#define MEMORY_TRACKER_FUNC
+#define MEMORY_EXTRA_DATA
+#define MEMORY_EXTRA_ARGS
#endif
+#ifndef MEMORY_LOGGING_ARGS
+#define MEMORY_LOGGING_ARGS "*dummy*", -1
+#endif
+#ifndef MEMORY_TRACKER_ARG
+#define MEMORY_TRACKER_ARG "*dummy*"
+#endif
+
+enum
+{
+ memory_logger = MEMORY_LOGGER,
+ memory_tracker = MEMORY_TRACKER,
+} ;
+
+/*------------------------------------------------------------------------------
+ * The macros used for all Quagga dynamic memory.
+ */
+
#define XMALLOC(mtype, size) zmalloc ((mtype), (size) \
- MEMORY_TRACKER_FUNC)
+ MEMORY_EXTRA_DATA)
#define XCALLOC(mtype, size) zcalloc ((mtype), (size) \
- MEMORY_TRACKER_FUNC)
+ MEMORY_EXTRA_DATA)
#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \
- MEMORY_TRACKER_FUNC)
-#define XFREE(mtype, ptr) do { \
- zfree ((mtype), (ptr)); \
- ptr = NULL; } \
- while (0)
+ MEMORY_EXTRA_DATA)
+#define XFREE(mtype, ptr) do { zfree ((mtype), (ptr) \
+ MEMORY_EXTRA_DATA); \
+ ptr = NULL; } while (0)
#define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \
- MEMORY_TRACKER_FUNC)
-
-#endif /* MEMORY_LOG */
+ MEMORY_EXTRA_DATA)
#define SIZE(t,n) (sizeof(t) * (n))
-/* Prototypes of memory function. */
-extern void *zmalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME);
-extern void *zcalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME);
-extern void *zrealloc (enum MTYPE type, void *ptr, size_t size
- MEMORY_TRACKER_NAME);
-extern void zfree (enum MTYPE type, void *ptr);
-extern char *zstrdup (enum MTYPE type, const char *str MEMORY_TRACKER_NAME);
+/*------------------------------------------------------------------------------
+ * Prototypes of memory functions
+ */
+extern void *zmalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) ;
+extern void *zcalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) ;
+extern void *zrealloc (mtype_t mtype, void *ptr, size_t size
+ MEMORY_EXTRA_ARGS) ;
+extern void zfree (mtype_t mtype, void *ptr MEMORY_EXTRA_ARGS) ;
+extern char *zstrdup (mtype_t mtype, const char *str MEMORY_EXTRA_ARGS) ;
-extern void *mtype_zmalloc (const char *file, int line, enum MTYPE type,
- size_t size);
+extern void* mem_mmap(size_t size) ;
+extern void mem_munmap(void* p, size_t size) ;
-extern void *mtype_zcalloc (const char *file, int line, enum MTYPE type,
- size_t size);
-
-extern void *mtype_zrealloc (const char *file, int line, enum MTYPE type,
- void *ptr, size_t size);
-
-extern void mtype_zfree (const char *file, int line, enum MTYPE type,
- void *ptr);
-
-extern char *mtype_zstrdup (const char *file, int line, enum MTYPE type,
- const char *str);
+extern void memory_start(void) ;
extern void memory_init (void);
extern void memory_init_r (void);
extern void memory_finish (void);
extern void log_memstats_stderr (const char *);
-/* return number of allocations outstanding for the type */
-extern unsigned long mtype_stats_alloc (enum MTYPE);
+/* Return number of allocations outstanding for all memory types */
+extern void mem_get_stats(mem_stats_t* mst) ;
-/* Human friendly string for given byte count */
+/* Return number of allocations outstanding for the given memory type */
+extern ulong mem_get_alloc(mem_stats_t* mst, mtype_t mtype) ;
+
+/* Return number of allocations outstanding for the given memory type */
+extern ulong mtype_stats_alloc(mtype_t mtype) ;
+
+/* Human friendly string for given byte count */
#define MTYPE_MEMSTR_LEN 20
-extern const char *mtype_memstr (char *, size_t, unsigned long);
+extern const char *mtype_memstr (char *, size_t, ulong);
+
#endif /* _ZEBRA_MEMORY_H */
diff --git a/lib/memtypes.awk b/lib/memtypes.awk
index a8004977..ac111b69 100644
--- a/lib/memtypes.awk
+++ b/lib/memtypes.awk
@@ -65,4 +65,17 @@ END {
printf (mlistformat "\n", mlists[i]);
}
printf (footer);
+
+ printf ("\n") ;
+ printf ("#ifdef MEM_MTYPE_MAP_REQUIRED\n") ;
+ printf ("static const char* const mem_mtype_map[] = {\n") ;
+
+ printf (" [%-30d] = \"*MTYPE = 0*\",\n", 0) ;
+
+ for (i = 0; i < tcount; i++) {
+ printf (" [%-30s] = \"%s\",\n", mtype[i], mtype[i]);
+ } ;
+
+ printf ("} ;\n") ;
+ printf ("#endif /* MEM_MTYPE_MAP_REQUIRED */\n") ;
}
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 98b53209..66d65328 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -15,7 +15,9 @@
struct memory_list memory_list_lib[] =
{
{ MTYPE_TMP, "Temporary memory" },
- { MTYPE_STRVEC, "String vector" },
+ { MTYPE_STRING, "String (general)" },
+ { MTYPE_CMD_ITEM, "Command item" },
+ { MTYPE_CMD_STRING, "Command string" },
{ MTYPE_VECTOR, "Vector structure" },
{ MTYPE_VECTOR_BODY, "Vector body" },
{ MTYPE_SYMBOL_TABLE, "Symbol Table structure" },
@@ -43,17 +45,19 @@ struct memory_list memory_list_lib[] =
{ MTYPE_QPN_NEXUS, "qtn nexus" },
{ MTYPE_TSD, "Thread specific data" },
{ MTYPE_VTY, "VTY" },
+ { MTYPE_CMD_EXEC, "Command exec context" },
{ MTYPE_CMD_PARSED, "Parsed command" },
- { MTYPE_MARSHAL, "marshalled commands" },
+ { MTYPE_TOKEN, "Command token" },
{ MTYPE_VTY_OUT_BUF, "VTY output buffer" },
{ MTYPE_VTY_HIST, "VTY history" },
{ MTYPE_VTY_NAME, "VTY name" },
{ MTYPE_KEY_STREAM, "Keystroke Stream" },
+ { MTYPE_VTY_CLI, "VTY CLI" },
{ MTYPE_VIO_FIFO, "VTY IO FIFO" },
{ 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_QPATH, "qpath structure" },
{ MTYPE_QIOVEC, "qiovec structure" },
{ MTYPE_QIOVEC_VEC, "qiovec iovec vector" },
{ MTYPE_IF, "Interface" },
@@ -86,7 +90,6 @@ struct memory_list memory_list_lib[] =
{ MTYPE_ROUTE_MAP_RULE, "Route map rule" },
{ MTYPE_ROUTE_MAP_RULE_STR, "Route map rule str" },
{ MTYPE_ROUTE_MAP_COMPILED, "Route map compiled" },
- { MTYPE_DESC, "Command desc" },
{ MTYPE_KEY, "Key" },
{ MTYPE_KEYCHAIN, "Key chain" },
{ MTYPE_IF_RMAP, "Interface route map" },
diff --git a/lib/misc.h b/lib/misc.h
new file mode 100644
index 00000000..8a0efb9a
--- /dev/null
+++ b/lib/misc.h
@@ -0,0 +1,166 @@
+/* Miscellaneous basic definitions
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_MISC_H
+#define _ZEBRA_MISC_H
+
+/* "zconfig.h" is included at the start of this "misc.h", and at the start
+ * of "zebra.h". This ensures that we get <features.h> defined early, so
+ * that all other #includes get the same set of features.
+ */
+
+#include "zconfig.h"
+
+/* This is horrible... but for some purposes wish to turn *off* __USE_GNU.
+ *
+ * e.g: to persuade <string.h> to give POSIX version of strerror_r !!!
+ */
+#ifdef NO_USE_GNU
+# undef NO_USE_GNU
+# ifdef __USE_GNU
+# define NO_USE_GNU 1
+# undef __USE_GNU
+# endif
+#endif
+
+/* Stuff which we generally expect to have */
+
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "confirm.h"
+#include "zassert.h"
+
+/* Bit number to bit mask */
+#define BIT(b) (1 << b)
+
+/* Just in case there are compiler issues */
+#define Inline static inline
+
+/* For things which have to be made extern -- typically because they are
+ * used by an inline function -- but are not for public consumption.
+ */
+#define Private extern
+
+/* For use in switch/case */
+#define fall_through
+
+/* Other names of true/false */
+enum on_off
+{
+ on = true,
+ off = false
+} ;
+typedef enum on_off on_off_b ;
+
+/* Whether to add or not on lookup. */
+enum add
+{
+ add = true,
+ no_add = false
+} ;
+typedef enum add add_b ;
+
+/* Used in object "reset" functions (destructors) */
+enum free_keep
+{
+ free_it = true,
+ keep_it = false
+} ;
+typedef enum free_keep free_keep_b ;
+
+/* We really want to be able to assume that an int is at least 32 bits
+ * and that a long is at least 64 bits !
+ */
+CONFIRM(UINT_MAX >= 0xFFFFFFFF) ;
+CONFIRM(ULONG_MAX >= 0xFFFFFFFFFFFFFFFF) ;
+
+/* Some useful shorthand */
+typedef unsigned char byte ;
+typedef unsigned char uchar ;
+
+typedef unsigned int uint ;
+typedef unsigned int usize ;
+typedef unsigned int ulen ;
+
+typedef int ssize ;
+typedef int slen ;
+
+typedef unsigned long ulong ;
+
+typedef long long llong ;
+typedef unsigned long long ullong ;
+
+/* cvp == const void* -- ptr to constant void
+ * cvp* == void * const* -- ptr to ptr to constant void
+ * const cvp* == void const* const* -- ptr to constant ptr to constant void
+ */
+typedef const void* cvp ;
+
+/* Macros for sexing value of compilation options.
+ *
+ * In particular allow a blank option to be treated as true, and a zero option
+ * to be treated as false.
+ *
+ * NB: the option MUST be defined, and must be decimal numeric !!
+ */
+#define STRING_VALUE_INNER(x) #x
+#define STRING_VALUE(x) STRING_VALUE_INNER(x)
+
+#define IS_BLANK_OPTION(x) IS_BLANK_OPTION_INNER(x)
+#define IS_ZERO_OPTION(x) IS_ZERO_OPTION_INNER(x)
+#define IS_NOT_ZERO_OPTION(x) IS_NOT_OPTION_ZERO_INNER(x)
+
+#define IS_BLANK_OPTION_INNER(x) (1##x##1 == 11)
+#define IS_ZERO_OPTION_INNER(x) (1##x##1 == 101)
+#define IS_NOT_ZERO_OPTION_INNER(x) (1##x##1 != 101)
+
+/* If QDEBUG is defined, make QDEBUG_NAME and set QDEBUG
+ *
+ * Numeric value for QDEBUG: undefined => 0
+ * defined, blank => 1
+ * defined, 0 => 0
+ * defined, other => other
+ *
+ * Template for turning compilation option into a value.
+ */
+#ifdef QDEBUG
+# if IS_BLANK_OPTION(QDEBUG)
+# undef QDEBUG
+# define QDEBUG 1
+# endif
+#else
+# define QDEBUG 0
+#endif
+
+enum { qdebug = QDEBUG } ;
+
+#ifndef QDEBUG_NAME
+# define QDEBUG_NAME STRING_VALUE(QDEBUG)
+#endif
+
+#endif /* _ZEBRA_MISC_H */
diff --git a/lib/miyagi.h b/lib/miyagi.h
index 569d2da9..639b962d 100644
--- a/lib/miyagi.h
+++ b/lib/miyagi.h
@@ -17,9 +17,7 @@
#ifndef _ZEBRA_MIYAGI_H
#define _ZEBRA_MIYAGI_H
-#ifndef Inline
-#define Inline static inline
-#endif
+#include "misc.h"
/*==============================================================================
* Ghastly kludge to discard "const" from pointer
diff --git a/lib/mqueue.c b/lib/mqueue.c
index 8fa9fbd5..f816932b 100644
--- a/lib/mqueue.c
+++ b/lib/mqueue.c
@@ -18,12 +18,10 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
-
-#include <string.h>
+#include "misc.h"
#include "memory.h"
#include "mqueue.h"
-#include "zassert.h"
/*==============================================================================
* These message queues are designed for inter-qpthread communication.
@@ -211,7 +209,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type)
memset(mq, 0, sizeof(struct mqueue_queue)) ;
if (qpt_freeze_qpthreads_enabled())
- qpt_mutex_init_new(&mq->mutex, qpt_mutex_quagga) ;
+ qpt_mutex_init_new(mq->mutex, qpt_mutex_quagga) ;
/* head, tail and tail_priority set NULL already */
/* count set zero already */
@@ -222,7 +220,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type)
{
case mqt_cond_unicast:
case mqt_cond_broadcast:
- qpt_cond_init_new(&mq->kick.cond.wait_here, qpt_cond_quagga) ;
+ qpt_cond_init_new(mq->kick.cond.wait_here, qpt_cond_quagga) ;
if (MQUEUE_DEFAULT_INTERVAL != 0)
{
@@ -254,7 +252,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type)
extern void
mqueue_empty(mqueue_queue mq)
{
- mqueue_revoke(mq, NULL) ;
+ mqueue_revoke(mq, NULL, 0) ;
assert((mq->head == NULL) && (mq->count == 0)) ;
} ;
@@ -268,19 +266,22 @@ mqueue_empty(mqueue_queue mq)
* NB: there MUST NOT be ANY waiters !
*/
extern mqueue_queue
-mqueue_reset(mqueue_queue mq, int free_structure)
+mqueue_reset(mqueue_queue mq, free_keep_b free_structure)
{
+ if (mq == NULL)
+ return NULL ;
+
mqueue_empty(mq) ;
passert(mq->waiters == 0) ;
- qpt_mutex_destroy_keep(&mq->mutex) ;
+ qpt_mutex_destroy_keep(mq->mutex) ;
switch (mq->type)
{
case mqt_cond_unicast:
case mqt_cond_broadcast:
- qpt_cond_destroy_keep(&mq->kick.cond.wait_here) ;
+ qpt_cond_destroy_keep(mq->kick.cond.wait_here) ;
break;
case mqt_signal_unicast:
@@ -327,13 +328,10 @@ mqueue_local_init_new(mqueue_local_queue lmq)
*
* Dequeues entries and dispatches them "mqb_destroy", to empty the queue.
*
- * See: mqueue_local_reset_keep(lmq)
- * mqueue_local_reset_free(lmq)
- *
* Returns address of Local Message Queue
*/
extern mqueue_local_queue
-mqueue_local_reset(mqueue_local_queue lmq, int free_structure)
+mqueue_local_reset(mqueue_local_queue lmq, free_keep_b free_structure)
{
mqueue_block mqb ;
@@ -362,7 +360,7 @@ mqueue_local_reset(mqueue_local_queue lmq, int free_structure)
extern void
mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval)
{
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
dassert( (mq->type == mqt_cond_unicast) ||
(mq->type == mqt_cond_broadcast) ) ;
@@ -370,7 +368,7 @@ mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval)
mq->kick.cond.interval = interval ;
mq->kick.cond.timeout = (interval > 0) ? qt_add_monotonic(interval)
: 0 ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
} ;
/*==============================================================================
@@ -484,9 +482,12 @@ mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0)
* NB: it is the caller's responsibility to free the value of any argument that
* requires it.
*/
-extern void
+extern mqueue_block
mqb_free(mqueue_block mqb)
{
+ if (mqb == NULL)
+ return NULL ;
+
if (mqb->argv != NULL)
XFREE(MTYPE_MQUEUE_BLOCK_ARGV, mqb->argv) ;
@@ -497,6 +498,8 @@ mqb_free(mqueue_block mqb)
++mqb_free_count ;
qpt_mutex_unlock(&mqb_mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+
+ return NULL ;
} ;
/*==============================================================================
@@ -533,12 +536,12 @@ static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ;
* never be any waiters... so no kicking is ever done.
*/
extern void
-mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority)
+mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority)
{
if (mq == NULL)
return mqb_dispatch_destroy(mqb) ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
if (mq->head == NULL)
{
@@ -588,12 +591,12 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority)
switch (mq->type)
{
case mqt_cond_unicast:
- qpt_cond_signal(&mq->kick.cond.wait_here) ;
+ qpt_cond_signal(mq->kick.cond.wait_here) ;
--mq->waiters ;
break ;
case mqt_cond_broadcast:
- qpt_cond_broadcast(&mq->kick.cond.wait_here) ;
+ qpt_cond_broadcast(mq->kick.cond.wait_here) ;
mq->waiters = 0 ;
break ;
@@ -612,7 +615,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority)
} ;
} ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
} ;
/*------------------------------------------------------------------------------
@@ -655,7 +658,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
if (mq == NULL)
return NULL ;
- qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
while (1)
{
@@ -676,13 +679,13 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
case mqt_cond_unicast: /* Now wait here */
case mqt_cond_broadcast:
if ((arg == NULL) && (mq->kick.cond.interval <= 0))
- qpt_cond_wait(&mq->kick.cond.wait_here, &mq->mutex) ;
+ qpt_cond_wait(mq->kick.cond.wait_here, mq->mutex) ;
else
{
timeout_time = (arg != NULL) ? *(qtime_mono_t*)arg
: mq->kick.cond.timeout ;
- if (qpt_cond_timedwait(&mq->kick.cond.wait_here, &mq->mutex,
+ if (qpt_cond_timedwait(mq->kick.cond.wait_here, mq->mutex,
timeout_time) == 0)
{
/* Timed out -- update timeout time, if required */
@@ -742,7 +745,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
mq->tail_priority = NULL ;
done:
- qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
return mqb ;
} ;
@@ -755,35 +758,41 @@ done:
*
* Revokes by calling mqb_dispatch_destroy().
*
- * During a revoke() operation more items may be enqueued, but no other mqueue
- * 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.
+ * NB: for safety, holds the queue locked for the duration of the revoke
+ * operation.
+ *
+ * If the destroy code can handle it, this means that can revoke stuff
+ * from one thread even though it is usually only dequeued by another.
+ *
+ * The danger is that if queues get very long, and many revokes happen,
+ * may (a) spend a lot of time scanning the message queue, which stops
+ * other threads as soon as they try to enqueue anything, and (b) if this
+ * happens a lot, could end up in an O(n^2) thing scanning the message
+ * queue once for each revoked object type.
*
* If mq is NULL, does nothing.
+ *
+ * If num > 0, stops after revoking that many messages.
+ *
+ * Returns: number of messages revoked.
*/
-extern void
-mqueue_revoke(mqueue_queue mq, void* arg0)
+extern int
+mqueue_revoke(mqueue_queue mq, void* arg0, int num)
{
mqueue_block mqb ;
mqueue_block prev ;
+ int did ;
if (mq == NULL)
- return ;
+ return 0 ;
- qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ did = 0 ;
prev = NULL ;
- while (1)
+ mqb = mq->head ;
+ while (mqb != NULL)
{
- if (prev == NULL)
- mqb = mq->head ;
- else
- mqb = prev->next ;
-
- if (mqb == NULL)
- break ;
-
if ((arg0 == NULL) || (arg0 == mqb->arg0))
{
assert(mq->count > 0) ;
@@ -800,16 +809,30 @@ mqueue_revoke(mqueue_queue mq, void* arg0)
mq->tail_priority = prev ;
--mq->count ;
+ ++did ;
+
+ mqb_dispatch_destroy(mqb) ;
+
+ if (num == 1)
+ break ;
+ else if (num > 1)
+ --num ;
- qpt_mutex_unlock(&mq->mutex) ;
- mqb_dispatch_destroy(mqb) ;
- qpt_mutex_lock(&mq->mutex) ;
+ if (prev == NULL)
+ mqb = mq->head ;
+ else
+ mqb = prev->next ;
}
else
- prev = mqb ;
+ {
+ prev = mqb ;
+ mqb = mqb->next ;
+ } ;
} ;
- qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+
+ return did ;
} ;
/*------------------------------------------------------------------------------
@@ -827,7 +850,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig)
if (!qpthreads_enabled)
return 0 ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
dassert( (mq->type == mqt_signal_unicast) ||
(mq->type == mqt_signal_broadcast) ) ;
@@ -844,7 +867,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig)
if (!kicked)
mqueue_dequeue_signal(mq, mtsig) ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
return kicked ;
} ;
@@ -930,8 +953,11 @@ mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread,
* Otherwise zeroises the structure, and returns address of same.
*/
extern mqueue_thread_signal
-mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure)
+mqueue_thread_signal_reset(mqueue_thread_signal mqt, free_keep_b free_structure)
{
+ if (mqt == NULL)
+ return NULL ;
+
passert(mqt->prev == NULL) ;
if (free_structure)
diff --git a/lib/mqueue.h b/lib/mqueue.h
index a28b6606..c787e19b 100644
--- a/lib/mqueue.h
+++ b/lib/mqueue.h
@@ -22,16 +22,11 @@
#ifndef _ZEBRA_MQUEUE_H
#define _ZEBRA_MQUEUE_H
-#include <stddef.h>
-#include <stdbool.h>
+#include "misc.h"
#include "qpthreads.h"
#include "qtime.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Message Queue Blocks -- mqb
*
@@ -196,83 +191,48 @@ struct mqueue_local_queue
* Functions
*/
-extern void
-mqueue_initialise(void) ;
-
-extern void
-mqueue_finish(void) ;
-
-extern mqueue_queue
-mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ;
-
-extern void
-mqueue_empty(mqueue_queue mq) ;
-
-extern mqueue_queue
-mqueue_reset(mqueue_queue mq, int free_structure) ;
-
-#define mqueue_reset_keep(mq) mqueue_reset(mq, 0)
-#define mqueue_reset_free(mq) mqueue_reset(mq, 1)
-
-extern mqueue_local_queue
-mqueue_local_init_new(mqueue_local_queue lmq) ;
-
-extern mqueue_local_queue
-mqueue_local_reset(mqueue_local_queue lmq, int free_structure) ;
+extern void mqueue_initialise(void) ;
+extern void mqueue_finish(void) ;
-#define mqueue_local_reset_keep(lmq) mqueue_local_reset(lmq, 0)
-#define mqueue_local_reset_free(lmq) mqueue_local_reset(lmq, 1)
+extern mqueue_queue mqueue_init_new(mqueue_queue mq,
+ enum mqueue_queue_type type) ;
+extern void mqueue_empty(mqueue_queue mq) ;
+extern mqueue_queue mqueue_reset(mqueue_queue mq, free_keep_b free_structure) ;
-extern void
-mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ;
+extern mqueue_local_queue mqueue_local_init_new(mqueue_local_queue lmq) ;
+extern mqueue_local_queue mqueue_local_reset(mqueue_local_queue lmq,
+ free_keep_b free_structure) ;
-extern mqueue_thread_signal
-mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread,
- int signum) ;
-mqueue_thread_signal
-mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) ;
+extern void mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ;
+extern mqueue_thread_signal mqueue_thread_signal_init(mqueue_thread_signal mqt,
+ qpt_thread_t thread, int signum) ;
+mqueue_thread_signal mqueue_thread_signal_reset(mqueue_thread_signal mqt,
+ free_keep_b free_structure) ;
-#define mqueue_thread_signal_reset_keep(mqt) mqueue_thread_signal_reset(mqt, 0)
-#define mqueue_thread_signal_reset_free(mqt) mqueue_thread_signal_reset(mqt, 1)
-
-extern mqueue_block
-mqb_init_new(mqueue_block mqb, mqueue_action action, void* arg0) ;
-
-extern mqueue_block
-mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) ;
-
-extern void
-mqb_free(mqueue_block mqb) ;
+extern mqueue_block mqb_init_new(mqueue_block mqb, mqueue_action action,
+ void* arg0) ;
+extern mqueue_block mqb_re_init(mqueue_block mqb, mqueue_action action,
+ void* arg0) ;
+extern mqueue_block mqb_free(mqueue_block mqb) ;
enum mqb_rank
{
mqb_priority = true,
mqb_ordinary = false
} ;
+typedef enum mqb_rank mqb_rank_b ;
-extern void
-mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) ;
-
-extern mqueue_block
-mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ;
-
-extern void
-mqueue_revoke(mqueue_queue mq, void* arg0) ;
+extern void mqueue_enqueue(mqueue_queue mq, mqueue_block mqb,
+ mqb_rank_b priority) ;
+extern mqueue_block mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ;
+extern int mqueue_revoke(mqueue_queue mq, void* arg0, int num) ;
-extern int
-mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ;
-
-extern void
-mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ;
-
-extern void
-mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ;
-
-Inline mqueue_block
-mqueue_local_head(mqueue_local_queue lmq) ;
+extern int mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ;
-extern mqueue_block
-mqueue_local_dequeue(mqueue_local_queue lmq) ;
+extern void mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ;
+extern void mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ;
+Inline mqueue_block mqueue_local_head(mqueue_local_queue lmq) ;
+extern mqueue_block mqueue_local_dequeue(mqueue_local_queue lmq) ;
/*==============================================================================
* Access functions for mqueue_block fields -- mqb_set_xxx/mqb_get_xxx
diff --git a/lib/network.c b/lib/network.c
index ae67a402..6f12805a 100644
--- a/lib/network.c
+++ b/lib/network.c
@@ -20,9 +20,27 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
+#include "misc.h"
+#include "qdebug_nb.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
#include "log.h"
#include "network.h"
+#include "memory.h"
+
+/*==============================================================================
+ * Read and write loops -- assuming blocking or not-blocking.
+ *
+ *
+ *
+ */
+
+static ssize_t read_qdebug_nb(int fd, void* buf, size_t nbyte) ;
+static ssize_t write_qdebug_nb(int fd, const void* buf, size_t nbyte) ;
/*------------------------------------------------------------------------------
* Read nbytes from fd and store into ptr -- BLOCKING
@@ -34,8 +52,8 @@
*
* NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
*/
-int
-readn (int fd, u_char *ptr, int nbytes)
+extern int
+readn (int fd, void* buf, int nbytes)
{
int nleft;
int nread;
@@ -44,15 +62,17 @@ readn (int fd, u_char *ptr, int nbytes)
while (nleft > 0)
{
- nread = read (fd, ptr, nleft);
+ nread = read (fd, buf, nleft);
if (nread > 0)
{
nleft -= nread;
- ptr += nread;
+ buf = (char*)buf + nread;
}
+
else if (nread == 0)
break;
+
else
{
if (errno != EINTR)
@@ -73,57 +93,122 @@ readn (int fd, u_char *ptr, int nbytes)
* -1 => failed -- see errno
* -2 => EOF met immediately
*
- * NB: if asked to write zero bytes, does nothing and will return 0.
+ * NB: if asked to read zero bytes, does nothing and will return 0.
*
* Reading zero bytes is defined for all types of files, and may be used
* to probe for error state.
*/
-int
+extern int
read_nb(int fd, void* buf, size_t nbyte)
{
- size_t nleft = nbyte ;
+ size_t nleft ;
+
+// char* p = buf ;
+
+ nleft = nbyte ;
do
{
- int ret = read(fd, buf, nleft);
+ ssize_t ret ;
+
+ if (qdebug_nb)
+ ret = read_qdebug_nb(fd, buf, nleft) ;
+ else
+ ret = read(fd, buf, nleft) ;
if (ret > 0)
{
buf = (char*)buf + ret ;
nleft -= ret ;
}
+
else if (ret == 0)
{
if (nleft < nbyte)
break ; /* if read something before EOF */
- return -2 ; /* hit EOF immediately */
+// fprintf(stderr, "[read (%d) %s]\n", fd, (nbyte == 0) ? "OK" : "EOF") ;
+
+ return (nbyte == 0) ? 0 : -2 ; /* OK or EOF */
}
+
else
{
int err = errno ;
if ((err == EAGAIN) || (err == EWOULDBLOCK))
break ;
if (err != EINTR)
+// assert(0) ; // PRO TEM
return -1 ; /* failed */
} ;
} while (nleft > 0) ;
+#if 0
+{
+ int n ;
+ char buffer[100] ;
+ char* q, * e ;
+ e = buffer + 95 ;
+
+ n = nbyte - nleft ;
+
+ fprintf(stderr, "[read (%d) %d: '", fd, n) ;
+ while (n > 0)
+ {
+ q = buffer ;
+ while ((q < e) && (n > 0))
+ {
+ char ch = *p++ ;
+ --n ;
+
+ if (ch < 0x20)
+ {
+ *q++ = '\\' ;
+ switch (ch)
+ {
+ case '\n':
+ ch = 'n' ;
+ break ;
+
+ case '\r':
+ ch = 'r' ;
+ break ;
+
+ case '\t':
+ ch = 't' ;
+ break ;
+
+ default:
+ ch += '@' ;
+ break ;
+ } ;
+ } ;
+
+ *q++ = ch ;
+ } ;
+ *q++ = '\0' ;
+
+ fprintf(stderr, "%s", buffer) ;
+ }
+ fprintf(stderr, "']\n") ;
+} ;
+#endif
+
return (nbyte - nleft) ;
} ;
/*------------------------------------------------------------------------------
- * Write nbytes to fd from ptr -- BLOCKING
+ * Write nbytes to fd from buf -- BLOCKING
*
* Loops internally if gets EINTR.
*
* Returns: >= 0 -- number of bytes written
- * < 0 => error
+ * -1 => error
*
* NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
*/
-int
-writen(int fd, const u_char *ptr, int nbytes)
+extern int
+writen(int fd, const void* buf, int nbytes)
{
int nleft;
int nwritten;
@@ -132,21 +217,23 @@ writen(int fd, const u_char *ptr, int nbytes)
while (nleft > 0)
{
- nwritten = write(fd, ptr, nleft);
+ nwritten = write(fd, buf, nleft);
- if (nwritten > 0)
+ if (nwritten >= 0)
{
+ /* write() is not expected to return 0 unless the request is 0,
+ * which in this case it isn't. So cannot happen -- and if it did,
+ * wouldn't know what else to do with it !
+ */
nleft -= nwritten;
- ptr += nwritten;
+ buf = (const char*)buf + nwritten;
}
- else if (nwritten == 0)
- break ;
else
{
if (errno != EINTR)
- return (nwritten);
- }
- }
+ return -1 ;
+ } ;
+ } ;
return nbytes - nleft;
}
@@ -163,29 +250,39 @@ writen(int fd, const u_char *ptr, int nbytes)
* Writing zero bytes is defined for "regular files", but not for anything
* else.
*/
-int
-write_nb(int fd, void* buf, size_t nbyte)
+extern int
+write_nb(int fd, const void* buf, size_t nbyte)
{
- size_t nleft = nbyte ;
+ size_t nleft ;
+
+ nleft = nbyte ;
while (nleft > 0)
{
- int ret = write(fd, buf, nleft);
+ ssize_t ret ;
- if (ret > 0)
+ if (qdebug_nb)
+ ret = write_qdebug_nb(fd, buf, nleft) ;
+ else
+ ret = write(fd, buf, nleft) ;
+
+ if (ret > 0)
{
- buf = (char*)buf + ret ;
+ buf = (const char*)buf + ret ;
nleft -= ret ;
}
else if (ret == 0)
- break ; /* not sure can happen... but
- cannot assume will go away */
+ /* write() is not expected to return 0 unless the request is 0,
+ * which in this case it isn't. So cannot happen -- but if it were
+ * to happen, this treats it as another form of "EAGAIN" !
+ */
+ break ;
else
{
- int err = errno ;
- if ((err == EAGAIN) || (err == EWOULDBLOCK))
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
break ;
- if (err != EINTR)
+ if (errno != EINTR)
+// assert(0) ; // PRO TEM
return -1 ; /* failed */
} ;
} ;
@@ -194,12 +291,46 @@ write_nb(int fd, void* buf, size_t nbyte)
} ;
/*------------------------------------------------------------------------------
+ * Copy from one fd to another -- blocking.
+ *
+ * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
+ */
+extern int
+copyn (int dst_fd, int src_fd)
+{
+ void* buffer ;
+ int r, err ;
+
+ enum { buffer_size = 64 * 1024 } ;
+ buffer = XMALLOC(MTYPE_TMP, buffer_size) ;
+
+ do
+ {
+ r = readn(src_fd, buffer, buffer_size) ;
+ if (r > 0)
+ r = writen(dst_fd, buffer, r) ;
+ }
+ while (r > 0) ;
+
+ err = errno ;
+
+ XFREE(MTYPE_TMP, buffer) ;
+
+ errno = err ;
+ return r ;
+} ;
+
+/*==============================================================================
+ *
+ */
+
+/*------------------------------------------------------------------------------
* Set fd to non-blocking
*
* Returns: 0 => OK
* -1 => failed
*/
-int
+extern int
set_nonblocking(int fd)
{
int flags;
@@ -220,3 +351,52 @@ set_nonblocking(int fd)
}
return 0;
}
+
+/*==============================================================================
+ * Simulate read/write with tiny buffers and a lot of blocking.
+ */
+
+static qrand_seq_t rseq = QRAND_SEQ_INIT(2001) ;
+static qrand_seq_t wseq = QRAND_SEQ_INIT(3001) ;
+
+static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ;
+
+/*------------------------------------------------------------------------------
+ * Simulate read(), with tiny input buffer and lots of blocking.
+ */
+static ssize_t
+read_qdebug_nb(int fd, void* buf, size_t nbyte)
+{
+ if (nbyte > 0)
+ {
+ if (qrand(rseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(rseq, 3)] ;
+ return -1 ;
+ } ;
+
+ nbyte = qrand(rseq, (nbyte < 200) ? nbyte : 200) + 1 ;
+ } ;
+
+
+ return read(fd, buf, nbyte) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Simulate write(), with tiny input buffer and lots of blocking.
+ */
+static ssize_t
+write_qdebug_nb(int fd, const void* buf, size_t nbyte)
+{
+ assert(nbyte > 0) ;
+
+ if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(wseq, 3)] ;
+ return -1 ;
+ } ;
+
+ nbyte = qrand(wseq, (nbyte < 200) ? nbyte : 200) + 1 ;
+
+ return write(fd, buf, nbyte) ;
+} ;
diff --git a/lib/network.h b/lib/network.h
index 72d38b52..0e626e72 100644
--- a/lib/network.h
+++ b/lib/network.h
@@ -26,8 +26,9 @@
/* Both readn and writen are deprecated and will be removed. They are not
suitable for use with non-blocking file descriptors.
*/
-extern int readn (int, u_char *, int);
-extern int writen (int, const u_char *, int);
+extern int readn (int, void*, int);
+extern int writen (int, const void*, int);
+extern int copyn (int dst_fd, int src_fd) ;
/* Set the file descriptor to use non-blocking I/O. Returns 0 for success,
-1 on error. */
@@ -35,7 +36,7 @@ extern int set_nonblocking(int fd);
/* Non-Blocking versions of read/write */
int read_nb(int fd, void* buf, size_t nbyte) ;
-int write_nb(int fd, void* buf, size_t nbyte) ;
+int write_nb(int fd, const void* buf, size_t nbyte) ;
/* Does the I/O error indicate that the operation should be retried later? */
#define ERRNO_IO_RETRY(EN) \
diff --git a/lib/node_type.h b/lib/node_type.h
deleted file mode 100644
index 7ec1107d..00000000
--- a/lib/node_type.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* Command handler node_type stuff -- header
- * Copyright (C) 1997, 98 Kunihiro Ishiguro
- *
- * This file is part of GNU Zebra.
- *
- * GNU Zebra is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * GNU Zebra is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#ifndef _ZEBRA_NODE_TYPE_H
-#define _ZEBRA_NODE_TYPE_H
-
-/* There are some command levels which called from command node. */
-enum node_type
-{
- AUTH_NODE, /* Authentication mode of vty interface. */
- RESTRICTED_NODE, /* Restricted view mode */
- VIEW_NODE, /* View node. Default mode of vty interface. */
- AUTH_ENABLE_NODE, /* Authentication mode for change enable. */
- ENABLE_NODE, /* Enable node. */
-
- MIN_DO_SHORTCUT_NODE = ENABLE_NODE,
- /* May not "do xxx" at any node lower */
- MAX_NON_CONFIG_NODE = ENABLE_NODE,
- /* May not be higher than this without owning
- * the configuration symbol of power */
-
- CONFIG_NODE, /* Config node. Default mode of config file. */
-
- MIN_CONTEXT_NODE = CONFIG_NODE,
- /* May not change context to any node lower */
-
- SERVICE_NODE, /* Service node. */
- DEBUG_NODE, /* Debug node. */
- AAA_NODE, /* AAA node. */
- KEYCHAIN_NODE, /* Key-chain node. */
- KEYCHAIN_KEY_NODE, /* Key-chain key node. */
- INTERFACE_NODE, /* Interface mode node. */
- ZEBRA_NODE, /* zebra connection node. */
- TABLE_NODE, /* rtm_table selection node. */
- RIP_NODE, /* RIP protocol mode node. */
- RIPNG_NODE, /* RIPng protocol mode node. */
- BGP_NODE, /* BGP protocol mode which includes BGP4+ */
- BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */
- BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */
- BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */
- BGP_IPV6_NODE, /* BGP IPv6 address family */
- BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */
- OSPF_NODE, /* OSPF protocol mode */
- OSPF6_NODE, /* OSPF protocol for IPv6 mode */
- ISIS_NODE, /* ISIS protocol mode */
- MASC_NODE, /* MASC for multicast. */
- IRDP_NODE, /* ICMP Router Discovery Protocol mode. */
- IP_NODE, /* Static ip route node. */
- ACCESS_NODE, /* Access list node. */
- PREFIX_NODE, /* Prefix list node. */
- ACCESS_IPV6_NODE, /* Access list node. */
- PREFIX_IPV6_NODE, /* Prefix list node. */
- AS_LIST_NODE, /* AS list node. */
- COMMUNITY_LIST_NODE, /* Community list node. */
- RMAP_NODE, /* Route map node. */
- SMUX_NODE, /* SNMP configuration node. */
- DUMP_NODE, /* Packet dump node. */
- FORWARDING_NODE, /* IP forwarding node. */
- PROTOCOL_NODE, /* protocol filtering node */
- VTY_NODE, /* Vty node. */
-};
-
-#endif /* _ZEBRA_NODE_TYPE_H */
diff --git a/lib/plist.c b/lib/plist.c
index e5c71d35..c761c5c5 100644
--- a/lib/plist.c
+++ b/lib/plist.c
@@ -268,7 +268,7 @@ static void
prefix_master_reset(struct prefix_master * pm)
{
struct prefix_list* plist ;
- while ((plist = symbol_table_ream_keep(&(pm->table))))
+ while ((plist = symbol_table_ream(&(pm->table), keep_it)))
prefix_list_delete(plist) ;
pm->seqnum_flag = 1 ; /* Default is to generate sequence numbers. */
@@ -326,7 +326,8 @@ prefix_list_set_ref(prefix_list_ref* p_ref, afi_t afi, const char* name)
if (pm == NULL)
return NULL ;
- return *p_ref = symbol_set_ref(*p_ref, symbol_find(&(pm->table), name)) ;
+ return *p_ref = symbol_set_ref(*p_ref,
+ symbol_lookup(&(pm->table), name, add)) ;
} ;
/* Copy reference to prefix_list.
@@ -389,7 +390,7 @@ prefix_list_apply (struct prefix_list *plist, void *object)
struct prefix *p ;
int plen ;
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
in_addr_t ip ;
#ifdef s6_addr32
@@ -575,7 +576,7 @@ prefix_list_delete (struct prefix_list* plist)
static struct prefix_list *
prefix_list_seek (struct prefix_master* pm, const char *name)
{
- return symbol_get_value(symbol_seek(&(pm->table), name)) ;
+ return symbol_get_value(symbol_lookup(&(pm->table), name, no_add)) ;
} ;
/* Lookup prefix_list by afi and name -- if afi is known, and name not NULL.
@@ -603,7 +604,7 @@ prefix_list_get (struct prefix_master* pm, const char *name, afi_t afi)
assert((pm != NULL) && (name != NULL)) ;
- sym = symbol_find(&(pm->table), name) ; /* creates if required */
+ sym = symbol_lookup(&(pm->table), name, add) ; /* creates if required */
plist = symbol_get_value(sym) ;
return plist ? plist : prefix_list_new(pm, sym, afi) ;
@@ -691,7 +692,7 @@ prefix_list_entry_ge_le_check(struct prefix_list_entry* pe, afi_t afi)
* result > 0 -- not found. index returned is of the entry with the largest
* sequence number smaller than the given one.
*/
-static vector_index
+static vector_index_t
prefix_list_entry_lookup_seq(struct prefix_list *plist, int seq, int* result)
{
return vector_bsearch(&plist->list, (vector_bsearch_cmp*)prefix_seq_cmp,
@@ -725,7 +726,7 @@ prefix_list_entry_lookup_seq(struct prefix_list *plist, int seq, int* result)
*
* Note that the cache is never empty.
*/
-static vector_index
+static vector_index_t
prefix_list_entry_lookup_val(struct prefix_list *plist,
struct prefix_list_entry* temp,
vector* cache,
@@ -753,7 +754,7 @@ prefix_list_entry_lookup_val(struct prefix_list *plist,
else
{
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
*cache = NULL ; /* Not found in cache. */
*result = 0 ; /* Assume found ! */
for (VECTOR_ITEMS(&plist->list, pe, i))
@@ -781,12 +782,12 @@ prefix_list_entry_lookup_val(struct prefix_list *plist,
* -- result != 0 not found -- index is immaterial
*
* */
-static vector_index
+static vector_index_t
prefix_list_entry_lookup (struct prefix_list* plist,
struct prefix_list_entry* pe_seek, int* result)
{
struct prefix_list_entry* pe_found ;
- vector_index i ;
+ vector_index_t i ;
if (pe_seek->flags & PREFIX_SEQ)
{
@@ -840,7 +841,7 @@ prefix_list_entry_insert(struct prefix_list *plist,
{
struct prefix_list_entry* pe ;
vector cache ;
- vector_index i, ic ;
+ vector_index_t i, ic ;
int ret, retc ;
u_int32_t mask ;
int pl, sh ;
@@ -901,7 +902,7 @@ prefix_list_entry_insert(struct prefix_list *plist,
if (cache)
{
/* We need to know where the old value was. */
- vector_index io ;
+ vector_index_t io ;
int reto ;
io = vector_bsearch(cache, (vector_bsearch_cmp*)plist->cmp, pe,
&reto) ;
@@ -1005,7 +1006,7 @@ prefix_list_entry_delete (struct prefix_list *plist,
struct prefix_list_entry *pe_seek)
{
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
int ret ;
i = prefix_list_entry_lookup (plist, pe_seek, &ret) ;
@@ -1155,7 +1156,7 @@ static void __attribute__ ((unused))
prefix_list_print (struct prefix_list *plist)
{
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
struct vty* vty = NULL ;
if (plist == NULL)
@@ -1461,7 +1462,7 @@ vty_show_prefix_entry (struct vty *vty, struct prefix_list *plist,
if (dtype != summary_display)
{
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
int with_seq = pm->seqnum_flag ;
int with_stats = (dtype == detail_display)
||(dtype == sequential_display) ;
@@ -1507,7 +1508,7 @@ vty_show_prefix_list (struct vty *vty, afi_t afi, const char *name,
else
{
vector extract ;
- vector_index i ;
+ vector_index_t i ;
struct symbol* sym ;
if (dtype == detail_display || dtype == summary_display)
@@ -1542,7 +1543,7 @@ vty_show_prefix_list_prefix (struct vty *vty, afi_t afi, const char *name,
{
struct prefix_list *plist;
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
struct prefix p;
int ret;
int match;
@@ -1598,7 +1599,7 @@ vty_clear_prefix_list (struct vty *vty, afi_t afi, const char *name,
int ret;
struct prefix p;
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
pm = prefix_master_get (afi);
if (pm == NULL)
@@ -2820,7 +2821,7 @@ config_write_prefix_afi (afi_t afi, struct vty *vty)
struct prefix_master *pm;
int write = 0;
vector extract ;
- vector_index i, ipe ;
+ vector_index_t i, ipe ;
struct symbol* sym ;
pm = prefix_master_get (afi);
@@ -2875,7 +2876,7 @@ prefix_bgp_orf_entry (struct stream *s, prefix_list_ref ref,
u_char init_flag, u_char permit_flag, u_char deny_flag)
{
struct prefix_list_entry *pe;
- vector_index i ;
+ vector_index_t i ;
struct prefix_list *plist = prefix_list_ref_plist(ref) ;
@@ -2900,7 +2901,7 @@ prefix_bgp_orf_entry (struct stream *s, prefix_list_ref ref,
* return 0 - no such entry
*/
int
-prefix_bgp_orf_get(struct prefix_list *plist, vector_index i,
+prefix_bgp_orf_get(struct prefix_list *plist, vector_index_t i,
struct orf_prefix *orfpe, enum prefix_list_type *pe_type)
{
struct prefix_list_entry *pe = NULL;
@@ -2991,7 +2992,7 @@ prefix_bgp_show_prefix_list (struct vty *vty, afi_t afi, char *name)
if (vty)
{
struct prefix_list_entry* pe ;
- vector_index i ;
+ vector_index_t i ;
vty_prefix_list_name_count_print(vty, plist, VTY_NEWLINE) ;
diff --git a/lib/plist.h b/lib/plist.h
index 4f383417..4abddcd0 100644
--- a/lib/plist.h
+++ b/lib/plist.h
@@ -62,7 +62,7 @@ extern const char* prefix_list_get_name(struct prefix_list* plist) ;
extern struct stream * prefix_bgp_orf_entry (struct stream *,
prefix_list_ref ref,
u_char, u_char, u_char);
-extern int prefix_bgp_orf_get(struct prefix_list *plist, vector_index i,
+extern int prefix_bgp_orf_get(struct prefix_list *plist, vector_index_t i,
struct orf_prefix *orfpe, enum prefix_list_type *pe_type);
extern int prefix_bgp_orf_set (char *, afi_t, struct orf_prefix *, int, int);
extern void prefix_bgp_orf_remove_all (char *);
diff --git a/lib/prefix.c b/lib/prefix.c
index 867c86a3..d511a535 100644
--- a/lib/prefix.c
+++ b/lib/prefix.c
@@ -27,6 +27,7 @@
#include "sockunion.h"
#include "memory.h"
#include "log.h"
+#include "tstring.h"
/* Maskbit. */
static const u_char maskbit[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0,
@@ -273,18 +274,19 @@ prefix_ipv4_free (struct prefix_ipv4 *p)
int
str2prefix_ipv4 (const char *str, struct prefix_ipv4 *p)
{
- char* pnt ;
- char* cp ;
- int ret ;
- unsigned plen ;
+ tstring_t(ipv4, 24) ;
+ char* pnt ;
+ const char* cp ;
+ int ret ;
+ unsigned plen ;
pnt = strchr (str, '/');
if (pnt == NULL)
{
/* No / => simple address */
- plen = IPV4_MAX_BITLEN;
- ret = inet_aton (str, &p->prefix);
+ plen = IPV4_MAX_BITLEN ;
+ cp = str ;
}
else
{
@@ -293,19 +295,18 @@ str2prefix_ipv4 (const char *str, struct prefix_ipv4 *p)
if (plen > IPV4_MAX_PREFIXLEN)
return 0;
- cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1);
- strncpy (cp, str, pnt - str);
- *(cp + (pnt - str)) = '\0';
- ret = inet_aton (cp, &p->prefix);
- XFREE (MTYPE_TMP, cp);
- }
+ cp = tstring_set_n(ipv4, str, (pnt - str)) ;
+ } ;
+ ret = inet_aton (cp, &p->prefix);
if (ret <= 0) /* should not return < 0, but it would not be valid ! */
return 0;
p->family = AF_INET;
p->prefixlen = plen;
+ tstring_free(ipv4) ;
+
return 1 ;
}
@@ -435,10 +436,11 @@ prefix_ipv6_free (struct prefix_ipv6 *p)
int
str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p)
{
- char* pnt ;
- char* cp ;
- int ret ;
- unsigned plen ;
+ tstring_t(ipv6, 64) ;
+ char* pnt ;
+ const char* cp ;
+ int ret ;
+ unsigned plen ;
pnt = strchr (str, '/');
@@ -446,7 +448,7 @@ str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p)
{
/* No / => simple address */
plen = IPV6_MAX_BITLEN;
- ret = inet_pton (AF_INET6, str, &p->prefix);
+ cp = str ;
}
else
{
@@ -455,19 +457,18 @@ str2prefix_ipv6 (const char *str, struct prefix_ipv6 *p)
if (plen > IPV6_MAX_PREFIXLEN)
return 0 ;
- cp = XMALLOC (MTYPE_TMP, (pnt - str) + 1);
- strncpy (cp, str, pnt - str);
- *(cp + (pnt - str)) = '\0';
- ret = inet_pton (AF_INET6, cp, &p->prefix);
- XFREE (MTYPE_TMP, cp);
- }
+ cp = tstring_set_n(ipv6, str, (pnt - str)) ;
+ } ;
+ ret = inet_pton (AF_INET6, cp, &p->prefix);
if (ret <= 0)
return 0 ;
p->family = AF_INET6;
p->prefixlen = plen;
+ tstring_free(ipv6) ;
+
return 1 ;
}
diff --git a/lib/prefix.h b/lib/prefix.h
index ca7ee687..7944fd69 100644
--- a/lib/prefix.h
+++ b/lib/prefix.h
@@ -23,12 +23,9 @@
#ifndef _ZEBRA_PREFIX_H
#define _ZEBRA_PREFIX_H
+#include "misc.h"
#include "sockunion.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
typedef const union sockunion* const_sockunion ;
/*
diff --git a/lib/privs.c b/lib/privs.c
index be3265ed..46dc0977 100644
--- a/lib/privs.c
+++ b/lib/privs.c
@@ -21,7 +21,7 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
-#include <zebra.h>
+#include "zebra.h"
#include "log.h"
#include "privs.h"
#include "memory.h"
@@ -29,8 +29,8 @@
/* Needs to be qpthread safe */
static qpt_mutex_t privs_mutex;
-#define LOCK qpt_mutex_lock(&privs_mutex);
-#define UNLOCK qpt_mutex_unlock(&privs_mutex);
+#define LOCK qpt_mutex_lock(privs_mutex);
+#define UNLOCK qpt_mutex_unlock(privs_mutex);
#ifdef HAVE_CAPABILITIES
/* sort out some generic internal types for:
@@ -675,20 +675,28 @@ zprivs_state_null (void)
void
zprivs_init_r()
{
- qpt_mutex_init(&privs_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(privs_mutex, qpt_mutex_quagga);
}
void
zprivs_finish(void)
{
- qpt_mutex_destroy(&privs_mutex, 0);
+ qpt_mutex_destroy(privs_mutex, 0);
}
+/*------------------------------------------------------------------------------
+ * Initialise privilege handling and set any running group.
+ *
+ * Returns with lowered privileges.
+ *
+ * Must be initialised before vty. Will be initialised very early in the
+ * morning, before daemonisation and therefore before any pthreads.
+ */
void
zprivs_init(struct zebra_privs_t *zprivs)
{
struct passwd *pwentry = NULL;
- struct group *grentry = NULL;
+ struct group *grentry = NULL;
if (!zprivs)
{
@@ -696,18 +704,18 @@ zprivs_init(struct zebra_privs_t *zprivs)
exit (1);
}
- LOCK
-
- /* NULL privs */
- if (! (zprivs->user || zprivs->group
- || zprivs->cap_num_p || zprivs->cap_num_i) )
+ /* NULL privs */
+ if ( (zprivs->user == NULL) && (zprivs->group == NULL)
+ && (zprivs->cap_num_p == 0)
+ && (zprivs->cap_num_i == 0) )
{
- zprivs->change = zprivs_change_null;
+ zprivs->change = zprivs_change_null;
zprivs->current_state = zprivs_state_null;
- UNLOCK
+
return;
}
+ /* Get uid for configured user (if any) */
if (zprivs->user)
{
if ( (pwentry = getpwnam (zprivs->user)) )
@@ -719,11 +727,11 @@ zprivs_init(struct zebra_privs_t *zprivs)
/* cant use log.h here as it depends on vty */
fprintf (stderr, "privs_init: could not lookup user %s\n",
zprivs->user);
- UNLOCK
exit (1);
}
}
+ /* Get gid for vty_group and add to our groups */
grentry = NULL;
if (zprivs->vty_group)
@@ -736,7 +744,6 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not setgroups, %s\n",
errtostr(errno, 0).str) ;
- UNLOCK
exit (1);
}
}
@@ -744,11 +751,11 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not lookup vty group %s\n",
zprivs->vty_group);
- UNLOCK
exit (1);
}
}
+ /* Get gid for configured group and switch to same, now. */
if (zprivs->group)
{
if ( (grentry = getgrnam (zprivs->group)) )
@@ -759,15 +766,13 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not lookup group %s\n",
zprivs->group);
- UNLOCK
exit (1);
}
- /* change group now, forever. uid we do later */
+ /* change group now, forever. uid we do later */
if ( setregid (zprivs_state.zgid, zprivs_state.zgid) )
{
fprintf (stderr, "zprivs_init: could not setregid, %s\n",
errtostr(errno, 0).str) ;
- UNLOCK
exit (1);
}
}
@@ -775,7 +780,8 @@ zprivs_init(struct zebra_privs_t *zprivs)
#ifdef HAVE_CAPABILITIES
zprivs_caps_init (zprivs);
#else /* !HAVE_CAPABILITIES */
- /* we dont have caps. we'll need to maintain rid and saved uid
+
+ /* We don't have caps. we'll need to maintain rid and saved uid
* and change euid back to saved uid (who we presume has all necessary
* privileges) whenever we are asked to raise our privileges.
*
@@ -784,20 +790,23 @@ zprivs_init(struct zebra_privs_t *zprivs)
zprivs_state.zsuid = geteuid();
if ( zprivs_state.zuid )
{
+ /* If the zuid != real uid, will set the saved uid == zuid, leaving
+ * just the two zuid to choose from -- though if the real uid is root,
+ * this makes little difference.
+ */
if ( setreuid (-1, zprivs_state.zuid) )
{
fprintf (stderr, "privs_init (uid): could not setreuid, %s\n",
errtoa(errno, 0).str);
- UNLOCK
exit (1);
}
}
- zprivs->change = zprivs_change_uid;
- zprivs->current_state = zprivs_state_uid;
+ zprivs->change = zprivs_change_uid ;
+ zprivs->current_state = zprivs_state_uid ;
+
#endif /* HAVE_CAPABILITIES */
- UNLOCK
}
void
@@ -826,9 +835,9 @@ zprivs_terminate (struct zebra_privs_t *zprivs)
}
#endif /* HAVE_LCAPS */
- zprivs->change = zprivs_change_null;
+ zprivs->change = zprivs_change_null;
zprivs->current_state = zprivs_state_null;
- zprivs_null_state = ZPRIVS_LOWERED;
+ zprivs_null_state = ZPRIVS_LOWERED;
raise_count = 0;
UNLOCK
@@ -841,12 +850,12 @@ zprivs_get_ids(struct zprivs_ids_t *ids)
LOCK
ids->uid_priv = getuid();
- (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
- : (ids->uid_normal = -1);
- (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
- : (ids->gid_normal = -1);
+ (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
+ : (ids->uid_normal = -1);
+ (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
+ : (ids->gid_normal = -1);
(zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp)
- : (ids->gid_vty = -1);
+ : (ids->gid_vty = -1);
UNLOCK
return;
diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c
index c4ab9679..336f686e 100644
--- a/lib/pthread_safe.c
+++ b/lib/pthread_safe.c
@@ -25,6 +25,12 @@
* safe_ function is called on the same thread
*/
+/* Need "NO_USE_GNU" to turn off __USE_GNU in "misc.h" in order to get the
+ * POSIX version of strerror_r() :-(
+ */
+#define NO_USE_GNU
+
+#include "misc.h"
#include "pthread_safe.h"
#include "qpthreads.h"
#include "memory.h"
@@ -41,14 +47,25 @@
#include "qfstring.h"
#include "errno_names.h"
-/* prototypes */
-static void destructor(void* data);
-static char * thread_buff(void);
-
+/*==============================================================================
+ * Initialisation, close down and local variables.
+ *
+ * Note that the "thread_safe" mutex is recursive, so one thread safe function
+ * can call another, if required.
+ */
static pthread_key_t tsd_key;
static const int buff_size = 1024;
-/* Module initialization, before any threads have been created */
+static qpt_mutex thread_safe = NULL ;
+
+static const char ellipsis[] = "..." ;
+
+static void destructor(void* data);
+static char * thread_buff(void);
+
+/*------------------------------------------------------------------------------
+ * Module initialization, before any threads have been created
+ */
void
safe_init_r(void)
{
@@ -58,50 +75,36 @@ safe_init_r(void)
status = pthread_key_create(&tsd_key, destructor);
if (status != 0)
zabort("Can't create thread specific data key");
- }
+
+ qassert(thread_safe == NULL) ;
+
+ thread_safe = qpt_mutex_init_new(NULL, qpt_mutex_recursive) ;
+ } ;
}
-/* Clean up */
+/*------------------------------------------------------------------------------
+ * Clean up
+ */
void
safe_finish(void)
{
if (qpthreads_enabled)
- pthread_key_delete(tsd_key);
-}
+ {
+ pthread_key_delete(tsd_key) ;
-/* called when thread terminates, clean up */
+ thread_safe = qpt_mutex_destroy(thread_safe, free_it) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * called when thread terminates, clean up
+ */
static void
destructor(void* data)
{
XFREE(MTYPE_TSD, data);
}
-/* Thread safe version of strerror. Never returns NULL.
- * Contents of result remains intact until another call of
- * a safe_ function.
- */
-const char *
-safe_strerror(int errnum)
-{
- static const char * unknown = "Unknown error";
- if (qpthreads_enabled)
- {
- char * buff = thread_buff();
- int ret = strerror_r(errnum, buff, buff_size);
-
- return (ret >= 0)
- ? buff
- : unknown;
- }
- else
- {
- const char *s = strerror(errnum);
- return (s != NULL)
- ? s
- : unknown;
- }
-}
-
/*==============================================================================
* Alternative error number handling.
*
@@ -134,7 +137,7 @@ safe_strerror(int errnum)
* to show this has happened.
*/
-static void errtox(strerror_t* st, int err, int len, int want) ;
+static void errtox(strerror_t* st, int err, uint len, uint want) ;
/*------------------------------------------------------------------------------
* Construct string to describe the given error of the form:
@@ -144,7 +147,7 @@ static void errtox(strerror_t* st, int err, int len, int want) ;
* Thread safe extension to strerror. Never returns NULL.
*/
extern strerror_t
-errtoa(int err, int len)
+errtoa(int err, uint len)
{
strerror_t st ;
@@ -159,7 +162,7 @@ errtoa(int err, int len)
* Thread-safe. Never returns NULL.
*/
extern strerror_t
-errtoname(int err, int len)
+errtoname(int err, uint len)
{
strerror_t st ;
@@ -174,7 +177,7 @@ errtoname(int err, int len)
* Thread safe replacement for strerror. Never returns NULL.
*/
extern strerror_t
-errtostr(int err, int len)
+errtostr(int err, uint len)
{
strerror_t st ;
@@ -189,21 +192,23 @@ errtostr(int err, int len)
* want == 1 -- return just name
* want == 2 -- return just the strerror()
* want == 3 -- return both, with the strerror() in single quotes.
+ *
+ * NB: this is not async-signal-safe !
*/
static void
-errtox(strerror_t* st, int err, int len, int want)
+errtox(strerror_t* st, int err, uint len, uint want)
{
qf_str_t qfs ;
const char* q ;
- int ql ;
+ uint ql ;
/* Prepare. */
int errno_saved = errno ;
- if ((len <= 0) || (len >= (int)sizeof(st->str)))
- len = sizeof(st->str) - 1 ;
- qfs_init(&qfs, st->str, len + 1) ;
+ if ((len <= 0) || (len >= sizeof(st->str)))
+ len = sizeof(st->str) ;
+ qfs_init(qfs, st->str, len) ;
q = "" ;
ql = 0 ;
@@ -214,15 +219,15 @@ errtox(strerror_t* st, int err, int len, int want)
const char* name = errno_name_lookup(err) ;
if (name != NULL)
- qfs_append(&qfs, name) ;
+ qfs_append(qfs, name) ;
else
- qfs_printf(&qfs, "ERRNO=%d", err) ;
+ qfs_printf(qfs, "ERRNO=%d", err) ;
} ;
/* name and string ? */
if (want == 3)
{
- qfs_append(&qfs, " ") ;
+ qfs_append(qfs, " ") ;
q = "'" ;
ql = 2 ;
} ;
@@ -285,24 +290,30 @@ errtox(strerror_t* st, int err, int len, int want)
else
{
qf_str_t qfs_b ;
- qfs_init(&qfs_b, buf, sizeof(buf)) ;
- qfs_printf(&qfs_b, "strerror%s(%d) returned error %d",
+ qfs_init(qfs_b, buf, sizeof(buf)) ;
+ qfs_printf(qfs_b, "strerror%s(%d) returned error %d",
qpthreads_enabled ? "_r" : "", err, ret) ;
errm = buf ;
} ;
} ;
} ;
- /* Add strerror to the result... looking out for overflow. */
- len = strlen(errm) ;
+ /* Add strerror to the result... with quotes as rquired */
+ if (ql != 0)
+ qfs_append(qfs, q) ;
- if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */
- qfs_printf(&qfs, "%s%s%s", q, errm, q) ;
- else
- qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, errm, q) ;
- /* -ve precision is ignored ! */
+ qfs_append(qfs, errm) ;
+
+ if (ql != 0)
+ qfs_append(qfs, q) ;
} ;
+ /* '\0' terminate -- if has overflowed, replace last few characters
+ * by "..." -- noting that sizeof("...") includes the '\0'.
+ */
+ if (qfs_term(qfs) != 0)
+ qfs_term_string(qfs, ellipsis, sizeof(ellipsis)) ;
+
/* Put back errno */
errno = errno_saved ;
} ;
@@ -340,7 +351,7 @@ errtox(strerror_t* st, int err, int len, int want)
* error.
*/
-static void eaitox(strerror_t* st, int eai, int err, int len, int want) ;
+static void eaitox(strerror_t* st, int eai, int err, uint len, uint want) ;
/*------------------------------------------------------------------------------
* Construct string to describe the given EAI_XXX error of the form:
@@ -351,7 +362,7 @@ static void eaitox(strerror_t* st, int eai, int err, int len, int want) ;
* Thread safe. Never returns NULL.
*/
extern strerror_t
-eaitoa(int eai, int err, int len)
+eaitoa(int eai, int err, uint len)
{
strerror_t st ;
@@ -368,7 +379,7 @@ eaitoa(int eai, int err, int len)
* Thread-safe. Never returns NULL.
*/
extern strerror_t
-eaitoname(int eai, int err, int len)
+eaitoname(int eai, int err, uint len)
{
strerror_t st ;
@@ -385,7 +396,7 @@ eaitoname(int eai, int err, int len)
* Thread-safe. Never returns NULL.
*/
extern strerror_t
-eaitostr(int eai, int err, int len)
+eaitostr(int eai, int err, uint len)
{
strerror_t st ;
@@ -404,12 +415,12 @@ eaitostr(int eai, int err, int len)
* err != 0 => if EAI_SYSTEM, return result for errno == err instead.
*/
static void
-eaitox(strerror_t* st, int eai, int err, int len, int want)
+eaitox(strerror_t* st, int eai, int err, uint len, uint want)
{
qf_str_t qfs ;
const char* q ;
- int ql ;
+ uint ql ;
/* Look out for mapping EAI_SYSTEM */
if ((eai == EAI_SYSTEM) && (err != 0))
@@ -420,7 +431,7 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
if ((len <= 0) || (len >= (int)sizeof(st->str)))
len = sizeof(st->str) - 1 ;
- qfs_init(&qfs, st->str, len + 1) ;
+ qfs_init(qfs, st->str, len + 1) ;
q = "" ;
ql = 0 ;
@@ -431,15 +442,15 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
const char* name = eaino_name_lookup(eai) ;
if (name != NULL)
- qfs_append(&qfs, name) ;
+ qfs_append(qfs, name) ;
else
- qfs_printf(&qfs, "EAI=%d", eai) ;
+ qfs_printf(qfs, "EAI=%d", eai) ;
} ;
/* name and string ? */
if (want == 3)
{
- qfs_append(&qfs, " ") ;
+ qfs_append(qfs, " ") ;
q = "'" ;
ql = 2 ;
} ;
@@ -454,23 +465,103 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
else
eaim = gai_strerror(eai) ;
- /* Add strerror to the result... looking out for overflow. */
- len = strlen(eaim) ;
+ /* Add strerror to the result... with quotes as rquired */
+ if (ql != 0)
+ qfs_append(qfs, q) ;
- if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */
- qfs_printf(&qfs, "%s%s%s", q, eaim, q) ;
- else
- qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, eaim, q) ;
- /* -ve precision is ignored ! */
+ qfs_append(qfs, eaim) ;
+
+ if (ql != 0)
+ qfs_append(qfs, q) ;
+
+ /* '\0' terminate -- if has overflowed, replace last few characters
+ * by "..." -- noting that sizeof("...") includes the '\0'.
+ */
+ if (qfs_term(qfs) != 0)
+ qfs_term_string(qfs, ellipsis, sizeof(ellipsis)) ;
} ;
/* Put back errno */
errno = errno_saved ;
} ;
-/*============================================================================*/
+/*==============================================================================
+ * Miscellaneous thread-safe functions
+ */
+
+/*------------------------------------------------------------------------------
+ * getenv_r -- fetch environment variable into the given buffer.
+ *
+ * If buffer is not long enough, fetches as much as can and '\0' terminates.
+ *
+ * Returns: -1 => not found -- buffer set empty
+ * >= 0 == length of environment variable
+ *
+ * NB: this is NOT signal safe. If need value of environment variable in
+ * a signal action -- make OTHER arrangements !!
+ */
+extern int
+getenv_r(const char* name, char* buf, int buf_len)
+{
+ char* val ;
+ int len ;
+ int cl ;
+
+ qpt_mutex_lock(thread_safe) ;
+
+ val = getenv(name) ;
+ if (val == NULL)
+ {
+ len = -1 ;
+ cl = 0 ;
+ }
+ else
+ {
+ len = strlen(val) ;
+ cl = (len < buf_len) ? len : buf_len - 1 ;
+ } ;
+
+ if (buf_len > 0)
+ {
+ if (cl > 0)
+ memcpy(buf, val, cl) ;
+ buf[cl] = '\0' ;
+ } ;
+
+ qpt_mutex_unlock(thread_safe) ;
+
+ return len ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Thread safe version of strerror. Never returns NULL.
+ * Contents of result remains intact until another call of
+ * a safe_ function.
+ */
+const char *
+safe_strerror(int errnum)
+{
+ static const char * unknown = "Unknown error";
+ if (qpthreads_enabled)
+ {
+ char * buff = thread_buff();
+ int ret = strerror_r(errnum, buff, buff_size);
+
+ return (ret >= 0)
+ ? buff
+ : unknown;
+ }
+ else
+ {
+ const char *s = strerror(errnum);
+ return (s != NULL)
+ ? s
+ : unknown;
+ }
+}
-/* Thread safe version of inet_ntoa. Never returns NULL.
+/*------------------------------------------------------------------------------
+ * Thread safe version of inet_ntoa. Never returns NULL.
* Contents of result remains intact until another call of
* a safe_ function.
*/
@@ -489,7 +580,8 @@ safe_inet_ntoa (struct in_addr in)
: unknown;
}
-/* Return the thread's buffer, create it if necessary.
+/*------------------------------------------------------------------------------
+ * Return the thread's buffer, create it if necessary.
* (pthread Thread Specific Data)
*/
static char *
diff --git a/lib/pthread_safe.h b/lib/pthread_safe.h
index 9518f3d1..f3974081 100644
--- a/lib/pthread_safe.h
+++ b/lib/pthread_safe.h
@@ -27,20 +27,22 @@
typedef struct strerror strerror_t ;
struct strerror
{
- char str[121] ; /* cannot imagine anything as big */
+ char str[120] ; /* cannot imagine anything as big */
} ;
extern void safe_init_r(void);
extern void safe_finish(void);
-extern const char * safe_strerror(int errnum);
-extern const char * safe_inet_ntoa (struct in_addr in);
-extern strerror_t errtoa(int err, int len) ;
-extern strerror_t errtoname(int err, int len) ;
-extern strerror_t errtostr(int err, int len) ;
+extern strerror_t errtoa(int err, uint len) ;
+extern strerror_t errtoname(int err, uint len) ;
+extern strerror_t errtostr(int err, uint len) ;
+
+extern strerror_t eaitoa(int eai, int err, uint len) ;
+extern strerror_t eaitoname(int eai, int err, uint len) ;
+extern strerror_t eaitostr(int eai, int err, uint len) ;
-extern strerror_t eaitoa(int eai, int err, int len) ;
-extern strerror_t eaitoname(int eai, int err, int len) ;
-extern strerror_t eaitostr(int eai, int err, int len) ;
+extern int getenv_r(const char* name, char* buf, int buf_len) ;
+extern const char * safe_strerror(int errnum);
+extern const char * safe_inet_ntoa (struct in_addr in);
#endif /* PTHREAD_SAFE_H_ */
diff --git a/lib/qafi_safi.h b/lib/qafi_safi.h
index 337532d5..5559097d 100644
--- a/lib/qafi_safi.h
+++ b/lib/qafi_safi.h
@@ -23,7 +23,7 @@
#ifndef _QUAGGA_AFI_SAFI_H
#define _QUAGGA_AFI_SAFI_H
-#include <stdint.h>
+#include "misc.h"
#include "lib/zassert.h"
/*==============================================================================
diff --git a/lib/qdebug_nb.h b/lib/qdebug_nb.h
new file mode 100644
index 00000000..6da2a120
--- /dev/null
+++ b/lib/qdebug_nb.h
@@ -0,0 +1,61 @@
+/* Debug definitions for non-blocking I/O testing
+ * 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_QDEBUG_NB_H
+#define _ZEBRA_QDEBUG_NB_H
+
+#include "misc.h"
+#include "qrand.h"
+
+/*------------------------------------------------------------------------------
+ * Sort out QDEBUG_NB_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QDEBUG_NB_DEBUG == 0 to turn off debug
+ * * or set QDEBUG_NB_DEBUG != 0 to turn on debug
+ * * or set QDEBUG_NB_NO_DEBUG != to force debug off
+ */
+
+#ifdef QDEBUG_NB /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QDEBUG_NB)
+# undef QDEBUG_NB
+# define QDEBUG_NB 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define QDEBUG_NB QDEBUG
+#endif
+
+#ifdef QDEBUG_NB_NO /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QDEBUG_NB_NO)
+# undef QDEBUG_NB
+# define QDEBUG_NB 0
+# endif
+#endif
+
+enum { qdebug_nb = QDEBUG_NB } ;
+
+#endif /* _ZEBRA_QDEBUG_NB_H */
diff --git a/lib/qfstring.c b/lib/qfstring.c
index 35708640..eda2aa9a 100644
--- a/lib/qfstring.c
+++ b/lib/qfstring.c
@@ -18,58 +18,102 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
+
+#include <stdio.h>
#include "qfstring.h"
-#include "zassert.h"
-#include <stdbool.h>
/*==============================================================================
*/
/*------------------------------------------------------------------------------
- * Initialise qf_str -- to given size (which includes the '\0')
+ * Initialise qf_str -- to given size, zero offset and zero overflow.
*
- * Sets pointers and terminates an empty string with one byte reserved for the
- * terminating '\0'.
+ * Note that does not terminate the string -- that must be done separately.
*
* This operation is async-signal-safe.
*/
extern void
-qfs_init(qf_str qfs, char* str, size_t size)
+qfs_init(qf_str qfs, char* str, uint size)
{
- assert(size > 0) ;
+ qfs->str = str ;
+ qfs->ptr = str ;
+ qfs->end = str + size ;
+ qfs->offset = 0 ;
+ qfs->overflow = 0 ;
+} ;
- qfs->str = str ;
- qfs->end = str + size - 1 ;
+/*------------------------------------------------------------------------------
+ * Initialise qf_str -- to given size, with given offset and zero overflow.
+ *
+ * Note that does not terminate the string -- that must be done separately.
+ *
+ * This operation is async-signal-safe.
+ */
+extern void
+qfs_init_offset(qf_str qfs, char* str, uint size, uint offset)
+{
+ qfs->str = str ;
+ qfs->ptr = str ;
+ qfs->end = str + size ;
+ qfs->offset = offset ;
+ qfs->overflow = 0 ;
+} ;
- *str = '\0' ;
- qfs->ptr = str ;
+/*------------------------------------------------------------------------------
+ * Reset given qf_str -- with the given offset and zero overflow.
+ *
+ * Sets ptr back to the start of the string and set the given offset.
+ *
+ * This operation is async-signal-safe.
+ */
+extern void
+qfs_reset_offset(qf_str qfs, uint offset)
+{
+ qfs->ptr = qfs->str ;
+ qfs->offset = offset ;
+ qfs->overflow = 0 ;
} ;
/*------------------------------------------------------------------------------
- * Initialise qf_str which already contains string -- to given size (which
- * includes the '\0')
+ * Initialise qf_str which already contains string -- to given size with zero
+ * overflow.
*
* This may be used to prepare for appending to a buffer which already contains
* something.
*
- * Sets pointers, setting the write pointer to the existing terminating '\0'.
+ * Sets pointers, setting the write pointer to the existing '\0'.
*
* This operation is async-signal-safe.
+ *
+ * NB: it is a mistake if the size given is less than the length of the
+ * string (excluding the trailing '\0').
*/
extern void
-qfs_init_as_is(qf_str qfs, char* str, size_t size)
+qfs_init_as_is(qf_str qfs, char* str, uint size)
{
assert(size > 0) ;
- qfs->str = str ;
- qfs->end = str + size - 1 ;
+ qfs->str = str ;
+ qfs->end = str + size ;
+ qfs->offset = 0 ;
+ qfs->overflow = 0 ;
+
+ while (*str != '\0')
+ ++str ;
- qfs->ptr = strchr(str, '\0') ;
+ qfs->ptr = str ; /* point at '\0' */
+
+ assert(qfs->ptr <= qfs->end) ;
} ;
/*------------------------------------------------------------------------------
- * Terminate string with the given string.
+ * Terminate string with the given string if given length (which may include
+ * a '\0').
+ *
+ * This is for when the qstring has overflowed, and wish to indicate that at
+ * the end -- so takes no notice of offset.
*
* If necessary, characters are discarded from the end of the string in order
* to fit in the terminating stuff.
@@ -81,34 +125,25 @@ qfs_init_as_is(qf_str qfs, char* str, size_t size)
* This operation is async-signal-safe.
*/
extern void
-qfs_term(qf_str qfs, const char* src)
+qfs_term_string(qf_str qfs, const char* src, uint n)
{
- int len ;
- int excess ;
+ uint h ;
- if ((src == NULL) || (*src == '\0'))
- {
- *qfs->ptr = '\0' ; /* should be true anyway */
- return ;
- } ;
+ h = qfs->end - qfs->ptr ; /* space available */
- len = strlen(src) ;
- excess = qfs_len(qfs) - len ;
- if (excess > 0)
+ if (h < n)
{
- if (excess <= (qfs->ptr - qfs->str))
- qfs->ptr -= excess ;
- else
+ h = qfs->end - qfs->str ; /* total space */
+ if (h < n)
{
- int want = len ;
- len = qfs->end - qfs->str ; /* take what can... */
- src += (want - len) ; /* ... from the end */
- qfs->ptr = qfs->str ;
+ src += n - h ; /* past what will not fit */
+ n = h ;
} ;
+ qfs->ptr = qfs->end - n ;
} ;
- memcpy(qfs->ptr, src, len + 1) ; /* include the '\0' */
- qfs->ptr += len ;
+ while (n--)
+ *qfs->ptr++ = *src++ ;
} ;
/*==============================================================================
@@ -120,26 +155,29 @@ qfs_term(qf_str qfs, const char* src)
*
* May append nothing at all !
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
qfs_append(qf_str qfs, const char* src)
{
- int n ;
-
- if ((src == NULL) || (*src == '\0'))
+ if (src == NULL)
return ;
- n = strlen(src) ;
-
- if (n > qfs_left(qfs))
- n = qfs_left(qfs) ;
-
- if (n == 0)
- return ;
+ while (qfs->offset > 0)
+ {
+ if (*src++ == '\0')
+ return ;
+ --qfs->offset ;
+ } ;
- memcpy(qfs->ptr, src, n + 1) ;
- qfs->ptr += n ;
+ while (*src != '\0')
+ {
+ if (qfs->ptr < qfs->end)
+ *qfs->ptr++ = *src++ ;
+ else
+ ++qfs->overflow ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -148,43 +186,76 @@ qfs_append(qf_str qfs, const char* src)
*
* May append nothing at all !
*
- * This operation is async-signal-safe.
+ * src may be NULL iff n == 0
+ *
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
-qfs_append_n(qf_str qfs, const char* src, size_t n)
+qfs_append_n(qf_str qfs, const char* src, uint n)
{
- if ((int)n > qfs_left(qfs))
- n = qfs_left(qfs) ;
+ uint h ;
- if (n <= 0)
- return ;
+ if (qfs->offset > 0)
+ {
+ if (qfs->offset >= n)
+ {
+ qfs->offset -= n ;
+ return ;
+ } ;
+
+ src += qfs->offset ;
+ n -= qfs->offset ;
+
+ qfs->offset = 0 ;
+ } ;
- memcpy(qfs->ptr, src, n) ;
- qfs->ptr += n ;
+ h = (qfs->end - qfs->ptr) ;
+ if (n > h)
+ {
+ qfs->overflow += n - h ;
+ n = h ;
+ } ;
- *qfs->ptr = '\0' ;
+ while (n--)
+ *qfs->ptr++ = *src++ ;
} ;
/*------------------------------------------------------------------------------
- * Append upto 'n' copies of the given character to the qf_str
+ * Append upto 'n' copies of the given character to the qf_str.
*
* May append nothing at all !
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
-qfs_append_ch_x_n(qf_str qfs, char ch, size_t n)
+qfs_append_ch_x_n(qf_str qfs, char ch, uint n)
{
- if ((int)n > qfs_left(qfs))
- n = qfs_left(qfs) ;
+ uint h ;
- if (n <= 0)
- return ;
+ if (qfs->offset > 0)
+ {
+ if (qfs->offset >= n)
+ {
+ qfs->offset -= n ;
+ return ;
+ } ;
+
+ n -= qfs->offset ;
+
+ qfs->offset = 0 ;
+ } ;
+
+ h = (qfs->end - qfs->ptr) ;
+ if (n > h)
+ {
+ qfs->overflow += n - h ;
+ n = h ;
+ } ;
while (n--)
*qfs->ptr++ = ch ;
-
- *qfs->ptr = '\0' ;
} ;
/*------------------------------------------------------------------------------
@@ -197,19 +268,13 @@ qfs_append_ch_x_n(qf_str qfs, char ch, size_t n)
*
* May append nothing at all !
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
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) ;
+ qfs_append_justified_n(qfs, src, qfs_strlen(src), width) ;
} ;
/*------------------------------------------------------------------------------
@@ -222,10 +287,11 @@ qfs_append_justified(qf_str qfs, const char* src, int width)
*
* May append nothing at all !
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
-qfs_append_justified_n(qf_str qfs, const char* src, size_t n, int width)
+qfs_append_justified_n(qf_str qfs, const char* src, uint n, int width)
{
if ((int)n >= abs(width))
width = 0 ;
@@ -239,6 +305,23 @@ qfs_append_justified_n(qf_str qfs, const char* src, size_t n, int width)
qfs_append_ch_x_n(qfs, ' ', - width - n) ;
} ;
+/*------------------------------------------------------------------------------
+ * Append single character.
+ *
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
+ */
+inline static void
+qfs_append_ch(qf_str qfs, char ch)
+{
+ if (qfs->offset > 0)
+ --qfs->offset ;
+ else if (qfs->ptr < qfs->end)
+ *qfs->ptr++ = ch ;
+ else
+ ++qfs->overflow ;
+} ;
+
/*==============================================================================
* Number conversion
*/
@@ -252,7 +335,8 @@ qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
*
* Result is appended to the given qf_str.
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags,
@@ -280,7 +364,8 @@ qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags,
*
* Result is appended to the given qf_str.
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags,
@@ -294,7 +379,8 @@ qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags,
*
* Result is appended to the given qf_str.
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
extern void
qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags,
@@ -365,7 +451,8 @@ qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags,
* 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.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
static void
qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
@@ -447,31 +534,49 @@ qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
/* Set up any required sign and radix prefix */
if ((flags & pf_unsigned) || (sign == 0))
- sign_str = "" ;
+ {
+ sign_str = "" ;
+ sign_len = 0 ;
+ }
else if (sign < 0)
- sign_str = "-" ;
+ {
+ sign_str = "-" ;
+ sign_len = 1 ;
+ }
else if (flags & pf_plus)
- sign_str = "+" ;
+ {
+ sign_str = "+" ;
+ sign_len = 1 ;
+ }
else if (flags & pf_space)
- sign_str = " " ;
+ {
+ sign_str = " " ;
+ sign_len = 1 ;
+ }
else
- sign_str = "" ;
-
- sign_len = strlen(sign_str) ;
+ {
+ sign_str = "" ;
+ sign_len = 0 ;
+ } ;
radix_str = "" ;
+ radix_len = 0 ;
+
if (flags & pf_alt)
{
if (flags & pf_hex)
- radix_str = (flags & pf_uc) ? "0X" : "0x" ;
+ {
+ confirm(pf_uc != 0) ;
+ radix_str = (flags & pf_uc) ? "0X" : "0x" ;
+ radix_len = 2 ;
+ }
else if ((flags & pf_oct) && (val != 0))
- radix_str = "0" ;
-
- confirm(pf_uc != 0) ;
+ {
+ radix_str = "0" ;
+ radix_len = 1 ;
+ } ;
} ;
- radix_len = strlen(radix_str) ;
-
/* Turn off zero fill if left justify (width < 0) */
if (width < 0)
flags &= ~pf_zeros ;
@@ -496,8 +601,7 @@ qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
digits = (flags & pf_uc) ? uc : lc ;
confirm(pf_uc != 0) ;
- e = p = num + sizeof(num) - 1 ;
- *p = '\0' ;
+ e = p = num + sizeof(num) ;
v = val ;
do
{
@@ -545,6 +649,7 @@ qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
while (t--)
*cp++ = *cq++ ;
*cp++ = comma ;
+ t = interval ;
} ;
assert(len == (e - p)) ;
@@ -688,42 +793,51 @@ static enum pf_phase qfs_arg_float(qf_str qfs, va_list* p_va,
const char* start, size_t flen, enum arg_num_type ant) ;
/*------------------------------------------------------------------------------
- * Formatted print to qf_str -- cf printf()
+ * Formatted print to qf_str -- cf printf() -- appends to the qf_str.
*
* This operation is async-signal-safe -- EXCEPT for floating point values.
+ * Takes into account the offset, and adds up any overflow.
+ *
+ * Returns: the resulting length of the qf_str.
*/
-extern void
+extern uint
qfs_printf(qf_str qfs, const char* format, ...)
{
va_list va ;
+ uint did ;
va_start (va, format);
- qfs_vprintf(qfs, format, va);
+ did = qfs_vprintf(qfs, format, va);
va_end (va);
+
+ return did ;
} ;
/*------------------------------------------------------------------------------
- * Formatted print to qf_str -- cf vprintf()
+ * Formatted print to qf_str -- cf vprintf() -- appends to the qf_str.
*
* This operation is async-signal-safe -- EXCEPT for floating point values.
+ * Takes into account the offset, and adds up any overflow.
*
* Operates on a copy of the va_list -- so the original is *unchanged*.
+ *
+ * Returns: the resulting length of the qf_str.
*/
-extern void
+extern uint
qfs_vprintf(qf_str qfs, const char *format, va_list va)
{
va_list vac ;
if (format == NULL)
- return ;
+ return qfs_len(qfs) ;
va_copy(vac, va) ;
- while ((qfs->ptr < qfs->end) && (*format != '\0'))
+ while (*format != '\0')
{
/* Have space for one byte and current format byte is not '\0' */
if (*format != '%')
- *qfs->ptr++ = *format++ ;
+ qfs_append_ch(qfs, *format++) ;
else
{
const char* start = format++ ; /* start points at the '%' ...
@@ -744,8 +858,12 @@ qfs_vprintf(qf_str qfs, const char *format, va_list va)
{
case '%': /* %% only */
if (phase == pfp_null)
- *qfs->ptr++ = '%' ;
- phase = (phase == pfp_null) ? pfp_done : pfp_failed ;
+ {
+ qfs_append_ch(qfs, '%') ;
+ phase = pfp_done ;
+ }
+ else
+ phase = pfp_failed ;
break ;
case '\'':
@@ -940,14 +1058,14 @@ qfs_vprintf(qf_str qfs, const char *format, va_list va)
if (phase == pfp_failed)
{
format = start ; /* back to the start */
- *qfs->ptr++ = *format++ ; /* copy the '%' */
+ qfs_append_ch(qfs, *format++) ;
} ;
} ;
} ;
- *qfs->ptr = '\0' ;
-
va_end(vac) ;
+
+ return qfs_len(qfs) ;
} ;
/*------------------------------------------------------------------------------
@@ -968,7 +1086,8 @@ qfs_vprintf(qf_str qfs, const char *format, va_list va)
* pf_unsigned
* pf_ptr
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
static enum pf_phase
qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags,
@@ -985,7 +1104,10 @@ qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags,
flags &= ~pf_precision ;
} ;
- len = (src != NULL) ? strlen(src) : 0 ;
+ len = 0 ;
+ if (src != NULL)
+ while (*(src + len) != '\0') ++len ;
+
if (((precision > 0) || (flags & pf_precision)) && (len > precision))
len = precision ;
@@ -1012,7 +1134,8 @@ qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags,
* pf_unsigned
* pf_ptr
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
static enum pf_phase
qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision)
@@ -1049,7 +1172,8 @@ qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision)
* Rejects: ant == ant_long_double -- which is how the parser spots an
* erroneous %Ld for example.
*
- * This operation is async-signal-safe.
+ * This operation is async-signal-safe. Takes into account the offset, and
+ * adds up any overflow
*/
static enum pf_phase
qfs_arg_integer(qf_str qfs, va_list* p_va, enum pf_flags flags,
@@ -1163,42 +1287,90 @@ qfs_arg_integer(qf_str qfs, va_list* p_va, enum pf_flags flags,
* doomed to failure, because any floating point operation may affect flags
* and other state in the processor :-(
*
- * This operation is *NOT* async-signal-safe.
+ * This operation is *NOT* async-signal-safe. Takes into account the offset,
+ * and adds up any overflow
*/
+
+union float_value
+{
+ double d ;
+ long double ld ;
+} ;
+
+static int
+qfs_arg_float_snprintf(void* buf, int have, const char* format,
+ union float_value* p_val, enum arg_num_type ant)
+{
+ if (ant == ant_default)
+ return snprintf(buf, have, format, p_val->d) ;
+ else
+ return snprintf(buf, have, format, p_val->ld) ;
+} ;
+
static enum pf_phase
qfs_arg_float(qf_str qfs, va_list* p_va, const char* start, size_t flen,
enum arg_num_type ant)
{
+ union float_value val ;
char format[flen + 1] ;
- int want ;
- int have ;
+ int want ;
+
+ if (ant == ant_default)
+ val.d = va_arg(*p_va, double) ;
+ else
+ val.ld = va_arg(*p_va, long double) ;
memcpy(format, start, flen) ;
format[flen + 1] = '\0' ;
- have = qfs_left(qfs) + 1 ;
-
- if (ant == ant_default)
+ if (qfs->offset == 0)
{
- double val ;
- val = va_arg(*p_va, double) ;
- want = snprintf(qfs_ptr(qfs), have, format, val) ;
+ /* No offset, so can use the qfs directly.
+ */
+ int have ;
+
+ have = qfs_left(qfs) ;
+ want = qfs_arg_float_snprintf(qfs->ptr, have + 1, format, &val, ant) ;
+
+ if (want > 0)
+ {
+ if (want <= have)
+ qfs->ptr += want ;
+ else
+ {
+ qfs->ptr = qfs->end ;
+ qfs->overflow += (want - have) ;
+ } ;
+ } ;
}
else
{
- long double val ;
- assert(ant == ant_long_double) ;
- val = va_arg(*p_va, long double) ;
- want = snprintf(qfs_ptr(qfs), have, format, val) ;
- } ;
+ /* Because the offset is not zero, need to use an intermediate
+ * buffer and then copy part after the offset.
+ *
+ * First, discover full extent of the formatted value, then if that
+ * exceeds the offset, construct buffer and copy what we can to the
+ * qps; otherwise, reduce the offset.
+ */
+ want = qfs_arg_float_snprintf(NULL, 0, format, &val, ant) ;
- if (want < 0)
- return pfp_failed ;
+ if (want > 0)
+ {
+ int take ;
- if (want < have)
- qfs->ptr += want ;
- else
- qfs->ptr = qfs->end ;
+ take = qfs->offset + qfs_left(qfs) ;
+ if (take > want)
+ take = want ;
- return pfp_done ;
+ {
+ char tmp[take + 1] ;
+ want = qfs_arg_float_snprintf(tmp, take + 1, format, &val, ant) ;
+
+ if (want > 0)
+ qfs_append_n(qfs, tmp, want) ;
+ } ;
+ } ;
+ } ;
+
+ return (want >= 0) ? pfp_done : pfp_failed ;
} ;
diff --git a/lib/qfstring.h b/lib/qfstring.h
index d9a51d21..be858899 100644
--- a/lib/qfstring.h
+++ b/lib/qfstring.h
@@ -22,48 +22,46 @@
#ifndef _ZEBRA_QFSTRING_H
#define _ZEBRA_QFSTRING_H
-#include "zebra.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#ifndef Inline
-#define Inline static inline
-#endif
-
-/* GCC have printf type attribute check. */
-#ifdef __GNUC__
-#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
-#else
-#define PRINTF_ATTRIBUTE(a,b)
-#endif /* __GNUC__ */
+#include "misc.h"
+#include "vargs.h"
/*==============================================================================
* These "qfstring" address the issues of dealing with *fixed* length
* strings, particularly where the string handling must be async-signal-safe.
*
+ * Are also used to support snprintf() style printing, but to one or more
+ * fixed length buffers.
+ *
* All operations that can possibly be async-signal-safe, are. Notable
* exception is anything involving floating point values -- because of the
* state contain in floating point status/option registers !
*/
-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
+ * str = start of string -- and this is never changed
+ * ptr = start of string -- this is moved as stuff is appended
+ * end = end of string -- and this is never changed
+ * offset = number of characters left to omit -- used to collect a long
+ * string in more than one section
+ * overflow = number of characters that had to leave out, because string
+ * was too short.
+ *
+ * When filling the string, will work right up to the byte just before the
+ * end. Does not terminate the string -- that must be done explicitly.
*/
struct qf_str
{
- char* str ; /* start of string */
- char* ptr ; /* current position */
- char* end ; /* end of string */
+ char* str ; /* start of string */
+ char* ptr ; /* current position */
+ char* end ; /* end of string */
+ uint offset ; /* number of chars to omit */
+ uint overflow ; /* number of excess chars */
} ;
+typedef struct qf_str qf_str_t[1] ;
+typedef struct qf_str* qf_str ;
+
/*------------------------------------------------------------------------------
* Print format flags for number printing
*/
@@ -72,22 +70,22 @@ enum pf_flags
pf_none = 0,
/* The following correspond to the "flags" */
- pf_commas = 1 << 0, /* "'" seen */
- pf_plus = 1 << 1, /* "+" seen */
- pf_space = 1 << 2, /* " " seen */
- pf_zeros = 1 << 3, /* "0" seen */
- pf_alt = 1 << 4, /* "#" seen */
+ pf_commas = BIT( 0), /* "'" seen */
+ pf_plus = BIT( 1), /* "+" seen */
+ pf_space = BIT( 2), /* " " seen */
+ pf_zeros = BIT( 3), /* "0" seen */
+ pf_alt = BIT( 4), /* "#" seen */
- pf_precision = 1 << 7, /* '.' seen */
+ pf_precision = BIT( 7), /* '.' seen */
/* The following signal how to render the value */
- pf_oct = 1 << 8, /* octal */
- pf_hex = 1 << 9, /* hex */
- pf_uc = 1 << 10, /* upper-case */
+ pf_oct = BIT( 8), /* octal */
+ pf_hex = BIT( 9), /* hex */
+ pf_uc = BIT(10), /* upper-case */
/* The following signal the type of value */
- pf_ptr = 1 << 14, /* is a pointer */
- pf_unsigned = 1 << 15, /* unsigned value */
+ pf_ptr = BIT(14), /* is a pointer */
+ pf_unsigned = BIT(15), /* unsigned value */
/* Common combination */
pf_hex_x = pf_unsigned | pf_hex,
@@ -100,22 +98,26 @@ enum pf_flags
* Functions
*/
-extern void qfs_init(qf_str qfs, char* str, size_t size) ;
-extern void qfs_init_as_is(qf_str qfs, char* str, size_t size) ;
+extern void qfs_init(qf_str qfs, char* str, uint size) ;
+extern void qfs_init_offset(qf_str qfs, char* str, uint size, uint offset) ;
+extern void qfs_reset_offset(qf_str qfs, uint offset) ;
+extern void qfs_init_as_is(qf_str qfs, char* str, uint size) ;
-extern void qfs_term(qf_str qfs, const char* src) ;
+Inline uint qfs_overflow(qf_str qfs) ;
+Inline uint qfs_term(qf_str qfs) ;
+extern void qfs_term_string(qf_str qfs, const char* src, uint n) ;
-Inline int qfs_len(qf_str qfs) ;
+Inline uint qfs_len(qf_str qfs) ;
Inline void* qfs_ptr(qf_str qfs) ;
-Inline int qfs_left(qf_str qfs) ;
+Inline uint 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_n(qf_str qfs, const char* src, uint n) ;
-extern void qfs_append_ch_x_n(qf_str qfs, char ch, size_t n) ;
+extern void qfs_append_ch_x_n(qf_str qfs, char ch, uint 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) ;
+ uint n, int width) ;
extern void qfs_signed(qf_str qfs, intmax_t s_val, enum pf_flags flags,
int width, int precision) ;
@@ -124,25 +126,27 @@ extern void qfs_unsigned(qf_str qfs, uintmax_t u_val, enum pf_flags flags,
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, ...)
+extern uint 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) ;
+extern uint qfs_vprintf(qf_str qfs, const char *format, va_list args) ;
+
+Inline uint qfs_strlen(const char* str) ;
/*==============================================================================
* The Inline functions.
*/
/*------------------------------------------------------------------------------
- * Current length of qf_str, not counting the terminating '\0'.
+ * Current length of qf_str.
*/
-Inline int
+Inline uint
qfs_len(qf_str qfs)
{
return qfs->ptr - qfs->str ;
} ;
/*------------------------------------------------------------------------------
- * Address of the terminating '\0'.
+ * Address for next byte -- assuming zero offset -- may be the end.
*/
Inline void*
qfs_ptr(qf_str qfs)
@@ -151,13 +155,149 @@ qfs_ptr(qf_str qfs)
} ;
/*------------------------------------------------------------------------------
- * Current space left in the qstr, given what has been reserved for terminating
- * '\0' and any other reservation.
+ * Current space left in the qstr -- assuming zero offset.
*/
-Inline int
+Inline uint
qfs_left(qf_str qfs)
{
return qfs->end - qfs->ptr ;
} ;
-#endif /* _ZEBRA_QSTRING_H */
+/*------------------------------------------------------------------------------
+ * Did everything we put in the qfs, fit ?.
+ *
+ * Returns: number of chars that did *not* fit.
+ */
+Inline uint
+qfs_overflow(qf_str qfs)
+{
+ return qfs->overflow ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Insert '\0' terminator -- overwrites the last byte, if required.
+ *
+ * Assumes the qfs is not zero length !
+ *
+ * Returns: number of chars that did *not* fit (after using one for '\0').
+ *
+ * NB: does not advance pointer -- so length does not include the '\0'
+ */
+Inline uint
+qfs_term(qf_str qfs)
+{
+ if (qfs->ptr >= qfs->end)
+ {
+ assert((qfs->ptr == qfs->end) && (qfs->ptr > qfs->str)) ;
+ --qfs->ptr ;
+ ++qfs->overflow ;
+ } ;
+
+ *qfs->ptr = '\0' ;
+ return qfs->overflow ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * async-signal-safe strlen
+ */
+Inline uint
+qfs_strlen(const char* str)
+{
+ const char* s ;
+
+ s = str ;
+
+ if (s != NULL)
+ while (*s != '\0')
+ ++s ;
+
+ return s - str ;
+}
+
+/*==============================================================================
+ * Fixed Size String Buffers
+ *
+ * This supports the common case of a function whose task is to construct a
+ * (small) string of known maximum length, which will promptly be output
+ * or something similar.
+ *
+ * This scheme removes the need for the caller to construct a small buffer
+ * and pass it to the string constructor. The "magic" is to make the callee
+ * return a struct containing the result. So the callee is, for example:
+ *
+ * foo_t make_foo(...) { ... } ;
+ *
+ * where foo_t is a struct, with a "str" element large enough for all known
+ * foo. So the caller can, for example:
+ *
+ * printf("...%s...", ..., make_foo(...).str, ...) ;
+ *
+ * All the fiddling around with buffers and buffer sizes is hidden from the
+ * caller. And, since the buffer is implicitly on the stack, this is thread
+ * safe (and async-signal-safe, provided make_foo() is).
+ *
+ * The macro: qfb_t(name, len) declares a fixed length buffer type. So:
+ *
+ * QFB_T(foo, 79) ;
+ *
+ * declares:
+ *
+ * typedef struct { char str[79 + 1] ; } foo_t ;
+ *
+ * NB: the length given *excludes* the terminating '\0' ;
+ *
+ * NB: the type declared has the "_t" added *automatically*.
+ *
+ * Having declared a suitable type, function(s) can be declared to return
+ * a string in a value of that type.
+ *
+ * A string generating function can use the buffer directly, for example:
+ *
+ * foo_t make_foo(...)
+ * {
+ * foo_t foo ;
+ *
+ * ... foo.str is the address of the string buffer
+ * ... sizeof(foo.str) is its length *including* the '\0'
+ *
+ * return foo ;
+ * } ;
+ *
+ * The qfstring facilities may be used to construct the string, and to
+ * facilitate that, the macro: qfb_qfs(buf, qfs) declares the buffer and a
+ * qf_str_t and initialises same, thus:
+ *
+ * QFB_QFS(foo, foo_qfs) ;
+ *
+ * declares:
+ *
+ * foo_t foo ;
+ * qf_str_t foo_qfs = { ...initialised for empty foo... } ;
+ *
+ * So the string generator can use foo_qfs and qfstring facilities to fill in
+ * the string in foo, and then return foo (having terminated it) as above.
+ *
+ * So... with two macros we reduce the amount of fiddling about required to
+ * do something reasonably simple.
+ *
+ * NB: it is quite possible that the compiler will allocate two buffers, one
+ * in the caller's stack frame and one in the callee's, and returning the
+ * value will involve copying from one to the other.
+ */
+#define QFB_T(name, len) \
+ typedef struct { char str[((len) | 7) + 1] ; } name##_t
+
+#define QFB_QFS(qfb, qfs) \
+ qfb##_t qfb ; \
+ qf_str_t qfs = { { .str = qfb.str, \
+ .ptr = qfb.str, \
+ .end = qfb.str + sizeof(qfb.str), \
+ .offset = 0, \
+ .overflow = 0 } }
+
+/* And, finally, a "standard" qfb for general use: qfb_gen_t ! */
+
+enum { qfb_gen_len = 200 } ; /* More than enough for most purposes ! */
+QFB_T(qfb_gen, qfb_gen_len) ;
+
+#endif /* _ZEBRA_QFSTRING_H */
diff --git a/lib/qiovec.c b/lib/qiovec.c
index 546dfcb0..9492731a 100644
--- a/lib/qiovec.c
+++ b/lib/qiovec.c
@@ -18,16 +18,31 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include "zebra.h"
+#include <errno.h>
+#include "qdebug_nb.h"
+#include "qlib_init.h"
#include "memory.h"
-#include "zassert.h"
#include "miyagi.h"
#include "qiovec.h"
/*==============================================================================
+ * We collect the _SC_IOV_MAX very early in the morning, but in any case
+ * only use up to 100 entries -- in order not to let a single operation hog
+ * the cpu.
+ */
+static int qiov_max ;
+
+extern void
+qiovec_start_up(void)
+{
+ qiov_max = (qlib_iov_max < 100) ? qlib_iov_max : 100 ;
+}
+
+/*==============================================================================
* Initialise, allocate and reset qiovec
*/
@@ -40,12 +55,12 @@
* Returns: address of qiovec
*/
extern qiovec
-qiovec_init_new(qiovec viov)
+qiovec_init_new(qiovec qiov)
{
- if (viov == NULL)
- viov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ;
+ if (qiov == NULL)
+ qiov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ;
else
- memset(viov, 0, sizeof(struct qiovec)) ;
+ memset(qiov, 0, sizeof(struct qiovec)) ;
/* Zeroising has set:
*
@@ -60,7 +75,7 @@ qiovec_init_new(qiovec viov)
* Nothing more is required.
*/
- return viov ;
+ return qiov ;
} ;
/*------------------------------------------------------------------------------
@@ -69,81 +84,188 @@ qiovec_init_new(qiovec viov)
* Returns: address of qiovec (if any) -- NULL if structure released
*/
extern qiovec
-qiovec_reset(qiovec viov, bool free_structure)
+qiovec_reset(qiovec qiov, bool free_structure)
{
- if (viov != NULL)
+ if (qiov != NULL)
{
- if (viov->vec != NULL)
- XFREE(MTYPE_QIOVEC_VEC, viov->vec) ;
+ if (qiov->vec != NULL)
+ XFREE(MTYPE_QIOVEC_VEC, qiov->vec) ;
if (free_structure)
- XFREE(MTYPE_QIOVEC, viov) ; /* sets viov = NULL */
+ XFREE(MTYPE_QIOVEC, qiov) ; /* sets qiov = NULL */
else
- qiovec_init_new(viov) ; /* re-initialise */
+ qiovec_init_new(qiov) ; /* re-initialise */
} ;
- return viov ;
+ return qiov ;
} ;
/*------------------------------------------------------------------------------
* Clear given qiovec.
*/
extern void
-qiovec_clear(qiovec viov)
+qiovec_clear(qiovec qiov)
{
- viov->i_get = 0 ;
- viov->i_put = 0 ;
- viov->writing = 0 ;
+ qiov->i_get = 0 ;
+ qiov->i_put = 0 ;
+ qiov->writing = 0 ;
} ;
/*------------------------------------------------------------------------------
- * Push item to given qiovec
- *
- * NB: avoids pushing zero length items.
+ * Establish total length of the qiov -- add up all the item lengths.
*/
-extern void
-qiovec_push(qiovec viov, const void* base, size_t len)
+extern size_t
+qiovec_length(qiovec qiov)
{
- struct iovec* p_iov ;
+ if (qiov->i_put > qiov->i_get)
+ {
+ size_t length ;
+ uint n ;
+ qiov_item item ;
+
+ n = qiov->i_put - qiov->i_get ;
+ item = &qiov->vec[qiov->i_get] ;
- if (len == 0)
- return ;
+ length = item->len ;
+ while (--n)
+ length += (++item)->len ;
- if (viov->i_put >= viov->i_alloc)
+ return length ;
+ }
+ else
{
- size_t size ;
- assert(viov->i_put == viov->i_alloc) ;
+ assert(qiov->i_get == qiov->i_put) ;
+ qiov->i_get = qiov->i_put = 0 ;
- assert( ((viov->i_alloc == 0) && (viov->vec == NULL))
- || ((viov->i_alloc != 0) && (viov->vec != NULL)) ) ;
+ return 0 ;
+ } ;
+} ;
- 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) ;
- } ;
+/*==============================================================================
+ * Extending and shuffling qiovec in order to support push and unshift
+ * operations.
+ *
+ * Note that if unshift becomes common, then should adjust the following.
+ * Also address the tendency to reset i_get and i_put to zero whenever they
+ * are found to be equal.
+ */
+enum
+{
+ i_get_limit = 200, /* in qiovec_extend, shuffle vector down if
+ i_get is >= i_get_limit */
+
+ i_alloc_unit = 100, /* allocate in lots of this much */
+
+ i_get_margin = 20, /* in qiovec_shuffle, if have to reorganise,
+ allow for this many unshifts. */
+
+ i_put_margin = 40, /* in qiovec_shuffle, if have to reorganise,
+ allow for this many pushes. */
+} ;
+
+static void qiovec_move_vector(qiovec qiov, uint new_i_get) ;
+static void qiovec_new_vector(qiovec qiov, uint items) ;
+
+/*------------------------------------------------------------------------------
+ * Extend -- see qiovec_push.
+ *
+ * NB: underlying vector may be reorganised, reallocated, etc. All pointers
+ * to items are now INVALID.
+ */
+Private void
+qiovec_extend(qiovec qiov)
+{
+ assert(qiov->i_put <= qiov->i_alloc) ;
+ assert(qiov->i_get <= qiov->i_put) ;
+
+ assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL))
+ || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ;
+
+ /* Short circuit if can make space by resetting indexes */
+ if ((qiov->i_put == qiov->i_get) && (qiov->i_alloc != 0))
+ {
+ qiov->i_put = 0 ;
+ qiov->i_get = 0 ;
+
+ return ;
} ;
- p_iov = &viov->vec[viov->i_put++] ;
+ if (qiov->i_get > i_get_limit) /* keep in check */
+ qiovec_move_vector(qiov, 0) ;
+ else
+ qiovec_new_vector(qiov, i_alloc_unit) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shuffle to allow for qiovec_unshift.
+ *
+ * Don't expect this to happen often -- but will do it if required.
+ *
+ * On exit the i_get will be > 0.
+ *
+ * NB: underlying vector may be reorganised, reallocated, etc. All pointers
+ * to items are now INVALID.
+ */
+Private void
+qiovec_shuffle(qiovec qiov)
+{
+ assert(qiov->i_put <= qiov->i_alloc) ;
+ assert(qiov->i_get <= qiov->i_put) ;
+
+ assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL))
+ || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ;
+
+ if (qiov->i_get > 0)
+ return ; /* shouldn't be here, really */
+
+ /* If do not have enough spare space for both margins, allocate extra to
+ * the tune of both margins -- will leave at least the margins and at
+ * most nearly twice those margins.
+ *
+ * This will allocate a vector is there is none at all.
+ */
+ if ((qiov->i_alloc - qiov->i_put) < (i_get_margin + i_put_margin))
+ qiovec_new_vector(qiov, i_get_margin + i_put_margin) ;
- p_iov->iov_base = miyagi(base) ;
- p_iov->iov_len = len ;
+ /* Shuffle into place and update i_get and i_put. */
+ qiovec_move_vector(qiov, i_get_margin) ;
+
+ confirm(i_get_margin > 0) ;
} ;
/*------------------------------------------------------------------------------
+ * Move contents of vector and set a new i_get (and i_put to suit).
+ */
+static void
+qiovec_move_vector(qiovec qiov, uint new_i_get)
+{
+ memmove(&qiov->vec[new_i_get], &qiov->vec[qiov->i_get],
+ (qiov->i_put - qiov->i_get) * sizeof(qiovec_t)) ;
+
+ qiov->i_put -= qiov->i_get - new_i_get ;
+ qiov->i_get = new_i_get ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Allocate or reallocate vector for given number of items.
+ */
+static void
+qiovec_new_vector(qiovec qiov, uint items)
+{
+ qiov->i_alloc += items ;
+
+ qiov->vec = XREALLOC(MTYPE_QIOVEC_VEC, qiov->vec,
+ qiov->i_alloc * sizeof(qiovec_t)) ;
+} ;
+
+/*==============================================================================
+ * Writing qiovec and iovec
+ */
+
+static ssize_t writev_qdebug_nb(int fd, struct iovec p_iov[], int n) ;
+
+/*------------------------------------------------------------------------------
* Write given qiovec -- assuming NON-BLOCKING.
*
* Does nothing if the qiovec is empty.
@@ -157,24 +279,24 @@ qiovec_push(qiovec viov, const void* base, size_t len)
* -1 => failed -- see errno
*/
extern int
-qiovec_write_nb(int fd, qiovec viov)
+qiovec_write_nb(int fd, qiovec qiov)
{
int n ;
int l ;
- n = viov->i_put - viov->i_get ;
+ n = qiov->i_put - qiov->i_get ;
- l = iovec_write_nb(fd, &viov->vec[viov->i_get], n) ;
+ l = iovec_write_nb(fd, (struct iovec*)(&qiov->vec[qiov->i_get]), n) ;
- if (l == 0)
+ if (l <= 0)
{
- viov->writing = 0 ;
- viov->i_get = viov->i_put = 0 ;
+ qiov->writing = false ;
+ qiov->i_get = qiov->i_put = 0 ;
}
else
{
- viov->writing = 1 ;
- viov->i_get += (n - l) ;
+ qiov->writing = true ;
+ qiov->i_get += (n - l) ;
} ;
return l ;
@@ -209,8 +331,6 @@ qiovec_write_nb(int fd, qiovec viov)
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 */
@@ -222,7 +342,15 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
while (n > 0)
{
- ret = writev(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
+ ssize_t ret ;
+ int m ;
+
+ m = (n < qiov_max) ? n : qiov_max ;
+
+ if (qdebug_nb)
+ ret = writev_qdebug_nb(fd, p_iov, m) ;
+ else
+ ret = writev (fd, p_iov, m) ;
if (ret > 0)
{
@@ -243,6 +371,7 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
ret = 0 ;
} ;
} ;
+
}
else if (ret == 0)
break ; /* not sure can happen... but
@@ -259,3 +388,52 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
return n ;
} ;
+
+/*==============================================================================
+ * Simulation of writev() for debug purposes -- generates lots of partial
+ * writes and lots of blocking
+ *
+ */
+
+static qrand_seq_t wseq = QRAND_SEQ_INIT(4001) ;
+
+static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ;
+
+/*------------------------------------------------------------------------------
+ * Simulate writev() with tiny output buffers and lots of blocking.
+ */
+static ssize_t
+writev_qdebug_nb(int fd, struct iovec p_iov[], int n)
+{
+ struct iovec* q_iov ;
+ size_t len ;
+ ssize_t ret ;
+
+ assert(n > 0) ;
+
+ if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(wseq, 3)] ;
+ return -1 ;
+ } ;
+
+ /* Process 1..n or 1..3 entries */
+ n = qrand(wseq, (n < 3) ? n : 3) ;
+ q_iov = p_iov + n ;
+ ++n ;
+
+ /* If new last entry is not zero length, write only part of
+ * it.
+ */
+ len = q_iov->iov_len ;
+ if (len > 0)
+ q_iov->iov_len = qrand(wseq, (len < 200) ? len : 200) + 1 ;
+
+ ret = writev(fd, p_iov, n) ;
+
+ q_iov->iov_len = len ; /* restore true length of entry */
+
+ return ret ;
+} ;
+
+
diff --git a/lib/qiovec.h b/lib/qiovec.h
index ee03d2f2..5c7890bf 100644
--- a/lib/qiovec.h
+++ b/lib/qiovec.h
@@ -22,53 +22,100 @@
#ifndef _ZEBRA_QIOVEC_H
#define _ZEBRA_QIOVEC_H
-#include "zebra.h"
+#include "misc.h"
+#include <sys/uio.h> /* iovec stuff */
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
+/*==============================================================================
+ * Alternative naming for struct iovec and its entries.
+ */
+struct qiov_item
+{
+ const char* base ;
+ size_t len ;
+} ;
+
+typedef struct qiov_item* qiov_item ;
+typedef struct qiov_item qiov_item_t[1] ;
+typedef struct qiov_item* qiov_vector ;
+
+union
+{
+ struct qiov_item q ;
+ struct iovec s ;
+} union_iovec ;
+
+CONFIRM(sizeof(struct qiov_item) == sizeof(struct iovec)) ;
-#ifndef Inline
-#define Inline static inline
-#endif
+CONFIRM(offsetof(struct qiov_item, base) == offsetof(struct iovec, iov_base)) ;
+CONFIRM(sizeof (union_iovec.q. base) == sizeof (union_iovec.s.iov_base)) ;
+CONFIRM(offsetof(struct qiov_item, len) == offsetof(struct iovec, iov_len)) ;
+CONFIRM(sizeof (union_iovec.q. len) == sizeof (union_iovec.s.iov_len)) ;
/*==============================================================================
* 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 */
+ qiov_vector vec ; /* the actual iovec array */
+
+ uint i_get ; /* next entry to get */
+ uint i_put ; /* next entry to put */
+
+ uint i_alloc ; /* number of entries allocated */
bool writing ; /* started, but not finished */
+} ;
- unsigned i_get ; /* next entry to get */
- unsigned i_put ; /* next entry to put */
+typedef struct qiovec* qiovec ;
+typedef struct qiovec qiovec_t[1] ;
- unsigned i_alloc ; /* number of entries allocated */
+enum
+{
+ QIOVEC_INIT_ALL_ZEROS = true
} ;
/*==============================================================================
* Functions
*/
+extern void qiovec_start_up(void) ;
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 qiovec qiovec_free(qiovec qiov) ;
Inline bool qiovec_empty(qiovec qiov) ;
+extern size_t qiovec_length(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) ;
+Inline uint qiovec_count(qiovec qiov) ;
+Inline void qiovec_push_this(qiovec qiov, const void* base, size_t len) ;
+Inline void qiovec_push(qiovec qiov, qiov_item item) ;
+Inline void qiovec_pop(qiovec qiov, qiov_item item) ;
+Inline void qiovec_shift(qiovec qiov, qiov_item item) ;
+Inline void qiovec_unshift(qiovec qiov, qiov_item item) ;
+
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) ;
+Private void qiovec_extend(qiovec qiov) ;
+Private void qiovec_shuffle(qiovec qiov) ;
+
+/*------------------------------------------------------------------------------
+ * Free given qiovec
+ *
+ * It is the caller's responsibility to free anything that the qiovec may
+ * point to.
+ *
+ * Returns: NULL
+ */
+Inline qiovec qiovec_free(qiovec qiov)
+{
+ return qiovec_reset(qiov, free_it) ;
+} ;
+
/*------------------------------------------------------------------------------
* Is given qiov empty ?
*
@@ -82,6 +129,107 @@ qiovec_empty(qiovec qiov)
} ;
/*------------------------------------------------------------------------------
+ * How many entries in the given qiov ?
+ */
+Inline uint
+qiovec_count(qiovec qiov)
+{
+ return (qiov->i_put - qiov->i_get) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push to qiov -- same as qiovec_push, but with item.
+ *
+ * Ignores zero length items.
+ */
+Inline void
+qiovec_push_this(qiovec qiov, const void* base, size_t len)
+{
+ qiov_item_t item ;
+
+ item->base = base ;
+ item->len = len ;
+ qiovec_push(qiov, item) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push (copy of) item to qiov -- ie append it to the end of the vector.
+ */
+Inline void
+qiovec_push(qiovec qiov, qiov_item item)
+{
+ if (item->len != 0)
+ {
+ if (qiov->i_put >= qiov->i_alloc)
+ qiovec_extend(qiov) ;
+
+ qiov->vec[qiov->i_put++] = *item ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pop last item from qiov -- return empty item if none.
+ */
+Inline void
+qiovec_pop(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get < qiov->i_put)
+ {
+ *item = qiov->vec[--qiov->i_put] ;
+ if (qiov->i_get == qiov->i_put)
+ qiov->i_get = qiov->i_put = 0 ;
+ }
+ else
+ {
+ assert(qiov->i_get == qiov->i_put) ;
+ item->base = NULL ;
+ item->len = 0 ;
+
+ qiov->i_get = qiov->i_put = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shift first item off qiov -- return empty item if none.
+ */
+Inline void
+qiovec_shift(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get < qiov->i_put)
+ {
+ *item = qiov->vec[qiov->i_get++] ;
+ if (qiov->i_get == qiov->i_put)
+ qiov->i_get = qiov->i_put = 0 ;
+ }
+ else
+ {
+ assert(qiov->i_get == qiov->i_put) ;
+ item->base = NULL ;
+ item->len = 0 ;
+
+ qiov->i_get = qiov->i_put = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Unshift item onto qiov -- ie prepend it to the start of the vector.
+ */
+Inline void
+qiovec_unshift(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get == 0)
+ {
+ if (qiov->i_put == 0)
+ return qiovec_push(qiov, item) ;
+
+ qiovec_shuffle(qiov) ;
+ } ;
+
+ if (item->len != 0)
+ qiov->vec[--qiov->i_get] = *item ;
+} ;
+
+/*------------------------------------------------------------------------------
* Set a given struct iovec
*
* Gets around the fact that the standard structure does not have a const
diff --git a/lib/qlib_init.c b/lib/qlib_init.c
index c44de575..1d641e26 100644
--- a/lib/qlib_init.c
+++ b/lib/qlib_init.c
@@ -18,16 +18,23 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
+
+#include <errno.h>
+#include <stdio.h>
#include "qlib_init.h"
#include "zassert.h"
#include "memory.h"
+#include "qpnexus.h"
#include "qpthreads.h"
#include "qpselect.h"
#include "thread.h"
#include "privs.h"
#include "mqueue.h"
#include "pthread_safe.h"
+#include "log_local.h"
+#include "qiovec.h"
/*==============================================================================
* Quagga Library Initialise/Closedown
@@ -42,6 +49,8 @@
*
* this is expected to be called before the program does anything at all.
*
+ * Collects a small number of useful system parameters -- see below.
+ *
* This performs all initialisation required to support asserts, logging,
* basic I/O (but not the remote console), trap signals... and so on.
*
@@ -55,41 +64,104 @@
* This performs all initialisation required to support socket I/O,
* thread handling, timers, and so on.
*
- * In particular, at this stage the system is set into Pthread Mode, if
- * required. No pthreads may be started before this. Up to this point
- * the system operates in non-Pthread Mode -- all mutexes are implicitly
- * free.
+ * NB: at this stage the system is set into pthread mode, if required.
+ *
+ * No pthreads may be started before this. Up to this point
+ * the system operates in non-pthread mode -- all mutexes are
+ * implicitly free.
*
* There is one stage of closedown. This is expected to be called last, and
* is passed the exit code.
*
+ *==============================================================================
+ * System parameters:
+ *
+ * iov_max -- _SC_IOV_MAX
*
+ * open_max -- _SC_OPEN_MAX
*/
-void
+int qlib_iov_max ;
+int qlib_open_max ;
+int qlib_pagesize ;
+
+struct
+{
+ int* p_var ;
+ int sc ;
+ const char* name ;
+ long min ;
+ long max ;
+} qlib_vars[] =
+{
+ { .p_var = &qlib_iov_max, .sc = _SC_IOV_MAX, .name = "_SC_IOV_MAX",
+ .min = 16, .max = INT_MAX },
+ { .p_var = &qlib_open_max, .sc = _SC_OPEN_MAX, .name = "_SC_OPEN_MAX",
+ .min = 256, .max = INT_MAX },
+ { .p_var = &qlib_pagesize, .sc = _SC_PAGESIZE, .name = "_SC_PAGESIZE",
+ .min = 256, .max = (INT_MAX >> 1) + 1 },
+ { .p_var = NULL }
+} ;
+
+extern void
qlib_init_first_stage(void)
{
+ int i ;
+
+ for (i = 0 ; qlib_vars[i].p_var != NULL ; ++i)
+ {
+ long val ;
+
+ errno = 0 ;
+ val = sysconf(qlib_vars[i].sc) ;
+
+ if (val == -1)
+ {
+ if (errno == 0)
+ val = INT_MAX ;
+ else
+ {
+ fprintf(stderr, "Failed to sysconf(%s): %s\n",
+ qlib_vars[i].name, errtoa(errno, 0).str) ;
+ exit(1) ;
+ } ;
+ } ;
+
+ if ((val < qlib_vars[i].min) || (val > qlib_vars[i].max))
+ {
+ fprintf(stderr, "sysconf(%s) = %ld: which is < %ld or > %ld\n",
+ qlib_vars[i].name, val, qlib_vars[i].min, qlib_vars[i].max) ;
+ exit(1) ;
+ } ;
+
+ *(qlib_vars[i].p_var) = (int)val ;
+ } ;
+
qps_start_up() ;
+ memory_start() ;
+ qiovec_start_up() ;
}
-void
-qlib_init_second_stage(int pthreads)
+extern void
+qlib_init_second_stage(bool pthreads)
{
qpt_set_qpthreads_enabled(pthreads);
+ qpn_init() ;
memory_init_r();
thread_init_r();
+ log_init_r() ;
zprivs_init_r();
mqueue_initialise();
safe_init_r();
}
-
-void
+extern void
qexit(int exit_code)
{
safe_finish();
mqueue_finish();
zprivs_finish();
+ log_finish();
thread_finish();
memory_finish();
exit (exit_code);
diff --git a/lib/qlib_init.h b/lib/qlib_init.h
index 0d5fbd7e..71ccc162 100644
--- a/lib/qlib_init.h
+++ b/lib/qlib_init.h
@@ -22,6 +22,8 @@
#ifndef _ZEBRA_QLIB_INIT_H
#define _ZEBRA_QLIB_INIT_H
+#include "misc.h"
+
/*==============================================================================
* Quagga Library Initialise/Closedown
*
@@ -33,8 +35,18 @@
extern void qlib_init_first_stage(void) ;
-extern void qlib_init_second_stage(int pthreads) ;
+extern void qlib_init_second_stage(bool pthreads) ;
extern void qexit(int exit_code) ;
+/*==============================================================================
+ * System parameters, set at qlib_init_first_stage() time.
+ */
+
+int qlib_iov_max ; /* Maximum length of iovec vector */
+
+int qlib_open_max ; /* Maximum number of file descriptors */
+
+int qlib_pagesize ; /* Size of system page */
+
#endif /* _ZEBRA_QLIB_INIT_H */
diff --git a/lib/qpath.c b/lib/qpath.c
new file mode 100644
index 00000000..955f307f
--- /dev/null
+++ b/lib/qpath.c
@@ -0,0 +1,1528 @@
+/* Some primitive path handling
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "misc.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+
+#include "qpath.h"
+#include "qstring.h"
+#include "memory.h"
+#include "pthread_safe.h"
+
+/*==============================================================================
+ * Some primitive path handling, based on qstrings.
+ *
+ *
+ *==============================================================================
+ * Path Reduction
+ *
+ * As per POSIX, multiple '/' count as single '/', except for the very special
+ * case of exactly two '/' at the start of a path. (That case is referred to
+ * here as a "double root".)
+ *
+ * So this code reduces runs of '/' to single '/' -- except for the special
+ * case.
+ *
+ * This code also replaces "/./" by "/", and removes xxxx/.. ! Does not strip
+ * trailing '/'.
+ */
+static void qpath_reduce(qpath qp) ;
+
+/*------------------------------------------------------------------------------
+ * Initialise a brand new qpath -- allocate if required.
+ *
+ * The result is a path with a not-empty body. All qpath values may be assumed
+ * to have a body at all times.
+ *
+ * Returns: address of qpath
+ *
+ * NB: assumes initialising a new structure. If not, then caller should
+ * use qpath_reset() or qpath_clear().
+ */
+extern qpath
+qpath_init_new(qpath qp)
+{
+ if (qp == NULL)
+ qp = XCALLOC(MTYPE_QPATH, sizeof(qpath_t)) ;
+ else
+ memset(qp, 0, sizeof(qpath_t)) ;
+
+ qs_init_new(qp->path, 50) ; /* Always have a body */
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create a new qpath if don't already have one.
+ */
+inline static qpath
+qpath_make_if_null(qpath qp)
+{
+ if (qp == NULL)
+ qp = qpath_init_new(NULL) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reset contents of given qpath, freeing the structure if required.
+ *
+ * Discards all the contents of the qpath.
+ */
+extern qpath
+qpath_reset(qpath qp, free_keep_b free_structure)
+{
+ if (qp == NULL)
+ return NULL ;
+
+ qs_reset(qp->path, keep_it) ;
+
+ if (free_structure)
+ XFREE(MTYPE_QPATH, qp) ; /* sets qp = NULL */
+ else
+ ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear down given qpath -- retaining any body, but setting it empty.
+ */
+extern qpath
+qpath_clear(qpath qp)
+{
+ if (qp == NULL)
+ return NULL ;
+
+ qs_clear(qp->path) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given string -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set(qpath dst, const char* src)
+{
+ return qpath_set_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given qstring -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_qs(qpath dst, const qstring src)
+{
+ return qpath_set_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given string -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ qs_set_n(dst->path, src, n) ;
+
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Copy qpath to the given qpath -- creating qpath if required.
+ * The result is an empty qpath if the given one is NULL.
+ */
+extern qpath
+qpath_copy(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_set_n(dst, qs_char_nn(src->path), qs_len_nn(src->path)) ;
+ else
+ return qpath_set_n(dst, NULL, 0) ;
+} ;
+
+/*==============================================================================
+ * Interfaces to system.
+ */
+
+/*------------------------------------------------------------------------------
+ * Get the current working directory -- creates a qpath if required.
+ *
+ * Returns: the (new) qpath if OK
+ * NULL if not OK -- any existing qpath is cleared.
+ *
+ * If fails will be because some directory on the way back to the root is
+ * not readable or searchable (!).
+ */
+extern qpath
+qpath_getcwd(qpath dst)
+{
+ qpath od ;
+
+ od = dst ;
+ dst = qpath_make_if_null(dst) ;
+
+ qs_new_size(dst->path, 50) ;
+
+ while (1)
+ {
+ void* r ;
+ usize s ;
+
+ s = qs_size_nn(dst->path) ;
+ r = getcwd(qs_char_nn(dst->path), s) ;
+
+ if (r != NULL)
+ {
+ qs_set_strlen_nn(dst->path) ;
+ return dst ; /* exit here if OK. */
+ } ;
+
+ if (errno != ERANGE)
+ break ; /* exit here on failure */
+
+ qs_new_size(dst->path, s * 2) ;
+ } ;
+
+ if (od == NULL)
+ qpath_reset(dst, free_it) ;
+ else
+ qpath_clear(dst) ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the current working directory
+ *
+ * Returns: 0 <=> OK
+ * errno otherwise
+ */
+extern int
+qpath_setcwd(qpath qp)
+{
+ return (chdir(qpath_string(qp)) == 0) ? 0 : errno ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do "stat" for given path.
+ *
+ * Returns: 0 <=> OK
+ * errno otherwise
+ */
+extern int
+qpath_stat(qpath qp, struct stat* sbuf)
+{
+ return (stat(qpath_string(qp), sbuf) == 0) ? 0 : errno ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is given path a file we might access -- according to stat ?
+ *
+ * Returns: -1 <=> no -- can access etc, but it's not a file
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_file(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISREG(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is given path a directory we might access -- according to stat ?
+ *
+ * Returns: -1 <=> no -- can access etc, but it's not a directory
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_directory(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISDIR(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get home directory for the given user.
+ *
+ * The name may be terminated by '\0' or '/'.
+ *
+ * If the qp path is NULL, creates a new qpath if required.
+ *
+ * Returns: qpath => OK
+ * NULL => errno == 0 => user not found ) existing qp set empty.
+ * errno != 0 => some error )
+ */
+extern qpath
+qpath_get_home(qpath qp, const char* name)
+{
+ qpath dst ;
+ const char* p ;
+
+ bool done ;
+ qpath home ;
+ int err ;
+
+ /* Prepare for action.
+ */
+ p = name ;
+
+ while ((*p != '\0') && (*p != '/'))
+ ++p ; /* find terminator */
+
+ dst = qpath_set_n(qp, name, p - name) ;
+ /* set user name */
+
+ done = false ; /* no result, yet */
+ home = NULL ; /* empty result, so far */
+ err = 0 ; /* no error, yet */
+
+ /* If the name is empty, attempt to get the HOME environment variable,
+ * failing that set the user name to the getlogin_r() name.
+ */
+ if ((p - name) == 0)
+ {
+ int l, s ;
+
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ l = getenv_r("HOME", qs_char_nn(dst->path), s) ;
+
+ if (l < s)
+ break ;
+
+ qs_new_size(dst->path, l) ;
+ } ;
+
+ if (l >= 0)
+ {
+ qs_set_len_nn(dst->path, l) ;
+ home = dst ; /* Successfully fetched HOME */
+ done = true ;
+ }
+ else
+ {
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ qassert(s > 0) ;
+ err = getlogin_r(qs_char_nn(dst->path), s) ;
+
+ if (err != ERANGE)
+ break ;
+
+ qs_new_size(dst->path, s * 2) ;
+ } ;
+
+ if (err == 0)
+ {
+ qs_set_strlen_nn(dst->path) ;
+
+ if (qs_len_nn(dst->path) == 0)
+ done = true ;
+ }
+ else
+ done = true ;
+ } ;
+ } ;
+
+ /* If name was empty, or not found "HOME", then proceed to getpwd_r
+ */
+ if (!done)
+ {
+ struct passwd pwd_s ;
+ struct passwd* pwd ;
+
+ char* scratch ;
+ uint scratch_size ;
+ char buffer[200] ; /* should be plenty */
+
+ scratch = buffer ;
+ scratch_size = sizeof(buffer) ;
+
+ while (1)
+ {
+ err = getpwnam_r(qpath_string(dst),
+ &pwd_s, scratch, scratch_size, &pwd) ;
+ if (err == EINTR)
+ continue ;
+
+ if (err != ERANGE)
+ break ;
+
+ scratch_size *= 2 ;
+ if (scratch == buffer)
+ scratch = XMALLOC(MTYPE_TMP, scratch_size) ;
+ else
+ scratch = XREALLOC(MTYPE_TMP, scratch, scratch_size) ;
+ } ;
+
+ done = true ;
+
+ if (pwd != NULL)
+ {
+ qpath_set(dst, pwd->pw_dir) ;
+ home = dst ;
+ } ;
+
+ if (scratch != buffer)
+ XFREE(MTYPE_TMP, scratch) ;
+ } ;
+
+ /* Complete result
+ */
+ if (home == NULL)
+ {
+ if (qp != NULL)
+ qs_set_len_nn(qp->path, 0) ; /* chop existing path */
+ else
+ qpath_free(dst) ; /* discard temporary */
+
+ errno = err ; /* as promised */
+ } ;
+
+ return home ;
+} ;
+
+/*==============================================================================
+ * Path editing functions
+ *
+ *
+ */
+
+/*------------------------------------------------------------------------------
+ * Shave any file part off the end of the given path.
+ *
+ * This treats anything after the last '/' of the path as being the "file part".
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty -> unchanged
+ *
+ * 2. "aaa" -- file part only -> ""
+ *
+ * 3. "/" -- root only, or -> unchanged
+ * "//" double root only -> unchanged
+ *
+ * 4. "/aaa" -- routed file -> "/"
+ * "//aaa" double routed file -> "//"
+ *
+ * 5. "zzz/" -- no file part -> unchanged
+ *
+ * 6. "zzz/aaa" -- non-empty file part -> "zzz/"
+ *
+ * Ensures that the path is "reduced" before shaving.
+ *
+ * Creates a new, empty path if qp is NULL.
+ */
+extern qpath
+qpath_shave(qpath qp)
+{
+ pp_t p ;
+
+ qp = qpath_make_if_null(qp) ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ /* Track back to last '/' */
+ while ( (p->e > p->p) && (*(p->e - 1) != '/') )
+ --p->e ;
+
+ qs_set_len_nn(qp->path, p->e - p->p) ;
+
+ return qp ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * See if path ends '/'.
+ */
+extern bool
+qpath_has_trailing_slash(qpath qp)
+{
+ pp_t p ;
+
+ if (qp == NULL)
+ return false ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ return (p->e > p->p) && (*(p->e - 1) == '/') ;
+} ;
+
+#if 0
+
+/*==============================================================================
+ * Pop the last part of the given path.
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty path
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 2. "aaa" -- the path is a single part
+ *
+ * - part removed from path, leaves ""
+ * - return the part
+ *
+ * Note that in (1) and (2) there is no '/', in the following there is at least
+ * one '/'.
+ *
+ * 3. "/" -- root only, or
+ * "//" double root only
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 4. "/aaa" -- one remaining part before the root,
+ * "//aaa" or one remaining part after double route
+ *
+ * - part removed from path, leaves "/" or "//"
+ * - return the part
+ *
+ * 5. "zzz/" -- empty last part
+ *
+ * - "/" removed from end of path
+ * - return empty part
+ *
+ * 6. "zzz/aaa" -- non-empty last part
+ *
+ * - part and "/" removed from end of path
+ * - return part
+ *
+ * Note that other forms of multiple '/' have been reduced, already.
+ */
+extern qpath
+qpath_pop(qpath to, qpath from)
+{
+ qpath part ;
+ qstring qs ;
+ char* sp ;
+ char* p ;
+
+ /* Get pointers to start and end of path */
+ if (qp != NULL)
+ {
+ qs = &qp->path ;
+
+ sp = qs_chars(qs) ; /* NULL if qpath is completely empty */
+ p = qs_ep_char(qs) ; /* points at trailing '\0' */
+ }
+ else
+ qs = sp = p = NULL ;
+
+ /* Deal with NULL-ness */
+ if (sp == NULL)
+ {
+ assert(p == sp) ; /* ie qs->len == 0 if qs->body == NULL */
+
+ return qpath_init_new(NULL, NULL) ;
+ } ;
+
+ /* Track back to last '/' */
+ while ( (p > sp) && (*(p - 1) != '/') )
+ --p ;
+
+ /* Construct result which is from p forwards
+ *
+ * p == NULL if the given path is completely empty.
+ */
+ part = qpath_init_new(NULL, p) ;
+
+ /* If what remains is not empty, and not just "/" or "//", remove trailing '/'
+ *
+ * If p == sp, there was no '/', so path ends up empty.
+ * If p == sp + 1, there is one '/', and that is at the front of the path
+ * If p == sp + 2, there is either "//" or "a/"
+ * If p > sp + 2, there is "aa/"
+ */
+ if (p >= (sp + 2)) /* if "//", "a/" or "aa/" etc. */
+ {
+ /* Unless is special case of "//"... */
+ if ( ! ((p == (sp + 2)) && (*sp == '/')) )
+ --p ; /* ... discard trailing '/' */
+ } ;
+
+ qs->len = (p - sp) ; /* set the new length (shorter !) */
+ *p = '\0' ; /* and terminate */
+
+ /* Return the part we hacked off */
+ return part ;
+} ;
+
+
+
+
+/*==============================================================================
+ * Shift off the first part of the given path.
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty path
+ *
+ * - path unchanged
+ * - return empty part
+ *
+ * 2. "aaa" -- the path is a single part
+ *
+ * - part removed from path, leaves ""
+ * - return the part
+ *
+ * Note that in (1) and (2) there is no '/', in the following there is at least
+ * one '/'.
+ *
+ * 3. "/" -- root only, or
+ * "//" double root
+ *
+ * - remove the "/" or "//" -- result is empty
+ * - return "/" or "//" part
+ *
+ * 4. "/azz" -- root followed by stuff, or
+ * "//zzz" double root followed by stuff.
+ *
+ * - remove the "/" or "//"
+ * - return "/" or "//" part
+ *
+ * 5. "aaa/" -- something followed by first '/' which is end of path
+ *
+ * - remove upto and including '/' -- result is empty
+ * - return upto but excluding '/'
+ *
+ * 6. "aaa/azz" -- something followed by first '/' followed by something else
+ *
+ * - remove upto and including '/'
+ * - return upto but excluding '/'
+ *
+ * Note that other forms of multiple '/' have been reduced, already.
+ */
+extern qpath
+qpath_shift(qpath qp)
+{
+ qpath part ;
+ qstring qs ;
+ char* sp ;
+ char* p ;
+ char* q ;
+
+ /* Get pointers to start and end of path */
+ if (qp != NULL)
+ {
+ qs = &qp->path ;
+ sp = qs_chars(qs) ; /* NULL if qpath is completely empty */
+ }
+ else
+ qs = sp = NULL ;
+
+ /* Deal with NULL-ness */
+ if (sp == NULL)
+ return qpath_init_new(NULL, NULL) ;
+
+ p = sp ;
+
+ /* Set p such that sp..p-1 is to be shifted off.
+ * And q such that q..end-1 is to be kept.
+ */
+ if (*sp == '/')
+ {
+ ++p ; /* single root */
+ if (*p == '/')
+ ++p ; /* double root */
+ q = p ;
+ }
+ else
+ {
+ while ((*p != '/') && (*p != '/0'))
+ ++p ; /* step to '/' or end */
+
+ if (*p == '/')
+ q = p + 1 ; /* don't keep '/' */
+ else
+ q = p ; /* keep '\0' ! */
+ } ;
+
+ /* Construct qpath for shifted off stuff */
+ part = qpath_init_new(NULL, NULL) ;
+
+ /* Hack off the shifted off stuff & '/' if required */
+
+
+ /* Return the part we hacked off */
+ return part ;
+} ;
+#endif
+
+/*==============================================================================
+ * Append, Prepend and Complete
+ */
+
+static ulen qpath_trim_home(const char* p, ulen n) ;
+
+/*------------------------------------------------------------------------------
+ * Append one path (src) onto the end of the given path (dst).
+ *
+ * If the dst path is NULL, creates a new, empty qpath to append to.
+ *
+ * The dst path is assumed to be the path to a "directory". An empty dst path
+ * is treated as "the current directory".
+ *
+ * If src path starts '/' or '~', then it is trimmed, removing leading
+ * characters up to and including '/'.
+ *
+ * If dst path is not empty, and does not end '/', then an '/' is appended
+ * before the src is appended.
+ *
+ * Note that this means:
+ *
+ * -- appending an empty path or one which is just "/", will leave the dst
+ * path ending "/" -- unless the dst path is empty.
+ *
+ * -- cannot create a rooted path by appending a path onto an empty path.
+ *
+ * -- appending a "homed" path "~..../" is assumed to be appending to the
+ * required "home".
+ *
+ * The resulting path is reduced (see above).
+ */
+extern qpath
+qpath_append(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_append_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_append_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src qstring onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_qs(qpath dst, const qstring src)
+{
+ return qpath_append_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src string onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_str(qpath dst, const char* src)
+{
+ return qpath_append_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append src string of given length onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_str_n(qpath dst, const char* src, ulen n)
+{
+ ulen l ;
+
+ dst = qpath_make_if_null(dst) ;
+
+ /* Trim the path to be appended:
+ *
+ * 1. discard from any leading '~' to the first '/' or to '\0'.
+ *
+ * 2. then discard any leading '/'
+ *
+ * 3. then establish length of result.
+ */
+ l = n ;
+ n = qpath_trim_home(src, n) ;
+ src += (l - n) ; /* step past stuff trimmed */
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ if (qs_len_nn(dst->path) != 0)
+ {
+ if (*(qs_ep_char_nn(dst->path) - 1) != '/')
+ qs_append_str_n(dst->path, "/", 1) ;
+ } ;
+
+ /* Now append the src */
+ qs_append_str_n(dst->path, src, n) ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src path.
+ *
+ * Does not introduce any '/' or any other stuff.
+ *
+ * The resulting path is reduced (see above).
+ */
+extern qpath
+qpath_extend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_extend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_extend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src qstring.
+ */
+extern qpath
+qpath_extend_qs(qpath dst, const qstring src)
+{
+ return qpath_extend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string.
+ */
+extern qpath
+qpath_extend_str(qpath dst, const char* src)
+{
+ return qpath_extend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string of the given
+ * length.
+ */
+extern qpath
+qpath_extend_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ qs_append_str_n(dst->path, src, n) ;
+
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src path onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src qstring onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_qs(qpath dst, const qstring src)
+{
+ return qpath_prepend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src string onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepend src string of given length onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_str_n(qpath dst, const char* src, ulen n)
+{
+ char* p ;
+ ulen r ;
+ bool need_slash ;
+
+ dst = qpath_make_if_null(dst) ;
+
+ /* Trim the path to be prepended to:
+ *
+ * 1. discard from any leading '~' to the first '/' (or end).
+ *
+ * 2. then discard any leading '/'
+ *
+ * 3. then establish length of any part to be replaced.
+ */
+ r = qs_len_nn(dst->path) ;
+ r -= qpath_trim_home(qs_char_nn(dst->path), r) ;
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ need_slash = (n > 0) && (*(src + n - 1) != '/') ;
+
+ /* Make room for src and possible slash in qstring */
+ qs_set_cp_nn(dst->path, 0) ;
+ qs_replace(dst->path, r, NULL, n + (need_slash ? 1 : 0)) ;
+
+ /* Now copy in the src */
+ p = qs_char_nn(dst->path) ;
+
+ if (n > 0)
+ memmove(p, src, n) ;
+
+ if (need_slash)
+ *(p + n) = '/' ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make a qpath from the given string, completing it, if required, by
+ * prepending the given directory qpath.
+ */
+extern qpath
+qpath_make(const char* src, const qpath dir)
+{
+ if (*src == '/')
+ return qpath_set(NULL, src) ;
+
+ return qpath_append_str(qpath_dup(dir), src) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src path to it. Result is reduced.
+ */
+extern qpath
+qpath_complete(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src qstring to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_qs(qpath dst, const qstring src)
+{
+ return qpath_complete_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string of given length to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ if ((qs_len_nn(dst->path) == 0) || (*(qs_char_nn(dst->path)) == '/'))
+ qpath_prepend_str_n(dst, src, n) ;
+ else
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim leading '~' up to and including one or more '/'.
+ *
+ * Return remaining length after the trim.
+ */
+static ulen
+qpath_trim_home(const char* p, ulen n)
+{
+ if ((n > 0) && (*p == '~'))
+ {
+ do /* Step past leadin '~' to first '/' */
+ {
+ ++p ;
+ --n ;
+ } while ((n > 0) && (*p != '/')) ;
+ } ;
+
+ while ((n > 0) && (*p == '/'))
+ {
+ ++p ; /* Step past leading '/' */
+ --n ;
+ } ;
+
+ return n ;
+} ;
+
+/*==============================================================================
+ *
+ */
+
+#if 0
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_sex(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return qp_empty ; /* NULL qpath is empty */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp == '\0'))
+ return qp_empty ; /* NULL body or just '\0' */
+
+ if (*sp == '~')
+ return qp_homed ;
+
+ if (*sp == '/')
+ {
+ ++sp ;
+ if (*sp == '\0')
+ return qp_root ;
+ if (*sp != '/')
+ return qp_absolute ;
+
+ ++sp ;
+ if (*sp == '\0')
+ return qp_double_root ;
+ else
+ return qp_double_absolute ;
+ } ;
+
+
+
+ qp_empty, /* nothing at all */
+ qp_relative, /* something, not starting '/' */
+ qp_root, /* "/" all on its own */
+ qp_absolute, /* something, starting with single '/' */
+ qp_homed, /* something, starting with '~' */
+ qp_double_root, /* "//" all on its own */
+ qp_double_absolute /* something, starting with "//" */
+
+
+
+ return ((qp->path).len == 1) && (*sp == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is there anything there ?
+ */
+extern bool
+qpath_is_empty(qpath qp)
+{
+ if (qp == NULL)
+ return true ; /* NULL qpath is empty */
+
+ return (qp->path).len == 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is it: not empty, not starting '/' (or '//') and not starting '~'
+ */
+extern bool
+qpath_is_relative(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath is not relative */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp != '/') && (*sp != '.') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_is_root(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be root */
+
+ sp = qs_chars(&qp->path) ;
+
+ return ((qp->path).len == 1) && (*sp == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '/' (and not '//') ?
+ *
+ * Note that just "/" (ie root) will return true => it is also absolute.
+ */
+extern bool
+qpath_is_absolute(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be absolute */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp == '/') && (*(sp + 1) != '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '~'
+ */
+extern bool
+qpath_is_homed(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be homed */
+
+ sp = qs_chars( &qp->path) ;
+
+ return (sp != NULL) && (*sp == '~') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start and end '/' ?
+ */
+extern bool
+qpath_is_double_root(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be root */
+
+ sp = qs_chars(&qp->path) ;
+
+ return ((qp->path).len == 2) && (*sp == '/') && (*(sp + 1) == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path start '/' (and not '//') ?
+ *
+ * Note that just "/" (ie root) will return true => it is also absolute.
+ */
+extern bool
+qpath_is_double_absolute(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be absolute */
+
+ sp = qs_chars(&qp->path) ;
+
+ return (sp != NULL) && (*sp == '/') && (*(sp + 1) == '/') ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Does the given path *end* '/', or is it ~aaaa with no '/' at all.
+ *
+ * Note that root and double route return directory true.
+ */
+extern bool
+qpath_is_directory(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be directory */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp = '\0'))
+ return false ; /* Empty qpath cannot be directory */
+
+ ep = qs_ep_char(qs) ;
+
+ if (*(ep - 1) == '/')
+ return true ; /* Ends '/' */
+
+ if (*sp == '~')
+ {
+ while (*(++sp) != '/')
+ {
+ if (*sp == '\0') /* Starts '~' and no '/' found */
+ return true ;
+ } ;
+ } ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is the given path an atom
+ *
+ * Note that root and double route are .
+ */
+extern bool
+qpath_is_atom(qpath qp)
+{
+ char* sp ;
+
+ if (qp == NULL)
+ return false ; /* NULL qpath cannot be directory */
+
+ sp = qs_chars(&qp->path) ;
+
+ if ((sp == NULL) || (*sp = '\0'))
+ return false ; /* Empty qpath cannot be directory */
+
+ ep = qs_ep_char(qs) ;
+
+ if (*(ep - 1) == '/')
+ return true ; /* Ends '/' */
+
+ if (*sp == '~')
+ {
+ while (*(++sp) != '/')
+ {
+ if (*sp == '\0') /* Starts '~' and no '/' found */
+ return true ;
+ } ;
+ } ;
+
+ return false ;
+} ;
+
+#endif
+
+/*==============================================================================
+ *
+ *
+ *
+ */
+/*------------------------------------------------------------------------------
+ * Reduce multiple '/' to single '/' (except for exactly "//" at start).
+ *
+ * Reduce "zzz/./zzz" to "zzz/zzz" -- s/\/\.(\/|\0)/$1/g
+ * "zzz/./" to "zzz/"
+ * "zzz/." to "zzz/"
+ * "/./" to "/"
+ * "/." to "/"
+ *
+ * where zzz is anything, including '/'
+ *
+ * Reduce "zzz/aaa/../zzz" to "zzz/zzz" -- s/\.*([^/.]+\.*)+\/\.\.\/?//g
+ * "aaa/../zzz" to "zzz"
+ * "/aaa/../zzz" to "/zzz"
+ * "/aaa/../" to "/"
+ * "aaa/../" to ""
+ * "aaa/.." to ""
+ *
+ * where aaa is anything except '/'
+ *
+ * NB:
+ *
+ * NB: does not strip trailing '/' (including '/' of trailing "/." or "/..")
+ *
+ * does not reduce leading "./" or single "." to nothing.
+ */
+static void
+qpath_reduce(qpath qp)
+{
+ char* sp ;
+ char* p ;
+ char* q ;
+ char ch ;
+ bool eat ;
+
+ sp = qs_make_string(qp->path) ;
+ p = sp ;
+
+ /* Deal with special case of "//" at start.
+ *
+ * If find "//x", where x is anything other than '/', step past the first
+ * '/'. Could step past both "//", but that stops it seeing "//./zzz"
+ */
+ if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/'))
+ ++p ;
+
+ /* Scan to see if there is anything that needs to be fixed.
+ *
+ * Looking for "//", "/./", "/.\0", "/../" or "/..\0".
+ *
+ * NB: does not consider that "." needs to be fixed, nor "./", and does not
+ * strip trailing "/".
+ */
+ while (1)
+ {
+ ch = *p++ ;
+
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch != '/') /* scanning for '/' */
+ continue ;
+
+ ch = *p++ ; /* get char after '/' */
+
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch == '/')
+ break ; /* second '/' */
+
+ if (ch != '.')
+ continue ; /* not "//" and not "/." */
+
+ ch = *p ; /* get char after "/." */
+
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/./" or "/.\0" */
+
+ if (ch != '.')
+ continue ; /* not "/..", either */
+
+ ch = *(p+1) ; /* get char after "/.." */
+
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/../" or "/..\0" */
+ } ;
+
+ /* Rats... there is something to be fixed.
+ *
+ * Enter the reduction loop ready to eat the first item.
+ */
+ --p ; /* back to second '/' or '.' */
+ q = p ; /* keep the first '/' */
+
+ eat = true ;
+
+ while (1)
+ {
+ if (eat)
+ {
+ eat = false ;
+
+ /* At this point:
+ *
+ * *p is '/', which is second of "//???"
+ * or (first) '.' of "./???", ".\0", "../???" or "..\0"
+ *
+ * *q is start of path or just after a '/'
+ *
+ * NB: after the first time through, p != q (or not necessarily).
+ */
+ qassert((*p == '/') || (*p == '.')) ;
+ if (*p == '.')
+ qassert((*(p+1) == '\0') || (*(p+1) == '/') || (*(p+1) == '.')) ;
+
+ qassert((q == sp) || (*(q-1) == '/')) ;
+
+ if ((*p == '.') && (*(p+1) == '.'))
+ {
+ char* sq = q ; /* in case find leading '~' */
+
+ qassert((*(p+2) == '\0') || (*(p+2) == '/')) ;
+
+ /* Deal with "../???" or "..\0"
+ *
+ * Before can strip the "..", have to verify that we have
+ * something to step back over.
+ *
+ * Can eat the ".." unless : is at start of path
+ * have nothing before the '/'
+ * (only) have '/' before the '/'
+ * only have "." before the '/'
+ * only have ".." before the '/'
+ * have "/.." before the '/'
+ *
+ * Note: can only have '/' before the '/' at the start.
+ *
+ * If cannot eat the '..', go back to the top of the loop, with
+ * eat == false, and process as an ordinary item.
+ */
+ if (q <= (sp + 1))
+ continue ; /* at start or
+ * nothing before the '/' */
+ if (*(q-2) == '/')
+ continue ; /* '/' before the '/' */
+
+ if (*(q-2) == '.')
+ {
+ /* Have '.' before the '/' */
+ if (q == (sp + 2))
+ continue ; /* only '.' before the '/' */
+
+ if (*(q-3) == '.')
+ {
+ /* Have ".." before the '/' */
+ if (q == (sp + 3))
+ continue ; /* only ".." before the '/' */
+
+ if (*(q-4) == '/')
+ continue ; /* "/.." before the '/' */
+ } ;
+ } ;
+
+ /* Eat the preceding item, including the final '/'
+ *
+ * *p == "../" or "..\0" to eat
+ * *q == just after '/' at end of item to eat
+ */
+ do /* starts with q just past '/' */
+ --q ;
+ while ((q > sp) && (*(q-1) != '/')) ;
+
+ /* Very special case: if have reduced the path to nothing,
+ * but path started with '~' then need to undo everything,
+ * and treate the '..' as ordinary item.
+ *
+ * Copes with ~fred/.. !!
+ */
+ if ((q == sp) && (*q == '~'))
+ {
+ q = sq ; /* put back item */
+ continue ; /* keep the '..' */
+ } ;
+
+ /* Eat the '..' and stop now if nothing more to come.
+ */
+ p += 2 ;
+
+ if (*p == '\0')
+ break ;
+
+ qassert(*p == '/') ;
+ } ;
+
+ /* Now discard '.' or '/' and any number of following '/'.
+ *
+ * *p == the '.' or first '/' to be discarded
+ *
+ * Ends up at first character after a '/' -- which may be anything,
+ * including a possible '.'
+ */
+ do
+ ++p ;
+ while (*p == '/') ;
+ }
+ else
+ {
+ /* Ordinary item (or '..') to copy across -- eat == false.
+ *
+ * Copying stuff, until get to '\0' or copy a '/'.
+ */
+ do
+ {
+ ch = *p ;
+
+ if (ch == '\0')
+ break ;
+
+ ++p ;
+ *q++ = ch ;
+ }
+ while (ch != '/') ;
+ } ;
+
+ /* Have processed to '\0' and/or just past a '/'
+ *
+ * Decide whether to continue, and if so whether to eat what follows
+ * or copy it across.
+ */
+ ch = *p ;
+
+ if (ch == '\0')
+ break ;
+
+ qassert(!eat) ;
+
+ if (ch == '/')
+ eat = true ; /* second '/' after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+1) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "./???" or ".\0" after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+2) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "../???" or "..\0" after '/' */
+ } ;
+ } ;
+ } ;
+
+ /* Adjust the length and terminate
+ */
+ qs_set_len_nn(qp->path, q - sp) ; /* set the new length (shorter !) */
+} ;
+
diff --git a/lib/qpath.h b/lib/qpath.h
new file mode 100644
index 00000000..9b0509d7
--- /dev/null
+++ b/lib/qpath.h
@@ -0,0 +1,217 @@
+/* Some primitive path handling -- header
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_QPATH_H
+#define _ZEBRA_QPATH_H
+
+#include "misc.h"
+#include "qstring.h"
+#include "memory.h"
+#include "sys/stat.h"
+
+/*==============================================================================
+ * For these purposes a path is "parts" separated by one more '/' characters.
+ *
+ * As per POSIX, a pair of leading "//" (not three or more leading '/') is
+ * very special.
+ *
+ * The following sexes of qpath are established after any "path reduction"
+ * has been done. Path reduction removes extraneous '/'.
+ */
+
+typedef enum qpath_sex qpath_sex_t ;
+
+enum qpath_sex
+{
+ qp_empty, /* nothing at all */
+ qp_relative, /* something, not starting '/' */
+ qp_root, /* "/" all on its own */
+ qp_absolute, /* something, starting with single '/' */
+ qp_homed, /* something, starting with '~' */
+ qp_double_root, /* "//" all on its own */
+ qp_double_absolute /* something, starting with "//" */
+} ;
+
+/* The qpath structure is largely a qstring, but one in which there is always
+ * a body, even if it only contains "\0", and the len is kept up to date.
+ */
+struct qpath
+{
+ qstring_t path ; /* Embedded */
+} ;
+
+typedef struct qpath qpath_t[1] ;
+typedef struct qpath* qpath ;
+
+/*==============================================================================
+ * Functions
+ */
+
+extern qpath qpath_init_new(qpath qp) ;
+extern qpath qpath_reset(qpath qp, free_keep_b free_structure) ;
+extern qpath qpath_clear(qpath qp) ;
+
+Inline qpath qpath_new(void) ;
+Inline qpath qpath_free(qpath qp) ;
+
+Inline const char* qpath_string(qpath qp) ;
+Inline char* qpath_char_string(qpath qp) ;
+Inline ulen qpath_len(qpath qp) ;
+Inline const qstring qpath_qs(qpath qp) ;
+
+extern qpath qpath_set(qpath dst, const char* src) ;
+extern qpath qpath_set_n(qpath dst, const char* src, ulen n) ;
+extern qpath qpath_set_qs(qpath dst, const qstring src) ;
+extern qpath qpath_copy(qpath dst, const qpath src) ;
+Inline qpath qpath_dup(const qpath qp) ;
+Inline qpath qpath_dup_str(const char* src) ;
+
+extern qpath qpath_getcwd(qpath dst) ;
+extern qpath qpath_get_home(qpath qp, const char* name) ;
+extern int qpath_setcwd(qpath dst) ;
+extern int qpath_stat(qpath qp, struct stat* stat) ;
+extern int qpath_stat_is_file(qpath qp) ;
+extern int qpath_stat_is_directory(qpath qp) ;
+
+extern qpath qpath_shave(qpath qp) ;
+extern bool qpath_has_trailing_slash(qpath qp) ;
+
+extern qpath qpath_append(qpath dst, const qpath src) ;
+extern qpath qpath_append_qs(qpath dst, const qstring src) ;
+extern qpath qpath_append_str(qpath dst, const char* src) ;
+extern qpath qpath_append_str_n(qpath dst, const char* src, ulen n) ;
+
+extern qpath qpath_extend(qpath dst, const qpath src) ;
+extern qpath qpath_extend_qs(qpath dst, const qstring src) ;
+extern qpath qpath_extend_str(qpath dst, const char* src) ;
+extern qpath qpath_extend_str_n(qpath dst, const char* src, ulen n) ;
+
+extern qpath qpath_prepend(qpath dst, const qpath src) ;
+extern qpath qpath_prepend_qs(qpath dst, const qstring src) ;
+extern qpath qpath_prepend_str(qpath dst, const char* src) ;
+extern qpath qpath_prepend_str_n(qpath dst, const char* src, ulen n) ;
+
+extern qpath qpath_make(const char* src, const qpath dir) ;
+
+extern qpath qpath_complete(qpath dst, const qpath src) ;
+extern qpath qpath_complete_qs(qpath dst, const qstring src) ;
+extern qpath qpath_complete_str(qpath dst, const char* src) ;
+extern qpath qpath_complete_str_n(qpath dst, const char* src, ulen n) ;
+
+/*==============================================================================
+ * Inline stuff
+ */
+
+/*------------------------------------------------------------------------------
+ * Create new qpath.
+ */
+Inline qpath
+qpath_new(void)
+{
+ return qpath_init_new(NULL) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free qpath.
+ */
+Inline qpath
+qpath_free(qpath qp)
+{
+ return qpath_reset(qp, free_it) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Duplicate qpath -- result will need to be freed.
+ */
+Inline qpath
+qpath_dup(const qpath qp)
+{
+ return qpath_copy(NULL, qp) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Duplicate string as a qpath -- result will need to be freed.
+ */
+Inline qpath
+qpath_dup_str(const char* src)
+{
+ return qpath_set(NULL, src) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get *temporary* pointer to actual path contained in the given qpath.
+ *
+ * This is *temporary* to the extent that when the qpath is changed or freed,
+ * this pointer will be INVALID -- you have been warned.
+ *
+ * This is a *const* pointer.
+ *
+ * For a NULL qpath, or an empty qpath, returns pointer to an empty string
+ * ('\0' terminated "").
+ */
+Inline const char*
+qpath_string(qpath qp)
+{
+ return (qp != NULL) ? qs_make_string(qp->path) : "" ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get *temporary* pointer to actual path contained in the given qpath.
+ *
+ * This is *temporary* to the extent that when the qpath is changed or freed,
+ * this pointer will be INVALID -- you have been warned.
+ *
+ * This is a *const* pointer.
+ *
+ * qpath may *not* be NULL qpath.
+ */
+Inline char*
+qpath_char_string(qpath qp)
+{
+ return qs_make_string(qp->path) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get length of the given qpath -- zero if NULL
+ */
+Inline ulen
+qpath_len(qpath qp)
+{
+ return (qp != NULL) ? qs_len_nn(qp->path) : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get *temporary* pointer to qstring rendering of the given path.
+ *
+ * This is *temporary* to the extent that when the qpath is changed or freed,
+ * this pointer will be INVALID -- you have been warned.
+ *
+ * This is a *const* pointer.
+ *
+ * For a NULL qpath returns NULL qstring.
+ */
+Inline const qstring
+qpath_qs(qpath qp)
+{
+ return (qp != NULL) ? qp->path : NULL ;
+} ;
+
+#endif /* _ZEBRA_QPATH_H */
diff --git a/lib/qpnexus.c b/lib/qpnexus.c
index 3b014be2..8a805900 100644
--- a/lib/qpnexus.c
+++ b/lib/qpnexus.c
@@ -18,9 +18,8 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
-
-#include <zebra.h>
-#include <stdbool.h>
+#include "misc.h"
+#include <signal.h>
#include "qpnexus.h"
#include "memory.h"
@@ -37,18 +36,39 @@ static void qpn_in_thread_init(qpn_nexus qpn);
*/
+/*------------------------------------------------------------------------------
+ * Initialise the qpnexus handling -- to be done as soon as state of
+ * qpthreads_enabled is established.
+ */
+extern void
+qpn_init(void)
+{
+ qpt_data_create(qpn_self) ; /* thread specific data */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the thread's qpn_self to point at its qpnexus.
+ */
+static void
+qpn_self_knowledge(qpn_nexus qpn)
+{
+ qpt_data_set_value(qpn_self, qpn) ;
+} ;
+
/*==============================================================================
* Initialisation, add hook, free etc.
*
*/
/*------------------------------------------------------------------------------
- * Initialise a nexus -- allocating it if required.
+ * Initialise a nexus -- allocating it if required.
*
- * If main_thread is set then no new thread will be created
- * when qpn_exec() is called, instead the finite state machine will be
- * run in the calling thread. The main thread will only block the
- * message queue's signal. Non-main threads will block most signals.
+ * If main_thread is set then no new thread will be created when qpn_exec() is
+ * called, instead the finite state machine will be run in the calling thread.
+ *
+ * The main thread will only block the message queue's signal.
+ *
+ * Non-main threads will block most signals.
*
* Returns the qpn_nexus.
*/
@@ -67,10 +87,13 @@ qpn_init_new(qpn_nexus qpn, bool main_thread)
qpn->start = qpn_start;
if (main_thread)
- qpn->thread_id = qpt_thread_self();
+ {
+ qpn->thread_id = qpt_thread_self();
+ qpn_self_knowledge(qpn) ;
+ } ;
return qpn;
-}
+} ;
/*------------------------------------------------------------------------------
* Add a hook function to the given nexus.
@@ -91,7 +114,7 @@ qpn_add_hook_function(qpn_hook_list list, void* hook)
* object is empty or otherwise out of action.
*/
extern qpn_nexus
-qpn_reset(qpn_nexus qpn, bool free_structure)
+qpn_reset(qpn_nexus qpn, free_keep_b free_structure)
{
qps_file qf;
qtimer qtr;
@@ -115,11 +138,8 @@ qpn_reset(qpn_nexus qpn, bool free_structure)
qpn->selection = NULL ;
}
- if (qpn->queue != NULL)
- qpn->queue = mqueue_reset(qpn->queue, 1);
-
- if (qpn->mts != NULL)
- qpn->mts = mqueue_thread_signal_reset(qpn->mts, 1);
+ qpn->queue = mqueue_reset(qpn->queue, free_it);
+ qpn->mts = mqueue_thread_signal_reset(qpn->mts, free_it);
if (free_structure)
XFREE(MTYPE_QPN_NEXUS, qpn) ; /* sets qpn = NULL */
@@ -263,50 +283,38 @@ qpn_start(void* arg)
static void
qpn_in_thread_init(qpn_nexus qpn)
{
- sigset_t newmask;
+ sigset_t sigmask[1];
qpn->thread_id = qpt_thread_self();
-
+ qpn_self_knowledge(qpn) ;
+
+ /* Signal mask.
+ *
+ * The main thread blocks nothing, except SIG_INTERRUPT. So (a) all
+ * signals other than the "hard cases" are routed to the main thread, and
+ * (b) SIG_INTERRUPT is masked until it is unmasked in pselect.
+ *
+ * Other threads block everything except the hard cases and SIG_INTERRUPT.
+ */
if (qpn->main_thread)
- {
- /* Main thread, block the message queue's signal */
- sigemptyset (&newmask);
- sigaddset (&newmask, SIGMQUEUE);
- }
+ sigmakeset(sigmask, SIG_INTERRUPT, -1) ;
else
- {
- /*
- * Not main thread. Block most signals, but be careful not to
- * defer SIGTRAP because doing so breaks gdb, at least on
- * NetBSD 2.0. Avoid asking to block SIGKILL, just because
- * we shouldn't be able to do so. Avoid blocking SIGFPE,
- * SIGILL, SIGSEGV, SIGBUS as this is undefined by POSIX.
- * Don't block SIGPIPE so that is gets ignored on this thread.
- */
- sigfillset (&newmask);
- sigdelset (&newmask, SIGTRAP);
- sigdelset (&newmask, SIGKILL);
- sigdelset (&newmask, SIGPIPE);
- sigdelset (&newmask, SIGFPE);
- sigdelset (&newmask, SIGILL);
- sigdelset (&newmask, SIGSEGV);
- sigdelset (&newmask, SIGBUS);
- }
+ siginvset(sigmask, signal_get_hard_set()) ;
- if (qpthreads_enabled)
- qpt_thread_sigmask(SIG_BLOCK, &newmask, NULL);
- else
- {
- if (sigprocmask(SIG_BLOCK, &newmask, NULL) != 0)
- zabort_errno("sigprocmask failed") ;
- }
+ qpt_thread_sigmask(SIG_BLOCK, sigmask, NULL);
+
+ /* The signal mask to be used during pselect() */
+ sigcopyset(qpn->pselect_mask, sigmask) ;
+ sigdelset(qpn->pselect_mask, SIG_INTERRUPT) ;
+ qpn->pselect_signal = SIG_INTERRUPT ;
- /* Now we have thread_id and mask, prep for using message queue. */
+ /* Now we have thread_id and mask, prep for using message queue. */
if (qpn->queue != NULL)
- qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, SIGMQUEUE);
+ qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id,
+ SIG_INTERRUPT) ;
if (qpn->selection != NULL)
- qps_set_signal(qpn->selection, SIGMQUEUE, newmask);
-}
+ qps_set_signal(qpn->selection, qpn->pselect_mask);
+} ;
/*------------------------------------------------------------------------------
* Ask the thread to terminate itself quickly and cleanly.
@@ -322,6 +330,6 @@ qpn_terminate(qpn_nexus qpn)
/* wake up any pselect */
if (qpthreads_enabled)
- qpt_thread_signal(qpn->thread_id, SIGMQUEUE);
+ qpt_thread_signal(qpn->thread_id, SIG_INTERRUPT);
} ;
}
diff --git a/lib/qpnexus.h b/lib/qpnexus.h
index 0cf5a824..38dcbebc 100644
--- a/lib/qpnexus.h
+++ b/lib/qpnexus.h
@@ -22,11 +22,11 @@
#ifndef _ZEBRA_QPNEXUS_H
#define _ZEBRA_QPNEXUS_H
-#include <stdint.h>
+#include "misc.h"
#include <time.h>
-#include <pthread.h>
#include <unistd.h>
#include <errno.h>
+#include <signal.h>
#include "zassert.h"
#include "qpthreads.h"
@@ -34,10 +34,6 @@
#include "mqueue.h"
#include "qpselect.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Quagga Nexus Interface -- qpn_xxxx
*
@@ -46,20 +42,21 @@
* action routines.
*
*/
+enum
+{
+ /* maximum time in seconds to sit in a pselect */
+ MAX_PSELECT_WAIT = 10,
-/* maximum time in seconds to sit in a pselect */
-#define MAX_PSELECT_WAIT 10
-
-/* signal for message queues */
-#define SIGMQUEUE SIGUSR2
+ /* signal for message queues */
+ SIG_INTERRUPT = SIGUSR2,
-/* number of hooks per hook list */
-enum { qpn_hooks_max = 4 } ;
+ /* number of hooks per hook list */
+ qpn_hooks_max = 4,
+} ;
/*==============================================================================
* Data Structures.
*/
-
typedef int qpn_hook_function(void) ; /* dispatch of tasks */
typedef int qpn_init_function(void) ; /* start/stop work */
@@ -74,26 +71,30 @@ typedef struct qpn_nexus* qpn_nexus ;
struct qpn_nexus
{
- /* set true to terminate the thread (eventually) */
+ /* set true to terminate the thread (eventually) */
bool terminate;
- /* true if this is the main thread */
+ /* true if this is the main thread */
bool main_thread;
- /* thread ID */
- qpt_thread_t thread_id;
+ /* thread ID */
+ qpt_thread_t thread_id;
+
+ /* Signal mask for pselect */
+ sigset_t pselect_mask[1] ;
+ int pselect_signal ;
- /* pselect handler */
+ /* pselect handler */
qps_selection selection;
- /* timer pile */
+ /* timer pile */
qtimer_pile pile;
- /* message queue */
+ /* message queue */
mqueue_queue queue;
mqueue_thread_signal mts;
- /* qpthread routine, can override */
+ /* qpthread routine, can override */
void* (*start)(void*);
/* in-thread initialise, can override. Called within the thread after all
@@ -142,17 +143,26 @@ struct qpn_nexus
struct qpn_hook_list background ;
};
+/*------------------------------------------------------------------------------
+ * Each thread that has a qpnexus uses this piece of thread specific data in
+ * order to be able to find its own nexus.
+ */
+qpt_data_t qpn_self ; /* thread specific data */
+
+Inline qpn_nexus
+qpn_find_self(void)
+{
+ return qpt_data_get_value(qpn_self) ;
+} ;
+
/*==============================================================================
* Functions
*/
-
+extern void qpn_init(void) ;
extern qpn_nexus qpn_init_new(qpn_nexus qpn, bool 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_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)
+extern qpn_nexus qpn_reset(qpn_nexus qpn, free_keep_b free_structure);
#endif /* _ZEBRA_QPNEXUS_H */
diff --git a/lib/qpselect.c b/lib/qpselect.c
index fd20d421..3332db4f 100644
--- a/lib/qpselect.c
+++ b/lib/qpselect.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include <signal.h>
#include <string.h>
@@ -29,13 +30,38 @@
#include "memory.h"
#include "vector.h"
-enum { qdebug =
-#ifdef QDEBUG
- 1
-#else
- 0
+/*------------------------------------------------------------------------------
+ * Sort out QPSELECT_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QPSELECT_DEBUG == 0 to turn off debug
+ * * or set QPSELECT_DEBUG != 0 to turn on debug
+ * * or set QPSELECT_NO_DEBUG != to force debug off
+ */
+
+#ifdef QPSELECT_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QPSELECT_DEBUG)
+# undef QPSELECT_DEBUG
+# define QPSELECT_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define QPSELECT_DEBUG QDEBUG
#endif
-};
+
+#ifdef QPSELECT_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QPSELECT_NO_DEBUG)
+# undef QPSELECT_DEBUG
+# define QPSELECT_DEBUG 0
+# endif
+#endif
+
+enum { qpselect_debug = QPSELECT_DEBUG } ;
/*==============================================================================
* Quagga pselect -- qps_xxxx
@@ -272,29 +298,18 @@ qps_selection_ream(qps_selection qps, int free_structure)
/*------------------------------------------------------------------------------
* Set the signal mask for the selection.
*
- * This supports the unmasking of a single signal for the duration of the
+ * This supports the unmasking of one or more signals for the duration of the
* pselect operation.
*
- * It is assumed that the set of signals generally masked by a thread is
- * essentially static. So this function is passed that set. (So the sigmask
- * argument must have the signum signal masked.)
- *
- * If the set of signals masked by the thread changes, then this function
+ * * If the set of signals masked by the thread changes, then this function
* should be called again.
*
- * Setting a signum == 0 turns OFF the use of the sigmask.
+ * Setting a sigmask == NULL turns OFF the use of the sigmask.
*/
-void
-qps_set_signal(qps_selection qps, int signum, sigset_t sigmask)
+extern void
+qps_set_signal(qps_selection qps, const sigset_t* sigmask)
{
- qps->signum = signum ;
-
- if (signum != 0)
- {
- assert(sigismember(&sigmask, signum)) ;
- sigdelset(&sigmask, signum) ;
- qps->sigmask = sigmask ;
- } ;
+ qps->sigmask = sigmask ;
} ;
/*------------------------------------------------------------------------------
@@ -319,7 +334,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait)
fd_set* p_fds[qps_mnum_count] ;
int n ;
- if (qdebug)
+ if (qpselect_debug)
qps_selection_validate(qps) ;
/* If there is stuff still pending, tidy up by zeroising the result */
@@ -366,7 +381,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait)
p_fds[qps_write_mnum],
p_fds[qps_error_mnum],
qtime2timespec(&ts, max_wait),
- (qps->signum != 0) ? &qps->sigmask : NULL) ;
+ qps->sigmask) ;
/* If have something, set and return the pending count. */
if (n > 0)
@@ -415,7 +430,7 @@ qps_dispatch_next(qps_selection qps)
qps_file qf ;
qps_mnum_t mnum ;
- if (qdebug)
+ if (qpselect_debug)
qps_selection_validate(qps) ;
if (qps->pend_count == 0)
@@ -683,7 +698,7 @@ static qps_file
qps_file_lookup_fd(qps_selection qps, int fd, qps_file insert)
{
qps_file qf ;
- vector_index i ;
+ vector_index_t i ;
int ret ;
dassert((fd >= 0) && (fd < (int)FD_SETSIZE)) ;
@@ -772,9 +787,9 @@ qps_file_remove(qps_selection qps, qps_file qf)
{
qps_file qf_last ;
int ret ;
- vector_index i = vector_bsearch(&qps->files,
- (vector_bsearch_cmp*)qps_fd_cmp,
- &qf->fd, &ret) ;
+ vector_index_t i = vector_bsearch(&qps->files,
+ (vector_bsearch_cmp*)qps_fd_cmp,
+ &qf->fd, &ret) ;
if (ret == 0)
qfd = vector_delete_item(&qps->files, i) ;
else
@@ -1279,9 +1294,9 @@ qps_selection_validate(qps_selection qps)
int enabled_count[qps_mnum_count] ;
fd_full_set enabled ;
- qps_file qf ;
- int fd, n, mnum, p_mnum ;
- vector_index i ;
+ qps_file qf ;
+ int fd, n, mnum, p_mnum ;
+ vector_index_t i ;
/* 1..4) Run down the selection vector and check. */
/* Collect new enabled_count and enabled bit vectors. */
@@ -1412,3 +1427,107 @@ qps_selection_validate(qps_selection qps)
if (n != qps->pend_count)
zabort("Non-empty bit vector where tried count == 0") ;
} ;
+
+/*==============================================================================
+ * Miniature pselect -- for where want to wait for a small number of things.
+ */
+
+uint qps_mini_timeout_debug = 0 ;
+
+/*------------------------------------------------------------------------------
+ * Initialise a qps_mini and set one fd in the given mode.
+ *
+ * If the fd is < 0, sets nothing (returns empty qps_mini).
+ *
+ * Zero timeout is an indefinite wait !
+ */
+extern qps_mini
+qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode, uint timeout)
+{
+ qps_super_set_zero(qm->sets, qps_mnum_count) ;
+ qm->fd_last = -1 ;
+
+ qm->interval = QTIME(timeout) ; /* convert from time in seconds */
+ qm->end_time = qt_add_monotonic(qm->interval) ;
+
+ qps_mini_add(qm, fd, mode) ;
+
+ return qm ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Add given fd in the given mode to the given qps_mini.
+ *
+ * If the fd is < 0, adds nothing (returns qps_mini unchanged).
+ */
+extern qps_mini
+qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode)
+{
+ if (qdebug && (qps_mini_timeout_debug > 0))
+ {
+ if (qps_mini_timeout_debug == 1)
+ fd = -1 ;
+
+ --qps_mini_timeout_debug ;
+ } ;
+
+ if (fd >= 0)
+ {
+ FD_SET(fd, &(qm->sets[mode].fdset)) ;
+ if (fd > qm->fd_last)
+ qm->fd_last = fd ;
+ } ;
+
+ return qm ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Wait for a qps_mini -- with given timeout, in seconds.
+ *
+ * If !signal, loops on EINTR, so will not exit until gets ready state or a
+ * timeout.
+ *
+ * If signal, will exit on EINTR. Can call this again after an EINTR return,
+ * and will continue to wait the *remainder* of the timeout interval.
+ *
+ * Returns what pselect returns: > 0 number of bits set
+ * == 0 no bits set <=> timed out
+ * < 0 EINTR (iff signal)
+ */
+extern int
+qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal)
+{
+ while (1)
+ {
+ struct timespec ts[1] ;
+ int n ;
+
+ n = pselect(qm->fd_last + 1,
+ &(qm->sets[qps_read_mnum].fdset),
+ &(qm->sets[qps_write_mnum].fdset),
+ &(qm->sets[qps_error_mnum].fdset),
+ (qm->interval > 0) ? qtime2timespec(ts, qm->interval)
+ : NULL,
+ sigmask) ;
+ if (n >= 0)
+ return n ;
+
+ if (errno != EINTR)
+ break ;
+
+ if (qm->interval > 0)
+ {
+ /* set new interval, in case comes back */
+ qm->interval = qm->end_time - qt_get_monotonic() ;
+ if (qm->interval < 0)
+ return 0 ;
+ } ;
+
+ if (signal)
+ return -1 ; /* signal EINTR */
+ } ;
+
+ zabort_errno("Failed in pselect") ;
+} ;
+
+
diff --git a/lib/qpselect.h b/lib/qpselect.h
index 8901ea36..fc9037ce 100644
--- a/lib/qpselect.h
+++ b/lib/qpselect.h
@@ -25,14 +25,11 @@
#include <sys/select.h>
#include <errno.h>
+#include "misc.h"
#include "zassert.h"
#include "qtime.h"
#include "vector.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Quagga pselect -- qps_xxxx
*
@@ -143,8 +140,7 @@ struct qps_selection
qps_mnum_t pend_mnum ; /* error/read/write mode pending (if any) */
int pend_fd ; /* fd pending (if any) */
- int signum ; /* signal that sigmask is enabling -- 0 => none */
- sigset_t sigmask ; /* sigmask to use for duration of pselect */
+ const sigset_t* sigmask ; /* sigmask to use for duration of pselect */
} ;
struct qps_file
@@ -184,7 +180,7 @@ qps_selection_ream(qps_selection qps, int free_structure) ;
#define qps_selection_ream_keep(qps) qps_selection_ream(qps, 0)
extern void
-qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ;
+qps_set_signal(qps_selection qps, const sigset_t* sigmask) ;
extern int
qps_pselect(qps_selection qps, qtime_mono_t timeout) ;
@@ -238,4 +234,27 @@ qps_set_file_info(qps_file qf, void* info)
qf->file_info = info ;
} ;
+/*==============================================================================
+ * Miniature pselect
+ *
+ */
+struct qps_mini
+{
+ fd_full_set sets ; /* bit vectors for pselect enabled stuff */
+ int fd_last ; /* highest numbered fd; -1 => none at all */
+
+ qtime_t interval ;
+ qtime_mono_t end_time ;
+} ;
+
+typedef struct qps_mini qps_mini_t[1] ;
+typedef struct qps_mini* qps_mini ;
+
+extern qps_mini qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode,
+ uint timeout) ;
+extern qps_mini qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode) ;
+extern int qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal) ;
+
+extern uint qps_mini_timeout_debug ;
+
#endif /* _ZEBRA_QPSELECT_H */
diff --git a/lib/qpthreads.c b/lib/qpthreads.c
index baa34d52..8909f64b 100644
--- a/lib/qpthreads.c
+++ b/lib/qpthreads.c
@@ -18,10 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
-
-/* This MUST come first... otherwise we don't get __USE_UNIX98, which is */
-/* essential if glibc is to allow pthread_mutexattr_settype() to be used. */
-#include "config.h"
+#include "misc.h"
#include <signal.h>
#include <string.h>
@@ -29,11 +26,6 @@
#include "qpthreads.h"
#include "memory.h"
-/* If this is not set, will get errors later. */
-//#ifndef __USE_UNIX98
-//#error "_USE_UNIX98 not defined"
-//#endif
-
/*==============================================================================
* Quagga Pthread Interface -- qpt_xxxx
*
@@ -251,49 +243,56 @@ enum qpthreads_enabled_state
qpt_state_set_disabled = 2,
qpt_state_set_enabled = 3,
} ;
+typedef enum qpthreads_enabled_state qpthreads_enabled_state_t ;
-static enum qpthreads_enabled_state qpthreads_enabled_state = qpt_state_unset ;
+static qpthreads_enabled_state_t qpthreads_enabled_state = qpt_state_unset ;
-uint8_t qpthreads_enabled_flag = 0 ;
-uint8_t qpthreads_thread_created_flag = 0 ;
+bool qpthreads_enabled_flag = false ;
+bool qpthreads_thread_created_flag = false ;
-/* Function to set qpthreads_enabled, one way or the other.
+/*------------------------------------------------------------------------------
+ * Function to set qpthreads_enabled, one way or the other.
*
* Returns: true <=> successful set the required state.
* false <=> it is too late to enable qpthreads :-(
*
* NB: can repeatedly set to the same state, but not change state once set.
*/
-extern int
-qpt_set_qpthreads_enabled(int how)
+extern bool
+qpt_set_qpthreads_enabled(bool want_enabled)
{
switch (qpthreads_enabled_state)
- {
- case qpt_state_unset:
- break ;
- case qpt_state_set_frozen:
- if (how != 0)
- return 0 ;
- break ;
- case qpt_state_set_disabled:
- if (how != 0)
- zabort("qpthreads_enabled is already set: cannot set enabled") ;
- break ;
- case qpt_state_set_enabled:
- if (how == 0)
- zabort("qpthreads_enabled is already set: cannot set disabled") ;
- break ;
- default:
- break ;
- }
-
- qpthreads_enabled_flag = (how != 0) ;
- qpthreads_enabled_state = (how != 0) ? qpt_state_set_enabled
- : qpt_state_set_disabled ;
- return 1 ;
+ {
+ case qpt_state_unset:
+ break ;
+
+ case qpt_state_set_frozen:
+ if (want_enabled)
+ return false ; /* too late to set the state */
+ break ;
+
+ case qpt_state_set_disabled:
+ if (want_enabled)
+ zabort("qpthreads_enabled is already set: cannot set enabled") ;
+ break ;
+
+ case qpt_state_set_enabled:
+ if (!want_enabled)
+ zabort("qpthreads_enabled is already set: cannot set disabled") ;
+ break ;
+
+ default:
+ break ;
+ } ;
+
+ qpthreads_enabled_flag = want_enabled ;
+ qpthreads_enabled_state = want_enabled ? qpt_state_set_enabled
+ : qpt_state_set_disabled ;
+ return true ;
} ;
-/* Get state of qpthreads_enabled, and freeze if not yet explictly set.
+/*------------------------------------------------------------------------------
+ * Get state of qpthreads_enabled, and freeze if not yet explictly set.
*
* Where some initialisation depends on the state of qpthreads_enabled(), this
* returns the state and freezes it if it is implicitly not enabled.
@@ -320,7 +319,8 @@ qpt_freeze_qpthreads_enabled(void)
* are NULL, then the system defaults are used.
*/
-/* Initialise a set of attributes -- setting the scheduling options.
+/*------------------------------------------------------------------------------
+ * Initialise a set of attributes -- setting the scheduling options.
*
* Options:
*
@@ -351,7 +351,7 @@ qpt_freeze_qpthreads_enabled(void)
*
* Returns the address of the qpt_thread_attr_t structure.
*/
-qpt_thread_attr_t*
+extern qpt_thread_attr_t*
qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
int scope, int policy, int priority)
{
@@ -420,7 +420,8 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
return attr ;
} ;
-/* Create Thread with given attributes (if any).
+/*------------------------------------------------------------------------------
+ * Create Thread with given attributes (if any).
*
* If no attributes are given (attr == NULL) the thread is created with system
* default attributes -- *except* that it is created joinable.
@@ -429,7 +430,7 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
*
* Returns the qpt_thread_t "thread id".
*/
-qpt_thread_t
+extern qpt_thread_t
qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
{
qpt_thread_attr_t thread_attr ;
@@ -438,7 +439,7 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
int err ;
passert(qpthreads_enabled) ;
- qpthreads_thread_created_flag = 1 ; /* and at least one thread created */
+ qpthreads_thread_created_flag = true ; /* at least one thread created */
default_attr = (attr == NULL) ;
if (default_attr)
@@ -458,7 +459,8 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
return thread_id ;
} ;
-/* Join given thread -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Join given thread -- do nothing if !qpthreads_enabled
*
* Tolerates ESRCH (no thread known by given id).
*
@@ -490,7 +492,8 @@ qpt_thread_join(qpt_thread_t thread_id)
* Mutex initialise and destroy.
*/
-/* Initialise Mutex (allocating if required)
+/*------------------------------------------------------------------------------
+ * Initialise Mutex (allocating if required)
*
* Does nothing if !qpthreads_enabled -- but freezes the state (attempting to
* later enable qpthreads will be a FATAL error).
@@ -508,7 +511,7 @@ qpt_thread_join(qpt_thread_t thread_id)
*
* Returns the mutex -- or original mx if !qpthreads_enabled.
*/
-qpt_mutex
+extern qpt_mutex
qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
{
pthread_mutexattr_t mutex_attr ;
@@ -569,7 +572,8 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
return mx ;
} ;
-/* Destroy given mutex, and (if required) free it.
+/*------------------------------------------------------------------------------
+ * Destroy given mutex, and (if required) free it.
* -- or do nothing if !qpthreads_enabled.
*
* Returns NULL if freed the mutex, otherwise the address of same.
@@ -578,7 +582,7 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
* anything, so there can be nothing to release -- so does nothing, but
* returns the original mutex address (if any).
*/
-qpt_mutex
+extern qpt_mutex
qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
{
int err ;
@@ -600,7 +604,8 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
* Condition Variable initialise and destroy.
*/
-/* Initialise Condition Variable (allocating if required).
+/*------------------------------------------------------------------------------
+ * Initialise Condition Variable (allocating if required).
*
* Does nothing if !qpthreads_enabled -- but freezes the state (attempting to
* later enable qpthreads will be a FATAL error).
@@ -615,7 +620,7 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
*
* Returns the condition variable -- or original cv id !qpthreads_enabled.
*/
-qpt_cond
+extern qpt_cond
qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
{
pthread_condattr_t cond_attr ;
@@ -662,7 +667,8 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
return cv ;
} ;
-/* Destroy given condition variable, and (if required) free it
+/*------------------------------------------------------------------------------
+ * Destroy given condition variable, and (if required) free it
* -- or do nothing if !qpthreads_enabled.
*
* NB: if !qpthreads_enabled qpt_cond_init_new() will not have allocated
@@ -671,7 +677,7 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
*
* Returns NULL if freed the condition variable, otherwise the address of same.
*/
-qpt_cond
+extern qpt_cond
qpt_cond_destroy(qpt_cond cv, int free_cond)
{
int err ;
@@ -689,7 +695,8 @@ qpt_cond_destroy(qpt_cond cv, int free_cond)
return cv ;
} ;
-/* Wait for given condition variable or time-out
+/*------------------------------------------------------------------------------
+ * Wait for given condition variable or time-out
* -- or return immediate success if !qpthreads_enabled.
*
* Returns: wait succeeded (1 => success, 0 => timed-out).
@@ -698,8 +705,7 @@ qpt_cond_destroy(qpt_cond cv, int free_cond)
*
* Has to check the return value, so zabort_errno if not EBUSY.
*/
-
-int
+extern int
qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time)
{
struct timespec ts ;
@@ -729,40 +735,43 @@ qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time)
* Signal Handling.
*/
-/* Set thread signal mask -- requires qpthreads_enabled.
- *
- * Thin wrapper around pthread_sigmask.
+/*------------------------------------------------------------------------------
+ * Set thread signal mask.
*
- * zaborts if gets any error.
+ * If !qpthreads_enabled will use sigprocmask(), otherwise pthread_sigmask().
*
- * NB: it is a FATAL error to do this if !qpthreads_enabled.
+ * In fact pthread_sigmask() works for single-threaded processes, but we avoid
+ * depending on pthread library if it's not essential.
*
- * This is mostly because wish to avoid all pthreads_xxx calls when not
- * using pthreads. There is no reason not to use this in a single threaded
- * program.
+ * zaborts if gets any error.
*/
-void
+extern void
qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset)
{
- int err ;
-
- passert(qpthreads_enabled) ;
-
if (oset != NULL)
sigemptyset(oset) ; /* to make absolutely sure */
- err = pthread_sigmask(how, set, oset) ;
- if (err != 0)
- zabort_err("pthread_sigmask failed", err) ;
+ if (qpthreads_enabled)
+ {
+ int err = pthread_sigmask(how, set, oset) ;
+ if (err != 0)
+ zabort_err("pthread_sigmask failed", err) ;
+ }
+ else
+ {
+ if (sigprocmask(how, set, oset) != 0)
+ zabort_errno("sigprocmask failed") ;
+ } ;
} ;
-/* Send given thread the given signal -- requires qpthreads_enabled (!)
+/*------------------------------------------------------------------------------
+ * Send given thread the given signal -- requires qpthreads_enabled (!)
*
* Thin wrapper around pthread_kill.
*
* zaborts if gets any error.
*/
-void
+extern void
qpt_thread_signal(qpt_thread_t thread, int signum)
{
int err ;
@@ -773,3 +782,58 @@ qpt_thread_signal(qpt_thread_t thread, int signum)
if (err != 0)
zabort_err("pthread_kill failed", err) ;
} ;
+
+/*==============================================================================
+ * Thread Specific Data Handling.
+ *
+ * When creating a key for a piece of thread specific data one could:
+ *
+ * a. arrange for all keys to be created before any threads are
+ * created -- or at least before any have a need for the key.
+ *
+ * b. use pthread_once() to protect the creation of the key.
+ *
+ * Note that there does not appear to be a way of distinguishing a key
+ * that has been created from one that has not.
+ *
+ * For !qpthreads_enabled systems, the "thread specific" data is embedded in
+ * the qpt_data object.
+ */
+
+/*------------------------------------------------------------------------------
+ * Create the given thread specific data.
+ *
+ * NB: if no value is ever set, then qpt_data_get_value() will return NULL
+ * (whether qpthreads_enabled, or not).
+ */
+extern void
+qpt_data_create(qpt_data data)
+{
+ memset(data, 0, sizeof(union qpt_data)) ;
+
+ if (qpthreads_enabled_freeze)
+ {
+ int err = pthread_key_create(&data->key, NULL) ;
+ if (err != 0)
+ zabort_err("pthread_key_create failed", err) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Delete the given thread specific data.
+ *
+ * NB: it is the caller's responsibility to release any memory the value of
+ * the thread specific data may refer to.
+ */
+extern void
+qpt_data_delete(qpt_data data)
+{
+ if (qpthreads_enabled)
+ {
+ int err = pthread_key_delete(data->key) ;
+ if (err != 0)
+ zabort_err("pthread_key_delete failed", err) ;
+ } ;
+
+ memset(data, 0, sizeof(union qpt_data)) ;
+} ;
diff --git a/lib/qpthreads.h b/lib/qpthreads.h
index d73182ef..d05f7e34 100644
--- a/lib/qpthreads.h
+++ b/lib/qpthreads.h
@@ -22,24 +22,15 @@
#ifndef _ZEBRA_QPTHREADS_H
#define _ZEBRA_QPTHREADS_H
-#include <stdint.h>
+#include "misc.h"
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
-#include <stdbool.h>
#include "zassert.h"
#include "qtime.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
-#ifndef private
-#define private extern
-#endif
-
/*==============================================================================
* Quagga Pthread Interface -- qpt_xxxx
*
@@ -61,6 +52,39 @@
#error Require _POSIX_THREADS
#endif
+/*------------------------------------------------------------------------------
+ * Sort out QPTHREADS_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if QPTHREADS_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QPTHREADS_DEBUG == 0 to turn off debug
+ * * or set QPTHREADS_DEBUG != 0 to turn on debug
+ * * or set QPTHREADS_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef QPTHREADS_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QPTHREADS_DEBUG)
+# undef QPTHREADS_DEBUG
+# define QPTHREADS_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define QPTHREADS_DEBUG QDEBUG
+#endif
+
+#ifdef QPTHREADS_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QPTHREADS_NO_DEBUG)
+# undef QPTHREADS_DEBUG
+# define QPTHREADS_DEBUG 0
+# endif
+#endif
+
+enum { qpthreads_debug = QPTHREADS_DEBUG } ;
+
/*==============================================================================
* Global Switch -- this allows the library to be run WITHOUT pthreads !
*
@@ -74,42 +98,41 @@
* qpthreads_enabled_freeze -- to test and freeze unset if not yet enabled
*/
-#define qpthreads_enabled ((const uint8_t)qpthreads_enabled_flag)
+#define qpthreads_enabled ((const bool)qpthreads_enabled_flag)
#define qpthreads_enabled_freeze qpt_freeze_qpthreads_enabled()
-#define qpthreads_thread_created ((const uint8_t) \
- qpthreads_thread_created_flag)
+#define qpthreads_thread_created ((const bool)qpthreads_thread_created_flag)
/*==============================================================================
* Data types
*/
-
typedef pthread_t qpt_thread_t ;
-typedef pthread_mutex_t qpt_mutex_t ;
-typedef pthread_cond_t qpt_cond_t ;
+
+typedef pthread_mutex_t qpt_mutex_t[1] ;
+typedef pthread_mutex_t* qpt_mutex ;
+
+typedef pthread_cond_t qpt_cond_t[1] ;
+typedef pthread_cond_t* qpt_cond ;
typedef pthread_attr_t qpt_thread_attr_t ;
-typedef qpt_mutex_t* qpt_mutex ;
-typedef qpt_cond_t* qpt_cond ;
/*==============================================================================
* Thread Creation -- see qpthreads.c for further discussion.
*
* NB: it is a FATAL error to attempt these if !qpthreads_enabled.
*/
-
enum qpt_attr_options
{
qpt_attr_joinable = 0, /* the default for Quagga */
- qpt_attr_detached = 0x0001, /* otherwise will set joinable */
+ qpt_attr_detached = BIT(0), /* otherwise will set joinable */
- qpt_attr_sched_inherit = 0x0002, /* otherwise will use default */
+ qpt_attr_sched_inherit = BIT(1), /* otherwise will use default */
- qpt_attr_sched_scope = 0x0004, /* otherwise inherit/default */
- qpt_attr_sched_policy = 0x0008, /* otherwise inherit/default */
- qpt_attr_sched_priority = 0x0010, /* otherwise inherit/default */
+ qpt_attr_sched_scope = BIT(2), /* otherwise inherit/default */
+ qpt_attr_sched_policy = BIT(3), /* otherwise inherit/default */
+ qpt_attr_sched_priority = BIT(4), /* otherwise inherit/default */
} ;
#define qpt_attr_sched_explicit ( qpt_attr_sched_scope \
@@ -133,13 +156,13 @@ qpt_thread_join(qpt_thread_t thread_id) ;
/*==============================================================================
* qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION !
*/
-private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */
-private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */
+Private bool qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */
+Private bool qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */
-private int
-qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */
+Private bool
+qpt_set_qpthreads_enabled(bool want_enabled) ; /* qpthreads_enabled := want */
-private int
+Private int
qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */
/*==============================================================================
@@ -174,7 +197,7 @@ Inline bool qpt_thread_is_self(qpt_thread_t id)
*
* Quagga's default mutex type is:
*
- * * PTHREAD_MUTEX_ERRORCHECK unless NDEBUG && NDEBUG_QPTHREADS
+ * * PTHREAD_MUTEX_ERRORCHECK if QPTHREADS_DEBUG
* * QPT_MUTEX_TYPE_DEFAULT
*
* QPT_MUTEX_TYPE_DEFAULT may be set elsewhere. If it is not set then it is
@@ -198,22 +221,25 @@ Inline bool qpt_thread_is_self(qpt_thread_t id)
enum qpt_mutex_options
{
- qpt_mutex_quagga = 0x0000, /* Quagga's default */
- qpt_mutex_normal = 0x0001,
- qpt_mutex_recursive = 0x0002,
- qpt_mutex_errorcheck = 0x0003,
- qpt_mutex_default = 0x0004, /* system default */
+ qpt_mutex_quagga = 0, /* Quagga's default */
+ qpt_mutex_normal,
+ qpt_mutex_recursive,
+ qpt_mutex_errorcheck,
+ qpt_mutex_default, /* system default */
} ;
#ifndef QPT_MUTEX_TYPE_DEFAULT
# define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL
#endif
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
-# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT
+enum
+{
+#if QPTHREADS_DEBUG
+ QPT_MUTEX_TYPE = PTHREAD_MUTEX_ERRORCHECK
#else
-# define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK
+ QPT_MUTEX_TYPE = QPT_MUTEX_TYPE_DEFAULT
#endif
+} ;
extern qpt_mutex /* freezes qpthreads_enabled */
qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) ;
@@ -313,7 +339,7 @@ qpt_mutex_lock(qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
+#if QPTHREADS_DEBUG
pthread_mutex_lock(mx) ;
#else
int err = pthread_mutex_lock(mx) ;
@@ -323,7 +349,8 @@ qpt_mutex_lock(qpt_mutex mx)
} ;
} ;
-/* Try to lock given mutex -- every time a winner if !qpthreads_enabled.
+/*------------------------------------------------------------------------------
+ * Try to lock given mutex -- every time a winner if !qpthreads_enabled.
*
* Returns: lock succeeded (1 => have locked, 0 => unable to lock).
*
@@ -347,23 +374,19 @@ qpt_mutex_trylock(qpt_mutex mx)
return 1 ;
} ;
-/* Unlock given mutex -- or do nothing if !qpthreads_enabled.
+/*------------------------------------------------------------------------------
+ * Unlock given mutex -- or do nothing if !qpthreads_enabled.
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_mutex_unlock(qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_mutex_unlock(mx) ;
-#else
int err = pthread_mutex_unlock(mx) ;
if (err != 0)
zabort_err("pthread_mutex_unlock failed", err) ;
-#endif
} ;
} ;
@@ -371,73 +394,111 @@ qpt_mutex_unlock(qpt_mutex mx)
* Condition variable inline functions
*/
-/* Wait for given condition variable -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Wait for given condition variable -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_wait(qpt_cond cv, qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_wait(cv, mx) ;
-#else
int err = pthread_cond_wait(cv, mx) ;
if (err != 0)
zabort_err("pthread_cond_wait failed", err) ;
-#endif
} ;
} ;
-/* Signal given condition -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Signal given condition -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_signal(qpt_cond cv)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_signal(cv) ;
-#else
int err = pthread_cond_signal(cv) ;
if (err != 0)
zabort_err("pthread_cond_signal failed", err) ;
-#endif
} ;
} ;
-/* Broadcast given condition -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Broadcast given condition -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_broadcast(qpt_cond cv)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_broadcast(cv) ;
-#else
int err = pthread_cond_broadcast(cv) ;
if (err != 0)
zabort_err("pthread_cond_broadcast failed", err) ;
-#endif
} ;
} ;
/*==============================================================================
* Signal Handling.
*/
-void /* FATAL error if !qpthreads_enabled */
+extern void /* sigprocmask() if !qpthreads_enabled */
qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) ;
-void /* FATAL error if !qpthreads_enabled */
+extern void /* FATAL error if !qpthreads_enabled */
qpt_thread_signal(qpt_thread_t thread, int signum) ;
+/*==============================================================================
+ * Thread Specific Data Handling.
+ *
+ * Note that if !qpthreads_enabled, this maintains the data value, without
+ * using any pthread primitives.
+ *
+ * Note also that this does not support the pthread value destructor -- because
+ * cannot support that for non qpthreads_enabled (straightforwardly, anyway).
+ */
+union qpt_data
+{
+ pthread_key_t key ; /* if qpthreads_enabled */
+ void* value ; /* otherwise */
+ const void* cvalue ;
+} ;
+
+typedef union qpt_data qpt_data_t[1] ;
+typedef union qpt_data* qpt_data ;
+
+extern void qpt_data_create(qpt_data data) ;
+extern void qpt_data_delete(qpt_data data) ;
+
+/*------------------------------------------------------------------------------
+ * Set thread specific data value -- value is void*
+ */
+Inline void
+qpt_data_set_value(qpt_data data, const void* value)
+{
+ if (qpthreads_enabled)
+ {
+ int err = pthread_setspecific(data->key, value) ;
+ if (err != 0)
+ zabort_err("pthread_setspecific failed", err) ;
+ }
+ else
+ data->cvalue = value ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get thread specific data value -- value is void*
+ */
+Inline void*
+qpt_data_get_value(qpt_data data)
+{
+ if (qpthreads_enabled)
+ return pthread_getspecific(data->key) ;
+ else
+ return data->value ;
+} ;
+
#endif /* _ZEBRA_QPTHREADS_H */
diff --git a/lib/qrand.c b/lib/qrand.c
new file mode 100644
index 00000000..402bb432
--- /dev/null
+++ b/lib/qrand.c
@@ -0,0 +1,48 @@
+/* Pseudo Random Sequence
+ * 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 "misc.h"
+#include "qrand.h"
+
+/*==============================================================================
+ * Simple 32 bit random sequence.
+ */
+
+/*------------------------------------------------------------------------------
+ * Return next in the given sequence.
+ *
+ * Returns value in range 0..range-1, or 0..0x7FFF_FFFF if range == 0
+ *
+ * If range == 1, returns 0 every time !
+ */
+extern int
+qrand(qrand_seq seq, int range)
+{
+ uint64_t r ;
+
+ r = seq->last ^ 3141592653 ;
+ r = ((r * 2650845021) + 5) & 0xFFFFFFFF ; /* see Knuth */
+ seq->last = r ;
+
+ if (range == 0)
+ return r >> 1 ;
+ else
+ return (r % range) ;
+} ;
diff --git a/lib/qrand.h b/lib/qrand.h
new file mode 100644
index 00000000..e9a18436
--- /dev/null
+++ b/lib/qrand.h
@@ -0,0 +1,52 @@
+/* Pseudo Random Sequence -- 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_QRAND_H
+#define _ZEBRA_QRAND_H
+
+#include "misc.h"
+
+/*==============================================================================
+ * Simple 32 bit random sequence.
+ *
+ * Produces 32-bit signed integers in 0..0x7FFF_FFFF.
+ *
+ * Object of the exercise is to be able to produce repeatable sequence, so can
+ * debug !
+ */
+
+struct qrand_seq
+{
+ uint last ;
+} ;
+
+typedef struct qrand_seq* qrand_seq ;
+typedef struct qrand_seq qrand_seq_t[1] ;
+
+#define QRAND_SEQ_INIT(s) { { s } }
+
+/*==============================================================================
+ * Functions
+ */
+
+extern int qrand(qrand_seq seq, int range) ;
+
+#endif /* _ZEBRA_QRAND_H */
diff --git a/lib/qstring.c b/lib/qstring.c
index 5ca4d868..97f8e8b0 100644
--- a/lib/qstring.c
+++ b/lib/qstring.c
@@ -18,494 +18,697 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
+#include "stdio.h"
#include "qstring.h"
-
#include "memory.h"
-#include "zassert.h"
/*==============================================================================
*/
/*------------------------------------------------------------------------------
- * Initialise qstring -- allocate if required.
+ * Create a new body or extend existing one to accommodate at least slen + 1.
*
- * If non-zero len is given, a body is allocated (for at least len + 1).
+ * Sets size to multiple of 8 (minimum 16), with at least 9 bytes free beyond
+ * the requested slen.
*
- * Returns: address of qstring
+ * Does not affect 'len' or 'cp'.
*
- * NB: assumes initialising a new structure. If not, then caller should
- * use qs_reset() or qs_clear().
+ * If 'keep_alias', ONLY sets 'b_size' & 'b_body'.
+ *
+ * If !'keep_alias', sets the elstring body & size & clears 'alias'.
*/
-extern qstring
-qs_init_new(qstring qs, size_t len)
+static inline void
+qs_new_body(qstring qs, usize slen, bool keep_alias)
{
- if (qs == NULL)
- qs = qs_new() ;
- else
- memset(qs, 0, sizeof(qstring_t)) ; /* see qs_new() */
+ qs->b_size = (slen + 0x10) & ~(usize)(0x08 - 1) ;
+ qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ;
- if (len != 0)
- return qs_make_to_length(qs, len) ;
-
- return qs ;
+ if (!keep_alias)
+ qs_set_real_body_nn(qs) ;
} ;
/*------------------------------------------------------------------------------
- * Allocate or reallocate so that string is big enough for the given length.
+ * Create a new, empty qs
*
- * Allocate qstring if required. Returns with a body with size > 0.
+ * If non-zero slen is given, a body is allocated (size = slen + 1).
+ * If zero slen is given, no body is allocated.
*
- * Allocates to 16 byte boundaries with space for '\0' beyond given length.
+ * Sets 'len' = 'cp' = 0.
*
* 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 qstring
-qs_make_to_length(qstring qs, size_t len)
+qs_new(usize slen)
{
- size_t size = (len + 0x10) & ~(size_t)(0x10 - 1) ;
+ qstring qs ;
- if (qs == NULL)
- qs = qs_new() ;
+ /* zeroising sets a completely empty qstring -- see qs_init_new() */
- if (size > qs->size)
- {
- if (qs->size == 0)
- {
- void* old ;
- old = qs->body ;
+ qs = XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ;
- qs->size = size ;
- qs->body = XMALLOC(MTYPE_QSTRING_BODY, qs->size) ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
- 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) ;
- } ;
- };
+ if (slen != 0)
+ qs_new_body(qs, slen, false) ;
return qs ;
} ;
/*------------------------------------------------------------------------------
- * Add 'n' to the current string length, allocating or extending the body as
- * required.
+ * Create a new, empty qs with body
+ */
+extern qstring
+qs_new_with_body(usize slen)
+{
+ return qs_new(slen | 1) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Initialise qstring -- allocate if required.
*
- * Allocate qstring if required. Returns with a body with size > 0.
+ * If non-zero slen is given, a body is allocated (size = slen + 1).
+ * If zero slen is given, no body is allocated.
*
- * Allocates to 16 byte boundaries with space for '\0' beyond new length.
+ * Sets 'len' = 'cp' = 0.
*
* 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.
+ * NB: assumes initialising a new structure. If not, then caller should
+ * use qs_reset() or qs_clear().
*/
extern qstring
-qs_add_len(qstring qs, size_t n, char** p_ep)
+qs_init_new(qstring qs, usize slen)
{
- size_t len ;
- len = (qs != NULL) ? qs->len + n : n ;
+ if (qs == NULL)
+ return qs_new(slen) ;
- qs = qs_make_to_length(qs, len) ;
+ memset(qs, 0, sizeof(qstring_t)) ;
- qs->len = len ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
- *p_ep = (char*)qs->body + len ;
+ /* Zeroising has set:
+ *
+ * empty elstring -- no body, 'len' = 0
+ *
+ * size = 0 -- no elstring body
+ *
+ * cp = 0
+ *
+ * b_body = NULL -- no body buffer
+ * b_size = 0 -- no body buffer
+ *
+ * alias = false -- not an alias qstring
+ */
+
+ if (slen != 0)
+ qs_new_body(qs, slen, false) ;
return qs ;
} ;
/*------------------------------------------------------------------------------
- * Free body of qstring -- zeroise size, len and cp
+ * Reset qstring -- free body and, if required, free the structure.
*
- * Does nothing if qstring is NULL
+ * If not freeing the structure, zeroise size, len and cp -- qs_free_body()
+ *
+ * Returns: NULL if freed the structure
+ * 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 void
-qs_free_body(qstring qs)
+extern qstring
+qs_reset(qstring qs, free_keep_b free_structure)
{
if (qs != NULL)
{
- if (qs->size != 0)
- XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */
+ if (qs->b_body != NULL)
+ XFREE(MTYPE_STRING, qs->b_body) ; /* sets qs->b_body = NULL */
+
+ if (free_structure)
+ XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */
+ else
+ memset(qs, 0, sizeof(qstring_t)) ; /* see qs_init_new */
- qs->size = 0 ;
- qs->len = 0 ;
- qs->cp = 0 ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
} ;
+ return qs ;
} ;
/*------------------------------------------------------------------------------
- * Reset qstring -- free body and, if required, free the structure.
+ * Allocate or reallocate body so that is big enough for the given 'slen'.
*
- * If not freeing the structure, zeroise size, len and cp -- qs_free_body()
+ * qstring may NOT be NULL.
*
- * Returns: NULL if freed the structure
- * address of structure (if any), otherwise
+ * Returns with a body with size > 0 (even if 'slen' == 0).
*
- * NB: frees the body if the size != 0. So, a "dummy" qstring will not retain
- * the old body.
+ * Expects, but does not require, that 'slen' > 'size'.
+ *
+ * Does not change 'cp' or 'len' (even if 'len' > 'slen').
+ *
+ * If is an alias, copies min('len', 'alen', 'slen') characters to the body.
*/
-extern qstring
-qs_reset(qstring qs, int free_structure)
+Private void
+qs_make_to_size(qstring qs, usize slen, usize alen)
{
- if (qs != NULL)
+ /* If body needs to be extended, do that now */
+ if (slen >= qs->b_size)
+ qs_new_body(qs, slen, (qs->alias && (alen != 0))) ;
+ /* keeps alias if required */
+
+ /* Worry about alias. If we keep it, we keep all of it. */
+ if (qs->alias)
{
- if (qs->size != 0)
- XFREE(MTYPE_QSTRING_BODY, qs->body) ; /* sets qs->body = NULL */
+ /* alias or empty */
+ usize len = qs_len_nn(qs) ;
- if (free_structure)
- XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */
- else
- {
- qs->size = 0 ;
- qs->len = 0 ;
- qs->cp = 0 ;
- } ;
+ if (alen >= len)
+ alen = len ; /* keep min(alen, len) */
+
+ if (alen > slen)
+ alen = slen ; /* keep only to new len. */
+
+ if (alen != 0)
+ memcpy(qs->b_body, qs_body_nn(qs), alen) ;
+
+ qs_set_real_body_nn(qs) ;
} ;
- return qs ;
} ;
/*==============================================================================
- * printf() and vprintf() type functions
+ * Setting value of qstring
+ *
+ * Copy the given string to the qstring, allocating qstring and/or extending
+ * it as required.
+ *
+ * Any alias is simply discarded.
+ *
+ * Sets 'len' to new length
+ * Sets 'cp' = 0
+ *
+ * Returns: address of the qstring (allocated if required).
*/
/*------------------------------------------------------------------------------
- * Formatted print to qstring -- cf printf()
+ * Set qstring to be copy of the given string.
*
- * Allocate qstring if required.
+ * Treats src == NULL as an empty string. Otherwise src must be a '\0'
+ * terminated string.
*
- * Returns: address of qstring if OK
- * NULL if failed (unlikely though that is)
+ * Does not copy or count the '\0' terminator.
+ *
+ * See notes above.
*/
extern qstring
-qs_printf(qstring qs, const char* format, ...)
+qs_set(qstring qs, const char* src)
{
- va_list args;
+ return qs_set_n(qs, src, (src != NULL ? strlen(src) : 0)) ;
+} ;
- va_start (args, format);
- qs = qs_vprintf(qs, format, args);
- va_end (args);
+/*------------------------------------------------------------------------------
+ * Set qstring to be copy of leading 'n' bytes of given string.
+ *
+ * If n == 0, src is ignored (and may be NULL)
+ * If n > 0, src string MUST be at least 'n' bytes long.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_set_n(qstring qs, const char* src, usize len)
+{
+ qs = qs_new_size(qs, len) ; /* ensures have body > len */
- return qs;
+ if (len != 0)
+ memcpy(qs_char_nn(qs), src, len) ;
+
+ qs_set_len_nn(qs, len) ;
+ qs_set_cp_nn(qs, 0) ;
+
+ return qs ;
} ;
/*------------------------------------------------------------------------------
- * Formatted print to qstring -- cf vprintf()
+ * Set qstring to be copy of given qstring contents.
*
- * Allocate qstring if required.
+ * If the given qstring is an alias, then the contents of the alias are copied
+ * (so the result is not an alias). See qs_copy() for the alternative.
*
- * Returns: address of qstring if OK
- * NULL if failed (unlikely though that is)
+ * See notes above -- and note that 'cp' is set to 0.
*/
extern qstring
-qs_vprintf(qstring qs, const char *format, va_list args)
+qs_set_qs(qstring qs, qstring src)
{
- va_list ac ;
- int len ;
- qstring qqs ;
+ return qs_set_n(qs, qs_body(src), qs_len(src)) ;
+} ;
- qqs = qs ;
- if (qs == NULL)
- qs = qs_new() ;
+/*------------------------------------------------------------------------------
+ * Set qstring to be copy of given elstring contents.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_set_els(qstring qs, elstring src)
+{
+ return qs_set_n(qs, els_body(src), els_len(src)) ;
+} ;
- while (1)
+/*------------------------------------------------------------------------------
+ * Set qstring with given pattern to given length.
+ *
+ * Repeats the given pattern as many times as necessary to get to the given
+ * length -- using a final partial piece of the pattern as required.
+ *
+ * If the pattern is zero length, fills with spaces !
+ *
+ * See notes above.
+ */
+extern qstring
+qs_set_fill(qstring qs, usize len, const char* src)
+{
+ return qs_set_fill_n(qs, len, src, (src != NULL ? strlen(src) : 0)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set qstring with given pattern to given length.
+ *
+ * Repeats the given pattern as many times as necessary to get to the given
+ * length -- using a final partial piece of the pattern as required.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_set_fill_n(qstring qs, usize len, const char* src, usize flen)
+{
+ char* p ;
+ char* q ;
+ usize left ;
+
+ qs = qs_new_size(qs, len) ; /* ensures have body > len */
+
+ if (len != 0)
{
- /* Note that vsnprintf() returns the length of what it would like to have
- * produced, if it had the space. That length does not include the
- * trailing '\0'.
- *
- * Also note that given a zero length the string address may be NULL, and
- * the result is still the length required.
- */
- va_copy(ac, args);
- qs->len = len = vsnprintf (qs->body, qs->size, format, ac) ;
- va_end(ac);
+ if (flen == 0)
+ {
+ src = " " ;
+ flen = strlen(src) ;
+ } ;
+
+ if (len < flen)
+ flen = len ;
+
+ q = p = qs_char_nn(qs) ;
+ memcpy(p, src, flen) ;
+ p += flen ;
+ left = len - flen ;
- if (len < 0)
- break ;
+ while (left > 0)
+ {
+ if (left < flen)
+ flen = left ;
- if (len < (int)qs->size)
- return qs ;
+ memcpy(p, q, flen) ;
+ p += flen ;
+ left -= flen ;
- qs_make_to_length(qs, len) ;
+ flen += flen ;
+ } ;
} ;
- if (qqs == NULL)
- qs_reset_free(qs) ; /* discard what was allocated */
- else
- qs->len = 0 ;
+ qs_set_len_nn(qs, len) ;
+ qs_set_cp_nn(qs, 0) ;
- return NULL ;
+ return qs ;
} ;
/*==============================================================================
- * Other operations
+ * Appending to a qstring
+ *
+ * Copy the given string to the end of the given qstring (at 'len'),
+ * allocating qstring and/or extending it as required.
+ *
+ * If this is an alias, it is copied to before being appended to (even if
+ * appending nothing).
+ *
+ * Can append to NULL or empty qstring.
+ *
+ * Sets 'len' to new length.
+ * Does not affect 'cp'.
+ *
+ * Will work even if the stuff being appended is somewhere in the body of the
+ * qstring !!
+ *
+ * Returns: address of the qstring (allocated if required).
*/
/*------------------------------------------------------------------------------
- * Set qstring to be copy of the given string.
+ * Append given qstring to a qstring.
*
- * Allocates a qstring, if required.
+ * See notes above.
+ */
+extern qstring
+qs_append(qstring qs, qstring src)
+{
+ return qs_append_str_n(qs, qs_body(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append given string to a qstring.
*
- * Sets qs->len to the length of the string (excluding trailing '\0')
+ * Treats src == NULL as an empty string. Otherwise src must be a '\0'
+ * terminated string.
*
- * NB: if src == NULL, sets qstring to be zero length string.
+ * Does not copy or count the '\0' terminator.
*
- * Returns: address of the qstring copied to.
+ * See notes above.
+ */
+extern qstring qs_append_str(qstring qs, const char* src)
+{
+ return qs_append_str_n(qs, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append leading 'n' bytes of given string to a qstring.
+ *
+ * If n == 0, src may be NULL
+ * If n > 0, src string MUST be at least 'n' bytes long.
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * See notes above.
*/
extern qstring
-qs_set(qstring qs, const char* src)
+qs_append_str_n(qstring qs, const char* src, usize n)
{
- 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' ;
+ qs = qs_extend(qs, n) ; /* allocate, copy any alias, extend body,
+ set new length, etc */
+
+ if (n != 0)
+ memmove(qs_ep_char_nn(qs) - n, src, n) ;
return qs ;
} ;
/*------------------------------------------------------------------------------
- * Set qstring to be leading 'n' bytes of given string.
+ * Append given elstring to a qstring.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_append_els(qstring qs, elstring src)
+{
+ return qs_append_str_n(qs, els_body(src), els_len(src)) ;
+} ;
+
+/*==============================================================================
+ * Setting of alias.
+ *
+ * Does NOT copy the given string, but sets the qstring to be a pointer to it.
+ * This means that:
*
- * Allocates qstring if required.
+ * NB: it is the caller's responsibility to ensure that the original string
+ * stays put for however long the qstring is an alias for it.
*
- * Inserts '\0' terminator after the 'n' bytes copied.
+ * It is also the caller's responsibility to see that the original string
+ * is discarded as required (once the alias is no longer required.)
*
- * Returns: address of the qstring copied to.
+ * NB: if the qstring is changed in any way, a copy of the aliased string will
+ * be made first.
*
- * NB: src string MUST be at least 'n' bytes long.
+ * NB: if a pointer to the body of the qstring is taken, then while that is in
+ * use, the qstring must not be released, so that the alias is not
+ * released.
*
- * NB: src may not be NULL unless n == 0.
+ * Preserves any existing qstring body.
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * Returns: address of the qstring (allocated if required).
+ */
+
+/*------------------------------------------------------------------------------
+ * Set qstring to be an alias for the given string.
+ *
+ * Treats src == NULL as an empty string. Otherwise src must be a '\0'
+ * terminated string.
+ *
+ * Does not count the '\0' terminator.
+ *
+ * See notes above.
*/
extern qstring
-qs_set_n(qstring qs, const char* src, size_t n)
+qs_set_alias(qstring qs, const char* src)
{
- qs = qs_set_len(qs, n) ; /* ensures have body > n */
- if (n != 0)
- memcpy(qs->body, src, n) ;
+ return qs_set_alias_n(qs, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
- *((char*)qs->body + n) = '\0' ;
+/*------------------------------------------------------------------------------
+ * Set qstring to be an alias for the leading 'n' bytes of given string.
+ *
+ * If n == 0, src may be NULL
+ * If n > 0, src string MUST be at least 'n' bytes long.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_set_alias_n(qstring qs, const char* src, usize n)
+{
+ if (qs == NULL)
+ qs = qs_new(0) ;
+
+ /* Make the alias. Note that any existing b_body and b_size are preserved,
+ * so that any current body can be reused at a later date.
+ */
+ qs_set_body_nn(qs, (n != 0) ? src : "") ;
+ qs_set_len_nn(qs, n) ;
+ qs_set_cp_nn(qs, 0) ;
+ qs->alias = true ;
+ qs->size = 0 ; /* => empty or alias */
return qs ;
} ;
/*------------------------------------------------------------------------------
- * Append given string to a qstring.
+ * Set qstring to be an alias for the given qstring.
*
- * Allocates a qstring, if required.
+ * If the src is not an alias, then the qstring is an alias for the body of
+ * src -- so must be careful not to disturb that !
*
- * Sets qs->len to the length of the result (excluding trailing '\0')
+ * If the src is an alias, then the qstring is another alias.
*
- * NB: if src == NULL, appends nothing -- but result will be '\0' terminated.
+ * See notes above.
+ */
+extern qstring
+qs_set_alias_qs(qstring qs, qstring src)
+{
+ return qs_set_alias_n(qs, qs_body(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Construct a qstring which is an alias for the given elstring.
*
- * Returns: address of the qstring copied to.
+ * If n == 0, src may be NULL
+ * If n > 0, src string MUST be at least 'n' bytes long.
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * See notes above.
*/
-extern qstring qs_append(qstring qs, const char* src)
+extern qstring
+qs_set_alias_els(qstring qs, elstring src)
{
- size_t n ;
- char* ep ;
-
- n = (src != NULL) ? strlen(src) : 0 ;
-
- qs = qs_add_len(qs, n, &ep) ;
- ep = (char*)qs->body + qs->len ;
-
- if (n != 0)
- memcpy(ep - n, src, n + 1) ;
- else
- *ep = '\0' ;
-
- return qs ;
+ return qs_set_alias_n(qs, els_body(src), els_len(src)) ;
} ;
+/*==============================================================================
+ * Copying of qstring
+ */
+
/*------------------------------------------------------------------------------
- * Set qstring to be leading 'n' bytes of given string.
+ * Copy one qstring to another -- allocating/extending as required.
*
- * Allocates qstring if required.
- *
- * Returns: address of the qstring copied to.
+ * If both are NULL, returns NULL.
+ * Otherwise if dst is NULL, creates a new qstring.
*
- * NB: src string MUST be at least 'n' bytes long.
+ * If src is NULL it is treated as zero length, with 'cp' == 0.
*
- * NB: src may not be NULL unless n == 0.
+ * If src is not an alias, a copy is made to dst.
+ * If src is an alias, dst becomes another alias for the same thing.
*
- * NB: if n == 0, appends nothing -- but result will be '\0' terminated.
+ * If dst is an alias, that is discarded.
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * Copies the src 'cp' to the dst.
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * Returns: the destination qstring (allocated if required).
*/
extern qstring
-qs_append_n(qstring qs, const char* src, size_t n)
+qs_copy(qstring dst, qstring src)
{
- char* ep ;
-
- qs = qs_add_len(qs, n, &ep) ;
+ if (src == NULL)
+ {
+ qs_clear(dst) ; /* if dst not NULL, clear it */
+ return dst ;
+ } ;
- if (n != 0)
- memcpy(ep - n, src, n) ;
+ if (src->alias)
+ dst = qs_set_alias_qs(dst, src) ;
+ else
+ dst = qs_set_qs(dst, src) ;
- *ep = '\0' ;
+ qs_set_cp_nn(dst, qs_cp_nn(src)) ; /* copy in the src cp. */
- return qs ;
+ return dst ;
} ;
-/*------------------------------------------------------------------------------
- * Copy one qstring to another
+/*==============================================================================
+ * printf() and vprintf() type functions
*
- * If both are NULL, returns NULL.
+ * Allocate and/or extend qstring as required.
*
- * Otherwise if dst is NULL, creates a new qstring.
+ * Any alias is discarded.
*
- * Sets dst: body = copy of src->len bytes of src->body -- '\0' terminated.
- * cp = src->cp
- * len = src->len
+ * Sets 'len' = length of the '\0' terminated result (less the '\0').
+ * Sets 'cp' = 0
*
- * Where a NULL src has zero cp and len.
+ * If fails and qs != NULL:
*
- * If not NULL, the destination is guaranteed to have a body, and that will be
- * '\0' terminated.
+ * Sets 'len' = 0
+ * Sets 'cp' = 0
+ * But retains any existing body.
*
- * Returns: the destination qstring
+ * Returns: address of qstring if OK
+ * NULL if failed (unlikely though that is)
+ */
+
+/*------------------------------------------------------------------------------
+ * Formatted print to qstring -- cf printf()
*
- * NB: if copying to a dummy qstring, the old body is simply discarded.
+ * See notes above.
*/
extern qstring
-qs_copy(qstring dst, qstring src)
+qs_printf(qstring qs, const char* format, ...)
{
- size_t n ;
+ va_list args;
- if (src == NULL)
- {
- if (dst == NULL)
- return dst ;
+ va_start (args, format);
+ qs = qs_vprintf(qs, format, args);
+ va_end (args);
+
+ return qs;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Formatted print to qstring -- cf vprintf()
+ *
+ * See notes above.
+ */
+extern qstring
+qs_vprintf(qstring qs, const char *format, va_list args)
+{
+ va_list ac ;
+ int slen ;
+ qstring qqs ;
- n = 0 ;
- dst->cp = 0 ;
- }
+ qqs = qs ; /* NULL => need to make qs */
+ if (qs == NULL)
+ qqs = qs_new(0) ; /* Sets size, cp & len = 0 */
else
+ qs_clear(qqs) ; /* Sets cp & len = 0, discard any alias, but
+ keep existing body */
+
+ while (1)
{
- if (dst == NULL)
- dst = qs_new() ;
+ /* Note that vsnprintf() returns the length of what it would like to have
+ * produced, if it had the space. That length does not include the
+ * trailing '\0'.
+ *
+ * Also note that given a zero length the string address may be NULL, and
+ * the result is still the length required.
+ */
+ va_copy(ac, args);
+ slen = vsnprintf(qs_body_nn(qqs), qqs->size, format, ac) ;
+ va_end(ac);
- n = src->len ;
- dst->cp = src->cp ;
- } ;
+ if (slen < 0)
+ break ; /* failed */
- qs_set_len(dst, n) ;
+ if ((usize)slen < qqs->size)
+ {
+ qs_set_len_nn(qqs, slen) ;
+ return qqs ; /* succeeded */
+ } ;
- if (n > 0)
- memcpy(dst->body, src->body, n) ;
+ qs_make_to_size(qqs, slen, 0) ; /* need space for slen */
+ } ;
- *((char*)dst->body + n) = '\0' ;
+ /* Failed... discard anything that has been allocated. */
+ if (qs == NULL)
+ qs_reset(qqs, free_it) ;
- return dst ;
+ return NULL ;
} ;
+/*==============================================================================
+ * Other operations
+ */
+
/*------------------------------------------------------------------------------
- * Compare significant parts of two qstrings.
+ * Replace 'r' bytes at 'cp' by 'n' bytes -- extending if required.
+ *
+ * May increase or decrease 'len'. but does not affect 'cp'.
*
- * By significant, mean excluding leading/trailing isspace() and treating
- * multiple isspace() as single isspace().
+ * If the given src is NULL, do not insert anything, just leave the space
+ * ready for it.
*
- * Compares the 'len' portions of the strings.
+ * Returns: number of bytes beyond 'cp' that now exist.
*
- * If either is NULL, it is deemed to be an empty string.
+ * qstring MUST NOT be NULL
*
- * Returns: -1 => a < b
- * 0 => a == b
- * +1 => a > b
+ * If 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce
+ * one or more undefined bytes.
+ *
+ * If this is a aliased qstring, a copy is made, so is no longer an alias.
*/
-extern int
-qs_cmp_sig(qstring a, qstring b)
+extern usize
+qs_replace(qstring qs, usize r, const void* src, usize n)
{
- const unsigned char* p_a ;
- const unsigned char* e_a ;
- const unsigned char* p_b ;
- const unsigned char* e_b ;
+ usize cp, len, nlen, after ;
+ const char* ap ;
+ char* np ;
- /* 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 ;
- }
+ len = qs_len_nn(qs) ;
+ cp = qs_cp_nn(qs) ;
- 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 ;
- }
+ if ((cp + r) >= len)
+ /* Replacing up to or beyond the end of the string */
+ after = 0 ;
else
- {
- p_b = NULL ;
- e_b = NULL ;
- } ;
+ /* Replacing section short of the end of the string */
+ after = len - (cp + r) ;
- /* Now set about finding the first difference */
- while ((p_a != e_a) && (p_b != e_b))
+ nlen = cp + n + after ;
+ if (nlen >= qs->b_size)
+ qs_new_body(qs, nlen, qs->alias) ; /* keeping any alias */
+
+ ap = np = qs->b_body ;
+
+ if (qs->alias)
{
- if (isspace(*p_a) && isspace(*p_b))
- {
- do { ++p_a ; } while (isspace(*p_a)) ;
- do { ++p_b ; } while (isspace(*p_b)) ;
- } ;
+ ap = qs_body_nn(qs) ; /* copy from the alias */
+
+ uint before = cp ;
+ if (before > len)
+ before = len ;
- if (*p_a != *p_b)
- return (*p_a < *p_b) ? -1 : +1 ;
+ if (before > 0)
+ memmove(np, ap, before) ;
- ++p_a ;
- ++p_b ;
+ qs_set_real_body_nn(qs) ;
} ;
- /* 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 ;
+ if (after > 0) /* move the after part before inserting */
+ memmove(np + cp + n, ap + cp + r, after) ;
+
+ if ((n > 0) && (src != NULL)) /* insert */
+ memmove(np + cp, src, n) ;
+
+ /* Set new 'len' */
+ qs_set_len_nn(qs, nlen) ;
+
+ return n + after ;
} ;
diff --git a/lib/qstring.h b/lib/qstring.h
index 0597eda8..b9813bc7 100644
--- a/lib/qstring.h
+++ b/lib/qstring.h
@@ -22,23 +22,11 @@
#ifndef _ZEBRA_QSTRING_H
#define _ZEBRA_QSTRING_H
-#include "zebra.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
+#include "misc.h"
+#include "vargs.h"
+#include "zassert.h"
#include "memory.h"
-
-#ifndef Inline
-#define Inline static inline
-#endif
-
-/* GCC have printf type attribute check. */
-#ifdef __GNUC__
-#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
-#else
-#define PRINTF_ATTRIBUTE(a,b)
-#endif /* __GNUC__ */
+#include "elstring.h"
/*==============================================================================
* These "qstrings" address address the lack of a flexible length string in 'C'.
@@ -49,420 +37,681 @@
*
* The caller does, however, have to explicitly release the contents of a
* qstring when it is done with.
+ *
+ * The mechanics of a qstring work on a length/pointer basis. The body of a
+ * qstring is not guaranteed to be '\0' terminated, but when memory is
+ * allocated provision is always made for a terminator beyond the 'len'.
+ * The qs_string() function will add a '\0' at the 'len' position.
+ *
+ * The body of a qstring is allocated and extended automatically as the length
+ * or the size is set. The address of the body can, therefore, change -- so
+ * should be careful when handling pointers to the body. The qstring handling
+ * tends to hold on to any body that has been allocated -- only qs_reset() will
+ * free the body.
+ *
+ * The qstring supports an "alias" state. A qstring can be set to be an
+ * "alias" for another length/pointer string, and at some later date that
+ * can be copied to the qstring body -- to be changed or for any other
+ * reason.
*/
+struct qstring
+{
+ elstring_t els ; /* *embedded* */
+
+ usize size ; /* of the els body */
-typedef struct qstring qstring_t ;
+ ulen cp ;
+
+ void* b_body ;
+ usize b_size ;
+
+ bool alias ;
+ char* empty ;
+} ;
+
+typedef struct qstring qstring_t[1] ;
typedef struct qstring* qstring ;
-struct qstring
+/* Setting an qstring object to all zeros is enough to initialise it to
+ * an empty string -- including the embedded elstring.
+ */
+CONFIRM(ELSTRING_INIT_ALL_ZEROS) ;
+enum
{
- union
- {
- void* body ;
- const void* const_body ;
- char* char_body ;
- unsigned char* uchar_body ;
- } ;
- size_t size ;
-
- size_t len ;
- size_t cp ;
+ QSTRING_INIT_ALL_ZEROS = true
} ;
/*------------------------------------------------------------------------------
* Access functions for body of qstring -- to take care of casting pointers
*
- * NB: if the body has not yet been allocated, these functions will return
- * NULL or NULL + the offset.
+ * There are generally two versions of each function:
+ *
+ * xxxx_nn -- where the argument may NOT be NULL
+ *
+ * xxxx -- where a NULL or zero value is returned if the argument
+ * is NULL
+ *
+ * NB: the various 'cp', 'len' and 'at' functions do not guarantee that these
+ * positions exist within the current body of the string.
+ *
*/
-Inline char* /* pointer to body of qstring */
-qs_chars(qstring qs)
+
+Inline elstring qs_els(qstring qs) ;
+Inline elstring qs_els_nn(qstring qs) ;
+Inline void qs_els_copy(elstring els, qstring qs) ;
+Inline void qs_els_copy_nn(elstring els, qstring qs) ;
+
+Inline char* qs_char(qstring qs) ;
+Inline char* qs_char_nn(qstring qs) ;
+
+Inline char* qs_cp_char(qstring qs) ;
+Inline char* qs_cp_char_nn(qstring qs) ;
+
+Inline char* qs_ep_char(qstring qs) ;
+Inline char* qs_ep_char_nn(qstring qs) ;
+
+Inline char* qs_char_at(qstring qs, usize off) ;
+Inline char* qs_char_at_nn(qstring qs, usize off) ;
+
+Inline void* qs_body(qstring qs) ;
+Inline void* qs_body_nn(qstring qs) ;
+Inline void qs_set_body_nn(qstring qs, const void* body) ;
+
+Inline usize qs_size(qstring qs) ;
+Inline usize qs_size_nn(qstring qs) ;
+
+Inline ulen qs_len(qstring qs) ;
+Inline ulen qs_len_nn(qstring qs) ;
+Inline void qs_set_len_nn(qstring qs, ulen len) ;
+Inline void qs_set_strlen_nn(qstring qs) ;
+
+Inline ulen qs_cp(qstring qs) ;
+Inline ulen qs_cp_nn(qstring qs) ;
+Inline void qs_set_cp_nn(qstring qs, usize cp) ;
+Inline void qs_move_cp_nn(qstring qs, int delta) ;
+
+Inline ulen qs_after_cp(qstring qs) ;
+Inline ulen qs_after_cp_nn(qstring qs) ;
+
+Inline void qs_pp(pp p, qstring qs) ;
+Inline void qs_pp_nn(pp p, qstring qs) ;
+
+Inline void qs_cpp(cpp p, qstring qs) ;
+Inline void qs_cpp_nn(cpp p, qstring qs) ;
+
+/*------------------------------------------------------------------------------
+ * Functions to fetch the elstring body of the qstring.
+ */
+
+/* Pointer to elstring of qstring -- returns NULL if qstring is NULL */
+Inline elstring
+qs_els(qstring qs)
+{
+ return (qs != NULL) ? qs->els : NULL ;
+} ;
+
+/* Pointer to elstring of qstring (not NULL) */
+Inline elstring
+qs_els_nn(qstring qs)
+{
+ return qs->els ;
+} ;
+
+/* Copy elstring of qstring to another elstring (elstring not NULL) */
+Inline void
+qs_els_copy(elstring els, qstring qs)
{
- return (char*)qs->body ;
+ if (qs != NULL)
+ qs_els_copy_nn(els, qs) ;
+ else
+ els_null(els) ;
} ;
-Inline unsigned char* /* pointer to body of qstring */
-qs_bytes(qstring qs)
+/* Copy elstring of qstring to another elstring (neither NULL) */
+Inline void
+qs_els_copy_nn(elstring els, qstring qs)
{
- return (unsigned char*)qs->body ;
+ *els = *(qs->els) ;
} ;
-Inline char* /* pointer to given offset in qstring */
-qs_chars_at(qstring qs, size_t off)
+/*------------------------------------------------------------------------------
+ * Functions to fetch pointers to or into the string body.
+ *
+ * NB: these pointers must be treated with care -- and change to the string
+ * may invalidate the pointer. Where the qstring is an alias, the pointer
+ * returned is the alias -- which could disappear !
+ */
+
+/* Start of qstring -- returns NULL if qstring is NULL, or body is. */
+Inline char*
+qs_char(qstring qs)
{
- return qs_chars(qs) + off ;
+ return qs_body(qs) ;
} ;
-Inline unsigned char* /* pointer to given offset in qstring */
-qs_bytes_at(qstring qs, size_t off)
+/* Start of qstring (not NULL)-- returns NULL if body is NULL. */
+Inline char*
+qs_char_nn(qstring qs)
{
- return qs_bytes(qs) + off ;
+ return qs_body_nn(qs) ;
} ;
-Inline char* /* pointer to 'cp' offset in qstring */
+/* Offset in qstring -- returns NULL if qstring is NULL
+ * returns *nonsense if body is NULL */
+Inline char*
+qs_char_at(qstring qs, usize off)
+{
+ return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ;
+} ;
+
+/* Offset in qstring (not NULL) -- returns *nonsense if body is NULL */
+Inline char*
+qs_char_at_nn(qstring qs, usize off)
+{
+ return qs_char_nn(qs) + off ;
+} ;
+
+/* 'cp' in qstring -- returns NULL if qstring is NULL
+ * returns *nonsense if body is NULL */
+Inline char*
qs_cp_char(qstring qs)
{
- return qs_chars_at(qs, qs->cp) ;
+ return (qs != NULL) ? qs_cp_char_nn(qs) : NULL ;
} ;
-Inline unsigned char* /* pointer to 'cp' offset in qstring */
-qs_cp_byte(qstring qs)
+/* 'cp' in qstring (not NULL) -- returns *nonsense if body is NULL */
+Inline char*
+qs_cp_char_nn(qstring qs)
{
- return qs_bytes_at(qs, qs->cp) ;
+ return qs_char_at_nn(qs, qs_cp_nn(qs)) ;
} ;
-Inline char* /* pointer to 'len' offset in qstring */
+/* 'len' in qstring -- returns NULL if qstring is NULL
+ * returns *nonsense if body is NULL */
+Inline char*
qs_ep_char(qstring qs)
{
- return qs_chars_at(qs, qs->len) ;
+ return (qs != NULL) ? qs_ep_char_nn(qs) : NULL ;
} ;
-Inline unsigned char* /* pointer to 'len' offset in qstring */
-qs_ep_byte(qstring qs)
+/* 'len' in qstring (not NULL) -- returns *nonsense if body is NULL */
+Inline char*
+qs_ep_char_nn(qstring qs)
{
- return qs_bytes_at(qs, qs->len) ;
+ return qs_char_at_nn(qs, qs_len_nn(qs)) ;
} ;
-/*==============================================================================
- * Functions
+/* Start of qstring -- returns NULL if qstring is NULL, or body is. */
+Inline void*
+qs_body(qstring qs)
+{
+ return (qs != NULL) ? qs_body_nn(qs) : NULL ;
+} ;
+
+/* Start of qstring (not NULL)-- returns NULL if body is NULL. */
+Inline void*
+qs_body_nn(qstring qs)
+{
+ return els_body_nn(qs->els) ;
+} ;
+
+/* Set new body of qstring (not NULL).
+ *
+ * Caller must ensure that 'size', 'len' & 'cp' are all valid ! */
+Inline void
+qs_set_body_nn(qstring qs, const void* body)
+{
+ els_set_body_nn(qs->els, body) ;
+} ;
+
+/* Size of qstring body -- zero if qstring is NULL, or is alias. */
+Inline usize
+qs_size(qstring qs)
+{
+ return (qs != NULL) ? qs_size_nn(qs) : 0 ;
+} ;
+
+/* Size of qstring (not NULL). */
+Inline usize
+qs_size_nn(qstring qs)
+{
+ return (qs->size) ;
+} ;
+
+/*----------------------------------------------------------------------------*/
+
+/* 'len' of qstring -- returns 0 if qstring is NULL */
+Inline ulen
+qs_len(qstring qs)
+{
+ return (qs != NULL) ? qs_len_nn(qs) : 0 ;
+} ;
+
+/* 'len' of qstring (not NULL) */
+Inline ulen
+qs_len_nn(qstring qs)
+{
+ return els_len_nn(qs->els) ;
+} ;
+
+/* set 'len' of qstring (not NULL) -- caller responsible for validity */
+Inline void
+qs_set_len_nn(qstring qs, ulen len)
+{
+ els_set_len_nn(qs->els, len) ;
+} ;
+
+/* set 'len' of qstring according to strlen(body) -- nothing NULL ! */
+Inline void
+qs_set_strlen_nn(qstring qs)
+{
+ els_set_len_nn(qs->els, strlen(qs_body_nn(qs))) ;
+} ;
+
+/*----------------------------------------------------------------------------*/
+
+/* 'cp' of qstring -- returns 0 if qstring is NULL */
+Inline ulen
+qs_cp(qstring qs)
+{
+ return (qs != NULL) ? qs_cp_nn(qs) : 0 ;
+} ;
+
+/* 'cp' of qstring (not NULL) */
+Inline ulen
+qs_cp_nn(qstring qs)
+{
+ return qs->cp ;
+} ;
+
+/* set 'cp' of qstring (not NULL) -- caller responsible for validity */
+Inline void
+qs_set_cp_nn(qstring qs, usize cp)
+{
+ qs->cp = cp ;
+} ;
+
+/* move 'cp' of qstring (not NULL) -- caller responsible for validity */
+Inline void
+qs_move_cp_nn(qstring qs, int delta)
+{
+ qs->cp += delta ;
+} ;
+
+/*----------------------------------------------------------------------------*/
+
+/* 'len' - 'cp' of qstring (not NULL) -- zero if 'len' < 'cp' */
+Inline ulen
+qs_after_cp_nn(qstring qs)
+{
+ return (qs_len_nn(qs) > qs_cp_nn(qs)) ? qs_len_nn(qs) - qs_cp_nn(qs) : 0 ;
+} ;
+
+/* 'len' - 'cp' of qstring -- zero if NULL or 'len' < 'cp' */
+Inline ulen
+qs_after_cp(qstring qs)
+{
+ return (qs != NULL) ? qs_after_cp_nn(qs) : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Functions to fetch various pointer pairs.
*/
-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) ;
+Inline void
+qs_pp(pp p, qstring qs)
+{
+ if (qs != NULL)
+ qs_pp_nn(p, qs) ;
+ else
+ pp_null(p) ;
+} ;
-#define qs_reset_keep(qs) qs_reset(qs, 0)
-#define qs_reset_free(qs) qs_reset(qs, 1)
+Inline void
+qs_pp_nn(pp p, qstring qs)
+{
+ els_pp_nn(p, qs->els) ;
+} ;
-Inline qstring qs_new(void) ;
-Inline qstring qs_dummy(qstring qs, const char* src, int pos) ;
+Inline void
+qs_cpp(cpp p, qstring qs)
+{
+ if (qs != NULL)
+ qs_cpp_nn(p, qs) ;
+ else
+ cpp_null(p) ;
+} ;
-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) ;
+Inline void
+qs_cpp_nn(cpp p, qstring qs)
+{
+ els_cpp_nn(p, qs->els) ;
+} ;
-extern qstring qs_set(qstring qs, const char* src) ;
-extern qstring qs_set_n(qstring qs, const char* src, size_t n) ;
+/*------------------------------------------------------------------------------
+ * Set real body -- discarding any alias.
+ *
+ * NB: does not affect 'len' or 'cp'
+ */
+Inline void qs_set_real_body_nn(qstring qs)
+{
+ qs->size = qs->b_size ;
+ qs_set_body_nn(qs, qs->b_body) ;
+ qs->alias = false ;
+} ;
+
+/*==============================================================================
+ * Functions
+ */
-extern qstring qs_append(qstring qs, const char* src) ;
-extern qstring qs_append_n(qstring qs, const char* src, size_t n) ;
+extern qstring qs_new(usize slen) ;
+extern qstring qs_new_with_body(usize slen) ;
+extern qstring qs_init_new(qstring qs, usize len) ;
+extern qstring qs_reset(qstring qs, free_keep_b free_structure) ;
+Inline qstring qs_free(qstring qs) ;
-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 char* qs_make_string(qstring qs) ;
+Inline const char* qs_string(qstring qs) ;
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 qstring qs_new_size(qstring qs, usize slen) ;
+Inline qstring qs_extend(qstring qs, usize elen) ;
+Inline void qs_chop(qstring qs, usize clen) ;
-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) ;
+Private void qs_make_to_size(qstring qs, usize len, usize alen) ;
+
+extern qstring qs_set(qstring qs, const char* src) ;
+extern qstring qs_set_n(qstring qs, const char* src, usize n) ;
+extern qstring qs_set_qs(qstring qs, qstring src) ;
+extern qstring qs_set_els(qstring qs, elstring src) ;
+extern qstring qs_set_fill(qstring qs, usize len, const char* src) ;
+extern qstring qs_set_fill_n(qstring qs, usize len, const char* src,
+ usize flen) ;
+
+extern qstring qs_append_str(qstring qs, const char* src) ;
+extern qstring qs_append_str_n(qstring qs, const char* src, usize n) ;
+extern qstring qs_append(qstring qs, qstring src) ;
+extern qstring qs_append_els(qstring qs, elstring src) ;
+
+extern qstring qs_set_alias(qstring qs, const char* src) ;
+extern qstring qs_set_alias_n(qstring qs, const char* src, usize len) ;
+extern qstring qs_set_alias_qs(qstring qs, qstring src) ;
+extern qstring qs_set_alias_els(qstring qs, elstring src) ;
extern qstring qs_copy(qstring dst, qstring src) ;
-extern int qs_cmp_sig(qstring a, qstring b) ;
+
+extern qstring qs_printf(qstring qs, const char* format, ...)
+ PRINTF_ATTRIBUTE(2, 3) ;
+extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ;
+
+extern usize qs_replace(qstring qs, usize r, const void* src, usize n) ;
+Inline usize qs_insert(qstring qs, const void* src, usize n)
+{
+ return qs_replace(qs, 0, src, n) ;
+} ;
+Inline usize qs_delete(qstring qs, usize n)
+{
+ return qs_replace(qs, n, NULL, 0) ;
+} ;
+
+Inline int qs_cmp(qstring a, qstring b) ;
+Inline int qs_cmp_word(qstring a, qstring w) ;
+Inline int qs_cmp_sig(qstring a, qstring b) ;
+Inline bool qs_equal(qstring a, qstring b) ;
+Inline bool qs_substring(qstring a, qstring b) ;
/*==============================================================================
* The Inline functions.
*/
/*------------------------------------------------------------------------------
- * Make a brand new, completely empty qstring
+ * Free given qstring
*/
Inline qstring
-qs_new(void)
+qs_free(qstring qs)
{
- /* 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)) ;
+ return qs_reset(qs, free_it) ;
} ;
/*------------------------------------------------------------------------------
- * Construct a "dummy" qstring from the given string.
+ * Return pointer to string value -- ensure not alias and '\0' terminated.
*
- * Allocates a qstring if required.
+ * If is alias, copies that before adding '\0' terminator.
*
- * This sets: body = the src
- * len = strlen(src) (0 if src is NULL)
- * cp = 0 if 'pos' is zero
- * len otherwise
- * size = 0
+ * Sets the '\0' terminator at the 'len' position, extending string if that
+ * is required.
*
- * The zero size means that the qstring handling will not attempt to free
- * the body, nor will it write to it... Operations which require the qstring
- * to have a size will allocate a new body, and discard this one.
+ * If qs == NULL returns pointer to empty '\0' terminated string.
*
- * Returns: the address of the dummy qstring.
+ * NB: The qstring should not be changed or reset until this pointer has been
+ * discarded !
+ *
+ * NB: The value returned is not "const" BUT caller is NOT entitled to change
+ * any part of the string -- CERTAINLY nothing from the '\0' onwards !
*/
-Inline qstring
-qs_dummy(qstring qs, const char* src, int pos)
+Inline char*
+qs_make_string(qstring qs)
{
- if (qs == NULL)
- qs = qs_new() ;
+ static char empty_string[1] ;
- qs->const_body = src ;
- qs->len = (src != NULL) ? strlen(src) : 0 ;
- qs->cp = (pos == 0) ? 0 : qs->len ;
- qs->size = 0 ;
+ usize len ;
+ char* p ;
- return qs ;
-}
+ if (qs == NULL)
+ {
+ len = 0 ;
+ p = empty_string ;
+ }
+ else
+ {
+ len = qs_len_nn(qs) ;
+ if (len >= qs->size) /* for alias, qs_size == 0 */
+ qs_make_to_size(qs, len, len) ; /* extend and/or copy any alias */
-/*------------------------------------------------------------------------------
- * Need space for a string of 'len' characters (plus possible '\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'. (Will be zero if new qstring allocated.)
- */
-Inline qstring
-qs_need(qstring qs, size_t len)
-{
- if ((qs == NULL) || (len >= qs->size))
- return qs_make_to_length(qs, len) ;
+ p = qs_char_nn(qs) ;
+ } ;
- assert(qs->body != NULL) ;
- return qs ;
+ *(p + len) = '\0' ;
+
+ return p ;
} ;
/*------------------------------------------------------------------------------
- * Set 'len' -- allocate or extend body as required.
+ * Return pointer to string value.
*
- * Allocates the qstring, if required.
+ * Writes '\0' at 'len' in order to return a terminated string, if required.
*
- * Returns: address of qstring
+ * If qs == NULL or body == NULL, or 'len' == 0 returns pointer to constant
+ * empty '\0' terminated string (ie "").
*
- * NB: setting len == 0 bytes will cause a body to be allocated, ready for any
- * '\0' !
+ * NB: if 'len' is beyond the current 'size' of the of the qstring, then
+ * will extend the string.
*
- * NB: has no effect on 'cp' -- even if 'cp' > 'len'.
+ * NB: if string is an alias, and that is not '\0' terminated, will make a
+ * copy, before writing '\0' at end.
*
- * NB: if this is a "dummy" qstring, a copy is made of the original body.
+ * NB: In any event, the string should not be changed or reset until this
+ * pointer has been discarded !
*/
-Inline qstring
-qs_set_len(qstring qs, size_t len)
+Inline const char*
+qs_string(qstring qs)
{
- qs = qs_need(qs, len) ;
- qs->len = len ;
- return qs ;
+ char* p ;
+ usize len ;
+
+ if ( (qs == NULL) || ((len = qs_len_nn(qs) ) == 0)
+ || ((p = qs_char_nn(qs)) == NULL) )
+ return "" ;
+
+ if (len >= qs->size) /* for alias, qs_size == 0 */
+ {
+ if (qs->alias && (*(p + len) == '\0'))
+ return p ;
+
+ qs_make_to_size(qs, len, len) ; /* extend and/or copy alias */
+ p = qs_char_nn(qs) ;
+ } ;
+
+ *(p + len) = '\0' ;
+
+ return p ;
} ;
/*------------------------------------------------------------------------------
- * Reset contents of qstring.
+ * Clear contents of qstring -- preserves any qstring body, but sets len = 0.
*
* Does nothing if qstring is NULL
*
- * Sets 'cp' = 'len' = 0. Sets first byte of body (if any) to NULL.
+ * Sets 'cp' = 'len' = 0
+ *
+ * If is an alias qstring, discard the alias.
*
- * For "dummy" qstring, discards the body.
+ * NB: does not create a qstring body if there isn't one.
+ *
+ * NB: does not change the qstring body if there is one. (Which is used in
+ * vio_lc_write_nb().
*/
Inline void
qs_clear(qstring qs)
{
if (qs != NULL)
{
- qs->len = 0 ;
- qs->cp = 0 ;
- if (qs->size > 0)
- *((char*)qs->body) = '\0' ;
- else
- qs->body = NULL ;
+ if (qs->alias)
+ qs_set_real_body_nn(qs) ;
+ qs_set_len_nn(qs, 0) ;
+ qs_set_cp_nn(qs, 0) ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Get length of qstring -- by doing strlen() -- and record it in qs->len.
+ * Ensure have space for a string of 'slen' characters (plus possible '\0'),
+ * and discard any alias.
+ *
+ * Allocate qstring if required (setting 'len' = 'cp' = 0).
*
- * Returns: the string length
+ * Returns: address of qstring -- with body that can be written upto and
+ * including 'slen' + 1.
*
- * NB: if no body has been allocated, length = 0
+ * Has no effect on 'len' -- even if 'len' > 'slen'.
+ *
+ * Has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'.
+ *
+ * If this is a aliased qstring, the alias is discarded.
*/
-Inline size_t
-qs_len(qstring qs)
+Inline qstring
+qs_new_size(qstring qs, usize slen)
{
- return (qs != NULL) ? (qs->len = (qs->body != NULL) ? strlen(qs_chars(qs))
- : 0)
- : 0 ;
+ if (qs == NULL)
+ qs = qs_new_with_body(slen) ;
+ else if (slen >= qs->size) /* for alias qs->size == 0 ! */
+ qs_make_to_size(qs, slen, 0) ; /* extend and/or make body */
+
+ return qs ;
} ;
/*------------------------------------------------------------------------------
- * Get size of qstring body.
+ * Extend to 'len' + 'elen' -- allocate or extend body as required.
+ *
+ * Allocate qstring if required (setting 'cp' = 0).
*
- * NB: if no body has been allocated, size == 0
- * if qstring is NULL, size == 0
+ * Returns: address of qstring -- with body that can be written up to and
+ * including 'len' + 'elen' + 1.
*
- * NB: if this is a "dummy" qstring, size == 0.
+ * Has no effect on 'cp' -- even if 'cp' > 'len'.
+ *
+ * If this is a aliased qstring, a copy is made.
*/
-Inline size_t
-qs_size(qstring qs)
+Inline qstring
+qs_extend(qstring qs, usize elen)
{
- return (qs != NULL) ? qs->size : 0 ;
+ if (qs == NULL)
+ qs = qs_new_with_body(elen) ;
+ else
+ {
+ elen = qs_len_nn(qs) + elen ;
+ if (elen >= qs->size) /* for alias qs->size == 0 ! */
+ qs_make_to_size(qs, elen, elen) ; /* extend and/or copy any alias */
+ } ;
+
+ qs_set_len_nn(qs, elen) ;
+
+ return qs ;
} ;
/*------------------------------------------------------------------------------
- * Get address of current end of qstring body -- ie byte at 'len'.
+ * Chop to given length -- will neither allocate nor extend body.
*
- * NB: allocates body if required.
+ * Does nothing if qstring is NULL.
*
- * There will be space for '\0' after 'len', so the address returned
- * is within the real body of the string.
+ * Does not change the 'len' if it is <= length to chop to.
*
- * NB: if this is a "dummy" qstring, a copy is made of the original body.
+ * NB: has no effect on 'cp' -- even if 'cp' > (new) 'len'.
*
- * NB: address of qstring may NOT be NULL.
+ * NB: if this is a aliased qstring, then it remains an aliased string, but
+ * shorter (unless no change made to the length).
*/
-Inline void*
-qs_end(qstring qs)
+Inline void
+qs_chop(qstring qs, usize clen)
{
- if (qs->len >= qs->size)
- qs_make_to_length(qs, qs->len) ; /* allows for trailing '\0' */
-
- return (char*)qs->body + qs->len ;
+ if (qs != NULL)
+ {
+ if (clen < qs_len_nn(qs))
+ qs_set_len_nn(qs, clen) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Set '\0' at qs->len -- allocate or extend body as required.
- *
- * Returns address of body -- NULL if the qstring is NULL
+ * Compare two qstrings -- returns the usual -ve, 0, +ve cmp result.
*
- * NB: if this is a "dummy" qstring, a copy is made of the original body.
+ * NULL qstring is treated as empty.
*/
-Inline void*
-qs_term(qstring qs)
+Inline int
+qs_cmp(qstring a, qstring b)
{
- size_t len ;
-
- if (qs == NULL)
- return NULL ;
-
- if ((len = qs->len) >= qs->size)
- qs_make_to_length(qs, len) ;
-
- *qs_chars_at(qs, len) = '\0' ;
-
- return qs->body ;
+ return els_cmp(qs_els(a), qs_els(b)) ;
} ;
/*------------------------------------------------------------------------------
- * Insert 'n' bytes at 'cp' -- moves anything cp..len up.
- *
- * Increases 'len'. but does not affect 'cp'.
- *
- * Returns: number of bytes beyond 'cp' that were moved before insert.
- *
- * NB: qstring MUST NOT be NULL
- *
- * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce
- * one or more undefined bytes.
- *
- * NB: the string is NOT re-terminated.
- *
- * NB: if this is a "dummy" qstring, a copy is made of the original body.
+ * Compare qstrings to given word -- see els_cmp_word
*/
-Inline size_t
-qs_insert(qstring qs, const void* src, size_t n)
+Inline int
+qs_cmp_word(qstring a, qstring w)
{
- size_t after ;
- char* p ;
-
- if (qs->len < qs->cp)
- qs->len = qs->cp ;
- after = qs->len - qs->cp ;
-
- qs_set_len(qs, qs->len + n) ; /* set len and ensure have space */
-
- p = qs_cp_char(qs) ;
- if (after > 0)
- memmove (p + n, p, after) ;
-
- if (n > 0)
- memmove(p, src, n) ;
-
- return after ;
+ return els_cmp_word(qs_els(a), qs_els(w)) ;
} ;
/*------------------------------------------------------------------------------
- * Replace 'n' bytes at 'cp' -- extending if required.
- *
- * May increase 'len'. but does not affect 'cp'.
+ * Compare significant parts of two qstrings -- returns the usual -ve, 0, +ve
+ * cmp result.
*
- * NB: qstring MUST NOT be NULL
+ * By significant, mean excluding leading/trailing isspace() and treating
+ * multiple isspace() as single isspace().
*
- * 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.
+ * NULL qstring is treated as empty.
*/
-Inline void
-qs_replace(qstring qs, const void* src, size_t n)
+Inline int
+qs_cmp_sig(qstring a, qstring b)
{
- if ((qs->len < qs->cp + n) || (qs->size == 0))
- qs_set_len(qs, qs->cp + n) ; /* set len and ensure have space */
+ return els_cmp_sig(qs_els(a), qs_els(b)) ;
+} ;
- if (n > 0)
- memmove(qs_cp_char(qs), src, n) ;
+/*------------------------------------------------------------------------------
+ * Are two qstrings equal ? -- returns true if they are.
+ */
+Inline bool
+qs_equal(qstring a, qstring b)
+{
+ return els_equal(qs_els(a), qs_els(b)) ;
} ;
/*------------------------------------------------------------------------------
- * Remove 'n' bytes at 'cp' -- extending if required.
- *
- * May change 'len'. but does not affect 'cp'.
+ * Is 'b' a leading substring of 'a' ? -- returns true if it is.
*
- * 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.
+ * If 'b' is empty it is always a leading substring.
*/
-Inline size_t
-qs_delete(qstring qs, size_t n)
+Inline bool
+qs_substring(qstring a, qstring b)
{
- 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)
- {
- qs_set_len(qs, qs->cp) ; /* set len, looks after cp > len */
- return 0 ; /* nothing after */
- }
-
- /* There is at least one byte after cp (so body must exist) */
- after = qs->len - (qs->cp + n) ;
-
- if (n > 0)
- {
- p = qs_cp_char(qs) ;
- memmove (p, p + n, after) ;
-
- qs->len -= n ;
- } ;
-
- return after ;
+ return els_substring(qs_els(a), qs_els(b)) ;
} ;
-
#endif /* _ZEBRA_QSTRING_H */
diff --git a/lib/qtime.c b/lib/qtime.c
index 42b903da..d1283198 100644
--- a/lib/qtime.c
+++ b/lib/qtime.c
@@ -18,11 +18,11 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include <sys/times.h>
#include <errno.h>
-#include "zassert.h"
#include "qtime.h"
/*==============================================================================
@@ -69,17 +69,40 @@
* (It appears that 60, 100, 250 and 1,000 ticks/sec. are popular options.)
*
* If sizeof(clock_t) > 4, it is assumed large enough never to wrap around.
+ * (But seems unlikely that such a system would not support CLOCK_MONOTONIC !)
*
* When clock_t is a 32-bit integer must be at least ready for wrap around.
- * There are two cases:
+ * We take the clock_t signed values and widen to 64-bit signed, so we have
+ * the current sample (this) and the previous one (last), and two cases to
+ * consider:
*
- * * +ve wrap around. new < old value, and new >= 0
+ * * +ve wrap around -- so value is 31-bit unsigned, and wraps from large
+ * +ve value to small +ve value.
*
- * step = (INT32_MAX - old + 1) + new
+ * step = this - last will be -ve
*
- * * -ve wrap around. new < old value, and new < 0 (and old > 0)
+ * 'last' will be some value ((INT32_MAX + 1) - x), and 'this' will be some
+ * (relatively) small value y. The step is x + y, we have:
*
- * step = (INT32_MAX - old + 1) - (INT32_MIN - new)
+ * step = y - ((INT32_MAX + 1) - x) = (x + y) - (INT32_MAX + 1)
+ *
+ * so we correct by adding (INT32_MAX + 1).
+ *
+ * * -ve wrap around -- so value is 32-bit signed, and wraps from a large
+ * +ve value to a very -ve value.
+ *
+ * step = this - last will be -ve
+ *
+ * 'last will' be some value (INT32_MAX + 1) - x, and 'this' will be some
+ * value (y - (INT32_MAX + 1)). The step is x + y, we have:
+ *
+ * step = (y - (INT32_MAX + 1)) - ((INT32_MAX + 1) - x)
+ * = (x + y) - 2 * (INT32_MAX + 1)
+ *
+ * so we correct by adding (INT32_MAX + 1).
+ *
+ * In both cases the wrap around gives an apparently -ve 'step', and that is
+ * corrected by adding (INT32_MAX + 1) until it goes +ve.
*
* In any event, a step > 24 hours is taken to means that something has gone
* very, very badly wrong.
@@ -97,23 +120,24 @@ CONFIRM((sizeof(clock_t) >= 4) && (sizeof(clock_t) <= 8)) ;
#ifdef GNU_LINUX
#define TIMES_TAKES_NULL 1
#else
-#undef TIMES_TAKES_NULL
+#define TIMES_TAKES_NULL 0
#endif
-static uint64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */
-static int64_t last_times_sample = 0 ; /* last value returned by times() */
+static int64_t monotonic = 0 ; /* monotonic clock in _SC_CLK_TCK's */
+static int64_t last_times_sample = 0 ; /* last value returned by times() */
-static uint64_t step_limit = 0 ; /* for sanity check */
+static int64_t step_limit = 0 ; /* for sanity check */
-static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */
-static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */
-static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */
+static int64_t times_clk_tcks = 0 ; /* sysconf(_SC_CLK_TCK) */
+static qtime_t times_scale_q = 0 ; /* 10**9 / times_clk_tcks */
+static qtime_t times_scale_r = 0 ; /* 10**9 % times_clk_tcks */
qtime_mono_t
qt_craft_monotonic(void) {
- struct tms dummy ;
- int64_t this_times_sample ;
- uint64_t step ;
+#if !TIMES_TAKES_NULL
+ struct tms dummy[1] ;
+#endif
+ clock_t this_times_sample ;
/* Set up times_scale_q & times_scale_q if not yet done. */
if (times_clk_tcks == 0) /* Is zero until it's initialized */
@@ -130,7 +154,7 @@ qt_craft_monotonic(void) {
times_scale_q = qr.quot ;
times_scale_r = qr.rem ;
- step_limit = (uint64_t)24 * 60 * 60 * times_clk_tcks ;
+ step_limit = (int64_t)24 * 60 * 60 * times_clk_tcks ;
} ;
/* No errors are defined for times(), but a return of -1 is defined */
@@ -139,51 +163,54 @@ qt_craft_monotonic(void) {
/* The following deals carefully with this -- cannot afford for the */
/* clock either to jump or to get stuck ! */
-#ifdef TIMES_TAKES_NULL
- this_times_sample = times(NULL) ; /* assume this saves effort ! */
+#if TIMES_TAKES_NULL
+# define TIMES_ARG NULL
#else
- this_times_sample = times(&dummy) ;
+# define TIMES_ARG dummy
#endif
+ this_times_sample = times(TIMES_ARG) ;
if (this_times_sample == -1) /* deal with theoretical error */
{
errno = 0 ;
- this_times_sample = times(&dummy) ;
+ this_times_sample = times(TIMES_ARG) ;
if (errno != 0)
zabort_errno("times() failed") ;
} ;
+#undef TIMES_ARG
- /* Calculate the step and verify sensible. */
- /* */
- /* Watch out for huge jumps and/or time going backwards. */
- /* For 32-bit clock_t, look out for wrap-around. */
-
- if ((sizeof(clock_t) > 4) || (this_times_sample > last_times_sample))
- /* time going backwards will appear as HUGE step forwards. */
- step = (uint64_t)(this_times_sample - last_times_sample) ;
+ /* If clock_t is large enough, treat as monotonic (!).
+ *
+ * Otherwise calculate the difference between this sample and the
+ * previous one -- the step.
+ *
+ * We do the sum in signed 64 bits, and the samples are signed 64 bits.
+ */
+ if (sizeof(clock_t) > 4)
+ monotonic = this_times_sample ;
else
{
- if (this_times_sample > 0)
- /* both samples +ve => +ve wrap around. */
- step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
- + this_times_sample ) ;
- else
- /* this sample -ve and last sample +ve => -ve wrap round */
- /* this sample -ve and last sample -ve => time gone backwards */
- /* (which appears as a HUGE step forwards). */
- step = (uint64_t)( ((int64_t)INT32_MAX - last_times_sample + 1)
- - ((int64_t)INT32_MIN - this_times_sample) ) ;
- } ;
+ int64_t step ;
- /* TODO: better error messaging for large clock jumps. */
- if (step > step_limit)
- zabort("Sudden large monotonic clock jump") ;
+ step = this_times_sample - last_times_sample ;
- /* Advance the monotonic clock in sysconf(_SC_CLK_TCK) units. */
- monotonic += step ;
+ while (step < 0)
+ {
+ /* If times() wraps unsigned, then result needs INT32_MAX + 1
+ * adding to it to get to +ve result.
+ *
+ * If times() wraps signed, then result needs INT32_MAX + 1 adding
+ * to it *twice*.
+ */
+ step += (uint64_t)INT32_MAX + 1 ;
+ } ;
- /* Remember what we got, for next time. */
- last_times_sample = this_times_sample ;
+ if (step > step_limit)
+ zabort("Sudden large monotonic clock jump") ;
+
+ monotonic += step ;
+ last_times_sample = this_times_sample ;
+ } ;
/* Scale to qtime_t units. */
if (times_scale_r == 0)
@@ -203,3 +230,135 @@ qt_craft_mono_secs(void)
return monotonic / times_clk_tcks ;
} ;
+
+/*==============================================================================
+ * A simple minded random number generator.
+ *
+ * Designed to be reasonably unpredictable... particularly the ms bits !
+ */
+
+static inline uint32_t
+qt_rand(uint64_t q, uint64_t s)
+{
+ /* Takes q ^ s and reduces to 32 bits by xoring ms and ls halves
+ * then uses Knuth recommended linear congruent to randomise that, so that
+ * most of the original bits affect the result.
+ *
+ * Note that linear congruent tends to be "more random" in the ms bits.
+ */
+ q ^= s ;
+ q = (q ^ (q >> 32)) & 0xFFFFFFFF ;
+ return ((q * 2650845021) + 5) & 0xFFFFFFFF ;
+} ;
+
+extern uint32_t
+qt_random(uint32_t seed)
+{
+ uint32_t x, y ;
+ uint64_t t ;
+
+ t = qt_get_realtime() ;
+
+ /* Set x by munging the time, the address of x, the current contents of x,
+ * and the "seed". (Munge the seed a bit for good measure.)
+ */
+ x = qt_rand(t ^ (uint64_t)x ^ (uint64_t)&x, seed ^ 3141592653) ;
+ /* munge the address and the contents with the seed */
+
+ /* Set y by munging the time, the address of y, the current contents of y,
+ * and the "seed". (Munge the seed a bit for good measure.)
+ */
+ y = qt_rand(t ^ (uint64_t)y ^ (uint64_t)&y, seed ^ 3562951413) ;
+ /* munge the current real time with the seed */
+
+ /* Return x and y munged together.
+ *
+ * Note that we swap the halves of y before munging, in order to spread
+ * the "more random" part of y down to the ls end of the result.
+ */
+ return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ;
+} ;
+
+/*==============================================================================
+ * Tracking the local timezone, so can:
+ *
+ * a) rapidly convert clock_gettime(CLOCK_REALTIME, ...) times
+ *
+ * b) do that thread-safe
+ *
+ * c) do that async-signal-safe
+ *
+ * Assumptions:
+ *
+ * a) that timezones are on at most 5 minute boundaries (15 probably!)
+ *
+ * b) that DST changes are at least 60 days apart
+ *
+ * c) that DST changes occur on times which are on 5 minute boundaries
+ * (60 probably -- but this means that the DST change is on a 5 minute
+ * bounderay in local and epoch times !)
+ *
+ * Sets up and maintains a table containing 8 entries:
+ *
+ * [-3] previous - 2
+ * [-2] previous - 1
+ * [-1] previous -- previous 0-7 days
+ * [ 0] current -- current 0-7 days
+ * [+1] next -- next 0-7 days
+ * [+2] next + 1
+ * [+3] next + 2
+ * [ X] sentinal
+ *
+ * These are configured before any threads or anything else very much runs, so
+ * they are essentially static. There is a "current index", which is set to
+ * '0' to start with.
+ *
+ * Each entry comprises:
+ *
+ * * start time -- entry is valid for epoch times >= start
+ * * end time -- entry is valid for epoch times < end
+ * * offset -- add to epoch time to get local
+ *
+ * When set up the current timezone initially starts on the nearest 5 minute
+ * boundary in the past, and covers up to 7 days into the future, unless the
+ * timezone changes in that time. The timezones on either side are set
+ * similarly.
+ *
+ * At most one of these timezones may be a short one -- so this covers at least
+ * 14 days into the past and 21 into the future, and as time advances, upto
+ * 21 days into the past and down to 14 days into the future.
+ *
+ * Maximum range is 56 days -- which is within the assumed 60 days between
+ * DST changes.
+ *
+ * When time advances past the current entry the next, next + 1 and + 2 cover
+ * at least 14 days (21 if none are short entries).
+ *
+ * Every now and then (say every 5 minutes) a background process can check the
+ * current time. If that is no longer in the current entry, needs to update
+ * the table. Assuming time is moving forward: sets sentinal to be the next
+ * 0-7 days following the current last entry, and updates the "current index".
+ *
+ * BIG ASSUMPTION: that the "current index" value is written atomically, wrt
+ * to threads as well as signals.
+ *
+ * It doesn't matter if a thread or signal action code picks
+ * up an out of date "current index" value, because all the
+ * entries for the old state are still valid.
+ *
+ * No entry is changed while it is covered by the current
+ * index -3..+3.
+ *
+ * This works fine, UNLESS the clock_gettime(CLOCK_REALTIME, ...) changes
+ * dramatically -- as might happen if the operator adjusts the system clock a
+ * long way !
+ *
+ * To cope with this, a spare set of 8 entries are kept, and a new table can
+ * be built (under mutex). The worst that happens is that threads may be
+ * blocked waiting for the table to be updated.
+ *
+ * If the table is found to be out of date when a signal is bringing the
+ * system down, then the times logged will just have to use either the first
+ * or the last entry, and have done with it.
+ */
+
diff --git a/lib/qtime.h b/lib/qtime.h
index df486dfd..577c34a3 100644
--- a/lib/qtime.h
+++ b/lib/qtime.h
@@ -22,19 +22,12 @@
#ifndef _ZEBRA_QTIME_H
#define _ZEBRA_QTIME_H
-#include <stdint.h>
-#include <stdlib.h>
+#include "misc.h"
+
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
-#include "zassert.h"
-#include "config.h"
-
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* qtime_t -- signed 64-bit integer.
*
@@ -59,26 +52,22 @@ typedef qtime_t qtime_mono_t ; /* qtime_t value, monotonic time-base */
typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */
/* ...just in case != CLOCK_REALTIME ! */
-/* A qtime_t second 123456789 -- nano-seconds */
-#define QTIME_SECOND 1000000000
-#define TIMESPEC_SECOND 1000000000
-#define TIMEVAL_SECOND 1000000
+/* A qtime_t second 123456789 -- nano-seconds */
+#define QTIME_SECOND ((qtime_t)1000000000)
+#define TIMESPEC_SECOND 1000000000
+#define TIMEVAL_SECOND 1000000
/* Macro to convert time in seconds to a qtime_t */
/* Note that the time to convert may be a float. */
#define QTIME(s) ((qtime_t)((s) * (qtime_t)QTIME_SECOND))
-Inline qtime_t
-timespec2qtime(struct timespec* p_ts) ;
-
-Inline qtime_t
-timeval2qtime(struct timeval* p_tv) ;
-
-Inline struct timespec*
-qtime2timespec(struct timespec* p_ts, qtime_t qt) ;
-
-Inline struct timeval*
-qtime2timeval(struct timeval* p_tv, qtime_t qt) ;
+/*==============================================================================
+ * Conversion functions
+ */
+Inline qtime_t timespec2qtime(struct timespec* p_ts) ;
+Inline qtime_t timeval2qtime(struct timeval* p_tv) ;
+Inline struct timespec* qtime2timespec(struct timespec* p_ts, qtime_t qt) ;
+Inline struct timeval* qtime2timeval(struct timeval* p_tv, qtime_t qt) ;
/*==============================================================================
* Clocks.
@@ -97,47 +86,40 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt) ;
* a manufactured equivalent using times() -- see qt_craft_monotonic().
*/
-Inline qtime_real_t
-qt_get_realtime(void) ; /* clock_gettime(CLOCK_REALTIME, ...) */
-
-Inline qtime_mono_t
-qt_add_realtime(qtime_t interval) ; /* qt_get_realtime() + interval */
+Inline qtime_real_t qt_get_realtime(void) ;
+ /* clock_gettime(CLOCK_REALTIME, ...) */
+Inline qtime_mono_t qt_add_realtime(qtime_t interval) ;
+ /* qt_get_realtime() + interval */
-Inline qtime_mono_t
-qt_get_monotonic(void) ; /* clock_gettime(CLOCK_MONOTONIC, ...) */
+Inline qtime_mono_t qt_get_monotonic(void) ;
+ /* clock_gettime(CLOCK_MONOTONIC, ...) */
/* OR equivalent using times() */
-Inline qtime_mono_t
-qt_add_monotonic(qtime_t interval) ; /* qt_get_monotonic() + interval */
+Inline qtime_mono_t qt_add_monotonic(qtime_t interval) ;
+ /* qt_get_monotonic() + interval */
Inline time_t qt_get_mono_secs(void) ;
/* clock_gettime(CLOCK_MONOTONIC, ...) */
/* OR equivalent using times() */
-Inline qtime_mono_t /* monotonic time from CLOCK_REALTIME */
-qt_realtime2monotonic(qtime_real_t realtime) ;
-Inline qtime_real_t /* CLOCK_REALTIME from monotonic time */
-qt_monotonic2realtime(qtime_mono_t monotonic) ;
-
-/* Function to manufacture a monotonic clock. */
-extern qtime_mono_t qt_craft_monotonic(void) ;
-extern time_t qt_craft_mono_secs(void) ;
-
/* These are provided just in case gettimeofday() != CLOCK_REALTIME */
-Inline qtime_tod_t
-qt_get_timeofday(void) ; /* gettimeofday(&tv, NULL) */
-
-Inline qtime_tod_t
-qt_add_timeofday(qtime_t interval) ; /* qt_get_timeofday() + interval */
+Inline qtime_tod_t qt_get_timeofday(void) ;
+ /* gettimeofday(&tv, NULL) */
+Inline qtime_tod_t qt_add_timeofday(qtime_t interval) ;
+ /* qt_get_timeofday() + interval */
-Inline qtime_mono_t /* monotonic time from timeofday */
-qt_timeofday2monotonic(qtime_tod_t timeofday) ;
-Inline qtime_tod_t /* timeofday from monotonic time */
-qt_monotonic2timeofday(qtime_mono_t monotonic) ;
+/*==============================================================================
+ * Primitive random number generation.
+ *
+ * Uses time and other stuff to produce something which is not particularly
+ * predictable.
+ */
+extern uint32_t qt_random(uint32_t seed) ;
/*==============================================================================
* Inline conversion functions
*/
-/* Convert timespec to qtime_t
+/*------------------------------------------------------------------------------
+ * Convert timespec to qtime_t
*
* Returns qtime_t value.
*/
@@ -148,7 +130,8 @@ timespec2qtime(struct timespec* p_ts)
confirm(QTIME_SECOND == TIMESPEC_SECOND) ;
} ;
-/* Convert timeval to qtime_t
+/*------------------------------------------------------------------------------
+ * Convert timeval to qtime_t
*
* Returns qtime_t value.
*/
@@ -159,7 +142,8 @@ timeval2qtime(struct timeval* p_tv)
confirm(QTIME_SECOND == TIMEVAL_SECOND * 1000) ;
} ;
-/* Convert qtime_t to timespec
+/*------------------------------------------------------------------------------
+ * Convert qtime_t to timespec
*
* Takes address of struct timespec and returns that address.
*/
@@ -176,7 +160,8 @@ qtime2timespec(struct timespec* p_ts, qtime_t qt)
return p_ts ;
} ;
-/* Convert timespec to qtime_t
+/*------------------------------------------------------------------------------
+ * Convert timespec to qtime_t
*
* Takes address of struct timespec and returns that address.
*/
@@ -197,7 +182,12 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt)
* Inline Clock Functions.
*/
-/* Read given clock & return a qtime_t value.
+/* Function to manufacture a monotonic clock. */
+Private qtime_mono_t qt_craft_monotonic(void) ;
+Private time_t qt_craft_mono_secs(void) ;
+
+/*------------------------------------------------------------------------------
+ * Read given clock & return a qtime_t value.
*
* While possibility of error is essentially theoretical, must treat it as a
* FATAL error -- cannot continue with broken time value !
@@ -214,7 +204,8 @@ qt_clock_gettime(clockid_t clock_id)
return timespec2qtime(&ts) ;
} ;
-/* clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value
+/*------------------------------------------------------------------------------
+ * clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value
*
* While possibility of error is essentially theoretical, must treat it as a
* FATAL error -- cannot continue with broken time value !
@@ -225,7 +216,8 @@ qt_get_realtime(void)
return qt_clock_gettime(CLOCK_REALTIME) ;
} ;
-/* qt_get_realtime() + interval
+/*------------------------------------------------------------------------------
+ * qt_get_realtime() + interval
*/
Inline qtime_real_t
qt_add_realtime(qtime_t interval)
@@ -233,7 +225,8 @@ qt_add_realtime(qtime_t interval)
return qt_get_realtime() + interval;
} ;
-/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic()
+/*------------------------------------------------------------------------------
+ * clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic()
* -- returning qtime_t value
*
* While possibility of error is essentially theoretical, must treat it as a
@@ -249,7 +242,8 @@ qt_get_monotonic(void)
#endif
} ;
-/* qt_get_monotonic() + interval
+/*------------------------------------------------------------------------------
+ * qt_get_monotonic() + interval
*/
Inline qtime_mono_t
qt_add_monotonic(qtime_t interval)
@@ -257,7 +251,8 @@ qt_add_monotonic(qtime_t interval)
return qt_get_monotonic() + interval;
} ;
-/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic()
+/*------------------------------------------------------------------------------
+ * clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic()
* -- returning time_t value
*
* Value returned is in seconds -- for coarser grain timings.
@@ -280,7 +275,8 @@ qt_get_mono_secs(void)
#endif
} ;
-/* gettimeofday(&tv, NULL) -- returning qtime_t value
+/*------------------------------------------------------------------------------
+ * gettimeofday(&tv, NULL) -- returning qtime_t value
*/
Inline qtime_tod_t
qt_get_timeofday(void)
@@ -290,7 +286,8 @@ qt_get_timeofday(void)
return timeval2qtime(&tv) ;
}
-/* qt_get_timeofday() + interval
+/*------------------------------------------------------------------------------
+ * qt_get_timeofday() + interval
*/
Inline qtime_tod_t
qt_add_timeofday(qtime_t interval)
@@ -298,36 +295,4 @@ qt_add_timeofday(qtime_t interval)
return qt_get_timeofday() + interval;
} ;
-/*==============================================================================
- * Conversion between realtime/timeofday and monotonic
- */
-
-/* Convert a CLOCK_REALTIME time to our local monotonic time. */
-Inline qtime_mono_t
-qt_realtime2monotonic(qtime_real_t realtime)
-{
- return qt_get_monotonic() + (realtime - qt_get_realtime()) ;
-} ;
-
-/* Convert a local monotonic time to CLOCK_REALTIME time. */
-Inline qtime_real_t
-qt_monotonic2realtime(qtime_mono_t monotonic)
-{
- return qt_get_realtime() + (monotonic - qt_get_monotonic()) ;
-} ;
-
-/* Convert a gettimeofday() time to our local monotonic time. */
-Inline qtime_mono_t
-qt_timeofday2monotonic(qtime_tod_t timeofday)
-{
- return qt_get_monotonic() + (timeofday - qt_get_timeofday()) ;
-} ;
-
-/* Convert a local monotonic time to gettimeofday() time. */
-Inline qtime_tod_t
-qt_monotonic2timeofday(qtime_mono_t monotonic)
-{
- return qt_get_timeofday() + (monotonic - qt_get_monotonic()) ;
-} ;
-
#endif /* _ZEBRA_QTIME_H */
diff --git a/lib/qtimers.c b/lib/qtimers.c
index 8c08a6bc..ad8da395 100644
--- a/lib/qtimers.c
+++ b/lib/qtimers.c
@@ -18,23 +18,12 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
-#include <stddef.h>
-#include <string.h>
-
-#include "zassert.h"
#include "qtimers.h"
#include "memory.h"
#include "heap.h"
-enum { qdebug =
-#ifdef QDEBUG
- 1
-#else
- 0
-#endif
-};
-
/*==============================================================================
* Quagga Timers -- qtimer_xxxx
*
@@ -170,7 +159,7 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto)
{
qtimer qtr ;
- if (qdebug)
+ if (qtimers_debug)
qtimer_pile_verify(qtp) ;
qtr = heap_top_item(&qtp->timers) ;
@@ -213,11 +202,12 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto)
* and the process MUST be run to completion.
*/
qtimer
-qtimer_pile_ream(qtimer_pile qtp, int free_structure)
+qtimer_pile_ream(qtimer_pile qtp, free_keep_b free_structure)
{
qtimer qtr ;
+ confirm(free_it == true) ;
- qtr = heap_ream_keep(&qtp->timers) ; /* ream, keeping the heap structure */
+ qtr = heap_ream(&qtp->timers, keep_it) ; /* ream, keeping the heap */
if (qtr != NULL)
qtr->active = false ; /* has been removed from pile */
else
@@ -342,7 +332,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action)
qtp = qtr->pile ;
assert(qtp != NULL) ;
- if (qdebug)
+ if (qtimers_debug)
qtimer_pile_verify(qtp) ;
qtr->time = when ;
@@ -370,7 +360,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action)
else
assert(qtr->action != NULL) ;
- if (qdebug)
+ if (qtimers_debug)
qtimer_pile_verify(qtp) ;
} ;
@@ -386,7 +376,7 @@ qtimer_unset(qtimer qtr)
assert(qtp != NULL) ;
- if (qdebug)
+ if (qtimers_debug)
qtimer_pile_verify(qtp) ;
if (qtr->active)
@@ -396,7 +386,7 @@ qtimer_unset(qtimer qtr)
heap_delete_item(&qtp->timers, qtr) ;
- if (qdebug)
+ if (qtimers_debug)
qtimer_pile_verify(qtp) ;
qtr->active = false ;
@@ -413,10 +403,10 @@ qtimer_pile_verify(qtimer_pile qtp)
{
heap th = &qtp->timers ;
vector v ;
- vector_index i ;
- vector_index e ;
+ vector_index_t i ;
+ vector_length_t e ;
qtimer qtr ;
- bool seen ;
+ bool seen = false ;
assert(qtp != NULL) ;
@@ -429,7 +419,7 @@ qtimer_pile_verify(qtimer_pile qtp)
assert(th->state == Heap_Has_Backlink) ;
assert(th->backlink_offset == offsetof(qtimer_t, backlink)) ;
- v = &th->v ;
+ v = th->v ;
e = vector_end(v) ;
for (i = 0 ; i < e ; ++i)
{
@@ -437,7 +427,10 @@ qtimer_pile_verify(qtimer_pile qtp)
assert(qtr != NULL) ;
if (qtr == qtp->implicit_unset)
- seen = 1 ;
+ {
+ assert(!seen) ;
+ seen = true ;
+ } ;
assert(qtr->active) ;
diff --git a/lib/qtimers.h b/lib/qtimers.h
index 5beb931b..16808f1b 100644
--- a/lib/qtimers.h
+++ b/lib/qtimers.h
@@ -22,16 +22,12 @@
#ifndef _ZEBRA_QTIMERS_H
#define _ZEBRA_QTIMERS_H
-#include <stdbool.h>
+#include "misc.h"
#include "zassert.h"
#include "qtime.h"
#include "heap.h"
-#ifndef Inline
-#define Inline static inline
-#endif
-
/*==============================================================================
* Quagga Timers -- qtimer_xxxx
*
@@ -39,6 +35,39 @@
* each with an action to be executed when the timer expires.
*/
+/*------------------------------------------------------------------------------
+ * Sort out QTIMERS_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if QTIMERS_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QTIMERS_DEBUG == 0 to turn off debug
+ * * or set QTIMERS_DEBUG != 0 to turn on debug
+ * * or set QTIMERS_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef QTIMERS_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QTIMERS_DEBUG)
+# undef QTIMERS_DEBUG
+# define QTIMERS_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define QTIMERS_DEBUG QDEBUG
+#endif
+
+#ifdef QTIMERS_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QTIMERS_NO_DEBUG)
+# undef QTIMERS_DEBUG
+# define QTIMERS_DEBUG 0
+# endif
+#endif
+
+enum { qtimers_debug = QTIMERS_DEBUG } ;
+
/*==============================================================================
* Data Structures.
*/
@@ -76,7 +105,7 @@ struct qtimer_pile
extern qtimer_pile qtimer_pile_init_new(qtimer_pile qtp) ;
extern bool qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) ;
extern qtime_t qtimer_pile_top_wait(qtimer_pile qtp, qtime_t max_wait) ;
-extern qtimer qtimer_pile_ream(qtimer_pile qtp, int free_structure) ;
+extern qtimer qtimer_pile_ream(qtimer_pile qtp, free_keep_b free_structure) ;
/* Ream out qtimer pile and free the qtimer structure. */
#define qtimer_pile_ream_free(qtp) qtimer_pile_ream(qtp, 1)
diff --git a/lib/regex.c b/lib/regex.c
index a22e03f6..425f3c8e 100644
--- a/lib/regex.c
+++ b/lib/regex.c
@@ -27,9 +27,8 @@
#undef _GNU_SOURCE
#define _GNU_SOURCE
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include "misc.h"
+
#ifdef _WIN32
/* Windows does not provide unistd.h, which is required for abort() */
#include <process.h>
@@ -209,7 +208,7 @@ init_syntax_once ()
# define SYNTAX(c) re_syntax_table[c]
#endif /* not emacs */
-
+
/* Get the interface, including the syntax bits. */
#include <regex-gnu.h>
@@ -279,7 +278,7 @@ init_syntax_once ()
/* As in Harbison and Steele. */
# define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128)
#endif
-
+
/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we
use `alloca' instead of `malloc'. This is because using malloc in
re_search* or re_match* could cause memory leaks when C-g is used in
@@ -388,7 +387,7 @@ static int re_match_2_internal PARAMS ((struct re_pattern_buffer *bufp,
int pos,
struct re_registers *regs,
int stop));
-
+
/* These are the command codes that appear in compiled regular
expressions. Some opcodes are followed by argument bytes. A
command code can specify any interpretation whatsoever for its
@@ -527,7 +526,7 @@ typedef enum
notsyntaxspec
#endif /* emacs */
} re_opcode_t;
-
+
/* Common operations on the compiled pattern. */
/* Store NUMBER in two contiguous bytes starting at DESTINATION. */
@@ -604,7 +603,7 @@ extract_number_and_incr (destination, source)
# endif /* not EXTRACT_MACROS */
#endif /* DEBUG */
-
+
/* If DEBUG is defined, Regex prints many voluminous messages about what
it is doing (if the variable `debug' is nonzero). If linked with the
main program in `iregex.c', you can enter patterns and strings
@@ -977,7 +976,7 @@ printchar (c)
# define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)
#endif /* not DEBUG */
-
+
/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
also be assigned to arbitrarily: each pattern buffer stores its own
syntax, so it can be changed between regex compilations. */
@@ -1011,7 +1010,7 @@ re_set_syntax (syntax)
#ifdef _LIBC
weak_alias (__re_set_syntax, re_set_syntax)
#endif
-
+
/* This table gives an error message for each of the error codes listed
in regex.h. Obviously the order here has to be same as there.
POSIX doesn't require that we do anything for REG_NOERROR,
@@ -1091,7 +1090,7 @@ static const size_t re_error_msgid_idx[] =
REG_ESIZE_IDX,
REG_ERPAREN_IDX
};
-
+
/* Avoiding alloca during matching, to placate r_alloc. */
/* Define MATCH_MAY_ALLOCATE unless we need to make sure that the
@@ -1129,7 +1128,7 @@ static const size_t re_error_msgid_idx[] =
# undef MATCH_MAY_ALLOCATE
#endif
-
+
/* Failure stack declarations and macros; both re_compile_fastmap and
re_match_2 use a failure stack. These have to be macros because of
REGEX_ALLOCATE_STACK. */
@@ -1495,7 +1494,7 @@ typedef struct
} /* POP_FAILURE_POINT */
-
+
/* Structure for per-register (a.k.a. per-group) information.
Other register information, such as the
starting and ending positions (which are addresses), and the list of
@@ -1555,7 +1554,7 @@ typedef union
static char reg_unset_dummy;
#define REG_UNSET_VALUE (&reg_unset_dummy)
#define REG_UNSET(e) ((e) == REG_UNSET_VALUE)
-
+
/* Subroutine declarations and macros for regex_compile. */
static reg_errcode_t regex_compile _RE_ARGS ((const char *pattern, size_t size,
@@ -1809,7 +1808,7 @@ typedef struct
|| STREQ (string, "punct") || STREQ (string, "graph") \
|| STREQ (string, "cntrl") || STREQ (string, "blank"))
#endif
-
+
#ifndef MATCH_MAY_ALLOCATE
/* If we cannot allocate large objects within re_match_2_internal,
@@ -1857,7 +1856,7 @@ regex_grow_registers (num_regs)
}
#endif /* not MATCH_MAY_ALLOCATE */
-
+
static boolean group_in_compile_stack _RE_ARGS ((compile_stack_type
compile_stack,
regnum_t regnum));
@@ -2991,7 +2990,7 @@ regex_compile (pattern, size, syntax, bufp)
return REG_NOERROR;
} /* regex_compile */
-
+
/* Subroutines for `regex_compile'. */
/* Store OP at LOC followed by two-byte integer parameter ARG. */
@@ -3178,7 +3177,7 @@ compile_range (p_ptr, pend, translate, syntax, b)
return REG_NOERROR;
}
-
+
/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in
BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible
characters can start a string that matches the pattern. This fastmap
@@ -3484,7 +3483,7 @@ re_compile_fastmap (bufp)
#ifdef _LIBC
weak_alias (__re_compile_fastmap, re_compile_fastmap)
#endif
-
+
/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
this memory for recording register information. STARTS and ENDS
@@ -3522,7 +3521,7 @@ re_set_registers (bufp, regs, num_regs, starts, ends)
#ifdef _LIBC
weak_alias (__re_set_registers, re_set_registers)
#endif
-
+
/* Searching routines. */
/* Like re_search_2, below, but only one string is specified, and
@@ -3704,7 +3703,7 @@ re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop)
#ifdef _LIBC
weak_alias (__re_search_2, re_search_2)
#endif
-
+
/* This converts PTR, a pointer into one of the search strings `string1'
and `string2' into an offset from the beginning of that string. */
#define POINTER_TO_OFFSET(ptr) \
@@ -3783,7 +3782,7 @@ weak_alias (__re_search_2, re_search_2)
to actually save any registers when none are active. */
#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH)
#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1)
-
+
/* Matching routines. */
#ifndef emacs /* Emacs never uses this. */
@@ -5248,7 +5247,7 @@ re_match_2_internal (bufp, string1, size1, string2, size2, pos, regs, stop)
return -1; /* Failure to match. */
} /* re_match_2 */
-
+
/* Subroutine definitions for re_match_2. */
@@ -5511,7 +5510,7 @@ bcmp_translate (s1, s2, len, translate)
}
return 0;
}
-
+
/* Entry points for GNU code. */
/* re_compile_pattern is the GNU regular expression compiler: it
@@ -5552,7 +5551,7 @@ re_compile_pattern (pattern, length, bufp)
#ifdef _LIBC
weak_alias (__re_compile_pattern, re_compile_pattern)
#endif
-
+
/* Entry points compatible with 4.2 BSD regex library. We don't define
them unless specifically requested. */
@@ -5623,7 +5622,7 @@ re_exec (s)
}
#endif /* _REGEX_RE_COMP */
-
+
/* POSIX.2 functions. Don't define these for Emacs. */
#ifndef emacs
diff --git a/lib/routemap.c b/lib/routemap.c
index b452530d..6de2098d 100644
--- a/lib/routemap.c
+++ b/lib/routemap.c
@@ -913,14 +913,15 @@ route_map_finish (void)
}
/* VTY related functions. */
-DEFUN (route_map,
- route_map_cmd,
- "route-map WORD (deny|permit) <1-4294967295>",
- "Create route-map or enter route-map command mode\n"
- "Route map tag\n"
- "Route map denies set operations\n"
- "Route map permits set operations\n"
- "Sequence to insert to/delete from existing route-map entry\n")
+DEFUN_ATTR (route_map,
+ route_map_cmd,
+ "route-map WORD (deny|permit) <1-4294967295>",
+ "Create route-map or enter route-map command mode\n"
+ "Route map tag\n"
+ "Route map denies set operations\n"
+ "Route map permits set operations\n"
+ "Sequence to insert to/delete from existing route-map entry\n",
+ CMD_ATTR_NODE + RMAP_NODE)
{
int permit;
unsigned long seq;
@@ -959,7 +960,7 @@ DEFUN (route_map,
index = route_map_index_get (map, permit, seq);
vty->index = index;
- vty_set_node(vty, RMAP_NODE) ;
+ vty->node = RMAP_NODE ;
return CMD_SUCCESS;
}
diff --git a/lib/routemap.h b/lib/routemap.h
index ac6cb999..05f037f1 100644
--- a/lib/routemap.h
+++ b/lib/routemap.h
@@ -22,7 +22,7 @@
#ifndef _ZEBRA_ROUTEMAP_H
#define _ZEBRA_ROUTEMAP_H
-#include <stdint.h>
+#include "misc.h"
/* Route map's type. */
enum route_map_type
diff --git a/lib/sigevent.c b/lib/sigevent.c
index 18fcffb0..5dfb67cf 100644
--- a/lib/sigevent.c
+++ b/lib/sigevent.c
@@ -19,11 +19,19 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
-#include <sigevent.h>
-#include <log.h>
-
-#ifdef SA_SIGINFO
+#include "zebra.h"
+#include "misc.h"
+#include "sigevent.h"
+#include "log.h"
+#include "vty.h"
+#include "qpnexus.h"
+#include "qpthreads.h"
+
+#include <stdarg.h>
+
+/*------------------------------------------------------------------------------
+ * Want to get some context for core and exit handlers.
+ */
#ifdef HAVE_UCONTEXT_H
#ifdef GNU_LINUX
/* get REG_EIP from ucontext.h */
@@ -33,50 +41,407 @@
#endif /* GNU_LINUX */
#include <ucontext.h>
#endif /* HAVE_UCONTEXT_H */
-#endif /* SA_SIGINFO */
+/*------------------------------------------------------------------------------
+ * Use SA_SIGINFO type handlers throughout
+ */
+#ifndef SA_SIGINFO
+#error Sorry... require SA_SIGINFO
+#endif
+
+typedef void sig_handler(int signo, siginfo_t* info, void* context) ;
+
+/*==============================================================================
+ * Signal handling for Quagga.
+ *
+ * The objectives are:
+ *
+ * 1) to handle the abnormal terminations so that they are logged, and
+ * any available information logged with them.
+ *
+ * 2) to ignore a number of signals that have no significance
+ *
+ * 3) to catch some signals such that they are treated as events in either
+ * the qpthreads or the legacy threads worlds.
+ *
+ * For the qpthreads world, these are all handled in the main thread.
+ *
+ * These may not be any of the "hard" signals or any of the signals
+ * reserved by the library.
+ *
+ * 4) to catch some signals such that they cause an "interrupt" to, e.g.
+ * pselect(), but have no other effect.
+ *
+ * SIGUSR2 is reserved for this purpose for qpthreads world
+ * (aka SIG_INTERRUPT).
+ *
+ * Signal disposition is established early in the morning, and is static from
+ * then on.
+ */
+
+/*==============================================================================
+ * Signal Sets.
+ *
+ * The following signal sets are initialised by signal_init().
+ *
+ * * hard_signals -- signals that mean that the program has misbehaved, and
+ * should exit, now -- e.g. SIGSEGV or SIGILL.
+ *
+ * In the pthreaded world, these signals are handled
+ * (or at least, not blocked) by all threads, and it is
+ * expected that they will be given to the thread which
+ * has failed.
+ *
+ * These signals are not blocked.
+ *
+ * This includes SIGKILL and SIGSTOP, which cannot be
+ * blocked, caught or ignored.
+ *
+ * * core_signals -- signals that by default are terminate + core, so this
+ * more or less the hard_signals, except:
+ *
+ * * excludes SIGKILL and SIGSTOP
+ *
+ * * includes at least one (SIGQUIT) signal that is
+ * not a hard_signal.
+ *
+ * The default action is to catch these signals, log the
+ * event and then abort() -- having turned off the
+ * SIGABRT handler !
+ *
+ * * exit_signals -- signals that by default are terminate, so this
+ * includes, e.g., SIGTERM or SIGINT.
+ *
+ * The default action is to catch these signals, log the
+ * event and then exit().
+ *
+ * * ignore_signals -- signals which, by default, we wish to ignore, so are
+ * set to SIG_IGN.
+ *
+ * * qsig_signals -- signals which are caught, and are later signalled to
+ * quagga_sigevent_process().
+ *
+ * Note that multiple signals may be seen before
+ * quagga_sigevent_process() is run, but they will only
+ * generate one event.
+ *
+ * * qsig_interrupts -- signals which are caught, but otherwise ignored,
+ * so their only function is to interrupt -- in particular
+ * to interrupt pselect().
+ *
+ * * qsig_reserved -- signal which the library reserves for itself.
+ *
+ * In the pthreads world, all signals other than the hard_signals are blocked
+ * by all threads other than the main thread.
+ *
+ * Note that we leave SIGTRAP alone -- so do not disturb debuggger(s).
+ */
+static bool signals_initialised = false ;
+
+static sigset_t hard_signals[1] ;
+static sigset_t core_signals[1] ;
+static sigset_t exit_signals[1] ;
+static sigset_t ignore_signals[1] ;
+static sigset_t qsig_signals[1] ;
+static sigset_t qsig_interrupts[1] ;
+static sigset_t qsig_reserved[1] ;
+
+static void qsig_add(int signo, qsig_event* event) ;
+static int signal_set_set(sigset_t* set, sig_handler* handler, bool required) ;
+static int signal_set(int signo, sig_handler* handler, bool required) ;
-/* master signals descriptor struct */
-struct quagga_sigevent_master_t
+static void __attribute__ ((noreturn))
+ core_handler(int signo, siginfo_t *info, void *context) ;
+static void __attribute__ ((noreturn))
+ exit_handler(int signo, siginfo_t* info, void* context) ;
+static void
+ quagga_signal_handler(int signo, siginfo_t* info, void* context) ;
+static void
+ quagga_interrupt_handler(int signo, siginfo_t* info, void* context) ;
+
+/*------------------------------------------------------------------------------
+ * The following signals are not known to POSIX (2008) or are extensions.
+ */
+#ifndef SIGEMT
+# define SIGEMT 0
+#endif
+#ifndef SIGIO
+# define SIGIO 0
+#endif
+#ifndef SIGIOT
+# define SIGIOT 0
+#endif
+#ifndef SIGPOLL
+# define SIGPOLL 0
+#endif
+#ifndef SIGPROF
+# define SIGPROF 0
+#endif
+#ifndef SIGPWR
+# define SIGPWR 0
+#endif
+#ifndef SIGSTKFLT
+# define SIGSTKFLT 0
+#endif
+#ifndef SIGSYS
+# define SIGSYS 0
+#endif
+#ifndef SIGVTALRM
+# define SIGVTALRM 0
+#endif
+#ifndef SIGWINCH
+# define SIGWINCH 0
+#endif
+#ifndef SIGXRES
+# define SIGXRES 0
+#endif
+
+/*------------------------------------------------------------------------------
+ * The signal handling below assumes that no signal that we have any interest
+ * in will have a signal number greater than the following.
+ *
+ * All the signals we are interested in are initialised in signal_init, so
+ * asserts in the code below will trap, very early on, any signal with a
+ * larger number than this.
+ *
+ * This value is used to place a very outside limit on the signal numbers that
+ * will attempt to deal with.
+ */
+enum { SIG_MAX = 128, SIG_COUNT } ;
+
+/* The value is established at signal_init() time, as the maximum signal
+ * number to consider -- established by sigaddset() on an empty set.
+ */
+int sig_max = 0 ;
+
+/*------------------------------------------------------------------------------
+ * Quagga signals descriptor struct
+ *
+ * Maps real signal numbers to "qso" ordinals... A relatively limited number
+ * of signals need to be fed into the event system, this mechanism minimises
+ * the work required to discover which signal has gone off.
+ *
+ * The qsig actions are held in a small vector in the static sigmaster
+ * structure.
+ */
+enum
+{
+ sig_null = 0, /* no real signal is this */
+ sig_min = 1, /* first real signal is at least this */
+} ;
+typedef uchar sig_num_t ; /* signal number */
+CONFIRM(SIG_MAX <= UCHAR_MAX) ;
+
+enum
{
+ qso_null = 0, /* no qsig uses this */
+ qso_min = 1, /* first qsig is this */
+ qso_max = 10, /* only a handful are really required */
+
+ qso_count, /* number qs ordinals */
+} ;
+typedef uchar qs_ord_t ; /* qs ordinal */
+
+/*------------------------------------------------------------------------------
+ * Static structure for all known qsig_signals.
+ *
+ * Note that the qsig_interrupts are not recorded here !
+ */
+struct quagga_sigevent_master
+{
+ qs_ord_t qsigc ; /* number of signals known */
+ volatile sig_atomic_t caught[qso_count] ;
+
+ qsig_event* event[qso_count] ; /* how to deal with them */
+
+ qs_ord_t map[SIG_COUNT] ; /* real signal to qs ordinal */
+
+#ifdef SIGEVENT_SCHEDULE_THREAD
struct thread *t;
+#endif
- struct quagga_signal_t *signals;
- int sigc;
+} qsig_master ;
- volatile sig_atomic_t caught;
-} sigmaster;
+/*------------------------------------------------------------------------------
+ * Initialise signals.
+ *
+ * 1. construct the signal sets discussed above.
+ *
+ * 2. set default handlers for: core_signals -- core_handler()
+ * exit_signals -- exit_handler()
+ * ignore_signals -- SIG_IGN
+ *
+ * 3. set handlers for signals used by library
+ *
+ * 4. set handlers for signals used by the daemon.
+ *
+ * This is done once, and once only, early in the morning.
+ */
+extern void
+signal_init (struct thread_master *m, int sigc,
+ struct quagga_signal_t signals[])
+{
+ int i ;
+
+ /* Set sig_max by experiment */
+ {
+ sigset_t trial[1] ;
+ int signo ;
+
+ sigemptyset(trial) ;
+ for (signo = sig_min ; signo <= SIG_COUNT ; ++signo)
+ {
+ if (sigaddset(trial, signo) < 0)
+ break ;
+ } ;
+
+ --signo ; /* last acceptable signo */
+ if ((signo < sig_min) || (signo > SIG_MAX))
+ zabort("cannot establish reasonable 'sig_max'") ;
+
+ sig_max = signo ;
+ } ;
+
+ /* Construct the standard sets of signals. */
+ sigmakeset(hard_signals, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGKILL,
+ SIGSEGV, SIGSTOP, SIGXCPU, SIGXFSZ,
+ SIGSYS,
+ SIGEMT, SIGIOT, SIGXRES,
+ -1) ;
+
+ sigcopyset(core_signals, hard_signals) ;
+ sigaddset(core_signals, SIGQUIT) ;
+ sigdelset(core_signals, SIGKILL) ;
+ sigdelset(core_signals, SIGSTOP) ;
+
+ sigmakeset(exit_signals, SIGALRM, SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2,
+ SIGIO, SIGPOLL, SIGPROF, SIGPWR, SIGSTKFLT,
+ SIGVTALRM,
+ -1) ;
+
+ sigmakeset(ignore_signals, SIGCHLD, SIGCONT, SIGPIPE, SIGTSTP, SIGTTIN,
+ SIGTTOU, SIGURG, SIGWINCH,
+ -1) ;
+
+ /* Initialise the sig_master, qsig_signals and qsig_interrupts.
+ *
+ * Reserve all the hard_signals.
+ */
+ memset(&qsig_master, 0, sizeof(qsig_master)) ;
+
+ sigemptyset(qsig_signals) ;
+ sigemptyset(qsig_interrupts) ;
+ sigcopyset(qsig_reserved, hard_signals) ;
-/* Generic signal handler
- * Schedules signal event thread
+ /* The signals used by the library.
+ *
+ * Added to qsig_signals or qsig_interrupts and to qsig_reserved.
+ */
+ qsig_add(SIGCHLD, vty_sigchld) ;
+ qsig_add(SIG_INTERRUPT, NULL) ;
+
+ /* Now collect the daemon's own signals.
+ *
+ * Added to qsig_signals or qsig_interrupts and to qsig_reserved.
+ */
+ for (i = 0 ; i < sigc ; ++i)
+ qsig_add(signals[i].signal, signals[i].handler) ;
+
+ /* In case signals with different names are the same, and to make the
+ * required reservations, let qsig_reserved take precedence over exit_signals
+ * and those take precedence over ignore_signals.
+ */
+ sigsubsets(exit_signals, qsig_reserved) ;
+ sigsubsets(ignore_signals, qsig_reserved) ;
+ sigsubsets(ignore_signals, exit_signals) ;
+
+ /* Also, remove anything from core_signals which is now qsig_signals or
+ * qsig_interrupts (possibly SIGQUIT !).
+ */
+ sigsubsets(core_signals, qsig_signals) ;
+ sigsubsets(core_signals, qsig_interrupts) ;
+
+ /* Install handlers */
+ signal_set_set(core_signals, core_handler, false) ;
+ signal_set_set(exit_signals, exit_handler, false) ;
+ signal_set_set(ignore_signals, NULL, false) ;
+ signal_set_set(qsig_signals, quagga_signal_handler, true) ;
+ signal_set_set(qsig_interrupts, quagga_interrupt_handler, true) ;
+
+ /* If using a timer thread to scan for signal events, start that now.
+ */
+#ifdef SIGEVENT_SCHEDULE_THREAD
+ sig_master.t =
+ thread_add_timer (m, quagga_signal_timer, &sig_master,
+ QUAGGA_SIGNAL_TIMER_INTERVAL);
+#endif /* SIGEVENT_SCHEDULE_THREAD */
+
+ /* Signals are now initialised */
+ signals_initialised = true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the hard_signals set
+ */
+extern const sigset_t*
+signal_get_hard_set(void)
+{
+ assert(signals_initialised) ;
+ return hard_signals ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Add signal to those to be caught either for quagga_sigevent_process() or
+ * to then be dropped (so called interrupt signals).
+ *
+ * Checks that signal is not amongst the reserved signals.
+ *
+ * Add signal to the reserved signals.
*/
static void
-quagga_signal_handler (int signo)
+qsig_add(int signo, qsig_event* event)
{
- int i;
- struct quagga_signal_t *sig;
+ int s ;
- for (i = 0; i < sigmaster.sigc; i++)
+ s = sigismember(qsig_reserved, signo) ;
+ if ((s < 0) || (signo > sig_max))
+ zabort("invalid or unknown signal number") ;
+ if (s > 0)
+ zabort("signal is reserved (or already set)") ;
+
+ sigaddset(qsig_reserved, signo) ;
+
+ if (event == NULL)
+ sigaddset(qsig_interrupts, signo) ;
+ else
{
- sig = &(sigmaster.signals[i]);
+ sigaddset(qsig_signals, signo) ;
- if (sig->signal == signo)
- sig->caught = 1;
- }
+ if (qsig_master.qsigc >= qso_count)
+ zabort("too many signals to be caught") ;
- sigmaster.caught = 1;
-}
+ ++qsig_master.qsigc ;
+
+ qsig_master.map[signo] = qsig_master.qsigc ;
+ qsig_master.event[qsig_master.qsigc] = event ;
+
+ } ;
+} ;
-/* check if signals have been caught and run appropriate handlers
+/*==============================================================================
+ * The event level handling of qsig_signals, delivered via qsig_master.
+ */
+
+/*------------------------------------------------------------------------------
+ * check if signals have been caught and run respective event functions
*
* Returns: 0 => nothing to do
* -1 => failed
* > 0 => done this many signals
*/
-int
+extern int
quagga_sigevent_process (void)
{
- struct quagga_signal_t *sig;
int i;
int done ;
#ifdef SIGEVENT_BLOCK_SIGNALS
@@ -100,21 +465,18 @@ quagga_sigevent_process (void)
#endif /* SIGEVENT_BLOCK_SIGNALS */
done = 0 ;
- if (sigmaster.caught > 0)
+ if (qsig_master.caught[qso_null] != 0)
{
- sigmaster.caught = 0;
- /* must not read or set sigmaster.caught after here,
+ qsig_master.caught[qso_null] = 0;
+ /* must not read or set sigmaster.caught[0] after here,
* race condition with per-sig caught flags if one does
*/
-
- for (i = 0; i < sigmaster.sigc; i++)
+ for (i = 1 ; i <= qsig_master.qsigc ; i++)
{
- sig = &(sigmaster.signals[i]);
-
- if (sig->caught > 0)
+ if (qsig_master.caught[i] != 0)
{
- sig->caught = 0;
- sig->handler ();
+ qsig_master.caught[i] = 0;
+ (qsig_master.event[i])() ;
++done ;
}
}
@@ -128,6 +490,9 @@ quagga_sigevent_process (void)
return done ;
}
+/*------------------------------------------------------------------------------
+ * Optional timer thread to poll for signals
+ */
#ifdef SIGEVENT_SCHEDULE_THREAD
/* timer thread to check signals. Shouldnt be needed */
int
@@ -144,246 +509,411 @@ quagga_signal_timer (struct thread *t)
}
#endif /* SIGEVENT_SCHEDULE_THREAD */
-/* Initialization of signal handles. */
-/* Signal wrapper. */
-static int
-signal_set (int signo)
+/*==============================================================================
+ * The signal handlers.
+ */
+static void * program_counter(void *context) ;
+
+/*------------------------------------------------------------------------------
+ * Terminate + Core
+ */
+static void __attribute__ ((noreturn))
+core_handler(int signo, siginfo_t *info, void *context)
{
- int ret;
- struct sigaction sig;
- struct sigaction osig;
-
- sig.sa_handler = &quagga_signal_handler;
- sigfillset (&sig.sa_mask);
- sig.sa_flags = 0;
- if (signo == SIGALRM) {
-#ifdef SA_INTERRUPT
- sig.sa_flags |= SA_INTERRUPT; /* SunOS */
-#endif
- } else {
-#ifdef SA_RESTART
- sig.sa_flags |= SA_RESTART;
-#endif /* SA_RESTART */
- }
-
- ret = sigaction (signo, &sig, &osig);
- if (ret < 0)
- return ret;
- else
- return 0;
+ zlog_signal(signo, "aborting...", info, program_counter(context)) ;
+ zabort_abort();
}
-#ifdef SA_SIGINFO
+/*------------------------------------------------------------------------------
+ * Terminate
+ */
+static void __attribute__ ((noreturn))
+exit_handler(int signo, siginfo_t* info, void* context)
+{
+ zlog_signal(signo, "exiting...", info, program_counter(context));
+ _exit(128+signo);
+}
+
+/*------------------------------------------------------------------------------
+ * Generic signal handler -- captures signals in the sig_master caught vector.
+ *
+ * quagga_sigevent_process() deals with the caught signals.
+ */
+static void
+quagga_signal_handler(int signo, siginfo_t* info, void* context)
+{
+ qs_ord_t qso ;
+
+ if (sigismember(qsig_signals, signo) <= 0)
+ {
+ zlog_signal(signo, "quagga_signal_handler: unknown or invalid signal",
+ info, program_counter(context)) ;
+ zabort_abort();
+ } ;
+
+ qso = qsig_master.map[signo] ;
+
+ /* Set individual caught flag before composite.
+ *
+ * This works if quagga_signal_handler() and quagga_sigevent_process()
+ * were to run at the same time -- unlikely though that may be.
+ *
+ * If quagga_sigevent_process() sees individual flag before the composite,
+ * that's fine -- worst that happens is will run through a further time
+ * and not find anything.
+ *
+ * If flags were set in the opposite order, could clear the composite
+ * and miss the individual, so lose the signal till the next time the
+ * composite is set.
+ */
+ qsig_master.caught[qso] = 1 ;
+ qsig_master.caught[qso_null] = 1 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Generic signal handler -- where signal is to be caught, and immediately
+ * dropped.
+ */
+static void
+quagga_interrupt_handler(int signo, siginfo_t* info, void* context)
+{
+ if (sigismember(qsig_interrupts, signo) <= 0)
+ {
+ zlog_signal(signo, "quagga_interrupt_handler: unknown or invalid signal",
+ info, program_counter(context)) ;
+ zabort_abort();
+ } ;
+} ;
-/* XXX This function should be enhanced to support more platforms
- (it currently works only on Linux/x86). */
+/*------------------------------------------------------------------------------
+ * Extract program counter from context.
+ *
+ * XXX This function should be enhanced to support more platforms
+ * (it currently works only on Linux/x86).
+ */
static void *
program_counter(void *context)
{
#ifdef HAVE_UCONTEXT_H
-#ifdef GNU_LINUX
-#ifdef REG_EIP
+# ifdef GNU_LINUX
+# ifdef REG_EIP
if (context)
return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_EIP]);
-#endif /* REG_EIP */
-#endif /* GNU_LINUX */
+# endif /* REG_EIP */
+# ifdef REG_RIP
+ if (context)
+ return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_RIP]);
+# endif /* REG_RIP */
+# endif /* GNU_LINUX */
#endif /* HAVE_UCONTEXT_H */
return NULL;
-}
+} ;
-#endif /* SA_SIGINFO */
+/*==============================================================================
+ * Signal clearing for abort() and fork()/vfork().
+ */
-static void __attribute__ ((noreturn))
-exit_handler(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *context
-#endif
- )
+/*------------------------------------------------------------------------------
+ * Set default sigaction for given signo
+ */
+static int
+sigaction_set_default(int signo)
{
- zlog_signal(signo, "exiting..."
-#ifdef SA_SIGINFO
- , siginfo, program_counter(context)
-#endif
- );
- _exit(128+signo);
-}
+ struct sigaction act[1] ;
-static void __attribute__ ((noreturn))
-core_handler(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *context
-#endif
- )
+ memset(act, 0, sizeof(act)) ; /* inter alia, clear sa_flags */
+ act->sa_handler = SIG_DFL ; /* return to default state */
+ sigemptyset(&act->sa_mask) ; /* no extra masking */
+
+ return sigaction(signo, act, NULL) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * When finally aborting, need to turn off the handling of SIGABRT, and need
+ * to make sure that the signal is not blocked.
+ */
+extern void
+quagga_sigabrt_no_trap(void)
{
- zlog_signal(signo, "aborting..."
-#ifdef SA_SIGINFO
- , siginfo, program_counter(context)
-#endif
- );
- zabort_abort();
-}
+ sigset_t set[1] ;
-/* For the signals known to Quagga, and which are in their default state,
- * set a Quagga default handler.
+ sigaction_set_default(SIGABRT) ;
+
+ sigemptyset(set) ;
+ sigaddset(set, SIGABRT) ;
+ qpt_thread_sigmask(SIG_UNBLOCK, set, NULL) ;
+ /* sigprocmask() if !qpthreads_enabled */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Having forked, make sure that all signals are in default state and that
+ * no signals are blocked.
+ *
+ * Expects not to fail.
*/
-static void
-trap_default_signals(void)
+extern void
+quagga_signal_reset(void)
{
- static const int core_signals[] = {
- SIGQUIT,
- SIGILL,
- SIGABRT,
-#ifdef SIGEMT
- SIGEMT,
-#endif
-#ifdef SIGIOT
- SIGIOT,
-#endif
- SIGFPE,
- SIGBUS,
- SIGSEGV,
-#ifdef SIGSYS
- SIGSYS,
-#endif
-#ifdef SIGXCPU
- SIGXCPU,
-#endif
-#ifdef SIGXFSZ
- SIGXFSZ,
-#endif
- };
-
- static const int exit_signals[] = {
- SIGHUP,
- SIGINT,
- SIGALRM,
- SIGTERM,
- SIGUSR1,
- SIGUSR2,
-#ifdef SIGPOLL
- SIGPOLL,
-#endif
-#ifdef SIGVTALRM
- SIGVTALRM,
-#endif
-#ifdef SIGSTKFLT
- SIGSTKFLT,
-#endif
- };
-
- static const int ignore_signals[] = {
- SIGPIPE,
- };
-
- static const struct {
- const int *sigs;
- u_int nsigs;
- void (*handler)(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *info, void *context
-#endif
- );
- } sigmap[] = {
- { core_signals, sizeof(core_signals)/sizeof(core_signals[0]), core_handler},
- { exit_signals, sizeof(exit_signals)/sizeof(exit_signals[0]), exit_handler},
- { ignore_signals, sizeof(ignore_signals)/sizeof(ignore_signals[0]), NULL},
- };
- u_int i;
-
- for (i = 0; i < sizeof(sigmap)/sizeof(sigmap[0]); i++)
+ sigset_t set[1] ;
+ int signo ;
+
+ /* Before changing the handling of any signals, mask everything and
+ * clear out any pending signals.
+ */
+ sigfillset(set) ;
+ sigprocmask(SIG_SETMASK, set, NULL) ;
+
+ while (1)
{
- u_int j;
+ sigpending(set) ;
+ if (sighasmember(set) == 0)
+ break ;
+ sigwait(set, &signo) ;
+ } ;
+
+ /* Set all signals to default handler. */
+ for (signo = sig_min ; signo <= sig_max ; ++signo)
+ {
+ if ((signo == SIGKILL) || (signo == SIGSTOP))
+ continue ;
- for (j = 0; j < sigmap[i].nsigs; j++)
- {
- struct sigaction oact;
- if (sigaction(sigmap[i].sigs[j], NULL, &oact) < 0)
- zlog_warn("Unable to get signal handler for signal %d: %s",
- sigmap[i].sigs[j], errtoa(errno, 0).str);
- else {
-#ifdef SA_SIGINFO
- if (oact.sa_flags & SA_SIGINFO)
- continue ; /* Don't set again */
+ sigaction_set_default(signo) ;
+ } ;
+
+ /* Unmask everything */
+ sigemptyset(set) ;
+ sigprocmask(SIG_SETMASK, set, NULL) ;
+} ;
+
+/*==============================================================================
+ * Functions to install signal handlers.
+ */
+
+/*------------------------------------------------------------------------------
+ * Set given handler for given set of signals. NULL handler => SIG_IGN.
+ *
+ * Returns: < 0 => failed -- value is - failing signo !
+ */
+static int
+signal_set_set(sigset_t* set, sig_handler* handler, bool required)
+{
+ int signo ;
+
+ signo = 0 ;
+ for (signo = sig_min ; signo <= sig_max ; ++signo)
+ {
+ int s ;
+ s = sigismember(set, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ if (signal_set(signo, handler, required) < 0)
+ return -signo ;
+ } ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given handler for given signal. NULL handler => SIG_IGN.
+ *
+ * Returns: < 0 => failed
+ */
+#ifndef SA_INTERRUPT
+# define SA_INTERRUPT 0
#endif
- if (oact.sa_handler != SIG_DFL)
- continue ; /* Don't set again */
- }
- if ( (sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) &&
- (oact.sa_handler == SIG_DFL) )
- {
- struct sigaction act;
- sigfillset (&act.sa_mask);
- if (sigmap[i].handler == NULL)
- {
- act.sa_handler = SIG_IGN;
- act.sa_flags = 0;
- }
- else
- {
-#ifdef SA_SIGINFO
- /* Request extra arguments to signal handler. */
- act.sa_sigaction = sigmap[i].handler;
- act.sa_flags = SA_SIGINFO;
-#else
- act.sa_handler = sigmap[i].handler;
- act.sa_flags = 0;
+#ifndef SA_RESTART
+# define SA_RESTART 0
#endif
- }
- if (sigaction(sigmap[i].sigs[j], &act, NULL) < 0)
- zlog_warn("Unable to set signal handler for signal %d: %s",
- sigmap[i].sigs[j], errtoa(errno, 0).str);
- }
- }
+static int
+signal_set(int signo, sig_handler* handler, bool required)
+{
+ struct sigaction act[1] ;
+
+ /* If a signal handler is already set for the given signal then we leave
+ * that as it is -- unless this is one of the "required" signals, used by
+ * Quagga.
+ */
+ sigaction(signo, NULL, act) ;
+
+ if ((act->sa_handler != SIG_DFL) && (act->sa_handler != SIG_IGN))
+ {
+ if (!required)
+ return 0 ;
+ } ;
+
+ /* Set our handler */
+
+ memset(act, 0, sizeof(struct sigaction)) ;
+
+ if (handler == NULL)
+ {
+ act->sa_handler = SIG_IGN ;
+ act->sa_flags = 0 ;
}
-}
+ else
+ {
+ act->sa_sigaction = handler ;
+ act->sa_flags = SA_SIGINFO ;
+ } ;
-void
-signal_init (struct thread_master *m, int sigc,
- struct quagga_signal_t signals[])
+ sigfillset (&act->sa_mask) ; /* mask everything */
+
+ if (signo == SIGALRM)
+ act->sa_flags |= SA_INTERRUPT ; /* want SIGALRM to interrupt */
+ else
+ act->sa_flags |= SA_RESTART ; /* all others want restart */
+
+ act->sa_flags |= SA_NOCLDSTOP ;
+
+ return sigaction (signo, act, NULL) ;
+} ;
+
+/*==============================================================================
+ * Additional signal set support.
+ */
+
+/*------------------------------------------------------------------------------
+ * Make a signal set.
+ *
+ * Takes variable list of signal number arguments:
+ *
+ * * ignores zeros
+ *
+ * * stops on first value < 0
+ */
+extern void
+sigmakeset(sigset_t* set, ...)
{
+ va_list va ;
+ int signo ;
- int i = 0;
- struct quagga_signal_t *sig;
+ va_start(va, set) ;
- /* First establish some default handlers that can be overridden by
- the application. */
- trap_default_signals();
+ sigemptyset(set) ;
+ while ((signo = va_arg(va, int)) >= 0)
+ {
+ if (signo != 0)
+ if (sigaddset(set, signo) < 0)
+ zabort("invalid signal number") ;
+ } ;
- while (i < sigc)
+ va_end(va) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Copy a signal set.
+ */
+extern void
+sigcopyset(sigset_t* dst, const sigset_t* src)
+{
+ *dst = *src ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Add signal set 'b' into set 'a'.
+ */
+extern void
+sigaddsets(sigset_t* a, const sigset_t* b)
+{
+ int signo ;
+
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
{
- sig = &signals[i];
- if ( signal_set (sig->signal) < 0 )
- exit (-1);
- i++;
- }
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigaddset(a, signo) ;
+ } ;
+} ;
- sigmaster.sigc = sigc;
- sigmaster.signals = signals;
+/*------------------------------------------------------------------------------
+ * Subtract signal set 'b' from set 'a'.
+ */
+extern void
+sigsubsets(sigset_t* a, const sigset_t* b)
+{
+ int signo ;
-#ifdef SIGEVENT_SCHEDULE_THREAD
- sigmaster.t =
- thread_add_timer (m, quagga_signal_timer, &sigmaster,
- QUAGGA_SIGNAL_TIMER_INTERVAL);
-#endif /* SIGEVENT_SCHEDULE_THREAD */
-}
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigdelset(a, signo) ;
+ } ;
+} ;
-/* turn off trap for SIGABRT ! */
-extern void quagga_sigabrt_no_trap(void)
+/*------------------------------------------------------------------------------
+ * Make set 'a' be the inverse of set 'b'
+ */
+extern void
+siginvset(sigset_t* a, const sigset_t* b)
{
- struct sigaction new_act ;
- sigset_t set ;
+ int signo ;
- sigfillset(&set) ;
+ sigfillset(a) ;
- new_act.sa_handler = SIG_DFL ;
- new_act.sa_mask = set ;
- new_act.sa_flags = 0 ;
- sigaction(SIGABRT, &new_act, NULL) ;
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigdelset(a, signo) ;
+ } ;
+} ;
- sigemptyset(&set) ;
- sigaddset(&set, SIGABRT) ;
- sigprocmask(SIG_UNBLOCK, &set, NULL) ;
+/*------------------------------------------------------------------------------
+ * See if there is any intersection between two sets.
+ *
+ * Returns: first signo of intersection -- may be more !
+ * 0 <=> none
+ */
+extern int
+sigincommon(const sigset_t* a, const sigset_t* b)
+{
+ int signo ;
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(a, signo) ;
+ if (s < 0)
+ return 0 ;
+ if ((s > 0) && (sigismember(b, signo) > 0))
+ return signo ;
+ } ;
+
+ return 0 ;
} ;
+/*------------------------------------------------------------------------------
+ * See if there is anything in the given set.
+ *
+ * Returns: first signo found -- may be more !
+ * 0 <=> none
+ */
+extern int
+sighasmember(const sigset_t* a)
+{
+ int signo ;
+
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(a, signo) ;
+ if (s < 0)
+ return 0 ;
+ if (s > 0)
+ return signo ;
+ } ;
+
+ return 0 ;
+} ;
diff --git a/lib/sigevent.h b/lib/sigevent.h
index 57486bc2..bc4ef945 100644
--- a/lib/sigevent.h
+++ b/lib/sigevent.h
@@ -25,32 +25,34 @@
#define _QUAGGA_SIGNAL_H
#include <thread.h>
+#include <signal.h>
#define QUAGGA_SIGNAL_TIMER_INTERVAL 2L
#define Q_SIGC(sig) (sizeof(sig)/sizeof(sig[0]))
+typedef void qsig_event(void) ;
+
struct quagga_signal_t
{
- int signal; /* signal number */
- void (*handler) (void); /* handler to call */
-
- volatile sig_atomic_t caught; /* private member */
-};
-
-/* initialise sigevent system
- * takes:
- * - pointer to valid struct thread_master
- * - number of elements in passed in signals array
- * - array of quagga_signal_t's describing signals to handle
- * and handlers to use for each signal
- */
+ int signal ; /* signal number */
+ qsig_event* handler ; /* event function */
+} ;
+
extern void signal_init (struct thread_master *m, int sigc,
- struct quagga_signal_t *signals);
+ struct quagga_signal_t *signals);
-/* check whether there are signals to handle, process any found */
extern int quagga_sigevent_process (void);
-/* turn off trap for SIGABRT ! */
+extern const sigset_t* signal_get_hard_set(void) ;
extern void quagga_sigabrt_no_trap(void) ;
+extern void quagga_signal_reset(void) ;
+
+extern void sigmakeset(sigset_t* set, ...) ;
+extern void sigcopyset(sigset_t* dst, const sigset_t* src) ;
+extern void sigaddsets(sigset_t* a, const sigset_t* b) ;
+extern void sigsubsets(sigset_t* a, const sigset_t* b) ;
+extern void siginvset(sigset_t* a, const sigset_t* b) ;
+extern int sigincommon(const sigset_t* a, const sigset_t* b) ;
+extern int sighasmember(const sigset_t* a) ;
#endif /* _QUAGGA_SIGNAL_H */
diff --git a/lib/sockopt.c b/lib/sockopt.c
index 8dfd0f16..c7a8db5f 100644
--- a/lib/sockopt.c
+++ b/lib/sockopt.c
@@ -232,7 +232,7 @@ setsockopt_ttl (int sock_fd, int ttl)
* < 0 => failed or not supported -- see errno
* EOPNOTSUPP if not supported
*
- * Logs a LOG_WARNING message if fails (and is supported).
+ * Logs a LOG_WARNING message if fails (including not supported).
*
* NB: for AF_INET6 where have: IN6_IS_ADDR_V4MAPPED, there is the question
* of whether to use IPPROTO_IP/IP_TTL or IPPROTO_IPV6/IPV6_UNICAST_HOPS.
diff --git a/lib/sockunion.c b/lib/sockunion.c
index b34d7047..9084c27e 100644
--- a/lib/sockunion.c
+++ b/lib/sockunion.c
@@ -20,6 +20,7 @@
*/
#include <zebra.h>
+#include "misc.h"
#include "prefix.h"
#include "vty.h"
diff --git a/lib/stream.c b/lib/stream.c
index 1fce7103..0183ce78 100644
--- a/lib/stream.c
+++ b/lib/stream.c
@@ -20,8 +20,8 @@
* 02111-1307, USA.
*/
+#include "zebra.h"
#include <stddef.h>
-#include <zebra.h>
#include "stream.h"
#include "memory.h"
diff --git a/lib/symtab.c b/lib/symtab.c
index 57a49396..cac52962 100644
--- a/lib/symtab.c
+++ b/lib/symtab.c
@@ -212,11 +212,11 @@ symbol_base(symbol_table table, u_int32_t hash)
* any existing chain base array, symbols and symbol references are simply
* discarded -- which will leak memory and is probably a mistake.
*/
-symbol_table
+extern symbol_table
symbol_table_init_new(symbol_table table,
- void* parent,
- unsigned int base_count,
- unsigned int density,
+ void* parent,
+ uint base_count,
+ uint density,
symbol_hash_function* hash_function,
symbol_call_back_function* value_call_back)
{
@@ -240,21 +240,21 @@ symbol_table_init_new(symbol_table table,
} ;
/* Set "parent" of symbol table. */
-void
+extern void
symbol_table_set_parent(symbol_table table, void* parent)
{
table->parent = parent ;
} ;
/* Get "parent" of symbol table. */
-void*
+extern void*
symbol_table_get_parent(symbol_table table)
{
return table->parent ;
} ;
/* Set the value_call_back */
-void
+extern void
symbol_table_set_value_call_back(symbol_table table,
symbol_call_back_function* value_call_back)
{
@@ -311,8 +311,8 @@ symbol_table_setup(symbol_table table)
*
* NB: must only be done when the table is empty -- see assertion !
*/
-symbol_table
-symbol_table_reset(symbol_table table, int free_structure)
+extern symbol_table
+symbol_table_reset(symbol_table table, free_keep_b free_structure)
{
if (table== NULL)
return NULL ; /* allow for already freed table */
@@ -322,6 +322,8 @@ symbol_table_reset(symbol_table table, int free_structure)
if (table->bases)
XFREE(MTYPE_SYMBOL_BASES, table->bases);
+ confirm(free_it == true) ;
+
if (free_structure)
{
XFREE(MTYPE_VECTOR, table) ;
@@ -416,8 +418,8 @@ symbol_free(symbol sym)
* NB: it is the caller's responsibility to unset all references and release
* any that need to be released -- either before or after this operation.
*/
-void*
-symbol_table_ream(symbol_table table, int free_structure)
+extern void*
+symbol_table_ream(symbol_table table, free_keep_b free_structure)
{
void* value ;
symbol sym ;
@@ -470,8 +472,8 @@ symbol_table_ream(symbol_table table, int free_structure)
* value and no references. Where that distinction matters, it is
* necessary to do an extra lookup.
*/
-symbol
-symbol_lookup(symbol_table table, const void* name, int add)
+extern symbol
+symbol_lookup(symbol_table table, const void* name, add_b add)
{
struct symbol* this ;
struct symbol** base ;
@@ -485,7 +487,7 @@ symbol_lookup(symbol_table table, const void* name, int add)
base = symbol_base(table, hash.hash) ;
this = *base ;
- while (this)
+ while (this != NULL)
{
if ((this->hash == hash.hash)
&& (this->name_len == hash.name_len)
@@ -554,7 +556,7 @@ symbol_lookup(symbol_table table, const void* name, int add)
* NB: orphan symbols can be deleted. The effect is to free the symbol if
* possible.
*/
-void*
+extern void*
symbol_delete(symbol sym)
{
void* old_value = symbol_unset_value(sym) ;
@@ -578,7 +580,7 @@ symbol_delete(symbol sym)
static u_int32_t crc_table[] ;
/* Standard symbol string hash function. */
-void
+extern void
symbol_hash_string(symbol_hash p_hash, const char* string) {
u_int32_t h = 0 ;
const char* p = string ;
@@ -595,7 +597,7 @@ symbol_hash_string(symbol_hash p_hash, const char* string) {
} ;
/* Standard symbol byte vector hash function. */
-void
+extern void
symbol_hash_bytes(symbol_hash p_hash, const void* bytes, size_t len) {
assert(len < 0xFFFF) ;
@@ -670,8 +672,8 @@ symbol_extend_bases(symbol_table table)
/* Zeroise the reference count.*/
-symbol
-symbol_zero_ref(symbol sym, int force)
+Private symbol
+symbol_zero_ref(symbol sym, bool force)
{
assert((sym->ref_count == 1) || force) ;
@@ -807,7 +809,7 @@ symbol_ref_is_bookmark(symbol_ref ref)
} ;
/* Start walk of symbol references */
-void
+extern void
symbol_ref_walk_start(symbol sym, symbol_ref walk)
{
symbol_init_ref(walk) ; /* keeping things tidy */
@@ -817,7 +819,7 @@ symbol_ref_walk_start(symbol sym, symbol_ref walk)
} ;
/* Step walk and return the next reference (if any). */
-symbol_ref
+extern symbol_ref
symbol_ref_walk_step(symbol_ref walk)
{
symbol_ref next_ref ;
@@ -850,7 +852,7 @@ symbol_ref_walk_step(symbol_ref walk)
* NB: if the symbol is not defined and has no references or bookmarks it
* will now be freed.
*/
-void
+extern void
symbol_ref_walk_end(symbol_ref walk)
{
assert(symbol_ref_is_bookmark(walk)) ; /* must be a bookmark ! */
@@ -876,7 +878,7 @@ symbol_ref_walk_end(symbol_ref walk)
*
* Returns previous value -- which may require releasing.
*/
-void*
+extern void*
symbol_set_value(symbol sym, void* new_value)
{
void* old_value ;
@@ -912,7 +914,7 @@ symbol_set_value(symbol sym, void* new_value)
*/
/* Initialise symbol reference -- allocate if required. */
-symbol_ref
+extern symbol_ref
symbol_init_ref(symbol_ref ref)
{
if (ref == NULL)
@@ -936,7 +938,7 @@ symbol_init_ref(symbol_ref ref)
*
* if reference is not allocated, the parent and tag are unchanged.
*/
-symbol_ref
+extern symbol_ref
symbol_set_ref(symbol_ref ref, struct symbol* sym)
{
if (ref != NULL)
@@ -944,7 +946,7 @@ symbol_set_ref(symbol_ref ref, struct symbol* sym)
if (ref->sym == sym)
return ref ; /* Nothing more to do if already set to given value */
if (ref->sym != NULL)
- symbol_unset_ref_keep(ref) ;
+ symbol_unset_ref(ref, keep_it) ;
}
else
ref = symbol_init_ref(NULL) ;
@@ -967,8 +969,8 @@ symbol_set_ref(symbol_ref ref, struct symbol* sym)
*
* NB: copes if the reference is already unset, of course.
*/
-symbol_ref
-symbol_unset_ref(symbol_ref ref, int free_ref_structure)
+extern symbol_ref
+symbol_unset_ref(symbol_ref ref, free_keep_b free_ref_structure)
{
if (ref == NULL) return ref ;
@@ -979,6 +981,7 @@ symbol_unset_ref(symbol_ref ref, int free_ref_structure)
ref->sym = NULL ;
} ;
+ confirm(free_it == true) ;
if (free_ref_structure)
XFREE(MTYPE_SYMBOL_REF, ref) ; /* ref is set to NULL */
@@ -1008,7 +1011,7 @@ symbol_unset_ref(symbol_ref ref, int free_ref_structure)
* progress -- up to and including deleting it. Any other changes to
* the table must NOT be attempted.
*/
-void
+extern void
symbol_walk_start(symbol_table table, struct symbol_walker* walker)
{
walker->next = NULL ;
@@ -1016,7 +1019,7 @@ symbol_walk_start(symbol_table table, struct symbol_walker* walker)
walker->base_count = table->base_count ;
} ;
-symbol
+extern symbol
symbol_walk_next(struct symbol_walker* walker)
{
symbol this = walker->next ;
@@ -1049,9 +1052,9 @@ symbol_walk_next(struct symbol_walker* walker)
* caller's responsibility to avoid deleting any symbol whose pointer
* in the vector they expect to rely on !
*/
-vector
+extern vector
symbol_table_extract(symbol_table table,
- symbol_select_cmp* selector, const void* p_val, int most,
+ symbol_select_cmp* selector, const void* p_val, bool most,
symbol_sort_cmp* sort)
{
vector extract ;
@@ -1093,7 +1096,7 @@ symbol_table_extract(symbol_table table,
*
* This comparison treats substrings of digits as numbers, so "a10" is > "a1".
*/
-int
+extern int
symbol_mixed_name_cmp(const symbol* p_a,
const symbol* p_b)
{
diff --git a/lib/symtab.h b/lib/symtab.h
index a8a6e622..008b7853 100644
--- a/lib/symtab.h
+++ b/lib/symtab.h
@@ -22,14 +22,8 @@
#ifndef _ZEBRA_SYMTAB_H
#define _ZEBRA_SYMTAB_H
+#include "misc.h"
#include "vector.h"
-#include <stddef.h>
-#include <stdint.h>
-
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
/* Maximum number of symbol table bases -- something has gone tragically wrong
* if we hit this. Assume can multiply this by 2 and get valid size_t result.
@@ -145,21 +139,18 @@ struct symbol_walker
/* Symbol Table Operations. */
-extern symbol_table
-symbol_table_init_new(symbol_table table,
- void* parent,
- unsigned bases,
- unsigned density,
- symbol_hash_function* hash_function,
- symbol_call_back_function* value_call_back) ;
-void symbol_table_set_parent(symbol_table table, void* parent) ;
-void* symbol_table_get_parent(symbol_table table) ;
-void* symbol_table_ream(symbol_table table, int free_structure) ;
-#define symbol_table_ream_free(table) symbol_table_ream(table, 1)
-#define symbol_table_ream_keep(table) symbol_table_ream(table, 0)
-symbol_table symbol_table_reset(symbol_table table, int free_structure) ;
-#define symbol_table_reset_free(table) symbol_table_reset(table, 1)
-#define symbol_table_reset_keep(table) symbol_table_reset(table, 0)
+extern symbol_table symbol_table_init_new(
+ symbol_table table,
+ void* parent,
+ uint bases,
+ uint density,
+ symbol_hash_function* hash_function,
+ symbol_call_back_function* value_call_back) ;
+extern void symbol_table_set_parent(symbol_table table, void* parent) ;
+extern void* symbol_table_get_parent(symbol_table table) ;
+extern void* symbol_table_ream(symbol_table table, free_keep_b free_structure) ;
+extern symbol_table symbol_table_reset(symbol_table table,
+ free_keep_b free_structure) ;
extern void symbol_hash_string(struct symbol_hash* p_hash, const char* string) ;
extern void symbol_hash_bytes(struct symbol_hash* p_hash, const void* bytes,
@@ -169,28 +160,31 @@ extern void symbol_table_set_value_call_back(symbol_table table,
extern void symbol_table_free(symbol_table) ;
-extern symbol symbol_lookup(symbol_table table, const void* name, int add) ;
-
-#define symbol_seek(table, name) symbol_lookup(table, name, 0)
-#define symbol_find(table, name) symbol_lookup(table, name, 1)
+extern symbol symbol_lookup(symbol_table table, const void* name, add_b add) ;
extern void* symbol_delete(symbol sym) ;
extern void* symbol_set_value(symbol sym, void* new_value) ;
-#define symbol_unset_value(sym) symbol_set_value(sym, NULL)
+Inline void*
+symbol_unset_value(symbol sym)
+{
+ return symbol_set_value(sym, NULL) ;
+} ;
-void symbol_ref_walk_start(symbol sym, symbol_ref walk) ;
-symbol_ref symbol_ref_walk_step(symbol_ref walk) ;
-void symbol_ref_walk_end(symbol_ref walk) ;
+extern void symbol_ref_walk_start(symbol sym, symbol_ref walk) ;
+extern symbol_ref symbol_ref_walk_step(symbol_ref walk) ;
+extern void symbol_ref_walk_end(symbol_ref walk) ;
-void symbol_walk_start(symbol_table table, struct symbol_walker* walker) ;
-symbol symbol_walk_next(struct symbol_walker* walker) ;
+extern void symbol_walk_start(symbol_table table, struct symbol_walker* walker);
+extern symbol symbol_walk_next(struct symbol_walker* walker) ;
typedef int symbol_select_cmp(const symbol, const void*) ;
typedef int symbol_sort_cmp(const symbol*, const symbol*) ;
-vector symbol_table_extract(symbol_table table,
- symbol_select_cmp* select, const void* p_value,
- int most, symbol_sort_cmp* sort) ;
+extern vector symbol_table_extract(symbol_table table,
+ symbol_select_cmp* select,
+ const void* p_value,
+ bool most,
+ symbol_sort_cmp* sort) ;
extern symbol_sort_cmp symbol_mixed_name_cmp ;
@@ -226,29 +220,22 @@ symbol_inc_ref(symbol sym)
return sym ;
} ;
-extern symbol symbol_zero_ref(symbol sym, int force) ;
+Private symbol symbol_zero_ref(symbol sym, bool force) ;
Inline symbol
symbol_dec_ref(symbol sym)
{
if (sym->ref_count <= 1)
- return symbol_zero_ref(sym, 0) ;
+ return symbol_zero_ref(sym, false) ;
--sym->ref_count ;
return sym ;
} ;
-extern symbol_ref
-symbol_init_ref(symbol_ref ref) ;
-
-extern symbol_ref
-symbol_set_ref(symbol_ref ref, symbol sym) ;
-
-extern symbol_ref
-symbol_unset_ref(symbol_ref ref, int free_ref_structure) ;
-
-#define symbol_unset_ref_free(ref) symbol_unset_ref(ref, 1) ;
-#define symbol_unset_ref_keep(ref) symbol_unset_ref(ref, 0) ;
+extern symbol_ref symbol_init_ref(symbol_ref ref) ;
+extern symbol_ref symbol_set_ref(symbol_ref ref, symbol sym) ;
+extern symbol_ref symbol_unset_ref(symbol_ref ref,
+ free_keep_b free_ref_structure) ;
/* Access functions -- argument is address of symbol_ref. */
/* These cope if address of symbol_ref is null, or reference is undefined. */
diff --git a/lib/temp.c b/lib/temp.c
new file mode 100644
index 00000000..759c2bb3
--- /dev/null
+++ b/lib/temp.c
@@ -0,0 +1,1349 @@
+/* Generic vector interface routine -- functions
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * 24-Nov-2009 -- extended to add a number of new operations on vectors.
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#include "vector.h"
+#include "memory.h"
+
+/* Vectors are implemented as a structure which points to an array of pointers
+ * to vector items. That array -- the body of the vector -- can change size,
+ * and therefore may move around in memory.
+ *
+ * The vector structure may be statically allocated, embedded in another
+ * structure, or allocated dynamically. In any case the vector operations
+ * require the address of the vector structure -- see typedef for vector.
+ *
+ * Vector items are accessed by index, fetching and storing pointers to
+ * those items. Can also push and pop items.
+ *
+ * A vector has a known (logical) end position. Everything beyond the end is
+ * defined to be NULL. When a vector is initialised it is set empty.
+ *
+ * At any given moment the vector body has a limit on the number of items it
+ * can accommodate (the physical end).
+ *
+ * A vector will grow to accommodate what is put into it. Adding items beyond
+ * the (logical) end moves it. Adding items beyond the (physical) limit causes
+ * the body to be extended to suit, and to leave some spare space for future
+ * expansion.
+ *
+ * While the vector is small (see VECTOR_LIMIT_DOUBLE_MAX) the body will grow by
+ * doubling in size. When it is larger, it grows to be multiples of
+ * VECTOR_LIMIT_DOUBLE_MAX.
+ *
+ * Deleting items reduces the (logical) end position, but does NOT release
+ * memory -- the (physical) limit is not changed.
+ *
+ * To release memory: vector_chop will release everything beyond the current
+ * end; vector_decant will create a new body of exactly the current size,
+ * releasing the old body; vector_discard will release everything beyond a
+ * given position.
+ *
+ * NB: you can set a vector item to be NULL. If you set a vector item beyond
+ * the current end, NULL items are inserted in the vector.
+ *
+ * NB: when setting a vector item it is the caller's responsibility to
+ * deallocate any pre-existing value of the item.
+ *
+ * NB: when deleting items it is also the caller's responsibility to deallocate
+ * any values that require it.
+ *
+ * Implementation Notes
+ *
+ * Everything beyond the (logical) end is implicitly NULL.
+ *
+ * Actual memory between (logical) end and (physical) limit is UNDEFINED. So
+ * when advancing the end some care has to be taken to ensure that any new
+ * items in the vector are either set to something or cleared to NULL.
+ *
+ * It would have been possible to ensure that everything between end and limit
+ * is cleared to NULL, but that is more work -- in particular it creates work
+ * when it is not always required.
+ */
+
+#define P_ITEMS_SIZE(n) SIZE(p_vector_item, n)
+
+/*==============================================================================
+ * Initialisation, allocation, reset etc.
+ */
+
+/*------------------------------------------------------------------------------
+ * Initialise a brand new vector, setting it empty.
+ *
+ * Allocates vector structure if none given -- that is, if v == NULL.
+ *
+ * If size is given as zero, no body is allocated, otherwise body of exactly
+ * the required size is allocated.
+ *
+ * NB: discards any existing vector body -- so it is the caller's responsibility
+ * to release any existing body, and any items in that body.
+ */
+extern vector
+vector_init_new(vector v, vector_length_t limit)
+{
+ if (v == NULL)
+ v = XCALLOC(MTYPE_VECTOR, sizeof(struct vector)) ;
+ else
+ memset(v, 0, sizeof(struct vector)) ;
+
+ if (limit != 0)
+ {
+ v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(limit)) ;
+ v->limit = limit ;
+ } ;
+
+ return v ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Initialize vector : allocate memory and return vector.
+ * allocates body with at least 1 entry.
+ *
+ * This is a "legacy" function.
+ */
+extern vector
+vector_init (vector_length_t limit)
+{
+ return vector_init_new(NULL, limit ? limit : 1) ; /* at least 1 entry */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Basic: free the vector body and the vector structure.
+ *
+ * NB: it is the caller's responsibility to release any vector item values
+ * *before* doing this.
+ */
+void
+vector_free (vector v)
+{
+ XFREE (MTYPE_VECTOR_BODY, v->p_items);
+ XFREE (MTYPE_VECTOR, v);
+} ;
+
+/*------------------------------------------------------------------------------
+ * Re-initialise vector (or create new one), setting it empty.
+ *
+ * Allocates vector structure if none given -- that is, if v == NULL.
+ *
+ * If size is given as zero, no body is allocated, but any existing body is
+ * retained. (vector_reset() will discard body.)
+ *
+ * Otherwise ensures existing body is at least the required size, or a body
+ * of exactly the required size is allocated.
+ *
+ * NB: when re-initialising an existing vector it is the caller's responsibility
+ * to release any vector item values *before* doing this.
+ * */
+extern vector
+vector_re_init(vector v, vector_length_t)
+{
+ if ((v == NULL) || (v->p_items == NULL))
+ return vector_init_new(v, limit) ;
+
+ v->end = 0 ;
+
+ if (v->limit < size)
+ {
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items,
+ P_ITEMS_SIZE(limit)) ;
+ v->limit = limit ;
+ } ;
+
+ return v ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free the vector body, and (if required) free the vector structure.
+ *
+ * Return NULL if releases vector, otherwise the address of the vector.
+ *
+ * NB: it is the caller's responsibility to release any vector item values
+ * *before* doing this.
+ */
+vector
+vector_reset(vector v, free_keep_b free_structure)
+{
+ if (v == NULL)
+ return NULL ; /* allow for already freed vector */
+
+ if (v->p_items != NULL)
+ XFREE(MTYPE_VECTOR_BODY, v->p_items) ;
+
+ if (free_structure)
+ {
+ confirm(free_it == true) ;
+ XFREE(MTYPE_VECTOR, v) ;
+ return NULL ;
+ }
+ else
+ return vector_init_new(v, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set vector length to be (at least) the given fixed length.
+ *
+ * There must be a vector.
+ *
+ * Does nothing if the vector is already as long or longer than the given
+ * length.
+ *
+ * If the body is not big enough for the new length, allocates or extends to
+ * exactly the new length. Otherwise, leaves body as it is.
+ *
+ * Appends NULLs as required to extend to the required length.
+ *
+ * Note that the existing contents of the vector are preserved in all cases.
+ */
+Private void
+vector_set_new_min_length(vector v, vector_length_t len)
+{
+ assert (v != NULL) ;
+
+ if (len > v->limit)
+ {
+ if (v->p_items == NULL)
+ v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ;
+ else
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items,
+ P_ITEMS_SIZE(len)) ;
+ v->limit = len ;
+ } ;
+
+ if (v->end < len)
+ vector_extend(v, len) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pop item from vector, stepping past any NULLs.
+ * If vector is empty, free the body and, if required, the vector structure.
+ *
+ * Useful for emptying out and discarding a vector:
+ *
+ * while ((p_v = vector_ream_out(v, 1)))
+ * ... do what's required to release the item p_v
+ *
+ * Returns NULL if vector was empty and has now been freed as required.
+ */
+p_vector_item
+vector_ream(vector v, free_keep_b free_structure)
+{
+ p_vector_item p_v ;
+
+ if (v == NULL)
+ return NULL ;
+
+ while (v->end != 0)
+ {
+ p_v = v->p_items[--v->end] ;
+ if (p_v != NULL)
+ return p_v ; /* return non-NULL item */
+ } ;
+
+ /* vector is empty: free the body, and (if required) the vector structure. */
+ vector_reset(v, free_structure) ;
+
+ return NULL ; /* signals end */
+} ;
+
+/*==============================================================================
+ * Unset item, condensing and trimming vector.
+ *
+ * These are legacy operations.
+ */
+
+/*------------------------------------------------------------------------------
+ * Unset item at given index (ie set it NULL).
+ *
+ * Return the old value of the item.
+ *
+ * If the item at the current (logical) end of the vector is NULL, move the
+ * end backwards until finds a non-NULL item, or the vector becomes empty.
+ */
+extern p_vector_item
+vector_unset_item(vector v, vector_index_t i)
+{
+ p_vector_item was ;
+
+ if (i < v->end)
+ {
+ was = v->p_items[i] ;
+ v->p_items[i] = NULL ;
+ }
+ else if (v->end == 0)
+ return NULL ; /* avoid test for last entry NULL if is empty */
+ else
+ was = NULL ;
+
+ if (v->p_items[v->end - 1] == NULL)
+ vector_trim(v) ;
+
+ return was ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim any NULL entries at the current (logical) end of the vector.
+ *
+ * Returns the (new) length (end) of the vector.
+ */
+extern vector_length_t
+vector_trim(vector v)
+{
+ vector_length_t e = v->end ;
+ while ((e > 0) && (v->p_items[e - 1] == NULL))
+ --e ;
+ v->end = e ;
+ return e ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Removes any NULL entries from the given vector.
+ *
+ * Returns the (new) length (end) of the vector.
+ */
+extern vector_length_t
+vector_condense(vector v)
+{
+ vector_length_t e = 0 ;
+ vector_index_t j ;
+
+ /* Find first NULL, if any */
+ while ((e < v->end) && (v->p_items[e] != NULL))
+ ++e ;
+
+ /* Quit if no NULLs (or vector is empty) */
+ if (e == v->end)
+ return e ;
+
+ /* Shuffle any remaining non-NULL down */
+ for (j = e + 1 ; j < v->end ; ++j)
+ if (v->p_items[j] != NULL)
+ v->p_items[e++] = v->p_items[j] ;
+
+ v->end = e ;
+
+ return e ;
+} ;
+
+/*==============================================================================
+ * Inserting and deleting items.
+ */
+
+/*------------------------------------------------------------------------------
+ * Insert item in vector, before item at given position
+ * Move items and extend vector as required.
+ */
+extern void
+vector_insert_item(vector v, vector_index_t i, void* p_v)
+{
+ if ((i == v->end) && (i < v->limit))
+ ++v->end ;
+ else
+ vector_insert(v, i, 1) ;
+
+ v->p_items[i] = (p_vector_item)p_v ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Insert item in vector at given position with rider:
+ *
+ * rider < 0 -- insert before the item at the given position
+ * rider == 0 -- insert at the given position -- REPLACING any existing value
+ * rider > 0 -- insert after the item at the given position
+ *
+ * NB: when an item is replaced, it is the caller's responsibility to release
+ * any memory used by the item, if required.
+ *
+ * Move items and extend vector as required.
+ */
+extern void
+vector_insert_item_here(vector v, vector_index_t i, int rider,
+ p_vector_item p_v)
+{
+ if (rider == 0)
+ return vector_set_item(v, i, p_v) ;
+
+ if (rider > 0)
+ ++i ; /* insert before next item */
+ vector_insert_item(v, i, p_v) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Delete item from vector.
+ *
+ * Move items as required. Reduces number of items in the vector (unless
+ * the item in question is beyond the end of the vector !)
+ *
+ * Returns: the item that has just been deleted.
+ *
+ * NB: it is the caller's responsibility to release memory used by any
+ * current value of the item, if required.
+ *
+ * NB: does NOT change the size of the vector body.
+ */
+extern p_vector_item
+vector_delete_item(vector v, vector_index_t i)
+{
+ p_vector_item p_e ;
+ if (i < v->end)
+ {
+ p_e = v->p_items[i] ; /* pick up the current value */
+ if (i != (v->end - 1))
+ vector_delete(v, i, 1) ;
+ else
+ v->end = i ;
+ return p_e ;
+ }
+ else
+ return NULL ;
+} ;
+
+/*==============================================================================
+ * Moving items within vector.
+ */
+
+/*------------------------------------------------------------------------------
+ * Move item in vector from source position to destination position.
+ *
+ * Moves intervening items up or down as required.
+ *
+ * Extends vector to include the destination, if required.
+ *
+ * A source item beyond the end of the vector is implicitly NULL.
+ */
+extern void
+vector_move_item(vector v, vector_index_t i_dst, vector_index_t i_src)
+{
+ p_vector_item* pp_s ;
+ p_vector_item* pp_d ;
+ p_vector_item p_e ;
+
+ vector_length_t old_end = v->end ;
+
+ /* Worry about whether both source and destination exist. */
+ if (i_dst >= old_end)
+ {
+ vector_insert(v, i_dst, 1) ; /* ensure destination exists */
+ if (i_src >= old_end)
+ return ; /* both were beyond the end */
+ }
+ else if (i_src >= old_end)
+ {
+ i_src = old_end ; /* clamp to just beyond last */
+ vector_insert(v, i_src, 1) ; /* create empty entry */
+ } ;
+
+ if (i_dst == i_src) /* avoid work and edge case */
+ return ;
+
+ /* Both src and dst are within the vector and src != dst */
+ pp_s = &v->p_items[i_src] ; /* address of src entry */
+ pp_d = &v->p_items[i_dst] ; /* address of dst entry */
+ p_e = *pp_s ; /* pick up item to move */
+ if (i_src < i_dst)
+ memmove(pp_s, pp_s+1, P_ITEMS_SIZE(i_dst - i_src)) ;
+ else
+ memmove(pp_d+1, pp_d, P_ITEMS_SIZE(i_src - i_dst)) ;
+ *pp_d = p_e ; /* put down the item to move */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Move item in vector to given position with rider:
+ *
+ * rider < 0 -- move to before the item at the given position
+ * rider == 0 -- move to replace item at the given position
+ * rider > 0 -- insert after the item at the given position
+ *
+ * NB: it is the caller's responsibility to release the any existing value
+ * that will be replaced.
+ *
+ * Move items and extend vector as required.
+ */
+extern void
+vector_move_item_here(vector v, vector_index_t i_dst, int rider,
+ vector_index_t i_src)
+{
+ if (rider != 0)
+ {
+ if (rider > 0)
+ ++i_dst ;
+ vector_move_item(v, i_dst, i_src) ;
+ }
+ else
+ {
+ /* to replace: copy and then delete. */
+ vector_set_item(v, i_dst, vector_get_item(v, i_src)) ;
+ vector_delete_item(v, i_src) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reverse vector: reverse the order of items in the vector.
+ */
+extern void
+vector_reverse(vector v)
+{
+ if (v != NULL)
+ vector_part_reverse(v, 0, v->end) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reverse portion of vector.
+ */
+extern void
+vector_part_reverse(vector v, vector_index_t i, vector_length_t n)
+{
+ vector_index_t j ;
+
+ if (v == NULL)
+ return ;
+
+ if ((i + n) > v->limit)
+ vector_extend(v, i + n) ; /* ensure portion exists */
+
+ if (n <= 1)
+ return ;
+
+ j = i + n - 1 ; /* j > i, because n > 1 */
+ do
+ {
+ p_vector_item p_i = v->p_items[i] ;
+ v->p_items[i++] = v->p_items[j] ;
+ v->p_items[j--] = p_i ;
+ } while (j > i) ;
+} ;
+
+/*==============================================================================
+ * Copying, moving and appending entire vectors.
+ */
+
+static void vector_new_limit(vector v, vector_length_t new_end) ;
+
+/*------------------------------------------------------------------------------
+ * Shallow vector copy -- copies pointers to item values, not the values.
+ *
+ * Creates a new vector.
+ *
+ * NB: creates new vector with same limit as existing one, but copies only
+ * the known items (ie up to end, not up to limit).
+ */
+vector
+vector_copy (vector v)
+{
+ vector new = vector_init_new(NULL, v->limit) ;
+
+ new->end = v->end;
+
+ if (v->limit > 0)
+ memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->end)) ;
+
+ return new;
+}
+
+/*------------------------------------------------------------------------------
+ * Shallow vector copy -- copies pointers to item values, not the values.
+ * Creates a new vector or re-initialises an existing one.
+ *
+ * NB: creates new vector with same limit as existing one, but copies only
+ * the known items (ie up to end, not up to limit).
+ *
+ * NB: if re-initialising existing vector, it is the caller's responsibility
+ * to release any existing items if that is required.
+ *
+ * NB: if re-initialising existing vector, it is the caller's responsibility
+ * to ensure the vector structure is currently valid.
+ *
+ * NB: do NOT try copying a vector to itself !!
+ */
+vector
+vector_copy_here(vector dst, vector src)
+{
+ assert((src != NULL) && (src != dst)) ;
+
+ dst = vector_re_init(dst, src->limit) ;
+
+ dst->end = src->end;
+
+ if (src->end > 0)
+ memcpy(dst->p_items, src->p_items, P_ITEMS_SIZE(src->end)) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Vector move -- moves body of vector.
+ *
+ * Creates a new vector or re-initialises an existing one.
+ * Leaves the source vector empty -- does not release the structure.
+ *
+ * NB: if re-initialising existing vector, it is the caller's responsibility
+ * to release any existing items if that is required.
+ *
+ * NB: if re-initialising existing vector, it is the caller's responsibility
+ * to ensure the vector structure is currently valid.
+ *
+ * NB: do NOT try moving a vector to itself !!
+ */
+extern vector
+vector_move_here(vector dst, vector src)
+{
+ assert((src != NULL) && (src != dst)) ;
+
+ if (dst != NULL)
+ dst = vector_reset(dst, 0) ; /* Reset to deallocate any existing body */
+ else
+ dst = vector_init_new(dst, 0) ; /* Create new structure sans body. */
+
+ *dst = *src ; /* Copy the vector structure */
+
+ vector_init_new(src, 0) ; /* Set empty, forgetting body */
+
+ return dst ;
+}
+
+/*------------------------------------------------------------------------------
+ * Shallow vector copy append -- copies pointers to item values, not the values.
+ *
+ * Appends copied pointers to the destination vector.
+ *
+ * Creates a new destination vector if required.
+ *
+ * NB: Can append to self.
+ */
+extern vector
+vector_copy_append(vector dst, vector src)
+{
+ vector_index_t new_end ;
+
+ assert(src != NULL) ;
+
+ if (dst != NULL)
+ {
+ new_end = dst->end + src->end ;
+ if (new_end > dst->limit)
+ vector_new_limit(dst, new_end) ;
+ }
+ else
+ {
+ new_end = src->end ;
+ vector_init_new(dst, new_end) ;
+ } ;
+
+ if (src->end)
+ memcpy(&dst->p_items[dst->end], src->p_items, P_ITEMS_SIZE(src->end)) ;
+
+ dst->end = new_end ; /* Done last, allows for append to self ! */
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Vector move append -- moves pointers to item values.
+ *
+ * Appends moved pointers to the destination vector.
+ *
+ * Creates a new destination vector if required (dst == NULL).
+ *
+ * Leaves the source vector empty -- does not release the structure.
+ *
+ * NB: do NOT try moving a vector to itself !!
+ */
+extern vector
+vector_move_append(vector dst, vector src)
+{
+ assert((src != NULL) && (src != dst)) ;
+
+ if ((dst == NULL) || (dst->end == 0))
+ return vector_move_here(dst, src) ; /* Easy way to do it if dst empty */
+
+ vector_copy_append(dst, src) ; /* Extend dst and copy src */
+ vector_init_new(src, 0) ; /* Set empty, forgetting body */
+
+ return dst ;
+} ;
+
+/*==============================================================================
+ * Portmanteau splice/extract/replace function.
+ *
+ * All take a portion of 'src' vector and:
+ *
+ * splice:
+ *
+ * a) replace a portion of the 'dst' vector by a portion of the 'src' vector
+ * copying the replaced portion of the 'dst' vector to the 'to' vector
+ * b) either: leave 'src' unchanged -- copy
+ * or: remove the stuff copied from 'src' -- move
+ *
+ * Arguments: to_copy -- true
+ * to -- vector, or NULL => allocate new vector
+ * dst -- vector
+ * i_dst -- start of portion to replace
+ * n_dst -- length of portion to replace. 0 => insertion.
+ * src -- vector, or NULL => nothing to copy/move
+ * i_src -- start of portion to copy/move
+ * n_src -- length of portion to copy/move. 0 => nothing.
+ * src_move -- true => move, otherwise copy.
+ *
+ * Returns: the (possibly new) 'to' vector
+ *
+ * NB: 'to', 'dst' and 'src' must be distinct vectors.
+ *
+ * extract:
+ *
+ * a) copy a portion of the 'src' vector to the 'to' vector
+ * c) either: leave 'src' unchanged -- copy
+ * or: remove the stuff copied from 'src' -- move
+ *
+ * Arguments: to_copy -- true
+ * to -- vector, or NULL => allocate new vector
+ * dst -- NULL
+ * i_dst -- ignored
+ * n_dst -- ignored
+ * src -- vector, or NULL => nothing to copy/move
+ * i_src -- start of portion to copy/move
+ * n_src -- length of portion to copy/move. 0 => nothing.
+ * src_move -- true => move, otherwise copy.
+ *
+ * Returns: the (possibly new) 'to' vector
+ *
+ * NB: 'to' and 'src' must be distinct vectors.
+ *
+ * replace:
+ *
+ * a) replace a portion of the 'dst' vector by a portion of the 'src' vector
+ * b) either: leave 'src' unchanged -- copy
+ * or: remove the stuff copied from 'src' -- move
+ *
+ * Arguments: to_copy -- false
+ * to -- ignored
+ * dst -- vector
+ * i_dst -- start of portion to replace
+ * n_dst -- length of portion to replace. 0 => insertion.
+ * src -- vector, or NULL => nothing to copy/move
+ * i_src -- start of portion to copy/move
+ * n_src -- length of portion to copy/move. 0 => nothing.
+ * src_move -- true => move, otherwise copy.
+ *
+ * Returns: original 'to' argument
+ *
+ * NB: 'dst' and 'src' must be distinct vectors.
+ *
+ * All copies are shallow -- pointers to item values are copied, not the values.
+ *
+ * NB: any existing contents of the 'to' vector are discarded.
+ *
+ * NB: it is the caller's responsibility to release memory allocated to any
+ * items which are discarded in these operations.
+ *
+ * NB: for splice and replace, the resulting destination vector will be at
+ * least i_dst + n_src long. (Even if is copying actual or implied NULLs
+ * from the source.)
+ *
+ * NB: where new vectors are created, they will be of exactly the required size.
+ *
+ * NB: where an existing vector is reused, it is the caller's responsibility
+ * to ensure the vector structure is currently valid (by vector_init_new()
+ * or by ensuring it is zeroized).
+ */
+extern vector
+vector_sak(int to_copy, vector to,
+ vector dst, vector_index_t i_dst, vector_length_t n_dst,
+ vector src, vector_index_t i_src, vector_length_t n_src, int src_move)
+{
+ int dst_replace ; /* true => replace portion of 'dst' */
+
+ vector_index_t new_dst_end = 0 ; /* new end of dst */
+
+ vector_length_t n_dst_nulls ; /* number of implicit NULLs to add */
+ vector_length_t n_dst_move ; /* number of items to move up or down */
+ vector_length_t n_src_real ; /* number of items to really copy */
+ vector_length_t n_src_nulls ; /* number of implicit NULLs to "copy" */
+
+ assert((to == NULL) || (dst == NULL) || (to != dst)) ;
+ assert((src == NULL) || (dst == NULL) || (src != dst)) ;
+
+ /* Worry about how much we really have in the source vector. */
+
+ n_src_real = n_src ; /* assume all real */
+ n_src_nulls = 0 ; /* so no NULLs to "copy" */
+
+ if (n_src != 0)
+ {
+ if ((src == NULL) || (i_src >= src->end))
+ n_src_real = 0 ;
+ else if ((i_src + n_src) > src->end)
+ n_src_real = src->end - i_src ;
+ n_src_nulls = n_src - n_src_real ;
+ } ;
+
+ /* If no 'dst' vector, then this is an extract. */
+
+ n_dst_move = 0 ; /* assume nothing to move */
+ n_dst_nulls = 0 ; /* assume no NULLs to add */
+
+ if (dst == NULL)
+ /* For extract: set up dst, i_dst and n_dst so that can copy to the */
+ /* 'to' vector as if from 'dst'. */
+ {
+ dst_replace = 0 ; /* no replacement operation */
+ dst = src ; /* copy from here */
+ i_dst = i_src ;
+ n_dst = n_src_real ;
+ }
+ else
+ /* Reduce n_dst to the number of actual items to be replaced. */
+ /* */
+ /* Calculate the new end of 'dst'. */
+ {
+ dst_replace = 1 ; /* have replacement to do */
+ if (i_dst >= dst->end)
+ /* If i_dst is beyond the end of 'dst', then there is nothing */
+ /* to replace (so set n_dst == 0). Will be adding n_src items */
+ /* at i_dst -- so new end must be i_dst + n_src. */
+ {
+ n_dst_nulls = i_dst - dst->end ; /* fill from end to i_dst */
+ n_dst = 0 ; /* nothing to replace */
+ new_dst_end = i_dst + n_src ; /* all beyond current end */
+ }
+ else
+ /* If i_dst + n_dst is beyond the end of 'dst', reduce n_dst to */
+ /* number of items up to the end. */
+ /* Will remove n_dst items and insert n_src, so end will move */
+ /* by n_src - n_dst. */
+ {
+ if ((i_dst + n_dst) > dst->end)
+ n_dst = dst->end - i_dst ; /* what we actually replace */
+ else if (n_dst != n_src)
+ n_dst_move = dst->end - (i_dst + n_dst) ;
+ /* what we move up or down */
+
+ new_dst_end = dst->end + n_src - n_dst ;
+ /* end depends on amount added */
+ /* & amount actually replaced */
+ } ;
+ } ;
+
+ /* Copy portion of 'dst' (or of 'src') to 'to', if required. */
+ /* */
+ /* Have arranged: n_dst -- number of items to copy, all existent */
+ /* dst -- vector to copy from -- if n_dst > 0 */
+ /* i_dst -- first item to copy -- if n_dst > 0 */
+
+ if (to_copy)
+ {
+ to = vector_re_init(to, n_dst) ; /* reinitialise or create */
+ to->end = n_dst ;
+ if (n_dst > 0)
+ memcpy(to->p_items, &dst->p_items[i_dst], P_ITEMS_SIZE(n_dst)) ;
+ } ;
+
+ /* Replace portion of 'dst' by portion of 'src', if required. */
+ /* */
+ /* Have arranged: */
+ /* */
+ /* new_dst_end -- end of dst once dust settles */
+ /* n_dst_nulls -- number of NULLs to insert at dst->end to fill up */
+ /* to i_dst (when i_dst is beyond old end.) */
+ /* n_dst_move -- number of items in dst to move up or down to */
+ /* leave n_src item hole at i_dst to fill in. */
+ /* n_src_real -- number of real src items at i_src to copy to dst */
+ /* at i_dst. */
+ /* n_src_nulls -- number of nulls to add to fill to i_dst + n_src. */
+
+ if (dst_replace)
+ {
+ if (new_dst_end > dst->limit) /* extend body if required */
+ vector_new_limit(dst, new_dst_end) ;
+
+ if (n_dst_nulls > 0)
+ memset(&dst->p_items[dst->end], 0, P_ITEMS_SIZE(n_dst_nulls)) ;
+ if (n_dst_move > 0)
+ memmove(&dst->p_items[i_dst + n_dst], &dst->p_items[i_dst + n_src],
+ P_ITEMS_SIZE(n_dst_move)) ;
+ if (n_src_real > 0)
+ memcpy(&dst->p_items[i_dst], &src->p_items[i_src],
+ P_ITEMS_SIZE(n_src_real)) ;
+ if (n_src_nulls > 0)
+ memset(&dst->p_items[i_dst + n_src_real], 0,
+ P_ITEMS_SIZE(n_src_nulls)) ;
+ dst->end = new_dst_end ;
+ } ;
+
+ /* Delete portion of 'src', if required (and have 'src' !) */
+
+ if (src_move && (n_src_real != 0))
+ vector_delete(src, i_src, n_src_real) ;
+
+ /* Done -- return 'to' vector as promised. */
+
+ return to ;
+} ;
+
+/*==============================================================================
+ * Legacy Vector Operations
+ */
+
+/*------------------------------------------------------------------------------
+ * Set value to the smallest empty slot.
+ *
+ * Returns: index of slot used.
+ */
+extern int
+vector_set (vector v, void *val)
+{
+ vector_index_t i;
+
+ i = 0 ;
+ while (1)
+ {
+ if (i == v->end)
+ {
+ i = vector_extend_by_1(v) ;
+ break ;
+ }
+
+ if (v->p_items[i] == NULL)
+ break ;
+
+ ++i ;
+ } ;
+
+ v->p_items[i] = val;
+
+ return i ;
+}
+
+/*------------------------------------------------------------------------------
+ * Set value to specified index slot.
+ *
+ * Returns: index of slot (as given)
+ */
+extern int
+vector_set_index (vector v, vector_index_t i, void *val)
+{
+ vector_ensure (v, i);
+ v->p_items[i] = val;
+ return i;
+}
+
+/*------------------------------------------------------------------------------
+ * Look up vector -- get the i'th item.
+ *
+ * Returns: the i'th item -- NULL if item is null, or i >= logical len of vector
+ */
+extern p_vector_item
+vector_lookup (vector v, vector_index_t i)
+{
+ if (i >= v->end)
+ return NULL;
+ return v->p_items[i];
+}
+
+/*------------------------------------------------------------------------------
+ * Lookup vector, ensure it -- get i'th item and ensure logical len > i.
+ *
+ * Returns: the i'th item -- NULL if item is null
+ */
+extern p_vector_item
+vector_lookup_ensure (vector v, vector_index_t i)
+{
+ vector_ensure (v, i);
+ return v->p_items[i];
+}
+
+/*------------------------------------------------------------------------------
+ * Count the number of not empty slots.
+ */
+extern vector_length_t
+vector_count (vector v)
+{
+ vector_index_t i;
+ vector_length_t count = 0;
+
+ for (i = 0; i < v->end; i++)
+ if (v->p_items[i] != NULL)
+ count++;
+
+ return count;
+}
+
+/*==============================================================================
+ * Sorting and Searching vector.
+ */
+
+/*------------------------------------------------------------------------------
+ * Sort the given vector.
+ *
+ * NB: the comparison function receives a pointer to the pointer to the
+ * vector item's value.
+ *
+ * NB: if there are NULL items in the vector, the comparison function MUST
+ * be ready for them.
+ */
+extern void
+vector_sort(vector v, vector_sort_cmp* cmp)
+{
+ if (v->end <= 1)
+ return ; /* Stop dead if 0 or 1 items */
+
+ typedef int qsort_cmp(const void*, const void*) ;
+
+ qsort(v->p_items, v->end, sizeof(p_vector_item), (qsort_cmp*)cmp) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Perform binary search on the given vector.
+ *
+ * The vector MUST be sorted in the order implied by the comparison function
+ * given. The vector need not contain unique values, but this search makes
+ * no effort to select any particular instance of a sequence of equals.
+ *
+ * Returns:
+ *
+ * result == 0: found an equal value.
+ * index returned is of first entry found which is equal to
+ * the value sought. There may be other equal values, before
+ * and/or after this one in the vector.
+ *
+ * result == +1: value not found and vector not empty.
+ * index returned is of the largest entry whose value is less
+ * than the value sought.
+ * (The value sought belongs after this point.)
+ *
+ * result == -1: value is less than everything in the vector, or the
+ * vector is empty.
+ * index returned is 0
+ *
+ * NB: The comparison function takes arguments which are:
+ *
+ * const void** pointer to pointer to value being searched for.
+ * const void** pointer to pointer to vector item to compare with
+ *
+ * The value being searched for need not be in the same form as a vector
+ * item. However, if it is then the same comparison function can be used
+ * to sort and search.
+ *
+ * NB: if there are NULL items in the vector, the comparison function MUST
+ * be ready for them.
+ */
+extern vector_index_t
+vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val,
+ int* result)
+{
+ vector_index_t il, iv, ih ;
+ int c ;
+
+ if (v->end <= 1)
+ {
+ *result = (v->end == 0) ? -1 : cmp(&p_val, (const void**)&v->p_items[0]) ;
+ return 0 ; /* Stop dead if 0 or 1 items */
+ } ;
+
+ /* We have at least two items. */
+ il = 0 ;
+ ih = v->end - 1 ;
+
+ /* Pick off the edge cases: >= last and <= first. */
+ if ((c = cmp(&p_val, (const void**)&v->p_items[ih])) >= 0)
+ {
+ *result = c ; /* 0 => found. +1 => val > last */
+ return ih ; /* return high index. */
+ } ;
+ if ((c = cmp(&p_val, (const void**)&v->p_items[il])) <= 0)
+ {
+ *result = c ; /* 0 => found. -1 => val < first */
+ return il ; /* return low index. */
+ }
+
+ /* Now binary chop. We know that item[il] < val < item[ih] */
+ /* We also know that il < ih */
+ while (1)
+ {
+ iv = (il + ih) / 2 ;
+ if (iv == il) /* true if (ih == il+1) */
+ {
+ *result = +1 ;
+ return il ; /* return il: item[il] < val < item[il+1] */
+ } ;
+ /* We now know that il < iv < ih */
+ c = cmp(&p_val, (const void**)&v->p_items[iv]) ;
+ if (c == 0)
+ {
+ *result = 0 ;
+ return iv ; /* found !! */
+ }
+ if (c < 0)
+ ih = iv ; /* step down iv > il, so new ih > il */
+ else
+ il = iv ; /* step up iv < ih, so new il < ih */
+ } ;
+} ;
+
+/*==============================================================================
+ * Mechanics for adding/deleting items and managing the vector (logical) end
+ * and (physical) limit.
+ */
+
+/* Extract the LS bit of unsigned integer 'x'. */
+#define lsbit(x) ((x) & ((~(x)) + 1))
+/* Round 'x' up to a multiple of 'm' */
+#define multiple(x, m) ((((x) + (m) - 1) / (m)) * (m))
+
+/*------------------------------------------------------------------------------
+ * Set new limit to suit new end for given vector.
+ *
+ * The new limit will be at least: VECTOR_LIMIT_MIN.
+ *
+ * While the vector is relatively small, the limit is doubled until there
+ * is at least 1/8 of the new vector free.
+ *
+ * Beyond VECTOR_LIMIT_DOUBLE_MAX, however, the limit is set to the
+ * smallest multiple of VECTOR_LIMIT_DOUBLE_MAX which gives at least
+ * VECTOR_LIMIT_SLACK_MIN free entries beyond the new end.
+ *
+ * This is an attempt to balance the cost of repeated reallocations of
+ * memory against the cost of possible wasted space at the end of the
+ * vector.
+ *
+ * NB: the new_limit depends entirely on the new end position. (Current
+ * end position is ignored.)
+ *
+ * NB: the new limit may be less than the current limit, in which case the
+ * vector body is reduced in size.
+ *
+ * Except for any size set when the vector is initialised, the vector body
+ * size will be a power of 2 or a multiple of VECTOR_LIMIT_DOUBLE_MAX.
+ * (Vectors are regular in their habits, which may help the memory allocator).
+ *
+ * TODO: what to do if calculation of new_limit overflows, or calculation
+ * of P_ITEMS_SIZE will ?
+ */
+static void
+vector_new_limit(vector v, vector_index_t new_end)
+{
+ vector_length_t old_limit = v->limit ;
+ vector_length_t new_limit ;
+
+ if (new_end > ((VECTOR_LIMIT_DOUBLE_MAX * 7) / 8))
+ {
+ new_limit = multiple(new_end + VECTOR_LIMIT_SLACK_MIN,
+ VECTOR_LIMIT_DOUBLE_MAX) ;
+ }
+ else
+ {
+ /* Want the new_limit to be a power of 2. */
+ /* If the old_limit was a power of 2, start from there. */
+ /* Otherwise start from a power of 2 less than new_end: either the */
+ /* minimum value or a value mid way to VECTOR_LIMIT_DOUBLE_MAX. */
+ if ( (old_limit != 0) && (old_limit == lsbit(old_limit))
+ && (new_end >= old_limit) )
+ new_limit = old_limit ;
+ else
+ new_limit = (new_end < VECTOR_LIMIT_MID) ? VECTOR_LIMIT_MIN
+ : VECTOR_LIMIT_MID ;
+ while (new_end > ((new_limit * 7) / 8))
+ new_limit *= 2 ;
+ } ;
+
+ v->p_items =
+ XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ;
+
+ v->limit = new_limit ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend vector and set new (logical) end.
+ *
+ * Extends body if required.
+ * Ensures everything between old and new end is set NULL.
+ *
+ * NB: expects new end > old end, but copes with new end <= old end.
+ */
+extern void
+vector_extend(vector v, vector_length_t new_end)
+{
+ vector_length_t old_end = v->end ;
+
+ if (new_end > v->limit)
+ vector_new_limit(v, new_end) ;
+ v->end = new_end ;
+
+ if (new_end > old_end)
+ memset(&v->p_items[old_end], 0, P_ITEMS_SIZE(new_end - old_end)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Insert entries into vector: insert 'n' NULL entries at location 'i'.
+ *
+ * Updates end (and limit) to be at least 'i' + 'n'.
+ * (So if 'i' < end then end becomes end + 'n', else end becomes 'i' + 'n'.)
+ */
+extern void
+vector_insert(vector v, vector_index_t i, vector_length_t n)
+{
+ vector_length_t old_end, new_end ;
+ vector_length_t n_above ;
+
+ /* If i < old end, then we are inserting n NULLs, and need
+ * to shuffle at least one item up.
+ * else we are setting new end to i + n and need to NULL
+ * fill from old end to the new end.
+ */
+ old_end = v->end ;
+ if (i < old_end)
+ {
+ if (n == 0)
+ return ; /* give up now if not inserting anything */
+ n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */
+ new_end = old_end + n ;
+ }
+ else
+ {
+ n_above = 0 ; /* nothing to shuffle up. */
+ new_end = i + n ;
+ i = old_end ; /* where to zeroize from */
+ n = new_end - old_end ; /* how much to zeroize */
+ } ;
+
+ /* Now we extend the body if we need to. */
+ if (new_end > v->limit)
+ vector_new_limit(v, new_end) ;
+ v->end = new_end ;
+
+ if (n_above > 0)
+ memmove(&v->p_items[i + n], &v->p_items[i], P_ITEMS_SIZE(n_above)) ;
+
+ memset(&v->p_items[i], 0, P_ITEMS_SIZE(n)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Delete items from vector: delete 'n' items at location 'i'.
+ *
+ * Does nothing if 'i' is beyond current end of vector or if 'n' == 0.
+ *
+ * Deletes from 'i' to end if less than 'n' items to the end.
+ *
+ * NB: does NOT change the size of the body.
+ *
+ * NB: it is the caller's responsibility to have released any memory allocated
+ * for the items that are being deleted.
+*/
+extern void
+vector_delete(vector v, vector_index_t i, vector_length_t n)
+{
+ vector_length_t old_end, new_end ;
+
+ old_end = v->end ;
+
+ if ((i >= old_end) || (n == 0))
+ return ;
+
+ /* If i + n < old_end, we have 1 or more items to keep and move down */
+ if ((i + n) < old_end)
+ {
+ memmove(&v->p_items[i], &v->p_items[i + n],
+ P_ITEMS_SIZE(old_end - (i + n))) ;
+ new_end = old_end - n ;
+ }
+ else
+ {
+ new_end = i ; /* We are keeping nothing above 'i' */
+ /* NB: new_end < old_end */
+ } ;
+
+ v->end = new_end ; /* account for stuff dropped */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Discard entries from vector: discard entries from location 'i' onwards.
+ *
+ * Releases memory from 'i' onwards.
+ * Releases the body altogether if this sets the vector empty ('i' == 0).
+ * Sets new end of vector iff 'i' < current end.
+ *
+ * Does nothing if 'i' is beyond current limit (physical end) of vector.
+ *
+ * NB: it is the caller's responsibility to have released any memory allocated
+ * for the items that are being discarded.
+*/
+extern void
+vector_discard(vector v, vector_index_t i)
+{
+ if (i >= v->limit)
+ return ;
+
+ if (i == 0)
+ vector_reset(v, 0) ; /* reset, without releasing the structure */
+ else
+ {
+ v->limit = i ;
+ if (i < v->end)
+ v->end = i ;
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(i)) ;
+ } ;
+} ;
+
+/* Chop vector at the current (logical) end.
+ *
+ * Releases the body altogether if the vector is currently empty.
+ */
+extern void
+vector_chop(vector v)
+{
+ vector_length_t new_limit = v->end ;
+
+ if (new_limit == 0)
+ vector_reset(v, 0) ; /* reset, without releasing the structure */
+ else if (new_limit != v->limit)
+ {
+ v->limit = new_limit ;
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ;
+ } ;
+} ;
+
+/* Decant vector into a new body allocated to current logical size, and
+ * release the old body.
+ *
+ * Releases the body altogether if the vector is currently empty.
+*/
+void
+vector_decant(vector v)
+{
+ p_vector_item* p_old_body ;
+ vector_length_t new_limit = v->end ;
+
+ if (new_limit == 0)
+ vector_reset(v, 0) ; /* reset, without releasing the structure */
+ else
+ {
+ p_old_body = v->p_items ;
+
+ vector_init_new(v, new_limit) ; /* initialise with new body */
+
+ memcpy(v->p_items, p_old_body, P_ITEMS_SIZE(new_limit)) ;
+ /* copy the old body across */
+ v->end = new_limit ;
+
+ XFREE(MTYPE_VECTOR_BODY, p_old_body) ;
+ } ;
+} ;
diff --git a/lib/thread.c b/lib/thread.c
index a15df557..c130d876 100644
--- a/lib/thread.c
+++ b/lib/thread.c
@@ -44,8 +44,8 @@ static unsigned short timers_inited;
/* cpu stats needs to be qpthread safe. */
static qpt_mutex_t thread_mutex;
-#define LOCK qpt_mutex_lock(&thread_mutex);
-#define UNLOCK qpt_mutex_unlock(&thread_mutex);
+#define LOCK qpt_mutex_lock(thread_mutex);
+#define UNLOCK qpt_mutex_unlock(thread_mutex);
static struct hash *cpu_record = NULL;
/* Pointer to qtimer pile to be used, if any */
@@ -554,7 +554,7 @@ thread_master_create ()
{
#ifdef USE_MQUEUE
sigfillset (&newmask);
- sigdelset (&newmask, SIGMQUEUE);
+ sigdelset (&newmask, SIG_INTERRUPT);
#endif
if (cpu_record == NULL)
@@ -839,7 +839,7 @@ funcname_thread_add_write (struct thread_master *m,
* THREAD_TIMER or a THREAD_BACKGROUND thread, then thread->u.qtr points
* at the qtimer.
*
- * AND, conversely, if there is no qtimer, then thread->u.ptr == NULL.
+ * AND, conversely, if there is no qtimer, then thread->u.qtr == NULL.
*/
/*------------------------------------------------------------------------------
@@ -1524,14 +1524,14 @@ funcname_thread_execute (struct thread_master *m,
void
thread_init_r (void)
{
- qpt_mutex_init(&thread_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(thread_mutex, qpt_mutex_quagga);
}
/* Finished with module */
void
thread_finish (void)
{
- qpt_mutex_destroy(&thread_mutex, 0);
+ qpt_mutex_destroy(thread_mutex, 0);
}
#undef USE_MQUEUE
diff --git a/lib/thread.h b/lib/thread.h
index a38fb19a..d9c58640 100644
--- a/lib/thread.h
+++ b/lib/thread.h
@@ -206,8 +206,8 @@ extern int thread_should_yield (struct thread *);
/* Internal libzebra exports */
extern void thread_getrusage (RUSAGE_T *);
-extern struct cmd_element show_thread_cpu_cmd;
-extern struct cmd_element clear_thread_cpu_cmd;
+extern struct cmd_command show_thread_cpu_cmd;
+extern struct cmd_command clear_thread_cpu_cmd;
/* replacements for the system gettimeofday(), clock_gettime() and
* time() functions, providing support for non-decrementing clock on
diff --git a/lib/tstring.h b/lib/tstring.h
new file mode 100644
index 00000000..4dd45edb
--- /dev/null
+++ b/lib/tstring.h
@@ -0,0 +1,139 @@
+/* Temporary string handling -- header
+ * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_TSTRING_H
+#define _ZEBRA_TSTRING_H
+
+#include "misc.h"
+#include "zassert.h"
+#include "memory.h"
+
+/*==============================================================================
+ * tstrings are allocated on the stack, but if (unexpectedly) the standard
+ * size is not enough, then they can be allocated dynamically.
+ *
+ * To declare a "tstring":
+ *
+ * tstring(foo, 64) ; // creates a "tstring" variable called "foo"
+ * // with 64 char buffer.
+ *
+ * Can then:
+ *
+ * s = tstring_set_len(foo, n) ; // ensures have buffer for n+1 chars
+ * s = tstring_set(foo, "...") ; // copies "..." (with '\0') to buffer
+ * s = tstring_set_n(foo, q, n) ; // copies n characters from q to buffer
+ * and '\0' terminates
+ *
+ * If can fit stuff in the buffer, will do so. Otherwise will allocate an
+ * MTYPE_TMP buffer to work in.
+ *
+ * And before leaving the scope of "foo" must:
+ *
+ * tstring_free(foo) ; // releases any dynamically allocated memory.
+ */
+
+struct tstring
+{
+ usize size ;
+ char* str ;
+ char* alloc ;
+} ;
+
+typedef struct tstring tstring[1] ;
+
+/* tstring(foo, 93) ; -- declare the variable "foo". */
+#define tstring_t(name, sz) \
+ char _zlxq_##name##_b[ ((sz) + 7) & 0xFFFFFFF8] ; \
+ tstring name = { { .size = ((sz) + 7) & 0xFFFFFFF8, \
+ .str = _zlxq_##name##_b, \
+ .alloc = NULL } }
+
+/*------------------------------------------------------------------------------
+ * Ensure the tstring "foo" can accomodate at least "len" characters plus the
+ * terminating '\0'.
+ *
+ * Returns: address of buffer
+ *
+ * NB: address of buffer may not be the same as returned by a previous operation
+ * on foo. Also, previous contents of foo may be lost.
+ */
+Inline char*
+tstring_set_len(struct tstring* ts, usize len)
+{
+ if (len >= ts->size)
+ {
+ ts->size = len + 1 ;
+ ts->str = ts->alloc = XREALLOC(MTYPE_TMP, ts->alloc, len + 1) ;
+ } ;
+
+ return ts->str ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Copy "len" characters from "src" to the tstring "foo", and append a
+ * terminating '\0'.
+ *
+ * The "src" address is ignored if "len" == 0 (sets "foo" to be empty string).
+ *
+ * Returns: address of buffer
+ *
+ * NB: address of buffer may not be the same as returned by a previous operation
+ * on foo. Also, previous contents of foo may be lost.
+ */
+static inline char*
+tstring_set_n(struct tstring* ts, const char* str, usize len)
+{
+ char* tss = tstring_set_len(ts, len) ;
+
+ if (len > 0)
+ memcpy(tss, str, len) ;
+ *(tss + len) = '\0' ;
+
+ return tss ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Copy the string "str" to the tstring "foo", with terminating '\0'.
+ *
+ * If "str" is NULL, sets "foo" to be an empty string.
+ *
+ * Returns: address of buffer
+ *
+ * NB: address of buffer may not be the same as returned by a previous operation
+ * on foo. Also, previous contents of foo may be lost.
+ */
+static inline char*
+tstring_set(struct tstring* ts, const char* str)
+{
+ return tstring_set_n(ts, str, (str != NULL) ? strlen(str) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If have dynamically allocated buffer for tstring "foo", release it now.
+ */
+static inline void
+tstring_free(struct tstring* ts)
+{
+ if (ts->alloc != NULL)
+ XFREE(MTYPE_TMP, ts->alloc) ; /* sets ts->alloc NULL */
+} ;
+
+#endif /* _ZEBRA_TSTRING_H */
diff --git a/lib/uty.h b/lib/uty.h
deleted file mode 100644
index d1a8b584..00000000
--- a/lib/uty.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/* VTY internal stuff -- header
- * Copyright (C) 1997 Kunihiro Ishiguro
- *
- * Copyright (C) 2009 Chris Hall (GMCH), Highwayman
- *
- * This file is part of GNU Zebra.
- *
- * GNU Zebra is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * GNU Zebra is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#ifndef _ZEBRA_UTY_H
-#define _ZEBRA_UTY_H
-
-#include <stdbool.h>
-
-#include "qpthreads.h"
-#include "qpnexus.h"
-#include "thread.h"
-#include "list_util.h"
-#include "vty.h"
-#include "node_type.h"
-
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
-
-/*==============================================================================
- * This is stuff which is used by the close family of:
- *
- * vty
- * command
- * command_queue
- * log
- *
- * and which is not for use elsewhere.
- *
- * 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
- * for both, i.e. they are treated as being part of the same monitor.
- *
- * A recursive mutex is used. This simplifies the calling from log to vty and
- * back again. It also allows for the vty internals to call each other.
- *
- * There are some "uty" functions which assume the mutex is locked.
- *
- * vty is closely bound to the command handling -- the main vty structure
- * contains the context in which commands are parsed and executed.
- */
-
-extern qpt_mutex_t vty_mutex ;
-
-#ifdef NDEBUG
-# define VTY_DEBUG 0 /* NDEBUG override */
-#else
-# ifndef VTY_DEBUG
-# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */
-# endif
-#endif
-
-#if VTY_DEBUG
-
-extern int vty_lock_count ;
-extern int vty_assert_fail ;
-
-#endif
-
-Inline void
-VTY_LOCK(void)
-{
- qpt_mutex_lock(&vty_mutex) ;
- if (VTY_DEBUG)
- ++vty_lock_count ;
-} ;
-
-Inline void
-VTY_UNLOCK(void)
-{
- if (VTY_DEBUG)
- --vty_lock_count ;
- qpt_mutex_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_FAILED(void)
-{
- if (vty_assert_fail == 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
-
-/*==============================================================================
- * Shared definitions
- */
-
-enum cli_do
-{
- cli_do_nothing = 0, /* no action required */
-
- cli_do_command, /* dispatch the current command line */
- cli_do_ctrl_c, /* received ^c */
- cli_do_ctrl_d, /* received ^d on empty line */
- cli_do_ctrl_z, /* received ^z */
-
- cli_do_eof, /* hit "EOF" */
-
- cli_do_count /* number of different cli_do_xxx */
-} ;
-
-/*==============================================================================
- * Functions in vty.c -- used in any of the family
- */
-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 -- used outside the immediate vty family
- */
-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 -- 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/vargs.h b/lib/vargs.h
new file mode 100644
index 00000000..5f0207cb
--- /dev/null
+++ b/lib/vargs.h
@@ -0,0 +1,34 @@
+/* Variable arguments definitions
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VARGS_H
+#define _ZEBRA_VARGS_H
+
+#include <stdarg.h>
+
+/* GCC has printf type attribute check. */
+#ifdef __GNUC__
+#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
+#else
+#define PRINTF_ATTRIBUTE(a,b)
+#endif /* __GNUC__ */
+
+#endif /* _ZEBRA_VARGS_H */
diff --git a/lib/vector.c b/lib/vector.c
index e03febdc..83d0397c 100644
--- a/lib/vector.c
+++ b/lib/vector.c
@@ -22,7 +22,7 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
+#include "misc.h"
#include "vector.h"
#include "memory.h"
@@ -84,11 +84,13 @@
*/
#define P_ITEMS_SIZE(n) SIZE(p_vector_item, n)
+
/*==============================================================================
* Initialisation, allocation, reset etc.
*/
-/* Initialise a brand new vector, setting it empty.
+/*------------------------------------------------------------------------------
+ * Initialise a brand new vector, setting it empty.
*
* Allocates vector structure if none given -- that is, if v == NULL.
*
@@ -98,41 +100,50 @@
* NB: discards any existing vector body -- so it is the caller's responsibility
* to release any existing body, and any items in that body.
*/
-vector
-vector_init_new(vector v, unsigned int size)
+extern vector
+vector_init_new(vector v, vector_length_t limit)
{
if (v == NULL)
v = XCALLOC(MTYPE_VECTOR, sizeof(struct vector)) ;
else
memset(v, 0, sizeof(struct vector)) ;
- if (size != 0)
+ if (limit != 0)
{
- v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(size)) ;
- v->limit = size ;
+ v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(limit)) ;
+ v->limit = limit ;
} ;
return v ;
} ;
-/* Initialize vector : allocate memory and return vector.
+/*------------------------------------------------------------------------------
+ * Initialize vector : allocate memory and return vector.
* allocates body with at least 1 entry.
+ *
+ * This is a "legacy" function.
*/
-vector
-vector_init (unsigned int size)
+extern vector
+vector_init (vector_length_t limit)
{
- return vector_init_new(NULL, size ? size : 1) ; /* at least 1 entry */
+ return vector_init_new(NULL, limit ? limit : 1) ; /* at least 1 entry */
} ;
-/* Basic: free the vector body and the vector structure. */
+/*------------------------------------------------------------------------------
+ * Basic: free the vector body and the vector structure.
+ *
+ * NB: it is the caller's responsibility to release any vector item values
+ * *before* doing this.
+ */
void
vector_free (vector v)
{
XFREE (MTYPE_VECTOR_BODY, v->p_items);
XFREE (MTYPE_VECTOR, v);
-}
+} ;
-/* Re-Initialize vector (or create new one), setting it empty.
+/*------------------------------------------------------------------------------
+ * Re-initialise vector (or create new one), setting it empty.
*
* Allocates vector structure if none given -- that is, if v == NULL.
*
@@ -142,27 +153,29 @@ vector_free (vector v)
* Otherwise ensures existing body is at least the required size, or a body
* of exactly the required size is allocated.
*
- * NB: when re-initialising an existing vector it is the caller's
- * responsibility to release any vector item values *before* doing this.
+ * NB: when re-initialising an existing vector it is the caller's responsibility
+ * to release any vector item values *before* doing this.
* */
-vector
-vector_re_init(vector v, unsigned int size)
+extern vector
+vector_re_init(vector v, vector_length_t limit)
{
if ((v == NULL) || (v->p_items == NULL))
- return vector_init_new(v, size) ;
+ return vector_init_new(v, limit) ;
v->end = 0 ;
- if (v->limit < size)
+ if (v->limit < limit)
{
- v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(size)) ;
- v->limit = size ;
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items,
+ P_ITEMS_SIZE(limit)) ;
+ v->limit = limit ;
} ;
return v ;
} ;
-/* Free the vector body, and free the vector structure or reset it.
+/*------------------------------------------------------------------------------
+ * Free the vector body, and (if required) free the vector structure.
*
* Return NULL if releases vector, otherwise the address of the vector.
*
@@ -170,16 +183,17 @@ vector_re_init(vector v, unsigned int size)
* *before* doing this.
*/
vector
-vector_reset(vector v, int free_structure)
+vector_reset(vector v, free_keep_b free_structure)
{
if (v == NULL)
- return NULL ; /* allow for already freed vector */
+ return NULL ; /* allow for already freed vector */
if (v->p_items != NULL)
XFREE(MTYPE_VECTOR_BODY, v->p_items) ;
if (free_structure)
{
+ confirm(free_it == true) ;
XFREE(MTYPE_VECTOR, v) ;
return NULL ;
}
@@ -187,7 +201,8 @@ vector_reset(vector v, int free_structure)
return vector_init_new(v, 0) ;
} ;
-/* Set vector length to be (at least) the given fixed length.
+/*------------------------------------------------------------------------------
+ * Set vector length to be (at least) the given fixed length.
*
* There must be a vector.
*
@@ -201,8 +216,8 @@ vector_reset(vector v, int free_structure)
*
* Note that the existing contents of the vector are preserved in all cases.
*/
-extern void
-vector_set_new_min_length(vector v, unsigned int len)
+Private void
+vector_set_new_min_length(vector v, vector_length_t len)
{
assert (v != NULL) ;
@@ -211,7 +226,8 @@ vector_set_new_min_length(vector v, unsigned int len)
if (v->p_items == NULL)
v->p_items = XMALLOC(MTYPE_VECTOR_BODY, P_ITEMS_SIZE(len)) ;
else
- v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(len));
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items,
+ P_ITEMS_SIZE(len)) ;
v->limit = len ;
} ;
@@ -219,8 +235,8 @@ vector_set_new_min_length(vector v, unsigned int len)
vector_extend(v, len) ;
} ;
-/* Pop item from vector, stepping past any NULLs.
- *
+/*------------------------------------------------------------------------------
+ * Pop item from vector, stepping past any NULLs.
* If vector is empty, free the body and, if required, the vector structure.
*
* Useful for emptying out and discarding a vector:
@@ -231,7 +247,7 @@ vector_set_new_min_length(vector v, unsigned int len)
* Returns NULL if vector was empty and has now been freed as required.
*/
p_vector_item
-vector_ream(vector v, int free_structure)
+vector_ream(vector v, free_keep_b free_structure)
{
p_vector_item p_v ;
@@ -242,61 +258,23 @@ vector_ream(vector v, int free_structure)
{
p_v = v->p_items[--v->end] ;
if (p_v != NULL)
- return p_v ; /* return non-NULL item */
+ return p_v ; /* return non-NULL item */
} ;
/* vector is empty: free the body, and (if required) the vector structure. */
vector_reset(v, free_structure) ;
- return NULL ; /* signals end */
+ return NULL ; /* signals end */
} ;
/*==============================================================================
* Unset item, condensing and trimming vector.
- */
-
-/* Trim any NULL entries at the current (logical) end of the vector.
*
- * Returns the (new) end of the vector.
+ * These are legacy operations.
*/
-extern vector_index
-vector_trim(vector v)
-{
- vector_index i = v->end ;
- while ((i > 0) && (v->p_items[i - 1] == NULL))
- --i ;
- v->end = i ;
- return i ;
-} ;
-
-/* Removes any NULL entries from the given vector.
- *
- * Returns the (new) end of the vector.
- */
-extern vector_index vector_condense(vector v)
-{
- vector_index i = 0 ;
- vector_index j ;
-
- /* Find first NULL, if any */
- while ((i < v->end) && (v->p_items[i] != NULL))
- ++i ;
-
- /* Quit if no NULLs (or vector is empty) */
- if (i == v->end)
- return i ;
-
- /* Shuffle any remaining non-NULL down */
- for (j = i + 1 ; j < v->end ; ++j)
- if (v->p_items[j] != NULL)
- v->p_items[i++] = v->p_items[j] ;
- v->end = i ;
-
- return i ;
-} ;
-
-/* Unset item at given index (ie set it NULL).
+/*------------------------------------------------------------------------------
+ * Unset item at given index (ie set it NULL).
*
* Return the old value of the item.
*
@@ -304,7 +282,7 @@ extern vector_index vector_condense(vector v)
* end backwards until finds a non-NULL item, or the vector becomes empty.
*/
extern p_vector_item
-vector_unset_item(vector v, vector_index i)
+vector_unset_item(vector v, vector_index_t i)
{
p_vector_item was ;
@@ -324,15 +302,60 @@ vector_unset_item(vector v, vector_index i)
return was ;
} ;
+/*------------------------------------------------------------------------------
+ * Trim any NULL entries at the current (logical) end of the vector.
+ *
+ * Returns the (new) length (end) of the vector.
+ */
+extern vector_length_t
+vector_trim(vector v)
+{
+ vector_length_t e = v->end ;
+ while ((e > 0) && (v->p_items[e - 1] == NULL))
+ --e ;
+ v->end = e ;
+ return e ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Removes any NULL entries from the given vector.
+ *
+ * Returns the (new) length (end) of the vector.
+ */
+extern vector_length_t
+vector_condense(vector v)
+{
+ vector_length_t e = 0 ;
+ vector_index_t j ;
+
+ /* Find first NULL, if any */
+ while ((e < v->end) && (v->p_items[e] != NULL))
+ ++e ;
+
+ /* Quit if no NULLs (or vector is empty) */
+ if (e == v->end)
+ return e ;
+
+ /* Shuffle any remaining non-NULL down */
+ for (j = e + 1 ; j < v->end ; ++j)
+ if (v->p_items[j] != NULL)
+ v->p_items[e++] = v->p_items[j] ;
+
+ v->end = e ;
+
+ return e ;
+} ;
+
/*==============================================================================
* Inserting and deleting items.
*/
-/* Insert item in vector, before item at given position
+/*------------------------------------------------------------------------------
+ * Insert item in vector, before item at given position
* Move items and extend vector as required.
*/
-void
-vector_insert_item(vector v, vector_index i, void* p_v)
+extern void
+vector_insert_item(vector v, vector_index_t i, void* p_v)
{
if ((i == v->end) && (i < v->limit))
++v->end ;
@@ -342,7 +365,8 @@ vector_insert_item(vector v, vector_index i, void* p_v)
v->p_items[i] = (p_vector_item)p_v ;
} ;
-/* Insert item in vector at given position with rider:
+/*------------------------------------------------------------------------------
+ * Insert item in vector at given position with rider:
*
* rider < 0 -- insert before the item at the given position
* rider == 0 -- insert at the given position -- REPLACING any existing value
@@ -353,8 +377,8 @@ vector_insert_item(vector v, vector_index i, void* p_v)
*
* Move items and extend vector as required.
*/
-void
-vector_insert_item_here(vector v, vector_index i, int rider,
+extern void
+vector_insert_item_here(vector v, vector_index_t i, int rider,
p_vector_item p_v)
{
if (rider == 0)
@@ -365,18 +389,21 @@ vector_insert_item_here(vector v, vector_index i, int rider,
vector_insert_item(v, i, p_v) ;
} ;
-/* Delete item from vector.
+/*------------------------------------------------------------------------------
+ * Delete item from vector.
*
* Move items as required. Reduces number of items in the vector (unless
* the item in question is beyond the end of the vector !)
*
+ * Returns: the item that has just been deleted.
+ *
* NB: it is the caller's responsibility to release memory used by any
* current value of the item, if required.
*
* NB: does NOT change the size of the vector body.
*/
-p_vector_item
-vector_delete_item(vector v, vector_index i)
+extern p_vector_item
+vector_delete_item(vector v, vector_index_t i)
{
p_vector_item p_e ;
if (i < v->end)
@@ -396,19 +423,23 @@ vector_delete_item(vector v, vector_index i)
* Moving items within vector.
*/
-/* Move item in vector from source position to destination position.
+/*------------------------------------------------------------------------------
+ * Move item in vector from source position to destination position.
+ *
* Moves intervening items up or down as required.
+ *
* Extends vector to include the destination, if required.
+ *
* A source item beyond the end of the vector is implicitly NULL.
*/
-void
-vector_move_item(vector v, vector_index i_dst, vector_index i_src)
+extern void
+vector_move_item(vector v, vector_index_t i_dst, vector_index_t i_src)
{
p_vector_item* pp_s ;
p_vector_item* pp_d ;
p_vector_item p_e ;
- vector_index old_end = v->end ;
+ vector_length_t old_end = v->end ;
/* Worry about whether both source and destination exist. */
if (i_dst >= old_end)
@@ -437,7 +468,8 @@ vector_move_item(vector v, vector_index i_dst, vector_index i_src)
*pp_d = p_e ; /* put down the item to move */
} ;
-/* Move item in vector to given position with rider:
+/*------------------------------------------------------------------------------
+ * Move item in vector to given position with rider:
*
* rider < 0 -- move to before the item at the given position
* rider == 0 -- move to replace item at the given position
@@ -451,8 +483,8 @@ vector_move_item(vector v, vector_index i_dst, vector_index i_src)
* Move items and extend vector as required.
*/
void
-vector_move_item_here(vector v, vector_index i_dst, int rider,
- vector_index i_src)
+vector_move_item_here(vector v, vector_index_t i_dst, int rider,
+ vector_index_t i_src)
{
if (rider != 0)
{
@@ -477,21 +509,23 @@ vector_move_item_here(vector v, vector_index i_dst, int rider,
} ;
} ;
-/* Reverse vector: reverse the order of items in the vector.
+/*------------------------------------------------------------------------------
+ * Reverse vector: reverse the order of items in the vector.
*/
-void
+extern void
vector_reverse(vector v)
{
if (v != NULL)
vector_part_reverse(v, 0, v->end) ;
} ;
-/* Reverse portion of vector.
+/*------------------------------------------------------------------------------
+ * Reverse portion of vector.
*/
-void
-vector_part_reverse(vector v, vector_index i, unsigned int n)
+extern void
+vector_part_reverse(vector v, vector_index_t i, vector_length_t n)
{
- vector_index j ;
+ vector_index_t j ;
if (v == NULL)
return ;
@@ -515,12 +549,16 @@ vector_part_reverse(vector v, vector_index i, unsigned int n)
* Copying, moving and appending entire vectors.
*/
-static void vector_new_limit(vector v, vector_index new_end) ;
+static void vector_new_limit(vector v, vector_length_t new_end) ;
-/* Shallow vector copy -- copies pointers to item values, not the values. */
-/* Creates a new vector. */
-/* NB: copies whole body, including stuff beyond (logical) end ! */
-/* TODO: is this behaviour required ? */
+/*------------------------------------------------------------------------------
+ * Shallow vector copy -- copies pointers to item values, not the values.
+ *
+ * Creates a new vector.
+ *
+ * NB: creates new vector with same limit as existing one, but copies only
+ * the known items (ie up to end, not up to limit).
+ */
vector
vector_copy (vector v)
{
@@ -529,12 +567,13 @@ vector_copy (vector v)
new->end = v->end;
if (v->limit > 0)
- memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->limit)) ;
+ memcpy(new->p_items, v->p_items, P_ITEMS_SIZE(v->end)) ;
return new;
}
-/* Shallow vector copy -- copies pointers to item values, not the values.
+/*------------------------------------------------------------------------------
+ * Shallow vector copy -- copies pointers to item values, not the values.
* Creates a new vector or re-initialises an existing one.
*
* NB: creates new vector with same limit as existing one, but copies only
@@ -563,7 +602,9 @@ vector_copy_here(vector dst, vector src)
return dst ;
} ;
-/* Vector move -- moves body of vector.
+/*------------------------------------------------------------------------------
+ * Vector move -- moves body of vector.
+ *
* Creates a new vector or re-initialises an existing one.
* Leaves the source vector empty -- does not release the structure.
*
@@ -575,7 +616,7 @@ vector_copy_here(vector dst, vector src)
*
* NB: do NOT try moving a vector to itself !!
*/
-vector
+extern vector
vector_move_here(vector dst, vector src)
{
assert((src != NULL) && (src != dst)) ;
@@ -585,23 +626,26 @@ vector_move_here(vector dst, vector src)
else
dst = vector_init_new(dst, 0) ; /* Create new structure sans body. */
- *dst = *src ; /* Copy the vector structure */
+ *dst = *src ; /* Copy the vector structure */
- vector_init_new(src, 0) ; /* Set empty, forgetting body */
+ vector_init_new(src, 0) ; /* Set empty, forgetting body */
return dst ;
}
-/* Shallow vector copy append -- copies pointers to item values, not the values.
+/*------------------------------------------------------------------------------
+ * Shallow vector copy append -- copies pointers to item values, not the values.
+ *
* Appends copied pointers to the destination vector.
+ *
* Creates a new destination vector if required.
*
* NB: Can append to self.
*/
-vector
+extern vector
vector_copy_append(vector dst, vector src)
{
- vector_index new_end ;
+ vector_index_t new_end ;
assert(src != NULL) ;
@@ -624,14 +668,18 @@ vector_copy_append(vector dst, vector src)
return dst ;
} ;
-/* Vector move append -- moves pointers to item values.
+/*------------------------------------------------------------------------------
+ * Vector move append -- moves pointers to item values.
+ *
* Appends moved pointers to the destination vector.
+ *
* Creates a new destination vector if required (dst == NULL).
+ *
* Leaves the source vector empty -- does not release the structure.
*
* NB: do NOT try moving a vector to itself !!
*/
-vector
+extern vector
vector_move_append(vector dst, vector src)
{
assert((src != NULL) && (src != dst)) ;
@@ -640,7 +688,7 @@ vector_move_append(vector dst, vector src)
return vector_move_here(dst, src) ; /* Easy way to do it if dst empty */
vector_copy_append(dst, src) ; /* Extend dst and copy src */
- vector_init_new(src, 0) ; /* Set empty, forgetting body */
+ vector_init_new(src, 0) ; /* Set empty, forgetting body */
return dst ;
} ;
@@ -728,19 +776,19 @@ vector_move_append(vector dst, vector src)
* to ensure the vector structure is currently valid (by vector_init_new()
* or by ensuring it is zeroized).
*/
-vector
+extern vector
vector_sak(int to_copy, vector to,
- vector dst, vector_index i_dst, unsigned int n_dst,
- vector src, vector_index i_src, unsigned int n_src, int src_move)
+ vector dst, vector_index_t i_dst, vector_length_t n_dst,
+ vector src, vector_index_t i_src, vector_length_t n_src, int src_move)
{
int dst_replace ; /* true => replace portion of 'dst' */
- vector_index new_dst_end = 0 ; /* new end of dst */
+ vector_index_t new_dst_end = 0 ; /* new end of dst */
- unsigned int n_dst_nulls ; /* number of implicit NULLs to add */
- unsigned int n_dst_move ; /* number of items to move up or down */
- unsigned int n_src_real ; /* number of items to really copy */
- unsigned int n_src_nulls ; /* number of implicit NULLs to "copy" */
+ vector_length_t n_dst_nulls ; /* number of implicit NULLs to add */
+ vector_length_t n_dst_move ; /* number of items to move up or down */
+ vector_length_t n_src_real ; /* number of items to really copy */
+ vector_length_t n_src_nulls ; /* number of implicit NULLs to "copy" */
assert((to == NULL) || (dst == NULL) || (to != dst)) ;
assert((src == NULL) || (dst == NULL) || (src != dst)) ;
@@ -866,11 +914,15 @@ vector_sak(int to_copy, vector to,
* Legacy Vector Operations
*/
-/* Set value to the smallest empty slot. */
-int
+/*------------------------------------------------------------------------------
+ * Set value to the smallest empty slot.
+ *
+ * Returns: index of slot used.
+ */
+extern int
vector_set (vector v, void *val)
{
- vector_index i;
+ vector_index_t i;
i = 0 ;
while (1)
@@ -889,41 +941,55 @@ vector_set (vector v, void *val)
v->p_items[i] = val;
- return i;
+ return i ;
}
-/* Set value to specified index slot. */
-int
-vector_set_index (vector v, vector_index i, void *val)
+/*------------------------------------------------------------------------------
+ * Set value to specified index slot.
+ *
+ * Returns: index of slot (as given)
+ */
+extern int
+vector_set_index (vector v, vector_index_t i, void *val)
{
vector_ensure (v, i);
v->p_items[i] = val;
return i;
}
-/* Look up vector. */
-p_vector_item
-vector_lookup (vector v, vector_index i)
+/*------------------------------------------------------------------------------
+ * Look up vector -- get the i'th item.
+ *
+ * Returns: the i'th item -- NULL if item is null, or i >= logical len of vector
+ */
+extern p_vector_item
+vector_lookup (vector v, vector_index_t i)
{
if (i >= v->end)
return NULL;
return v->p_items[i];
}
-/* Lookup vector, ensure it. */
-p_vector_item
-vector_lookup_ensure (vector v, vector_index i)
+/*------------------------------------------------------------------------------
+ * Lookup vector, ensure it -- get i'th item and ensure logical len > i.
+ *
+ * Returns: the i'th item -- NULL if item is null
+ */
+extern p_vector_item
+vector_lookup_ensure (vector v, vector_index_t i)
{
vector_ensure (v, i);
return v->p_items[i];
}
-/* Count the number of not empty slots. */
-vector_index
+/*------------------------------------------------------------------------------
+ * Count the number of not empty slots.
+ */
+extern vector_length_t
vector_count (vector v)
{
- vector_index i;
- unsigned count = 0;
+ vector_index_t i;
+ vector_length_t count = 0;
for (i = 0; i < v->end; i++)
if (v->p_items[i] != NULL)
@@ -936,7 +1002,8 @@ vector_count (vector v)
* Sorting and Searching vector.
*/
-/* Sort the given vector.
+/*------------------------------------------------------------------------------
+ * Sort the given vector.
*
* NB: the comparison function receives a pointer to the pointer to the
* vector item's value.
@@ -944,18 +1011,19 @@ vector_count (vector v)
* NB: if there are NULL items in the vector, the comparison function MUST
* be ready for them.
*/
-void
+extern void
vector_sort(vector v, vector_sort_cmp* cmp)
{
if (v->end <= 1)
- return ; /* Stop dead if 0 or 1 items */
+ return ; /* Stop dead if 0 or 1 items */
typedef int qsort_cmp(const void*, const void*) ;
qsort(v->p_items, v->end, sizeof(p_vector_item), (qsort_cmp*)cmp) ;
} ;
-/* Perform binary search on the given vector.
+/*------------------------------------------------------------------------------
+ * Perform binary search on the given vector.
*
* The vector MUST be sorted in the order implied by the comparison function
* given. The vector need not contain unique values, but this search makes
@@ -989,18 +1057,17 @@ vector_sort(vector v, vector_sort_cmp* cmp)
* NB: if there are NULL items in the vector, the comparison function MUST
* be ready for them.
*/
-vector_index
+extern vector_index_t
vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val,
- int* result)
+ int* result)
{
- vector_index il, iv, ih ;
+ vector_index_t il, iv, ih ;
int c ;
if (v->end <= 1)
{
- *result = (v->end == 0) ? -1 :
- cmp(&p_val, (const void* const*)&v->p_items[0]) ;
- return 0 ; /* Stop dead if 0 or 1 items */
+ *result = (v->end == 0) ? -1 : cmp(&p_val, (const cvp*)&v->p_items[0]) ;
+ return 0 ; /* Stop dead if 0 or 1 items */
} ;
/* We have at least two items. */
@@ -1008,38 +1075,38 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val,
ih = v->end - 1 ;
/* Pick off the edge cases: >= last and <= first. */
- if ((c = cmp(&p_val, (const void* const*)&v->p_items[ih])) >= 0)
+ if ((c = cmp(&p_val, (const cvp*)&v->p_items[ih])) >= 0)
{
- *result = c ; /* 0 => found. +1 => val > last */
- return ih ; /* return high index. */
+ *result = c ; /* 0 => found. +1 => val > last */
+ return ih ; /* return high index. */
} ;
- if ((c = cmp(&p_val, (const void* const*)&v->p_items[il])) <= 0)
+ if ((c = cmp(&p_val, (const cvp*)&v->p_items[il])) <= 0)
{
- *result = c ; /* 0 => found. -1 => val < first */
- return il ; /* return low index. */
+ *result = c ; /* 0 => found. -1 => val < first */
+ return il ; /* return low index. */
}
- /* Now binary chop. We know that item[il] < val < item[ih] */
- /* We also know that il < ih */
+ /* Now binary chop. We know that item[il] < val < item[ih] */
+ /* We also know that il < ih */
while (1)
{
iv = (il + ih) / 2 ;
- if (iv == il) /* true if (ih == il+1) */
- {
- *result = +1 ;
- return il ; /* return il: item[il] < val < item[il+1] */
- } ;
- /* We now know that il < iv < ih */
- c = cmp(&p_val, (const void* const*)&v->p_items[iv]) ;
+ if (iv == il) /* true if (ih == il+1) */
+ {
+ *result = +1 ;
+ return il ; /* return il: item[il] < val < item[il+1] */
+ } ;
+ /* We now know that il < iv < ih */
+ c = cmp(&p_val, (const cvp*)&v->p_items[iv]) ;
if (c == 0)
- {
- *result = 0 ;
- return iv ; /* found !! */
- }
+ {
+ *result = 0 ;
+ return iv ; /* found !! */
+ }
if (c < 0)
- ih = iv ; /* step down iv > il, so new ih > il */
+ ih = iv ; /* step down iv > il, so new ih > il */
else
- il = iv ; /* step up iv < ih, so new il < ih */
+ il = iv ; /* step up iv < ih, so new il < ih */
} ;
} ;
@@ -1053,7 +1120,8 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val,
/* Round 'x' up to a multiple of 'm' */
#define multiple(x, m) ((((x) + (m) - 1) / (m)) * (m))
-/* Set new limit to suit new end for given vector.
+/*------------------------------------------------------------------------------
+ * Set new limit to suit new end for given vector.
*
* The new limit will be at least: VECTOR_LIMIT_MIN.
*
@@ -1082,10 +1150,10 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val,
* of P_ITEMS_SIZE will ?
*/
static void
-vector_new_limit(vector v, vector_index new_end)
+vector_new_limit(vector v, vector_index_t new_end)
{
- vector_index old_limit = v->limit ;
- vector_index new_limit ;
+ vector_length_t old_limit = v->limit ;
+ vector_length_t new_limit ;
if (new_end > ((VECTOR_LIMIT_DOUBLE_MAX * 7) / 8))
{
@@ -1114,17 +1182,18 @@ vector_new_limit(vector v, vector_index new_end)
v->limit = new_limit ;
} ;
-/* Extend vector and set new (logical) end.
+/*------------------------------------------------------------------------------
+ * Extend vector and set new (logical) end.
*
* Extends body if required.
* Ensures everything between old and new end is set NULL.
*
* NB: expects new end > old end, but copes with new end <= old end.
*/
-void
-vector_extend(vector v, vector_index new_end)
+extern void
+vector_extend(vector v, vector_length_t new_end)
{
- vector_index old_end = v->end ;
+ vector_length_t old_end = v->end ;
if (new_end > v->limit)
vector_new_limit(v, new_end) ;
@@ -1134,16 +1203,17 @@ vector_extend(vector v, vector_index new_end)
memset(&v->p_items[old_end], 0, P_ITEMS_SIZE(new_end - old_end)) ;
} ;
-/* Insert entries into vector: insert 'n' NULL entries at location 'i'.
+/*------------------------------------------------------------------------------
+ * Insert entries into vector: insert 'n' NULL entries at location 'i'.
*
* Updates end (and limit) to be at least 'i' + 'n'.
* (So if 'i' < end then end becomes end + 'n', else end becomes 'i' + 'n'.)
*/
-void
-vector_insert(vector v, vector_index i, unsigned int n)
+extern void
+vector_insert(vector v, vector_index_t i, vector_length_t n)
{
- vector_index old_end, new_end ;
- unsigned int n_above ;
+ vector_length_t old_end, new_end ;
+ vector_length_t n_above ;
/* If i < old end, then we are inserting n NULLs, and need
* to shuffle at least one item up.
@@ -1154,19 +1224,19 @@ vector_insert(vector v, vector_index i, unsigned int n)
if (i < old_end)
{
if (n == 0)
- return ; /* give up now if not inserting anything */
- n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */
+ return ; /* give up now if not inserting anything */
+ n_above = old_end - i ; /* number of items to shuffle up.. >= 1 */
new_end = old_end + n ;
}
else
{
- n_above = 0 ; /* nothing to shuffle up. */
+ n_above = 0 ; /* nothing to shuffle up. */
new_end = i + n ;
i = old_end ; /* where to zeroize from */
n = new_end - old_end ; /* how much to zeroize */
} ;
- /* Now we extend the body if we need to. */
+ /* Now we extend the body if we need to. */
if (new_end > v->limit)
vector_new_limit(v, new_end) ;
v->end = new_end ;
@@ -1177,7 +1247,8 @@ vector_insert(vector v, vector_index i, unsigned int n)
memset(&v->p_items[i], 0, P_ITEMS_SIZE(n)) ;
} ;
-/* Delete items from vector: delete 'n' items at location 'i'.
+/*------------------------------------------------------------------------------
+ * Delete items from vector: delete 'n' items at location 'i'.
*
* Does nothing if 'i' is beyond current end of vector or if 'n' == 0.
*
@@ -1188,10 +1259,10 @@ vector_insert(vector v, vector_index i, unsigned int n)
* NB: it is the caller's responsibility to have released any memory allocated
* for the items that are being deleted.
*/
-void
-vector_delete(vector v, vector_index i, unsigned int n)
+extern void
+vector_delete(vector v, vector_index_t i, vector_length_t n)
{
- vector_index old_end, new_end ;
+ vector_length_t old_end, new_end ;
old_end = v->end ;
@@ -1202,7 +1273,7 @@ vector_delete(vector v, vector_index i, unsigned int n)
if ((i + n) < old_end)
{
memmove(&v->p_items[i], &v->p_items[i + n],
- P_ITEMS_SIZE(old_end - (i + n))) ;
+ P_ITEMS_SIZE(old_end - (i + n))) ;
new_end = old_end - n ;
}
else
@@ -1214,7 +1285,8 @@ vector_delete(vector v, vector_index i, unsigned int n)
v->end = new_end ; /* account for stuff dropped */
} ;
-/* Discard entries from vector: discard entries from location 'i' onwards.
+/*------------------------------------------------------------------------------
+ * Discard entries from vector: discard entries from location 'i' onwards.
*
* Releases memory from 'i' onwards.
* Releases the body altogether if this sets the vector empty ('i' == 0).
@@ -1225,8 +1297,8 @@ vector_delete(vector v, vector_index i, unsigned int n)
* NB: it is the caller's responsibility to have released any memory allocated
* for the items that are being discarded.
*/
-void
-vector_discard(vector v, vector_index i)
+extern void
+vector_discard(vector v, vector_index_t i)
{
if (i >= v->limit)
return ;
@@ -1242,34 +1314,37 @@ vector_discard(vector v, vector_index i)
} ;
} ;
-/* Chop vector at the current (logical) end.
+/*------------------------------------------------------------------------------
+ * Chop vector at the current (logical) end.
*
* Releases the body altogether if the vector is currently empty.
-*/
-void
+ */
+extern void
vector_chop(vector v)
{
- vector_index new_limit = v->end ;
+ vector_length_t new_limit = v->end ;
if (new_limit == 0)
vector_reset(v, 0) ; /* reset, without releasing the structure */
else if (new_limit != v->limit)
{
v->limit = new_limit ;
- v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items, P_ITEMS_SIZE(new_limit)) ;
+ v->p_items = XREALLOC(MTYPE_VECTOR_BODY, v->p_items,
+ P_ITEMS_SIZE(new_limit)) ;
} ;
} ;
-/* Decant vector into a new body allocated to current logical size, and
+/*------------------------------------------------------------------------------
+ * Decant vector into a new body allocated to current logical size, and
* release the old body.
*
* Releases the body altogether if the vector is currently empty.
*/
-void
+extern void
vector_decant(vector v)
{
p_vector_item* p_old_body ;
- vector_index new_limit = v->end ;
+ vector_length_t new_limit = v->end ;
if (new_limit == 0)
vector_reset(v, 0) ; /* reset, without releasing the structure */
diff --git a/lib/vector.h b/lib/vector.h
index e69e628a..bae2a2f8 100644
--- a/lib/vector.h
+++ b/lib/vector.h
@@ -25,30 +25,41 @@
#ifndef _ZEBRA_VECTOR_H
#define _ZEBRA_VECTOR_H
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
+#include "misc.h"
/*------------------------------------------------------------------------------
* types and struct for vector
*
* NB: an entirely zero structure represents an entirely empty vector.
- *
- * TODO: could force vector_index to be 32 bits ?
*/
typedef void* p_vector_item ;
-typedef unsigned int vector_index ;
+typedef unsigned int vector_index_t ;
+typedef unsigned int vector_length_t ;
+
+enum {
+ VECTOR_LENGTH_MAX = (vector_length_t)INT_MAX + 1
+} ;
-typedef struct vector* vector ; /* pointer to vector structure */
-typedef struct vector vector_t ; /* embedded vector structure */
struct vector
{
- p_vector_item* p_items ; /* pointer to array of vector item pointers */
- vector_index end ; /* number of "active" item entries */
- vector_index limit ; /* number of allocated item entries */
+ p_vector_item* p_items ; /* pointer to array of vector item pointers */
+ vector_length_t end ; /* number of "active" item entries */
+ vector_length_t limit ; /* number of allocated item entries */
};
+typedef struct vector vector_t[1] ; /* embedded vector structure */
+typedef struct vector* vector ; /* pointer to vector structure */
+
+/* Setting a vector object to all zeros is enough to initialise it to
+ * an empty vector.
+ */
+enum
+{
+ VECTOR_INIT_ALL_ZEROS = true
+} ;
+
+#define VECTOR_INIT_EMPTY { .p_items = NULL, .end = 0, .limit = 0 }
+
/* Under very controlled circumstances, may access the vector body */
typedef p_vector_item const* vector_body_t ;
@@ -83,7 +94,7 @@ typedef p_vector_item const* vector_body_t ;
/* To walk all items in a vector:
*
- * vector_index i ;
+ * vector_index_t i ;
* xxxxx* p_v ;
*
* for (VECTOR_ITEMS(v, p_v, i))
@@ -104,74 +115,67 @@ typedef p_vector_item const* vector_body_t ;
* Prototypes.
*/
-extern vector vector_init (unsigned int size);
-Inline void vector_ensure(vector v, vector_index i) ;
+extern vector vector_init (vector_length_t size);
+Inline void vector_ensure(vector v, vector_index_t i) ;
extern int vector_set (vector v, void *val);
-extern int vector_set_index (vector v, vector_index i, void *val);
+extern int vector_set_index (vector v, vector_index_t i, void *val);
#define vector_unset(v, i) (void)vector_unset_item(v, i)
-extern vector_index vector_count (vector v);
+extern vector_length_t vector_count (vector v);
extern void vector_free (vector v);
extern vector vector_copy (vector v);
-extern void *vector_lookup (vector, vector_index);
-extern void *vector_lookup_ensure (vector, vector_index);
+extern void *vector_lookup (vector, vector_index_t);
+extern void *vector_lookup_ensure (vector, vector_index_t);
-extern vector vector_init_new(vector v, unsigned int size) ;
-extern vector vector_re_init(vector v, unsigned int size) ;
-extern vector vector_reset(vector v, int free_structure) ;
-extern p_vector_item vector_ream(vector v, int free_structure) ;
+extern vector vector_init_new(vector v, vector_length_t size) ;
+extern vector vector_re_init(vector v, vector_length_t size) ;
+extern vector vector_reset(vector v, free_keep_b free_structure) ;
+extern p_vector_item vector_ream(vector v, free_keep_b free_structure) ;
-/* Reset vector and free the vector structure. */
-#define vector_reset_free(v) vector_reset(v, 1)
-/* Reset vector but free the heap structure. */
-#define vector_reset_keep(v) vector_reset(v, 0)
-/* Ream out vector and free the vector structure. */
-#define vector_ream_free(v) vector_ream(v, 1)
-/* Ream out vector but keep the vector structure. */
-#define vector_ream_keep(v) vector_ream(v, 0)
+Inline void vector_set_min_length(vector v, vector_length_t len) ;
+Private void vector_set_new_min_length(vector v, vector_length_t len) ;
-Inline void vector_set_min_length(vector v, unsigned int len) ;
-extern void vector_set_new_min_length(vector v, unsigned int len) ;
-
-Inline void vector_set_length(vector v, unsigned int len) ;
+Inline void vector_set_length(vector v, vector_length_t len) ;
#define vector_set_end(v, l) vector_set_length(v, l)
-Inline vector_index vector_length(vector v) ;
-#define vector_end(v) vector_length(v)
-Inline int vector_is_empty(vector v) ;
+Inline vector_length_t vector_length(vector v) ;
+#define vector_end(v) vector_length(v)
+Inline bool vector_is_empty(vector v) ;
Inline vector_body_t vector_body(vector v) ;
-Inline p_vector_item vector_get_item(vector v, vector_index i) ;
+Inline p_vector_item vector_get_item(vector v, vector_index_t i) ;
Inline p_vector_item vector_get_first_item(vector v) ;
Inline p_vector_item vector_get_last_item(vector v) ;
-Inline void vector_set_item(vector v, vector_index i, p_vector_item p_v) ;
-Inline void vector_assign_item(vector v, vector_index dst, vector_index src) ;
-extern p_vector_item vector_unset_item(vector v, vector_index i) ;
-extern vector_index vector_trim(vector v) ;
-extern vector_index vector_condense(vector v) ;
-
-extern void vector_insert_item(vector v, vector_index i, p_vector_item p_v) ;
-extern void vector_insert_item_here(vector v, vector_index i, int rider,
+Inline void vector_set_item(vector v, vector_index_t i, p_vector_item p_v) ;
+Inline void vector_assign_item(vector v, vector_index_t dst,
+ vector_index_t src) ;
+extern p_vector_item vector_unset_item(vector v, vector_index_t i) ;
+extern vector_length_t vector_trim(vector v) ;
+extern vector_length_t vector_condense(vector v) ;
+
+extern void vector_insert_item(vector v, vector_index_t i, p_vector_item p_v) ;
+extern void vector_insert_item_here(vector v, vector_index_t i, int rider,
p_vector_item p_v) ;
-extern void vector_move_item(vector v, vector_index dst, vector_index src) ;
-extern void vector_move_item_here(vector v, vector_index dst, int rider,
- vector_index src) ;
-extern p_vector_item vector_delete_item(vector v, vector_index i) ;
+extern void vector_move_item(vector v, vector_index_t dst, vector_index_t src) ;
+extern void vector_move_item_here(vector v, vector_index_t dst, int rider,
+ vector_index_t src) ;
+extern p_vector_item vector_delete_item(vector v, vector_index_t i) ;
extern void vector_reverse(vector v) ;
-extern void vector_part_reverse(vector v, vector_index i, unsigned int n) ;
+extern void vector_part_reverse(vector v, vector_index_t i, vector_length_t n) ;
Inline void vector_push_item(vector v, p_vector_item p_v) ;
Inline p_vector_item vector_pop_item(vector v) ;
+Inline void vector_unshift_item(vector v, p_vector_item p_v) ;
+Inline p_vector_item vector_shift_item(vector v) ;
-extern void vector_insert(vector v, vector_index i, unsigned int n) ;
-extern void vector_delete(vector v, vector_index i, unsigned int n) ;
+extern void vector_insert(vector v, vector_index_t i, vector_length_t n) ;
+extern void vector_delete(vector v, vector_index_t i, vector_length_t n) ;
-typedef int vector_bsearch_cmp(const void* const* pp_val,
- const void* const* item) ;
-vector_index vector_bsearch(vector v, vector_bsearch_cmp* cmp,
+typedef int vector_bsearch_cmp(const cvp* pp_val, const cvp* item) ;
+vector_index_t vector_bsearch(vector v, vector_bsearch_cmp* cmp,
const void* p_val, int* result) ;
-typedef int vector_sort_cmp(const void* const* a, const void* const* b) ;
+typedef int vector_sort_cmp(const cvp* a, const cvp* b) ;
void vector_sort(vector v, vector_sort_cmp* cmp) ;
extern vector vector_copy_here(vector dst, vector src) ;
@@ -193,15 +197,15 @@ extern vector vector_move_append(vector dst, vector src) ;
vector_sak(0, NULL, dst, i_dst, n_dst, src, i_src, n_src, 1)
extern vector vector_sak(int to_copy, vector to,
- vector dst, vector_index i_dst, unsigned int n_dst,
- vector src, vector_index i_src, unsigned int n_src, int src_move) ;
+ vector dst, vector_index_t i_dst, vector_length_t n_dst,
+ vector src, vector_index_t i_src, vector_length_t n_src, int src_move) ;
-extern void vector_discard(vector v, vector_index i) ;
+extern void vector_discard(vector v, vector_index_t i) ;
extern void vector_chop(vector v) ;
extern void vector_decant(vector v) ;
-Inline vector_index vector_extend_by_1(vector v) ;
-extern void vector_extend(vector v, vector_index new_end) ;
+Inline vector_index_t vector_extend_by_1(vector v) ;
+extern void vector_extend(vector v, vector_length_t new_end) ;
/*==============================================================================
* The inline functions:
@@ -210,10 +214,10 @@ extern void vector_extend(vector v, vector_index new_end) ;
/* Extend vector by one item at the end, which is about to be set. */
/* Returns index of new least item in the vector. */
/* NB: if left unset, the item may be UNDEFINED. */
-Inline vector_index
+Inline vector_index_t
vector_extend_by_1(vector v)
{
- vector_index i = v->end ;
+ vector_index_t i = v->end ;
if (i < v->limit)
return v->end++ ; /* simple if we have room */
@@ -226,7 +230,7 @@ vector_extend_by_1(vector v)
/* Adjusts logical and physical end of the vector as required, filling */
/* with NULLs upto any new logical end. */
Inline void
-vector_ensure(vector v, vector_index i)
+vector_ensure(vector v, vector_index_t i)
{
if (i < v->end) /* trivial if within vector */
return ;
@@ -242,7 +246,7 @@ vector_ensure(vector v, vector_index i)
/* with NULLs upto any new logical end -- does not allocate any more */
/* than is exactly necessary. */
Inline void
-vector_set_min_length(vector v, unsigned int len)
+vector_set_min_length(vector v, vector_length_t len)
{
if (len > v->end) /* will not reduce the length */
vector_set_new_min_length(v, len) ;
@@ -257,7 +261,7 @@ vector_set_min_length(vector v, unsigned int len)
/* with NULLs upto any new logical end -- does not allocate any more */
/* than is exactly necessary. */
Inline void
-vector_set_length(vector v, unsigned int len)
+vector_set_length(vector v, vector_length_t len)
{
if (len > v->end)
vector_set_new_min_length(v, len) ; /* Extend if new length greater */
@@ -266,14 +270,14 @@ vector_set_length(vector v, unsigned int len)
} ;
/* Return index of end of vector (index of last item + 1) */
-Inline vector_index
+Inline vector_length_t
vector_length(vector v)
{
return v->end ;
} ;
/* Returns whether vector is empty or not. */
-Inline int
+Inline bool
vector_is_empty(vector v)
{
return (v->end == 0) ;
@@ -296,7 +300,7 @@ vector_body(vector v)
/* Get pointer to item. Returns NULL if accessing beyond end. */
Inline p_vector_item
-vector_get_item(vector v, vector_index i)
+vector_get_item(vector v, vector_index_t i)
{
return (i < v->end) ? v->p_items[i] : NULL ;
} ;
@@ -319,7 +323,7 @@ vector_get_last_item(vector v)
/* NB: it is the caller's responsibility to release memory used by any */
/* current value of the item, if required. */
Inline void
-vector_set_item(vector v, vector_index i, void* p_v)
+vector_set_item(vector v, vector_index_t i, p_vector_item p_v)
{
vector_ensure(v, i) ;
v->p_items[i] = (p_vector_item)p_v ;
@@ -331,16 +335,16 @@ vector_set_item(vector v, vector_index i, void* p_v)
* used by the current dst item or the new (duplicated) src item.
*/
Inline void
-vector_assign_item(vector v, vector_index dst, vector_index src)
+vector_assign_item(vector v, vector_index_t dst, vector_index_t src)
{
vector_set_item(v, dst, vector_get_item(v, src)) ;
} ;
/* Push value onto vector, extending as required. */
Inline void
-vector_push_item(vector v, void* p_v)
+vector_push_item(vector v, p_vector_item p_v)
{
- vector_index i = vector_extend_by_1(v) ;
+ vector_index_t i = vector_extend_by_1(v) ;
v->p_items[i] = (p_vector_item)p_v ;
} ;
@@ -352,4 +356,22 @@ vector_pop_item(vector v)
return (v->end > 0) ? v->p_items[--v->end] : NULL ;
} ;
+/* Unshift value onto start of vector, extending as required. */
+Inline void
+vector_unshift_item(vector v, p_vector_item p_v)
+{
+ vector_insert(v, 0, 1) ;
+ v->p_items[0] = p_v ;
+} ;
+
+/* Pop value from vector. Returns NULL if vector is empty. */
+/* NB: does NOT change the size of the vector body. */
+Inline p_vector_item
+vector_shift_item(vector v)
+{
+ p_vector_item p_v = vector_get_first_item(v) ;
+ vector_delete(v, 0, 1) ;
+ return p_v ;
+} ;
+
#endif /* _ZEBRA_VECTOR_H */
diff --git a/lib/version.h.in b/lib/version.h.in
index 429474d1..ded43944 100644
--- a/lib/version.h.in
+++ b/lib/version.h.in
@@ -34,6 +34,8 @@
#define QUAGGA_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al."
+#include <sys/types.h>
+
pid_t pid_output (const char *);
#ifndef HAVE_DAEMON
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c
index 685f33ec..0612c465 100644
--- a/lib/vio_fifo.c
+++ b/lib/vio_fifo.c
@@ -19,15 +19,14 @@
* Boston, MA 02111-1307, USA.
*/
-#include <stddef.h>
-#include <string.h>
+#include "misc.h"
+#include <stdio.h>
#include "vio_fifo.h"
+#include "qfstring.h"
#include "network.h"
-#include "list_util.h"
#include "memory.h"
-#include "zassert.h"
/*==============================================================================
* VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer.
@@ -39,503 +38,558 @@
* ever needed.
*
* When releasing lumps, keeps one lump "spare", to be reused as necessary.
- * This is used in ... TODO <<<< And is released...
*
*------------------------------------------------------------------------------
* Implementation notes:
*
- * The FIFO is initialised with all pointers NULL -- so with no lumps at all.
+ * The FIFO is initialised with one lump in it (the "own_lump", which is
+ * embedded in the FIFO structure). There is always at least one lump in
+ * the FIFO.
*
- * Once a lump has been allocated there is always one lump in the FIFO.
+ * The hold_ptr allows the get_ptr to move forward, but retaining the data in
+ * the FIFO until the hold_ptr is cleared. Can move the get_ptr back to the
+ * hold_ptr to reread the data.
+ *
+ * The end_ptr allows put_ptr to move forward, but the new data cannot be got
+ * from the FIFO until the end_ptr is cleared. Can discard the new data
+ * and move the put_ptr back to the end_ptr.
+ *
+ * There are four lumps of interest:
+ *
+ * * head -- where the hold_mark is, if there is one.
+ *
+ * * get_lump -- where the get_ptr is.
+ * Same as head when no hold_mark.
+ *
+ * * end_lump -- where the end_mark is, if there is one.
+ * Same as tail when no end mark.
+ *
+ * * tail -- where the put_ptr is.
+ *
+ * Some or all of those may be the same, depending on how big the FIFO is.
*
* The following are expected to be true:
*
- * * put_ptr == get_ptr => FIFO empty
+ * * p_start == &get_ptr => no hold mark
+ * &hold_ptr => hold mark is set
+ *
+ * * put_ptr == get_ptr => FIFO empty -- unless *p_start != get_ptr.
*
- * * put_ptr == tail->end -- at all times (NULL when no lumps)
+ * * put_end == tail->end -- at all times
*
* put_ptr >= tail->data ) otherwise something is broken
* put_ptr <= tail->end )
*
- * * get_ptr == head->end -- when there is more than one lump
- * get_ptr <= put_ptr -- when there is only one lump
+ * * p_get_end == &get_lump->end -- when get_lump != end_lump
+ * == &end_ptr -- when get_lump == end_lump & end mark set
+ * == &put_ptr -- when get_lump == end_lump & no end mark
*
- * get_ptr >= head->data ) otherwise something is broken
- * get_ptr <= head->end )
+ * get_ptr >= get_lump->data ) otherwise something is broken
+ * get_ptr <= get_lump->end )
*
* * put_ptr == put_end => tail lump is full
* put_ptr < put_end => space exists in the tail lump
* put_ptr > put_end => broken
*
- * * get_ptr == get_end => head lump is empty
- * BUT if there is only one lump, make sure that
- * get_end == put_ptr.
- * get_ptr < get_end => data exists in the head lump
- * get_ptr > get_end => broken
+ * * get_ptr == *p_get_end => nothing to get -- get_lump == end_lump
+ * get_ptr < *p_get_end => data exists in the current get_lump
+ * get_ptr > *p_get_end => broken
+ *
+ * * p_end == &put_ptr => no end mark
+ * == &end_ptr => end mark is set
*
* Note that:
*
- * * when the get_ptr reaches the put_ptr the pointers are reset to the
- * start of the one and only lump.
+ * * while get_ptr < *p_get_end can get stuff without worrying about other
+ * pointers or moving between lumps etc.
+ *
+ * When get_ptr reaches *p_get_end, however, must move to the next lump,
+ * if possible, or collapse the pointers if have hit the put_ptr. Keeping
+ * the get_ptr honest in this way: (a) ensures that will always get from
+ * the beginning of a lump if possible; (b) simplifies the handling of
+ * hold_ptr et al (because get_ptr is never in the ambiguous position
+ * at the end of one lump, which is the same as the start of the next).
+ *
+ * Similarly, while put_ptr < put_end, can put stuff without worrying
+ * about other pointers or moving between lumps etc. Will leave the
+ * put_ptr at the very end of the current lump if just fills it.
+ *
+ * * the value of p_get_end depends on whether get_lump == end_lump, and
+ * then whether there is an end_ptr. But at any time, points to the end
+ * of what can be read by get_ptr without stepping between lumps etc.
+ *
+ * Note that get_ptr == p_get_end <=> is at the current end of the FIFO,
+ * because after any get operation, will advance to the next lump. If
+ * FIFO is empty after advancing the get_ptr, will reset the pointers
+ * back to the start of the then current (and only) lump.
+ *
+ * * some care must be taken to ensure that if the fifo is empty, the
+ * pointers will be at the start of one empty lump.
+ *
+ * In this context, empty means nothing between *p_start and put_ptr.
+ * Noting that *p_start is &hold_ptr or &get_ptr, depending on whether
+ * there is a hold mark or not. (If *p_start == put_ptr, there may be
+ * an end mark, but it must be end_ptr == put_ptr !)
+ *
+ * - get_ptr -- as above, if this hits *p_get_end, must:
*
- * Everywhere that the get_ptr is moved, must check for meeting the
- * put_ptr and reset pointers. At the same time, when reaches the end of
- * a lump, gets rid of it.
+ * * step to the next lump, if there is one, and,
+ * unless something is held behind the get_ptr,
+ * release the lump just stepped from.
*
- * * when advancing the put_ptr does not check for advancing the get_end.
+ * * if has hit put_ptr, reset pointers -- unless there
+ * is something held behind the get_ptr.
*
- * The one exception to this, is that when the put_ptr advances to a new
- * block, if there was one lump, sets the get_end to the end of that block.
+ * - put_ptr -- is always somewhere in the tail lump.
*
- * Everywhere that the get_end is used, must check for there being one
- * lump and the possibility that put_ptr has changed.
+ * - end_ptr -- when a new lump is added, if the end_ptr is at the
+ * end of the last lump, it is moved to the start of the
+ * new last lump (along with the put_ptr).
+ *
+ * If the end_ptr is equal to the put_ptr and the get_ptr
+ * hits it, if the pointers are reset, the end_ptr will be
+ * reset along with the put_ptr.
+ *
+ * If the put_ptr is reset back to the end_ptr, need to
+ * see if the FIFO is empty, and reset pointers if it is.
+ *
+ * - hold_ptr -- because the pointers are reset whenever the FIFO
+ * becomes empty, when hold_ptr is set, it will be at
+ * the start of an empty FIFO, or somewhere in a not-
+ * empty one. When a hold_ptr is cleared, the get_ptr
+ * may be equal to the put_ptr, and pointers must be
+ * reset.
*/
+inline static void vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump,
+ char* ptr) ;
+inline static void vio_fifo_set_get_end(vio_fifo vff) ;
+inline static void vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump upto) ;
+static void vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) ;
-/*==============================================================================
- * Initialisation, allocation and freeing of FIFO and lumps thereof.
+/*------------------------------------------------------------------------------
+ * Test whether there is a hold mark.
*/
-
-/* Return default size, or given size rounded up to 16 byte boundary */
-static size_t
-vio_fifo_size(size_t size)
+inline static bool
+vio_fifo_have_hold_mark(vio_fifo vff)
{
- if (size == 0)
- return 4096 ;
- else
- return ((size + 16 - 1) / 16) * 16 ;
+ return vff->p_start == &vff->hold_ptr ;
} ;
-/*==============================================================================
- * Initialise VTY I/O FIFO -- allocating if required.
+/*------------------------------------------------------------------------------
+ * Test whether there is an end mark.
*/
-extern vio_fifo
-vio_fifo_init_new(vio_fifo vf, size_t size)
+inline static bool
+vio_fifo_have_end_mark(vio_fifo vff)
{
- if (vf == NULL)
- vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ;
- else
- memset(vf, 0, sizeof(vio_fifo_t)) ;
-
- /* Zeroising the the vio_fifo_t has set:
- *
- * lump -- base pair, both pointers NULL => list is empty
- *
- * put_ptr -- NULL ) no lump to put anything into
- * put_end -- NULL ) put_ptr == put_end => no room in current lump
- *
- * get_ptr -- NULL ) no lump to get anything from
- * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump
- *
- * rdr_lump -- NULL ) no rdr_lump
- * rdr_ptr -- NULL
- *
- * spare -- NULL no spare lump
- *
- * ALSO put_ptr == get_ptr => FIFO is empty !
- */
-
- vf->size = vio_fifo_size(size) ;
-
- VIO_FIFO_DEBUG_VERIFY(vf) ;
-
- return vf ;
-}
+ return vff->p_end == &vff->end_ptr ;
+} ;
/*------------------------------------------------------------------------------
- * Free contents of given FIFO, and free FIFO structure as well, if required.
+ * The FIFO is empty, with one lump -- reset all pointers.
*
- * Does nothing if given a NULL pointer -- must already have been freed !
+ * Preserves and hold mark or end mark -- so no need to change p_start or p_end.
*
- * If does not free the FIFO structure, resets it all empty.
+ * Resets the hold_ptr and the end_ptr whether there is a hold and/or end mark
+ * or not -- saves testing for whether to do it or not. These values are
+ * only significant if p_start or p_end/p_get_end point at them.
*
- * Frees *all* FIFO lumps.
+ * HOWEVER: does not set p_get_end -- so if get_lump or end_lump or p_end have
+ * changed, then must also call vio_fifo_set_get_end().
*
- * See also: vio_fifo_reset_keep(vio_fifo)
- * vio_fifo_reset_free(vio_fifo)
+ * ALSO: does not set put_end -- so if the tail lumps has changed, that must
+ * be updated.
*/
-extern vio_fifo
-vio_fifo_reset(vio_fifo vf, int free_structure)
+inline static void
+vio_fifo_reset_ptrs(vio_fifo vff)
{
- vio_fifo_lump lump ;
+ char* ptr = ddl_tail(vff->base)->data ;
- if (vf == NULL)
- return NULL ;
+ if (vio_fifo_debug)
+ assert(ddl_head(vff->base) == ddl_tail(vff->base)) ;
- while (ddl_pop(&lump, vf->base, list) != NULL)
- XFREE(MTYPE_VIO_FIFO_LUMP, lump) ;
+ vff->hold_ptr = ptr ;
+ vff->get_ptr = ptr ;
+ vff->end_ptr = ptr ;
+ vff->put_ptr = ptr ;
+} ;
- if (vf->spare != NULL)
- XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ;
+/*------------------------------------------------------------------------------
+ * This is called *iff* get_ptr >= *p_get_end -- and preferably only after
+ * it has been adjusted forwards by at least 1 (but that is not required).
+ *
+ * If there is anything available to be got, adjust get_ptr and/or get_end
+ * in order to be able to get it -- discarding lumps as required.
+ */
+Private void
+vio_fifo_sync_get(vio_fifo vff)
+{
+ if (vio_fifo_debug)
+ assert(vff->get_ptr == *vff->p_get_end) ;
+
+ if (vff->get_lump == vff->end_lump)
+ {
+ if (vio_fifo_debug)
+ assert(vff->get_ptr == *vff->p_end) ;
- if (free_structure)
- XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */
+ /* We are in the end_lump, and there is nothing more to be read.
+ *
+ * If have reached the put_ptr, then unless there is something held
+ * behind the get_ptr, the fifo is completely empty, and pointers can
+ * be reset to the start of the end_lump (which is the only lump).
+ *
+ * p_start == &hold_ptr or &get_ptr, so can check for empty in one test.
+ */
+ if (*vff->p_start == vff->put_ptr)
+ vio_fifo_reset_ptrs(vff) ;
+ }
else
- vio_fifo_init_new(vf, vf->size) ;
+ {
+ /* Good news, can advance the get_ptr
+ *
+ * Step the get_ptr to the start of the next lump, and if no hold mark,
+ * discard any lumps which precede the new get_lump.
+ */
+ vio_fifo_lump get_lump ;
+
+ if (vio_fifo_debug)
+ {
+ assert(vff->get_lump != vff->end_lump) ;
+ assert(vff->get_ptr == vff->get_lump->end) ;
+ } ;
- return vf ;
+ get_lump = ddl_next(vff->get_lump, list) ;
+ vio_fifo_set_get_ptr(vff, get_lump, get_lump->data) ;
+
+ if (!vio_fifo_have_hold_mark(vff))
+ vio_fifo_release_up_to(vff, get_lump) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * The FIFO is empty, with one lump -- reset all pointers.
+ * Set get_lump/get_ptr and p_get_end to suit.
*/
inline static void
-vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump)
+vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump, char* ptr)
{
- if (vf->rdr_lump != NULL)
- {
- assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ;
- vf->rdr_ptr = lump->data ;
- } ;
+ vff->get_lump = lump ;
+ vff->get_ptr = ptr ;
- /* 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 ;
+ vio_fifo_set_get_end(vff) ;
} ;
/*------------------------------------------------------------------------------
- * The FIFO is utterly empty, with ZERO lumps -- unset all pointers.
+ * Set the p_get_end depending on whether the get_lump == end_lump, or not.
+ *
+ * This must be called if the get_lump or the end_lump are changed, or if
+ * p_end changes.
*/
inline static void
-vio_fifo_ptr_unset(vio_fifo vf)
+vio_fifo_set_get_end(vio_fifo vff)
{
- 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 ;
+ vff->p_get_end = (vff->get_lump == vff->end_lump) ? vff->p_end
+ : &vff->get_lump->end ;
} ;
/*------------------------------------------------------------------------------
- * Clear out contents of FIFO -- will continue to use the FIFO.
+ * Release all lumps up to (but excluding) the given lump.
*
- * Keeps one FIFO lump. (Frees everything else, including any spare.)
+ * NB: takes no notice of hold_ptr or anything else.
*/
-extern void
-vio_fifo_clear(vio_fifo vf)
+inline static void
+vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump to)
{
- vio_fifo_lump tail ;
-
- VIO_FIFO_DEBUG_VERIFY(vf) ;
-
- assert(vf != NULL) ;
-
- tail = ddl_tail(vf->base) ;
-
- if (tail != NULL)
- {
- 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) ;
+ vio_fifo_lump lump ;
+ while (ddl_head(vff->base) != to)
+ vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ;
} ;
/*------------------------------------------------------------------------------
- * See how much room there is in the FIFO.
- *
- * If no lumps have been allocated, returns the size of the lump that would
- * allocate.
+ * Release all lumps back to (but excluding) the given lump.
*
- * Otherwise, returns the amount of space available *without* allocating any
- * further lumps.
+ * Reset vff->put_end to be the end of the to->lump.
*
- * Returns: room available as described
+ * NB: takes no notice of hold_ptr or anything else.
*/
-extern size_t
-vio_fifo_room(vio_fifo vf)
+inline static void
+vio_fifo_release_back_to(vio_fifo vff, vio_fifo_lump to)
{
- if (vf->put_ptr != NULL)
- return vf->put_end - vf->put_ptr ;
- else
- return vf->size ;
+ vio_fifo_lump lump ;
+ do
+ vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ;
+ while (to != ddl_tail(vff->base)) ;
+
+ vff->put_end = to->end ;
} ;
+/*==============================================================================
+ * Initialisation, allocation and freeing of FIFO and lumps thereof.
+ */
+
/*------------------------------------------------------------------------------
- * Allocate another lump for putting into.
+ * Allocate and initialise a new FIFO.
*
- * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal.
+ * The size given is the size for all lumps in the FIFO. 0 => default size.
*
- * Set the put_ptr/put_end pointers to point at the new lump.
+ * Size is rounded up to a 128 byte boundary.
*
- * If this is the first lump allocated, set the get_ptr/get_end pointers too.
+ * Once allocated and initialised, the FIFO contains one lump, and if it
+ * grows to more than one, will retain a spare lump once it shrinks again.
*
- * If have just filled the first lump on the list, update the get_end pointer
- * to reflect the fact that the out lump is now full.
+ * Keeping a pair of lumps allows the get_ptr to lag behind the put_ptr by
+ * about a lump full, without requiring repeated memory allocation. Also,
+ * vio_fifo_write_nb() can be asked to write only lumps -- so if called
+ * regularly while putting stuff to a FIFO, will write entire lumps at once.
*/
-extern void
-vio_fifo_lump_new(vio_fifo vf, size_t size)
+extern vio_fifo
+vio_fifo_new(ulen size)
{
- vio_fifo_lump lump ;
- int first_alloc ;
+ vio_fifo vff ;
+ ulen total_size ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ if (size == 0)
+ size = VIO_FIFO_DEFAULT_LUMP_SIZE ;
- passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */
+ size = ((size + 128 - 1) / 128) * 128 ;
- if (vf->one)
- vf->get_end = vf->put_ptr ; /* update get_end */
+ if (vio_fifo_debug)
+ size = 29 ;
- lump = ddl_tail(vf->base) ;
+ total_size = offsetof(struct vio_fifo, own_lump[0].data[size]) ;
- first_alloc = (lump == NULL) ; /* extra initialisation needed */
+ vff = XCALLOC(MTYPE_VIO_FIFO, total_size) ;
- if (first_alloc)
- assert(vf->put_ptr == NULL) ; /* must all be NULL together */
- else
- assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */
+ /* Zeroising the the vio_fifo_t has set:
+ *
+ * base -- base pair, both pointers NULL => list is empty
+ *
+ * p_start -- X -- see vio_fifo_ptr_set()
+ *
+ * hold_ptr -- NULL -- not relevant until hold mark is set
+ *
+ * get_lump -- X )
+ * get_ptr -- X ) -- see vio_fifo_ptr_set()
+ * p_get_end -- X )
+ *
+ * end_lump -- X -- see vio_fifo_ptr_set()
+ *
+ * end_ptr -- NULL -- not relevant until hold mark is set
+ *
+ * put_ptr -- X )
+ * put_end -- X ) -- see vio_fifo_ptr_set()
+ * p_end -- X )
+ *
+ * size -- X -- set below
+ *
+ * spare -- NULL -- no spare lump
+ *
+ * own_lump -- all zeros: list -- pointers NULL, set below
+ * end -- set below
+ */
+ vff->size = size ;
+ vff->own_lump->end = vff->own_lump->data + vff->size ;
- size = vio_fifo_size(size) ;
+ if (vio_fifo_debug)
+ assert(vff->own_lump->end == ((char*)vff + total_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 ;
- } ;
+ ddl_append(vff->base, vff->own_lump, list) ;
- lump->end = lump->data + lump->size ;
+ vff->p_start = &vff->get_ptr ;
- ddl_append(vf->base, lump, list) ;
+ vff->get_lump = vff->own_lump ;
+ vff->get_ptr = vff->own_lump->data ;
+ vff->p_get_end = &vff->put_ptr ;
- vf->one = first_alloc ;
+ vff->end_lump = vff->own_lump ;
- vf->put_ptr = lump->data ;
- vf->put_end = lump->end ;
+ vff->put_ptr = vff->own_lump->data ;
+ vff->put_end = vff->own_lump->end ;
- if (first_alloc)
- {
- vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */
- vf->get_end = vf->put_ptr ;
- } ;
+ vff->p_end = &vff->put_ptr ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ return vff ;
} ;
/*------------------------------------------------------------------------------
- * Release lump, head or tail (or both) and update pointers.
+ * Free contents of given FIFO and the FIFO structure as well.
*
- * 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.
+ * Does nothing if given a NULL pointer -- must already have been freed !
*
- * * 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 !
+ * Returns: NULL
*/
-static void
-vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump)
+extern vio_fifo
+vio_fifo_free(vio_fifo vff)
{
- 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)
+ if (vff != NULL)
{
- vio_fifo_lump keep ;
+ vio_fifo_lump lump ;
+
+ lump = vff->spare ;
+ vff->spare = NULL ;
- /* Consistency checks */
- if (release_head)
+ do
{
- if (release_tail)
- assert(vf->get_ptr == vf->put_ptr) ;
- else
- assert(vf->get_ptr == lump->end) ;
+ if (lump != vff->own_lump)
+ XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; /* accepts lump == NULL */
- if (vf->rdr_lump == lump)
- assert(vf->rdr_ptr == vf->get_ptr) ;
+ ddl_pop(&lump, vff->base, list) ;
}
- else if (release_tail)
- {
- assert(vf->put_ptr == lump->data) ;
+ while (lump != NULL) ;
- if (vf->rdr_lump == lump)
- assert(vf->rdr_ptr == vf->put_ptr) ;
- } ;
+ XFREE(MTYPE_VIO_FIFO, vff) ;
+ } ;
- /* 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) ;
+ return NULL ;
+} ;
- keep = vf->spare ; /* expect to keep current spare */
+/*------------------------------------------------------------------------------
+ * Clear out contents of FIFO -- will continue to use the FIFO.
+ *
+ * If required, clears any hold mark and/or end mark.
+ *
+ * Keeps one spare lump.
+ *
+ * Does nothing if there is no FIFO !
+ */
+extern void
+vio_fifo_clear(vio_fifo vff, bool clear_marks)
+{
+ vio_fifo_lump lump ;
- if ((keep == NULL) || (keep->size < lump->size))
- {
- keep = lump ;
- free = vf->spare ;
- } ;
+ if (vff == NULL)
+ return ;
- vf->spare = keep ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- head = ddl_head(vf->base) ; /* changed if released head */
- tail = ddl_tail(vf->base) ; /* changed if released tail */
- } ;
+ lump = ddl_tail(vff->base) ;
- /* 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) ;
+ vff->get_lump = lump ; /* before releasing */
+ vff->end_lump = lump ;
- vio_fifo_ptr_unset(vf) ;
- }
- else
- {
- /* Have at least one lump left -- so must have had at least two ! */
- assert(!vf->one) ;
+ vio_fifo_release_up_to(vff, lump) ;
- vf->one = (head == tail) ; /* update */
+ vio_fifo_reset_ptrs(vff) ;
- 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 ;
- } ;
- } ;
+ if (clear_marks)
+ {
+ vff->p_start = &vff->get_ptr ;
+ vff->p_end = &vff->put_ptr ;
} ;
- /* Finally, free any lump that is actually to be freed */
-
- if (free != NULL)
- XFREE(MTYPE_VIO_FIFO_LUMP, free) ;
+ vff->p_get_end = vff->p_end ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*------------------------------------------------------------------------------
- * Re-allocate lump for putting into.
+ * Add a new lump to put stuff into -- work-horse for putting to the FIFO.
*
- * Call when vf->put_ptr == start of last lump, and that lump is not big
- * enough !
+ * Call when (vff->put_ptr >= vff->put_end) -- asserts that they are equal.
*
- * There must be at least one lump.
+ * The FIFO cannot be empty -- if it were, the pointers would have been reset,
+ * and could not be vff->put_ptr >= vff->put_end !!
*
- * Updates put_ptr/put_end pointers to point at the new lump.
+ * Allocates a new lump (or reuses the spare) and updates the put_ptr.
*
- * Updates get_ptr/get_end pointers if required.
- *
- * Updates rdr_ptr if required.
+ * If the end_ptr and the put_ptr were equal, then advances that too, which
+ * ensures that the end_ptr is not ambiguous.
+
+ * If the get_ptr and the put_ptr were equal, then advances that too, which
+ * ensures that the get_ptr is not ambiguous. This can be the case if there
+ * is a hold_ptr.
*/
-static void
-vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size)
+Private void
+vio_fifo_add_lump(vio_fifo vff)
{
- bool rdr_set ;
+ vio_fifo_lump lump ;
+
+ assert(vff->put_ptr == vff->put_end) ; /* must be end of tail lump */
+ assert(vff->put_ptr != *vff->p_start) ; /* cannot be empty ! */
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- /* FIFO may not be completely empty.
- * This must be the last lump.
- * The last lump must be empty.
+ /* If we can use the spare, do so, otherwise make a new one and
+ * add to the end of the FIFO.
*/
- assert((lump != NULL) && (lump == ddl_tail(vf->base))) ;
+ lump = vff->spare ;
+ vff->spare = NULL ;
+
+ if (lump == NULL)
+ {
+ ulen lump_size = offsetof(vio_fifo_lump_t, data[vff->size]) ;
- /* Remove the last, *empty* lump, and update all pointers to suit. */
- rdr_set = (vf->rdr_lump == lump) ;
+ lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, lump_size) ;
+ lump->end = (char*)lump + lump_size ;
- vio_fifo_lump_release(vf, lump) ;
+ if (vio_fifo_debug)
+ assert(lump->end == (lump->data + vff->size)) ;
+ } ;
- /* Now allocate a new lump with the required size */
- vio_fifo_lump_new(vf, size) ;
+ ddl_append(vff->base, lump, list) ;
- /* Restore the rdr_ptr, if required */
- if (rdr_set)
+ /* Allocated new lump on the end of FIFO.
+ *
+ * If the get_ptr == put_ptr, advance the get_ptr. If there is an end_ptr,
+ * it must be == put_ptr, and is about to advance too.
+ *
+ * If put_ptr == *p_end, advance the end_lump and the end_ptr. If there is
+ * no end_mark, then p_end == &put_ptr, and the end_lump must follow the
+ * put_ptr. If there is an end_mark, then p_end == &end_ptr, and that must
+ * follow the put_ptr if they are equal.
+ *
+ * The get_lump may or may not have been the end_lump, and that may or may
+ * not have changed. Simplest thing is to set p_get_end to what it should
+ * be now.
+ */
+ if (vff->get_ptr == vff->put_ptr)
{
- vio_fifo_lump tail ;
+ if (vio_fifo_debug)
+ assert(vio_fifo_have_hold_mark(vff)) ;
- tail = ddl_tail(vf->base) ;
+ vff->get_lump = lump ;
+ vff->get_ptr = lump->data ;
+ } ;
- vf->rdr_lump = tail ;
- vf->rdr_ptr = tail->data ;
+ if (vff->put_ptr == *vff->p_end)
+ {
+ vff->end_lump = lump ;
+ vff->end_ptr = lump->data ; /* no effect if no end_mark */
} ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ vff->put_ptr = lump->data ;
+ vff->put_end = lump->end ;
+
+ vio_fifo_set_get_end(vff) ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Release the given lump, provided it is neither get_lump nor end_lump.
+ *
+ * If don't have a spare lump, keep this one.
+ * If do have a spare lump, discard this one, unless it is "own_lump".
+ */
+static void
+vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump)
+{
+ assert(lump != NULL) ;
+ assert(lump != vff->get_lump) ;
+ assert(lump != vff->end_lump) ;
+
+ if (vff->spare == NULL)
+ vff->spare = lump ;
+ else
+ {
+ if (lump == vff->own_lump)
+ {
+ lump = vff->spare ; /* free the spare instead */
+ vff->spare = vff->own_lump ;
+ } ;
+
+ XFREE(MTYPE_VIO_FIFO_LUMP, lump) ;
+ } ;
} ;
/*==============================================================================
@@ -543,623 +597,912 @@ vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size)
*/
/*------------------------------------------------------------------------------
- * Store 'n' bytes -- allocate new lump if current is exhausted.
+ * Put 'n' bytes -- allocating as required.
*/
extern void
-vio_fifo_put(vio_fifo vf, const char* src, size_t n)
+vio_fifo_put_bytes(vio_fifo vff, const char* src, ulen n)
{
- size_t take ;
-
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
while (n > 0)
{
- if (vf->put_ptr >= vf->put_end)
- vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */
+ ulen take ;
+
+ if (vff->put_ptr >= vff->put_end)
+ vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */
- take = (vf->put_end - vf->put_ptr) ;
+ take = (vff->put_end - vff->put_ptr) ;
if (take > n)
take = n ;
- memcpy(vf->put_ptr, src, take) ;
- vf->put_ptr += take ;
+ memcpy(vff->put_ptr, src, take) ;
+ vff->put_ptr += take ;
src += take ;
n -= take ;
} ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*------------------------------------------------------------------------------
- * Formatted print to fifo -- cf printf()
+ * 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, ...)
+vio_fifo_printf(vio_fifo vff, const char* format, ...)
{
va_list args;
int len ;
va_start (args, format);
- len = vio_fifo_vprintf(vf, format, args);
+ len = vio_fifo_vprintf(vff, format, args);
va_end (args);
return len;
} ;
/*------------------------------------------------------------------------------
- * Formatted print to fifo -- cf vprintf()
+ * Formatted print to FIFO -- cf vprintf()
+ *
+ * Does nothing if vff is NULL !
*
* Returns: >= 0 -- number of bytes written
* < 0 -- failed (unlikely though that is)
+ *
+ * NB: uses qfs_vprintf(qfs, format, va), which allows the result to be
+ * collected a section at a time, if required. With reasonable size
+ * lumps, expect to need no more than two sections, and then only
+ * occasionally.
*/
extern int
-vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args)
+vio_fifo_vprintf(vio_fifo vff, const char *format, va_list va)
{
- va_list ac ;
- int len ;
- int have ;
- size_t size ;
- vio_fifo_lump lump ;
+ qf_str_t qfs ;
+ ulen done ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ if (vff == NULL)
+ return 0 ;
- 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) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- if (len < have)
- {
- if (len < 0)
- break ; /* quit if failed */
+ done = 0 ;
+ do
+ {
+ ulen did ;
- vf->put_ptr += len ;
- break ; /* done */
- } ;
+ if (vff->put_ptr >= vff->put_end)
+ vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */
- /* 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 */
+ qfs_init_offset(qfs, vff->put_ptr, vff->put_end - vff->put_ptr, done) ;
- lump = ddl_tail(vf->base) ;
+ did = qfs_vprintf(qfs, format, va) ;
- 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 ;
- } ;
+ done += did ;
+ vff->put_ptr += did ;
+ }
+ while (qfs_overflow(qfs) != 0) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- return len ;
+ return done ;
} ;
-/*==============================================================================
- * Get data from the FIFO.
- */
-
-static bool vio_fifo_get_next_lump(vio_fifo vf) ;
-
/*------------------------------------------------------------------------------
- * Get ready to read something out of the FIFO.
+ * Read part of file into FIFO -- assuming non-blocking file
+ *
+ * Will read up to the end of the current lump, then will read as may whole
+ * lumps as are requested -- request of 0 reads up to the end of the current
+ * lump (at least 1 byte). Will stop if would block.
*
- * Makes sure vf->get_end is up to date (if required) and if the FIFO is not
- * empty, makes sure vf->get_ptr points at the next byte to be read.
+ * Except where blocking intervenes, this reads in units of the lump size.
*
- * Returns: true <=> there is something in the FIFO.
+ * Returns: 0..n -- number of bytes read
+ * -1 => failed -- see errno
+ * -2 => EOF met immediately
+ *
+ * Note: will work perfectly well for a non-blocking file -- which should
+ * never return EAGAIN/EWOULDBLOCK, so will return from here with
+ * something, error or EOF.
*/
-static inline bool
-vio_fifo_get_ready(vio_fifo vf)
+extern int
+vio_fifo_read_nb(vio_fifo vff, int fd, ulen request)
{
- assert(vf->rdr_lump == NULL) ;
+ ulen total ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+
+ total = 0 ;
+
+ do
+ {
+ int got ;
+
+ if (vff->put_ptr >= vff->put_end)
+ {
+ vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */
+
+ if (request > 0)
+ --request ;
+ } ;
- if (vf->one)
- vf->get_end = vf->put_ptr ; /* make sure have everything */
+ got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ;
- if (vf->get_ptr >= vf->get_end)
- if (!vio_fifo_get_next_lump(vf))
- return 0 ; /* quit now if nothing there */
+ if (got <= 0)
+ {
+ if (got == -2) /* EOF met */
+ return (total > 0) ? (int)total : got ;
+ else
+ return (got == 0) ? (int)total : got ;
+ } ;
+
+ vff->put_ptr += got ;
+ total += got ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ } while (request > 0) ;
- return 1 ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+
+ return total ;
} ;
/*------------------------------------------------------------------------------
- * Get upto 'n' bytes.
+ * Strip trailing whitespace and, if required, insert '\n' if result is not
+ * empty and does not now end in '\n'
*
- * Returns: number of bytes got -- may be zero.
+ * Strips anything 0x00..0x20 except for '\n'.
+ *
+ * Returns: 0..n -- number of bytes read
+ * -1 => failed -- see errno
+ * -2 => EOF met immediately
*/
-extern size_t
-vio_fifo_get(vio_fifo vf, void* dst, size_t n)
+extern void
+vio_fifo_trim(vio_fifo vff, bool term)
{
- size_t have ;
- void* dst_in ;
+ vio_fifo_lump lump ;
+ char* p ;
+ char* s ;
+ char* end_ptr ;
+ bool end_ptr_passed ;
+ char ch ;
+
+ /* Position at end of fifo, and establish how far back in current lump
+ * can discard whitespace.
+ */
+ lump = ddl_tail(vff->base) ;
+ p = vff->put_ptr ;
+ s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ;
- if (!vio_fifo_get_ready(vf))
- return 0 ; /* quit now if nothing there */
+ if (vio_fifo_have_end_mark(vff))
+ end_ptr = vff->end_ptr ;
+ else
+ end_ptr = NULL ;
+ end_ptr_passed = false ;
- dst_in = dst ;
- while (n > 0)
+ /* Track backwards, until reach get_ptr or hit '\n' or something which is
+ * not 0x00..0x20.
+ */
+ ch = '\0' ;
+ while (1)
{
- have = vf->get_end - vf->get_ptr ;
+ if (s == p)
+ {
+ /* At the start of the current lump and/or at the get_ptr.
+ *
+ * If at the get_ptr, then cannot track any further back, but if
+ * there is a hold mark, there may be something before the get_ptr.
+ */
+ if (lump == vff->get_lump)
+ {
+ qassert(p == vff->get_ptr) ; /* hit get_ptr */
+
+ ch = '\0' ;
+ if (p == *vff->p_start)
+ break ; /* hit start of fifo */
+
+ if (p != lump->data)
+ qassert(p > lump->data) ;
+ else
+ {
+ qassert(lump != ddl_head(vff->base)) ;
+ s = ddl_prev(lump, list)->end ;
+ } ;
+
+ ch = *(s - 1) ;
+
+ if (ch != '\n')
+ ch = '\xFF' ; /* anything but '\0' */
+
+ break ;
+ } ;
+
+ lump = ddl_prev(lump, list) ;
+
+ p = lump->end ;
+ s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ;
- if (have > n)
- have = n ;
+ continue ;
+ } ;
+
+ qassert(p > s) ;
- memcpy(dst, vf->get_ptr, have) ;
- vf->get_ptr += have ;
- dst = (char*)dst + have ;
+ ch = *(p-1) ;
- if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */
- if (!vio_fifo_get_next_lump(vf))
- break ; /* quit if nothing more to come */
+ if ((ch > 0x20) || (ch == '\n'))
+ break ;
- n -= have ;
+ if (p == end_ptr)
+ end_ptr_passed = true ; /* stepped past the end mark */
+
+ --p ;
} ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ /* Decide what to do now.
+ *
+ * ch == '\0' <=> p points at end of empty fifo !
+ * ch == '\n' <=> p points after a '\n'.
+ * otherwise <=> p points after non-whitespace, or is at get_ptr and
+ * something other than '\n' precedes it.
+ *
+ * lump points at the lump that 'p' is in.
+ *
+ * If stepped back past the end_ptr, now is the time to set the end_ptr
+ * to the current position.
+ */
+ if (end_ptr_passed)
+ {
+ qassert(end_ptr != NULL) ;
- return (char*)dst - (char*)dst_in ;
+ vff->end_lump = lump ;
+ vff->end_ptr = p ;
+ } ;
+
+ /* If have stepped back across one or more lumps, trim now excess lumps off
+ * the end.
+ *
+ * Note, it is (just) possible that in inserting a '\n', that this will
+ * trim off a lump that needs to be re-instated -- but it is more
+ * straightforward to do this, and the odds are pretty slim.
+ */
+ if (lump != ddl_tail(vff->base))
+ vio_fifo_release_back_to(vff, lump) ;
+
+ /* set the (new) put_ptr.
+ */
+ vff->put_ptr = p ;
+
+ /* If we need to add a terminator, do that now.
+ */
+ if (term && (ch != '\n') && (ch != '\0'))
+ vio_fifo_put_byte(vff, '\n') ;
} ;
+/*==============================================================================
+ * Copy operations -- from one FIFO to another.
+ */
+
/*------------------------------------------------------------------------------
- * Get byte -- the long winded way.
+ * Copy src FIFO (everything from get_ptr to end mark or put_ptr) to dst FIFO.
*
- * See the inline vio_fifo_get_byte().
+ * Create a dst FIFO if there isn't one. There must be a src FIFO.
*
- * The version is used when the get_ptr is at or just before the end of the
- * current lump. Looks after all the necessary pointer updates associated with
- * hitting end of lump, or hitting end of FIFO.
+ * Appends to the dst FIFO.
*
- * Returns: 0x00..0xFF -- byte value (as an int)
- * -1 => FIFO is empty.
+ * Does not change the src FIFO in any way.
*/
-
-extern int
-vio_fifo_get_next_byte(vio_fifo vf)
+extern vio_fifo
+vio_fifo_copy(vio_fifo dst, vio_fifo src)
{
- unsigned char u ;
+ vio_fifo_lump src_lump ;
+ char* src_ptr ;
- if (!vio_fifo_get_ready(vf))
- return -1 ; /* quit now if nothing there */
+ if (dst == NULL)
+ dst = vio_fifo_new(src->size) ;
- u = *vf->get_ptr++ ;
+ VIO_FIFO_DEBUG_VERIFY(src) ;
+ VIO_FIFO_DEBUG_VERIFY(dst) ;
- /* As soon as reach the end want either to discard empty lump, or reset
- * the pointers.
- */
- if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */
- vio_fifo_get_next_lump(vf) ;
+ src_lump = src->get_lump ;
+ src_ptr = src->get_ptr ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ while (1)
+ {
+ char* src_end ;
+
+ if (src_lump != src->end_lump)
+ src_end = src_lump->end ; /* end of not end_lump */
+ else
+ src_end = *src->p_end ; /* end of end_lump */
+
+ vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ;
+
+ if (src_lump == src->end_lump)
+ break ;
+
+ src_lump = ddl_next(src_lump, list) ;
+ src_ptr = src_lump->data ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(dst) ;
- return u ;
+ return dst ;
} ;
/*------------------------------------------------------------------------------
- * Get pointer to a lump of bytes.
+ * Copy tail of src FIFO (everything from end mark to put_ptr) to dst FIFO.
*
- * Returns: address of next byte to get, *p_have = number of bytes available
- * or: NULL => FIFO is empty, *p_have = 0
+ * Create a dst FIFO if there isn't one. There must be a src FIFO.
*
- * If the FIFO is not empty, will return pointer to at least one byte.
+ * Appends to the dst FIFO.
*
- * Returns number of bytes to the end of the current lump. There may be
- * further lumps beyond the current one.
+ * Does not change the src FIFO in any way.
*/
-extern void*
-vio_fifo_get_lump(vio_fifo vf, size_t* p_have)
+extern vio_fifo
+vio_fifo_copy_tail(vio_fifo dst, vio_fifo src)
{
- if (!vio_fifo_get_ready(vf))
+ vio_fifo_lump src_lump ;
+ char* src_ptr ;
+ vio_fifo_lump tail ;
+
+ if (dst == NULL)
+ dst = vio_fifo_new(src->size) ;
+
+ VIO_FIFO_DEBUG_VERIFY(src) ;
+ VIO_FIFO_DEBUG_VERIFY(dst) ;
+
+ if (!vio_fifo_have_end_mark(src))
+ return dst ;
+
+ src_lump = src->end_lump ;
+ src_ptr = src->end_ptr ;
+ tail = ddl_tail(src->base) ;
+
+ while (1)
{
- *p_have = 0 ;
- return NULL ;
+ char* src_end ;
+
+ if (src_lump != tail)
+ src_end = src_lump->end ;
+ else
+ src_end = src->put_ptr ;
+
+ vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ;
+
+ if (src_lump == tail)
+ break ;
+
+ src_lump = ddl_next(src_lump, list) ;
+ src_ptr = src_lump->data ;
} ;
- *p_have = (vf->get_end - vf->get_ptr) ;
- return vf->get_ptr ;
+ VIO_FIFO_DEBUG_VERIFY(dst) ;
+
+ return dst ;
} ;
+/*==============================================================================
+ * End Mark Operations.
+ *
+ * Set/Clear end mark is pretty straightforward:
+ *
+ * * if there was an end_ptr before and the put_ptr is ahead of it:
+ *
+ * this adds one or more bytes between the get_ptr and the (new) end.
+ *
+ * * if there was no end_ptr, or it is the same as the put_ptr:
+ *
+ * setting/clearing the end_ptr makes no difference, because whenever
+ * the get_ptr reaches the put_ptr, the pointers are reset if they can
+ * be -- so need not worry about that here.
+ *
+ * Set the p_end and p_get_end to the new reality.
+ *
+ * The put_ptr stays in its current lump, so no need to worry about put_end.
+ */
+
/*------------------------------------------------------------------------------
- * Advance FIFO to position reached.
+ * Set end_mark at the current put position.
+ *
+ * If there was an end_mark before, move it (forward) to the current put_ptr,
+ * which keeps everything in between in the FIFO.
*
- * Having done vio_fifo_get_lump(), can take any number of bytes (up to the
- * number that "have"), then call this function to advance the pointers.
+ * If the put_ptr is at the end of the last lump, then the end_ptr will follow
+ * it if another lump is added to the FIFO.
*
- * The "here" argument must the the address returned by vio_fifo_get_lump()
- * plus the number of bytes taken.
+ * Set the p_end and p_get_end to the new reality.
*/
extern void
-vio_fifo_got_upto(vio_fifo vf, void* here)
+vio_fifo_set_end_mark(vio_fifo vff)
{
- vf->get_ptr = here ;
+ vff->p_end = &vff->end_ptr ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ vff->end_ptr = vff->put_ptr ;
+ vff->end_lump = ddl_tail(vff->base) ;
- if (vf->get_ptr >= vf->get_end)
- vio_fifo_get_next_lump(vf) ;
+ vio_fifo_set_get_end(vff) ; /* in case end_lump or p_end changed */
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*------------------------------------------------------------------------------
- * Write contents of FIFO -- assuming non-blocking file
+ * If there is an end mark, advance it to the put_ptr.
*
- * 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
+ * If there was an end_mark before, move it (forward) to the current put_ptr,
+ * which keeps everything in between in the FIFO.
*
- * Note: will work perfectly well for a non-blocking file -- which should
- * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone".
+ * If there was no end mark before, do nothing.
*/
-extern int
-vio_fifo_write_nb(vio_fifo vf, int fd, bool all)
+extern void
+vio_fifo_step_end_mark(vio_fifo vff)
{
- char* src ;
- size_t have ;
- int done ;
-
- while ((src = vio_fifo_get_lump(vf, &have)) != NULL)
+ if (vio_fifo_have_end_mark(vff))
{
- if (!all && vf->one)
- break ; /* don't write last lump */
-
- done = write_nb(fd, src, have) ;
-
- if (done < 0)
- return -1 ; /* failed */
+ vff->end_ptr = vff->put_ptr ;
+ vff->end_lump = ddl_tail(vff->base) ;
- vio_fifo_got_upto(vf, src + done) ;
+ vio_fifo_set_get_end(vff) ; /* in case end_lump changed */
- if (done < (int)have)
- return 1 ; /* blocked */
- } ;
-
- return 0 ; /* all gone */
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+ }
} ;
/*------------------------------------------------------------------------------
- * Get the current rdr_end value.
+ * If there is an end_ptr, clear it -- everything between end_ptr and the
+ * current put_ptr is kept in the FIFO.
*
- * Unlike get_end, do not have a field for this, but find it each time.
+ * Set the p_end and p_get_end to the new reality.
*/
-inline static char*
-vio_fifo_rdr_end(vio_fifo vf)
+extern void
+vio_fifo_clear_end_mark(vio_fifo vff)
{
- if (vf->rdr_lump == ddl_tail(vf->base))
- return vf->put_ptr ;
- else
- return vf->rdr_lump->end ;
+ vff->p_end = &vff->put_ptr ;
+
+ vff->end_lump = ddl_tail(vff->base) ;
+
+ vio_fifo_set_get_end(vff) ; /* in case end_lump or p_end changed */
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*------------------------------------------------------------------------------
- * Get the current rdr position -- sets it up if not currently set.
+ * Move put_ptr back to the end mark, if any, and discard data.
*
- * Returns: address of next byte to get, *p_have = number of bytes available
- * or: NULL => FIFO is empty, *p_have = 0
+ * If there is an end_mark, keep it if required.
*
- * If the FIFO is not empty, will return pointer to at least one byte.
+ * If there is no end mark, do nothing.
*
- * Returns number of bytes to the end of the current lump. There may be
- * further lumps beyond the current one.
+ * If there is an end mark but it is the same as the put_ptr, then need do
+ * nothing at all.
*
- * 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.
+ * Note that: if there is an end mark, then p_end == &end_ptr, so if
+ * *p_end == put_ptr, then end_ptr == put_ptr ; if there is no end mark,
+ * then p_end == &put_ptr, so *p_end == put_ptr !!
*/
-extern void*
-vio_fifo_get_rdr(vio_fifo vf, size_t* p_have)
+extern void
+vio_fifo_back_to_end_mark(vio_fifo vff, bool keep)
{
- if (!vio_fifo_get_ready(vf))
+ if (*vff->p_end != vff->put_ptr) /* test for not-empty end mark */
{
- *p_have = 0 ;
- return NULL ;
+ if (vio_fifo_debug)
+ assert(vio_fifo_have_end_mark(vff)) ;
+
+ if (vff->end_lump != ddl_tail(vff->base))
+ vio_fifo_release_back_to(vff, vff->end_lump) ;
+
+ if (*vff->p_start == vff->end_ptr)
+ vio_fifo_reset_ptrs(vff) ;
+ else
+ vff->put_ptr = vff->end_ptr ;
} ;
- if (vf->rdr_lump == NULL) /* set up new rdr if required */
+ if (!keep)
{
- vf->rdr_lump = ddl_head(vf->base) ;
- vf->rdr_ptr = vf->get_ptr ;
+ vff->p_end = &vff->put_ptr ;
+ vio_fifo_set_get_end(vff) ; /* in case get_lump == end_lump */
} ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
-
- *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ;
- return vf->rdr_ptr ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
+/*==============================================================================
+ * Get data from the FIFO.
+ */
+
/*------------------------------------------------------------------------------
- * Step the rdr forward by the given number of bytes.
+ * Get upto 'n' bytes -- steps past the bytes fetched.
*
- * Returns: address of next byte to get, *p_have = number of bytes available
- * or: NULL => FIFO is empty, *p_have = 0
+ * Stops at current end of FIFO (and not before).
*
- * If the FIFO is not empty, will return pointer to at least one byte.
+ * Returns: number of bytes got -- may be zero.
+ */
+extern ulen
+vio_fifo_get_bytes(vio_fifo vff, void* dst, ulen n)
+{
+ void* dst_start ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+
+ dst_start = dst ;
+ while (n > 0)
+ {
+ ulen take ;
+
+ take = *vff->p_get_end - vff->get_ptr ;
+
+ if (take > n)
+ take = n ;
+ else if (take == 0)
+ break ;
+
+ memcpy(dst, vff->get_ptr, take) ;
+ dst = (char*)dst + take ;
+
+ n -= take ;
+
+ vio_fifo_step(vff, take) ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+
+ return (char*)dst - (char*)dst_start ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write contents of FIFO -- assuming non-blocking file
*
- * Returns number of bytes to the end of the current lump. There may be
- * further lumps beyond the current one.
+ * Will write all of FIFO up to end mark or put_ptr, or upto but excluding
+ * the end_lump.
*
- * 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().
+ * Returns: > 0 => blocked
+ * 0 => all gone (up to last lump if !all)
+ * < 0 => failed -- see errno
*
- * NB: the step may NOT exceed the last reported "have".
+ * Note: will work perfectly well for a non-blocking file -- which should
+ * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone".
*/
-extern void*
-vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step)
+extern int
+vio_fifo_write_nb(vio_fifo vff, int fd, bool all)
{
- char* rdr_end ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- assert(vf->rdr_lump != NULL) ;
+ while (1)
+ {
+ ulen have ;
+ int done ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ if ((vff->get_lump == vff->end_lump) && !all)
+ break ; /* don't write last lump */
- rdr_end = vio_fifo_rdr_end(vf) ;
- vf->rdr_ptr += step ;
+ have = vio_fifo_get(vff) ;
- if (vf->rdr_ptr >= rdr_end)
- {
- assert(vf->rdr_ptr == rdr_end) ;
+ if (have == 0)
+ break ;
- if (vf->rdr_lump != ddl_tail(vf->base))
- {
- vf->rdr_lump = ddl_next(vf->rdr_lump, list) ;
- vf->rdr_ptr = vf->rdr_lump->data ;
+ done = write_nb(fd, vio_fifo_get_ptr(vff), have) ;
- rdr_end = vio_fifo_rdr_end(vf) ;
- } ;
+ if (done < 0)
+ return -1 ; /* failed */
+
+ vio_fifo_step(vff, done) ;
+
+ if (done < (int)have)
+ return 1 ; /* blocked */
} ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- *p_have = (rdr_end - vf->rdr_ptr) ;
- return (*p_have > 0) ? vf->rdr_ptr : NULL ;
+ return 0 ; /* all gone */
} ;
/*------------------------------------------------------------------------------
- * Move FIFO get position to the rdr position, if any.
+ * Write contents of FIFO -- assuming blocking file
+ *
+ * Will write all of FIFO up to end mark or put_ptr.
+ *
+ * Returns: 0 => all gone
+ * < 0 => failed -- see errno
*
- * This clears the rdr position, and removes all data between the current and
- * new get positions from the FIFO.
+ * Note: will work perfectly well for a non-blocking file -- which should
+ * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone".
*/
-extern void
-vio_fifo_sync_rdr(vio_fifo vf)
+extern int
+vio_fifo_fwrite(vio_fifo vff, FILE* file)
{
- vio_fifo_lump head ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ while (1)
+ {
+ int done ;
+ ulen have ;
- if (vf->rdr_lump == NULL)
- return ;
+ have = vio_fifo_get(vff) ;
+ if (have == 0)
+ break ;
- 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) ;
- } ;
+ done = fwrite(vio_fifo_get_ptr(vff), have, 1, file) ;
- vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */
+ if (done < 1)
+ return -1 ; /* failed */
- vf->rdr_lump = NULL ; /* clear the rdr */
- vf->rdr_ptr = NULL ;
+ vio_fifo_step(vff, have) ;
+ } ;
- 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(vff) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ return 0 ; /* all gone */
} ;
/*------------------------------------------------------------------------------
- * Drop the rdr position (if any).
+ * Skip get_ptr to the current end -- which may be the current end mark.
*
- * This clears the rdr position leaving the get position and FIFO unchanged.
+ * Does not clear any hold_ptr or end_ptr.
*/
extern void
-vio_fifo_drop_rdr(vio_fifo vf)
+vio_fifo_skip_to_end(vio_fifo vff)
{
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ /* Advance directly to the current end and then synchronise get_ptr
+ */
+ vff->get_lump = vff->end_lump ;
+ vff->get_ptr = *vff->p_end ;
+ vff->p_get_end = vff->p_end ;
- vf->rdr_lump = NULL ;
- vf->rdr_ptr = NULL ;
+ vio_fifo_sync_get(vff) ;
} ;
-/*------------------------------------------------------------------------------
- * Move on to next lump to get stuff from.
- *
- * Advance pointers etc. so that have at least one byte available, unless
- * the FIFO is entirely empty.
- *
- * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that
- * these are equal !
+/*==============================================================================
+ * Hold Mark Operations.
*
- * NB: when there is only one block, it may be that get_end is out of date,
- * and should be advanced to the current put_ptr position.
+ * Set or clear hold_ptr.
*
- * That is done here, but may be worth updating get_end before testing
- * against get_ptr.
+ * The get_ptr is unambiguous -- so the hold_ptr is, because it is only ever
+ * set equal to the get_ptr !
*
- * Returns: true <=> at least one byte in FIFO.
+ * The put_ptr stays in its current lump, so no need to worry about put_end.
+ */
+
+/*------------------------------------------------------------------------------
+ * Set hold mark -- clearing existing one, if any.
*
- * NB: if finds that the FIFO is empty, resets the pointers to the start
- * of the last lump -- does not release the last lump.
+ * Discard all contents up to the current get_ptr (easy if no hold mark), then
+ * set hold mark at get_ptr.
*/
-static bool
-vio_fifo_get_next_lump(vio_fifo vf)
+extern void
+vio_fifo_set_hold_mark(vio_fifo vff)
{
- vio_fifo_lump head ;
+ vio_fifo_release_up_to(vff, vff->get_lump) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
- assert(vf->get_ptr == vf->get_end) ;
+ if (vff->get_ptr == vff->put_ptr)
+ vio_fifo_reset_ptrs(vff) ;
+ else
+ vff->hold_ptr = vff->get_ptr ;
- head = ddl_head(vf->base) ; /* current lump for get */
+ vff->p_start = &vff->hold_ptr ;
- /* 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 == ddl_tail(vf->base)) ) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+} ;
- if (vf->get_ptr < vf->put_ptr)
- {
- /* Had an out of date vf->get_end */
- vf->get_end = vf->put_ptr ;
+/*------------------------------------------------------------------------------
+ * Clear hold mark -- if any.
+ *
+ * Discard all contents up to the current get_ptr (easy if no hold mark), then
+ * clear hold mark (no effect if not set).
+ *
+ * Note that clearing a hold_ptr in an empty FIFO resets all the pointers. To
+ * avoid that could test for an empty hold mark (*p_start == get_ptr), but the
+ * extra step in the majority case seems worse than the extra work in the
+ * minority one.
+ */
+extern void
+vio_fifo_clear_hold_mark(vio_fifo vff)
+{
+ vio_fifo_release_up_to(vff, vff->get_lump) ;
- return true ; /* FIFO not empty */
- } ;
+ if (vff->get_ptr == vff->put_ptr)
+ vio_fifo_reset_ptrs(vff) ;
- assert(vf->get_ptr == vf->put_ptr) ;
+ vff->p_start = &vff->get_ptr ;
- /* FIFO is empty -- reset pointers and exit */
- vio_fifo_ptr_reset(vf, head) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+} ;
- return false ; /* FIFO empty */
- } ;
+/*------------------------------------------------------------------------------
+ * If there is an hold_mark, reset get_ptr *back* to it, and leave the mark
+ * set or clear.
+ *
+ * If there is no hold mark, set one at the current position, if required.
+ *
+ * Setting the get_ptr back to the hold_ptr sets it to an unambiguous
+ * position. If the get_ptr == hold_ptr then if the FIFO is empty, the
+ * pointers will have been reset.
+ */
+extern void
+vio_fifo_back_to_hold_mark(vio_fifo vff, on_off_b set)
+{
+ if (vio_fifo_have_hold_mark(vff))
+ vio_fifo_set_get_ptr(vff, ddl_head(vff->base), vff->hold_ptr) ;
+
+ vff->p_start = (set) ? &vff->hold_ptr : &vff->get_ptr ;
- /* 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) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*==============================================================================
* For debug purposes -- verify the state of the given FIFO
*/
Private void
-vio_fifo_verify(vio_fifo vf)
+vio_fifo_verify(vio_fifo vff)
{
vio_fifo_lump head ;
vio_fifo_lump lump ;
vio_fifo_lump tail ;
+ bool own_seen ;
- head = ddl_head(vf->base) ;
- tail = ddl_tail(vf->base) ;
+ head = ddl_head(vff->base) ;
+ tail = ddl_tail(vff->base) ;
- /* If nothing allocated, should all be NULL & !vf->one */
- /* If something allocated, tail must not be NULL */
+ /* FIFO always has at least one lump. */
if (head == NULL)
+ zabort("head is NULL") ;
+ if (tail == NULL)
+ zabort("tail is NULL") ;
+
+ /* Make sure that the lump pointers all work
+ *
+ * When finished, know that head <= get_lump <= end_lump <= tail.
+ */
+ own_seen = false ;
+
+ lump = head ;
+ while (1)
+ {
+ if (lump == vff->own_lump)
+ own_seen = true ;
+
+ if (lump == vff->get_lump)
+ break ;
+
+ lump = ddl_next(lump, list) ;
+ if (lump == NULL)
+ zabort("ran out of lumps looking for get_lump") ;
+ } ;
+
+ while (lump != vff->end_lump)
+ {
+ lump = ddl_next(lump, list) ;
+
+ if (lump == NULL)
+ zabort("ran out of lumps looking for end_lump") ;
+
+ if (lump == vff->own_lump)
+ own_seen = true ;
+ } ;
+
+ while (lump != tail)
+ {
+ lump = ddl_next(lump, list) ;
+
+ if (lump == NULL)
+ zabort("ran out of lumps looking for tail") ;
+
+ if (lump == vff->own_lump)
+ own_seen = true ;
+ } ;
+
+ if (vff->spare == vff->own_lump)
{
- if ( (tail != 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 ;
+ if (own_seen)
+ zabort("own seen in FIFO, but is also spare") ;
}
else
{
- if (tail == NULL)
- zabort("head pointer not NULL, but tail pointer is") ;
+ if (!own_seen)
+ zabort("not found own lump in the FIFO") ;
+ } ;
+
+ /* Check that the p_start, p_get_end and p_end are valid
+ */
+ if ((vff->p_start != &vff->hold_ptr) && (vff->p_start != &vff->get_ptr))
+ zabort("p_start is neither &get_ptr nor &hold_ptr") ;
+
+ if ((vff->p_end != &vff->end_ptr) && (vff->p_end != &vff->put_ptr))
+ zabort("p_end is neither &put_ptr nor &end_ptr") ;
+
+ if (vff->get_lump == vff->end_lump)
+ {
+ if (vff->p_get_end != vff->p_end)
+ zabort("p_get_end not equal to p_end and is in end_lump") ;
+ }
+ else
+ {
+ if (vff->p_get_end != &vff->get_lump->end)
+ zabort("p_get_end not equal to get_lump->end and is not in end_lump") ;
} ;
/* Check that all the pointers are within respective lumps
*
* Know that put_end is always tail->end, but get_end need not be.
+ *
+ * When finished, know that:
+ *
+ * - get_lump == head if !hold_mark
+ * - end_lump == tail if !end_mark
+ * - that all pointers are within their respective lumps
+ * - all ptr are <= their respective ends
+ * - if hold_mark: hold_ptr <= get_ptr or head != get_lump
+ * - if end_mark: end_ptr <= put_ptr or tail != end_lump
*/
- if ( (tail->data > vf->put_ptr)
- || (vf->put_ptr > vf->put_end)
- || (vf->put_end != tail->end) )
- zabort("put pointers outside the tail lump") ;
+ if (vio_fifo_have_hold_mark(vff))
+ {
+ if ( (head->data > vff->hold_ptr)
+ || (vff->hold_ptr > head->end) )
+ zabort("hold_ptr outside the head lump") ;
- if ( (head->data > vf->get_ptr)
- || (vf->get_ptr > vf->get_end)
- || (vf->get_end > head->end) )
- zabort("get pointers outside the head lump") ;
+ if ((vff->get_lump == head) && (vff->hold_ptr > vff->get_ptr))
+ zabort("hold_ptr greater than get_ptr") ;
+ }
+ else
+ {
+ if (vff->get_lump != head)
+ zabort("no hold_ptr, but get_lump is not head") ;
+ } ;
+
+ if ( (vff->get_lump->data > vff->get_ptr)
+ || (vff->get_ptr > *vff->p_get_end)
+ || (*vff->p_get_end > vff->get_lump->end))
+ zabort("get pointers outside the get lump") ;
- /* If head == tail, should be vf->one, etc. */
- if (head == tail)
+ if (vio_fifo_have_end_mark(vff))
{
- if (!vf->one)
- zabort("have one lump, but !vf->one") ;
+ if ( (vff->end_lump->data > vff->end_ptr)
+ || (vff->end_ptr > vff->end_lump->end) )
+ zabort("end pointer outside the end lump") ;
- if (vf->get_end > vf->put_ptr)
- zabort("get_end is greater than put_ptr when vf->one") ;
+ if ((vff->end_lump == tail) && (vff->end_ptr > vff->put_ptr))
+ zabort("end pointer greater than put pointer") ;
}
else
{
- if (vf->one)
- zabort("have two or more lumps, but vf->one is true") ;
-
- if (vf->get_end != head->end)
- zabort("get_end is not head->end when !vf->one") ;
+ if (vff->end_lump != tail)
+ zabort("no end_ptr, but end_lump is not tail") ;
} ;
- /* If have an rdr_lump -- make sure everything else is valid */
- if (vf->rdr_lump != NULL)
+ if ( (tail->data > vff->put_ptr)
+ || (vff->put_ptr > vff->put_end)
+ || (vff->put_end != tail->end) )
+ zabort("put pointers outside the tail lump") ;
+
+ /* Check that if get_ptr == p_get_end, that it is empty, or there is some
+ * not-empty hold or end mark.
+ *
+ * The point is to trap any failure to reset pointers or advance the get_ptr
+ * when it hits *p_get_end.
+ */
+ if (vff->get_ptr == *vff->p_get_end)
{
- lump = head ;
- while (lump != vf->rdr_lump)
+ if (*vff->p_start != vff->put_ptr)
{
- if (lump == tail)
- zabort("rdr_lump is not part of FIFO") ;
- lump = ddl_next(lump, list) ;
+ /* Not empty -- so must have a hold and/or end */
+ if (!(vio_fifo_have_hold_mark(vff) || vio_fifo_have_end_mark(vff)))
+ zabort("get_ptr is at get_end, is not empty by no marks set") ;
} ;
+ } ;
- 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
+ /* Check that if is empty, the pointers are reset.
+ */
+ if (*vff->p_start == vff->put_ptr)
{
- if (vf->rdr_ptr != NULL)
- zabort("rdr_ptr not NULL when rdr_lump is") ;
- }
+ if ( (tail != head)
+ || (vff->get_lump != head)
+ || (vff->end_lump != head)
+ || (vff->get_ptr != head->data)
+ || (vff->put_ptr != head->data)
+ || (vff->put_end != head->end)
+ || !( (vff->p_start != &vff->hold_ptr) || (vff->hold_ptr == head->data) )
+ || !( (vff->p_end != &vff->end_ptr) || (vff->end_ptr == head->data) )
+ )
+ zabort("pointers not valid for empty fifo") ;
+ } ;
} ;
diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h
index 52f3455e..44f2cf47 100644
--- a/lib/vio_fifo.h
+++ b/lib/vio_fifo.h
@@ -22,152 +22,199 @@
#ifndef _ZEBRA_VIO_FIFO_H
#define _ZEBRA_VIO_FIFO_H
-#include "zebra.h"
-#include <stdint.h>
-#include <stdbool.h>
+#include "misc.h"
+#include "vargs.h"
+#include <stdio.h>
#include "list_util.h"
-#include "zassert.h"
-
-#ifndef Inline /* in case of compiler issues */
-#define Inline static inline
-#endif
-
-#ifndef Private /* extern, but for "friends" only */
-#define Private extern
-#endif
-
-/* GCC have printf type attribute check. */
-#ifdef __GNUC__
-#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
-#else
-#define PRINTF_ATTRIBUTE(a,b)
-#endif /* __GNUC__ */
/*==============================================================================
* VTY I/O FIFO -- buffering of arbitrary amounts of I/O.
*/
-#ifdef NDEBUG
-# define VIO_FIFO_DEBUG 0 /* NDEBUG override */
-#else
-# ifndef VIO_FIFO_DEBUG
-# define VIO_FIFO_DEBUG 1 /* Set to 1 to turn on debug checks */
+/*------------------------------------------------------------------------------
+ * Sort out VIO_FIFO_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VIO_FIFO_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set VIO_FIFO_DEBUG == 0 to turn off debug
+ * * or set VIO_FIFO_DEBUG != 0 to turn on debug
+ * * or set VIO_FIFO_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef VIO_FIFO_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(VIO_FIFO_DEBUG)
+# undef VIO_FIFO_DEBUG
+# define VIO_FIFO_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define VIO_FIFO_DEBUG QDEBUG
+#endif
+
+#ifdef VIO_FIFO_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(VIO_FIFO_NO_DEBUG)
+# undef VIO_FIFO_DEBUG
+# define VIO_FIFO_DEBUG 0
# endif
#endif
+enum { vio_fifo_debug = VIO_FIFO_DEBUG } ;
+
/*==============================================================================
* Data Structures
+ *
+ * Note that the main fifo structure contains the first "lump", so the fifo
+ * always contains at least one.
*/
-typedef struct vio_fifo vio_fifo_t ;
-typedef struct vio_fifo* vio_fifo ;
-
typedef struct vio_fifo_lump vio_fifo_lump_t ;
typedef struct vio_fifo_lump* vio_fifo_lump ;
+struct vio_fifo_lump
+{
+ struct dl_list_pair(vio_fifo_lump) list ;
+
+ char* end ; /* end of this particular lump */
+ char data[] ;
+} ;
+
struct vio_fifo
{
struct dl_base_pair(vio_fifo_lump) base ;
- bool one ;
+ char** p_start ; /* -> hold_ptr/get_ptr */
- char* put_ptr ;
- char* put_end ;
+ char* hold_ptr ; /* p_start == &hold_ptr <=> hold mark set */
+ /* implicitly in the head lump, if set */
+ vio_fifo_lump get_lump ; /* head lump unless "hold_ptr" */
char* get_ptr ;
- char* get_end ;
- vio_fifo_lump rdr_lump ;
- char* rdr_ptr ;
+ char** p_get_end ; /* -> lump->end/end_ptr/put_ptr */
+ char** p_end ; /* -> end_ptr/put_ptr */
+
+ vio_fifo_lump end_lump ; /* tail lump unless "end_ptr" */
+ char* end_ptr ; /* p_end == &end_ptr <=> end mark set */
+
+ char* put_ptr ; /* implicitly in the tail lump */
+ char* put_end ;
+
+ ulen size ; /* set when initialised */
- size_t size ;
+ vio_fifo_lump spare ; /* may be "own_lump" */
- vio_fifo_lump spare ;
+ vio_fifo_lump_t own_lump[] ; /* embedded lump */
} ;
-struct vio_fifo_lump
-{
- struct dl_list_pair(vio_fifo_lump) list ;
+typedef struct vio_fifo vio_fifo_t[1] ; /* embedded */
+typedef struct vio_fifo* vio_fifo ;
- char* end ; /* end of this particular lump */
- size_t size ; /* size of lump when allocated */
- char data[] ;
+/* Setting a FIFO object to all zeros is enough to initialise it to an
+ * empty FIFO (with default lump sizes).
+ */
+enum
+{
+ VIO_FIFO_INIT_ALL_ZEROS = true,
+ VIO_FIFO_DEFAULT_LUMP_SIZE = 4 * 1024
} ;
+#define VIO_FIFO_INIT_EMPTY { 0 }
+
/*==============================================================================
* Functions
*/
+extern vio_fifo vio_fifo_new(ulen size) ;
+extern vio_fifo vio_fifo_free(vio_fifo vff) ;
-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_clear(vio_fifo vf) ;
-Inline bool vio_fifo_empty(vio_fifo vf) ;
-extern size_t vio_fifo_room(vio_fifo vf) ;
+extern void vio_fifo_clear(vio_fifo vff, bool clear_marks) ;
+Inline bool vio_fifo_empty(vio_fifo vff) ;
+Inline bool vio_fifo_tail_empty(vio_fifo vff) ;
-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_put_bytes(vio_fifo vff, const char* src, ulen n) ;
+Inline void vio_fifo_put_byte(vio_fifo vff, char b) ;
-extern int vio_fifo_printf(vio_fifo vf, const char* format, ...)
+extern int vio_fifo_printf(vio_fifo vff, const char* format, ...)
PRINTF_ATTRIBUTE(2, 3) ;
-extern int vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) ;
-
-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) ;
-
-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 void vio_fifo_lump_new(vio_fifo vf, size_t size) ;
-Private int vio_fifo_get_next_byte(vio_fifo vf) ;
+extern int vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) ;
+extern int vio_fifo_read_nb(vio_fifo vff, int fd, ulen request) ;
+extern void vio_fifo_trim(vio_fifo vff, bool term) ;
+
+extern ulen vio_fifo_get_bytes(vio_fifo vff, void* dst, ulen n) ;
+Inline int vio_fifo_get_byte(vio_fifo vff) ;
+Inline ulen vio_fifo_get(vio_fifo vff) ;
+Inline void* vio_fifo_get_ptr(vio_fifo vff) ;
+Inline void vio_fifo_step(vio_fifo vff, ulen step) ;
+Inline ulen vio_fifo_step_get(vio_fifo vff, ulen step) ;
+
+extern vio_fifo vio_fifo_copy(vio_fifo dst, vio_fifo src) ;
+extern vio_fifo vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) ;
+
+extern int vio_fifo_write_nb(vio_fifo vff, int fd, bool all) ;
+extern int vio_fifo_fwrite(vio_fifo vff, FILE* file) ;
+
+extern void vio_fifo_skip_to_end(vio_fifo vff) ;
+extern void vio_fifo_set_end_mark(vio_fifo vff) ;
+extern void vio_fifo_step_end_mark(vio_fifo vff) ;
+extern void vio_fifo_clear_end_mark(vio_fifo vff) ;
+extern void vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) ;
+extern void vio_fifo_set_hold_mark(vio_fifo vff) ;
+extern void vio_fifo_clear_hold_mark(vio_fifo vff) ;
+extern void vio_fifo_back_to_hold_mark(vio_fifo vff, on_off_b on) ;
/*==============================================================================
* Debug -- verification function
*/
-Private void vio_fifo_verify(vio_fifo vf) ;
+Private void vio_fifo_verify(vio_fifo vff) ;
-#if VIO_FIFO_DEBUG
-# define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf)
-#else
-# define VIO_FIFO_DEBUG_VERIFY(vf)
-#endif
+#define VIO_FIFO_DEBUG_VERIFY(vff) if (vio_fifo_debug) vio_fifo_verify(vff)
/*==============================================================================
* Inline Functions
*/
+Private void vio_fifo_add_lump(vio_fifo vff) ;
+Private void vio_fifo_sync_get(vio_fifo vff) ;
+
+/*------------------------------------------------------------------------------
+ * Returns true <=> FIFO is empty -- at least: get_ptr == end_ptr (if any)
+ * or: get_ptr == put_ptr.
+ */
+Inline bool
+vio_fifo_empty(vio_fifo vff)
+{
+ return (vff == NULL) || (vff->get_ptr == *vff->p_end) ;
+} ;
+
/*------------------------------------------------------------------------------
- * Returns true <=> FIFO is empty
+ * Returns true <=> FIFO is empty beyond end_ptr (if any).
*/
Inline bool
-vio_fifo_empty(vio_fifo vf)
+vio_fifo_tail_empty(vio_fifo vff)
{
- return (vf->get_ptr == vf->put_ptr) ;
-}
+ /* if vff is NULL, treat as empty !
+ * if end_ptr is NULL, then tail is empty !
+ * else tail is empty iff end_ptr == put_ptr.
+ */
+ return (vff == NULL) || (vff->put_ptr == *vff->p_end) ;
+} ;
/*------------------------------------------------------------------------------
* Put one byte to the FIFO
*/
Inline void
-vio_fifo_put_byte(vio_fifo vf, char b)
+vio_fifo_put_byte(vio_fifo vff, char b)
{
- if (vf->put_ptr >= vf->put_end)
- vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */
+ if (vff->put_ptr >= vff->put_end)
+ vio_fifo_add_lump(vff) ; /* traps put_ptr > put_end */
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
- *vf->put_ptr++ = b ;
+ *vff->put_ptr++ = b ;
} ;
/*------------------------------------------------------------------------------
@@ -177,29 +224,73 @@ vio_fifo_put_byte(vio_fifo vf, char b)
* -1 => FIFO is empty.
*/
Inline int
-vio_fifo_get_byte(vio_fifo vf)
+vio_fifo_get_byte(vio_fifo vff)
{
- if (vf->get_end <= (vf->get_ptr + 1))
- return vio_fifo_get_next_byte(vf) ;
+ if (vff->get_ptr < *vff->p_get_end)
+ {
+ int ch ;
+ ch = (uchar)*vff->get_ptr ;
+
+ vio_fifo_step(vff, 1) ;
- VIO_FIFO_DEBUG_VERIFY(vf) ;
+ return ch ;
+ }
- return (unsigned char)*vf->get_ptr++ ;
+ return -1 ;
} ;
/*------------------------------------------------------------------------------
- * See if have at least one full lump.
+ * Get count of bytes available in the current lump.
*
- * This may be used with vio_fifo_write_nb(..., false) to use FIFO as a sort of
- * double buffer.
+ * There will always be at least 1 byte available in the current lump, unless
+ * the FIFO is empty, or at the end mark.
*
- * Returns: true <=> there is at least one full lump in the FIFO
- * (excluding the last lump if it happens to be full)
+ * Returns: address of bytes to get
*/
-Inline bool
-vio_fifo_full_lump(vio_fifo vf)
+Inline ulen
+vio_fifo_get(vio_fifo vff)
+{
+ return *vff->p_get_end - vff->get_ptr ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get pointer to bytes in the current lump.
+ *
+ * NB: to be called only after vio_fifo_get(), or vio_fifo_steo_get() have
+ * returned a non-zero value.
+ */
+Inline void*
+vio_fifo_get_ptr(vio_fifo vff)
+{
+ return vff->get_ptr ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Step FIFO past bytes used.
+ *
+ * Can be called after a vio_fifo_get() or vio_fifo_step_get().
+ *
+ * NB: the "step" argument MUST not exceed the "have" previously returned.
+ */
+Inline void
+vio_fifo_step(vio_fifo vff, ulen step)
+{
+ vff->get_ptr += step ;
+
+ if (vff->get_ptr >= *vff->p_get_end)
+ vio_fifo_sync_get(vff) ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * vio_fifo_step() forllowed by vio_fifo_get() !
+ */
+Inline ulen
+vio_fifo_step_get(vio_fifo vff, ulen step)
{
- return (!vf->one && (vf->put_ptr != NULL)) ;
+ vio_fifo_step(vff, step) ;
+ return vio_fifo_get(vff) ;
} ;
#endif /* _ZEBRA_VIO_FIFO_H */
diff --git a/lib/vio_lines.c b/lib/vio_lines.c
index c2e9c43c..cb5bf26c 100644
--- a/lib/vio_lines.c
+++ b/lib/vio_lines.c
@@ -19,10 +19,8 @@
* Boston, MA 02111-1307, USA.
*/
-#include <stdint.h>
-
+#include "misc.h"
#include "memory.h"
-#include "zassert.h"
#include "vio_lines.h"
#include "qiovec.h"
@@ -60,7 +58,7 @@
*
* WHAT IT DOES DO:
*
- * 1) maps bare '\n' to '\r''\n'.
+ * 1) maps bare '\n' to '\r''\n' -- if required.
*
* Swallows '\r' immediately before '\n' if present.
*
@@ -81,66 +79,84 @@
* 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
+ * Returns: address of vio_line_control
*/
extern vio_line_control
-vio_lc_init_new(vio_line_control lc, int width, int height)
+vio_lc_init_new(vio_line_control lc, int width, int height, const char* newline)
{
if (lc == NULL)
lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ;
else
- memset(lc, 0, sizeof(struct vio_line_control)) ;
+ memset(lc, 0, sizeof(vio_line_control_t)) ;
/* Zeroising has set:
*
- * pause = 0 -- no limit on the number of lines to append
- * paused = 0 -- not paused
+ * width = X -- set below.
+ * height = X -- set below
*
- * col = 0 -- at column 0
- * lines = 0 -- no lines collected, yet
+ * counter = X -- set below
*
- * iov = all 0 -- empty
- * writing = 0 -- not writing
+ * incomplete = false -- no incomplete line in hand
+ * fragments = NULL -- set below
+ * here = NULL -- set below
+ *
+ * qiov = NULL -- set below
+ *
+ * newline = zeros -- set below
*/
lc->width = width >= 0 ? width : 0 ;
lc->height = height >= 0 ? height : 0 ;
+ vio_lc_counter_reset(lc) ;
+
+ lc->fragments = qiovec_init_new(NULL) ;
+ lc->here = qs_new(120) ;
+
+ lc->qiov = qiovec_init_new(NULL) ;
+
+ lc->newline->base = newline ;
+ lc->newline->len = strlen(newline) ;
return lc ;
} ;
/*------------------------------------------------------------------------------
- * Reset vio_line_control (if any) -- release body and (if required) the
+ * Reset vio_line_control (if any) -- release body and (if required) free the
* structure.
*
* Returns: address of vio_line_control (if any) -- NULL if structure released
+ *
+ * NB: if the line control is not freed, it MUST be reinitialised by
+ * vio_lc_init_new() before it is used again !
+ *
+ * NB: it is the callers responsibility to release anything which was buffered
+ * for the line control to look after.
+ *
+ * It is also the callers responsibility to release the newline string,
+ * if required.
*/
extern vio_line_control
-vio_lc_reset(vio_line_control lc, bool free_structure)
+vio_lc_reset(vio_line_control lc, free_keep_b free_structure)
{
if (lc != NULL)
{
+ lc->fragments = qiovec_free(lc->fragments) ;
+ lc->here = qs_free(lc->here) ;
+
+ lc->qiov = qiovec_free(lc->qiov) ;
+
if (free_structure)
XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */
else
- vio_lc_init_new(lc, lc->width, lc->height) ;
- /* re-initialise */
+ memset(lc, 0, sizeof(vio_line_control_t)) ;
} ;
return lc ;
} ;
/*------------------------------------------------------------------------------
- * Clear given vio_line_control.
- *
- * Sets: pause = 0
- * paused = 0
- * col = 0
- * writing = 0
+ * Clear given vio_line_control -- discard all buffered lines and reset the
+ * counter.
*
* NB: it is the callers responsibility to release anything buffered because
* it was earlier appended.
@@ -148,12 +164,17 @@ vio_lc_reset(vio_line_control lc, bool free_structure)
extern void
vio_lc_clear(vio_line_control lc)
{
- qiovec_clear(&lc->qiov) ;
+ if (lc == NULL)
+ return ;
+
+ vio_lc_counter_reset(lc) ;
+
+ lc->incomplete = false ;
+
+ qiovec_clear(lc->fragments) ;
+ qs_clear(lc->here) ;
- lc->pause = 0 ;
- lc->paused = 0 ;
- lc->col = 0 ;
- lc->writing = 0 ;
+ qiovec_clear(lc->qiov) ;
} ;
/*------------------------------------------------------------------------------
@@ -162,196 +183,404 @@ vio_lc_clear(vio_line_control lc)
* 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.
+ * This may happen at almost any time when a Telnet terminal is resized.
+ * From line control perspective, can happen between calls of vio_lc_append(),
+ * vio_lc_flush() and vio_lc_write_nb().
+ *
+ * If happens while the line control has stuff buffered, then it is too late to
+ * change anything, unless was an incomplete line.
+ *
+ * Tries to sort out the counter... but is is not possible to do this perfectly.
*/
extern void
vio_lc_set_window(vio_line_control lc, int width, int height)
{
- unsigned old_height ;
+ unsigned depth ;
- old_height = lc->height ;
+ depth = 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) ;
- } ;
+ /* If counter already exhausted, or just set indefinite height, need do
+ * nothing more.
+ */
+ if ((lc->counter <= 0) || (lc->height == 0))
+ return ;
+
+ /* Counter not already exhausted, and is setting a definite height.
+ *
+ * If no height was set before, start from scratch, now.
+ */
+ if (depth == 0)
+ return vio_lc_counter_reset(lc) ;
+
+ /* Had a definite height and setting a new one.
+ *
+ * Now calculate the depth, which is how far down the old height have
+ * got to. (The counter should be less than the old height, but if it
+ * isn't we end up with a huge depth here...)
+ */
+ depth -= lc->counter ;
+
+ /* If the new height is > depth set the counter to be the new height - depth.
+ *
+ * Otherwise, set the counter to 0, so is immediately exhausted.
+ *
+ * Cannot solve the problem of what to do if the width has changed !
+ */
+ lc->counter = (lc->height > depth) ? lc->height - depth : 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) ;
- } ;
-} ;
+static uint vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) ;
+static void vio_lc_append_line(vio_line_control lc, qiov_item item) ;
/*------------------------------------------------------------------------------
* 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.
+ * Breaks the output into lines which are no longer than the lc->width. If
+ * that is zero (unset) we use a very large width indeed.
+ *
+ * Breaks the output into a number of screen lines, limited by the line counter.
+ *
+ * Effect of line counter:
+ *
+ * * if definite height, will output only as many screen lines as it can,
+ * including none at all.
+ *
+ * * if indefinite height, when the line counter is reset it is set to some
+ * limit on the number of screen lines to buffer in the line control.
+ *
+ * Note that this is not exact, in particular will *not* stop in the
+ * middle of a complete line, just because the limit of screen lines has
+ * been exceeded.
+ *
+ * * in any case, the counter may reach exactly 0 if the number of screen
+ * lines required is exactly the number of screen lines allowed.
+ *
+ * Trims trailing whitespace from each line (but not from screen lines), before
+ * breaking up into screen lines.
+ *
+ * NB: this means that does not output anything until gets a '\n'.
+ *
+ * See vio_lc_flush().
*
* Maps '\n' to '\r''\n'.
*
- * Discards '\r' if found before '\n', and possibly at other times.
+ * Discards '\r' amongst trailing whitespace, but not otherwise.
*
* If lc->width == 0, use a very large width indeed.
*
- * If lc->pause == 0, append an indefinite number of lines
+ * Returns: number of bytes able to append to the line control.
+ *
+ * NB: the buffer presented MUST be retained until the contents of the line
+ * control's buffers have been written -- see vio_lc_write_nb().
+ *
+ * NB: the line control may buffer stuff "in hand", before it reaches the
+ * output iovec, either because:
+ *
+ * a) the current line is incomplete -- nothing will be output until
+ * a '\n' is appended, or the line control is flushed.
+ *
+ * b) the line counter was exhausted while outputting a line -- this will
+ * be output on the next call of vio_lc_append(), or when the line
+ * control is flushed.
+ *
+ * So even when the buffers that feed the line control are empty,
+ * a call of vio_lc_append() may push out some lines buffered in
+ * the line control.
*
- * NB: the buffer presented MUST be retained until the contents of the
- * line control's buffers have been written.
+ * NB: all output from the line control ends with a newline.
*
- * Returns: number of bytes able to append
+ * Incomplete lines are held in the line control until they are completed,
+ * or until the line control is flushed. When the line control is flushed,
+ * unless the incomplete line is all whitespace, a newline is appended.
*/
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 ;
+ qiov_item_t item ;
+ const char* bp ;
+ const char* be ;
- /* 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 ;
+ /* If we have a line which was being output, but was interrupted part
+ * way through by line counter expiry... try to empty that out, first.
+ *
+ * This may be prevented by the line counter (partly or completely), or may
+ * exhaust it, which will be noticed, shortly, below.
+ */
+ if (!qiovec_empty(lc->fragments) && !lc->incomplete)
+ {
+ qiovec_shift(lc->fragments, item) ; /* want first fragment */
+ vio_lc_append_line(lc, item) ;
+ } ;
/* Append: stop when run out of data or run out of lines */
- end = (const char*)buf + len ;
- p = buf ;
+ be = (const char*)buf + len ;
+ bp = buf ;
- while ((p < end) && (lc->pause != pause))
+ /* If is counter exhausted, stop appending now.
+ *
+ * In vio_lc_append_line() updates the counter, and may set it -ve.
+ */
+ while ((bp < be) && (lc->counter > 0))
{
- const char* e ;
- bool nl ;
- int nlx ;
+ item->base = bp ; /* start of current fragment */
- nlx = 0 ; /* no line ending chars yet */
+ /* scan for '\n' -- note that we look for a complete line,
+ * before worrying about the line */
+ bp = memchr(bp, '\n', (be - bp)) ;
- /* 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 */
+ if (bp == NULL)
+ {
+ /* We do not have a '\n' -- rats */
+ item->len = be - item->base ;
+ qiovec_push(lc->fragments, item) ;
+
+ lc->incomplete = true ; /* have incomplete line in hand */
+
+ bp = be ;
+ break ; /* done */
+ } ;
- /* peel off trailing '\r'.
+ /* We have a '\n' -- hurrah !
+ *
+ * Have a complete line ready to be added to the qiov -- the
+ * current fragment and any buffered ones.
*
- * NB: if have not got a '\n', then this may discard a bare
- * '\r' -- but bare '\r' are undefined in any case.
+ * Trim off any trailing whitespace and establish the length of the
+ * last fragment of the current (complete) line. If the current
+ * fragment is all whitespace, works its way up any in hand fragments.
+ */
+ lc->incomplete = false ; /* definitely not */
+
+ item->len = vio_lc_trim(lc, item, bp) ;
+
+ ++bp ; /* step past the '\n' */
+
+ /* If have fragments in hand, then want to have the first fragment
+ * as the current fragment (currently the current fragment is the
+ * last fragment).
*/
- if ((e > p) && (*(e - 1) == '\r'))
+ if (!qiovec_empty(lc->fragments))
{
- --e ; /* strip the '\r' */
- ++nlx ; /* but account for it */
- }
+ qiovec_push(lc->fragments, item) ;
+ qiovec_shift(lc->fragments, item) ;
+ } ;
- /* have p..e characters and possibly nl to add to the output.
+ /* We are now ready to break the current line up into width
+ * sections, if required -- to the extend allowed by the counter.
+ *
+ * Have trimmed off any trailing whitespace, including back across
+ * any fragments. So:
*
- * 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.
+ * item = first fragment of the line
*/
- while ((p < e) && (lc->pause != pause))
+ vio_lc_append_line(lc, item) ;
+ } ;
+
+ /* Exhausted the available data or the line count */
+
+ return (bp - (const char*)buf) ; /* what have taken */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Flush anything which the line control has buffered.
+ *
+ * There are two possible cases:
+ *
+ * 1) had collected a complete line in vio_lc_append(), but when putting to
+ * the qiovec was stopped by the line counter.
+ *
+ * So have one or more screen lines buffered up, ready to go.
+ *
+ * 2) had collected an incomplete line in vio_lc_append().
+ *
+ * That will now be flushed as if a newline had been appended, except that
+ * if the result would be a newline *only*, then all the whitespace is
+ * discarded and nothing is output.
+ *
+ * If there is nothing to flush, or an incomplete line turns out to be
+ * entirely whitespace, return false.
+ *
+ * Otherwise we have something to flush, and will attempt to do so.
+ *
+ * Effect of line counter:
+ *
+ * * if definite height, will output only as many screen lines as it can,
+ * including none at all.
+ *
+ * * if indefinite height, will output as many screen lines as it takes,
+ * whatever the state of the line counter.
+ *
+ * * in any case, the counter may reach exactly 0 if the number of screen
+ * lines required is exactly the number of screen lines allowed.
+ *
+ * Returns: true <=> have something to flush (but counter may be exhausted)
+ * false <=> nothing to be flushed
+ */
+extern bool
+vio_lc_flush(vio_line_control lc)
+{
+ qiov_item_t item ;
+
+ if (qiovec_count(lc->fragments) == 0)
+ return false ;
+
+ /* We have something in hand.
+ *
+ * If was an incomplete line, now is the time to trim off any trailing
+ * whitespace -- more or less as if there had been a '\n' at this point.
+ */
+ if (lc->incomplete)
+ {
+ lc->incomplete = false ;
+
+ qiovec_pop(lc->fragments, item) ;
+
+ item->len = vio_lc_trim(lc, item, item->base + item->len) ;
+
+ if (item->len == 0)
{
- const char* t ;
- unsigned col ;
+ qassert(qiovec_empty(lc->fragments)) ;
+ return false ; /* it was all whitespace which has all
+ been discarded. */
+ } ;
+
+ qiovec_push(lc->fragments, item) ;
+ } ;
+
+ /* Have something in hand, so now attempt to output it. */
+ qiovec_shift(lc->fragments, item) ;
+ vio_lc_append_line(lc, item) ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim trailing whitespace from given fragment -- ' ', '\t' and '\r'.
+ *
+ * If required, pops fragments until finds some non-whitespace, or runs out
+ * of fragments.
+ *
+ * Returns: resulting length.
+ */
+static uint
+vio_lc_trim(vio_line_control lc, qiov_item item, const char* e)
+{
+ const char* p ;
+
+ while (1)
+ {
+ p = item->base ;
+
+ while (e > p)
+ {
+ char ch ;
+
+ ch = *(e - 1) ;
+ if ( (ch != '\r') && (ch != ' ') && (ch != '\t') )
+ return e - p ; /* <<< found non-whitespace <<< exit */
+
+ --e ;
+ } ;
- col = lc->col + (e - p) ; /* NB: e > p */
+ qiovec_pop(lc->fragments, item) ; /* pops a NULL if empty */
- if (col > width)
+ if (item->len == 0)
+ return 0 ;
+
+ e = item->base + item->len ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append line to the output qiov -- breaking it up into width sections,
+ * inserting newlines and looking out for and updating the line counter.
+ *
+ * If definite height, may stop immediately, without outputting anything.
+ * If indefinite height, will output the current line, whatever the state of
+ * the line counter.
+ *
+ * Note that do not trim whitespace from screen lines.
+ *
+ * At this point all trailing whitespace will have been removed from the
+ * original line.
+ *
+ * Requires that have previously trimmed off any trailing whitespace, including
+ * back across any fragments. So:
+ *
+ * item = first fragment of the line -- NOT in lc->fragments.
+ *
+ * It is possible for the first fragment to be empty (empty line !).
+ */
+static void
+vio_lc_append_line(vio_line_control lc, qiov_item item)
+{
+ bool done = false ;
+
+ while ((lc->counter > 0) || (lc->height == 0))
+ {
+ uint take ;
+
+ /* Take fragments or parts thereof until we have run out of output
+ * (which sets done == true) or reach the width.
+ */
+ take = (lc->width > 0) ? lc->width : UINT_MAX ;
+ while (1)
+ {
+ if (item->len <= take)
{
- /* can use only part of what there is */
- if (width > lc->col)
- t = p + (width - lc->col) ;
- /* take to edge of screen */
+ /* Use entire fragment, and step to next (if any)
+ *
+ * Note that qiovec_push() ignores zero length items, so if
+ * this is an empty line, will push no fragments and will stop
+ * here.
+ */
+ qiovec_push(lc->qiov, item) ;
+
+ if (qiovec_empty(lc->fragments))
+ {
+ done = true ; /* signal all done */
+ break ;
+ }
else
- t = p ;
- assert(t < e) ; /* if not need to deal with nl */
+ {
+ take -= item->len ;
+ qiovec_shift(lc->fragments, item) ;
+ }
}
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 */
- } ;
+ /* Use leading part of fragment, and reduce same */
+ qiovec_push_this(lc->qiov, item->base, take) ;
+ item->base += take ;
+ item->len -= take ;
- t = e ; /* take it all */
+ break ;
} ;
-
- 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)) ;
+ qiovec_push(lc->qiov, lc->newline) ;
- p += nlx ; /* step past '\r' or '\n' */
- } ;
- } ;
+ /* Count down & exit if all done. */
+ --lc->counter ;
- /* Exhausted the available data or the line count */
- assert(p <= end) ;
+ if (done)
+ return ;
+ } ;
- return (p - (const char*)buf) ; /* what have taken */
+ /* Counter exhausted, but we are not done with the current line.
+ *
+ * unshift the current item onto the (front of) the fragments qiovec.
+ * (We know we can do this straightforwardly, because either the qiovec is
+ * empty, or what was the first item has previously been shifted off.)
+ */
+ qiovec_unshift(lc->fragments, item) ;
} ;
/*------------------------------------------------------------------------------
@@ -366,15 +595,52 @@ vio_lc_append(vio_line_control lc, const void* buf, size_t len)
* -1 => failed -- see errno
*
* Sets lc->writing if write does not complete
+ *
+ * NB: when says "all done" (or failed) the caller may release all buffered
+ * material that has been accepted by vio_lc_append() -- and not before.
*/
extern int
vio_lc_write_nb(int fd, vio_line_control lc)
{
int ret ;
- ret = qiovec_write_nb(fd, &lc->qiov) ;
+ ret = qiovec_write_nb(fd, lc->qiov) ;
+
+ if (ret <= 0)
+ {
+ /* About to promise that all buffered material previously accepted by
+ * vio_lc_append() may now be released.
+ *
+ * Unfortunately, if we have any line fragments in hand, have to buffer
+ * those locally, now.
+ *
+ * NB: if have a particularly long original line, or a particularly
+ * narrow or short screen, it is possible to pass through here
+ * more than once for the same original line.
+ *
+ * In this obscure case, the line will already be buffered in
+ * lc->here. Happily qs_clear() does not disturb the body of the
+ * qstring, and qs_append_str_n will append from within its own
+ * body !
+ */
+ if (!qiovec_empty(lc->fragments))
+ {
+ qs_set_len_nn(lc->here, 0) ; /* ready to append stuff */
+ do
+ {
+ qiov_item_t item ;
+
+ qiovec_shift(lc->fragments, item) ;
+
+ qs_append_str_n(lc->here, item->base, item->len) ;
+ }
+ while (!qiovec_empty(lc->fragments)) ;
- lc->writing = (ret > 0) ;
+ /* One fragment in hand, collected from all previous fragments
+ */
+ qiovec_push_this(lc->fragments, qs_char(lc->here), qs_len(lc->here)) ;
+ } ;
+ } ;
return ret ;
} ;
diff --git a/lib/vio_lines.h b/lib/vio_lines.h
index ffef94ec..be62f143 100644
--- a/lib/vio_lines.h
+++ b/lib/vio_lines.h
@@ -22,16 +22,9 @@
#ifndef _ZEBRA_VIO_LINES_H
#define _ZEBRA_VIO_LINES_H
-#include "zebra.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
+#include "misc.h"
#include "qiovec.h"
-
-#ifndef Inline
-#define Inline static inline
-#endif
+#include "qstring.h"
/*==============================================================================
*
@@ -40,52 +33,155 @@
/*------------------------------------------------------------------------------
* Line control -- collecting lines of a given width for output.
*
- * NB: a completely zero structure is a valid, clear vio_line_control.
+ * NB: need to explicitly initialise line control in order to set the required
+ * new line, the qiovec vectors and a qstring buffer !
*/
-
-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 */
+ uint width ; /* console width -- 0 => HUGE */
+ uint height ; /* console height -- 0 => indefinite */
+
+ int counter ; /* number of lines to next pause
+ <= 0 => paused. */
+
+ bool incomplete ; /* fragments in hand are an incomplete line */
+ qiovec fragments ;
+ qstring here ; /* any fragments after write */
+
+ qiovec qiov ; /* output screen lines */
- unsigned pause ; /* number of lines to next pause
- 0 => indefinite */
- bool paused ; /* true <=> last append stopped on pause */
+ qiov_item_t newline ; /* the required sequence */
+} ;
- unsigned col ; /* current column position */
- unsigned line ; /* line number of last complete line */
+typedef struct vio_line_control* vio_line_control ;
+typedef struct vio_line_control vio_line_control_t[1] ;
- struct qiovec qiov ; /* iovec control */
- bool writing ; /* write started, but not completed */
+enum
+{
+ VIO_LINE_CONTROL_INIT_ALL_ZEROS = false
} ;
/*==============================================================================
* 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)
+ int height,
+ const char* newline) ;
+Inline vio_line_control vio_lc_new(int width, int height,
+ const char* newline) ;
+extern vio_line_control vio_lc_reset(vio_line_control lc,
+ free_keep_b free_structure) ;
+Inline vio_line_control vio_lc_free(vio_line_control lc) ;
-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) ;
+Inline void vio_lc_counter_reset(vio_line_control lc) ;
+Inline void vio_lc_clear_pause(vio_line_control lc) ;
+
+Inline bool vio_lc_counter_is_exhausted(vio_line_control lc) ;
+Inline bool vio_lc_have_complete_line_in_hand(vio_line_control lc) ;
+Inline bool vio_lc_is_empty(vio_line_control lc) ;
+Inline bool vio_lc_pending(vio_line_control lc) ;
+
extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) ;
+extern bool vio_lc_flush(vio_line_control lc) ;
extern int vio_lc_write_nb(int fd, vio_line_control lc) ;
/*------------------------------------------------------------------------------
+ * Create new line control.
+ *
+ * Returns: address of new line control.
+ */
+Inline vio_line_control
+vio_lc_new(int width, int height, const char* newline)
+{
+ return vio_lc_init_new(NULL, width, height, newline) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free given line control (if any).
+ *
+ * It is the caller's responsibility to free anything that the line control may
+ * point to.
+ *
+ * Returns: NULL
+ */
+Inline vio_line_control
+vio_lc_free(vio_line_control lc)
+{
+ return vio_lc_reset(lc, free_it) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Counter reset.
+ */
+Inline void
+vio_lc_counter_reset(vio_line_control lc)
+{
+ lc->counter = (lc->height > 0) ? lc->height : 100 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If no height is set, set counter to large number -- to do a tranche of
+ * output.
+ *
+ * Otherwise, if the line control is paused (or would pause as soon as any
+ * output is sent), reset the counter.
+ */
+Inline void
+vio_lc_clear_pause(vio_line_control lc)
+{
+ if ((lc->counter <= 0) || (lc->height == 0))
+ vio_lc_counter_reset(lc) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is the given line control counter exhausted ?
+ *
+ * Any attempt to output more stuff will be prevented -- except for
+ * vio_lc_flush() which will succeed if height is indefinite.
+ */
+Inline bool
+vio_lc_counter_is_exhausted(vio_line_control lc)
+{
+ return (lc->counter <= 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do we have a complete line "in hand" ?
+ *
+ * This will only be the case if have a definite height, and the line counter
+ * has expired.
+ */
+Inline bool
+vio_lc_have_complete_line_in_hand(vio_line_control lc)
+{
+ return !lc->incomplete && !qiovec_empty(lc->fragments) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Is given line control empty ?
+ *
+ * Is empty if the qiov is empty and there is nothing in hand.
+ *
+ * NB: if there is something in hand, it may be complete or incomplete.
+ */
+Inline bool
+vio_lc_is_empty(vio_line_control lc)
+{
+ return qiovec_empty(lc->qiov) && qiovec_empty(lc->fragments) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is there something pending, still to be written, in the qiov ?
+ *
+ * NB: ignoring anything that there may be in hand.
*/
Inline bool
-vio_lc_empty(vio_line_control lc)
+vio_lc_pending(vio_line_control lc)
{
- return qiovec_empty(&lc->qiov) ;
+ return lc->qiov->writing ;
} ;
#endif /* _ZEBRA_VIO_LINES_H */
diff --git a/lib/vty.c b/lib/vty.c
index d5d07676..831ce721 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -1,4 +1,4 @@
-/* VTY top level
+/* VTY external interface
* Copyright (C) 1997, 98 Kunihiro Ishiguro
*
* Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
@@ -21,36 +21,76 @@
* 02111-1307, USA.
*/
-#include "zebra.h"
-#include <stdbool.h>
+#include "misc.h"
#include "lib/version.h"
-#include "vty_io.h"
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <unistd.h>
+
#include "vty.h"
-#include "uty.h"
+#include "vty_local.h"
+#include "vty_io.h"
+#include "vty_io_file.h"
+#include "vty_command.h"
#include "vty_cli.h"
+#include "vty_log.h"
+#include "vio_fifo.h"
+#include "log_local.h"
#include "list_util.h"
#include "command.h"
-#include "command_queue.h"
+#include "command_local.h"
#include "command_execute.h"
+#include "command_parse.h"
#include "memory.h"
-#include "log.h"
#include "mqueue.h"
+#include "qstring.h"
+#include "qpath.h"
+#include "network.h"
+
+/*==============================================================================
+ * The vty family comprises:
+ *
+ * vty -- level visible from outside the vty/command/log family
+ * and within those families.
+ *
+ * vty_common.h -- definitions used by both external and internal users
+ * vty_local.h -- definitions used within the family only
+ *
+ * vty_io -- top level of the vio handling
+ *
+ * vty_command -- functions called by the command family
+ * vty_log -- functions called by the log family
+ *
+ * vty_cli -- terminal command line handling
+ * vty_io_term -- terminal (telnet) I/O
+ * vty_io_vsh -- vtysh I/O
+ * vty_io_file -- file I/O
+ * vty_io_shell -- system shell I/O
+ *
+ * vty_io_basic -- common low level I/O handling
+ * encapsulates the differences between qpselect and legacy
+ * thread/select worlds.
+ *
+ * vio_lines -- for terminal: handles width, CRLF, line counting etc.
+ * vio_fifo -- for all vty types, indefinite size buffer
+ * qiovec -- using writev() to output contents of buffers
+ */
/*==============================================================================
- * Variables etc. (see uty.h)
+ * Variables etc. (see vty_local.h)
*/
/* The mutex and related debug counters */
qpt_mutex_t vty_mutex ;
-#if VTY_DEBUG
-
int vty_lock_count = 0 ;
-int vty_assert_fail = 0 ;
+#if VTY_DEBUG
+int vty_assert_fail = 0 ;
#endif
/* For thread handling -- initialised in vty_init */
@@ -59,43 +99,30 @@ struct thread_master* vty_master = NULL ;
/* In the qpthreads world, have nexus for the CLI and one for the Routeing
* Engine. Some commands are processed directly in the CLI, most have to
* be sent to the Routeing Engine.
+ *
+ * If not in the qpthreads world, vty_cli_nexus == vty_cmd_nexus == NULL.
+ *
+ * If in the qpthreads world these vty_cli_nexus == vty_cmd_nexus if not
+ * actually running pthreaded.
*/
-qpn_nexus vty_cli_nexus = NULL ;
-qpn_nexus vty_cmd_nexus = NULL ;
+bool vty_nexus ; /* true <=> in the qpthreads world */
+bool vty_multi_nexus ; /* true <=> more than one qpthread */
+
+qpn_nexus vty_cli_nexus = NULL ;
+qpn_nexus vty_cmd_nexus = NULL ;
/* List of all known vio */
-vty_io vio_list_base = NULL ;
+vty_io vio_live_list = NULL ;
/* List of all vty which are in monitor state. */
-vty_io vio_monitors_base = NULL ;
-
-/* List of all vty which are on death watch */
-vty_io vio_death_watch = NULL ;
-
-/* Vty timeout value -- see "exec timeout" command */
-unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT;
-
-/* Vty access-class command */
-char *vty_accesslist_name = NULL;
-
-/* Vty access-class for IPv6. */
-char *vty_ipv6_accesslist_name = NULL;
+vty_io vio_monitor_list = NULL ;
-/* Current directory -- initialised in vty_init() */
-static char *vty_cwd = NULL;
+/* List of child processes in our care */
+vio_child vio_childer_list = NULL ;
-/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */
-bool vty_config = 0 ;
-
-/* Login password check override. */
-bool no_password_check = 0;
-
-/* Restrict unauthenticated logins? */
-const bool restricted_mode_default = 0 ;
- bool restricted_mode = 0 ;
-
-/* Watch-dog timer. */
-union vty_watch_dog vty_watch_dog = { NULL } ;
+/* See vty_child_signal_nexus_set() */
+qpt_mutex_t vty_child_signal_mutex ;
+qpn_nexus vty_child_signal_nexus = NULL ;
/*------------------------------------------------------------------------------
* VTYSH stuff
@@ -107,9 +134,12 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ;
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void uty_reset (bool final, const char* why) ;
+static void uty_reset (const char* why, bool curtains) ;
static void uty_init_commands (void) ;
-static void vty_save_cwd (void) ;
+
+//static bool vty_terminal (struct vty *);
+//static bool vty_shell_server (struct vty *);
+//static bool vty_shell_client (struct vty *);
/*------------------------------------------------------------------------------
* Tracking the initialisation state.
@@ -143,23 +173,24 @@ static enum vty_init_state vty_init_state ;
extern void
vty_init (struct thread_master *master_thread)
{
- VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */
VTY_ASSERT_CLI_THREAD() ; /* True if !qpthreads_enabled */
+ VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */
assert(vty_init_state == vty_init_pending) ;
vty_master = master_thread; /* Local pointer to the master thread */
- vty_save_cwd (); /* need cwd for config reading */
+ vio_live_list = NULL ; /* no VTYs yet */
+ vio_childer_list = NULL ;
- vio_list_base = NULL ; /* no VTYs yet */
- vio_monitors_base = NULL ;
- vio_death_watch = NULL ;
-
- vty_cli_nexus = NULL ; /* not running qnexus-wise */
+ vty_nexus = false ; /* not running qnexus-wise */
+ vty_multi_nexus = false ; /* not more than one thread either */
+ vty_cli_nexus = NULL ;
vty_cmd_nexus = NULL ;
- vty_watch_dog.anon = NULL ; /* no watch dog */
+ vty_child_signal_nexus = NULL ; /* none, yet */
+
+ uty_init_monitor() ;
uty_init_commands() ; /* install nodes */
@@ -190,10 +221,14 @@ 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 ;
+ vty_nexus = true ;
+ vty_multi_nexus = (cli != cmd) ;
+ vty_cli_nexus = cli ;
+ vty_cmd_nexus = cmd ;
+
+ qpt_mutex_init(vty_mutex, qpt_mutex_recursive);
- qpt_mutex_init(&vty_mutex, qpt_mutex_recursive);
+ qpt_mutex_init(vty_child_signal_mutex, qpt_mutex_quagga);
vty_init_state = vty_init_2nd_stage ;
} ;
@@ -214,7 +249,7 @@ vty_init_vtysh (void)
/*------------------------------------------------------------------------------
* Start the VTY going.
*
- * This starts the listeners for VTY_TERM and VTY_SHELL_SERV.
+ * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER.
*
* Also starts the watch dog.
*
@@ -222,14 +257,12 @@ vty_init_vtysh (void)
* 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 void
vty_start(const char *addr, unsigned short port, const char *path)
{
- VTY_LOCK() ;
VTY_ASSERT_CLI_THREAD() ;
+ VTY_LOCK() ;
assert( (vty_init_state == vty_init_1st_stage)
|| (vty_init_state == vty_init_2nd_stage) ) ;
@@ -249,40 +282,49 @@ extern void
vty_reset()
{
vty_reset_because("Reset") ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Reset all VTY status
- *
- * This is done in response to SIGHUP -- and runs in the CLI thread.
+ * Reset all VTY.
*
- * Half closes all VTY, leaving the death watch to tidy up once all output
- * and any command in progress have completed.
+ * This is done in response to SIGHUP/SIGINT -- and runs in the CLI pthread
+ * (if there is one).
*
- * Closes all listening sockets.
+ * Stops any command loop and closes all VTY, "final". Issues the "why"
+ * message just before closing the base output.
*
- * TODO: revoke ?
+ * Note that if a command loop is currently executing a command in the
+ * Routing Engine pthread, then when that completes the command loop will stop
+ * and close the VTY. So completion of the close depends on the CLI pthread
+ * continuing to run and consume messages. The Routing Engine will act on
+ * the SIGHUP/SIGINT/SIGTERM when it consumes a message sent to it, so any
+ * currently executing command will complete, before any further action is
+ * taken.
*
- * NB: old code discarded all output and hard closed all the VTY...
+ * Closes all listening sockets.
*/
extern void
vty_reset_because(const char* why)
{
- VTY_LOCK() ;
VTY_ASSERT_CLI_THREAD() ;
+ VTY_LOCK() ;
+
+ if (vty_init_state != vty_init_reset)
+ {
+ assert(vty_init_state == vty_init_started) ;
- assert(vty_init_state == vty_init_started) ;
+ uty_reset(why, false) ; /* not curtains */
- uty_reset(0, why) ; /* not final ! */
+ vty_init_state = vty_init_reset ;
+ } ;
- vty_init_state = vty_init_reset ;
VTY_UNLOCK() ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Restart the VTY, following a vty_reset().
+ * Restart the VTY, following a vty_reset()/vty_reset_because().
*
- * This starts the listeners for VTY_TERM and VTY_SHELL_SERV, again.
+ * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER, again.
*
* NB: may be called once, and once only, *after* a vty_reset().
*
@@ -296,7 +338,7 @@ struct vty_restart_args
} ;
MQB_ARGS_SIZE_OK(vty_restart_args) ;
-static void uty_restart_action(mqueue_block mqb, mqb_flag_t flag) ;
+static void vty_restart_action(mqueue_block mqb, mqb_flag_t flag) ;
static void uty_restart(const char *addr, unsigned short port,
const char *path) ;
extern void
@@ -308,14 +350,14 @@ vty_restart(const char *addr, unsigned short port, const char *path)
*
* Otherwise, construct and dispatch message to do a uty_restart.
*/
- if (!vty_cli_nexus)
+ if (!vty_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) ;
+ mqb = mqb_init_new(NULL, vty_restart_action, vty_cli_nexus) ;
args = mqb_get_args(mqb) ;
if (addr != NULL)
@@ -330,7 +372,7 @@ vty_restart(const char *addr, unsigned short port, const char *path)
else
args->path = NULL ;
- mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ;
+ mqueue_enqueue(vty_cli_nexus->queue, mqb, mqb_priority) ;
} ;
VTY_UNLOCK() ;
@@ -338,7 +380,7 @@ vty_restart(const char *addr, unsigned short port, const char *path)
/* Deal with the uty_restart message */
static void
-uty_restart_action(mqueue_block mqb, mqb_flag_t flag)
+vty_restart_action(mqueue_block mqb, mqb_flag_t flag)
{
struct vty_restart_args* args ;
args = mqb_get_args(mqb) ;
@@ -388,89 +430,131 @@ uty_restart(const char *addr, unsigned short port, const char *path)
extern void
vty_terminate (void)
{
+ VTY_ASSERT_CLI_THREAD() ;
+
if ( (vty_init_state == vty_init_pending)
|| (vty_init_state == vty_init_terminated) )
- return ; /* nothing to do ! */
+ return ; /* nothing to do ! */
VTY_LOCK() ;
- VTY_ASSERT_CLI_THREAD() ;
assert( (vty_init_state > vty_init_pending)
&& (vty_init_state < vty_init_terminated) ) ;
- uty_reset(1, "Shut down") ; /* final reset */
+ uty_reset("Shut down", true) ; /* curtains */
+
+ vty_child_close_register() ;
VTY_UNLOCK() ;
- qpt_mutex_destroy(&vty_mutex, 0);
+ qpt_mutex_destroy(vty_mutex, 0);
+ qpt_mutex_destroy(vty_child_signal_mutex, 0);
vty_init_state = vty_init_terminated ;
}
/*------------------------------------------------------------------------------
- * Reset -- final or for SIGHUP
+ * Reset -- for SIGHUP/SIGINT or at final curtain.
+ *
+ * Closes listeners and resets the vty timeout and access lists.
+ *
+ * Closes all vty "final" -- see uty_close(). Before can close the vty must
+ * stop any related command loop -- see uty_cmd_loop_stop():
+ *
+ * * at "not-curtains" this is called in the *CLI* pthread, with all message
+ * and event handling is still running.
+ *
+ * This is called *before* a message is sent to the Routing Engine pthread
+ * to inform it that a SIGHUP/SIGINT/SIGTERM has been seen.
+ *
+ * It is possible that the command loop for a given vty cannot be stopped
+ * immediately and must be left to continue, until it sees the CMD_STOP
+ * vio->signal which will be set.
+ *
+ * * at "curtains" the system is being terminated, all threads have been
+ * joined, so is implicity in the CLI thread, but all message and event
+ * handling has stopped
+ *
+ * At "curtains" it is always possible to stop any command loop.
+ *
+ * This is part of vty_terminate -- see above.
+ *
+ * Close down is then a two step process:
*
- * Closes listeners.
+ * 1. vty_reset()/vty_reset_because() -- which immediately closes everything
+ * that does not have to wait for a command loop to stop, and anything
+ * that remains will be closed, automatically, as soon as possible.
*
- * Closes (final) or half closes (SIGHUP) all VTY, and revokes any outstanding
- * commands.
+ * 2. vty_terminate() -- which sweeps up and closes anything that remains
+ * to be closed, and which the automatic mechanism has not got to yet.
*
- * Resets the vty timeout and access lists.
+ * So, for each vty, if uty_cmd_loop_stop() does stop the command loop, then:
*
- * 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 !
+ * * the close of the vty proceeds immediately.
+ *
+ * Otherwise:
+ *
+ * * the CMD_STOP vio->signal has been set and the vty is left open.
+ *
+ * Note that the command loop *must* be running in the vty_cmd_nexus
+ * -- see uty_cmd_loop_stop().
+ *
+ * * nothing further will happen until the Routing Engine collects the
+ * CMD_STOP vio->signal, and sends a message to pass the command loop
+ * back to the CLI pthread, to deal with the CMD_STOP.
+ *
+ * Before passing the command loop back, the Routing Engine will release
+ * the config symbol of power. So, if the Routing Engine goes on to
+ * re-read the configuration, it does not have to wait for the CLI to get
+ * round to stopping the vty.
+ *
+ * * when the CLI pthread receives the command loop back, it will collect the
+ * CMD_STOP (again) stop the command loop and close the vty.
+ *
+ * * when the Routing Engine picks up any SIGHUP/SIGTERM/etc message, it
+ * will either re-read the configuration or vty_terminate().
+ *
+ * The Routing Engine will not do this until after it has come out
+ * of the command loop, after dealing with the vio->signal, as above.
*/
static void
-uty_reset (bool curtains, const char* why)
+uty_reset (const char* why, bool curtains)
{
vty_io vio ;
vty_io next ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
uty_close_listeners() ;
- next = sdl_head(vio_list_base) ;
+ next = sdl_head(vio_live_list) ;
while (next != NULL)
{
vio = next ;
next = sdl_next(vio, vio_list) ;
- if (vio->type == VTY_TERM)
- cq_revoke(vio->vty) ;
-
- if (why != NULL)
- vio->close_reason = why ;
+ /* Save the close reason for later, unless one is already set. */
+ if ((why != NULL) && (vio->close_reason == NULL))
+ vio->close_reason = XSTRDUP(MTYPE_TMP, why) ;
- if (curtains)
+ /* Stop the command loop -- if not already stopped.
+ *
+ * If command loop is running, it will be signalled to stop, soonest.
+ */
+ if (uty_cmd_loop_stop(vio, curtains))
uty_close(vio) ;
- else
- uty_half_close(vio, why) ;
} ;
- vty_timeout_val = VTY_TIMEOUT_DEFAULT;
-
- if (vty_accesslist_name)
- {
- XFREE(MTYPE_VTY, vty_accesslist_name);
- vty_accesslist_name = NULL;
- }
+ host.vty_timeout_val = VTY_TIMEOUT_DEFAULT;
- if (vty_ipv6_accesslist_name)
- {
- XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
- vty_ipv6_accesslist_name = NULL;
- }
+ XFREE(MTYPE_HOST, host.vty_accesslist_name) ;
+ /* sets host.vty_accesslist_name = NULL */
- if (curtains && vty_cwd)
- XFREE (MTYPE_TMP, vty_cwd);
+ XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name);
+ /* sets host.vty_ipv6_accesslist_name = NULL */
if (curtains)
- uty_watch_dog_stop() ; /* and final death watch run */
+ uty_watch_dog_stop() ;
} ;
/*==============================================================================
@@ -484,59 +568,78 @@ uty_reset (bool curtains, const char* why)
/*------------------------------------------------------------------------------
* Create a new VTY of the given type
*
- * The type may NOT be: VTY_TERM or VTY_SHELL_SERV
+ * The type may NOT be: VTY_TERMINAL or VTY_SHELL_SERVER
+ *
+ * Appears only to be used by vtysh !! TODO ????
*/
-extern struct vty *
-vty_open(enum vty_type type)
+extern vty
+vty_open(vty_type_t type, node_type_t node)
{
struct vty* vty ;
VTY_LOCK() ;
- vty = uty_new(type, -1) ; /* fails for VTY_TERM or VTY_SHELL_SERV */
+ vty = uty_new(type, node) ;
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
- * command.
+ * This is used during command execution, to output the results of commands.
*
* All these end up in uty_vout -- see vty_io.
*/
/*------------------------------------------------------------------------------
* VTY output -- cf fprintf !
+ *
+ * This is for command output, which may later be suppressed
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
*/
extern int
-vty_out (struct vty *vty, const char *format, ...)
+vty_out(struct vty *vty, const char *format, ...)
{
- int result;
+ int ret ;
+ va_list args ;
VTY_LOCK() ;
- va_list args;
- va_start (args, format);
- result = uty_vout(vty, format, args);
- va_end (args);
+
+ va_start (args, format) ;
+ ret = vio_fifo_vprintf(vty->vio->obuf, format, args) ;
+ va_end (args) ;
+
VTY_UNLOCK() ;
- return result;
+ return ret ;
+}
+
+/*------------------------------------------------------------------------------
+ * VTY output -- cf write
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
+ */
+extern int
+vty_write(struct vty *vty, const void* buf, int n)
+{
+ VTY_LOCK() ;
+
+ vio_fifo_put_bytes(vty->vio->obuf, buf, n) ;
+
+ VTY_UNLOCK() ;
+ return 0 ;
}
/*------------------------------------------------------------------------------
* VTY output -- output a given numnber of spaces
+ *
+ * This is for command output, which may be suppressed
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
*/
/* 1 2 3 4 */
@@ -544,14 +647,19 @@ vty_out (struct vty *vty, const char *format, ...)
const char vty_spaces_string[] = " " ;
CONFIRM(VTY_MAX_SPACES == (sizeof(vty_spaces_string) - 1)) ;
-extern void
+extern int
vty_out_indent(struct vty *vty, int indent)
{
- while (indent > 0)
+ int ret ;
+
+ ret = 0 ;
+ while ((indent > 0) && (ret >= 0))
{
- vty_out(vty, VTY_SPACES(indent)) ;
+ ret = vty_out(vty, VTY_SPACES(indent)) ;
indent -= VTY_MAX_SPACES ;
}
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -565,9 +673,9 @@ vty_time_print (struct vty *vty, int cr)
quagga_timestamp(0, buf, sizeof(buf)) ;
if (cr)
- vty_out (vty, "%s%s", buf, VTY_NEWLINE);
+ vty_out(vty, "%s\n", buf);
else
- vty_out (vty, "%s ", buf);
+ vty_out(vty, "%s ", buf);
return;
}
@@ -575,474 +683,116 @@ vty_time_print (struct vty *vty, int cr)
/*------------------------------------------------------------------------------
* Say hello to vty interface.
*/
-void
+extern void
vty_hello (struct vty *vty)
{
- VTY_LOCK() ;
-
-#ifdef QDEBUG
- uty_out (vty, "%s%s", debug_banner, VTY_NEWLINE);
-#endif
- if (host.motdfile)
- {
- FILE *f;
- char buf[4096];
+ qpath path ;
+ const char* string ;
- 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);
-
- 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 to history is not empty or just comment
- *
- * This is for VTY_TERM type VTY.
- *
- * Outputs diagnostics if fails to parse.
- *
- * Returns: command return code
- */
-extern enum cmd_return_code
-uty_command(struct vty *vty)
-{
- enum cmd_return_code ret;
-
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
- assert(vty->vio->type == VTY_TERM) ;
+ path = (host.motdfile != NULL) ? qpath_dup(host.motdfile) : NULL ;
+ string = host.motd ;
- /* 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;
-
- GETRUSAGE(&before);
-#endif /* CONSUMED_TIME_CHECK */
-
- 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, vty->buf) ;
-#endif /* CONSUMED_TIME_CHECK */
- } ;
-
- /* Deal with the return code */
- switch (ret)
- {
- case CMD_ERR_AMBIGUOUS:
- uty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE);
- break;
+ VTY_UNLOCK() ;
- case CMD_ERR_NO_MATCH:
- uty_out (vty, "%% Unknown command.%s", VTY_NEWLINE) ;
- break;
+ if (qdebug)
+ vty_out (vty, "%s\n", debug_banner);
- case CMD_ERR_INCOMPLETE:
- uty_out (vty, "%% Command incomplete.%s", VTY_NEWLINE);
- break;
+ if (path != NULL)
+ vty_cat_file(vty, path, "motd file") ;
+ else if (string != NULL)
+ vty_out(vty, "%s", string);
- default:
- break ;
- } ;
+ /* This "\n" is a bit magic... if the motd file does not end in a "\n",
+ * then this makes sure that we start on a new line.
+ *
+ * Similarly, if the motd string doesn't end '\n', then this makes sure.
+ *
+ * This will also trim trailing space from the end of the motd message.
+ *
+ * Generally these will end in '\n', so this produces the extra blank line
+ * before the cheerful "User authentication" message, which is the most
+ * likely next line.
+ */
+ vty_out(vty, "\n") ;
- return ret ;
+ qpath_free(path) ;
} ;
/*------------------------------------------------------------------------------
- * Authentication of vty
+ * "cat" file to vty
*
- * During AUTH_NODE and AUTH_ENABLE_NODE, when a command line is dispatched by
- * any means this function is called.
*
- * Note that if the AUTH_NODE password fails too many times, the terminal is
- * closed.
- *
- * Returns: command return code
*/
-extern enum cmd_return_code
-uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do)
+extern cmd_return_code_t
+vty_cat_file(vty vty, qpath path, const char* desc)
{
- 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 ?
- *
- * In fact, all the exotic command terminators simply discard any input
- * and return.
- */
- switch (cli_do)
- {
- case cli_do_nothing:
- case cli_do_ctrl_c:
- case cli_do_ctrl_z:
- return CMD_SUCCESS ;
+ int fd ;
+ void* buf ;
- case cli_do_command:
- break ;
+ fd = uty_fd_file_open(qpath_string(path), vfd_io_read | vfd_io_blocking, 0) ;
- 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;
- 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;
-
- default:
- zabort("unknown node type") ;
- }
-
- if (passwd)
+ if (fd < 0)
{
- if (host.encrypt)
- fail = strcmp (crypt(buf, passwd), passwd);
- else
- fail = strcmp (buf, passwd);
- }
- else
- fail = 1;
+ vty_out (vty, "Cannot open %s file '%s': %s (%s)\n", desc,
+ qpath_string(path), errtostr(errno, 0).str,
+ errtoname(errno, 0).str) ;
+ return CMD_WARNING;
+ } ;
- ret = CMD_SUCCESS ;
+ enum { buffer_size = 32 * 1024 } ;
+ buf = XMALLOC(MTYPE_TMP, buffer_size) ;
- if (! fail)
- {
- vio->fail = 0;
- vty->node = next_node; /* Success ! */
- }
- else
+ while (1)
{
- vio->fail++;
- if (vio->fail >= 3)
- {
- if (vty->node == AUTH_NODE)
- {
- ret = uty_cmd_close(vty, "Bad passwords, too many failures!%s") ;
- }
- else
- {
- /* AUTH_ENABLE_NODE */
- vio->fail = 0;
- uty_out (vty, "%% Bad enable passwords, too many failures!%s",
- VTY_NEWLINE);
- vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE;
-
- ret = CMD_WARNING ;
- }
- }
- }
-
- return ret ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Command line "exit" command -- aka "quit"
- *
- * Falls back one NODE level.
- *
- * Returns: command return code
- */
-extern enum cmd_return_code
-vty_cmd_exit(struct vty* vty)
-{
- enum cmd_return_code ret ;
+ int r ;
- VTY_LOCK() ;
+ r = readn(fd, buf, buffer_size) ;
- ret = CMD_SUCCESS ;
- switch (vty->node)
- {
- case VIEW_NODE:
- case ENABLE_NODE:
- case RESTRICTED_NODE:
- if (vty_shell (vty))
- exit (0);
+ if (r > 0)
+ vty_write(vty, buf, r) ; // TODO push ??
else
- ret = uty_cmd_close(vty, "Exit") ;
- break;
- case CONFIG_NODE:
- uty_config_unlock (vty, ENABLE_NODE);
- break;
- case INTERFACE_NODE:
- case ZEBRA_NODE:
- case BGP_NODE:
- case RIP_NODE:
- case RIPNG_NODE:
- case OSPF_NODE:
- case OSPF6_NODE:
- case ISIS_NODE:
- case KEYCHAIN_NODE:
- case MASC_NODE:
- case RMAP_NODE:
- case VTY_NODE:
- vty->node = CONFIG_NODE ;
- break;
- case BGP_VPNV4_NODE:
- case BGP_IPV4_NODE:
- case BGP_IPV4M_NODE:
- case BGP_IPV6_NODE:
- case BGP_IPV6M_NODE:
- vty->node = BGP_NODE ;
- break;
- case KEYCHAIN_KEY_NODE:
- vty->node = KEYCHAIN_NODE ;
- break;
- default:
- break;
- }
-
- VTY_UNLOCK() ;
- return ret ;
-}
-
-/*------------------------------------------------------------------------------
- * Command line "end" command
- *
- * Falls back to ENABLE_NODE.
- *
- * Returns: command return code
- */
-extern enum cmd_return_code
-vty_cmd_end(struct vty* vty)
-{
- VTY_LOCK() ;
+ {
+ if (r == 0)
+ break ;
- 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, ENABLE_NODE);
- break;
- default:
- break;
- }
+ // TODO error handling ....
+ } ;
+ } ;
- VTY_UNLOCK() ;
- return CMD_SUCCESS ;
-} ;
+ XFREE(MTYPE_TMP, buf) ;
+ close(fd) ;
-/*------------------------------------------------------------------------------
- * 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 ;
+ return CMD_SUCCESS;
} ;
/*------------------------------------------------------------------------------
- * Command line ^C action.
- *
- * Ignores contents of command line (including not adding to history).
- *
- * Fall back to ENABLE_NODE if in any one of a number of nodes.
- *
- * Resets the history pointer.
- *
- * Returns: command return code
+ * Clear the contents of the command output FIFO etc.
*/
-extern enum cmd_return_code
-uty_stop_input(struct vty *vty)
+extern void
+vty_out_clear(vty vty)
{
- vty_io vio = vty->vio ;
-
- VTY_ASSERT_LOCKED() ;
-
- switch (vty->node)
- {
- case CONFIG_NODE:
- case INTERFACE_NODE:
- case ZEBRA_NODE:
- case RIP_NODE:
- case RIPNG_NODE:
- case BGP_NODE:
- case RMAP_NODE:
- case OSPF_NODE:
- case OSPF6_NODE:
- case ISIS_NODE:
- case KEYCHAIN_NODE:
- case KEYCHAIN_KEY_NODE:
- case MASC_NODE:
- case VTY_NODE:
- uty_config_unlock (vty, ENABLE_NODE) ;
- break;
- default:
- /* Unknown node, we have to ignore it. */
- break;
- }
-
- /* Set history pointer to the latest one. */
- vio->hp = vio->hindex;
-
- return CMD_SUCCESS ;
+ VTY_LOCK() ;
+ uty_out_clear(vty->vio) ;
+ VTY_UNLOCK() ;
} ;
-/*------------------------------------------------------------------------------
- * Command ^Z action.
- *
- * Ignores contents of command line (including not adding to history).
- *
- * Fall back to ENABLE_NODE if in any one of a number of nodes.
+/*==============================================================================
+ * Deal with SIGCHLD "event".
*
- * Returns: command return code
+ * NB: if there is a nexus to be signalled, we do that *before* attempting to
+ * lock the VTY -- because in that case the VTY will be locked by that
+ * nexus !
*/
-extern enum cmd_return_code
-uty_end_config (struct vty *vty)
+extern void
+vty_sigchld(void)
{
- VTY_ASSERT_LOCKED() ;
-
- 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, ENABLE_NODE) ;
- break;
- default:
- /* Unknown node, we have to ignore it. */
- break;
- }
-
- return CMD_SUCCESS ;
-}
+ vty_child_signal_nexus_signal() ;
-/*------------------------------------------------------------------------------
- * Command ^D action -- when nothing else on command line.
- *
- * Same as "exit" command.
- *
- * Returns: command return code
- */
-extern enum cmd_return_code
-uty_down_level (struct vty *vty)
-{
- return vty_cmd_exit(vty) ;
+ VTY_LOCK() ;
+ uty_sigchld() ;
+ VTY_UNLOCK() ;
} ;
/*==============================================================================
@@ -1066,18 +816,19 @@ uty_down_level (struct vty *vty)
* run directly in the thread -- no commands are queued.
*/
-static FILE * vty_use_backup_config (char *fullpath) ;
-static void vty_read_file (FILE *confp, struct cmd_element* first_cmd,
- bool ignore_warnings) ;
+static int vty_use_backup_config (qpath path) ;
+static void vty_read_config_file (int conf_fd, const char* name,
+ cmd_command first_cmd, bool ignore_warnings,
+ bool full_lex) ;
/*------------------------------------------------------------------------------
* Read the given configuration file.
*/
extern void
-vty_read_config (char *config_file,
- char *config_default)
+vty_read_config (const char *config_file,
+ const char *config_default)
{
- vty_read_config_first_cmd_special(config_file, config_default, NULL, 1);
+ vty_read_config_first_cmd_special(config_file, config_default, NULL, true);
}
/*------------------------------------------------------------------------------
@@ -1098,15 +849,14 @@ vty_read_config (char *config_file,
* command.
*/
extern void
-vty_read_config_first_cmd_special(char *config_file,
- char *config_default,
- struct cmd_element* first_cmd,
+vty_read_config_first_cmd_special(const char *config_file,
+ const char *config_default,
+ cmd_command first_cmd,
bool ignore_warnings)
{
- char cwd[MAXPATHLEN];
- FILE *confp = NULL;
- char *fullpath;
- char *tmp = NULL;
+ const char *name ;
+ qpath path ;
+ int conf_fd ;
/* Deal with VTYSH_ENABLED magic */
if (VTYSH_ENABLED && (config_file == NULL))
@@ -1132,7 +882,7 @@ vty_read_config_first_cmd_special(char *config_file,
{
ret = stat (integrate_default, &conf_stat);
if (ret >= 0)
- return;
+ return; /* TODO leaves host.config_file NULL */
}
} ;
@@ -1140,130 +890,115 @@ vty_read_config_first_cmd_special(char *config_file,
if (config_file == NULL)
config_file = config_default ;
- if (! IS_DIRECTORY_SEP (config_file[0]))
- {
- getcwd (cwd, sizeof(cwd)) ;
- tmp = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (config_file) + 2) ;
- sprintf (tmp, "%s/%s", cwd, config_file);
- fullpath = tmp;
- }
- else
- {
- tmp = NULL ;
- fullpath = config_file;
- } ;
+ path = qpath_make(config_file, host.cwd) ;
+ name = qpath_string(path) ;
/* try to open the configuration file */
- confp = fopen (fullpath, "r");
+ conf_fd = uty_fd_file_open(name, vfd_io_read, 0) ;
- if (confp == NULL)
+ if (conf_fd < 0)
{
fprintf (stderr, "%s: failed to open configuration file %s: %s\n",
- __func__, fullpath, errtostr(errno, 0).str);
+ __func__, name, errtostr(errno, 0).str) ;
- confp = vty_use_backup_config (fullpath);
- if (confp)
+ conf_fd = vty_use_backup_config (path);
+ if (conf_fd >= 0)
fprintf (stderr, "WARNING: using backup configuration file!\n");
else
{
fprintf (stderr, "can't open backup configuration file [%s%s]\n",
- fullpath, CONF_BACKUP_EXT);
+ name, CONF_BACKUP_EXT);
exit(1);
}
} ;
-#ifdef QDEBUG
- fprintf(stderr, "Reading config file: %s\n", fullpath);
-#endif
+ if (qdebug)
+ fprintf(stderr, "Reading config file: %s\n", name);
- vty_read_file (confp, first_cmd, ignore_warnings);
- fclose (confp);
+ vty_read_config_file(conf_fd, name, first_cmd, ignore_warnings, false);
- host_config_set (fullpath);
+ cmd_host_config_set(path) ;
-#ifdef QDEBUG
- fprintf(stderr, "Finished reading config file\n");
-#endif
+ qpath_free(path) ;
- if (tmp)
- XFREE (MTYPE_TMP, tmp);
+ if (qdebug)
+ fprintf(stderr, "Finished reading config file\n");
}
/*------------------------------------------------------------------------------
* Try to use a backup configuration file.
*
- * Having failed to open the file "<fullpath>", if there is a file called
- * "<fullpath>.sav" that can be opened for reading, then:
+ * Having failed to open the file "<path>", if there is a file called
+ * "<path>.sav" that can be opened for reading, then:
*
* - make a copy of that file
- * - call it "<fullpath>"
+ * - call it "<path>"
* - return an open FILE
*
- * Returns: NULL => no "<fullpath>.sav", or faild doing any of the above
- * otherwise, returns FILE* for open file.
+ * Returns: < 0 => no "<path>.sav", or failed doing any of the above
+ * >= 0 otherwise, fd file.
*/
-static FILE *
-vty_use_backup_config (char *fullpath)
+static int
+vty_use_backup_config (qpath path)
{
- char *tmp_path ;
- struct stat buf;
- int ret, tmp, sav;
- int c;
- char buffer[4096] ;
-
- enum { xl = 32 } ;
- tmp_path = malloc(strlen(fullpath) + xl) ;
+ qpath temp ;
+ char* name ;
+ int sav_fd, tmp_fd, err ;
+ bool ok ;
/* construct the name "<fullname>.sav", and try to open it. */
- confirm(xl > sizeof(CONF_BACKUP_EXT)) ;
- sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ;
+ temp = qpath_dup(path) ;
+ qpath_extend_str(temp, CONF_BACKUP_EXT) ;
- sav = -1 ;
- if (stat (tmp_path, &buf) != -1)
- sav = open (tmp_path, O_RDONLY);
+ sav_fd = -1 ;
+ tmp_fd = -1 ;
- if (sav < 0)
- {
- free (tmp_path);
- return NULL;
- } ;
+ sav_fd = uty_fd_file_open(qpath_string(temp),
+ vfd_io_read | vfd_io_blocking, 0) ;
/* construct a temporary file and copy "<fullpath.sav>" to it. */
- confirm(xl > sizeof(".XXXXXX"))
- sprintf (tmp_path, "%s%s", fullpath, ".XXXXXX") ;
+ qpath_extend_str(temp, ".XXXXXX") ;
+ name = qpath_char_string(temp) ;
- /* Open file to configuration write. */
- tmp = mkstemp (tmp_path);
- if (tmp < 0)
- {
- free (tmp_path);
- close(sav);
- return NULL;
- }
+ if (sav_fd >= 0)
+ tmp_fd = mkstemp(name) ;
+
+ ok = ((sav_fd >= 0) && (tmp_fd >= 0)) ;
- while((c = read (sav, buffer, sizeof(buffer))) > 0)
- write (tmp, buffer, c);
+ if (ok)
+ ok = (copyn(tmp_fd, sav_fd) == 0) ;
- close (sav);
- close (tmp);
+ err = errno ;
- /* Make sure that have the required file status */
- if (chmod(tmp_path, CONFIGFILE_MASK) != 0)
+ if (tmp_fd >= 0)
+ close(tmp_fd) ;
+ if (sav_fd >= 0)
+ close(sav_fd) ;
+
+ /* If now OK, then have copied the .sav to the temporary file. */
+
+ if (ok)
{
- unlink (tmp_path);
- free (tmp_path);
- return NULL;
- }
+ /* Make sure that have the required file status */
+ ok = chmod(name, CONFIGFILE_MASK) == 0 ;
+
+ /* Finally, make a link with the original name */
+ if (ok)
+ ok = link(name, qpath_string(path)) == 0 ;
+
+ err = errno ;
+ } ;
- /* Make <fullpath> be a name for the new file just created. */
- ret = link (tmp_path, fullpath) ;
+ if (tmp_fd >= 0) /* if made a temporary, done with it now */
+ unlink(name) ;
- /* Discard the temporary, now */
- unlink (tmp_path) ;
- free (tmp_path) ;
+ qpath_free(temp) ; /* done with the qpath */
- /* If link was successful, try to open -- otherwise, failed. */
- return (ret == 0) ? fopen (fullpath, "r") : NULL ;
+ if (ok)
+ return uty_fd_file_open(qpath_string(path), vfd_io_read, 0) ;
+
+ errno = err ;
+ return -1 ;
} ;
/*------------------------------------------------------------------------------
@@ -1287,143 +1022,40 @@ vty_use_backup_config (char *fullpath)
* so all commands are executed directly.
*/
static void
-vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings)
+vty_read_config_file(int fd, const char* name, cmd_command first_cmd,
+ bool ignore_warnings, bool full_lex)
{
- enum cmd_return_code ret ;
- struct vty *vty ;
+ cmd_return_code_t ret ;
+ vty vty ;
qtime_t taking ;
- /* 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
- * it -- same like other command execution.
- */
- qs_need(&vty->vio->clx, VTY_BUFSIZ) ;
- vty->buf = qs_chars(&vty->vio->clx) ;
-
- taking = qt_get_monotonic() ;
-
- /* Execute configuration file */
- ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx,
- ignore_warnings) ;
-
- taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ;
+ vty = vty_config_read_open(fd, name, full_lex) ;
- zlog_info("Finished reading configuration in %d.%dsecs%s",
- (int)(taking / 1000), (int)(taking % 1000),
- (ret == CMD_SUCCESS) ? "." : " -- FAILED") ;
-
- VTY_LOCK() ;
-
- if (ret != CMD_SUCCESS)
+ if (vty_cmd_config_loop_prepare(vty))
{
- fprintf (stderr, "%% while processing line %u of the configuration:\n"
- "%s", vty->lineno, vty->buf) ;
-
- switch (ret)
- {
- case CMD_WARNING:
- fprintf (stderr, "%% Warning...\n");
- break;
-
- case CMD_ERROR:
- fprintf (stderr, "%% Error...\n");
- break;
-
- case CMD_ERR_AMBIGUOUS:
- fprintf (stderr, "%% Ambiguous command.\n");
- break;
-
- case CMD_ERR_NO_MATCH:
- fprintf (stderr, "%% There is no such command.\n");
- break;
-
- case CMD_ERR_INCOMPLETE:
- fprintf (stderr, "%% Incomplete command.\n");
- break;
-
- default:
- fprintf(stderr, "%% (unknown cause %d)\n", ret) ;
- break ;
- } ;
-
- uty_out_fflush(vty->vio, stderr) ; /* flush command output buffer */
+ taking = qt_get_monotonic() ;
- exit (1);
- } ;
-
- uty_close(vty->vio) ;
- VTY_UNLOCK() ;
-} ;
+ ret = cmd_read_config(vty, first_cmd, ignore_warnings) ;
-/*==============================================================================
- * Configuration node/state handling
- *
- * At most one VTY may hold the configuration symbol of power at any time.
- */
+ taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ;
-/*------------------------------------------------------------------------------
- * Attempt to gain the configuration symbol of power
- *
- * If succeeds, set the given node.
- *
- * Returns: true <=> now own the symbol of power.
- */
-extern bool
-vty_config_lock (struct vty *vty, enum node_type node)
-{
- bool result;
-
- VTY_LOCK() ;
-
- if (vty_config == 0)
+ zlog_info("Finished reading configuration '%s' in %d.%d secs%s",
+ name, (int)(taking / 1000), (int)(taking % 1000),
+ (ret == CMD_SUCCESS) ? "." : " -- FAILED") ;
+ }
+ else
{
- vty->vio->config = 1 ;
- vty_config = 1 ;
+ /* This should be impossible -- before reading the configuration,
+ * all other vty are closed.
+ */
+ zlog_err("%s: Failed to gain config capability !", __func__) ;
+ ret = CMD_ERROR ;
} ;
- result = vty->vio->config;
-
- if (result)
- vty->node = node ;
-
- VTY_UNLOCK() ;
+ vty_cmd_loop_exit(vty) ; /* the vty is released */
- return result;
-}
-
-/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
- *
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
- */
-extern void
-vty_config_unlock (struct vty *vty, enum node_type node)
-{
- VTY_LOCK() ;
- uty_config_unlock(vty, node);
- VTY_UNLOCK() ;
-}
-
-/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
- *
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
- */
-extern void
-uty_config_unlock (struct vty *vty, enum node_type node)
-{
- VTY_ASSERT_LOCKED() ;
- if ((vty_config == 1) && (vty->vio->config == 1))
- {
- vty->vio->config = 0;
- vty_config = 0;
- }
-
- assert(node <= MAX_NON_CONFIG_NODE) ;
- vty->node = node ;
+ if (ret != CMD_SUCCESS)
+ exit(1) ;
} ;
/*==============================================================================
@@ -1440,24 +1072,26 @@ DEFUN_CALL (config_who,
VTY_LOCK() ;
- vio = vio_list_base ;
- while (vio != NULL) /* TODO: show only VTY_TERM ??? */
+ vio = vio_live_list ; /* once locked */
+
+ while (vio != NULL) /* TODO: show only VTY_TERM ??? */
{
- uty_out (vty, "%svty[%d] connected from %s.%s",
- vio->config ? "*" : " ",
- i, uty_get_name(vio), VTY_NEWLINE);
+ vty_out(vty, "%svty[%d] connected from %s.\n",
+ vio->vty->config ? "*" : " ", i, uty_get_name(vio)) ;
vio = sdl_next(vio, vio_list) ;
} ;
+
VTY_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")
+DEFUN_ATTR (line_vty,
+ line_vty_cmd,
+ "line vty",
+ "Configure a terminal line\n"
+ "Virtual terminal\n",
+ CMD_ATTR_DIRECT + CMD_ATTR_NODE + VTY_NODE)
{
VTY_LOCK() ;
vty->node = VTY_NODE;
@@ -1465,7 +1099,11 @@ DEFUN_CALL (line_vty,
return CMD_SUCCESS;
}
-/* Set time out value. */
+/*------------------------------------------------------------------------------
+ * Set time out value.
+ *
+ * Affects this and any future VTY_TERMINAL or VTY_SHELL_SERVER type VTY.
+ */
static int
exec_timeout (struct vty *vty, const char *min_str, const char *sec_str)
{
@@ -1483,10 +1121,9 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str)
if (sec_str)
timeout += strtol (sec_str, NULL, 10);
- vty_timeout_val = timeout;
+ host.vty_timeout_val = timeout;
- if (vty_term(vty) || vty_shell_serv(vty))
- uty_sock_set_timer(&vty->vio->sock, timeout) ;
+ uty_set_timeout(vty->vio, timeout) ; /* update own timeout, if required */
VTY_UNLOCK() ;
return CMD_SUCCESS;
@@ -1529,10 +1166,9 @@ DEFUN_CALL (vty_access_class,
{
VTY_LOCK() ;
- if (vty_accesslist_name)
- XFREE(MTYPE_VTY, vty_accesslist_name);
+ XFREE(MTYPE_HOST, host.vty_accesslist_name);
- vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]);
+ host.vty_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]);
VTY_UNLOCK() ;
return CMD_SUCCESS;
@@ -1546,23 +1182,25 @@ DEFUN_CALL (no_vty_access_class,
"Filter connections based on an IP access list\n"
"IP access list\n")
{
- int result = CMD_SUCCESS;
+ cmd_return_code_t ret ;
VTY_LOCK() ;
- if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0])))
+
+ if ((argc == 0) || ( (host.vty_accesslist_name != NULL) &&
+ (strcmp(host.vty_accesslist_name, argv[0]) == 0) ))
{
- uty_out (vty, "Access-class is not currently applied to vty%s",
- VTY_NEWLINE);
- result = CMD_WARNING;
+ XFREE(MTYPE_HOST, host.vty_accesslist_name);
+ /* sets host.vty_accesslist_name = NULL */
+ ret = CMD_SUCCESS ;
}
else
{
- XFREE(MTYPE_VTY, vty_accesslist_name);
- vty_accesslist_name = NULL;
- }
+ vty_out(vty, "Access-class is not currently applied to vty\n") ;
+ ret = CMD_WARNING;
+ } ;
VTY_UNLOCK() ;
- return result;
+ return ret;
}
#ifdef HAVE_IPV6
@@ -1575,10 +1213,10 @@ DEFUN_CALL (vty_ipv6_access_class,
"IPv6 access list\n")
{
VTY_LOCK() ;
- if (vty_ipv6_accesslist_name)
- XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
- vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]);
+ XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name);
+
+ host.vty_ipv6_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]);
VTY_UNLOCK() ;
return CMD_SUCCESS;
@@ -1593,26 +1231,25 @@ DEFUN_CALL (no_vty_ipv6_access_class,
"Filter connections based on an IP access list\n"
"IPv6 access list\n")
{
- int result = CMD_SUCCESS;
+ cmd_return_code_t ret ;
VTY_LOCK() ;
- if (! vty_ipv6_accesslist_name ||
- (argc && strcmp(vty_ipv6_accesslist_name, argv[0])))
+ if ((argc == 0) || ( (host.vty_ipv6_accesslist_name != NULL) &&
+ (strcmp(host.vty_ipv6_accesslist_name, argv[0]) == 0) ))
{
- uty_out (vty, "IPv6 access-class is not currently applied to vty%s",
- VTY_NEWLINE);
- result = CMD_WARNING;
+ XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name);
+ /* sets host.vty_ipv6_accesslist_name = NULL */
+ ret = CMD_SUCCESS ;
}
else
{
- XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
-
- vty_ipv6_accesslist_name = NULL;
- }
+ vty_out(vty, "IPv6 access-class is not currently applied to vty\n") ;
+ ret = CMD_WARNING;
+ } ;
VTY_UNLOCK() ;
- return CMD_SUCCESS;
+ return ret;
}
#endif /* HAVE_IPV6 */
@@ -1623,7 +1260,7 @@ DEFUN_CALL (vty_login,
"Enable password checking\n")
{
VTY_LOCK() ;
- no_password_check = 0;
+ host.no_password_check = false ;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1635,7 +1272,7 @@ DEFUN_CALL (no_vty_login,
"Enable password checking\n")
{
VTY_LOCK() ;
- no_password_check = 1;
+ host.no_password_check = true ;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1647,7 +1284,7 @@ DEFUN_CALL (vty_restricted_mode,
"Restrict view commands available in anonymous, unauthenticated vty\n")
{
VTY_LOCK() ;
- restricted_mode = 1;
+ host.restricted_mode = true;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1659,7 +1296,7 @@ DEFUN_CALL (vty_no_restricted_mode,
"Enable password checking\n")
{
VTY_LOCK() ;
- restricted_mode = 0;
+ host.restricted_mode = false;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1671,7 +1308,7 @@ DEFUN_CALL (service_advanced_vty,
"Enable advanced mode vty interface\n")
{
VTY_LOCK() ;
- host.advanced = 1;
+ host.advanced = true;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1684,7 +1321,7 @@ DEFUN_CALL (no_service_advanced_vty,
"Enable advanced mode vty interface\n")
{
VTY_LOCK() ;
- host.advanced = 0;
+ host.advanced = false;
VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1727,26 +1364,10 @@ DEFUN_CALL (show_history,
SHOW_STR
"Display the session command history\n")
{
- int index;
-
VTY_LOCK() ;
- for (index = vty->vio->hindex + 1; index != vty->vio->hindex;)
- {
- qstring line ;
-
- if (index == VTY_MAXHIST)
- {
- index = 0;
- continue;
- }
-
- line = vector_get_item(&vty->vio->hist, index) ;
- if (line != NULL)
- uty_out (vty, " %s%s", line->char_body, VTY_NEWLINE);
-
- index++;
- }
+ if (vty->type == VTY_TERMINAL)
+ uty_cli_hist_show(vty->vio->vin->cli) ;
VTY_UNLOCK() ;
return CMD_SUCCESS;
@@ -1760,35 +1381,40 @@ DEFUN_CALL (show_history,
static int
vty_config_write (struct vty *vty)
{
- vty_out (vty, "line vty%s", VTY_NEWLINE);
+ vty_io vio ;
+
+ VTY_LOCK() ; /* while accessing the host.xx */
+
+ vio = vty->vio ;
- if (vty_accesslist_name)
- vty_out (vty, " access-class %s%s",
- vty_accesslist_name, VTY_NEWLINE);
+ uty_out (vio, "line vty\n");
- if (vty_ipv6_accesslist_name)
- vty_out (vty, " ipv6 access-class %s%s",
- vty_ipv6_accesslist_name, VTY_NEWLINE);
+ if (host.vty_accesslist_name)
+ uty_out (vio, " access-class %s\n", host.vty_accesslist_name);
+
+ if (host.vty_ipv6_accesslist_name)
+ uty_out (vio, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name);
/* exec-timeout */
- if (vty_timeout_val != VTY_TIMEOUT_DEFAULT)
- vty_out (vty, " exec-timeout %ld %ld%s",
- vty_timeout_val / 60,
- vty_timeout_val % 60, VTY_NEWLINE);
+ if (host.vty_timeout_val != VTY_TIMEOUT_DEFAULT)
+ uty_out (vio, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60,
+ host.vty_timeout_val % 60);
/* login */
- if (no_password_check)
- vty_out (vty, " no login%s", VTY_NEWLINE);
+ if (host.no_password_check)
+ uty_out (vio, " no login\n");
- if (restricted_mode != restricted_mode_default)
+ if (host.restricted_mode != restricted_mode_default)
{
if (restricted_mode_default)
- vty_out (vty, " no anonymous restricted%s", VTY_NEWLINE);
+ uty_out (vio, " no anonymous restricted\n");
else
- vty_out (vty, " anonymous restricted%s", VTY_NEWLINE);
+ uty_out (vio, " anonymous restricted\n");
}
- vty_out (vty, "!%s", VTY_NEWLINE);
+ uty_out (vio, "!\n");
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1798,96 +1424,68 @@ vty_config_write (struct vty *vty)
*/
/*------------------------------------------------------------------------------
- * Save cwd
- *
- * This is done early in the morning so that any future operations on files
- * can use the original cwd if required.
+ * Get cwd as at start-up. Never changed -- so no locking required.
*/
-static void
-vty_save_cwd (void)
+extern qpath
+vty_getcwd (qpath qp)
{
- char cwd[MAXPATHLEN];
- char *c;
-
- c = getcwd (cwd, MAXPATHLEN);
+ VTY_LOCK() ;
- if (!c)
- {
- chdir (SYSCONFDIR);
- getcwd (cwd, MAXPATHLEN);
- }
+ qp = qpath_copy(qp, host.cwd) ;
- vty_cwd = XSTRDUP(MTYPE_TMP, cwd) ;
-} ;
+ VTY_UNLOCK() ;
-/*------------------------------------------------------------------------------
- * Get cwd as at start-up. Never changed -- so no locking required.
- */
-char *
-vty_get_cwd ()
-{
- return vty_cwd;
+return qp ;
}
/*==============================================================================
- * Access functions for VTY values, where locking is or might be required.
+ * Access functions for vio values, where locking is or might be required.
*/
+#if 0
-bool
-vty_shell (struct vty *vty)
+static bool
+vty_is_terminal(struct vty *vty)
{
bool result;
VTY_LOCK() ;
- result = (vty->vio->type == VTY_SHELL) ;
+ result = uty_is_terminal(vty) ;
VTY_UNLOCK() ;
return result;
}
-bool
-vty_term(struct vty *vty)
+static bool
+vty_is_shell_server (struct vty *vty)
{
bool result;
VTY_LOCK() ;
- result = (vty->vio->type == VTY_TERM);
+ result = uty_is_shell_server(vty) ;
VTY_UNLOCK() ;
return result;
}
-bool
-vty_shell_serv (struct vty *vty)
+static bool
+vty_is_shell_client (struct vty *vty)
{
bool result;
VTY_LOCK() ;
- result = (vty->vio->type == VTY_SHELL_SERV);
+ result = uty_is_shell_client(vty) ;
VTY_UNLOCK() ;
return result;
}
-enum node_type
-vty_get_node(struct vty *vty)
-{
- int result;
- VTY_LOCK() ;
- result = vty->node;
- VTY_UNLOCK() ;
- return result;
-}
+#endif
-void
-vty_set_node(struct vty *vty, enum node_type node)
+/*------------------------------------------------------------------------------
+ * When terminal length is set, change the current CLI setting, if required.
+ */
+extern void
+vty_set_lines(vty vty, int lines)
{
VTY_LOCK() ;
- vty->node = node;
- VTY_UNLOCK() ;
-}
-void
-vty_set_lines(struct vty *vty, int lines)
-{
- VTY_LOCK() ;
- vty->vio->lines = lines;
- vty->vio->lines_set = 1 ;
- uty_set_height(vty->vio) ;
+ if (vty->type == VTY_TERMINAL)
+ uty_cli_set_lines(vty->vio->vin->cli, lines, true) ;
+
VTY_UNLOCK() ;
}
@@ -1989,7 +1587,8 @@ DEFUN (delay_secs,
a = (rand() % 4) + 1 ;
} ;
- *q++ = '\n' ;
+ if (n != 0)
+ *q++ = '\n' ;
*q++ = '\0' ;
vty_out(vty, buf) ;
diff --git a/lib/vty.h b/lib/vty.h
index 126218ab..2a13ba6e 100644
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -1,4 +1,4 @@
-/* VTY top level
+/* VTY external interface -- header
* Copyright (C) 1997, 98 Kunihiro Ishiguro
*
* Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
@@ -24,7 +24,11 @@
#ifndef _ZEBRA_VTY_H
#define _ZEBRA_VTY_H
-#include <stdbool.h>
+#include "misc.h"
+#include "vargs.h"
+
+#include "command_common.h" /* NB: *not* command.h */
+#include "vty_common.h" /* struct vty & VTY types */
#include "thread.h"
#include "log.h"
@@ -35,141 +39,30 @@
#include "list_util.h"
#include "vector.h"
#include "qstring.h"
-#include "node_type.h"
-
-/* Macro in case there are particular compiler issues. */
-#ifndef Inline
- #define Inline static inline
-#endif
+#include "qpath.h"
/*==============================================================================
- * The VTYSH uses a unix socket to talk to the daemon.
- *
- * The ability to respond to a connection from VTYSH appears to be a *compile*
- * time option. In the interests of keeping the code up to date, the VTYSH
- * option is turned into a testable constant.
+ * These are definitions and functions for things which are required outside
+ * the vty and command family.
*/
-#ifdef VTYSH
-# define VTYSH_DEFINED 1
-#else
-# define VTYSH_DEFINED 0
-#endif
-
-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 the context in which commands are executed.
- */
-
- /* Node status of this vty
- *
- * NB: when using qpthreads should lock VTY to access this -- so use
- * vty_get_node() and vty_set_node().
- */
- enum node_type node ;
-
- /* For current referencing point of interface, route-map, access-list
- * etc...
- *
- * NB: this value is private to the command execution, which is assumed
- * to all be in the one thread... so no lock required.
- */
- void *index ;
-
- /* For multiple level index treatment such as key chain and key.
- *
- * NB: this value is private to the command execution, which is assumed
- * to all be in the one thread... so no lock required.
- */
- void *index_sub ;
-
- /* String which is newline... read only -- no locking */
- const char* newline ;
-
- /*----------------------------------------------------------------------------
- * The 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 */
- vty_io vio ;
-};
-
-/*------------------------------------------------------------------------------
- * Can now include this
- */
-
-#include "command.h"
/*------------------------------------------------------------------------------
*
*/
-#define VTY_BUFSIZ 512
-#define VTY_MAXHIST 51
+enum { VTY_HIST_COUNT = 55 } ;
/* Integrated configuration file. */
#define INTEGRATE_DEFAULT_CONFIG "Quagga.conf"
-/* Small macro to determine newline is newline only or linefeed needed. */
-#define VTY_NEWLINE (((vty) != NULL) ? (vty)->newline : "\n")
+/* Conversion of "\n" to "\r\n" is done at output time. */
+#define VTY_NEWLINE "\n"
+#define VTY_NL "\n"
/* For indenting, mostly. */
extern const char vty_spaces_string[] ;
enum { VTY_MAX_SPACES = 40 } ;
#define VTY_SPACES(n) (vty_spaces_string + ((n) < VTY_MAX_SPACES \
? VTY_MAX_SPACES - (n) : 0))
-
-/* Default time out value */
-#define VTY_TIMEOUT_DEFAULT 600
-
/* Vty read buffer size. */
#define VTY_READ_BUFSIZ 512
@@ -182,18 +75,11 @@ enum { VTY_MAX_SPACES = 40 } ;
#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP)
#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__ */
-
/* Utility macros to convert VTY argument to unsigned long or integer. */
#define VTY_GET_LONG(NAME,V,STR) \
do { \
char *endptr = NULL; \
- (V) = strtoul ((STR), &endptr, 10); \
+ (V) = strtoul ((STR), &endptr, 0); \
if (*endptr != '\0' || (V) == ULONG_MAX) \
{ \
vty_out (vty, "%% Invalid %s value%s", NAME, VTY_NEWLINE); \
@@ -217,7 +103,7 @@ do { \
VTY_GET_INTEGER_RANGE(NAME,V,STR,0U,UINT32_MAX)
#define VTY_GET_IPV4_ADDRESS(NAME,V,STR) \
-do { \
+do { \
int retv; \
retv = inet_aton ((STR), &(V)); \
if (!retv) \
@@ -228,7 +114,7 @@ do {
} while (0)
#define VTY_GET_IPV4_PREFIX(NAME,V,STR) \
-do { \
+do { \
int retv; \
retv = str2prefix_ipv4 ((STR), &(V)); \
if (retv <= 0) \
@@ -252,33 +138,32 @@ 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_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 void vty_out_indent(struct vty *vty, int indent) ;
+extern int vty_write(struct vty *vty, const void* buf, int n) ;
+extern int 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_sigchld(void) ;
-extern void vty_time_print (struct vty *, int);
+extern void vty_read_config (const char* config_file,
+ const char* config_default);
+extern void vty_read_config_first_cmd_special(const char* config_file,
+ const char* config_default,
+ cmd_command first_cmd,
+ bool ignore_warnings) ;
-extern char *vty_get_cwd (void);
+extern qpath vty_getcwd(qpath qp);
-extern bool vty_shell (struct vty *);
-extern bool vty_term (struct vty *);
-extern bool vty_shell_serv (struct vty *);
-extern void vty_hello (struct vty *);
-extern enum node_type vty_get_node(struct vty *);
-extern void vty_set_node(struct vty *, enum node_type);
-extern void vty_set_lines(struct vty *, int);
+/*------------------------------------------------------------------------------
+ * vtysh
+ */
+extern void vty_hello (vty vty);
+extern struct vty* vty_open(enum vty_type type, node_type_t node) ;
+extern void vty_close_final(vty vty);
+extern void vty_init_vtysh (void);
#endif /* _ZEBRA_VTY_H */
diff --git a/lib/vty_cli.c b/lib/vty_cli.c
index bdaf1128..a6a276e8 100644
--- a/lib/vty_cli.c
+++ b/lib/vty_cli.c
@@ -20,103 +20,403 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
+#include "misc.h"
+#include <ctype.h>
-#include <zebra.h>
-
-#include "keystroke.h"
-#include "vty.h"
-#include "uty.h"
+#include "vty_local.h"
+#include "vty_command.h"
#include "vty_cli.h"
+#include "vty_io_term.h"
#include "vty_io.h"
#include "vio_lines.h"
+#include "vio_fifo.h"
+
+#include "keystroke.h"
-#include "command.h"
+#include "command_common.h"
+#include "command_parse.h"
#include "command_execute.h"
#include "command_queue.h"
#include "memory.h"
/*==============================================================================
- * Host name handling
+ * This is the CLI which is part of a VIN_TERM vf.
+ *
+ * With a few exceptions, the externs here are called by vty_io_term.
+ */
+
+/*------------------------------------------------------------------------------
+ * Essential stuff.
+ */
+#define TELNET_NEWLINE "\r\n"
+static const char* telnet_newline = TELNET_NEWLINE ;
+ const char* uty_cli_newline = TELNET_NEWLINE ;
+
+/*==============================================================================
+ * Construct and destroy CLI object
+ */
+
+static bool uty_cli_callback(keystroke_callback_args) ;
+static void uty_cli_update_more(vty_cli cli) ;
+
+static void uty_cli_cancel(vty_cli cli) ;
+
+/*------------------------------------------------------------------------------
+ * Construct and initialise a new CLI object -- never embedded.
*
- * The host name is used in the command line prompt. The name used is either
- * the name set by "hostname" command, or the current machine host name.
+ * The CLI is started up in the state it is in waiting for a command to
+ * complete. This means that all start-up messages etc. are handled as the
+ * output from an implied start command. When the command loop is entered,
+ * will find its way to uty_cli_want_command().
*
- * Static variables -- under the VTY_LOCK !
+ * Note that the vty_io_term stuff sends out a bunch of telnet commands, which
+ * will be buffered in the CLI buffer. That will be output, before the
+ * start-up messages etc. on uty_cli_start().
*/
+extern vty_cli
+uty_cli_new(vio_vf vf)
+{
+ vty_cli cli ;
+
+ cli = XCALLOC(MTYPE_VTY_CLI, sizeof(struct vty_cli)) ;
+
+ /* Zeroising has initialised:
+ *
+ * vf = NULL -- set below
+ *
+ * hist = NULL -- set up when first used.
+ * hp = 0 -- hp == h_now => in the present ...
+ * h_now = 0 -- ... see uty_cli_hist_add()
+ * h_repeat = false -- ... see uty_cli_hist_add()
+ *
+ * width = 0 -- unknown width ) Telnet window size
+ * height = 0 -- unknown height )
+ *
+ * lines = 0 -- set below
+ * lines_set = false -- set below
+ *
+ * monitor = false -- not a "monitor"
+ * monitor_busy = false -- so not busy either
+ *
+ * key_stream = X -- set below
+ *
+ * drawn = false
+ *
+ * tilde_prompt = false -- tilde prompt is not drawn
+ * tilde_enabled = false -- set below if ! multi-threaded
+ *
+ * prompt_len = 0 -- not drawn, in any case
+ * extra_len = 0 -- not drawn, in any case
+ *
+ * prompt_node = NULL_NODE -- so not set !
+ * prompt_gen = 0 -- not a generation number
+ * prompt_for_node = NULL -- not set, yet
+ *
+ * dispatched = false -- see below
+ * in_progress = false -- see below
+ * blocked = false -- see below
+ * paused = false
+ *
+ * mon_active = false
+ * out_active = false
+ *
+ * more_wait = false
+ * more_enter = false
+ *
+ * more_enabled = false -- not in "--More--" state
+ *
+ * pause_timer = NULL -- set below if multi-threaded
+ *
+ * context = NULL -- see below
+ * auth_context = false -- set by uty_cli_want_command()
+ *
+ * parsed = NULL -- see below
+ * to_do = cmd_do_nothing
+ * cl = NULL qstring -- set below
+ * cls = NULL qstring -- set below
+ * clx = NULL qstring -- set below
+ * dispatch = all zeros -- nothing to dispatch
+ *
+ * cbuf = NULL -- see below
+ *
+ * olc = NULL -- see below
+ */
+ confirm(NULL_NODE == 0) ; /* prompt_node & node */
+ confirm(cmd_do_nothing == 0) ; /* to_do */
+ confirm(CMD_ACTION_ALL_ZEROS) ; /* dispatch */
-static char* vty_host_name = NULL ;
-int vty_host_name_set = 0 ;
+ cli->vf = vf ;
-static void uty_new_host_name(const char* name) ;
+ /* Allocate and initialise a keystroke stream TODO: CSI ?? */
+ cli->key_stream = keystroke_stream_new('\0', "\x03", uty_cli_callback, cli) ;
+
+ /* Set up cl, cls and clx qstrings, the command line output fifo and
+ * the output line control.
+ */
+ cli->cl = qs_new(120) ; /* reasonable line length */
+ cli->cls = qs_new(120) ;
+ cli->clx = qs_new(120) ;
+
+ cli->cbuf = vio_fifo_new(1000) ;
+
+ cli->olc = vio_lc_new(0 , 0, telnet_newline) ;
+
+ /* Make an empty context object and an empty parsed object */
+ cli->context = cmd_context_new() ;
+ cli->parsed = cmd_parsed_new() ;
+
+ /* Use global 'lines' setting, as default -- but 'lines_set' false. */
+ uty_cli_set_lines(cli, host.lines, false) ;
+ uty_cli_update_more(cli) ; /* and update the olc etc to suit. */
+
+ /* Enable "~ " prompt and pause timer if multi-threaded
+ *
+ * TODO decide whether to ditch the '~' prompt, given the priority given
+ * to commands.
+ */
+ if (vty_nexus)
+ {
+ cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ vty_term_pause_timeout, cli) ;
+#if 0
+ cli->tilde_enabled = vty_multi_nexus ;
+#endif
+ } ;
+
+ /* Ready to be started -- paused, out_active & more_wait are false.
+ *
+ * Is started by the first call of uty_cli_want_command(), which (inter alia)
+ * set the cli->context so CLI knows how to prompt etc.
+ */
+ cli->dispatched = true ;
+ cli->in_progress = true ;
+ cli->blocked = true ;
+
+ return cli ;
+} ;
/*------------------------------------------------------------------------------
- * Update vty_host_name as per "hostname" or "no hostname" command
+ * Close CLI object, if any -- may be called more than once.
+ *
+ * Shuts down and discards anything to do with the input.
+ *
+ * Revokes anything that can be revoked, which may allow output to proceed
+ * and the vty to clear itself down.
+ *
+ * If dispatched and not final, keeps the cbuf, the olc and clx if dispatched.
+ * If not final, keeps the cbuf and the olc.
+ *
+ * Destroys self if final.
+ *
+ * Expects other code to:
+ *
+ * - close down command loop and empty the vin/vout stacks.
+ *
+ * - turn of any monitor status.
+ *
+ * - half close the telnet connection.
*/
-extern void
-uty_set_host_name(const char* name)
+extern vty_cli
+uty_cli_close(vty_cli cli, bool final)
{
- VTY_ASSERT_LOCKED() ;
+ if (cli == NULL)
+ return NULL ;
- vty_host_name_set = (name != NULL) ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- if (vty_host_name_set)
- uty_new_host_name(name) ;
- else
- uty_check_host_name() ;
+ /* Bring as much of the command handling to a stop as possible, and
+ * turn off "---more--" etc. so that output can proceed.
+ */
+ cli->more_enabled = false ;
+ cli->lines = 0 ; /* so will not be re-enabled */
+
+ uty_cli_cancel(cli) ; /* " ^C\r\n" unless believed blank */
+
+ cli->more_wait = false ; /* exit more_wait (if was in it) */
+ cli->more_enter = false ; /* and so cannot be this */
+
+ cli->blocked = true ; /* do not renter the CLI */
+ cli->out_active = true ; /* if there is any output, it can go */
+
+ /* Ream out the history. */
+ {
+ qstring line ;
+ while ((line = vector_ream(cli->hist, free_it)) != NULL)
+ qs_reset(line, free_it) ;
+ cli->hist = NULL ;
+ } ;
+
+ /* Empty the keystroke handling. */
+ cli->key_stream = keystroke_stream_free(cli->key_stream) ;
+
+ /* Can discard active command line if not dispatched */
+ if (!cli->dispatched || final)
+ cli->clx = qs_reset(cli->clx, free_it) ;
+
+ /* Can discard context and parsed objects */
+ cli->context = cmd_context_free(cli->context, true) ; /* its a copy */
+ cli->parsed = cmd_parsed_free(cli->parsed) ;
+
+ /* Discard any pause_timer, and suppress */
+ cli->pause_timer = qtimer_free(cli->pause_timer) ;
+ cli->paused = false ;
+#if 0
+ cli->tilde_enabled = false ;
+#endif
+
+ /* If final, free the CLI object. */
+ if (final)
+ {
+ cli->prompt_for_node = qs_free(cli->prompt_for_node) ;
+ cli->cl = qs_free(cli->cl) ;
+ cli->cls = qs_free(cli->cls) ;
+
+ cli->cbuf = vio_fifo_free(cli->cbuf) ;
+ cli->olc = vio_lc_free(cli->olc) ;
+
+ XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */
+ } ;
+
+ return cli ;
} ;
/*------------------------------------------------------------------------------
- * If vty_host_name is set, free it.
+ * The keystroke callback function.
+ *
+ * This deals with interrupts and IAC sequences that should be dealt with as
+ * soon as they are read -- not stored in the keystroke stream for later
+ * processing.
+ *
+ * Returns: true <=> dealt with keystroke, do not store in keystroke stream
*/
-extern void
-uty_free_host_name(void)
+static bool
+uty_cli_callback(keystroke_callback_args /* void* context, keystroke stroke */)
{
- if (vty_host_name != NULL)
- XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */
+ vty_cli cli = context ;
+
+ switch (stroke->type)
+ {
+ case ks_char: /* character -- uint32_t */
+ if ((cli->in_progress || cli->out_active) && !cli->more_wait)
+ {
+ vty_io vio = cli->vf->vio ;
+
+ uty_cmd_signal(vio, CMD_CANCEL) ; /* ^C */
+
+ return true ;
+ } ;
+
+ return false ;
+
+ case ks_iac: /* Telnet command */
+ return uty_telnet_command(cli->vf, stroke, true) ;
+
+ case ks_esc: /* ESC xx */
+ case ks_csi: /* ESC [ ... or CSI ... */
+ zabort("invalid interrupt keystroke type") ;
+
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * If the host name is not set by command, see if the actual host name has
- * changed, and if so change it.
+ * Set the cli->lines, explicitly or implicitly, and update the "--more--"
+ * state to suit.
*
- * This is done periodically in case the actual host name changes !
+ * lines < 0 => use whatever telnet has said, or no "--more--"
+ * lines = 0 => no "--more--"
+ * lines > 0 => use
*/
extern void
-uty_check_host_name(void)
+uty_cli_set_lines(vty_cli cli, int lines, bool explicit)
{
- struct utsname names ;
-
- VTY_ASSERT_LOCKED() ;
+ cli->lines = lines ;
+ cli->lines_set = explicit ;
- if (vty_host_name_set)
- return ; /* nothing to do if set by command */
+ uty_cli_update_more(cli) ;
+} ;
- uname (&names) ;
+/*------------------------------------------------------------------------------
+ * Set the cli->lines, explicitly or implicitly, and update the "--more--"
+ * state to suit.
+ *
+ * lines < 0 => use whatever telnet has said, or no "--more--"
+ * lines = 0 => no "--more--"
+ * lines > 0 =>
+ */
+extern void
+uty_cli_set_window(vty_cli cli, int width, int height)
+{
+ cli->width = width ;
+ cli->height = height ;
- if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0))
- uty_new_host_name(names.nodename) ;
+ uty_cli_update_more(cli) ;
} ;
/*------------------------------------------------------------------------------
- * Set new vty_host_name and run along list of VTYs to mark the change.
+ * Update the "--more--" state, after changing one or more of:
+ *
+ * cli->lines
+ * cli->lines_set
+ * cli->width
+ * cli->height
+ *
+ * Set the effective height for line control, if required, which 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.
*/
static void
-uty_new_host_name(const char* name)
+uty_cli_update_more(vty_cli cli)
{
- vty_io vio ;
-
- VTY_ASSERT_LOCKED() ;
+ bool on ;
- uty_free_host_name() ;
- vty_host_name = XSTRDUP(MTYPE_HOST, name) ;
+ on = false ; /* default state */
- vio = vio_list_base ;
- while (vio != NULL)
+ if (cli->vf->vout_state == vf_open)
{
- vio->cli_prompt_set = 0 ;
- vio = sdl_next(vio, vio_list) ;
+ int height ;
+
+ height = 0 ; /* default state: no "--more--" */
+
+ if ((cli->width) != 0)
+ {
+ /* If window size is known, use lines or given height */
+ if (cli->lines >= 0)
+ height = cli->lines ;
+ else
+ {
+ /* Window height, leaving one line from previous "page"
+ * and one line for the "--more--" -- if at all possible
+ */
+ height = cli->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 (cli->lines_set)
+ height = cli->lines ;
+ } ;
+
+ if (height > 0)
+ on = true ; /* have a defined height */
+ else
+ height = 200 ; /* but no "--more--" */
+
+ vio_lc_set_window(cli->olc, cli->width, height) ;
} ;
+
+ cli->more_enabled = on ;
} ;
/*==============================================================================
@@ -131,32 +431,63 @@ uty_new_host_name(const char* name)
* -- setting read or write on, means enabling the file for select/pselect to
* consider it for read_ready or write_ready, respectively.
*
+ * All command actions are dispatched via the command queue -- commands are
+ * not executed inside the CLI code. [For legacy threads the event queue is
+ * used. If that is too slow, a priority event queue will have to be
+ * invented.]
+ *
* State of the CLI:
*
- * cli_blocked -- the CLI is unable to process any further keystrokes.
+ * dispatched -- a command line has been dispatched, and is waiting for
+ * (dsp) command loop to fetch it.
+ *
+ * in_progress -- a command has been taken by the command loop, and is still
+ * (inp) running -- or at least no further command has been
+ * fetched by the command loop.
+ *
+ * The command may be an in_pipe, so there may be many
+ * commands to be completed before the CLI level command is.
+ *
+ * or: the CLI has been closed.
+ *
+ * blocked -- is in_progress and a further command is now ready to be
+ * (bkd) dispatched.
*
- * cmd_in_progress -- a command has been dispatched and has not yet
- * completed (may have been queued).
+ * or: the CLI has been closed.
*
- * cmd_out_enabled -- the command FIFO is may be emptied.
+ * out_active -- the command output FIFO is being emptied.
+ * (oa)
+ * This is set when output is pushed, and cleared when
+ * everything is written away and in_progress is not set.
+ * When it is set, any current command line is wiped.
*
- * This is set when a command completes, and cleared when
- * everything is written away.
+ * Note that what this flag does is prevent the CLI from
+ * running until the output completes, and in particular
+ * prevents it from writing anything to the CLI buffer.
*
- * cli_more_wait -- is in "--more--" wait state
+ * more_wait -- is in "--more--" wait state. => out_active !
+ * (mwt)
+ * The "--more--" CLI uses the CLI output buffer to draw
+ * and undraw the "--more--" prompt. The buffer will
+ * otherwise be empty because is out_active.
+ *
+ * more_enter -- is in the process of entering the "--more--" wait state,
+ * (men) waiting to write the "--more--" prompt and prepare the
+ * keystroke input.
*
* The following are the valid combinations:
*
- * blkd : cip : o_en : m_wt :
- * -----:------:------:------:--------------------------------------------
- * 0 : 0 : 0 : 0 : collecting a new command
- * 0 : 1 : 0 : 0 : command dispatched
- * 1 : 1 : 0 : 0 : waiting for (queued) command to complete
- * 1 : 0 : 1 : 0 : waiting for command output to complete
- * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away
- * 0 : 0 : 0 : 1 : waiting for "--more--" response
- * 1 : 1 : 1 : 0 : waiting for command to complete, after the
- * CLI has been closed
+ * dsp:inp:bkd: oa:mwt:men:
+ * ---:---:---:---:---:---:-----------------------------------------
+ * 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command
+ * 1 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched
+ * 1 : 1 : 0 : X : 0 : 0 : command fetched and running
+ * 1 : 1 : 1 : X : 0 : 0 : waiting for command to complete
+ * 0 : 0 : 0 : 1 : 0 : 0 : waiting for command output to finish
+ * 1 : X : X : 1 : 1 : 1 : waiting for "--more--" to start
+ * 1 : X : X : 1 : 1 : 0 : waiting for "--more--" response
+ * 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete,
+ * after the CLI has been closed
*
* There are two output FIFOs:
*
@@ -165,56 +496,43 @@ uty_new_host_name(const char* name)
* 2. for output generated by commands -- vty_out and friends.
*
* The CLI FIFO is emptied whenever possible, in preference to the command
- * FIFO. The command FIFO is emptied when cmd_out_enabled. While
- * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a
- * given command is collected together before being sent to the file.
+ * FIFO. The command FIFO is emptied when out_active. While a command is
+ * in_progress all its output is collected in the command output buffer,
+ * to be written away when the command completes, or if it pushes the output
+ * explicitly. Note that where the CLI level command is an in_pipe, the first
+ * output will set out_active, and that will persist until returns to the CLI
+ * level. Between commands in the pipe, the output will be pushed, so will
+ * output stuff more or less as it is generated.
+ *
+ * It is expected that each command's output will end with a newline. However,
+ * the line control imposes some discipline, and holds on to incomplete lines
+ * until a newline arrives, or the output is because in_progress is false -- so
+ * that when the CLI is kicked, the cursor will be at the start of an empty
+ * line.
*
- * Note that only sets read on when the keystroke stream is empty and has not
- * yet hit eof. The CLI process is driven mostly by write_ready -- which
- * invokes read_ready.
+ * Note that is read ready until the keystroke stream hits eof. So any input
+ * will be hoovered up as soon as it is available. The CLI process is driven
+ * mostly by write_ready, except for when all output is complete and the
+ * input keystroke buffer has been emptied.
*
* Note also that after each command dispatch the CLI processor exits, to be
* re-entered again on write_ready/read_ready -- so does one command line at
* a time, yielding the processor after each one.
*
- * Note that select/pselect treat a socket which is at "EOF", or has seen an
- * error, or has been half closed, etc. as readable and writable. This means
- * that the CLI will continue to move forward even after the socket is no
- * longer delivering any data.
- *
*------------------------------------------------------------------------------
* The "--more--" handling.
*
* This is largely buried in the output handling.
*
- * While cmd_in_progress is true cmd_out_enabled will be false. When the
- * command completes:
- *
- * * cmd_in_progress is cleared
- *
- * * cmd_out_enabled is set
- *
- * * cli_blocked will be set
- *
- * * the line_control structure is reset
- *
- * * the output process is kicked off by setting write on
+ * When command is pushed to written away, out_active will be set (and any
+ * command line will be wiped). The output process is then kicked.
*
* The output process used the line_control structure to manage the output, and
* occasionally enter the trivial "--more--" CLI. This is invisible to the
- * main CLI. (See the cli_more_wait flag and its handling.)
- *
- * When all the output has completed the CLI will be kicked, which will see
- * that the output buffer is now empty, and it can proceed.
- *
- * It is expected that the output will end with a newline -- so that when the
- * CLI is kicked, the cursor will be at the start of an empty line.
- *
- * This mechanism means that the command output FIFO only ever contains the
- * output from the last command executed.
+ * main CLI. (See the more_wait and more_enter flags and their handling.)
*
* If the user decides to abandon output at the "--more--" prompt, then the
- * contents of the command output FIFO are discarded.
+ * then contents of the command output FIFO are discarded.
*
*------------------------------------------------------------------------------
* The qpthreads/qpnexus extension.
@@ -262,104 +580,17 @@ uty_new_host_name(const char* name)
* The CLI
*/
-#define CONTROL(X) ((X) - '@')
-
-static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ;
-static enum vty_readiness uty_cli_standard(vty_io vio) ;
-static enum vty_readiness uty_cli_more_wait(vty_io vio) ;
-static void uty_cli_draw(vty_io vio) ;
-static void uty_cli_draw_this(vty_io vio, enum node_type node) ;
-static void uty_cli_wipe(vty_io vio, int len) ;
-
-static void uty_will_echo (vty_io vio) ;
-static void uty_will_suppress_go_ahead (vty_io vio) ;
-static void uty_dont_linemode (vty_io vio) ;
-static void uty_do_window_size (vty_io vio) ;
-static void uty_dont_lflow_ahead (vty_io vio) ;
-
-/*------------------------------------------------------------------------------
- * Initialise CLI.
- *
- * It is assumed that the following have been initialised, empty or zero:
- *
- * cli_prompt_for_node
- * cl
- * clx
- * cli_vbuf
- * cli_obuf
- *
- * cli_drawn
- * cli_dirty
- *
- * cli_prompt_set
- *
- * cli_blocked
- * cmd_in_progress
- * cmd_out_enabled
- * cli_wait_more
- *
- * cli_more_enabled
- *
- * Sets the CLI such that there is apparently a command in progress, so that
- * further initialisation (in particular hello messages and the like) is
- * treated as a "start up command".
- *
- * Sends a suitable set of Telnet commands to start the process.
- */
-extern void
-uty_cli_init(vty_io vio)
-{
- assert(vio->type == VTY_TERM) ;
-
- vio->cmd_in_progress = 1 ;
- vio->cli_blocked = 1 ;
+#define CONTROL(X) ((X) & 0x1F)
- 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) ;
-} ;
+static enum vty_readiness uty_cli_standard(vty_cli cli) ;
+static cmd_do_t uty_cli_auth(vty_cli cli) ;
+static enum vty_readiness uty_cli_more_wait(vty_cli cli) ;
+static void uty_cli_draw(vty_cli cli) ;
/*------------------------------------------------------------------------------
- * Start the CLI.
+ * CLI for VTY_TERMINAL
*
- * All start-up operations are complete -- so the "command" is now complete.
- *
- * Returns: write_ready -- so the first event is a write event, to flush
- * any output to date.
- */
-extern enum vty_readiness
-uty_cli_start(vty_io vio)
-{
- uty_cli_cmd_complete(vio, CMD_SUCCESS) ;
- return write_ready ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close the CLI
- *
- * Note that if any command is revoked, then will clear cmd_in_progress and
- * set cmd_out_enabled -- so any output can now clear.
- */
-extern void
-uty_cli_close(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
- assert(vio->type == VTY_TERM) ;
-
- cq_revoke(vio->vty) ;
-
- vio->cli_blocked = 1 ; /* don't attempt any more */
- vio->cmd_out_enabled = 1 ; /* allow output to clear */
-} ;
-
-/*------------------------------------------------------------------------------
- * CLI for VTY_TERM
+ * Called by uty_term_ready(), so driven by read/write ready.
*
* Do nothing at all if half closed.
*
@@ -369,29 +600,34 @@ uty_cli_close(vty_io vio)
* 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)
+extern vty_readiness_t
+uty_cli(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert((vio->type == VTY_TERM) || (vio->real_type == VTY_TERM)) ;
- if (vio->half_closed)
- return not_ready ; /* Nothing more if half closed */
+ if (cli->vf->vin_state == vf_closed)
+ return not_ready ; /* Nothing more from CLI if closed */
/* Standard or "--more--" CLI ? */
- if (vio->cli_more_wait)
- return uty_cli_more_wait(vio) ;
+ if (cli->more_wait)
+ return uty_cli_more_wait(cli) ;
else
- return uty_cli_standard(vio) ;
+ return uty_cli_standard(cli) ;
} ;
/*==============================================================================
* The Standard CLI
*/
-static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ;
-static void uty_cli_response(vty_io vio, enum cli_do cli_do) ;
-static bool uty_cli_dispatch(vty_io vio) ;
+static cmd_do_t uty_cli_process(vty_cli cli) ;
+static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ;
+
+static bool uty_cli_dispatch(vty_cli cli) ;
+static cmd_do_t uty_cli_command(vty_cli cli) ;
+static void uty_cli_hist_add (vty_cli cli, qstring clx) ;
+
+static void uty_cli_pause_start(vty_cli cli) ;
+static void uty_cli_goto_end_if_drawn(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Standard CLI for VTY_TERM -- if not blocked, runs until:
@@ -407,282 +643,316 @@ static bool uty_cli_dispatch(vty_io vio) ;
* write_ready if there is anything in the keystroke stream
* read_ready otherwise
*/
-static enum vty_readiness
-uty_cli_standard(vty_io vio)
+static vty_readiness_t
+uty_cli_standard(vty_cli cli)
{
+ bool need_prompt ;
+
VTY_ASSERT_LOCKED() ;
- /* cli_blocked is set when is waiting for a command, or its output to
- * complete -- unless either of those has happened, is still blocked.
+ assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */
+
+ /* cli_blocked is set when is waiting for a command, or some output to
+ * complete.
*
* NB: in both these cases, assumes that other forces are at work to
* keep things moving.
*/
- if (vio->cli_blocked)
- {
- assert(vio->cmd_in_progress || vio->cmd_out_enabled) ;
-
- if (vio->cmd_in_progress)
- {
- assert(!vio->cmd_out_enabled) ;
- return not_ready ;
- } ;
+ if (cli->blocked || cli->out_active || cli->paused || cli->mon_active)
+ 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.
+ /* Make sure that the command line is drawn.
*
- * 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 there is nothing pending, then can run the CLI until there is
+ * something to do, or runs out of input.
*/
- if (vio->cli_do == cli_do_nothing)
- vio->cli_do = uty_cli_process(vio, vio->vty->node) ;
- else
- uty_cli_draw_this(vio, vio->vty->node) ;
+ if (!cli->drawn)
+ uty_cli_draw(cli) ;
+
+ if (cli->to_do == cmd_do_nothing)
+ cli->to_do = cli->auth_context ? uty_cli_auth(cli)
+ : uty_cli_process(cli) ;
+
+ /* If have something to do, do it if we can. */
+ need_prompt = false ;
- /* If have something to do, do it. */
- if (vio->cli_do != cli_do_nothing)
+ if (cli->to_do != cmd_do_nothing)
{
- /* Reflect immediate response */
- uty_cli_response(vio, vio->cli_do) ;
+ /* Reflect immediate response */
+ uty_cli_response(cli, cli->to_do) ;
/* If command not already in progress, dispatch this one, which may
* set the CLI blocked.
*
* Otherwise is now blocked until queued command completes.
*/
- if (!vio->cmd_in_progress)
- vio->cli_blocked = uty_cli_dispatch(vio) ;
+ if (cli->dispatched)
+ cli->blocked = true ; /* waiting for previous */
else
- vio->cli_blocked = 1 ;
+ need_prompt = uty_cli_dispatch(cli) ; /* dispatch latest */
} ;
- /* Use write_ready as a proxy for read_ready on the keystroke stream.
+ /* If blocked, must wait for some other event to continue in CLI.
*
- * Also, if the command line is not drawn, then return write_ready, so
- * that
+ * Note that will be blocked if have just dispatched a command, and is
+ * not "tilde_enabled" -- which is the case if single threaded, and may be
+ * so for other reasons.
*
- * Note that if has just gone cli_blocked, still returns ready. This is
- * defensive: at worst will generate one unnecessary read_ready/write_ready
- * event.
+ * If the keystroke stream is not empty, use write_ready as a proxy for
+ * CLI ready -- no point doing anything until any buffered output has been
+ * written away.
+ *
+ * If command prompt has been redrawn, need to kick writer to deal with
+ * that -- will reenter to then process any keystrokes.
*/
- if (keystroke_stream_empty(vio->key_stream))
- return read_ready ;
- else
+ assert(!cli->paused) ;
+
+ if (cli->blocked)
+ return not_ready ;
+
+ if (!keystroke_stream_empty(cli->key_stream) || need_prompt)
return write_ready ;
+
+ /* The keystroke stream is empty, but CLI is not blocked.
+ *
+ * If a command is dispatched, and output is not active, and the command
+ * line is not drawn, then go to paused state, so that will delay output
+ * of the prompt slightly (or until a keystroke arrives, or the current
+ * command completes, or something else happens).
+ *
+ * Note that if a command has been dispatched, and is !tilde_enabled, then
+ * will now be blocked, so won't be here.
+ */
+ if (cli->dispatched && !cli->out_active && !cli->drawn)
+ uty_cli_pause_start(cli) ;
+
+ return read_ready ;
} ;
/*------------------------------------------------------------------------------
- * Dispatch the current vio->cli_do -- queueing it if necessary.
+ * Dispatch the current cli->to_do -- queueing it if necessary.
*
* Requires that are NOT blocked and NO command is queued.
*
* Expects to be on new blank line, and when returns will be on new, blank
* line.
*
- * Returns: true <=> command completed and output is pending
- * false => command has been queued and is now in progress
+ * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty.
*
- * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty.
+ * Can set cli->cl_do = and cli->cl to be a follow-on command.
*
- * Can set vio->cl_do = and vio->cl to be a follow-on command.
+ * Returns: true <=> nothing, in fact, to do: prompt has been redrawn.
*/
static bool
-uty_cli_dispatch(vty_io vio)
+uty_cli_dispatch(vty_cli cli)
{
- qstring_t tmp ;
- enum cli_do cli_do ;
- enum cmd_return_code ret ;
+ qstring tmp ;
+ cmd_do_t to_do_now ;
- struct vty* vty = vio->vty ;
+ vty_io vio = cli->vf->vio ;
VTY_ASSERT_LOCKED() ;
- assert(!vio->cli_blocked && !vio->cmd_in_progress) ;
- /* Set vio->clx to the command about to execute.
+ /* About to dispatch a command, so must be in the following state. */
+ qassert(!cli->dispatched && !cli->in_progress
+ && !cli->blocked && !cli->out_active) ;
+ qassert(cli->context->node == vio->vty->exec->context->node) ;
+
+ /* Set cli->clx to the command about to execute & pick up cli->to_do.
*
- * Clear vio->cl and vio->cl_do.
+ * Clear cli->cl and cli->to_do.
*/
- vio->cmd_in_progress = 1 ; /* => vty->buf is valid */
- vio->cmd_out_enabled = 0 ; /* => collect all output */
-
- tmp = vio->clx ; /* swap clx and cl */
- vio->clx = vio->cl ;
- vio->cl = tmp ;
+ tmp = cli->clx ; /* swap clx and cl */
+ cli->clx = cli->cl ;
+ cli->cl = tmp ;
- qs_term(&vio->clx) ; /* ensure string is terminated */
- vty->buf = qs_chars(&vio->clx) ; /* terminated command line */
- cli_do = vio->cli_do ; /* current operation */
+ to_do_now = cli->to_do ; /* current operation */
- vio->cli_do = cli_do_nothing ; /* clear */
- qs_clear(&vio->cl) ; /* set cl empty (with '\0') */
+ cli->to_do = cmd_do_nothing ; /* clear */
+ qs_clear(cli->cl) ; /* set cl empty */
- /* Reset the command output FIFO and line_control */
- assert(vio_fifo_empty(&vio->cmd_obuf)) ;
- uty_out_clear(vio) ; /* clears FIFO and line control */
-
- /* Dispatch command */
- if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
- {
- /* AUTH_NODE and AUTH_ENABLE_NODE are unique */
- ret = uty_auth(vty, vty->buf, cli_do) ;
- }
+ /* Dispatch command */
+ if (cli->auth_context)
+ to_do_now |= cmd_do_auth ;
else
{
- /* All other nodes... */
- switch (cli_do)
+ /* All other nodes... */
+ switch (to_do_now)
{
- case cli_do_nothing:
- ret = CMD_SUCCESS ;
+ case cmd_do_nothing:
+ case cmd_do_ctrl_c:
+ case cmd_do_eof:
+ case cmd_do_timed_out:
break ;
- case cli_do_command:
- ret = uty_command(vty) ;
+ case cmd_do_command:
+ to_do_now = uty_cli_command(cli) ;
break ;
- case cli_do_ctrl_c:
- ret = uty_stop_input(vty) ;
+ case cmd_do_ctrl_d:
+ zabort("invalid cmd_do_ctrl_d") ;
break ;
- case cli_do_ctrl_d:
- ret = uty_down_level(vty) ;
- break ;
+ case cmd_do_ctrl_z:
+ to_do_now = uty_cli_command(cli) ;
- case cli_do_ctrl_z:
- ret = uty_command(vty) ;
- if (ret == CMD_QUEUED)
- vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */
+ if (to_do_now == cmd_do_nothing)
+ to_do_now = cmd_do_ctrl_z ;
else
- ret = uty_end_config(vty) ; /* do the ^Z now */
- break ;
+ cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */
- case cli_do_eof:
- ret = uty_cmd_close(vio->vty, "End") ;
break ;
default:
- zabort("unknown cli_do_xxx value") ;
+ zabort("unknown cmd_do_xxx value") ;
} ;
} ;
- if (ret == CMD_QUEUED)
+ cli->hp = cli->h_now ; /* in any event, back to the present */
+
+ if (to_do_now != cmd_do_nothing)
{
- uty_cli_draw(vio) ; /* draw the prompt */
- return false ; /* command not complete */
+ cmd_action_set(cli->dispatch, to_do_now, cli->clx) ;
+ cli->dispatched = true ;
+
+ uty_cmd_signal(vio, CMD_SUCCESS) ;
+
+ cli->blocked = (to_do_now != cmd_do_command)
+#if 0
+ || !cli->tilde_enabled
+#endif
+ ;
}
else
{
- uty_cli_cmd_complete(vio, ret) ;
- return true ; /* command complete */
+ cmd_action_clear(cli->dispatch) ;
+ cli->dispatched = false ;
+
+ uty_cli_draw(cli) ;
} ;
+
+ return !cli->dispatched ;
} ;
/*------------------------------------------------------------------------------
- * Queued command has completed.
+ * Check if command is empty, if not add to history
*
- * Note that sets write on whether there is anything in the output buffer
- * or not... write_ready will kick read_ready.
+ * Returns: cmd_do_nothing -- empty command line
+ * cmd_do_command -- command ready to be executed (added to history)
*/
-extern void
-vty_queued_result(struct vty *vty, enum cmd_return_code ret)
+static cmd_do_t
+uty_cli_command(vty_cli cli)
{
- vty_io vio ;
+ if (cmd_is_empty(cli->clx))
+ return cmd_do_nothing ; /* easy when nothing to do ! */
- VTY_LOCK() ;
+ /* Add not empty command to history */
+ uty_cli_hist_add(cli, cli->clx) ;
- vio = vty->vio ;
-
- if (!vio->closed)
- {
- uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */
-
- /* Do the command completion actions that were deferred because the
- * command was queued.
- *
- * Return of CMD_QUEUED => command was revoked before being executed.
- * However interesting that might be... frankly don't care.
- */
- uty_cli_cmd_complete(vio, ret) ;
-
- /* Kick the socket -- to write away any outstanding output, and
- * re-enter the CLI when that's done.
- */
- uty_sock_set_readiness(&vio->sock, write_ready) ;
- }
- else
- {
- /* If the VTY is closed, the only reason it still exists is because
- * there was cmd_in_progress.
- */
- vio->cmd_in_progress = 0 ;
-
- uty_close(vio) ; /* Final close */
- } ;
-
- VTY_UNLOCK() ;
-}
+ return cmd_do_command ;
+} ;
/*------------------------------------------------------------------------------
- * Command has completed, so:
+ * Want another command line from the CLI.
+ *
+ * If in_progress this <=> any previous command has completed, and output may
+ * be active writing the results away. Will:
*
- * * 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
+ * * clear cli->dispatched
+ * * clear cli->in_progress
+ * * clear cli->blocked
*
- * If the return is CMD_CLOSE, then also now does the required half close.
+ * May be in more_wait state -- so avoids touching that.
*
- * 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.
+ * If not in_progress, then if dispatched, we have a new command ready to pass
+ * to the command loop -- which we do here, and set cli->in_progress.
*
- * Note that everything proceeds as if there is some output. So after every
- * command goes through at least one write_ready event.
+ * Returns: CMD_SUCCESS -- the given action has been set to next command
+ * or: CMD_WAITING -- no command available (yet)
*
- * This ensures some multiplexing at the command level.
+ * Note that for the CLI eof and read time-out are handled as cmd_do_eof and
+ * cmd_do_timed_out -- so will float through as CMD_SUCCESS and be processed
+ * as commands.
*
- * It also means that the decision about whether there is anything to output
- * is left to the output code.
+ * Write I/O errors and time-outs are signalled by uty_vf_error(), and
+ * therefore caught in the command loop.
+ *
+ * Read I/O errors are signalled by uty_vf_error(). Read timeout is treated
+ * as above.
*/
-static void
-uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret)
+extern cmd_return_code_t
+uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context)
{
VTY_ASSERT_LOCKED() ;
- assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ;
+ if (cli->in_progress)
+ {
+ /* Previous command has completed
+ *
+ * Make sure state reflects the fact that we are now waiting for a
+ * command.
+ */
+ cli->dispatched = false ; /* no longer have a dispatched command */
+ cli->in_progress = false ; /* command complete */
+ cli->blocked = false ; /* releases possibly blocked command */
+ cli->paused = false ; /* override paused state */
+
+ *cli->context = *context ; /* make sure is up to date */
+ cli->auth_context = ( (cli->context->node == AUTH_NODE)
+ || (cli->context->node == AUTH_ENABLE_NODE) ) ;
+
+ /* If the output is owned by command output, then when the buffers
+ * empty, and in_progress is seen to be false, out_active will be
+ * cleared.
+ *
+ * If the output side is not owned by command output, rewrite the
+ * command line, so that prompt is up to date and visible.
+ *
+ * In any case, kick write_ready to ensure output clears and prompt is
+ * written and so on.
+ */
+ if (!cli->out_active)
+ uty_cli_draw(cli) ;
- if (ret == CMD_CLOSE)
- uty_half_close(vio, NULL) ;
+ uty_term_set_readiness(cli->vf, write_ready) ;
+ }
+ else if (cli->dispatched)
+ {
+ /* New command has been dispatched -- can now pass that to the
+ * command loop -- setting it in_progress.
+ */
+ assert(cli->dispatch->to_do != cmd_do_nothing) ;
+ cmd_action_take(action, cli->dispatch) ;
- vio->cmd_in_progress = 0 ; /* command complete */
- vio->cmd_out_enabled = 1 ; /* enable the output */
- vio->cli_blocked = 1 ; /* now blocked waiting for output */
+ cli->in_progress = true ;
+ } ;
- vio->vty->buf = NULL ; /* finished with command line */
+ return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ;
+} ;
- uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */
+/*------------------------------------------------------------------------------
+ * Start pause timer and set paused.
+ */
+static void
+uty_cli_pause_start(vty_cli cli)
+{
+ qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ;
+ cli->paused = true ;
} ;
/*==============================================================================
* The "--more--" CLI
*
- * While command output is being cleared from its FIFO, the CLI is cli_blocked.
+ * While command output is being cleared from its FIFO, the CLI is blocked.
*
* When the output side signals that "--more--" is required, it sets the
- * cli_more_wait flag and clears the cmd_out_enabled flag.
+ * more_wait and more_enter flags.
*
* 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.
+ * The more_enter flag indicates that the CLI is in this first stage.
*/
/*------------------------------------------------------------------------------
@@ -691,146 +961,211 @@ uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret)
* Outputs the new prompt line.
*/
extern void
-uty_cli_enter_more_wait(vty_io vio)
+uty_cli_enter_more_wait(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ;
+ qassert(cli->out_active && !cli->more_wait && !cli->drawn) ;
- 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--" */
+ cli->more_wait = true ; /* new state */
+ cli->more_enter = true ; /* drawing the "--more--" etc. */
} ;
/*------------------------------------------------------------------------------
- * Exit the "--more--" CLI.
+ * Handle the "--more--" state.
*
- * Wipes the "--more--" prompt.
+ * If is paused or the monitor is active, do nothing -- those override the
+ * "--more--" state.
*
- * This is used when the user responds to the prompt.
+ * If more_enter is set, then need to make sure the "--more--" prompt is
+ * written, and that any keystrokes which arrived before the prompt is
+ * written are taken into the keystroke stream -- so not stolen.
*
- * It is also used when the vty is "half-closed". In this case, it is (just)
- * possible that the '--more--' prompt is yet to be completely written away,
- * so:
+ * Note that the "--more--" state may be interrupted by monitor output,
+ * which, once complete, sets more_enter again.
*
- * * assert that is either: !vio->cli_blocked (most of the time it will)
- * or: !vio_fifo_empty(&vio->cli_obuf)
- *
- * * note that can wipe the prompt even though it hasn't been fully
- * written away yet. (The effect is to append the wipe action to the
- * cli_obuf !)
- */
-extern void
-uty_cli_exit_more_wait(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf))
- && !vio->cmd_out_enabled && vio->cli_more_wait) ;
-
- uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--')
- before changing the CLI state */
-
- vio->cli_blocked = 1 ; /* back to blocked waiting for output */
- vio->cli_more_wait = 0 ; /* exit more_wait */
- vio->cmd_out_enabled = 1 ; /* re-enable output */
-} ;
-
-/*------------------------------------------------------------------------------
- * Handle the "--more--" state.
- *
- * Deals with the first stage if cli_blocked.
- *
- * Tries to steal a keystroke, and when succeeds wipes the "--more--"
- * prompt and exits cli_more_wait -- and may cancel all outstanding output.
+ * If more_enter is not set, tries to steal a keystroke, and if 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
+ * Returns: read_ready -- waiting to steal a keystroke
+ * write_ready -- waiting to draw or undraw the "--more--" prompt
+ * not_ready -- otherwise
*/
-static enum vty_readiness
-uty_cli_more_wait(vty_io vio)
+static vty_readiness_t
+uty_cli_more_wait(vty_cli cli)
{
- struct keystroke steal ;
+ keystroke_t steal ;
+ bool cancel, stolen ;
VTY_ASSERT_LOCKED() ;
+ assert(cli->more_wait) ; /* must be in more_wait state ! */
+
+ if (cli->paused || cli->mon_active)
+ return not_ready ;
+
/* Deal with the first stage of "--more--" */
- if (vio->cli_blocked)
+ if (cli->more_enter)
{
int get ;
+ if (!cli->drawn)
+ uty_cli_draw(cli) ; /* draw the "--more--" */
+
/* 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 ;
+ if (!vio_fifo_empty(cli->cbuf))
+ return write_ready ;
- vio->cli_blocked = 0 ;
+ cli->more_enter = false ;
/* empty the input buffer into the keystroke stream */
do
{
- get = uty_read(vio, NULL) ;
+ get = uty_term_read(cli->vf) ;
} 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) ;
+ /* Try to get a stolen keystroke. If the keystroke stream has hit
+ * EOF (for any reason, including error or timed-out), will get a ks_null
+ * stolen keystroke.
+ *
+ * If nothing to be stolen exit.
+ */
+ stolen = keystroke_steal(cli->key_stream, 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 ;
- } ;
+ if (!stolen)
+ return read_ready ;
- /* Stolen a keystroke -- a (very) few terminate all output */
- if (steal.type == ks_char)
- {
- switch (steal.value)
+ /* Something has been stolen, so exit "--more--" state, and continue
+ * or cancel.
+ */
+ cancel = false ;
+ switch(steal->type)
+ {
+ case ks_null: /* at EOF for whatever reason */
+ cancel = true ;
+ break ;
+
+ case ks_char:
+ switch (steal->value)
{
case CONTROL('C'):
+ case CONTROL('Z'):
case 'q':
case 'Q':
- uty_out_clear(vio) ;
+ cancel = true ;
break;
- default: /* everything else, thrown away */
+ default:
break ;
} ;
- } ;
+ break ;
+
+ case ks_esc:
+ cancel = steal->value == '\x1B' ;
+ break ;
+
+ default:
+ break ;
} ;
/* End of "--more--" process
*
- * Wipe out the prompt and update state.
+ * If cancelling, make sure is at end of "--more--" prompt, and then
+ * clear the "drawn". The cancel process will append a " ^C\n" !
+ *
+ * If not cancelling, wipe out the prompt and update state.
*
* Return write_ready to tidy up the screen and, unless cleared, write
* some more.
*/
- uty_cli_exit_more_wait(vio) ;
+ if (cancel)
+ {
+ vio_fifo_clear(cli->vf->obuf, false) ;
+ vio_lc_clear(cli->olc) ; /* clear & reset counter */
- return now_ready ;
+ uty_cli_goto_end_if_drawn(cli) ;
+ cli->drawn = false ;
+
+ qassert(cli->out_active) ;
+
+ uty_cmd_signal(cli->vf->vio, CMD_CANCEL) ; /* ^C */
+ }
+ else
+ {
+ vio_lc_counter_reset(cli->olc) ;
+ uty_cli_wipe(cli, 0) ;
+ } ;
+
+ cli->more_wait = false ; /* exit more_wait */
+ cli->more_enter = false ; /* tidy */
+
+ return write_ready ;
+} ;
+
+/*==============================================================================
+ * Monitor output.
+ *
+ * To prepare for monitor output, wipe as much as is necessary for the
+ * monitor line to appear correctly.
+ *
+ * After monitor output, may need to do two things:
+ *
+ * * if the output was incomplete, place the rump in the CLI buffer,
+ * so that:
+ *
+ * a. don't mess up the console with partial lines
+ *
+ * b. don't lose part of a message
+ *
+ * c. act as a brake on further monitor output -- cannot do any more
+ * until the last, part, line is dealt with.
+ *
+ * * restore the command line, unless it is empty !
+ */
+
+ /*-----------------------------------------------------------------------------
+ * Prepare for new monitor output line.
+ *
+ * Wipe any existing command line.
+ */
+extern void
+uty_cli_pre_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_wipe(cli, 0) ;
+
+ cli->mon_active = true ; /* block cli & enable empty of fifo */
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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 void
+uty_cli_post_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_pause_start(cli) ; /* do not draw prompt immediately */
+
+ cli->more_enter = cli->more_wait ;
+ /* revert to more_enter state */
+
+ cli->mon_active = false ; /* unblock cli & turn off output */
} ;
/*==============================================================================
@@ -838,27 +1173,22 @@ uty_cli_more_wait(vty_io vio)
*
* 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.
+ * Has a dedicated buffer in the struct vty_cli, 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.
+ * keyboard then may need to have intermediate buffering.
*
- * No actual I/O takes place here-- all "output" is to vio->cli_obuf
- * and/or vio->cli_ex_obuf
- *
- * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0.
- * This is used while passwords are entered and to allow command line changes
- * to be made while the line is not visible.
+ * No actual I/O takes place here-- all "output" is to cli->cbuf
*/
enum { cli_rep_count = 32 } ;
typedef const char cli_rep_char[cli_rep_count] ;
+typedef const char cli_rep[] ;
-static const char telnet_backspaces[] =
+static cli_rep telnet_backspaces =
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
@@ -866,86 +1196,46 @@ static const char telnet_backspaces[] =
} ;
CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ;
-static const char telnet_spaces[] =
- { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
- } ;
-CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ;
+/* 12345678901234567890123456789012 */
+static cli_rep telnet_spaces = " " ;
+static cli_rep telnet_dots = "................................" ;
+static cli_rep telnet_stars = "********************************" ;
-static const char* telnet_newline = "\r\n" ;
+CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ;
+CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ;
+CONFIRM(sizeof(telnet_stars) == (sizeof(cli_rep_char) + 1)) ;
-static void uty_cli_write(vty_io vio, const char *this, int len) ;
-static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ;
+static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ;
/*------------------------------------------------------------------------------
* CLI VTY output -- cf fprintf()
*/
-static void
-uty_cli_out(vty_io vio, const char *format, ...)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->sock.write_open)
- {
- va_list args ;
-
- va_start (args, format);
- vio_fifo_vprintf(&vio->cli_obuf, format, args) ;
- va_end(args);
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo user input
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open
- */
-static void
-uty_cli_echo(vty_io vio, const char *this, size_t len)
+extern void
+uty_cli_out(vty_cli cli, const char *format, ...)
{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
+ va_list args ;
- uty_cli_write(vio, this, len) ;
-}
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo 'n' characters using a cli_rep_char string
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE)
- */
-static void
-uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n)
-{
VTY_ASSERT_LOCKED() ;
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
-
- uty_cli_write_n(vio, chars, n) ;
-}
+ va_start (args, format);
+ vio_fifo_vprintf(cli->cbuf, format, args) ;
+ va_end(args);
+} ;
/*------------------------------------------------------------------------------
* CLI VTY output -- cf write()
*/
-inline static void
-uty_cli_write(vty_io vio, const char *this, int len)
+extern void
+uty_cli_write(vty_cli cli, const char *this, int len)
{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->sock.write_open)
- vio_fifo_put(&vio->cli_obuf, this, len) ;
+ vio_fifo_put_bytes(cli->cbuf, this, len) ;
} ;
/*------------------------------------------------------------------------------
* CLI VTY output -- write 'n' characters using a cli_rep_char string
*/
static void
-uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
+uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n)
{
int len ;
@@ -954,7 +1244,7 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
{
if (n < len)
len = n ;
- uty_cli_write(vio, chars, len) ;
+ uty_cli_write(cli, chars, len) ;
n -= len ;
} ;
} ;
@@ -965,32 +1255,33 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
* Returns length of string.
*/
static int
-uty_cli_write_s(vty_io vio, const char *str)
+uty_cli_write_s(vty_cli cli, const char *str)
{
int len ;
len = strlen(str) ;
if (len != 0)
- uty_cli_write(vio, str, len) ;
+ uty_cli_write(cli, str, len) ;
return len ;
} ;
/*==============================================================================
- * Standard Messages
+ * Prompts and responses
*/
/*------------------------------------------------------------------------------
* Send newline to the console.
*
- * Clears the cli_drawn and the cli_dirty flags.
+ * Clears the cli_drawn flag.
*/
-static void
-uty_cli_out_newline(vty_io vio)
+extern void
+uty_cli_out_newline(vty_cli cli)
{
- uty_cli_write(vio, telnet_newline, 2) ;
- vio->cli_drawn = 0 ;
- vio->cli_dirty = 0 ;
+ uty_cli_goto_end_if_drawn(cli) ;
+
+ uty_cli_write(cli, telnet_newline, 2) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
@@ -1000,23 +1291,56 @@ uty_cli_out_newline(vty_io vio)
* 'n' > 0, wipes characters forwards, leaving cursor where it is
*/
static void
-uty_cli_out_wipe_n(vty_io vio, int n)
+uty_cli_out_wipe_n(vty_cli cli, int n)
{
if (n < 0)
{
n = abs(n) ;
- uty_cli_write_n(vio, telnet_backspaces, n);
+ uty_cli_write_n(cli, telnet_backspaces, n);
} ;
if (n > 0)
{
- uty_cli_write_n(vio, telnet_spaces, n) ;
- uty_cli_write_n(vio, telnet_backspaces, n) ;
+ uty_cli_write_n(cli, telnet_spaces, n) ;
+ uty_cli_write_n(cli, telnet_backspaces, n) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the command line is drawn, then show that it has been cancelled.
+ *
+ * Clears cli->drawn.
+ */
+static void
+uty_cli_cancel(vty_cli cli)
+{
+ if (cli->drawn)
+ {
+ uty_cli_goto_end_if_drawn(cli) ;
+
+ uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ;
+
+ cli->drawn = false ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Send response to the given cli_do
+ * If the command line is drawn and the cursor is not at the end of the line,
+ * move the physical cursor to the end of the line.
+ *
+ * Assumes about to issue newline.
+ */
+static void
+uty_cli_goto_end_if_drawn(vty_cli cli)
+{
+ ulen after ;
+
+ if (cli->drawn && ( (after = qs_after_cp_nn(cli->cl)) != 0 ))
+ uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Send response to the given cmd_do
*
* If no command is in progress, then will send newline to signal that the
* command is about to be dispatched.
@@ -1024,141 +1348,105 @@ uty_cli_out_wipe_n(vty_io vio, int n)
* If command is in progress, then leaves cursor on '^' to signal that is now
* waiting for previous command to complete.
*/
-static const char* cli_response [2][cli_do_count] =
+static const char* cli_response [2][cmd_do_count] =
{
{ /* when not waiting for previous command to complete */
- [cli_do_command] = "",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
+ [cmd_do_command] = "",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
},
{ /* when waiting for a previous command to complete */
- [cli_do_command] = "^",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
+ [cmd_do_command] = "^",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
}
} ;
static void
-uty_cli_response(vty_io vio, enum cli_do cli_do)
+uty_cli_response(vty_cli cli, cmd_do_t to_do)
{
const char* str ;
int len ;
- if ((cli_do == cli_do_nothing) || (vio->half_closed))
- return ;
+ assert((to_do != cmd_do_nothing) && cli->drawn
+ && (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))) ;
- str = (cli_do < cli_do_count)
- ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ;
+ str = (to_do < cmd_do_count)
+ ? cli_response[cli->dispatched ? 1 : 0][to_do] : NULL ;
assert(str != NULL) ;
- len = uty_cli_write_s(vio, str) ;
+ len = uty_cli_write_s(cli, str) ;
- if (vio->cmd_in_progress)
+ if (cli->dispatched)
{
- vio->cli_extra_len = len ;
- uty_cli_write_n(vio, telnet_backspaces, len) ;
+ cli->extra_len = len ;
+ uty_cli_write_n(cli, telnet_backspaces, len) ;
}
else
{
- uty_cli_out_newline(vio) ;
+ uty_cli_out_newline(cli) ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Send various messages with trailing newline.
+ * Current prompt length
*/
-
-static void
-uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio)
+extern ulen
+uty_cli_prompt_len(vty_cli cli)
{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ;
- uty_cli_out_newline(vio) ;
+ return cli->prompt_len ;
} ;
-static void
-uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio)
-{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ;
- uty_cli_out_newline(vio) ;
-} ;
-
-/*==============================================================================
- * Command line draw and wipe
- */
-
/*------------------------------------------------------------------------------
* Wipe the current console line -- if any.
+ *
+ * If it is known that some characters will immediately be written, then
+ * passing the number of them as "len" will reduce the number of characters
+ * that have to be sent.
*/
-static void
-uty_cli_wipe(vty_io vio, int len)
+extern void
+uty_cli_wipe(vty_cli cli, int len)
{
int a ;
int b ;
- if (!vio->cli_drawn)
+ if (!cli->drawn)
return ; /* quit if already wiped */
- assert(vio->cl.cp <= vio->cl.len) ;
+ assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ;
/* Establish how much ahead and how much behind the cursor */
- a = vio->cli_extra_len ;
- b = vio->cli_prompt_len ;
+ a = cli->extra_len ;
+ b = cli->prompt_len ;
- if (!vio->cli_echo_suppress && !vio->cli_more_wait)
+ if (!cli->more_wait)
{
- a += vio->cl.len - vio->cl.cp ;
- b += vio->cl.cp ;
+ a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ;
+ b += qs_cp_nn(cli->cl) ;
} ;
/* Wipe anything ahead of the current position and ahead of new len */
if ((a + b) > len)
- uty_cli_out_wipe_n(vio, +a) ;
+ uty_cli_out_wipe_n(cli, +a) ;
/* Wipe anything behind current position, but ahead of new len */
if (b > len)
{
- uty_cli_out_wipe_n(vio, -(b - len)) ;
+ uty_cli_out_wipe_n(cli, -(b - len)) ;
b = len ; /* moved the cursor back */
} ;
/* Back to the beginning of the line */
- uty_cli_write_n(vio, telnet_backspaces, b) ;
+ uty_cli_write_n(cli, 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) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
@@ -1171,50 +1459,45 @@ uty_cli_draw(vty_io vio)
*
* Draws prompt according to the given 'node', except:
*
- * * if is half_closed, draw nothing -- wipes the current line
+ * * if is closing, draw nothing -- wipes the current line
*
- * * if is cli_more_wait, draw the "--more--" prompt
+ * * if is more_wait, draw the "--more--" prompt
*
- * * if is cmd_in_progress, draw the vestigial prompt.
+ * * if is dispatched, 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)
+ * Sets: cli->drawn = true
+ * cli->prompt_len = length of prompt used
+ * cli->extra_len = 0
*/
static void
-uty_cli_draw_this(vty_io vio, enum node_type node)
+uty_cli_draw(vty_cli cli)
{
const char* prompt ;
size_t l_len ;
int p_len ;
- if (vio->cli_dirty)
- uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */
-
/* Sort out what the prompt is. */
- if (vio->half_closed)
+ if (cli->vf->vin_state != vf_open)
{
prompt = "" ;
p_len = 0 ;
l_len = 0 ;
}
- else if (vio->cli_more_wait)
+ else if (cli->more_wait)
{
prompt = "--more--" ;
p_len = strlen(prompt) ;
l_len = 0 ;
}
- else if (vio->cmd_in_progress)
+ else if (cli->dispatched)
{
/* If there is a queued command, the prompt is a minimal affair. */
prompt = "~ " ;
p_len = strlen(prompt) ;
- l_len = vio->cl.len ;
+ l_len = qs_len_nn(cli->cl) ;
}
else
{
@@ -1225,316 +1508,234 @@ uty_cli_draw_this(vty_io vio, enum node_type node)
*
* If the host name changes, the cli_prompt_set flag is cleared.
*/
- if (!vio->cli_prompt_set || (node != vio->cli_prompt_node))
+ node_type_t node = cli->context->node ;
+
+ if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen))
{
const char* prompt ;
- if (vty_host_name == NULL)
- uty_check_host_name() ; /* should never be required */
-
prompt = cmd_prompt(node) ;
if (prompt == NULL)
{
- zlog_err("vty %s has node %d", uty_get_name(vio), node) ;
+ zlog_err("vty %s has node %d", uty_get_name(cli->vf->vio), node) ;
prompt = "%s ???: " ;
} ;
- qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name);
+ cli->prompt_for_node =
+ qs_printf(cli->prompt_for_node, prompt, host.name) ;
- vio->cli_prompt_node = node ;
- vio->cli_prompt_set = 1 ;
+ cli->prompt_node = node ;
+ cli->prompt_gen = host.name_gen ;
} ;
- prompt = vio->cli_prompt_for_node.body ;
- p_len = vio->cli_prompt_for_node.len ;
- l_len = vio->cl.len ;
+ prompt = qs_char(cli->prompt_for_node) ;
+ p_len = qs_len(cli->prompt_for_node) ;
+ l_len = qs_len_nn(cli->cl) ;
} ;
/* Now, if line is currently drawn, time to wipe it */
- if (vio->cli_drawn)
- uty_cli_wipe(vio, p_len + l_len) ;
+ if (cli->drawn)
+ uty_cli_wipe(cli, 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) ;
+ cli->drawn = true ;
+ cli->extra_len = false ;
- vio->cli_prompt_len = p_len ;
+ cli->prompt_len = p_len ;
- uty_cli_write(vio, prompt, p_len) ;
+ uty_cli_write(cli, prompt, p_len) ;
if (l_len != 0)
{
- uty_cli_write(vio, qs_chars(&vio->cl), l_len) ;
- if (vio->cl.cp < l_len)
- uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ;
- } ;
-} ;
-
-/*==============================================================================
- * Monitor output.
- *
- * To prepare for monitor output, wipe as much as is necessary for the
- * monitor line to appear correctly.
- *
- * After monitor output, may need to do two things:
- *
- * * if the output was incomplete, place the rump in the CLI buffer,
- * so that:
- *
- * a. don't mess up the console with partial lines
- *
- * b. don't lose part of a message
- *
- * c. act as a brake on further monitor output -- cannot do any more
- * until the last, part, line is dealt with.
- *
- * * restore the command line, unless it is empty !
- */
-
- /*-----------------------------------------------------------------------------
- * Prepare for new monitor output line.
- *
- * Wipe any existing command line.
- */
-extern void
-uty_cli_pre_monitor(vty_io vio, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- uty_cli_wipe(vio, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Recover from monitor line output.
- *
- * If monitor line failed to complete, append the rump to the CLI buffer.
- *
- * If have a non-empty command line, or is cli_more_wait, redraw the command
- * line.
- *
- * Returns: 0 => rump was empty and no command line stuff written
- * > 0 => rump not empty or some command line stuff written
- */
-extern int
-uty_cli_post_monitor(vty_io vio, const char* buf, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (len != 0)
- uty_cli_write(vio, buf, len) ;
+ if (cli->auth_context)
+ uty_cli_write_n(cli, telnet_stars, l_len) ;
+ else
+ uty_cli_write(cli, qs_char(cli->cl), l_len) ;
- if (vio->cli_more_wait || (vio->cl.len != 0))
- {
- uty_cli_draw(vio) ;
- ++len ;
+ if (qs_cp_nn(cli->cl) < l_len)
+ uty_cli_write_n(cli, telnet_backspaces, l_len - qs_cp_nn(cli->cl)) ;
} ;
-
- return len ;
} ;
/*==============================================================================
* Command line processing loop
*/
+static cmd_do_t uty_cli_get_keystroke(vty_cli cli, keystroke stroke) ;
+static void uty_cli_update_line(vty_cli cli, uint rc) ;
-static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ;
-static int uty_cli_insert (vty_io vio, const char* chars, int n) ;
-static int uty_cli_overwrite (vty_io vio, char* chars, int n) ;
-static int uty_cli_word_overwrite (vty_io vio, char *str) ;
-static int uty_cli_forwards(vty_io vio, int n) ;
-static int uty_cli_backwards(vty_io vio, int n) ;
-static int uty_cli_del_forwards(vty_io vio, int n) ;
-static int uty_cli_del_backwards(vty_io vio, int n) ;
-static int uty_cli_bol (vty_io vio) ;
-static int uty_cli_eol (vty_io vio) ;
-static int uty_cli_word_forwards_delta(vty_io vio) ;
-static int uty_cli_word_forwards(vty_io vio) ;
-static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ;
-static int uty_cli_word_backwards_pure (vty_io vio) ;
-static int uty_cli_word_backwards (vty_io vio) ;
-static int uty_cli_del_word_forwards(vty_io vio) ;
-static int uty_cli_del_word_backwards(vty_io vio) ;
-static int uty_cli_del_to_eol (vty_io vio) ;
-static int uty_cli_clear_line(vty_io vio) ;
-static int uty_cli_transpose_chars(vty_io vio) ;
-static void uty_cli_history_use(vty_io vio, int step) ;
-static void uty_cli_next_line(vty_io vio) ;
-static void uty_cli_previous_line (vty_io vio) ;
-static void uty_cli_complete_command (vty_io vio, enum node_type node) ;
-static void uty_cli_describe_command (vty_io vio, enum node_type node) ;
+static void uty_cli_insert (qstring cl, const char* chars, int n) ;
+static int uty_cli_forwards(qstring cl, int n) ;
+static int uty_cli_backwards(qstring cl, int n) ;
+static void uty_cli_del_forwards(qstring cl, int n) ;
+static void uty_cli_del_backwards(qstring cl, int n) ;
+static void uty_cli_bol(qstring cl) ;
+static void uty_cli_eol(qstring cl) ;
+static int uty_cli_word_forwards_delta(qstring cl) ;
+static void uty_cli_word_forwards(qstring cl) ;
+static int uty_cli_word_backwards_delta(qstring cl) ;
+static void uty_cli_word_backwards(qstring cl) ;
+static void uty_cli_del_word_forwards(qstring cl) ;
+static void uty_cli_del_word_backwards(qstring cl) ;
+static void uty_cli_del_to_eol(qstring cl) ;
+static void uty_cli_clear_line(qstring cl) ;
+static void uty_cli_transpose_chars(qstring cl) ;
+static void uty_cli_complete_command (vty_cli cli) ;
+static void uty_cli_describe_command (vty_cli cli) ;
-/*------------------------------------------------------------------------------
- * Fetch next keystroke, reading from the file if required.
- */
-static inline bool
-uty_cli_get_keystroke(vty_io vio, keystroke stroke)
+enum hist_step
{
- if (keystroke_get(vio->key_stream, stroke))
- return 1 ;
-
- uty_read(vio, NULL) ; /* not stealing */
+ hist_previous = -1,
+ hist_next = +1,
+};
- return keystroke_get(vio->key_stream, stroke) ;
-} ;
+static void uty_cli_hist_use(vty_cli cli, enum hist_step) ;
/*------------------------------------------------------------------------------
- * Process keystrokes until run out of input, or get something to cli_do.
+ * Process keystrokes until run out of input, or get something to cmd_do.
*
- * If required, draw the prompt and command line.
+ * Expects the command line to have been drawn, so that what is in cli->cl
+ * is what is on the screen.
*
* Process keystrokes until run out of stuff to do, or have a "command line"
- * that must now be executed.
+ * that must now be executed. Updates cli->cl.
*
* Processes the contents of the keystroke stream. If exhausts that, will set
* ready to read and return. (To give some "sharing".)
*
- * Returns: cli_do_xxxx
+ * Returns: cmd_do_xxxx
*
- * When returns the cl is '\0' terminated.
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
-static enum cli_do
-uty_cli_process(vty_io vio, enum node_type node)
+static cmd_do_t
+uty_cli_process(vty_cli cli)
{
- struct keystroke stroke ;
- uint8_t u ;
- int auth ;
- enum cli_do ret ;
+ keystroke_t stroke ;
+ uint8_t u ;
+ cmd_do_t to_do ;
+
+ qs_copy(cli->cls, cli->cl) ; /* current screen line */
- auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
+ assert(cli->drawn) ;
/* Now process as much as possible of what there is */
- ret = cli_do_nothing ;
- while (1)
+ do
{
- if (!vio->cli_drawn)
- uty_cli_draw_this(vio, node) ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- if (!uty_cli_get_keystroke(vio, &stroke))
- {
- ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ;
- break ;
- } ;
-
- if (stroke.flags != 0)
- {
- /* TODO: deal with broken keystrokes */
- continue ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- switch (stroke.type)
+ switch (stroke->type)
{
/* Straightforward character -----------------------------------*/
/* Note: only interested in 8-bit characters ! */
case ks_char:
- u = (uint8_t)stroke.value ;
+ u = (uint8_t)stroke->value ;
- switch (stroke.value)
+ switch (stroke->value)
{
case CONTROL('A'):
- uty_cli_bol (vio);
+ uty_cli_bol(cli->cl) ;
break;
case CONTROL('B'):
- uty_cli_backwards(vio, 1);
+ uty_cli_backwards(cli->cl, 1);
break;
case CONTROL('C'):
- ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/
+ to_do = cmd_do_ctrl_c ; /* Exit on ^C ..................*/
break ;
case CONTROL('D'):
- if (vio->cl.len == 0) /* if at start of empty line */
- ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/
- else
- uty_cli_del_forwards(vio, 1);
+ uty_cli_del_forwards(cli->cl, 1);
break;
case CONTROL('E'):
- uty_cli_eol (vio);
+ uty_cli_eol(cli->cl);
break;
case CONTROL('F'):
- uty_cli_forwards(vio, 1);
+ uty_cli_forwards(cli->cl, 1);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_backwards(vio, 1);
+ uty_cli_del_backwards(cli->cl, 1);
break;
case CONTROL('K'):
- uty_cli_del_to_eol (vio);
+ uty_cli_del_to_eol(cli->cl);
break;
case CONTROL('N'):
- uty_cli_next_line (vio);
+ uty_cli_hist_use(cli, hist_next) ;
break;
case CONTROL('P'):
- uty_cli_previous_line (vio);
+ uty_cli_hist_use(cli, hist_previous) ;
break;
case CONTROL('T'):
- uty_cli_transpose_chars (vio);
+ uty_cli_transpose_chars(cli->cl) ;
break;
case CONTROL('U'):
- uty_cli_clear_line(vio);
+ uty_cli_clear_line(cli->cl) ;
break;
case CONTROL('W'):
- uty_cli_del_word_backwards (vio);
+ uty_cli_del_word_backwards(cli->cl) ;
break;
case CONTROL('Z'):
- ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/
+ to_do = cmd_do_ctrl_z ; /* Exit on ^Z ..................*/
break;
case '\n':
case '\r':
- ret = cli_do_command ; /* Exit on CR or LF.............*/
+ to_do = cmd_do_command ; /* Exit on CR or LF.............*/
break ;
case '\t':
- if (auth)
- break ;
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert (cli->cl, " ", 1) ;
else
- uty_cli_complete_command (vio, node);
+ uty_cli_complete_command(cli);
break;
case '?':
- if (auth)
- uty_cli_insert (vio, (char*)&u, 1);
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
else
- uty_cli_describe_command (vio, node);
+ uty_cli_describe_command(cli);
break;
default:
- if ((stroke.value >= 0x20) && (stroke.value < 0x7F))
- uty_cli_insert (vio, (char*)&u, 1) ;
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
break;
}
break ;
/* ESC X -------------------------------------------------------------*/
case ks_esc:
- switch (stroke.value)
+ switch (stroke->value)
{
case 'b':
- uty_cli_word_backwards (vio);
+ uty_cli_word_backwards(cli->cl);
break;
case 'f':
- uty_cli_word_forwards (vio);
+ uty_cli_word_forwards(cli->cl);
break;
case 'd':
- uty_cli_del_word_forwards (vio);
+ uty_cli_del_word_forwards(cli->cl);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_word_backwards (vio);
+ uty_cli_del_word_backwards(cli->cl);
break;
default:
@@ -1542,36 +1743,39 @@ uty_cli_process(vty_io vio, enum node_type node)
} ;
break ;
- /* ESC [ X -----------------------------------------------------------*/
+ /* ESC [ ... ---------------------------------------------------------*/
case ks_csi:
- if (stroke.len != 0)
- break ; /* only recognise 3 byte sequences */
-
- switch (stroke.value)
- {
- case ('A'):
- uty_cli_previous_line (vio);
- break;
-
- case ('B'):
- uty_cli_next_line (vio);
- break;
-
- case ('C'):
- uty_cli_forwards(vio, 1);
- break;
-
- case ('D'):
- uty_cli_backwards(vio, 1);
- break;
- default:
- break ;
- } ;
- break ;
-
- /* Telnet Command ----------------------------------------------------*/
- case ks_iac:
- uty_telnet_command(vio, &stroke, false) ;
+ if (stroke->len == 0)
+ {
+ /* ESC [ X */
+ switch (stroke->value)
+ {
+ case ('A'): /* up arrow */
+ uty_cli_hist_use(cli, hist_previous) ;
+ break;
+
+ case ('B'): /* down arrow */
+ uty_cli_hist_use(cli, hist_next) ;
+ break;
+
+ case ('C'): /* right arrow */
+ uty_cli_forwards(cli->cl, 1) ;
+ break;
+
+ case ('D'): /* left arrow */
+ uty_cli_backwards(cli->cl, 1) ;
+ break;
+
+ default:
+ break ;
+ } ;
+ }
+ else if (stroke->len == 1)
+ {
+ /* ESC [ 3 ~ only ! */
+ if ((stroke->value == '~') && (stroke->buf[0] == '3'))
+ uty_cli_del_forwards(cli->cl, 1) ;
+ } ;
break ;
/* Unknown -----------------------------------------------------------*/
@@ -1579,99 +1783,306 @@ uty_cli_process(vty_io vio, enum node_type node)
zabort("unknown keystroke type") ;
} ;
- /* After each keystroke..... */
-
- if (ret != cli_do_nothing)
- {
- uty_cli_eol (vio) ; /* go to the end of the line */
- break ; /* stop processing */
- } ;
- } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
/* Tidy up and return where got to. */
- qs_term(&vio->cl) ; /* add '\0' */
+ if (to_do != cmd_do_nothing)
+ uty_cli_eol(cli->cl) ; /* go to the end of the line */
- return ret ;
-} ;
+ uty_cli_update_line(cli, qs_cp_nn(cli->cl)) ;
-/*==============================================================================
- * Command line operations
- */
+ return to_do ;
+} ;
/*------------------------------------------------------------------------------
- * Insert 'n' characters at current position in the command line
+ * Update the command line to reflect the difference between old line and the
+ * new line.
*
- * Returns number of characters inserted -- ie 'n'
+ * cli->cls is the old line (currently on screen)
+ * cli->cl is the new line (possible changed in some way)
+ *
+ * Leave the screen cursor at the given required cursor position.
*/
-static int
-uty_cli_insert (vty_io vio, const char* chars, int n)
+static void
+uty_cli_update_line(vty_cli cli, uint rc)
{
- int after ;
+ const char* np ; /* new line */
+ const char* sp ; /* screen line */
- VTY_ASSERT_LOCKED() ;
+ ulen nl ; /* new length */
+ ulen sl ; /* screen length */
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
+ ulen sc ; /* screen cursor */
- if (n <= 0)
- return n ; /* avoid trouble */
+ ulen s, l ;
- after = qs_insert(&vio->cl, chars, n) ;
+ assert(cli->drawn) ;
- uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ;
+ np = qs_char_nn(cli->cl) ;
+ nl = qs_len_nn(cli->cl) ;
- if (after != 0)
- uty_cli_echo_n(vio, telnet_backspaces, after) ;
+ sp = qs_char_nn(cli->cls) ;
+ sl = qs_len_nn(cli->cls) ;
+ sc = qs_cp_nn(cli->cls) ;
- vio->cl.cp += n ;
+ /* Find how many characters are the same. */
+ l = (nl <= sl) ? nl : sl ;
+ s = 0 ;
+ while ((s < l) && (*(np + s) == *(sp + s)))
+ ++s ;
- return n ;
+ /* If the screen and new are different lengths, or the strings are not
+ * the same, then need to draw stuff to correct what is on the screen.
+ * That will leave the screen cursor at the end of the new line, after
+ * any spaces required to wipe out excess characters.
+ *
+ * Note that sc is the current cursor position on the screen, and we keep
+ * that up to date as we draw stuff.
+ */
+ if ((nl != sl) || (s != nl))
+ {
+ /* Move back if the screen cursor is beyond the same section */
+ if (sc > s)
+ {
+ uty_cli_write_n(cli, telnet_backspaces, sc - s) ;
+ sc = s ;
+ } ;
+
+ /* Write from cursor to the end of the new line. */
+ uty_cli_write(cli, np + sc, nl - sc) ;
+ sc = nl ;
+
+ /* If the old line was longer, need to wipe out old stuff */
+ if (sl > nl)
+ {
+ uty_cli_write_n(cli, telnet_spaces, sl - nl) ;
+ sc = sl ;
+ } ;
+ } ;
+
+ /* Now move cursor to the required cursor position */
+ if (sc > rc)
+ uty_cli_write_n(cli, telnet_backspaces, sc - rc) ;
+ else if (sc < rc) /* => lines unchanged, but cursor moved */
+ uty_cli_write(cli, np + sc, rc - sc) ;
} ;
/*------------------------------------------------------------------------------
- * Overstrike 'n' characters at current position in the command line
+ * For password: process keystrokes until run out of input, or get something
+ * to cmd_do.
+ *
+ * Similar to uty_cli_auth, except accepts a limited number of keystrokes.
*
- * Move current position forwards.
+ * Does not accept cursor moves, so does not do forwards delete, and ^D means
+ * exit.
*
- * Returns number of characters inserted -- ie 'n'
+ * Returns: cmd_do_xxxx
+ *
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
-static int
-uty_cli_overwrite (vty_io vio, char* chars, int n)
+static cmd_do_t
+uty_cli_auth(vty_cli cli)
{
- VTY_ASSERT_LOCKED() ;
+ keystroke_t stroke ;
+ uint8_t u ;
+ cmd_do_t to_do ;
+ int olen, nlen ;
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
+ /* For auth command lines, cursor is always at the end */
+ assert(qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) ;
- if (n > 0)
+ olen = qs_len_nn(cli->cl) ;
+
+ /* Now process as much as possible of what there is */
+ do
{
- qs_replace(&vio->cl, chars, n) ;
- uty_cli_echo(vio, chars, n) ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- vio->cl.cp += n ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- return n ;
-}
+ 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('C'):
+ to_do = cmd_do_ctrl_c ; /* Exit on ^C */
+ break ;
+
+ case CONTROL('D'):
+ to_do = cmd_do_ctrl_d ; /* Exit on ^D */
+ break;
+
+ case CONTROL('H'):
+ case 0x7F:
+ uty_cli_del_backwards(cli->cl, 1);
+ break;
+
+ case CONTROL('U'):
+ case CONTROL('W'):
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ case CONTROL('Z'):
+ to_do = cmd_do_ctrl_z ; /* Exit on ^Z */
+ break;
+
+ case '\n':
+ case '\r':
+ to_do = cmd_do_command ; /* Exit on CR or LF */
+ break ;
+
+ default:
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
+ break;
+ }
+ break ;
+
+ /* ESC X -----------------------------------------------------*/
+ case ks_esc:
+ switch (stroke->value)
+ {
+ case CONTROL('H'):
+ case 0x7f:
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ default:
+ break;
+ } ;
+ break ;
+
+ /* ESC [ ... ---------------------------------------------------*/
+ case ks_csi:
+ break ;
+
+ /* Unknown -----------------------------------------------------*/
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
+
+ /* Tidy up and return where got to. */
+
+ nlen = qs_len_nn(cli->cl) ;
+
+ if (nlen < olen)
+ uty_cli_out_wipe_n(cli, nlen - olen) ;
+ else if (nlen > olen)
+ uty_cli_write_n(cli, telnet_stars, nlen - olen) ;
+
+ return to_do ;
+} ;
/*------------------------------------------------------------------------------
- * Insert a word into vty interface with overwrite mode.
+ * Fetch next keystroke, reading from the file if required.
*
- * NB: Assumes result will then be the end of the line.
+ * Returns: cmd_do_keystroke: have a keystroke -- stroke != ks_null
+ * cmd_do_eof : eof in keystream -- stroke == knull_eof
+ * cmd_do_timed_out: timed_ keystream -- stroke == knull_timed_out
+ * cmd_do_nothing : nothing available -- stroke == knull_not_eof
*
- * Returns number of characters inserted -- ie length of string
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
+ *
+ * Note that any I/O error is signalled to the command loop, and is passed
+ * out of here as "nothing available". If the command loop is running, it
+ * will see the CMD_IO_ERROR signal and deal with the error.
*/
-static int
-uty_cli_word_overwrite (vty_io vio, char *str)
+static cmd_do_t
+uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
{
- int n ;
- VTY_ASSERT_LOCKED() ;
+ qassert(cli->vf->vin_state == vf_open) ;
- n = uty_cli_overwrite(vio, str, strlen(str)) ;
+ while (1)
+ {
+ if (keystroke_get(cli->key_stream, stroke))
+ {
+ if (stroke->flags != 0)
+ {
+ /* TODO: deal with broken keystrokes */
+ } ;
- vio->cl.len = vio->cl.cp ;
+ if (stroke->type != ks_iac)
+ return cmd_do_keystroke ; /* have a keystroke */
- return n ;
-}
+ /* Deal with telnet command, so invisible to upper level */
+ uty_telnet_command(cli->vf, stroke, false) ;
+ }
+ else
+ {
+ int get ;
+
+ qassert(stroke->type == ks_null) ;
+
+ switch (stroke->value)
+ {
+ case knull_not_eof:
+ break ;
+
+ case knull_eof:
+ return cmd_do_eof ;
+
+ case knull_timed_out:
+ return cmd_do_timed_out ;
+
+ default:
+ zabort("unknown knull_xxx") ;
+ break ;
+ } ;
+
+ get = uty_term_read(cli->vf) ; /* sets eof in key_stream
+ if hit eof or error */
+ if ((get == 0) || (get == -1))
+ return cmd_do_nothing ;
+ } ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Command line operations.
+ *
+ * These all affect the given command line, only. The effect on the screen
+ * is taken care of elsewhere -- see uty_cli_update_line().
+ */
+
+/*------------------------------------------------------------------------------
+ * Insert 'n' characters at current position in the command line, leaving
+ * cursor after the inserted characters.
+ */
+static void
+uty_cli_insert(qstring cl, const char* chars, int n)
+{
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ;
+
+ if (n > 0)
+ {
+ qs_insert(cl, chars, n) ;
+ qs_move_cp_nn(cl, n) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Replace 'm' characters at the current position, by 'n' characters and leave
+ * cursor at the end of the inserted characters.
+ */
+static void
+uty_cli_replace(qstring cl, int m, const char* chars, int n)
+{
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ;
+
+ qs_replace(cl, m, chars, n) ;
+
+ qs_move_cp_nn(cl, n) ;
+} ;
/*------------------------------------------------------------------------------
* Forward 'n' characters -- stop at end of line.
@@ -1679,25 +2090,20 @@ uty_cli_word_overwrite (vty_io vio, char *str)
* Returns number of characters actually moved
*/
static int
-uty_cli_forwards(vty_io vio, int n)
+uty_cli_forwards(qstring cl, int n)
{
int have ;
- VTY_ASSERT_LOCKED() ;
- have = vio->cl.len - vio->cl.cp ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ;
- assert(n >= 0) ;
+ qassert(n >= 0) ;
- if (n > 0)
- {
- uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ;
- vio->cl.cp += n ;
- } ;
+ qs_move_cp_nn(cl, n) ;
return n ;
-}
+} ;
/*------------------------------------------------------------------------------
* Backwards 'n' characters -- stop at start of line.
@@ -1705,88 +2111,74 @@ uty_cli_forwards(vty_io vio, int n)
* Returns number of characters actually moved
*/
static int
-uty_cli_backwards(vty_io vio, int n)
+uty_cli_backwards(qstring cl, int n)
{
- VTY_ASSERT_LOCKED() ;
+ if ((int)qs_cp_nn(cl) < n)
+ n = qs_cp_nn(cl) ;
- if ((int)vio->cl.cp < n)
- n = vio->cl.cp ;
+ qassert(n >= 0) ;
- assert(n >= 0) ;
-
- if (n > 0)
- {
- uty_cli_echo_n(vio, telnet_backspaces, n) ;
- vio->cl.cp -= n ;
- } ;
+ qs_move_cp_nn(cl, -n) ;
return n ;
-}
+} ;
+
+/*------------------------------------------------------------------------------
+ * Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of
+ * line.
+ */
+static void
+uty_cli_move(qstring cl, int n)
+{
+ if (n < 0)
+ uty_cli_backwards(cl, -n) ;
+ else if (n > 0)
+ uty_cli_forwards(cl, +n) ;
+} ;
/*------------------------------------------------------------------------------
* Delete 'n' characters -- forwards -- stop at end of line.
- *
- * Returns number of characters actually deleted.
*/
-static int
-uty_cli_del_forwards(vty_io vio, int n)
+static void
+uty_cli_del_forwards(qstring cl, int n)
{
- int after ;
int have ;
- VTY_ASSERT_LOCKED() ;
-
- have = vio->cl.len - vio->cl.cp ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ; /* cannot delete more than have */
- assert(n >= 0) ;
-
- if (n <= 0)
- return 0 ;
+ qassert(n >= 0) ;
- after = qs_delete(&vio->cl, n) ;
-
- if (after > 0)
- uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ;
-
- uty_cli_echo_n(vio, telnet_spaces, n) ;
- uty_cli_echo_n(vio, telnet_backspaces, after + n) ;
-
- return n ;
+ if (n > 0)
+ qs_delete(cl, n) ;
}
/*------------------------------------------------------------------------------
- * Delete 'n' characters before the point.
- *
- * Returns number of characters actually deleted.
+ * Delete 'n' characters before the point -- stopping at start of line.
*/
-static int
-uty_cli_del_backwards(vty_io vio, int n)
+static void
+uty_cli_del_backwards(qstring cl, int n)
{
- return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ;
+ uty_cli_del_forwards(cl, uty_cli_backwards(cl, n)) ;
}
/*------------------------------------------------------------------------------
* Move to the beginning of the line.
- *
- * Returns number of characters moved over.
*/
-static int
-uty_cli_bol (vty_io vio)
+static void
+uty_cli_bol(qstring cl)
{
- return uty_cli_backwards(vio, vio->cl.cp) ;
+ qs_set_cp_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
* Move to the end of the line.
- *
- * Returns number of characters moved over
*/
-static int
-uty_cli_eol (vty_io vio)
+static void
+uty_cli_eol(qstring cl)
{
- return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ;
+ qs_set_cp_nn(cl, qs_len_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -1797,18 +2189,16 @@ uty_cli_eol (vty_io vio)
* Steps over non-space characters and then any spaces.
*/
static int
-uty_cli_word_forwards_delta(vty_io vio)
+uty_cli_word_forwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* ep ;
- VTY_ASSERT_LOCKED() ; ;
+ cp = qs_cp_char(cl) ;
+ ep = qs_ep_char(cl) ;
- assert(vio->cl.cp <= vio->cl.len) ;
-
- cp = qs_cp_char(&vio->cl) ;
- ep = qs_ep_char(&vio->cl) ;
+ assert(cp <= ep) ;
tp = cp ;
@@ -1826,10 +2216,10 @@ uty_cli_word_forwards_delta(vty_io vio)
*
* Moves past any non-spaces, then past any spaces.
*/
-static int
-uty_cli_word_forwards(vty_io vio)
+static void
+uty_cli_word_forwards(qstring cl)
{
- return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
+ uty_cli_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -1837,28 +2227,25 @@ uty_cli_word_forwards(vty_io vio)
*
* 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.
+ * Steps back over spaces, and then until next (backwards) character is space,
+ * or hits start of line.
*/
static int
-uty_cli_word_backwards_delta(vty_io vio, int eat_spaces)
+uty_cli_word_backwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* sp ;
- VTY_ASSERT_LOCKED() ; ;
-
- assert(vio->cl.cp <= vio->cl.len) ;
+ assert(qs_cp_nn(cl) <= qs_len_nn(cl)) ;
- cp = qs_cp_char(&vio->cl) ;
- sp = qs_chars(&vio->cl) ;
+ cp = qs_cp_char(cl) ;
+ sp = qs_char(cl) ;
tp = cp ;
- if (eat_spaces)
- while ((tp > sp) && (*(tp - 1) == ' '))
- --tp ;
+ while ((tp > sp) && (*(tp - 1) == ' '))
+ --tp ;
while ((tp > sp) && (*(tp - 1) != ' '))
--tp ;
@@ -1867,143 +2254,170 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces)
} ;
/*------------------------------------------------------------------------------
- * Backward word, but not trailing spaces.
- *
- * Move back until next (backwards) character is space or start of line.
- *
- * Returns number of characters stepped over.
- */
-static int
-uty_cli_word_backwards_pure (vty_io vio)
-{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ;
-} ;
-
-/*------------------------------------------------------------------------------
* Backward word -- move to start of previous word.
*
* Moves past any spaces, then move back until next (backwards) character is
* space or start of line.
- *
- * Returns number of characters stepped over.
*/
-static int
-uty_cli_word_backwards (vty_io vio)
+static void
+uty_cli_word_backwards(qstring cl)
{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
+ uty_cli_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Delete to end of word -- forwards.
*
* Deletes any leading spaces, then deletes upto next space or end of line.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_word_forwards(vty_io vio)
+static void
+uty_cli_del_word_forwards(qstring cl)
{
- return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
+ uty_cli_del_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
}
/*------------------------------------------------------------------------------
* Delete to start of word -- backwards.
*
* Deletes any trailing spaces, then deletes upto next space or start of line.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_word_backwards(vty_io vio)
+static void
+uty_cli_del_word_backwards(qstring cl)
{
- return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
+ uty_cli_del_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill rest of line from current point.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_to_eol (vty_io vio)
+static void
+uty_cli_del_to_eol(qstring cl)
{
- return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ;
+ qs_set_len_nn(cl, qs_cp_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill line from the beginning.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_clear_line(vty_io vio)
+static void
+uty_cli_clear_line(qstring cl)
{
- uty_cli_bol(vio) ;
- return uty_cli_del_to_eol(vio) ;
+ qs_set_cp_nn(cl, 0) ;
+ qs_set_len_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
- * Transpose chars before or at the point.
+ * Transpose current character with previous one, and step forward one.
*
- * Return number of characters affected.
+ * If at end of line, transpose the last two characters.
*/
-static int
-uty_cli_transpose_chars(vty_io vio)
+static void
+uty_cli_transpose_chars(qstring cl)
{
- char chars[2] ;
+ char ch ;
char* cp ;
- VTY_ASSERT_LOCKED() ;
-
/* Give up if < 2 characters or at start of line. */
- if ((vio->cl.len < 2) || (vio->cl.cp < 1))
- return 0 ;
+ if ((qs_len_nn(cl) < 2) || (qs_cp_nn(cl) < 1))
+ return ;
- /* Move back to first of characters to exchange */
- if (vio->cl.cp == vio->cl.len)
- uty_cli_backwards(vio, 2) ;
- else
- uty_cli_backwards(vio, 1) ;
+ /* If we are not at the end, step past the second character */
+ if (qs_after_cp_nn(cl) == 0)
+ qs_move_cp_nn(cl, 1) ;
- /* Pick up in the new order */
- cp = qs_cp_char(&vio->cl) ;
- chars[1] = *cp++ ;
- chars[0] = *cp ;
+ /* Get address of first character */
+ cp = qs_cp_char(cl) - 2 ;
- /* And overwrite */
- return uty_cli_overwrite(vio, chars, 2) ;
+ /* swap characters */
+ ch = *(cp + 1) ;
+ *(cp + 1) = *cp ;
+ *cp = ch ;
} ;
/*==============================================================================
* Command line history handling
+ *
+ * cli->hist is vector of qstrings (created on demand)
+ * cli->h_now is index of the present time
+ * cli->hp is index of most recent line read back
+ *
+ * cli->hist is initialised empty, with h_now == hp == 0.
+ *
+ * On first use it is set to VTY_MAX_HIST entries, and its size never changes.
+ * Before VTY_MAX_HIST lines have been inserted, a NULL entry signals the end
+ * of history (to date).
+ *
+ * h_now is incremented after a line is inserted (and wraps round). So
+ * stepping +1 moves towards the present (down) and -1 moves towards the past
+ * (up).
+ *
+ * hp == h_now means we are in the present.
+ *
+ * Cannot step forwards from hp == h_now (into the future, which is the
+ * same as the oldest thing we can remember !).
+ *
+ * Before stepping backwards from hp == hp_now, sets hp_now to be a copy of
+ * the current line (complete with cp), so can return to the present.
+ *
+ * Cannot step backwards to hp == hp_now -- that would be to wrap round from
+ * the ancient past to the now time.
+ *
+ * When storing a line in the history, replaces the last line stored if that
+ * is the same as the new line, apart from whitespace.
+ */
+
+static inline uint
+hp_next(uint hp)
+{
+ return (hp != (VTY_HIST_COUNT - 1)) ? hp + 1 : 0 ;
+} ;
+
+static inline uint
+hp_prev(uint hp)
+{
+ return (hp != 0) ? hp - 1 : VTY_HIST_COUNT - 1 ;
+} ;
+
+static inline uint
+hp_step(uint hp, enum hist_step step)
+{
+ if (step == hist_previous)
+ return hp_prev(hp) ;
+
+ if (step == hist_next)
+ return hp_next(hp) ;
+
+ zabort("invalid hist_step") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create cli->hist.
*/
+static void
+uty_cli_hist_make(vty_cli cli)
+{
+ cli->hist = vector_init_new(cli->hist, VTY_HIST_COUNT) ;
+ vector_set_min_length(cli->hist, VTY_HIST_COUNT) ;
+} ;
/*------------------------------------------------------------------------------
* Add given command line to the history buffer.
*
- * This is inserting the vty->buf line into the history.
+ * The 'cp' stored with the line is set to be the end of the line, so that is
+ * all ready for when the stored line is used.
+ *
+ * Resets hp == h_now.
*/
-extern void
-uty_cli_hist_add (vty_io vio, const char* cmd_line)
+static void
+uty_cli_hist_add (vty_cli cli, qstring clx)
{
- qstring prev_line ;
- qstring_t line ;
- int prev_index ;
+ qstring hist_line ;
+ int h_prev ;
VTY_ASSERT_LOCKED() ;
- /* Construct a dummy qstring for the given command line */
- qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */
-
- /* make sure have a suitable history vector */
- vector_set_min_length(&vio->hist, VTY_MAXHIST) ;
-
- /* find the previous command line in the history */
- prev_index = vio->hindex - 1 ;
- if (prev_index < 0)
- prev_index = VTY_MAXHIST - 1 ;
-
- prev_line = vector_get_item(&vio->hist, prev_index) ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
/* If the previous line is NULL, that means the history is empty.
*
@@ -2011,23 +2425,35 @@ uty_cli_hist_add (vty_io vio, const char* cmd_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 !
+ * In both those cases, replace the the previous line entry by moving
+ * h_now back to it -- leaving hist_line pointing at it.
+ *
+ * Otherwise, leave cli->h_now and point hist_line at the most ancient
+ * line in history.
*/
- 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) ;
+ h_prev = hp_prev(cli->h_now) ;
- /* Now replace the hindex entry */
- vector_set_item(&vio->hist, vio->hindex, qs_copy(prev_line, &line)) ;
+ hist_line = vector_get_item(cli->hist, h_prev) ;
+
+ if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0))
+ {
+ cli->h_now = h_prev ;
+ cli->h_repeat = true ; /* latest history is a repeat */
+ }
+ else
+ {
+ hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ cli->h_repeat = false ; /* latest history is novel */
+ } ;
- /* Advance to the near future and reset the history pointer */
- vio->hindex++;
- if (vio->hindex == VTY_MAXHIST)
- vio->hindex = 0;
+ /* Now replace the h_now entry -- setting 'cp' to end of line
+ */
+ hist_line = qs_copy(hist_line, clx) ;
+ qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ;
+ vector_set_item(cli->hist, cli->h_now, hist_line) ;
- vio->hp = vio->hindex;
+ /* Advance history */
+ cli->hp = cli->h_now = hp_next(cli->h_now) ;
} ;
/*------------------------------------------------------------------------------
@@ -2035,236 +2461,344 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line)
*
* This function is called from vty_next_line and vty_previous_line.
*
- * Step +1 is towards the present
- * -1 is into the past
+ * Step -1 is into the past (up)
+ * +1 is towards the present (down)
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
-uty_cli_history_use(vty_io vio, int step)
+uty_cli_hist_use(vty_cli cli, enum hist_step step)
{
- int index ;
- unsigned old_len ;
- unsigned after ;
- unsigned back ;
- qstring hist ;
-
- VTY_ASSERT_LOCKED() ;
+ uint hp ;
+ qstring hist_line ;
assert((step == +1) || (step == -1)) ;
- index = vio->hp ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
- /* Special case of being at the insertion point */
- if (index == vio->hindex)
+ hp = cli->hp ;
+
+ /* Special case of being at the insertion point (the present)
+ *
+ * Cannot step forwards from the present.
+ *
+ * Before stepping back from the present, take a copy of the current
+ * command line -- so can get back to it.
+ *
+ * Note that the 'cp' is stored with the line. So if return to the present,
+ * the cursor returns to its current position. (When lines are added to
+ * the history, the cursor is set to the end of the line.)
+ */
+ if (hp == cli->h_now)
{
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)) ;
+ hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ vector_set_item(cli->hist, cli->h_now, qs_copy(hist_line, cli->cl)) ;
} ;
- /* Advance or retreat */
- index += step ;
- if (index < 0)
- index = VTY_MAXHIST - 1 ;
- else if (index >= VTY_MAXHIST)
- index = 0 ;
+ /* Advance or retreat and get history line. */
+ hp = hp_step(hp, step) ;
- hist = vector_get_item(&vio->hist, index) ;
+ hist_line = vector_get_item(cli->hist, hp) ;
- /* If moving backwards in time, may not move back to the insertion
+ /* If moving backwards in time, may not move back to the h_now
* 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))
+ if ((hist_line == NULL) || (hp == cli->h_now))
return ;
- /* Now, if arrived at the insertion point, this is returning to the
- * present, which is fine.
- */
- vio->hp = index;
+ /* Update the history pointer and copy history line to current line. */
+ cli->hp = hp ;
+ qs_copy(cli->cl, hist_line) ;
+} ;
- /* Move back to the start of the current line */
- uty_cli_bol(vio) ;
+/*------------------------------------------------------------------------------
+ * Show the contents of the history
+ */
+extern void
+uty_cli_hist_show(vty_cli cli)
+{
+ uint hp ;
+ uint h_end ;
- /* Get previous line from history buffer and echo that */
- old_len = vio->cl.len ;
- qs_copy(&vio->cl, hist) ;
+ VTY_ASSERT_LOCKED() ;
- /* 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 ;
+ if (cli->hist == NULL)
+ return ; /* if no history */
- back = after ;
- if (vio->cl.len > vio->cl.cp)
- back += (vio->cl.len - vio->cl.cp) ;
+ /* We start with the oldest thing we can remember, which means that
+ * we start by stepping "forwards" from "now".
+ *
+ * Until the history buffer fills, there will be a number of NULL entries
+ * between "now" and the oldest thing in the history.
+ *
+ * We do not show the "now" entry, which is not part of history.
+ *
+ * We do not show the entry before "now", because that is the current
+ * executing command, unless that was a repeat of the command before !
+ */
+ hp = cli->h_now ;
- if (vio->cl.len > 0)
- uty_cli_echo(vio, vio->cl.body, vio->cl.len) ;
+ h_end = cli->h_repeat ? hp : hp_prev(hp) ;
- if (after > 0)
- uty_cli_echo_n(vio, telnet_spaces, after) ;
+ while (1)
+ {
+ qstring line ;
- if (back > 0)
- uty_cli_echo_n(vio, telnet_backspaces, back) ;
+ hp = hp_next(hp) ;
- return ;
-} ;
+ if (hp == h_end)
+ break ; /* reached end of history */
-/*------------------------------------------------------------------------------
- * Use next history line, if any.
- */
-static void
-uty_cli_next_line(vty_io vio)
-{
- uty_cli_history_use(vio, +1) ;
-}
+ line = vector_get_item(cli->hist, hp) ;
-/*------------------------------------------------------------------------------
- * Use previous history line, if any.
- */
-static void
-uty_cli_previous_line (vty_io vio)
-{
- uty_cli_history_use(vio, -1) ;
-}
+ if (line != NULL)
+ uty_out(cli->vf->vio, " %s\n", qs_make_string(line));
+ } ;
+} ;
/*==============================================================================
* Command Completion and Command Description
*
+ * Any changes to the command line are made to cli->cl. If the command line
+ * is redrawn, updates cli->cls. Otherwise, the screen may need updating to
+ * reflect differences between cli->cl and cli->cls.
*/
-static void uty_cli_describe_show(vty_io vio, vector describe) ;
-static void uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc) ;
-static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str) ;
+static uint uty_cli_help_parse(vty_cli cli) ;
+
+static void uty_cli_complete_keyword(vty_cli cli, elstring keyword) ;
+static void uty_cli_complete_list(vty_cli cli, vector item_v) ;
-static vector uty_cli_cmd_prepare(vty_io vio, int help) ;
+static void uty_cli_describe_list(vty_cli cli, vector item_v) ;
+static void uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
+ ulen str_len, const char* doc, ulen doc_len) ;
+static uint uty_cli_width_to_use(vty_cli cli) ;
+
+static void uty_cli_help_message(vty_cli cli, const char* msg) ;
+static void uty_cli_help_newline(vty_cli cli) ;
+static void uty_cli_help_finish(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Command completion
+ *
+ * Requires that cmd_token_position() has been called to tokenise the line and
+ * establish which token the cursor is in. Must NOT call this if the cursor
+ * is in a "special" place.
+ *
+ * This is called from inside "uty_cli_process()".
*/
static void
-uty_cli_complete_command (vty_io vio, enum node_type node)
+uty_cli_complete_command (vty_cli cli)
{
- unsigned i ;
- int ret ;
- int len ;
- int n ;
- vector matched ;
- vector vline ;
+ uint n_items ;
+ cmd_parsed parsed ;
+ cmd_item item ;
VTY_ASSERT_LOCKED() ;
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- matched = cmd_complete_command (vline, node, &ret);
- cmd_free_strvec (vline);
+ parsed = cli->parsed ;
- /* Show the result. */
- switch (ret)
+ /* Establish what items may be present at the current token position. */
+ n_items = uty_cli_help_parse(cli) ;
+
+ if (n_items > 1) /* render list of alternatives */
+ uty_cli_complete_list(cli, parsed->item_v) ;
+ else if (n_items == 1)
{
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
+ /* One possible item -- one or more possible commands */
+ item = vector_get_item(parsed->item_v, 0) ;
- case CMD_ERR_NO_MATCH:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
+ switch (item->type)
+ {
+ case item_null:
+ zabort("invalid item_null") ;
- case CMD_COMPLETE_FULL_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- uty_cli_insert(vio, " ", 1);
- break ;
+ case item_eol:
- case CMD_COMPLETE_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- break ;
+ case item_option_word:
- case CMD_COMPLETE_LIST_MATCH:
- len = 6 ;
- for (i = 0; i < vector_end(matched); i++)
- {
- int sl = strlen((char*)vector_get_item(matched, i)) ;
- if (len < sl)
- len = sl ;
- } ;
+ case item_vararg:
- n = vio->width ;
- if (n == 0)
- n = 60 ;
- n = n / (len + 2) ;
- if (n == 0)
- n = 1 ;
+ case item_word:
- for (i = 0; i < vector_end(matched); i++)
- {
- if ((i % n) == 0)
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i));
- }
- uty_cli_out_newline(vio) ;
+ case item_ipv6_prefix:
+ case item_ipv6_address:
+ case item_ipv4_prefix:
+ case item_ipv4_address:
- break;
+ case item_range:
+ uty_cli_describe_list(cli, parsed->item_v) ;
+ break ;
- case CMD_COMPLETE_ALREADY:
- default:
- break;
+ case item_keyword:
+ uty_cli_complete_keyword(cli, item->str) ;
+ break ;
+
+ default:
+ zabort("unknown item type") ;
+ } ;
} ;
- cmd_free_strvec(matched);
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
} ;
/*------------------------------------------------------------------------------
* Command Description
*/
static void
-uty_cli_describe_command (vty_io vio, enum node_type node)
+uty_cli_describe_command (vty_cli cli)
{
- int ret;
- vector vline;
- vector describe;
+ uint n_items ;
VTY_ASSERT_LOCKED() ;
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- describe = cmd_describe_command (vline, node, &ret);
- cmd_free_strvec (vline);
+ /* Establish what items may be present at the current token position. */
+ n_items = uty_cli_help_parse(cli) ;
+
+ if (n_items > 0) /* render list of possibilities */
+ uty_cli_describe_list(cli, cli->parsed->item_v) ;
+
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
+} ;
- uty_cli_out_newline(vio); /* clears cli_drawn */
+/*------------------------------------------------------------------------------
+ * Parse for command completion and command description.
+ *
+ * Requires that cmd_token_position() has been called to tokenise the line and
+ * establish which token the cursor is in. Must NOT call this if the cursor
+ * is in a "special" place.
+ *
+ * Deal with all cases which yield no items at all.
+ *
+ * Returns: number of items to consider.
+ */
+static uint
+uty_cli_help_parse(vty_cli cli)
+{
+ const char* msg ;
+ cmd_return_code_t ret ;
+ uint n_items ;
- /* Deal with result. */
- switch (ret)
+ /* The preflight checks avoid getting into trouble doing command completion
+ * on a line with comment
+ */
+ msg = cmd_help_preflight(cli->parsed) ;
+ if (msg != NULL)
{
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
+ uty_cli_help_message(cli, msg) ;
+ return 0 ;
+ } ;
- case CMD_ERR_NO_MATCH:
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
+ /* Now see what the cmd_completion can come up with. */
+ ret = cmd_completion(cli->parsed, cli->context) ;
- default:
- uty_cli_describe_show(vio, describe) ;
- break ;
+ if (ret == CMD_ERR_PARSING)
+ {
+ if (cli->parsed->eloc >= 0)
+ {
+ uint eloc = cli->prompt_len + cli->parsed->eloc ;
+
+ uty_cli_help_newline(cli) ; /* clears cli_drawn etc. */
+ uty_cli_write_n(cli, telnet_dots, eloc) ;
+ uty_cli_write_s(cli, "^") ;
+ } ;
+
+ uty_cli_help_message(cli, qs_make_string(cli->parsed->emess)) ;
+
+ return 0 ;
} ;
- if (describe != NULL)
- vector_free (describe);
-}
+ /* Will now have 0, 1 or more items which match at the current
+ * cursor token.
+ */
+ n_items = vector_length(cli->parsed->item_v) ;
+
+ if (n_items == 0)
+ uty_cli_help_message(cli, "command not recognised") ;
+
+ return n_items ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Can complete a keyword.
+ */
+static void
+uty_cli_complete_keyword(vty_cli cli, elstring keyword)
+{
+ int pre, rep, ins, mov ;
+
+ cmd_complete_keyword(cli->parsed, &pre, &rep, &ins, &mov) ;
+
+ uty_cli_move(cli->cl, pre) ; /* move to start of token */
+ uty_cli_replace(cli->cl, rep, els_body_nn(keyword), els_len_nn(keyword)) ;
+
+ assert(ins <= 2) ;
+ if (ins > 0)
+ uty_cli_insert(cli->cl, " ", ins) ;
+
+ uty_cli_move(cli->cl, mov) ;
+
+ return ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show the command completions -- usually more than one.
+ *
+ * Generates a table of possible items, with fixed width entries, depending
+ * on the longest option.
+ *
+ * NB: the items will have been sorted before we get here. Inter alia, that
+ * ensures that any <cr> is shown last.
+ */
+static void
+uty_cli_complete_list(vty_cli cli, vector item_v)
+{
+ uint i, str_width, n ;
+
+ str_width = 6 ;
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+
+ item = vector_get_item(item_v, i) ;
+
+ if (str_width < els_len_nn(item->str))
+ str_width = els_len_nn(item->str) ;
+ } ;
+
+ n = uty_cli_width_to_use(cli) / (str_width + 2) ;
+
+ if (n == 0)
+ n = 1 ;
+
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ ulen str_len ;
+ ulen pad ;
+
+ item = vector_get_item(item_v, i) ;
+
+ if ((i % n) == 0)
+ uty_cli_out_newline(cli) ; /* clears cli_drawn */
+
+ str_len = els_len_nn(item->str) ;
+ uty_cli_write(cli, els_body_nn(item->str), str_len) ;
+
+ pad = (str_len < str_width) ? str_width - str_len : 0 ;
+
+ uty_cli_write_n(cli, telnet_spaces, pad + 2) ;
+ } ;
+
+ uty_cli_help_newline(cli) ;
+} ;
/*------------------------------------------------------------------------------
* Show the command description.
@@ -2280,426 +2814,185 @@ uty_cli_describe_command (vty_io vio, enum node_type node)
* word description ..................................
* .............text
*
- * If one of the options is '<cr>', that is always shown last.
+ * NB: the items will have been sorted before we get here. Inter alia, that
+ * ensures that any <cr> is shown last.
*/
static void
-uty_cli_describe_show(vty_io vio, vector describe)
+uty_cli_describe_list(vty_cli cli, vector item_v)
{
- unsigned int i, cmd_width, desc_width;
- struct desc *desc, *desc_cr ;
+ uint i, str_width, doc_width, width ;
/* Get width of the longest "word" */
- cmd_width = 0;
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- unsigned int len;
-
- if (desc->cmd[0] == '\0')
- continue;
-
- len = strlen (desc->cmd);
- if (desc->cmd[0] == '.')
- len--;
+ str_width = 0;
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ uint len ;
- if (cmd_width < len)
- cmd_width = len;
- }
+ item = vector_get_item(item_v, i) ;
- /* Set width of description string. */
- desc_width = vio->width - (cmd_width + 6);
+ len = els_len_nn(item->str) ;
+ if (*((char*)els_body_nn(item->str)) == '.')
+ len--;
- /* Print out description. */
- desc_cr = NULL ; /* put <cr> last if it appears */
+ if (len > str_width)
+ str_width = len ;
+ } ;
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- if (desc->cmd[0] == '\0')
- continue;
+ /* Set width of description string.
+ *
+ * Format is:
+ *
+ * __wo.....rd__description...
+ * ...continues
+ *
+ * The width of the word part has been established above as the width of the
+ * widest word.
+ *
+ * There are two spaces on either side of the word, so we here calculate the
+ * width of the description part
+ */
+ width = uty_cli_width_to_use(cli) ;
- if (strcmp (desc->cmd, command_cr) == 0)
- {
- desc_cr = desc;
- continue;
- }
+ if (width > ((str_width + 6) + 20))
+ doc_width = width - (str_width + 6) ;
+ else
+ doc_width = 0 ;
- uty_cli_describe_fold (vio, cmd_width, desc_width, desc);
- }
+ /* Print out description. */
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ const char* str, * dp, * ep ;
+ ulen str_len ;
- if (desc_cr != NULL)
- uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr);
-} ;
+ item = vector_get_item(item_v, i) ;
-/*------------------------------------------------------------------------------
- * Show one word and the description, folding the description as required.
- */
-static void
-uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc)
-{
- char *buf;
- const char *cmd, *p;
- int pos;
+ str = els_body_nn(item->str) ;
+ str_len = els_len_nn(item->str) ;
+ if (*str == '.')
+ {
+ ++str ;
+ --str_len ;
+ } ;
- VTY_ASSERT_LOCKED() ;
+ dp = item->doc ;
+ ep = dp + strlen(dp) ;
- cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
- p = desc->str ;
+ /* If have a sensible description width */
+ if (doc_width > 20)
+ {
+ while ((ep - dp) > doc_width)
+ {
+ const char* np ;
- /* If have a sensible description width */
- if (desc_width > 20)
- {
- buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1);
+ np = dp + doc_width ; /* target next position */
- while (strlen (p) > desc_width)
- {
- /* move back to first space */
- for (pos = desc_width; pos > 0; pos--)
- if (*(p + pos) == ' ')
- break;
+ while ((np > dp) && (*np != ' '))
+ --np ; /* seek back to ' ' */
- /* if did not find a space, break at width */
- if (pos == 0)
- pos = desc_width ;
+ if (np == dp) /* if no space... */
+ np = dp + doc_width ; /* ...force break */
- strncpy (buf, p, pos);
- buf[pos] = '\0';
- uty_cli_describe_line(vio, cmd_width, cmd, buf) ;
+ uty_cli_describe_line(cli, str_width, str, str_len, dp, np - dp) ;
- cmd = ""; /* for 2nd and subsequent lines */
+ str_len = 0 ; /* for 2nd and subsequent lines */
- p += pos ; /* step past what just wrote */
- while (*p == ' ')
- ++p ; /* skip spaces */
+ dp = np ; /* step past what just wrote */
+ while (*dp == ' ')
+ ++dp ; /* skip spaces */
+ } ;
} ;
- XFREE (MTYPE_TMP, buf);
+ uty_cli_describe_line(cli, str_width, str, str_len, dp, ep - dp) ;
} ;
- uty_cli_describe_line(vio, cmd_width, cmd, p) ;
+ uty_cli_help_newline(cli) ;
} ;
/*------------------------------------------------------------------------------
* Show one description line.
*/
static void
-uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str)
+uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
+ ulen str_len, const char* doc, ulen doc_len)
{
- if (str != NULL)
- uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ;
- else
- uty_cli_out (vio, " %-s", cmd) ;
- uty_cli_out_newline(vio) ;
-} ;
+ if ((str_len == 0) && (doc_len == 0))
+ return ; /* quit if nothing to say */
-/*------------------------------------------------------------------------------
- * Prepare "vline" token array for command handler.
- *
- * For "help" (command completion/description), if the command line is empty,
- * or ends in ' ', adds an empty token to the end of the token array.
- */
-static vector
-uty_cli_cmd_prepare(vty_io vio, int help)
-{
- vector vline ;
-
- vline = cmd_make_strvec(qs_term(&vio->cl)) ;
-
- /* Note that if there is a vector of tokens, then there is at least one
- * token, so can guarantee that vio->cl.len >= 1 !
- */
- if (help)
- if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1)))
- vline = cmd_add_to_strvec(vline, "") ;
-
- return vline ;
-} ;
+ uty_cli_help_newline(cli) ;
-/*==============================================================================
- * VTY telnet stuff
- */
-
-#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */
-
-static const char* telnet_commands[256] =
-{
- [tn_IAC ] = "IAC",
- [tn_DONT ] = "DONT",
- [tn_DO ] = "DO",
- [tn_WONT ] = "WONT",
- [tn_WILL ] = "WILL",
- [tn_SB ] = "SB",
- [tn_GA ] = "GA",
- [tn_EL ] = "EL",
- [tn_EC ] = "EC",
- [tn_AYT ] = "AYT",
- [tn_AO ] = "AO",
- [tn_IP ] = "IP",
- [tn_BREAK] = "BREAK",
- [tn_DM ] = "DM",
- [tn_NOP ] = "NOP",
- [tn_SE ] = "SE",
- [tn_EOR ] = "EOR",
- [tn_ABORT] = "ABORT",
- [tn_SUSP ] = "SUSP",
- [tn_EOF ] = "EOF",
-} ;
-
-static const char* telnet_options[256] =
-{
- [to_BINARY] = "BINARY", /* 8-bit data path */
- [to_ECHO] = "ECHO", /* echo */
- [to_RCP] = "RCP", /* prepare to reconnect */
- [to_SGA] = "SGA", /* suppress go ahead */
- [to_NAMS] = "NAMS", /* approximate message size */
- [to_STATUS] = "STATUS", /* give status */
- [to_TM] = "TM", /* timing mark */
- [to_RCTE] = "RCTE", /* remote controlled tx and echo */
- [to_NAOL] = "NAOL", /* neg. about output line width */
- [to_NAOP] = "NAOP", /* neg. about output page size */
- [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */
- [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */
- [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */
- [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */
- [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */
- [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */
- [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */
- [to_XASCII] = "XASCII", /* extended ascii character set */
- [to_LOGOUT] = "LOGOUT", /* force logout */
- [to_BM] = "BM", /* byte macro */
- [to_DET] = "DET", /* data entry terminal */
- [to_SUPDUP] = "SUPDUP", /* supdup protocol */
- [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */
- [to_SNDLOC] = "SNDLOC", /* send location */
- [to_TTYPE] = "TTYPE", /* terminal type */
- [to_EOR] = "EOR", /* end or record */
- [to_TUID] = "TUID", /* TACACS user identification */
- [to_OUTMRK] = "OUTMRK", /* output marking */
- [to_TTYLOC] = "TTYLOC", /* terminal location number */
- [to_3270REGIME] = "3270REGIME", /* 3270 regime */
- [to_X3PAD] = "X3PAD", /* X.3 PAD */
- [to_NAWS] = "NAWS", /* window size */
- [to_TSPEED] = "TSPEED", /* terminal speed */
- [to_LFLOW] = "LFLOW", /* remote flow control */
- [to_LINEMODE] = "LINEMODE", /* Linemode option */
- [to_XDISPLOC] = "XDISPLOC", /* X Display Location */
- [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */
- [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */
- [to_ENCRYPT] = "ENCRYPT", /* Encryption option */
- [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */
- [to_EXOPL] = "EXOPL", /* extended-options-list */
-} ;
-
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as decimal.
- */
-static void
-uty_cli_out_dec(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
+ if (str_len > 0)
+ {
+ uty_cli_write(cli, " ", 2) ;
+ uty_cli_write(cli, str, str_len) ;
+ }
else
- uty_cli_out(vio, "%d ", (int)u) ;
-} ;
+ str_width += 2 ;
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as hex.
- */
-static void
-uty_cli_out_hex(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
- else
- uty_cli_out(vio, "0x%02x ", (unsigned)u) ;
+ if (doc_len > 0)
+ {
+ ulen pad ;
+ pad = (str_len < str_width) ? str_width - str_len : 0 ;
+ uty_cli_write_n(cli, telnet_spaces, pad + 2) ;
+ uty_cli_write(cli, doc, doc_len) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Send telnet: "WILL TELOPT_ECHO"
- */
-static void
-uty_will_echo (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "suppress Go-Ahead"
+ * Return the actual or assumed console width.
+ *
+ * If we know the width we use it. Otherwise just assume something reasonable.
*/
-static void
-uty_will_suppress_go_ahead (vty_io vio)
+static uint
+uty_cli_width_to_use(vty_cli cli)
{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ return (cli->width == 0) ? 60 : cli->width ;
+} ;
/*------------------------------------------------------------------------------
- * Send telnet: "don't use linemode"
+ * Move to new line, issue message and leave on new line.
+ *
+ * Deals with udating the command line if we are currently on it.
*/
static void
-uty_dont_linemode (vty_io vio)
+uty_cli_help_message(vty_cli cli, const char* msg)
{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ uty_cli_help_newline(cli) ; /* clears cli->drawn etc. */
+ uty_cli_write_s(cli, "% ") ;
+ uty_cli_write_s(cli, msg) ;
+ uty_cli_write(cli, telnet_newline, 2) ;
+} ;
/*------------------------------------------------------------------------------
- * Send telnet: "Use window size"
+ * If the command line is drawn, make sure it is up to date, leaving cursor
+ * at the end of the line, and then issue newline.
+ *
+ * Clears cli->drawn.
*/
static void
-uty_do_window_size (vty_io vio)
+uty_cli_help_newline(vty_cli cli)
{
- unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ if (cli->drawn)
+ uty_cli_update_line(cli, qs_len_nn(cli->cl)) ;
-/*------------------------------------------------------------------------------
- * Send telnet: "don't use lflow" -- not currently used
- */
-static void
-uty_dont_lflow_ahead (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ uty_cli_write(cli, telnet_newline, 2) ;
-/*------------------------------------------------------------------------------
- * 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) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
- * 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:
+ * If the command line help has "undrawn" the command line, then redraw it now
+ * and make a new copy to cli->cls.
*
- * * telnet window size.
+ * Sets cli->drawn
*/
-static bool
-uty_telnet_command(vty_io vio, keystroke stroke, bool callback)
+static void
+uty_cli_help_finish(vty_cli cli)
{
- uint8_t* p ;
- uint8_t o ;
- int left ;
- bool dealt_with ;
-
- /* Echo to the other end if required */
- if (TELNET_OPTION_DEBUG)
+ if (!cli->drawn)
{
- uty_cli_wipe(vio, 0) ;
-
- p = stroke->buf ;
- left = stroke->len ;
-
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_commands[*p], *p) ;
- ++p ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_options[*p], *p) ;
- ++p ;
-
- if (left > 0)
- {
- while(left-- > 0)
- uty_cli_out_hex(vio, NULL, *p++) ;
-
- if (stroke->flags & kf_truncated)
- uty_cli_out(vio, "... ") ;
-
- if (!(stroke->flags & kf_broken))
- {
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
- uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ;
- }
- } ;
-
- if (stroke->flags & kf_broken)
- uty_cli_out (vio, "BROKEN") ;
-
- uty_cli_out (vio, "\r\n") ;
+ uty_cli_draw(cli) ;
+ qs_copy(cli->cls, cli->cl) ;
} ;
-
- /* Process the telnet command */
- dealt_with = false ;
-
- if (stroke->flags != 0)
- return dealt_with ; /* go no further if broken */
-
- p = stroke->buf ;
- left = stroke->len ;
-
- passert(left >= 1) ; /* must be if not broken ! */
- passert(stroke->value == *p) ; /* or something is wrong */
-
- ++p ; /* step past X of IAC X */
- --left ;
-
- /* Decode the one command that is interesting -- "NAWS" */
- switch (stroke->value)
- {
- case tn_SB:
- passert(left > 0) ; /* or parser failed */
-
- o = *p++ ; /* the option byte */
- --left ;
- switch(o)
- {
- case to_NAWS:
- if (left != 4)
- {
- uzlog(NULL, LOG_WARNING,
- "RFC 1073 violation detected: telnet NAWS option "
- "should send %d characters, but we received %d",
- (3 + 4 + 2), (3 + left + 2)) ;
- }
- else
- {
- vio->width = *p++ << 8 ;
- vio->width += *p++ ;
- vio->height = *p++ << 8 ;
- vio->height += *p ;
-
- if (TELNET_OPTION_DEBUG)
- uty_cli_out(vio, "TELNET NAWS window size received: "
- "width %d, height %d%s",
- vio->width, vio->height, telnet_newline) ;
- uty_set_height(vio) ;
-
- dealt_with = true ;
- } ;
- break ;
-
- default: /* no other IAC SB <option> */
- break ;
- } ;
- break ;
-
- default: /* no other IAC X */
- break ;
- } ;
-
- return dealt_with ;
} ;
diff --git a/lib/vty_cli.h b/lib/vty_cli.h
index f532616a..ec00965a 100644
--- a/lib/vty_cli.h
+++ b/lib/vty_cli.h
@@ -24,26 +24,202 @@
#ifndef _ZEBRA_VTY_CLI_H
#define _ZEBRA_VTY_CLI_H
+#include "misc.h"
+#include "vargs.h"
+
+#include "command_local.h"
#include "vty_io.h"
+#include "vty_io_basic.h"
+#include "vio_fifo.h"
+#include "vio_lines.h"
+#include "qstring.h"
+#include "qtimers.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) ;
+/*------------------------------------------------------------------------------
+ * The vty_cli structure pointed to by the vty_io structure.
+ */
+typedef struct vty_cli* vty_cli ;
+
+struct vty_cli
+{
+ vio_vf vf ; /* parent */
+
+ /* History of commands */
+ vector hist ; /* embedded */
+ uint hp ; /* current place in history */
+ uint h_now ; /* the present moment */
+ bool h_repeat ; /* latest entry is repeat */
+
+ /* Window width/height as reported by Telnet. 0 => unknown */
+ int width;
+ int height;
+
+ /* Configure lines. */
+ int lines;
+ bool lines_set ; /* true <=> explicitly set */
+
+ /* Terminal monitor. */
+ bool monitor ;
+ bool monitor_busy ;
+
+ /* The incoming stuff */
+ keystroke_stream key_stream ;
+
+ /* drawn <=> the current prompt and user input occupy the current
+ * line on the screen.
+ *
+ * This flag <=> the CLI "owns" the screen. This flag
+ * must be cleared -- by wiping the command line -- before
+ * any other output can use the screen.
+ *
+ * In particular, must be cleared before setting
+ * out_active -- see below.
+ *
+ * tilde_enabled <=> do the "~ " one command line ahead.
+ *
+ * If drawn is true, the following are valid:
+ *
+ * tilde_prompt -- the prompt is the "~ "
+ *
+ * prompt_len -- the length of the prompt part.
+ * (will be the "--more--" prompt in cli_more_wait)
+ *
+ * extra_len -- the length of any ^X at the cursor position
+ * (for when blocked waiting for queued command)
+ *
+ * echo_suppress -- the user part of the command line is suppressed
+ *
+ * NB: echo_suppress is only used for password entry.
+ */
+ bool drawn ;
+
+ bool tilde_prompt ;
+#if 0
+ bool tilde_enabled ;
+#endif
+
+ int prompt_len ;
+ int extra_len ;
+
+ /* "cache" for prompt -- when node or host name changes, prompt does */
+ node_type_t prompt_node ;
+ name_gen_t prompt_gen ;
+ qstring prompt_for_node ;
+
+ /* State of the CLI
+ *
+ * dispatched -- command dispatched by CLI
+ * in_progress -- command taken by the command loop
+ * blocked -- blocked until current command completes
+ * paused -- command dispatched and nothing else happened
+ *
+ * mon_active -- there is stuff in the logging monitor buffer
+ *
+ * out_active -- contents of the obuf FIFO are being written away
+ * though may be blocked in more_wait
+ *
+ * This flag <=> that the command output "owns" the screen.
+ *
+ * While this flag is set, the CLI may not write to the
+ * screen.
+ *
+ * Flag is cleared when obuf is empty, and is !in_progress.
+ *
+ * more_wait -- is in "--more--" wait state
+ * more_enter -- more_wait and waiting for "--more--" prompt to be
+ * written away and keystrokes to be consumed.
+ */
+ bool dispatched ;
+ bool in_progress ;
+ bool blocked ;
+ bool paused ;
+
+ bool mon_active ;
+ bool out_active ;
-extern enum vty_readiness uty_cli(vty_io vio) ;
-extern keystroke_callback uty_cli_iac_callback ;
+ bool more_wait ;
+ bool more_enter ;
-extern void uty_cli_hist_add (vty_io vio, const char* cmd_line) ;
-extern void uty_cli_enter_more_wait(vty_io vio) ;
-extern void uty_cli_exit_more_wait(vty_io vio) ;
+ /* This is set only if the "--more--" handling is enabled */
+ bool more_enabled ;
-extern void uty_free_host_name(void) ;
-extern void uty_check_host_name(void) ;
+ /* Timer for paused state -- multi-threaded only */
+ qtimer pause_timer ;
-extern bool uty_cli_draw_if_required(vty_io vio) ;
+ /* Command Line(s)
+ *
+ * context -- the node etc. that the CLI is in. This may be some way behind
+ * the VTY, but is updated when the CLI level command completes.
+ *
+ * Note that this is a copy of the state of exec->context when
+ * uty_want_command() was last called.
+ *
+ * auth_context -- true <=> context->node is AUTH_NODE or AUTH_ENABLE_NODE
+ *
+ * parsed -- the parsed object used to parse command for cli help
+ *
+ * to_do -- when current command being prepared is completed (by
+ * CR/LF or otherwise) this says what there now is to be done.
+ *
+ * cl -- current command line being prepared.
+ * cls -- current command line on the screen
+ *
+ * clx -- current command line being executed.
+ *
+ * dispatch -- the last action dispatched.
+ *
+ * NB: during command execution vty->buf is set to point at the '\0'
+ * terminated current command line being executed.
+ */
+ cmd_context context ;
+ bool auth_context ;
-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) ;
+ cmd_parsed parsed ;
+
+ cmd_do_t to_do ;
+ qstring cl ;
+ qstring cls ;
+
+ qstring clx ;
+
+ cmd_action_t dispatch ;
+
+ /* CLI line buffering and line control */
+ vio_fifo cbuf ;
+ vio_line_control olc ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Functions
+ */
+extern vty_cli uty_cli_new(vio_vf vf) ;
+extern vty_cli uty_cli_close(vty_cli cli, bool final) ;
+
+extern void uty_cli_hist_show(vty_cli cli) ;
+extern ulen uty_cli_prompt_len(vty_cli cli) ;
+
+extern vty_readiness_t uty_cli(vty_cli cli) ;
+
+extern cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action,
+ cmd_context context) ;
+extern void uty_cli_out(vty_cli cli, const char *format, ...)
+ PRINTF_ATTRIBUTE(2, 3) ;
+extern void uty_cli_out_newline(vty_cli cli) ;
+extern void uty_cli_write(vty_cli cli, const char *this, int len) ;
+extern void uty_cli_wipe(vty_cli cli, int len) ;
+
+extern void uty_cli_set_lines(vty_cli cli, int lines, bool explicit) ;
+extern void uty_cli_set_window(vty_cli cli, int width, int height) ;
+extern void uty_cli_enter_more_wait(vty_cli cli) ;
+extern void uty_cli_exit_more_wait(vty_cli cli) ;
+
+extern void uty_cli_pre_monitor(vty_cli cli) ;
+extern void uty_cli_post_monitor(vty_cli cli) ;
+
+/*------------------------------------------------------------------------------
+ * Pro tem -- "\r\n" string
+ */
+extern const char* uty_cli_newline ;
#endif /* _ZEBRA_VTY_CLI_H */
diff --git a/lib/vty_command.c b/lib/vty_command.c
new file mode 100644
index 00000000..d195f193
--- /dev/null
+++ b/lib/vty_command.c
@@ -0,0 +1,2202 @@
+/* VTY for command execution
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "misc.h"
+#include "stdio.h"
+
+#include "vty_command.h"
+#include "command_local.h"
+#include "command_queue.h"
+#include "command_execute.h"
+#include "command_parse.h"
+#include "vty_local.h"
+#include "vty_io.h"
+#include "vty_cli.h"
+#include "vio_fifo.h"
+#include "vty_io_file.h"
+#include "vty_io_term.h"
+#include "list_util.h"
+#include "qstring.h"
+
+/*==============================================================================
+ * vty_command.c contains functions used by the command processing, where
+ * that interacts with the vty -- in particular vty I/O.
+ *
+ * There are two command loops -- cmd_read_config() and cq_process(). Each
+ * command loop appears to be a thread of control sucking in command lines,
+ * parsing and executing them. In the process, input and output pipes are
+ * opened and closed, and the vty stack grows and shrinks.
+ *
+ * For cmd_read_config() -- see command_execute.c -- the command loop is,
+ * indeed, a thread of control and all the I/O operations are "blocking", so
+ * that any waiting required is done inside the loop.
+ *
+ * For cq_process() -- see command_queue.c -- things are a lot more
+ * complicated: first, I/O is non-blocking, so I/O operations may return
+ * CMD_WAITING and the command loop must exit and be able to restart, when
+ * the I/O completes in the background; second, in a multi-pthread environment
+ * some operations must be performed in the CLI pthread while others must be
+ * performed in the command (the Routing Engine, generally) pthread. (It would
+ * be easier if each cq_process instance were a separate pthread, but we here
+ * implement it as a form of co-routine, driven by messages.)
+ *
+ * The I/O is structured so that all output either completes before the I/O
+ * operation returns, or will be autonomously completed by the background
+ * pselect() process. Only when waiting for input does the loop need to exit
+ * waiting for I/O.
+ *
+ * So a command loop takes the general form:
+ *
+ * loop: fetch command line
+ * parse command line
+ * loop if empty or all comment
+ * reflect command line
+ * deal with any pipe open actions
+ * dispatch command (if any)
+ * push command output to output
+ * loop
+ *
+ * hiatus: deal with issue
+ * loop if OK and not waiting for input
+ * exit command loop
+ *
+ * In the loop, if any operation receives a return code it cannot immediately
+ * deal with, it jumps to the "hiatius". For everything except the command
+ * line fetch this will be some sort of error -- either I/O or command -- or
+ * some external event closing down the loop. For the command line fetch
+ * this may be because must now wait for input, or because the current input
+ * has reached EOF and must now be closed (which may pop things off the
+ * vty stack). In any event, once the issue is dealt with, can leave the
+ * hiatus and return to the top of the loop.
+ *
+ * Note that in hiatus the command loop may be waiting for some output I/O
+ * to complete -- e.g. while closing an output pipe.
+ *
+ * So, most obviously for cq_process(), the loop is a co-routine which exits
+ * and is re-entered at the hiatus. When the loop does exit, it has either
+ * come to a dead stop (for a number of reasons) or it is waiting for input.
+ *
+ * The state of the command loop is vty->vio->state. The main states are:
+ *
+ * vc_running -- somewhere in the command loop, doing things.
+ *
+ * vc_waiting -- waiting for I/O
+ *
+ * When some event (such as some input arriving) occurs it is signalled to
+ * the command loop by uty_cmd_signal(), where the value of the signal is
+ * a CMD_XXXX code. If the loop is in vc_waiting state, the loop can be
+ * re-entered (at the hiatus point) with the return code. Otherwise, what
+ * happens depends on the state and the return code -- see uty_cmd_signal().
+ *
+ * Functions called by the various steps in the command loop will check for
+ * a pending signal, and will force a jump to hiatus -- using CMD_HIATUS return
+ * code. (This is how the command loop may be "interrupted" by, for example
+ * an I/O error detected in the pselect() process.)
+ */
+
+/*------------------------------------------------------------------------------
+ * Prototypes
+ */
+static bool uty_cmd_loop_prepare(vty_io vio) ;
+static void uty_cmd_stopping(vty_io vio, bool exeunt) ;
+static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ;
+static cmd_return_code_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ;
+static void uty_cmd_out_cancel(vio_vf vf, bool base) ;
+static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ;
+static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) ;
+static void uty_cmd_prepare(vty_io vio) ;
+static bool uty_cmd_config_lock(vty vty) ;
+static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ;
+
+/*==============================================================================
+ * Starting up, communicating with and closing down a command loop.
+ */
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter the config read command loop.
+ *
+ * Initialise exec object, and copy required settings from the current vin
+ * and vout.
+ *
+ * Returns: true <=> acquired or did not need config symbol of power
+ * or: false <=> needed but could not acquire symbol of power
+ */
+extern bool
+vty_cmd_config_loop_prepare(vty vty)
+{
+ bool ok ;
+
+ VTY_LOCK() ;
+
+ assert(vty->type == VTY_CONFIG_READ) ;
+
+ ok = uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */
+
+ VTY_UNLOCK() ;
+
+ return ok ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Enter the command_queue command loop.
+ */
+extern void
+uty_cmd_queue_loop_enter(vty_io vio)
+{
+ bool ok ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(vio->vty->type == VTY_TERMINAL) ;
+
+ ok = uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */
+
+ if (!ok)
+ uty_out(vio, "%% unable to start in config mode\n") ;
+
+ qassert(vio->state == vc_running) ;
+ cq_loop_enter(vio->vty, (vio->vty->node == NULL_NODE) ? CMD_CLOSE
+ : ok ? CMD_SUCCESS
+ : CMD_WARNING) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter a command loop.
+ *
+ * Initialise cmd_exec object, and its cmd_context -- given vty->type and
+ * vty->node.
+ *
+ * Returns: true <=> acquired or did not need config symbol of power
+ * or: false <=> needed but could not acquire symbol of power
+ */
+static bool
+uty_cmd_loop_prepare(vty_io vio)
+{
+ bool ok ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->vty->exec == NULL) ;
+
+ vio->vty->exec = cmd_exec_new(vio->vty) ;
+ vio->state = vc_running ;
+
+ ok = true ;
+ if (vio->vty->node > MAX_NON_CONFIG_NODE)
+ {
+ ok = uty_cmd_config_lock(vio->vty) ;
+ if (!ok)
+ vio->vty->node = ENABLE_NODE ;
+ } ;
+
+ uty_cmd_prepare(vio) ;
+
+ return ok ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * When entering command loop, or after opening or closing a vin/vout object,
+ * update the vty->exec context.
+ *
+ * Output to the vout_base is suppressed for reading of configuration files.
+ *
+ * Reflection of the command line depends on the current context, and on the
+ * state of output suppression.
+ */
+static void
+uty_cmd_prepare(vty_io vio)
+{
+ cmd_exec exec = vio->vty->exec ;
+
+ exec->reflect = exec->context->reflect_enabled ;
+ exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ)
+ && (vio->vout_depth == 1)
+ && !exec->reflect ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Signal to the command loop that some I/O has completed -- successfully, or
+ * with some I/O error (including time out), or otherwise.
+ *
+ * Accepts the following return codes:
+ *
+ * CMD_SUCCESS -- if vc_waiting, passed in
+ * otherwise, ignored
+ *
+ * CMD_WAITING -- ignored
+ *
+ * CMD_IO_ERROR -- if vc_waiting, passed in
+ * if vc_running, set signal, unless already CMD_STOP
+ * otherwise, ignored
+ *
+ * CMD_CANCEL -- if vc_waiting, passed in
+ * if vc_running, set signal, unless already CMD_STOP
+ * or CMD_IO_ERROR
+ * otherwise, ignored
+ *
+ * NB: if sets CMD_CANCEL signal, sets vio->cancel.
+ *
+ * if passes CMD_CANCEL in, sets vio->cancel.
+ */
+extern void
+uty_cmd_signal(vty_io vio, cmd_return_code_t ret)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert( (ret == CMD_SUCCESS) || (ret == CMD_WAITING)
+ || (ret == CMD_IO_ERROR)
+ || (ret == CMD_CANCEL) ) ;
+
+ switch (vio->state)
+ {
+ case vc_running:
+ if ((ret == CMD_SUCCESS) || (ret == CMD_WAITING))
+ break ; /* Ignored */
+
+ if (vio->signal == CMD_STOP)
+ break ; /* Cannot override CMD_STOP */
+
+ if (ret != CMD_IO_ERROR)
+ {
+ qassert(ret == CMD_CANCEL) ;
+
+ if (vio->signal == CMD_IO_ERROR)
+ break ; /* Cannot override CMD_IO_ERROR */
+
+ vio->cancel = true ;
+ } ;
+
+ vio->signal = ret ;
+
+ break ;
+
+ case vc_waiting: /* pass in the return code */
+ if (ret != CMD_WAITING)
+ {
+ vio->state = vc_running ;
+ if (ret == CMD_CANCEL)
+ vio->cancel = true ;
+
+ cq_continue(vio->vty, ret) ;
+ } ;
+ break ;
+
+ case vc_stopped: /* ignore everything */
+ break ;
+
+ default:
+ zabort("unknown vio->state") ;
+ break ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Stop the command loop.
+ *
+ * If the loop is vc_null, it has never started, so is stopped.
+ *
+ * If is non-blocking then revoke any in-flight message (or legacy thread
+ * event), *and* if does that, then the command loop may be stopped.
+ *
+ * The loop may then be:
+ *
+ * vc_running -- for multi-pthread world this means that the
+ * command loop is executing, and must be sent a
+ * CMD_STOP signal. (Is not waiting for a message,
+ * because we just tried revoking.) Because this
+ * function is in the vty_cli_nexus, the command loop
+ * *must* be running in the vty_cmd_nexus.
+ *
+ * for single-pthread (or legacy thread), this should
+ * be impossible -- this code cannot run at the same
+ * time as the command loop. However, we send a
+ * CMD_STOP signal.
+ *
+ * vc_waiting -- can stop the command loop now. Setting vc_stopped
+ * turns off any further signals to the command loop.
+ *
+ * vc_stopped -- already stopped (or never started).
+ *
+ * Note: we still revoke any in-flight messages.
+ * This deals with the case of the command loop
+ * picking up a CMD_STOP signal in the vty_cmd_nexus,
+ * but the message transferring the command loop to
+ * the vty_cli_nexus has not been picked up yet.
+ *
+ * Note: if the command loop is exiting normally,
+ * then it will already be vc_stopped -- see
+ * vty_cmd_loop_exit()
+ *
+ * NB: if this is "curtains" then this *should* find the command loop already
+ * vc_stopped -- see uty_close() -- but will force the issue.
+ *
+ * Returns: true <=> the command loop is vc_stopped
+ * false => the command loop is running, but a CMD_STOP signal has
+ * been set.
+ */
+extern bool
+uty_cmd_loop_stop(vty_io vio, bool curtains)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (vio->blocking || !cq_revoke(vio->vty))
+ {
+ if ((vio->state == vc_running) && !curtains)
+ {
+ uty_set_monitor(vio, off) ; /* instantly */
+ vio->signal = CMD_STOP ;
+
+ return false ;
+ } ;
+
+ qassert(vio->state != vc_running) ;
+ } ;
+
+ uty_cmd_stopping(vio, true) ; /* -> vc_stopped */
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the command loop stopped -- forced exit.
+ */
+extern void
+vty_cmd_set_stopped(vty vty)
+{
+ VTY_LOCK() ;
+ uty_cmd_stopping(vty->vio, true) ; /* forced exit */
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * The command loop is stopping or closing.
+ *
+ * We drop the config symbol of power and effectively discard all input.
+ * Stops any log monitoring, immediately.
+ *
+ * This is done as soon as a CMD_CLOSE, CMD_STOP or CMD_STOP signal is seen, so
+ * that the symbol of power is not held while a vty is closing its vio stack,
+ * or while the command loop is being transferred from the vty_cmd_nexus to the
+ * vty_cli_nexus.
+ *
+ * If exeunt, set vc_stopped -- otherwise leave vc_running to tidy up.
+ *
+ * This can be called any number of times.
+ */
+static void
+uty_cmd_stopping(vty_io vio, bool exeunt)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_set_monitor(vio, off) ; /* instantly */
+
+ vio->vin_true_depth = 0 ; /* all stop */
+ uty_cmd_config_lock_check(vio->vty, NULL_NODE) ;
+
+ if (exeunt)
+ vio->state = vc_stopped ; /* don't come back */
+ else
+ qassert(vio->state == vc_running) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If we have a CMD_STOP on our hands, then drop the config symbol of power.
+ *
+ * This is done so that on SIGHUP the symbol of power can be acquired to read
+ * the configuration file, without an interlock to wait for closing then
+ * current vty.
+ */
+extern void
+vty_cmd_check_stop(vty vty, cmd_return_code_t ret)
+{
+ VTY_LOCK() ;
+
+ if ((ret == CMD_STOP) || (vty->vio->signal == CMD_STOP))
+ {
+ vty->vio->signal = CMD_STOP ; /* make sure signal set */
+
+ uty_cmd_stopping(vty->vio, false) ; /* not exit, yet */
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Exit command loop, with final close of the VTY.
+ *
+ * NB: on exit the VTY has been release, so do NOT attempt to touch the VTY
+ * or any of its components.
+ */
+extern void
+vty_cmd_loop_exit(vty vty)
+{
+ VTY_LOCK() ;
+
+ VTY_ASSERT_CAN_CLOSE(vty) ;
+
+ uty_cmd_stopping(vty->vio, true) ; /* exit -- set vc_stopping */
+ uty_close(vty->vio) ; /* down close the vty */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*==============================================================================
+ * Command line fetch.
+ *
+ * Will read command lines from the current input, until that signals EOF.
+ *
+ * Before attempting to read, will check for a vio->signal, and that the input
+ * and output stack depths do not require attention. The CMD_HIATUS return
+ * code signals that something needs to be dealt with before any input is
+ * attempted.
+ *
+ * NB: all closing of inputs and/or outputs is done in the hiatus, below.
+ */
+
+/*------------------------------------------------------------------------------
+ * Fetch the next command line to be executed.
+ *
+ * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
+ *
+ * vty->exec->line points at the line
+ * vty->exec->to_do says what to do with it
+ *
+ * or: CMD_WAITING => OK -- but waiting for command line to arrive
+ * <=> non-blocking
+ *
+ * or: CMD_EOF => OK -- but nothing to fetch from the current vin
+ *
+ * Need to close the current vin and pop vin/vout
+ * as necessary.
+ *
+ * or: CMD_HIATUS => OK -- but need to close one or more vin/vout
+ * to adjust stack.
+ *
+ * or: CMD_CANCEL => cancel vio->signal detected.
+ *
+ * or: CMD_IO_ERROR => failed (here or signal) -- I/O error or timeout.
+ *
+ * or: CMD_STOP => stop vio->signal detected.
+ *
+ * NB: can be called from any thread -- because does no closing of files or
+ * anything other than read/write.
+ */
+extern cmd_return_code_t
+vty_cmd_fetch_line(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+ vio_vf vf ;
+ cmd_exec exec ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ; /* once locked */
+ exec = vty->exec ;
+
+ cmd_action_clear(exec->action) ; /* tidy */
+
+ vf = vio->vin ;
+
+ ret = vio->signal ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ if ( (vio->vin_depth < vio->vout_depth) ||
+ (vio->vin_depth > vio->vin_true_depth) )
+ ret = CMD_HIATUS ;
+ else
+ {
+ qassert(vio->vin_depth == vio->vin_true_depth) ;
+ qassert(vio->vin_depth != 0) ;
+
+ switch (vf->vin_state)
+ {
+ case vf_closed:
+ zabort("invalid vf->vin_state (vf_closed)") ;
+ break ;
+
+ case vf_open:
+ switch (vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("invalid VIN_NONE") ;
+ break ;
+
+ case VIN_TERM:
+ ret = uty_term_fetch_command_line(vf, exec->action,
+ exec->context) ;
+ break ;
+
+ case VIN_VTYSH:
+ zabort("invalid VIN_VTYSH") ;
+ break ;
+
+ case VIN_DEV_NULL:
+ ret = CMD_EOF ;
+ break ;
+
+ case VIN_FILE:
+ case VIN_CONFIG:
+ ret = uty_file_fetch_command_line(vf, exec->action) ;
+ break ;
+
+ case VIN_PIPE:
+ ret = uty_pipe_fetch_command_line(vf, exec->action) ;
+ break ;
+
+ default:
+ zabort("unknown vin_type") ;
+ } ;
+
+ break ;
+
+ case vf_end: /* Should be dealt with in hiatus... */
+ ret = CMD_HIATUS ; /* ...so go (back) there ! */
+ break ;
+
+ default:
+ zabort("unknown vf->vin_state") ;
+ break ;
+ } ;
+ } ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ * Special command handling functions.
+ */
+
+/*------------------------------------------------------------------------------
+ * Handle a "special" command -- anything not cmd_do_command.
+ *
+ * These "commands" are related to VTY_TERMINAL CLI only.
+ *
+ * Returns: CMD_SUCCESS -- OK, carry on
+ * CMD_CLOSE -- bring everything to an orderly stop
+ */
+extern cmd_return_code_t
+vty_cmd_special(vty vty)
+{
+ cmd_return_code_t ret ;
+ cmd_do_t to_do ;
+ node_type_t next_node ;
+
+ ret = CMD_SUCCESS ;
+
+ to_do = vty->exec->action->to_do ;
+
+ /* Note that the next node handling is special here... we establish
+ * the next node explicitly here -- there is no parse operation to preset
+ * what CMD_SUCCESS next node will be.
+ */
+
+ vty->node = vty->exec->context->node ; /* as per all commands */
+ next_node = vty->exec->context->node ; /* by default. */
+
+ switch (to_do & cmd_do_mask)
+ {
+ case cmd_do_nothing:
+ case cmd_do_ctrl_c:
+ break ;
+
+ case cmd_do_eof:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal closed\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_timed_out:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal timed out\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_command:
+ if ((to_do & cmd_do_auth) != 0)
+ ret = vty_cmd_auth(vty, &next_node) ;
+ else
+ zabort("invalid cmd_do_command") ;
+ break ;
+
+ case cmd_do_ctrl_d:
+ if ((to_do & cmd_do_auth) != 0)
+ next_node = cmd_node_exit_to(vty->node) ;
+ else
+ zabort("invalid cmd_do_ctrl_d") ;
+ break ;
+
+ case cmd_do_ctrl_z:
+ next_node = cmd_node_end_to(vty->node) ;
+ break ;
+
+ default:
+ zabort("unknown or invalid cmd_do") ;
+ } ;
+
+ /* Now worry about changing node */
+ if (ret == CMD_CLOSE)
+ next_node = NULL_NODE ;
+ else if (next_node == NULL_NODE)
+ ret = CMD_CLOSE ;
+
+ if (next_node != vty->exec->context->node)
+ {
+ vty->exec->context->node = next_node ;
+ vty_cmd_config_lock_check(vty, next_node) ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Check that can enter AUTH_ENABLE_NODE.
+ *
+ * Must be: VTY_TERMINAL
+ *
+ * and: no pipes, in or out -- so talking directly to terminal
+ *
+ * and: be VIEW_NODE if there is no enable password.
+ *
+ * Note that "can_enable" <=> vin_depth == 1 and VTY_TERMINAL (or other VTY
+ * that can authenticate). But it may not => vout_depth == 0.
+ */
+extern cmd_return_code_t
+vty_cmd_can_auth_enable(vty vty)
+{
+ cmd_return_code_t ret ;
+
+ VTY_LOCK() ;
+
+ assert(vty->exec->parsed->nnode == AUTH_ENABLE_NODE) ;
+ assert((vty->exec->context->onode == VIEW_NODE) ||
+ (vty->exec->context->onode == RESTRICTED_NODE)) ;
+
+ ret = CMD_WARNING ;
+
+ if (vty->type != VTY_TERMINAL)
+ uty_out(vty->vio, "%% Wrong VTY type (%d) for 'enable'", vty->type) ;
+ else if ((vty->exec->context->onode != VIEW_NODE)
+ && (host.enable == NULL))
+ uty_out(vty->vio, "%% cannot enable because there is no enable password") ;
+ else if ((vty->vio->vin_depth != 1) || (vty->vio->vout_depth != 1))
+ uty_out(vty->vio,
+ "%% cannot authenticate for 'enable' in a pipe command") ;
+ else
+ {
+ assert(vty->exec->context->can_auth_enable) ;
+ ret = CMD_SUCCESS ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Authentication of vty
+ *
+ * Note that if the AUTH_NODE password fails too many times, the vty is
+ * closed.
+ *
+ * Quagga authentication is a touch complicated. The following appear to be
+ * the rules:
+ *
+ * 1. host.no_password_check -- set by "no login" command
+ *
+ * Means that a VTY_TERMINAL can start without going through any
+ * password check -- including when no host.password is configured.
+ *
+ * Note that this does not affect the authentication for enable, except
+ * at startup of a VTY_TERMINAL...
+ *
+ * When a VTY_TERMINAL starts:
+ *
+ * * if host.restricted_mode -> RESTRICTED_NODE
+ *
+ * * else if host.advanced -> ENABLE_NODE
+ *
+ * This is whether or not an enable password exists.
+ *
+ * * otherwise -> VIEW_NODE
+ *
+ * So being in RESTRICTED_NODE <=> was host.no_password_check &&
+ * host.restricted_mode when the VTY_TERMINAL was started.
+ *
+ * 2. host.restricted_mode -- set by "anonymous restricted"
+ *
+ * Significant only at VTY_TERMINAL start, and only if no_password_check.
+ *
+ * NB: if the enable password is NULL, there is not much point in
+ * RESTRICTED_NODE, since ENABLE_NODE is but one command away.
+ *
+ * NB: that behaviour is is modified here... if is in RESTRICTED_MODE,
+ * will not authenticate AUTH_ENABLE_NODE if there is no enable
+ * password.
+ *
+ * Note that the check is left to this point, so that is completed
+ * atomically. Elsewhere, will refuse to enter ENABLE_NODE from
+ * RESTRICTED_NODE if no enable password. By the time we get here
+ * it is (just) possible that the situation has changed.
+ *
+ * 3. host.advanced -- set by "service advanced-vty"
+ *
+ * Significant iff there is no enable password, when it sets ENABLE_NODE
+ * as the start up node (if no_password_check) or post AUTH_NODE node.
+ *
+ * 4. host.password -- set by "password xx"
+ *
+ * Unless no_password_check, if there is no password, you cannot start
+ * a vty.
+ *
+ * 5. host.enable -- set by "enable password xx"
+ *
+ * If this is set, then must authenticate against it for vty to reach
+ * ENABLE_NODE.
+ *
+ * If it is not set, then can enter ENABLE_NODE at any time.
+ *
+ * If AUTH_ENABLE_NODE fails, falls back to the node we came from -- which has
+ * been planted in the context for this purpose. (If host.restricted_mode has
+ * changed since the vty started, could argue this should change where should
+ * fall back to... but that seems unnecessarily complicated.)
+ *
+ * Returns: CMD_SUCCESS -- OK, one way or another
+ * CMD_WARNING -- with error message sent to output
+ * CMD_CLOSE -- too many password failures
+ */
+static cmd_return_code_t
+vty_cmd_auth(vty vty, node_type_t* p_next_node)
+{
+ char *crypt (const char *, const char *);
+
+ char* passwd = NULL ;
+ bool encrypted = false ;
+ bool enable = false ;
+ bool advanced ;
+ bool pass ;
+ cmd_return_code_t ret ;
+ cmd_exec exec ;
+ cmd_context context ;
+
+ exec = vty->exec ;
+ context = exec->context ;
+
+ /* Select the password we need to check against. */
+ passwd = NULL ;
+ encrypted = false ;
+ enable = false ;
+ advanced = false ;
+
+ pass = false ;
+
+ VTY_LOCK() ; /* while access host.xx */
+
+ switch (vty->node)
+ {
+ case AUTH_NODE:
+ passwd = host.password ;
+ encrypted = host.password_encrypted ;
+ enable = false ;
+
+ context->onode = NULL_NODE ; /* started from nowhere */
+
+ if (host.advanced && (host.enable == NULL))
+ {
+ context->tnode = ENABLE_NODE ;
+ advanced = true ;
+ }
+ else
+ {
+ context->tnode = VIEW_NODE ;
+ advanced = false ;
+ } ;
+ break ;
+
+ case AUTH_ENABLE_NODE:
+ passwd = host.enable ;
+ encrypted = host.enable_encrypted ;
+ enable = true ;
+ advanced = false ;
+
+ assert((context->onode == VIEW_NODE) ||
+ (context->onode == RESTRICTED_NODE)) ;
+ break ;
+
+ default:
+ zabort("unknown vty->node") ;
+ break ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ /* Check against selected password (if any) */
+ if (passwd == NULL)
+ {
+ /* Here we reject any attempt to AUTH_NODE against an empty password.
+ *
+ * Otherwise, is dealing with the (largely) theoretical case of
+ * This fails any attempt to AUTH_ENABLE against an empty password
+ * if was in RESTRICTED_NODE.
+ *
+ * This passes the theoretically possible case of enable in VIEW_NODE,
+ * when there was an enable password set when the enable command was
+ * executed, but it has since been unset !
+ */
+ pass = context->onode == VIEW_NODE ;
+ }
+ else
+ {
+ char* candidate = qs_make_string(exec->action->line) ;
+
+ if (encrypted)
+ candidate = crypt(candidate, passwd) ;
+
+ pass = (strcmp(candidate, passwd) == 0) ;
+ } ;
+
+ /* Now worry about the result */
+ ret = CMD_SUCCESS ; /* so far, so good */
+
+ if (pass)
+ {
+ *p_next_node = context->tnode ;
+
+ if (enable || advanced)
+ context->can_enable = true ;
+
+ if (*p_next_node == CONFIG_NODE)
+ {
+ ret = vty_cmd_config_lock(vty) ;
+ if (ret == CMD_WARNING)
+ *p_next_node = ENABLE_NODE ;
+ } ;
+
+ exec->password_failures = 0 ; /* forgive any failures */
+ }
+ else
+ {
+ bool no_more = false ;
+
+ if (passwd == NULL)
+ {
+ /* Cannot possibly authenticate ! */
+ no_more = true ;
+ vty_out(vty, "%% No password is set, cannot authenticate!\n") ;
+ }
+ else
+ {
+ exec->password_failures++ ;
+
+ if (exec->password_failures >= 3)
+ {
+ no_more = true ;
+ vty_out(vty, "%% Bad passwords, too many failures!\n") ;
+
+ exec->password_failures = 0 ; /* allow further attempts */
+ } ;
+ } ;
+
+ if (no_more)
+ {
+ if (!enable)
+ {
+ *p_next_node = NULL_NODE ;
+ ret = CMD_CLOSE ;
+ }
+ else
+ {
+ *p_next_node = context->onode ;
+ ret = CMD_WARNING ;
+ } ;
+ } ;
+ } ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ * Hiatus handling.
+ *
+ * The hiatus must deal with a number of slightly different things:
+ *
+ * * closing of inputs and/or outputs, and adjusting the stacks.
+ *
+ * All pipe closing is done here.
+ *
+ * * command errors
+ *
+ * * cancel
+ *
+ * * I/O errors and time-outs
+ *
+ * * closing of vty altogether, either:
+ *
+ * CMD_CLOSE -- which closes everything, completing all I/O
+ *
+ * CMD_STOP -- which stops the command loop, and lets uty_close()
+ * close everything "final".
+ *
+ * Note that while closing, for non-blocking vio, may return from the hiatus
+ * CMD_WAITING, and the hiatus will be called again (possibly a number of
+ * times) until all necessary closes and related I/O are complete.
+ */
+
+/*------------------------------------------------------------------------------
+ * Deal with return code at the "exec_hiatus" point in the command loop.
+ *
+ * May be entering the hiatus because a signal has been detected, which may
+ * override the given return code. Any signal is then cleared.
+ *
+ * The command_queue command loop runs until something happens that it
+ * cannot immediately deal with, at which point it enters "exec_hiatus", and
+ * this function is called. The command loop will deal with CMD_SUCCESS and
+ * CMD_EMPTY, but otherwise this function must deal with:
+ *
+ * CMD_HIATUS -- something requires attention, eg:
+ *
+ * - the vout_depth > vin_depth, so the vout needs to
+ * be closed and popped.
+ *
+ * - the vio->state needs to be checked.
+ *
+ * CMD_STOP -- stop the command loop and exit, closing vty
+ *
+ * CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof,
+ * or an end/exit command has forced the issue.
+ *
+ * The vin_real_depth must be reduced, and the top vin
+ * then closed.
+ *
+ * CMD_CLOSE -- from a command return => must close the entire vty.
+ *
+ * CMD_CLOSE causes the vty to be closed in an orderly
+ * fashion, dealing with all pending I/O, including
+ * all pipe return etc.
+ *
+ * Once everything has been closed down to the
+ *
+ * CMD_CANCEL -- cancel all output & close everything except the
+ * vin/vout base.
+ *
+ * CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to
+ * vc_waiting and the command loop MUST exit.
+ *
+ * CMD_SUCCESS -- see below
+ *
+ * CMD_EMPTY -- should not appear, but is treated as CMD_SUCCESS
+ *
+ * anything else -- treated as a command or I/O or other error.
+ *
+ * The handling of errors depends on the type of error:
+ *
+ * * command errors will cause all levels of the stack other than vin_base
+ * and vout_base to be closed, and a suitable error message output to the
+ * vout_base.
+ *
+ * Inputs are closed without dealing with any further input and discarding
+ * any buffered input.
+ *
+ * Pending output will be pushed out, and pipe return stuff will be sucked
+ * in and blown out, until the return signals EOF.
+ *
+ * * I/O errors and timeouts in any level of the stack other than vin_base
+ * and vout_base will cause everything except the vin_base and vout_base
+ * to be closed.
+ *
+ * I/O errors and time-outs in vin_base cause everything to be closed, but
+ * will try to put error messages and outstanding stuff to vout_base.
+ *
+ * I/O errors and time-outs in vout_base cause everything to be closed.
+ *
+ * Does the standard close... so will try to flush all outstanding stuff.
+ *
+ * This function will return:
+ *
+ * CMD_SUCCESS => OK -- can try and fetch a command line again.
+ *
+ * state == vc_running
+ *
+ * CMD_WAITING => OK -- but waiting for input to arrive or for something
+ * to be completely closed. => non-blocking
+ *
+ * state == vc_waiting
+ *
+ * CMD_STOP => OK -- all done: close the vty and exit command loop.
+ *
+ * state == vc_stopped
+ *
+ * If CMD_STOP is passed in, or a CMD_STOP signal
+ * is picked up, then has done nothing with the
+ * stacks -- uty_close() will do close "final".
+ *
+ * Otherwise, will have completing all pending stuff,
+ * and closed everything except the vout_base.
+ *
+ * And nothing else, except:
+ *
+ * CMD_IO_ERROR => an I/O error or time-out has been hit, probably while
+ * trying to close something.
+ *
+ * This error should be sent straight back to this
+ * function -- but is passed out so that any error is not
+ * hidden -- see cmd_read_config().
+ *
+ * When the command loop has gone vc_waiting, the I/O side of things can wake
+ * it up by uty_cmd_signal(), which passes in a return code. When the
+ * command loop runs it will call this function to handle the new return code.
+ * If CMD_SUCCESS is passed in, will continue trying to adjust the vin/vout
+ * stacks, if required.
+ *
+ * The configuration reader command loop also uses vty_cmd_hiatus() to handle
+ * all return codes. However, it will exit the command loop at the first
+ * hint of trouble.
+ *
+ * NB: can be called from any thread if !blocking, otherwise MUST be cli thread.
+ */
+extern cmd_return_code_t
+vty_cmd_hiatus(vty vty, cmd_return_code_t ret)
+{
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CAN_CLOSE(vty) ;
+
+ vio = vty->vio ;
+
+ qassert(vio->state == vc_running) ;
+
+ ret = uty_cmd_hiatus(vio, ret) ;
+
+ switch (ret)
+ {
+ case CMD_SUCCESS: /* ready to continue */
+ case CMD_IO_ERROR: /* for information */
+ break ;
+
+ case CMD_WAITING:
+ qassert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ break ;
+
+ case CMD_STOP: /* exit */
+ uty_cmd_stopping(vio, true) ; /* vio->state -> vc_stopped */
+ break ;
+
+ default:
+ zabort("invalid return code from uty_cmd_hiatus") ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Inside of vty_cmd_hiatus() -- see above.
+ */
+static cmd_return_code_t
+uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret)
+{
+ /* (1) Handle any vio->signal.
+ *
+ * If there is a signal it overrides most return codes, except:
+ *
+ * CMD_CANCEL -- trumped by: CMD_STOP, CMD_CLOSE or CMD_IO_ERROR
+ *
+ * CMD_IO_ERROR -- trumped by: CMD_STOP or CMD_CLOSE
+ *
+ * CMD_STOP -- trumps everything
+ *
+ * The vio_signal is now cleared (unless was CMD_STOP).
+ */
+ switch (vio->signal)
+ {
+ case CMD_SUCCESS:
+ break ;
+
+ case CMD_CANCEL:
+ if ((ret != CMD_STOP) && (ret != CMD_CLOSE) && (ret != CMD_IO_ERROR))
+ ret = CMD_CANCEL ;
+
+ vio->signal = CMD_SUCCESS ;
+ break ;
+
+ case CMD_IO_ERROR:
+ if ((ret != CMD_STOP) && (ret != CMD_CLOSE))
+ ret = CMD_IO_ERROR ;
+
+ vio->signal = CMD_SUCCESS ;
+ break ;
+
+ case CMD_STOP:
+ ret = CMD_STOP ;
+ break ;
+
+ default:
+ zabort("Invalid vio->signal value") ;
+ } ;
+
+ /* (2) Handle the return code/signal
+ *
+ * Deal here with the return codes that signify success, or signify
+ * success but some vin and/or vout need to be closed.
+ *
+ * Call uty_cmd_failed() to deal with return codes that signify some
+ * sort of failure. A failure generally means closing all the way to
+ * the vin_/vout_base, or possibly completely.
+ *
+ * CMD_WAITING is immediately returned
+ *
+ * CMD_STOP, whether passed in or from vio->signal is also immediately
+ * returned.
+ */
+ switch (ret)
+ {
+ case CMD_SUCCESS:
+ case CMD_EMPTY:
+ case CMD_HIATUS:
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ case CMD_STOP:
+ return CMD_STOP ; /* <<< ...exit here on CMD_STOP */
+
+ case CMD_WAITING:
+ return CMD_WAITING ; /* <<< ...exit here on CMD_WAITING */
+
+ case CMD_EOF:
+ uty_out_accept(vio) ; /* accept any buffered stuff. */
+
+ qassert(vio->vin_true_depth > 0) ;
+ --vio->vin_true_depth ; /* cause vin stack to pop */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ case CMD_CANCEL:
+ qassert(vio->vin_true_depth > 0) ;
+ vio->vin_true_depth = 1 ; /* cause vin stack to pop */
+ vio->cancel = true ; /* suppress output */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ case CMD_CLOSE:
+ uty_out_accept(vio) ; /* accept any buffered stuff. */
+
+ vio->vin_true_depth = 0 ; /* cause vin stack to close */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ default: /* some sort of error */
+ break ;
+ } ;
+
+ /* If the return code is (still) not CMD_SUCCESS, then must be an error
+ * of some kind, for which we now construct a suitable error message, and
+ * update the vin_true_depth as required.
+ */
+ if (ret != CMD_SUCCESS)
+ {
+ uint depth ;
+
+ depth = uty_cmd_failed(vio, ret) ;
+ if (depth < vio->vin_true_depth)
+ vio->vin_true_depth = depth ;
+
+ ret = CMD_SUCCESS ; /* Is now OK. */
+ } ;
+
+ /* Have established the (new) vio->vin_true_depth, so now need to make
+ * sure that the stack conforms to that.
+ *
+ * Adjusting the stack may take a while. So, if the vin_true_depth is 0,
+ * now is a good time to give up the config symbol of power. (This is
+ * for SIGHUP, which closes all vty before reading the configuration.)
+ *
+ * Note that because vin_true_depth is zero, could not fetch any further
+ * command lines or attempt to execute any commands, and don't care
+ * whether own the symbol of power or not.
+ */
+ if (vio->vin_true_depth == 0)
+ uty_cmd_stopping(vio, false) ; /* not exit, yet */
+
+ /* (3) Do we need to close one or more vin, or are we waiting for one to
+ * close ?
+ *
+ * The close will immediately close the input, and discard anything
+ * which has been buffered. The only difficulty with closing inputs
+ * is VIN_PIPE, where the "return" input (from the child stderr) may
+ * not yet have finished.
+ *
+ * For blocking vio, close operations will either complete or fail.
+ *
+ * For non-blocking vio, close operations may return CMD_WAITING
+ * (eg: VIN_PIPE where the child stderr is not yet at EOF, or the
+ * child completion status has not yet been collected).
+ *
+ * Where a close does not succeed, exit here and expect to come back
+ * later to complete the operation.
+ */
+ while (vio->vin_depth > vio->vin_true_depth)
+ {
+ ret = uty_vin_pop(vio, vio->vty->exec->context, false) ; /* not final */
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ qassert(vio->vin_depth == vio->vin_true_depth) ;
+
+ /* (4) Do we need to close one or more vout, or are we waiting for
+ * one to close ?
+ *
+ * Note that we do not close the vout_base here -- that is left open
+ * to the final minute, in case any parting messages are to be sent.
+ *
+ * Any remaining output is pushed and any remaining pipe return is
+ * shovelled through, and any child process is collected, along
+ * with its termination condition.
+ *
+ * For blocking vio, close operations will either complete or fail.
+ *
+ * For non-blocking vio, close operations may return CMD_WAITING
+ * (eg: where the output buffers have not yet been written away).
+ *
+ * Where a close does not succeed, exit here and expect to come back
+ * later to complete the operation.
+ */
+ while ((vio->vin_depth < vio->vout_depth) && (vio->vout_depth > 1))
+ {
+ if (vio->cancel)
+ uty_cmd_out_cancel(vio->vout, false) ; /* stop output & pipe return
+ * not vout_base */
+
+ ret = uty_vout_pop(vio, false) ; /* not final */
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ /* (5) If we are now at the vout_base, then:
+ *
+ * If there is anything in the pipe stderr return, copy that to the
+ * obuff -- unless vio->cancel.
+ *
+ * If there is an error message in hand, now is the time to move that to
+ * the obuf and clear the error message buffer. (If the vout_base has
+ * failed, then the error message is going nowhere, but there's nothing
+ * we can do about that -- the error has been logged in any case.)
+ *
+ * Push any outstanding output (including any error message) to the
+ * vout_base.
+ *
+ * If the vty is about to be closed, this step ensures that all output
+ * is tidily dealt with, before uty_close() performs its "final" close.
+ */
+ if (vio->vout_depth == 1)
+ {
+ if (vio->cancel)
+ {
+ /* Once we have cleared the output buffer etc., clear the cancel
+ * flag and output "^C" to show what has happened.
+ */
+ uty_cmd_out_cancel(vio->vout, true) ; /* stop output & pipe return
+ * is vout_base */
+ uty_out(vio, " ^C\n") ;
+ } ;
+
+ if (!vio_fifo_empty(vio->ps_buf))
+ {
+ if (!vio->cancel)
+ vio_fifo_copy(vio->obuf, vio->ps_buf) ;
+ vio_fifo_clear(vio->ps_buf, true) ; /* clear any marks too */
+ } ;
+
+ vio->cancel = false ;
+
+ if (!vio_fifo_empty(vio->ebuf))
+ {
+ vio_fifo_copy(vio->obuf, vio->ebuf) ;
+ vio_fifo_clear(vio->ebuf, true) ; /* clear any marks too */
+ } ;
+
+ ret = uty_cmd_out_push(vio->vout, false) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* CMD_WAITING or CMD_IO_ERROR */
+ } ;
+
+ /* (6) Stacks are straight.
+ *
+ * If there is no input left, the command loop must now stop, close the
+ * vty and exit.
+ *
+ * Otherwise, prepare to execute commands at the, presumed new, stack
+ * depth.
+ */
+ qassert(ret == CMD_SUCCESS) ;
+
+ if (vio->vin_depth == 0)
+ return CMD_STOP ;
+
+ uty_cmd_prepare(vio) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*==============================================================================
+ * Opening of pipes and adjustment of stacks.
+ */
+static void uty_cmd_command_path(qstring name, cmd_context context) ;
+
+/*------------------------------------------------------------------------------
+ * Open the given file as an in pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
+ */
+extern cmd_return_code_t
+uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context,
+ qstring name, cmd_pipe_type_t type)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ ret = uty_file_read_open(vio, name, context) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
+
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Run the given shell command as an in pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
+ */
+extern cmd_return_code_t
+uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command,
+ cmd_pipe_type_t type)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ uty_cmd_command_path(command, context) ;
+ ret = uty_pipe_read_open(vio, command, context) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
+
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open the given file as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
+extern cmd_return_code_t
+uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name,
+ cmd_pipe_type_t type, bool after)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ ret = uty_file_write_open(vio, name,
+ ((type & cmd_pipe_append) != 0), context, after) ;
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open the given shell command as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
+extern cmd_return_code_t
+uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command,
+ cmd_pipe_type_t type, bool after)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ uty_cmd_command_path(command, context) ;
+ ret = uty_pipe_write_open(vio, command,
+ ((type & cmd_pipe_shell_cmd) != 0), after) ;
+
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open "/dev/null" as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
+extern cmd_return_code_t
+uty_cmd_open_out_dev_null(vty_io vio, bool after)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ;
+ uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0, after) ;
+
+ uty_cmd_prepare(vio) ;
+
+ ret = CMD_SUCCESS ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Complete the given file name, if not rooted.
+ *
+ * Returns: given or new qpath (if given was NULL)
+ */
+extern qpath
+uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context)
+{
+ if (*name == '/')
+ return qpath_set(dst, name) ; /* done if is rooted */
+
+ if (*name != '~')
+ dst = qpath_copy(dst, context->dir_cd) ;
+ else
+ {
+ /* Have a leading '~' -- deal with:
+ *
+ * "~~/???" or "~~\0", which for Quagga -> configuration directory
+ *
+ * "~./???" or "~.\0", which for Quagga -> "here" (same as enclosing
+ * pipe)
+ *
+ * "~/???" or "~\0", which -> HOME environment variable
+ * or initial working directory
+ * for login.
+ *
+ * "~user/???" or "~user\0", which -> initial working directory
+ * for given user
+ */
+ if ((*(name + 1) == '~') &&
+ ( (*(name + 2) == '/') || (*(name + 2) == '\0')) )
+ dst = qpath_copy(dst, context->dir_home) ;
+ else if ((*(name + 1) == '.') &&
+ ( (*(name + 2) == '/') || (*(name + 2) == '\0')) )
+ dst = qpath_copy(dst, context->dir_here) ;
+ else
+ {
+ qpath was = dst ;
+
+ dst = qpath_get_home(dst, name + 1) ;
+
+ /* If didn't get a home, return the original name
+ */
+ if (dst == NULL)
+ return qpath_set(was, name) ;
+ } ;
+ } ;
+
+ return qpath_append_str(dst, name) ; /* create the full path */
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the given qstring starts with a '~' directory or is a relative path,
+ * then now is the time to complete it.
+ */
+static void
+uty_cmd_command_path(qstring command, cmd_context context)
+{
+ const char* p, * s ;
+ qstring cmd ;
+ qpath qp ;
+
+ s = p = qs_string(command) ;
+
+ if ((*p == '/') || (*p == '\0'))
+ return ; /* absolute path or empty ! */
+
+ do
+ {
+ ++p ;
+ if (*p <= ' ')
+ return ; /* no path involved */
+ }
+ while (*p != '/') ; /* look for '/' */
+
+ do
+ ++p ;
+ while (*p > ' ') ; /* look for end */
+
+ cmd = qs_set_n(NULL, s, p - s) ;
+ qp = uty_cmd_path_name_complete(NULL, qs_string(cmd), context) ;
+
+ qs_set_cp_nn(command, 0) ;
+ qs_replace(command, p - s, qpath_string(qp), qpath_len(qp)) ;
+
+ qs_free(cmd) ;
+ qpath_free(qp) ;
+} ;
+
+/*==============================================================================
+ * Output before and after command execution.
+ *
+ * All output goes to a fifo, after a fifo "end mark". After reflecting a
+ * command and after completing a command, all outstanding output is pushed
+ * out -- advancing the end mark past all output to date.
+ */
+
+/*------------------------------------------------------------------------------
+ * Reflect the command line to the current vio->obuf.
+ *
+ * Advances the end_mark past the reflected line, so that output (in particular
+ * error stuff) is separate.
+ *
+ * NB: pushes the output, so that if the command takes a long time to process,
+ * it is visible while it proceeds.
+ *
+ * Returns: CMD_SUCCESS -- all buffers are empty
+ * CMD_WAITING -- all buffers are not empty
+ * CMD_IO_ERROR -- error or time-out
+ * CMD_HIATUS -- the vty is not in vc_running state.
+ *
+ * This can be called in any thread.
+ *
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR).
+ */
+extern cmd_return_code_t
+vty_cmd_reflect_line(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ vio = vty->vio ; /* once locked */
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ vio_fifo obuf ;
+ qstring line ;
+
+ obuf = vio->obuf ;
+ line = vty->exec->action->line ;
+
+ vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ;
+ vio_fifo_put_byte(obuf, '\n') ;
+
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command has completed successfully.
+ *
+ * An output generated by the command is now pushed unless exec->out_suppress,
+ * or discarded.
+ */
+extern cmd_return_code_t
+vty_cmd_success(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ vio = vty->vio ; /* once locked */
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ if (!vio_fifo_tail_empty(vio->obuf))
+ {
+ if (!vty->exec->out_suppress)
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+ else
+ vio_fifo_back_to_end_mark(vio->obuf, true) ; /* keep end mark */
+ } ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If there is anything after the end_mark, push it to be written, now.
+ *
+ * This is used by configuration file output, which outputs to the fifo and
+ * pushes every now and then.
+ *
+ * See uty_cmd_out_push() below.
+ */
+extern cmd_return_code_t
+vty_cmd_out_push(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ vio = vty->vio ; /* once locked */
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If there is anything after the end_mark, advance the end mark and attempt to
+ * write away contents of the buffer.
+ *
+ * For non-blocking vf, will write as much as possible here, and anything left
+ * will be left to the pselect() process, unless "final".
+ *
+ * For blocking vf, may block here, unless "final".
+ *
+ * If "final", will attempt to write etc., but will not block and may turn
+ * off the pselect() processing of this vf. "final" is used when a pipe of
+ * some kind is being closed "final", and the slave output is being pushed.
+ *
+ * NB: takes no notice of vf->out_suppress, which applies only to buffered
+ * output present when successfully complete a command -- vty_cmd_success().
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ *
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR).
+ */
+extern cmd_return_code_t
+uty_cmd_out_push(vio_vf vf, bool final)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ vio_fifo_step_end_mark(vf->obuf) ; /* advance the end mark */
+
+ ret = CMD_SUCCESS ;
+
+ switch (vf->vout_state)
+ {
+ case vf_open:
+ switch (vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid vout_none") ;
+ break ;
+
+ case VOUT_TERM:
+ /* Note that we ignore "final" -- the VOUT_TERM runs until
+ * it is closed.
+ */
+ ret = uty_term_out_push(vf, false) ;
+ break ;
+
+ case VOUT_VTYSH:
+ /* Kick the writer */
+ break ;
+
+ case VOUT_FILE:
+ /* push everything if the vty is being closed. */
+ ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ;
+ break ;
+
+ case VOUT_PIPE:
+ ret = uty_pipe_out_push(vf, final) ;
+ break ;
+
+ case VOUT_CONFIG:
+ /* push everything if the vty is being closed. */
+ ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ;
+ break ;
+
+ case VOUT_DEV_NULL:
+ case VOUT_SH_CMD:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ break ;
+
+ case VOUT_STDOUT:
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors
+ break ;
+
+ case VOUT_STDERR:
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors
+ break ;
+
+ default:
+ zabort("unknown vout_type") ;
+ } ;
+ break ;
+
+ case vf_closed:
+ case vf_end:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ break ; /* immediate success ! */
+
+ default:
+ zabort("unknown vf->vout_state") ;
+ break ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Bring output and any pipe return to a sudden halt.
+ */
+static void
+uty_cmd_out_cancel(vio_vf vf, bool base)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ /* Dump contents of obuf and if not base: force vf_end (if vf_open)
+ */
+ vio_fifo_clear(vf->obuf, false) ;
+
+ if (!base && (vf->vout_state == vf_open))
+ vf->vout_state = vf_end ;
+
+ /* If there is a pipe return, close that down, too.
+ */
+ if (vf->pr_state == vf_open)
+ uty_pipe_return_cancel(vf) ;
+} ;
+
+/*==============================================================================
+ * Error handling
+ */
+
+/*------------------------------------------------------------------------------
+ * Dealing with error of some kind.
+ *
+ * In general any error causes the vin/vout stack to be closed either
+ * completely or down to the base vin/vout. vio->err_depth contains the
+ * default depth to close back to. An I/O error in either vin_base or
+ * vout_base will set the err_depth to 0.
+ *
+ * The vio->ebuf contains all error messages collected so far for the vio,
+ * and will be output to the vout_base when the stack has been closed to
+ * that point. The vio->ebuf will then be emptied.
+ *
+ * For command and parser errors:
+ *
+ * The current vio->obuf will have an end_mark. After the end_mark will be
+ * any output generated since the start of the current command (or any
+ * out_push since then). For command errors, that output is expected to be
+ * messages associated with the error.
+ *
+ * The location of the error is written to the vio->ebuf, and then the
+ * contents of the vio->obuf are moved to the end the vio->ebuf, possibly
+ * with other diagnostic information.
+ *
+ * For I/O errors:
+ *
+ * The contents of vio->obuf are left untouched -- the closing of the
+ * stack will do what it can with those.
+ *
+ * The vio->ebuf will already contain the required error message(s). The
+ * vio->err_depth will have been set to close as far as vin_base/vout_base,
+ * or to close the vty completely.
+ *
+ * Deals with:
+ *
+ * CMD_WARNING command: general failed or not fully succeeded
+ * CMD_ERROR command: definitely failed
+ *
+ * CMD_ERR_PARSING parser: general parser error
+ * CMD_ERR_NO_MATCH parser: command/argument not recognised
+ * CMD_ERR_AMBIGUOUS parser: more than on command matches
+ * CMD_ERR_INCOMPLETE
+ *
+ * CMD_IO_ERROR I/O: error or time-out
+ *
+ * NB: does not expect to see all the possible CMD_XXX return codes (see
+ * below), but treats all as a form of error !
+ */
+static uint
+uty_cmd_failed(vty_io vio, cmd_return_code_t ret)
+{
+ ulen indent ;
+ uint depth ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Stack depth to close back to.
+ *
+ * This could be overridden by the return code type.
+ */
+ depth = vio->err_depth ;
+
+ /* Now any additional error message if required */
+ uty_cmd_get_ebuf(vio) ;
+
+ switch (ret)
+ {
+ case CMD_WARNING:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ;
+ break ;
+
+ case CMD_ERROR:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ;
+ break ;
+
+ case CMD_ERR_PARSING:
+ indent = uty_show_error_context(vio->ebuf, vio->vin) ;
+ cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ;
+ break ;
+
+ case CMD_ERR_NO_MATCH:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+ vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ;
+ break;
+
+ case CMD_ERR_AMBIGUOUS:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+ vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n");
+ break;
+
+ case CMD_ERR_INCOMPLETE:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+ vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n");
+ break;
+
+ case CMD_IO_ERROR: /* Diagnostic already posted to ebuf */
+ break ;
+
+ default:
+ zlog_err("%s: unexpected return code (%d).", __func__, (int)ret) ;
+ vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret);
+ break ;
+ } ;
+
+ /* Now stick the obuf tail onto the end of the ebuf & discard the tail of
+ * the obuf.
+ */
+ vio_fifo_copy_tail(vio->ebuf, vio->obuf) ;
+ vio_fifo_back_to_end_mark(vio->obuf, true) ;
+
+ /* Return what stack depth to close back to. */
+ return depth ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * In the given fifo, construct message giving the context in which an error
+ * has occurred.
+ *
+ * For file and pipe input (including config file), it is assumed that no
+ * command line has been reflected, so the context is given as:
+ *
+ * % on line 99 of xxxx:
+ * <the command line -- noting small indent>
+ *
+ * For interactive input, if the stack depth is 1, then it is assumed that the
+ * command line is the last complete line output. Otherwise the context is
+ * given as:
+ *
+ * % in command line:
+ * <the command line -- noting small indent>
+ *
+ * The context starts with level 1 of the vin stack, and ends with the current
+ * level.
+ *
+ * Returns: "eloc" -- start of command line at the current level.
+ */
+static uint
+uty_show_error_context(vio_fifo ebuf, vio_vf vf)
+{
+ vio_vf vf_next ;
+ uint indent ;
+
+ /* Recurse until hit end of the vin stack */
+ vf_next = ssl_next(vf, vin_next) ;
+
+ if (vf_next != NULL)
+ uty_show_error_context(ebuf, vf_next) ;
+ else
+ assert(vf == vf->vio->vin_base) ;
+
+ /* On the way back, add the error location for each vin entry
+ * and establish the location of the start of the command line as shown.
+ */
+ indent = 0 ;
+
+ switch (vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("invalid VIN_NONE") ;
+ break ;
+
+ case VIN_TERM:
+ indent = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
+ break ;
+
+ case VIN_VTYSH:
+// eloc = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
+ break ;
+
+ case VIN_FILE:
+ case VIN_PIPE:
+ case VIN_CONFIG:
+ vio_fifo_printf(ebuf, "%% on line %d of %s:\n",
+ vf->line_number, vf->name) ;
+ vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cl)) ;
+ indent = 2 ;
+ break ;
+
+ case VIN_DEV_NULL:
+ break ;
+
+ default:
+ zabort("unknown vin_type") ;
+ } ;
+
+ return indent ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If there is no vio->ebuf, make one
+ */
+extern vio_fifo
+uty_cmd_get_ebuf(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->ebuf == NULL)
+ vio->ebuf = vio_fifo_new(1000) ;
+
+ return vio->ebuf ;
+} ;
+
+/*==============================================================================
+ * Configuration node/state handling
+ *
+ * At most one VTY may hold the configuration symbol of power at any time.
+ *
+ * Only at vin_depth == 1 may the symbol of power be acquired, and only at
+ * vin_depth <= 1 will the symbol of power be released. Inter alia, this
+ * means that the restoration of command context when an input pipe finishes
+ * does not have to worry about recovering or releasing the symbol of power.
+ */
+
+/*------------------------------------------------------------------------------
+ * Attempt to gain the configuration symbol of power -- may already own it !
+ *
+ * Returns: true <=> now own the symbol of power (or already did).
+ */
+extern cmd_return_code_t
+vty_cmd_config_lock (vty vty)
+{
+ bool locked ;
+
+ VTY_LOCK() ;
+ locked = uty_cmd_config_lock(vty) ;
+ VTY_UNLOCK() ;
+
+ if (vty->config)
+ return CMD_SUCCESS ;
+
+ if (locked)
+ vty_out(vty, "VTY configuration is locked by other VTY\n") ;
+ else
+ vty_out(vty, "VTY configuration is not available\n") ;
+
+ return CMD_WARNING ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Attempt to gain the configuration symbol of power -- may already own it !
+ *
+ * NB: cannot do this at any vin level except 1 !
+ */
+static bool
+uty_cmd_config_lock (vty vty)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (!host.config) /* If nobody owns the lock... */
+ {
+ if (vty->vio->vin_depth == 1)
+ {
+ host.config = true ; /* ...rope it... */
+ vty->config = true ;
+
+ do
+ ++host.config_brand ; /* ...update the brand... */
+ while (host.config_brand == 0) ;
+
+ vty->config_brand = host.config_brand ; /* ...brand it. */
+ } ;
+ }
+ else /* Somebody owns the lock... */
+ {
+ if (vty->config) /* ...if we think it is us, check brand */
+ assert(host.config_brand == vty->config_brand) ;
+ } ;
+
+ return host.config ;
+}
+
+/*------------------------------------------------------------------------------
+ * Check that given node and ownership of configuration symbol of power...
+ * ...see below.
+ */
+extern void
+vty_cmd_config_lock_check(vty vty, node_type_t node)
+{
+ VTY_LOCK() ;
+ uty_cmd_config_lock_check(vty, node) ;
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Check that given node and ownership of configuration symbol of power
+ * are mutually consistent.
+ *
+ * If node > MAX_NON_CONFIG_NODE, must own the symbol of power (unless
+ * vio->vin_true_depth == 0, in which case the node is irrelevant).
+ *
+ * If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it,
+ * PROVIDED is at vin_true_depth <= 1 !!
+ */
+static void
+uty_cmd_config_lock_check(vty vty, node_type_t node)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vty->config)
+ {
+ /* We think we own it, so we better had */
+ qassert(host.config) ;
+ qassert(host.config_brand == vty->config_brand) ;
+
+ /* If no longer need it, release */
+ if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_true_depth <= 1))
+ {
+ host.config = false ;
+ vty->config = false ;
+ } ;
+ }
+ else
+ {
+ /* We don't think we own it, so we had better not */
+ if (host.config)
+ qassert(host.config_brand != vty->config_brand) ;
+
+ /* Also, node had better not require that we do, noting that
+ * the node is irrelevant if the vin_true_depth is 0.
+ */
+ if (vty->vio->vin_true_depth > 0)
+ qassert(node <= MAX_NON_CONFIG_NODE) ;
+ } ;
+} ;
diff --git a/lib/vty_command.h b/lib/vty_command.h
new file mode 100644
index 00000000..1f66f7a7
--- /dev/null
+++ b/lib/vty_command.h
@@ -0,0 +1,70 @@
+/* VTY for command execution
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_COMMAND_H
+#define _ZEBRA_VTY_COMMAND_H
+
+#include "misc.h"
+
+#include "command_local.h"
+#include "vty_local.h"
+#include "vty_io.h"
+
+extern bool vty_cmd_config_loop_prepare(vty vty) ;
+extern void uty_cmd_queue_loop_enter(vty_io vio) ;
+extern void uty_cmd_signal(vty_io vio, cmd_return_code_t ret) ;
+extern bool uty_cmd_loop_stop(vty_io vio, bool curtains) ;
+
+extern void vty_cmd_loop_exit(vty vty) ;
+extern void vty_cmd_set_stopped(vty vty) ;
+extern void vty_cmd_check_stop(vty vty, cmd_return_code_t ret) ;
+
+extern cmd_return_code_t vty_cmd_fetch_line(vty vty) ;
+extern cmd_return_code_t vty_cmd_hiatus(vty vty, cmd_return_code_t ret) ;
+extern cmd_return_code_t vty_cmd_special(vty vty) ;
+extern cmd_return_code_t vty_cmd_can_auth_enable(vty vty) ;
+extern cmd_return_code_t vty_cmd_reflect_line(vty vty) ;
+extern cmd_return_code_t vty_cmd_out_push(vty vty) ;
+extern cmd_return_code_t uty_cmd_out_push(vio_vf vf, bool final) ;
+
+extern cmd_return_code_t uty_cmd_open_in_pipe_file(vty_io vio,
+ cmd_context context, qstring name, cmd_pipe_type_t type) ;
+extern cmd_return_code_t uty_cmd_open_in_pipe_shell(vty_io vio,
+ cmd_context context, qstring command, cmd_pipe_type_t type) ;
+extern cmd_return_code_t uty_cmd_open_out_pipe_file(vty_io vio,
+ cmd_context context, qstring name, cmd_pipe_type_t type,
+ bool after) ;
+extern cmd_return_code_t uty_cmd_open_out_dev_null(vty_io vio, bool after) ;
+extern cmd_return_code_t uty_cmd_open_out_pipe_shell(vty_io vio,
+ cmd_context context, qstring command, cmd_pipe_type_t type,
+ bool after) ;
+extern qpath uty_cmd_path_name_complete(qpath dst, const char* name,
+ cmd_context context) ;
+
+extern cmd_return_code_t vty_cmd_success(vty vty) ;
+extern vio_fifo uty_cmd_get_ebuf(vty_io vio) ;
+
+extern cmd_return_code_t vty_cmd_config_lock (vty vty) ;
+extern void vty_cmd_config_lock_check(struct vty *vty, node_type_t node) ;
+
+#endif /* _ZEBRA_VTY_COMMAND_H */
diff --git a/lib/vty_common.h b/lib/vty_common.h
new file mode 100644
index 00000000..c9331261
--- /dev/null
+++ b/lib/vty_common.h
@@ -0,0 +1,144 @@
+/* VTY top level
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_COMMON_H
+#define _ZEBRA_VTY_COMMON_H
+
+#include "misc.h"
+#include "qstring.h"
+#include "command_common.h"
+
+/*==============================================================================
+ * These are things required by:
+ *
+ * vty.h -- which is used by all "external" code.
+ *
+ * vty_local.h -- which is used by all "internal" code on the I/O side.
+ *
+ * command_local.h -- which is used by all "internal" code on the command
+ * processing side.
+ *
+ * This allows some things not to be published to "external" code.
+ */
+
+/*==============================================================================
+ * VTY Types and the VTY structure.
+ *
+ * The "struct vty" is used extensively across the Quagga daemons, where it
+ * has two functions relating to command handling as:
+ *
+ * 1) a "handle" for output produced by commands
+ *
+ * 2) the holder of some context -- notably the current command "node" -- for
+ * command execution to use
+ *
+ * The bulk of "struct vty" is, therefore, private to vty.c et al and is
+ * factored out into the "struct vty_io" -- opaque to users of the struct vty.
+ *
+ * There is also context used when parsing and executing commands which is
+ * private to command.c et al, and is factored out into the "struct cmd_exec"
+ * -- also opaque to users of the struct vty.
+ */
+enum vty_type /* Command output */
+{
+ VTY_TERMINAL, /* a telnet terminal server */
+ VTY_SHELL_SERVER, /* a vty_shell server */
+
+ VTY_SHELL_CLIENT, /* a vty_shell client */
+
+ VTY_CONFIG_READ, /* configuration file reader */
+
+ VTY_STDOUT, /* stdout */
+ VTY_STDERR, /* stderr */
+} ;
+typedef enum vty_type vty_type_t ;
+
+/* Most of the contents of the vty structure live in two opaque structures,
+ * which are forward referenced here.
+ */
+struct vty_io ;
+struct cmd_execution ;
+
+/* All command execution functions take a vty argument, and this is it.
+ */
+typedef struct vty* vty ;
+struct vty
+{
+ vty_type_t type ;
+
+ /*----------------------------------------------------------------------
+ * The following are the context in which commands are executed.
+ *
+ * While a command has the vty in its hands, it can access and change these
+ * because they are not touched by the CLI thread until the command has
+ * completed.
+ */
+
+ /* Node status of this vty. */
+ node_type_t node ;
+
+ /* For current referencing point of interface, route-map, access-list
+ * etc...
+ *
+ * NB: this value is private to the command execution, which is assumed
+ * to all be in the one thread... so no lock required.
+ */
+ void* index ;
+
+ /* For multiple level index treatment such as key chain and key.
+ *
+ * NB: this value is private to the command execution, which is assumed
+ * to all be in the one thread... so no lock required.
+ */
+ void* index_sub ;
+
+ /* In configure mode (owner of the symbol of power) */
+ bool config ; /* we own */
+ ulong config_brand ; /* and this is our mark */
+
+ /*----------------------------------------------------------------------------
+ * The current cmd_exec environment -- used in command_execute.c et al
+ *
+ * This is accessed freely by the command handling code because there is
+ * only one thread of execution per vty -- though for some vty types (notably
+ * VTY_TERMINAL) that may be in the vty_cli_thread or in the vty_cmd_thread
+ * at different times.
+ *
+ * Where information from the vio is required, the VTY_LOCK is acquired.
+ */
+ struct cmd_exec* exec ; /* one per vty */
+
+ /*----------------------------------------------------------------------
+ * The following is used inside vty.c etc only -- under VTY_LOCK.
+ *
+ * The lock is required because the vty_cli_thread may be doing I/O and
+ * other stuff at the same time as the vty_cmd_thread is doing I/O, or at the
+ * same time as other vty are being serviced.
+ *
+ * Could have one lock per vty -- but would then need a lock for the common
+ * parts of the cli thread, so one lock keeps things relatively simple.
+ */
+ struct vty_io* vio ; /* one per vty */
+} ;
+
+#endif /* _ZEBRA_VTY_COMMON_H */
diff --git a/lib/vty_io.c b/lib/vty_io.c
index 74e32c75..81af4e5e 100644
--- a/lib/vty_io.c
+++ b/lib/vty_io.c
@@ -1,4 +1,4 @@
-/* VTY IO Functions
+/* VTY IO Functions -- top level of VTY IO hierarchy
* Copyright (C) 1997, 98 Kunihiro Ishiguro
*
* Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
@@ -20,14 +20,19 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
-
-#include "zebra.h"
+#include "misc.h"
#include "vty.h"
#include "vty_io.h"
+#include "vty_io_term.h"
+#include "vty_io_file.h"
#include "vty_cli.h"
+#include "vty_command.h"
#include "qstring.h"
#include "keystroke.h"
+#include "list_util.h"
+#include "command_parse.h"
+#include "command_execute.h"
#include "memory.h"
@@ -41,148 +46,37 @@
#include <arpa/telnet.h>
#include <sys/un.h> /* for VTYSH */
#include <sys/socket.h>
+#include <wait.h>
#define VTYSH_DEBUG 0
/*==============================================================================
- * VTY Command Output -- base functions
- *
- * During command processing the output sent here is held until the command
- * completes.
+ * Basic output to VTY.
*/
-static int uty_config_write(vty_io vio, bool all) ;
-
/*------------------------------------------------------------------------------
- * VTY output function -- cf fprintf
+ * VTY output -- cf fprintf ! Same as vty_out, less the VTY_LOCK().
*
- * Returns: >= 0 => OK
- * < 0 => failed (see errno)
- */
-extern int
-uty_out (struct vty *vty, const char *format, ...)
-{
- int result;
- VTY_ASSERT_LOCKED() ;
- va_list args;
- va_start (args, format);
- result = uty_vout(vty, format, args);
- va_end (args);
- return result;
-}
-
-/*------------------------------------------------------------------------------
- * VTY output function -- cf vfprintf
+ * This is for command output, which may later be suppressed
*
* 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)
+uty_out(vty_io vio, const char *format, ...)
{
- vty_io vio ;
- int ret ;
+ int ret ;
+ va_list args ;
VTY_ASSERT_LOCKED() ;
- vio = vty->vio ;
-
- switch (vio->type)
- {
- case VTY_STDOUT:
- case VTY_SHELL:
- ret = vprintf (format, args) ;
- break ;
-
- case VTY_STDERR:
- ret = vfprintf (stderr, format, args) ;
- break ;
-
- case VTY_CONFIG_WRITE:
- ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ;
- if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf))
- ret = uty_config_write(vio, false) ;
- break ;
-
- case VTY_TERM:
- case VTY_SHELL_SERV:
- assert(vio->cmd_in_progress) ;
-
- if (!vio->sock.write_open)
- return 0 ; /* discard output if not open ! */
-
- /* fall through.... */
-
- case VTY_CONFIG_READ:
- ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ;
- break ;
-
- default:
- zabort("impossible VTY type") ;
- } ;
+ va_start (args, format) ;
+ ret = uty_vprintf(vio, format, args) ;
+ va_end (args) ;
return ret ;
} ;
-/*------------------------------------------------------------------------------
- * Clear the contents of the command output FIFO etc.
- *
- * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc
- * flags -- competent parties must deal with those
- */
-extern void
-uty_out_clear(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
-
- vio_fifo_clear(&vio->cmd_obuf) ;
-
- if (vio->cmd_lc != NULL)
- vio_lc_clear(vio->cmd_lc) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Flush the contents of the command output FIFO to the given file.
- *
- * Takes no notice of any errors !
- */
-extern void
-uty_out_fflush(vty_io vio, FILE* file)
-{
- char* src ;
- size_t have ;
-
- VTY_ASSERT_LOCKED() ;
-
- fflush(file) ;
-
- while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL)
- {
- fwrite(src, 1, have, file) ;
- vio_fifo_got_upto(&vio->cmd_obuf, src + have) ;
- } ;
-
- fflush(file) ;
-} ;
-
/*==============================================================================
* The watch dog.
*
@@ -190,2553 +84,2100 @@ uty_out_fflush(vty_io vio, FILE* file)
*
* * for changes to the host name, which should be reflected in the
* prompt for any terminals.
- *
- * * the death watch list
*/
-enum { vty_watch_dog_interval = 5 } ;
-
-static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ;
-static int vty_watch_dog_thread(struct thread *thread) ;
+/* Watch-dog timer */
+static vio_timer_t vty_watch_dog ;
-static void uty_watch_dog_bark(void) ;
-static bool uty_death_watch_scan(void) ;
+static vty_timer_time uty_watch_dog_bark(vio_timer timer, void* info) ;
/*------------------------------------------------------------------------------
- * Start watch dog -- the first time a VTY is created.
+ * Start watch dog -- before a VTY is created.
+ *
+ * The host name is set during initialisation, so no need to check it here.
*/
extern void
-uty_watch_dog_start()
+uty_watch_dog_start(void)
{
- if (vty_cli_nexus)
- vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile,
- NULL, NULL) ;
-
- uty_watch_dog_bark() ; /* start up by barking the first time */
-}
+ vio_timer_init_new(vty_watch_dog, uty_watch_dog_bark, NULL) ;
+ vio_timer_set(vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ;
+} ;
/*------------------------------------------------------------------------------
* Stop watch dog timer -- at close down.
- *
- * Final run along the death-watch
- *
*/
extern void
uty_watch_dog_stop(void)
{
- if (vty_watch_dog.anon != NULL)
- {
- if (vty_cli_nexus)
- qtimer_free(vty_watch_dog.qnexus) ;
- else
- thread_cancel(vty_watch_dog.thread) ;
- } ;
-
- uty_death_watch_scan() ; /* scan the death-watch list */
-}
-
-/*------------------------------------------------------------------------------
- * qnexus watch dog action
- */
-static void
-vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when)
-{
- VTY_LOCK() ;
-
- uty_watch_dog_bark() ;
-
- VTY_UNLOCK() ;
-} ;
-
-/*------------------------------------------------------------------------------
- * thread watch dog action
- */
-static int
-vty_watch_dog_thread(struct thread *thread)
-{
- VTY_LOCK() ;
-
- vty_watch_dog.thread = NULL ;
- uty_watch_dog_bark() ;
-
- VTY_UNLOCK() ;
- return 0 ;
+ vio_timer_reset(vty_watch_dog, keep_it) ;
} ;
/*------------------------------------------------------------------------------
- * Watch dog action
+ * Watch dog vio_timer action
*/
-static void
-uty_watch_dog_bark(void)
+static vty_timer_time
+uty_watch_dog_bark(vio_timer timer, void* info)
{
- 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. */
+ cmd_host_name(true) ; /* check for host name change */
- sdl_del(vio_death_watch, vio, vio_list) ;
-
- XFREE(MTYPE_VTY, vio->vty) ;
- XFREE(MTYPE_VTY, vio) ;
- } ;
- } ;
-
- return (vio_death_watch == NULL) ;
+ return VTY_WATCH_DOG_INTERVAL ;
} ;
/*==============================================================================
* Prototypes.
*/
-static void uty_sock_init_new(vio_sock sock, int fd, void* info) ;
-static void uty_sock_half_close(vio_sock sock) ;
-static void uty_sock_close(vio_sock sock) ;
-
-static void vty_read_qnexus (qps_file qf, void* file_info) ;
-static void vty_write_qnexus (qps_file qf, void* file_info) ;
-static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ;
-
-static int vty_read_thread (struct thread *thread) ;
-static int vty_write_thread (struct thread *thread) ;
-static int vty_timer_thread (struct thread *thread) ;
-
-static void vtysh_read_qnexus (qps_file qf, void* file_info) ;
-static int vtysh_read_thread (struct thread *thread) ;
-
-static enum vty_readiness uty_write(vty_io vio) ;
+static void uty_vout_close_reason(vio_vf vf, const char* reason) ;
+static cmd_return_code_t uty_vf_read_close(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) ;
+static vio_vf uty_vf_free(vio_vf vf) ;
/*==============================================================================
* Creation and destruction of VTY objects
*/
/*------------------------------------------------------------------------------
- * Allocate new vty struct
+ * Allocate new vty structure, including empty vty_io but no exec structure.
+ *
+ * Sets:
+ *
+ * * vio->err_depth = 1 if VTY_TERMINAL
+ * = 0 otherwise
*
- * Allocates and initialises basic vty and vty_io structures, setting the
- * given type.
+ * So on everthing except VTY_TERMINAL, any error
+ * will close the entire VTY. On VTY_TERMINAL, an
+ * error will close down to the vin_base/vout_base
+ * (unless error is in one of those).
*
- * Note that where is not setting up a vty_sock, this *may* be called from
- * any thread.
+ * * vio->blocking = true if VTY_CONFIG_READ
+ * = false otherwise
*
- * NB: may not create a VTY_CONFIG_WRITE type vty directly
+ * Caller must complete the initialisation of the vty_io, which means:
*
- * see: vty_open_config_write() and vty_close_config_write()
+ * * constructing a suitable vio_vf and doing uty_vin_push() to set the
+ * vin_base.
*
- * 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 !)
+ * All vty_io MUST have a vin_base, even if it is /dev/null.
*
- * the sock_fd is ignored for everything else.
+ * * constructing a suitable vio_vf and doing uty_vout_push() to set the
+ * vout_base and the vio->obuf.
*
- * Returns: new vty
+ * All vty_io MUST have a vout_base, even if it is /dev/null.
+ *
+ * * setting vio->cli, if required
+ *
+ * * etc.
+ *
+ * "exec" is allocated only when the command loop is entered.
*/
-extern struct vty *
-uty_new(enum vty_type type, int sock_fd)
+extern vty
+uty_new(vty_type_t type, node_type_t node)
{
- struct vty *vty ;
- struct vty_io* vio ;
+ vty vty ;
+ vty_io vio ;
VTY_ASSERT_LOCKED() ;
- /* If this is a VTY_TERM or a VTY_SHELL, place */
- switch (type)
- {
- case VTY_TERM: /* Require fd -- Telnet session */
- case VTY_SHELL_SERV: /* Require fd -- Unix socket */
- assert(sock_fd >= 0) ;
- break ;
-
- case VTY_CONFIG_WRITE:
- zabort("may not make a new VTY_CONFIG_WRITE VTY") ;
- break ;
-
- case VTY_CONFIG_READ:
- case VTY_STDOUT:
- case VTY_STDERR:
- case VTY_SHELL:
- sock_fd = -1 ; /* No fd -- output to stdout/stderr */
- break ;
-
- default:
- zabort("unknown VTY type") ;
- } ;
-
/* Basic allocation */
- vty = XCALLOC (MTYPE_VTY, sizeof (struct vty));
- vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ;
- vty->vio = vio ;
- vio->vty = vty ;
-
- /* Zeroising the vty_io structure has set:
- *
- * name = NULL -- no name, yet
- *
- * vio_list both pointers NULL
- * mon_list both pointers NULL
- *
- * half_closed = 0 -- NOT half closed (important !)
- * closed = 0 -- NOT closed (important !)
- * close_reason = NULL -- no reason, yet
- *
- * real_type = 0 -- not material
- * file_fd = 0 -- not material
- * file_error = 0 -- not material
- *
- * key_stream = NULL -- no key stream (always empty, at EOF)
+ /* Zeroising the vty structure will set:
*
- * cli_drawn = 0 -- not drawn
- * cli_dirty = 0 -- not dirty
- * cli_prompt_len = 0 )
- * cli_extra_len = 0 ) not material
- * cli_echo_suppress = 0 )
+ * type = X -- set to actual type, below
*
- * cli_prompt_node = 0 -- not material
- * cli_prompt_set = 0 -- so prompt needs to be constructed
+ * node = X -- set to actual node, below
*
- * 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--"
+ * index = NULL -- nothing, yet
+ * index_sub = NULL -- nothing, yet
*
- * cli_more_enabled = 0 -- not enabled for "--more--"
+ * config = false -- not owner of configuration symbol of power
+ * config_brand = 0 -- none, yet
*
- * cmd_out_done = 0 -- not material
+ * exec = NULL -- execution state set up when required
+ * vio = X -- set below
+ */
+ vty = XCALLOC(MTYPE_VTY, sizeof(struct vty)) ;
+
+ vty->type = type ;
+ vty->node = node ;
+
+ /* Zeroising the vty_io structure will set:
*
- * cli_do = 0 == cli_do_nothing
+ * vty = X -- set to point to parent vty, below
+ * vio_list = NULLs -- not on the vio_list, yet
*
- * cmd_lc = NULL -- no line control
+ * vin = NULL -- empty input stack
+ * vin_base = NULL -- empty input stack
+ * vin_depth = 0 -- no stacked vin's, yet
+ * vin_true_depth = 0 -- ditto
*
- * fail = 0 -- no login failures yet
+ * vout = NULL -- empty output stack
+ * vout_base = NULL -- empty output stack
+ * vout_depth = 0 -- no stacked vout's, yet
*
- * hist = empty vector
- * hp = 0 -- at the beginning
- * hindex = 0 -- the beginning
+ * ebuf = NULL -- no error at all, yet
+ * err_depth = 0 -- see below: zero unless VTY_TERMINAL
*
- * width = 0 -- unknown console width
- * height = 0 -- unknown console height
+ * blocking = false -- set below: false unless VTY_CONFIG_READ
+ * state = vc_stopped -- not started vty command loop
+ * signal = CMD_SUCCESS -- OK (null signal)
+ * close_reason = NULL -- none set
*
- * lines = 0 -- no limit
- * lines_set = 0 -- no explicit setting
+ * ps_buf = NULL -- no pipe stderr return buffer, yet
*
- * monitor = 0 -- not a monitor
- * monitor_busy = 0 -- not a busy monitor
+ * obuf = NULL -- no output buffer, yet
*
- * config = 0 -- not holder of "config" mode
+ * mon_list = NULLs -- not on the monitors list
+ * monitor = false -- not a monitor
+ * mon_kick = false
+ * maxlvl = 0
+ * mbuf = NULL
*/
- confirm(cli_do_nothing == 0) ;
- confirm(AUTH_NODE == 0) ; /* default node type */
+ vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ;
- vio->type = type ;
+ confirm(vc_stopped == 0) ;
+ confirm(CMD_SUCCESS == 0) ;
- /* 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" ;
-
- /* 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) ;
+ vty->vio = vio ;
+ vio->vty = vty ;
- vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */
+ vio->err_depth = (type == VTY_TERMINAL) ? 1 : 0 ;
- vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ;
+ vio->blocking = (type == VTY_CONFIG_READ) ;
/* Place on list of known vio/vty */
- sdl_push(vio_list_base, vio, vio_list) ;
-
- return vty;
-} ;
-
-/*------------------------------------------------------------------------------
- * Create new vty of type VTY_TERM -- ie attached to a telnet session.
- *
- * Returns: new vty
- */
-static struct vty *
-uty_new_term(int sock_fd, union sockunion *su)
-{
- struct vty *vty ;
- vty_io vio ;
- enum vty_readiness ready ;
-
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
-
- /* Allocate new vty structure and set up default values. */
- vty = uty_new (VTY_TERM, sock_fd) ;
- vio = vty->vio ;
-
- /* Allocate and initialise a keystroke stream TODO: CSI ?? */
- vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ;
-
- /* Set the socket action functions */
- if (vty_cli_nexus)
- {
- vio->sock.action.read.qnexus = vty_read_qnexus ;
- vio->sock.action.write.qnexus = vty_write_qnexus ;
- vio->sock.action.timer.qnexus = vty_timer_qnexus ;
- }
- else
- {
- vio->sock.action.read.thread = vty_read_thread ;
- vio->sock.action.write.thread = vty_write_thread ;
- vio->sock.action.timer.thread = vty_timer_thread ;
- } ;
-
- /* The text form of the address identifies the VTY */
- vio->name = sockunion_su2str (su, MTYPE_VTY_NAME);
-
- /* Set the initial node */
- if (no_password_check)
- {
- if (restricted_mode)
- vty->node = RESTRICTED_NODE;
- else if (host.advanced)
- vty->node = ENABLE_NODE;
- else
- vty->node = VIEW_NODE;
- }
- else
- vty->node = AUTH_NODE;
-
- /* Pick up current timeout setting */
- vio->sock.v_timeout = vty_timeout_val;
-
- /* Use global 'lines' setting, as default. May be -1 => unset */
- vio->lines = host.lines ;
-
- /* For VTY_TERM use vio_line_control for '\n' and "--more--" */
- vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ;
- uty_set_height(vio) ; /* set initial state */
-
- /* Initialise the CLI, ready for start-up messages etc. */
- uty_cli_init(vio) ;
-
- /* Reject connection if password isn't set, and not "no password" */
- if ((host.password == NULL) && (host.password_encrypt == NULL)
- && ! no_password_check)
- {
- uty_half_close (vio, "Vty password is not set.");
- vty = NULL;
- }
- else
- {
- /* Say hello to the world. */
- vty_hello (vty);
-
- if (! no_password_check)
- uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE);
- } ;
-
- /* Now start the CLI and set a suitable state of readiness */
- ready = uty_cli_start(vio) ;
- uty_sock_set_readiness(&vio->sock, ready) ;
+ sdl_push(vio_live_list, vio, vio_list) ;
return vty;
} ;
/*------------------------------------------------------------------------------
- * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session.
+ * Set timeout value.
*
- * Returns: new vty
+ * This is only ever called when a command (eg exec-timeout) sets a new
+ * time out value -- which applies only to VIN_TERM and VTY_VTYSH.
*/
-static struct vty *
-uty_new_shell_serv(int sock_fd)
+extern void
+uty_set_timeout(vty_io vio, vty_timer_time timeout)
{
- struct vty *vty ;
- vty_io vio ;
+ vio_in_type_t vt ;
VTY_ASSERT_LOCKED() ;
- /* Allocate new vty structure and set up default values. */
- vty = uty_new (VTY_SHELL_SERV, sock_fd) ;
- vio = vty->vio ;
-
- /* Set the action functions */
- if (vty_cli_nexus)
- {
- vio->sock.action.read.qnexus = vtysh_read_qnexus ;
- vio->sock.action.write.qnexus = vty_write_qnexus ;
- vio->sock.action.timer.qnexus = NULL ;
- }
- else
- {
- vio->sock.action.read.thread = vtysh_read_thread ;
- vio->sock.action.write.thread = vty_write_thread ;
- vio->sock.action.timer.thread = NULL ;
- } ;
-
- vty->node = VIEW_NODE;
-
- /* Kick start the CLI etc. */
- uty_sock_set_readiness(&vio->sock, write_ready) ;
-
- return vty;
-} ;
-
-/*------------------------------------------------------------------------------
- * Set/Clear "monitor" state:
- *
- * set: if VTY_TERM and not already "monitor" (and write_open !)
- * clear: if is "monitor"
- */
-extern void
-uty_set_monitor(vty_io vio, bool on)
-{
- VTY_ASSERT_LOCKED() ;
+ vt = vio->vin_base->vin_type ;
- if (on && !vio->monitor)
- {
- if ((vio->type == VTY_TERM) && vio->sock.write_open)
- {
- vio->monitor = 1 ;
- sdl_push(vio_monitors_base, vio, mon_list) ;
- } ;
- }
- else if (!on && vio->monitor)
- {
- vio->monitor = 0 ;
- sdl_del(vio_monitors_base, vio, mon_list) ;
- }
+ if ((vt == VIN_TERM) || (vt == VIN_VTYSH))
+ uty_vf_set_read_timeout(vio->vin_base, timeout) ;
} ;
/*------------------------------------------------------------------------------
- * Return "name" of VTY
+ * Return "name" of VTY.
*
- * For VTY_TERM this is the IP address of the far end of the telnet connection.
+ * The name of the base vin, or (failing that) the base vout.
*/
extern const char*
uty_get_name(vty_io vio)
{
- return (vio->name != NULL) ? vio->name : "?" ;
+ const char* name ;
+
+ name = vio->vin_base->name ;
+ if (name == NULL)
+ name = vio->vout_base->name ;
+
+ return (name != NULL) ? name : "?" ;
} ;
/*------------------------------------------------------------------------------
- * Closing down VTY for reading.
+ * Close VTY -- final.
*
- * For VTY_TERM (must be in CLI thread):
+ * Turns off any log monitoring immediately.
*
- * * shut the socket for reading
- * * discard all buffered input, setting it to "EOF"
- * * turns off any monitor status !
- * * drop down to RESTRICTED_NODE
+ * Close "final" means: terminate any input immediately, but attempt to flush
+ * any pending output (and any pipe return) but give up if would block or gets
+ * any error.
*
- * For VTY_SHELL_SERV (must be in CLI thread):
+ * To call this the command loop must be vc_stopped. When a command loop exits
+ * normally, it completes all pending I/O and closes the stacks down to, but
+ * not including vout_base. So there is not much to do here. When a vty is
+ * reset, once the command loop has been stopped, this function will close
+ * everything.
*
- * * shut the socket for reading
- * * discard all buffered input
- * * drop down to RESTRICTED_NODE
+ * Once the vio stacks are all closed, except for the vout_base, makes some
+ * effort to issue any "close reason" message, then closes the vout_base.
*
- * In all cases:
+ * The vty can then be closed.
*
- * * place on death watch
- * * set the vty half_closed
- * * sets the reason for closing (if any given)
+ * NB: releases the vty, the vio, the exec and all related objects.
*
- * 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.)
+ * On return DO NOT attempt to do anything with the one-time VTY.
*/
extern void
-uty_half_close (vty_io vio, const char* reason)
+uty_close(vty_io vio)
{
- VTY_ASSERT_LOCKED() ;
+ vty vty = vio->vty ;
- if (vio->half_closed)
- return ;
+ VTY_ASSERT_CAN_CLOSE(vio->vty) ;
- if (reason != NULL)
- vio->close_reason = reason ;
+ qassert(vio->state == vc_stopped) ;
- /* Do the file side of things
- *
- * Note that half closing the file sets a new timeout, sets read off
- * and write on.
+ /* The vio is about to be closed, so take off the live list and make
+ * sure that is not a log monitor.
*/
- uty_sock_half_close(&vio->sock) ;
- uty_set_monitor(vio, 0) ;
+ uty_set_monitor(vio, off) ;
+ sdl_del(vio_live_list, vio, vio_list) ;
- /* Discard everything in the keystroke stream and force it to EOF */
- if (vio->key_stream != NULL)
- keystroke_stream_set_eof(vio->key_stream) ;
-
- /* Turn off "--more--" so that all output clears without interruption.
+ /* Close all vin including the vin_base.
*
- * If is sitting on a "--more--" prompt, then exit the wait_more CLI.
+ * Note that the vin_base is closed, but is still on the vin stack.
*/
- vio->cli_more_enabled = 0 ;
-
- if (vio->cli_more_wait)
- uty_cli_exit_more_wait(vio) ;
+ do
+ uty_vin_pop(vio, NULL, true) ; /* final close, discard context */
+ while (vio->vin != vio->vin_base) ;
- /* 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.
+ /* Close all the vout excluding the vout_base.
*/
- if (!vio->cmd_in_progress)
- vio->cmd_out_enabled = 1 ;
+ while (vio->vout != vio->vout_base)
+ uty_vout_pop(vio, true) ; /* final close */
- /* Make sure no longer holding the config symbol of power */
- uty_config_unlock(vio->vty, RESTRICTED_NODE) ;
+ /* If the vout_base is not closed, try to output the close reason,
+ * if any -- note that this will attempt to output, even if some
+ * earlier output has failed.
+ */
+ uty_vout_close_reason(vio->vout_base, vio->close_reason) ;
- /* Log closing of VTY_TERM */
- if (vio->type == VTY_TERM)
- uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ;
+ /* Now final close the vout_base.
+ */
+ uty_vout_pop(vio, true) ; /* final close */
- /* Move to the death watch list */
- sdl_del(vio_list_base, vio, vio_list) ;
- sdl_push(vio_death_watch, vio, vio_list) ;
+ /* All should now be very quiet indeed. */
+ if (vty_debug)
+ {
+ assert(vio->vin == vio->vin_base) ;
+ assert(vio->vin_depth == 0) ;
+ assert(vio->vin_true_depth == 0) ;
- vio->half_closed = 1 ;
-} ;
+ assert(vio->vout == vio->vout_base) ;
+ assert(vio->vout_depth == 0) ;
-/*------------------------------------------------------------------------------
- * Closing down VTY.
- *
- * Shuts down everything and discards all buffers etc. etc.
- *
- * If cmd_in_progress, cannot complete the process -- but sets the closed
- * flag.
- *
- * Can call vty_close() any number of times.
- *
- * The vty structure is placed on death watch, which will finally free the
- * structure once no longer cmd_in_progress.
- */
-extern void
-uty_close (vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
+ assert(vio->vin->vin_state == vf_closed) ;
+ assert(vio->vout->vout_state == vf_closed) ;
- /* Empty all the output buffers */
- vio_fifo_reset_keep(&vio->cli_obuf) ;
- vio_fifo_reset_keep(&vio->cmd_obuf) ;
- vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ;
-
- /* If not already closed, close. */
- if (!vio->closed)
- {
- uty_half_close(vio, NULL) ; /* place on death watch -- if not
- already done */
- if (vio->type == VTY_TERM)
- uty_cli_close(vio) ; /* tell the CLI to stop */
+ assert(vty->vio == vio) ;
+ } ;
- vio->closed = 1 ; /* now closed (stop uty_write()
- from recursing) */
+ /* Release what remains of the vio. */
+ vio->vty = NULL ; /* no longer required. */
- if (vio->sock.write_open)
- uty_write(vio) ; /* last gasp attempt */
+ vio->ebuf = vio_fifo_free(vio->ebuf) ;
+ vio->obuf = NULL ; /* about to discard the vout_base */
+ vio->ps_buf = vio_fifo_free(vio->ps_buf) ;
- uty_sock_close(&vio->sock) ;
+ XFREE(MTYPE_TMP, vio->close_reason) ;
- } ;
+ if (vio->vin != vio->vout)
+ vio->vin = uty_vf_free(vio->vin) ;
+ else
+ vio->vin = NULL ;
- /* 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->vout = uty_vf_free(vio->vout) ;
- vio->key_stream = keystroke_stream_free(vio->key_stream) ;
+ vio->vin_base = NULL ;
+ vio->vout_base = NULL ;
- qs_free_body(&vio->cli_prompt_for_node) ;
- qs_free_body(&vio->cl) ;
+ assert(!vio->monitor) ;
+ vio->mbuf = vio_fifo_free(vio->mbuf) ;
- {
- qstring line ;
- while ((line = vector_ream_keep(&vio->hist)) != NULL)
- qs_reset_free(line) ;
- } ;
+ /* Release the vio and the exec (if any) */
+ XFREE(MTYPE_VTY, vty->vio) ;
+ vty->exec = cmd_exec_free(vty->exec) ;
- /* 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 ;
- } ;
+ /* Finally, release the VTY itself. */
+ XFREE(MTYPE_VTY, vty) ;
} ;
/*==============================================================================
- * For writing configuration file by command, temporarily redirect output to
- * an actual file.
- */
-
-/*------------------------------------------------------------------------------
- * Set the given fd as the VTY_FILE output.
+ * Pushing and popping vf objects on the vio->vin_stack and vio->vout_stack.
*/
-extern void
-vty_open_config_write(struct vty* vty, int fd)
-{
- vty_io vio ;
-
- VTY_LOCK() ;
-
- vio = vty->vio ;
-
- assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ;
-
- vio->real_type = vio->type ;
-
- vio->type = VTY_CONFIG_WRITE ;
- vio->file_fd = fd ;
- vio->file_error = 0 ;
-
- VTY_UNLOCK() ;
-} ;
/*------------------------------------------------------------------------------
- * Write away configuration file stuff -- all or just the full lump(s).
+ * Add a new vf to the vio->vin stack, and set read stuff.
+ *
+ * Sets the given vf->vin_type and set vf->vin_state = vf_open.
+ *
+ * Initialises an input buffer if required, and sets line_complete and
+ * line_step so that first attempt to fetch a line will give line 1.
*
- * Returns: > 0 => blocked
- * 0 => all gone (up to last lump if !all)
- * < 0 => failed -- see vio->file_error
+ * Sets the read ready action and the read timer timeout action, if required.
+ * If the vf is "blocking", then these actions are not required. Also,
+ * if the vin_type is one of the "specials", then these actions are not
+ * required -- noting that the vin_type may be "special" while the vout_type
+ * is a non-blocking ordinary output.
+ *
+ * NB: is usually called from the cli thread, but may be called from the cmd
+ * thread for vf which is blocking, or for a "special" vin_type !
+ *
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
+ *
+ * NB: see uty_vout_push() for notes on pushing a vin and a vout together.
*/
-static int
-uty_config_write(vty_io vio, bool all)
+extern void
+uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
+ vio_vfd_action* read_action,
+ vio_timer_action* read_timer_action,
+ usize ibuf_size)
{
- int ret ;
+ vf->vin_type = type ;
+ vf->vin_state = vf_open ;
- VTY_ASSERT_LOCKED() ;
+ qassert(type != VIN_NONE) ;
- if (vio->file_error == 0)
+ if ((!vf->blocking) && (vf->vin_type < VIN_SPECIALS))
{
- ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ;
-
- if (ret < 0)
- vio->file_error = errno ;
- }
- else
- ret = -1 ;
-
- return ret ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Write away any pending stuff, and return the VTY to normal.
- */
-extern int
-vty_close_config_write(struct vty* vty)
-{
- vty_io vio ;
- int err ;
-
- VTY_LOCK() ;
-
- vio = vty->vio ;
-
- assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ;
-
- uty_config_write(vio, true) ; /* write all that is left */
-
- err = vio->file_error ;
+ vio_vfd_set_read_action(vf->vfd, read_action) ;
+ vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ;
+ } ;
- vio->type = vio->real_type ;
- vio->file_fd = -1 ;
- vio->file_error = 0 ;
+ ssl_push(vio->vin, vf, vin_next) ;
+ vio->vin_true_depth = ++vio->vin_depth ;
- VTY_UNLOCK() ;
+ if (vio->vin_base == NULL)
+ {
+ qassert(vio->vin_depth == 1) ;
+ vio->vin_base = vf ;
+ } ;
- return err ;
+ if (ibuf_size != 0)
+ {
+ vf->ibuf = vio_fifo_new(ibuf_size) ;
+ vf->cl = qs_new(150) ;
+ vf->line_complete = true ;
+ vf->line_step = 1 ;
+ } ;
} ;
-/*==============================================================================
- * vio_sock level operations
- */
-
/*------------------------------------------------------------------------------
- * Initialise a new vio_sock structure.
+ * Save the given context in the current top of the vin stack.
+ *
+ * This is done when a new pipe is opened, so that:
*
- * Requires that: the vio_sock structure is not currently in use.
+ * a) saves the current context in the current vin (the new pipe has
+ * not yet been pushed) so that uty_vin_pop() can restore this context
+ * after closing the then top of the stack.
*
- * if fd >= 0 then: sock is open and ready read and write
- * otherwise: sock is not open
+ * b) can update context for about to be run vin, eg:
*
- * there are no errors, yet.
+ * - dir_here -- if required
*
- * Sets timeout to no timeout at all -- timeout is optional.
+ * - can_enable
*
- * NB: MUST be in the CLI thread if the fd is >= 0 !
+ * So the top of the vin stack does not contain the current context, that is
+ * in the vty->exec !
*/
-static void
-uty_sock_init_new(vio_sock sock, int fd, void* info)
+extern void
+uty_vin_new_context(vty_io vio, cmd_context context, qpath file_here)
{
- VTY_ASSERT_LOCKED() ;
-
- if (fd >= 0)
- VTY_ASSERT_CLI_THREAD() ;
-
- memset(sock, 0, sizeof(struct vio_sock)) ;
-
- /* Zeroising the structure has set:
- *
- * action = all the actions set NULL
- *
- * error_seen = 0 -- no error, yet
- *
- * qf = NULL -- no qfile, yet
- * t_read = NULL ) no threads, yet
- * t_write = NULL )
- *
- * v_timeout = 0 -- no timeout set
- * timer_runing = 0 -- not running, yet
- * t_timer = NULL -- no timer thread, yet
- * qtr = NULL -- no qtimer, yet
- */
- sock->fd = fd ;
- sock->info = info ;
-
- sock->read_open = (fd >= 0) ;
- sock->write_open = (fd >= 0) ;
-
- if ((fd >= 0) && vty_cli_nexus)
- {
- sock->qf = qps_file_init_new(NULL, NULL);
- qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info);
- } ;
+ qassert(vio->vin->context == NULL) ;
+ vio->vin->context = cmd_context_new_save(context, file_here) ;
} ;
/*------------------------------------------------------------------------------
- * Restart the timer.
+ * Push a new vf to the vio->vout stack, and set write stuff.
+ *
+ * Sets the vf->vout_type and set vf->vout_state = vf_open.
+ *
+ * Sets the write ready action and the write timer timeout action, if required.
+ * If the vf is "blocking", then these actions are not required. Also,
+ * if the vout_type is one of the "specials", then these actions are not
+ * required -- noting that the vout_type may be "special" while the vin_type
+ * is a non-blocking ordinary input.
+ *
+ * Initialises an output buffer and sets an end_mark.
+ *
+ * The new vout_depth depends on whether a vin and vout are being opened
+ * together, and if so, in what order they are pushed:
+ *
+ * * for a vout being opened on its own (e.g. VOUT_CONFIG_WRITE) the
+ * vout_depth is set to the current vin_depth + 1. This means that
+ * when the current command completes, vout_depth > vin_depth, so the
+ * vout will automatically be closed.
+ *
+ * * for a vout being opened at the same time as a vin, the vout_depth
+ * is set to the same as the *new* vin_depth. This means that the
+ * vout will not be closed until the vin is.
+ *
+ * If the vout is opened before the vin, the new vout_depth will be
+ * vin_depth + 1.
+ *
+ * If the vout is opened after the vin, the new vout_depth will be
+ * vin_depth.
+ *
+ * ...hence the "after" argument.
+ *
+ * NB: is usually called from the cli thread, but may be called from the cmd
+ * thread for vf which is blocking, or for a "special" vout_type !
*
- * If a timeout time is set, then start or restart the timer with that value.
+ * NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special.
*
- * If no timeout time is set, and the timer is running, unset it.
+ * The write_action and the write_timer_action are ignored.
+ *
+ * All actual I/O to these outputs is direct, blocking and via standard
+ * I/O -- except VOUT_DEV_NULL where all I/O is discarded.
+ *
+ * NB: all outputs are set up with an obuf, so all output is collected, even
+ * if it is later to be discarded.
+ *
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
*/
-static void
-uty_sock_restart_timer(vio_sock sock)
+extern void
+uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type,
+ vio_vfd_action* write_action,
+ vio_timer_action* write_timer_action,
+ usize obuf_size,
+ bool after)
{
VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
- if (sock->v_timeout != 0)
- {
- assert(sock->action.timer.anon != NULL) ;
+ vf->vout_type = type ;
+ vf->vout_state = vf_open ;
- if (vty_cli_nexus)
- {
- if (sock->qtr == NULL) /* allocate qtr if required */
- sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile,
- NULL, sock->info) ;
- qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)),
- sock->action.timer.qnexus) ;
- }
- else
- {
- if (sock->t_timer != NULL)
- thread_cancel (sock->t_timer);
- sock->t_timer = thread_add_timer (vty_master,
- sock->action.timer.thread, sock->info, sock->v_timeout) ;
- } ;
+ qassert(type != VOUT_NONE) ;
- sock->timer_running = 1 ;
- }
- else if (sock->timer_running)
+ if ((!vf->blocking) && (vf->vout_type < VOUT_SPECIALS))
{
- if (vty_cli_nexus)
- {
- if (sock->qtr != NULL)
- qtimer_unset(sock->qtr) ;
- }
- else
- {
- if (sock->t_timer != NULL)
- thread_cancel (sock->t_timer) ;
- } ;
+ vio_vfd_set_write_action(vf->vfd, write_action) ;
+ vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ;
+ } ;
- sock->timer_running = 0 ;
+ ssl_push(vio->vout, vf, vout_next) ;
+ if (vio->vout_base == NULL)
+ {
+ qassert(vio->vout_depth == 0) ;
+ vio->vout_base = vf ;
} ;
-} ;
-/*------------------------------------------------------------------------------
- * Set read on/off
- *
- * Returns: the on/off state set
- */
-static bool
-uty_sock_set_read(vio_sock sock, bool on)
-{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ vf->obuf = vio_fifo_new(obuf_size) ;
+ vio_fifo_set_end_mark(vf->obuf) ;
- if (sock->fd < 0)
- return 0 ;
+ vf->depth_mark = vio->vout_depth ; /* remember *old* depth */
- if (on)
+ if (after)
{
- assert(sock->action.read.anon != NULL) ;
-
- if (vty_cli_nexus)
- qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ;
- else
- {
- if (sock->t_read != NULL)
- thread_cancel(sock->t_read) ;
-
- sock->t_read = thread_add_read(vty_master,
- sock->action.read.thread, sock->info, sock->fd) ;
- } ;
+ qassert(vio->vout_depth < vio->vin_depth) ;
+ vio->vout_depth = vio->vin_depth ; /* set new depth */
}
else
{
- if (vty_cli_nexus)
- qps_disable_modes(sock->qf, qps_read_mbit) ;
- else
- {
- if (sock->t_read != NULL)
- thread_cancel (sock->t_read) ;
- } ;
+ qassert(vio->vout_depth <= vio->vin_depth) ;
+ vio->vout_depth = vio->vin_depth + 1 ; /* set new depth */
} ;
- return on ;
+ vio->obuf = vf->obuf ;
} ;
/*------------------------------------------------------------------------------
- * Set write on/off
+ * Close top of the vin stack and pop when done -- see uty_vf_read_close().
+ *
+ * If succeeds in closing, unless is vin_base, pops the vin stack and if this
+ * is read-only will free the vio_vf and all its contents. (So if this is
+ * vin_base, it is left on the stack, but vf_closed/vf_closing.)
+ *
+ * If the given context is not NULL, having popped the vin stack, the context
+ * in the new top of stack is restored to the given context.
+ *
+ * On final close, will not wait for I/O, will completely close the input,
+ * even if errors occur (and no errors are posted) and will return CMD_SUCCESS.
+ *
+ * Returns: CMD_SUCCESS -- input completely closed and popped
+ * CMD_WAITING -- waiting for input to close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*
- * Returns: the on/off state set
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
*/
-static bool
-uty_sock_set_write(vio_sock sock, bool on)
+extern cmd_return_code_t
+uty_vin_pop(vty_io vio, cmd_context context, bool final)
{
+ cmd_return_code_t ret ;
+
VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
- if (sock->fd < 0)
- return 0 ;
+ ret = uty_vf_read_close(vio->vin, final) ;
- if (on)
+ if ((ret == CMD_SUCCESS) || final)
{
- assert(sock->action.write.anon != NULL) ;
+ assert(vio->vin->vin_state == vf_closed) ;
- if (vty_cli_nexus)
- qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ;
- else
+ if (vio->vin_depth > 1)
{
- if (sock->t_write != NULL)
- thread_cancel(sock->t_write) ;
+ vio_vf vf ;
- sock->t_write = thread_add_write(vty_master,
- sock->action.write.thread, sock->info, sock->fd) ;
- } ;
- }
- else
- {
- if (vty_cli_nexus)
- qps_disable_modes(sock->qf, qps_write_mbit) ;
+ vf = ssl_pop(&vf, vio->vin, vin_next) ;
+ --vio->vin_depth ;
+
+ if (vf->vout_state == vf_closed)
+ uty_vf_free(vf) ;
+ }
else
{
- if (sock->t_write != NULL)
- thread_cancel (sock->t_write) ;
+ assert(vio->vin == vio->vin_base) ;
+ vio->vin_depth = 0 ; /* may already have been closed */
+ } ;
+
+ if (vio->vin_true_depth > vio->vin_depth)
+ vio->vin_true_depth = vio->vin_depth ;
+
+ if (vio->vin->context != NULL)
+ {
+ if (context != NULL)
+ vio->vin->context = cmd_context_restore(context, vio->vin->context);
+ else
+ vio->vin->context = cmd_context_free(vio->vin->context, false) ;
+ /* Not a copy */
} ;
} ;
- return on ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Set read/write readiness -- for VTY_TERM
+ * Close top of the vout stack and pop when done -- see uty_vf_write_close().
+ *
+ * If is vout_base, does not completely close, unless is "final" -- if not
+ * final, CMD_SUCCESS means that the output buffers are empty and the
+ * vout_depth has been reduced, but the vio_vf is still writeable, held in
+ * vf_closing state.
+ *
+ * Unless is vout_base, pops the vout stack. If this is write-only will free
+ * the vio_vf and all its contents.
+ *
+ * If this is vout_base, does not actually close the vfd and does not close
+ * the vout side of the vf. This leaves an active vio->obuf (inter alia.)
+ *
+ * Before closing, discard anything after end_mark, then push any outstanding
+ * output.
+ *
+ * Unless "final", the close is soft, that is, if there is any output still
+ * outstanding does not close the vout.
+ *
+ * If there is no outstanding output (or if final) will completely close the
+ * vf and free it (except for vout_base).
*
- * Note that for VTY_TERM, set only one of read or write, and sets write for
- * preference.
+ * Returns: CMD_SUCCESS -- output completely closed and popped
+ * CMD_WAITING -- waiting for input to close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
+ *
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
+ *
+ * That is not done here because this may be called from outside the
+ * command loop -- in particular by uty_close().
+ *
+ * However, ensures that vio->obuf is up to date !
*/
-extern void
-uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready)
+extern cmd_return_code_t
+uty_vout_pop(vty_io vio, bool final)
{
+ cmd_return_code_t ret ;
+
VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
- uty_sock_set_read(sock, (ready == read_ready)) ;
- uty_sock_set_write(sock, (ready >= write_ready)) ;
-} ;
+ ret = uty_vf_write_close(vio->vout, final) ;
-/*------------------------------------------------------------------------------
- * Set a new timer value.
- */
-extern void
-uty_sock_set_timer(vio_sock sock, unsigned long timeout)
-{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
- sock->v_timeout = timeout ;
- if (sock->timer_running)
- uty_sock_restart_timer(sock) ;
-} ;
+ if (vio->vout_depth > 1)
+ {
+ vio_vf vf ;
-/*------------------------------------------------------------------------------
- * Close given vty sock for reading.
- *
- * Sets timer to timeout for clearing any pending output.
- *
- * NB: if there is a socket, MUST be in the CLI thread
- */
-static void
-uty_sock_half_close(vio_sock sock)
-{
- VTY_ASSERT_LOCKED() ;
+ vf = ssl_pop(&vf, vio->vout, vout_next) ;
- sock->read_open = 0 ; /* make sure */
+ qassert(vf->depth_mark < vio->vout_depth) ;
+ vio->vout_depth = vf->depth_mark ;
- if (sock->fd < 0)
- return ; /* nothing more if no socket */
+ uty_vf_free(vf) ;
+ }
+ else
+ {
+ assert(vio->vout == vio->vout_base) ;
+ if (final)
+ assert(vio->vout->vout_state == vf_closed) ;
- VTY_ASSERT_CLI_THREAD() ;
+ qassert(vio->vout->depth_mark == 0) ;
+ vio->vout_depth = 0 ; /* may already be */
+ } ;
- shutdown(sock->fd, SHUT_RD) ; /* actual half close */
+ vio->obuf = vio->vout->obuf ;
- 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) ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Close given vio_sock, completely -- shut down any timer.
+ * Try to output the close reason to the vout_base.
*
- * Structure is cleared of everything except the last error !
+ * This is the final act for the vout_base. Will attempt to output unless
+ * the thing is completely closed.
*
- * NB: if there is a socket, MUST be in the CLI thread
+ * Should by now not be any buffered output -- but if there is, that is
+ * discarded before the close reason is output.
+ *
+ * Any actual output may be done when the vout_base is finally closed.
*/
static void
-uty_sock_close(vio_sock sock)
+uty_vout_close_reason(vio_vf vf, const char* reason)
{
- VTY_ASSERT_LOCKED() ;
-
- sock->read_open = 0 ; /* make sure */
- sock->write_open = 0 ;
-
- if (sock->fd < 0)
- {
- assert( (sock->qf == NULL)
- && (sock->qtr == NULL)
- && (sock->t_read == NULL)
- && (sock->t_write == NULL)
- && (sock->t_timer == NULL) ) ;
- return ; /* no more to be done here */
- } ;
-
- VTY_ASSERT_CLI_THREAD() ;
- close(sock->fd) ;
+ if ((vf->vout_state == vf_closed) || (reason == NULL) || (*reason == '\0'))
+ return ;
- 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 ;
- } ;
+ vio_fifo_clear(vf->obuf, true) ; /* clear any markers, too */
- sock->fd = -1 ;
+ if (vf->vout_type < VOUT_SPECIALS)
+ assert(vf->vfd != NULL) ; /* make sure */
- if (sock->t_read != NULL)
- thread_cancel(sock->t_read) ;
- if (sock->t_write != NULL)
- thread_cancel(sock->t_write) ;
+ switch(vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid VOUT_NONE") ;
+ break ;
- sock->t_read = NULL ;
- sock->t_write = NULL ;
+ case VOUT_TERM:
+ uty_term_close_reason(vf, reason) ;
+ break ;
- sock->info = NULL ;
- sock->action.read.anon = NULL ;
- sock->action.write.anon = NULL ;
- sock->action.timer.anon = NULL ;
+ case VOUT_VTYSH:
+ break ;
- if (sock->qtr != NULL)
- qtimer_free(sock->qtr) ;
- if (sock->t_timer != NULL)
- thread_cancel(sock->t_timer) ;
+ case VOUT_FILE:
+ case VOUT_PIPE:
+ case VOUT_SH_CMD:
+ break ;
- sock->v_timeout = 0 ;
- sock->qtr = NULL ;
- sock->t_timer = NULL ;
-} ;
+ case VOUT_CONFIG:
+ break ;
-/*------------------------------------------------------------------------------
- * Dealing with an I/O error on VTY socket
- *
- * If this is the first error for this VTY, produce suitable log message.
- *
- * If is a "monitor", turn that off, *before* issuing log message.
- */
-static int
-uty_sock_error(vty_io vio, const char* what)
-{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ case VOUT_DEV_NULL:
+ break ;
- /* can no longer be a monitor ! *before* any logging ! */
- uty_set_monitor(vio, 0) ;
+ case VOUT_STDOUT:
+ fprintf(stdout, "%% %s\n", reason) ;
+ break ;
- /* if this is the first error, log it */
- if (vio->sock.error_seen == 0)
- {
- const char* type ;
- switch (vio->type)
- {
- case VTY_TERM:
- type = "VTY Terminal" ;
- break ;
- case VTY_SHELL_SERV:
- type = "VTY Shell Server" ;
- break ;
- default:
- zabort("unknown VTY type for uty_sock_error()") ;
- } ;
-
- vio->sock.error_seen = errno ;
- uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s",
- type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ;
- } ;
+ case VOUT_STDERR:
+ fprintf(stderr, "%% %s\n", reason) ;
+ break ;
- return -1 ;
+ default:
+ zabort("unknown VOUT type") ;
+ } ;
} ;
/*==============================================================================
- * Readiness and the VTY_TERM type VTY.
- *
- * For VTY_TERM the driving force is write ready. This is used to prompt the
- * VTY_TERM when there is outstanding output (obviously), but also if there
- * is buffered input in the keystroke stream.
- *
- * The VTY_TERM uses read ready only when it doesn't set write ready. Does
- * not set both at once.
- *
- * So there is only one, common, uty_ready function, which:
- *
- * 1. attempts to clear any output it can.
+ * vio_vf level operations
+ */
+
+/*------------------------------------------------------------------------------
+ * Create and initialise a new vio_vf structure.
*
- * The state of the output affects the CLI, so must always do this before
- * before invoking the CLI.
+ * There are no errors, yet.
*
- * If this write enters the "--more--" state, then will have tried to
- * write away the prompt.
+ * This leaves most things unset/NULL/false. Caller will need to:
*
- * 2. invokes the CLI
+ * - uty_vin_push() and/or uty_vout_push()
*
- * Which will do either the standard CLI stuff or the special "--more--"
- * stuff.
+ * - once those are done, the following optional items remain to be set
+ * if they are required:
*
- * 3. attempts to write any output there now is.
+ * read_timeout -- default = 0 => no timeout
+ * write_timeout -- default = 0 => no timeout
*
- * If the CLI generated new output, as much as possible is written away
- * now.
+ * - for pipes the child state needs to be set, and for out pipes the return
+ * state needs to be set in this vio_vf (the master) and in the next
+ * vout (the slave).
*
- * If this write enters the "--more--" state, then it returns now_ready,
- * if the prompt was written away, which loops back to the CLI.
+ * NB: if there is no fd for this vio_vf, it should be set to -1, and the
+ * type (recommend vfd_none) and io_type are ignored.
*
- * Note that this is arranging:
+ * A VTY_STDOUT or a VTY_STDERR (output only, to the standard I/O) can
+ * be set up without an fd.
*
- * a. to write away the "--more--" prompt as soon as the tranche of output to
- * which it refers, completes
+ * A VTY_CONFIG_READ can be set up with the fd of the input file, but
+ * MUST be type = vfd_file and io_type = vfd_io_read -- because the fd
+ * is for input only. The required VOUT_STDERR will be set by
+ * uty_vout_push().
*
- * b. to enter the cli_more_wait CLI for the first time immediately after the
- * "--more--" prompt is written away.
+ * NB: if the parent vio is blocking, then the vf will be vfd_io_ps_blocking,
+ * and so will any vfd. An individual vf may be set blocking by setting
+ * vfd_io_blocking or vfd_io_ps_blocking in the io_type.
*
- * The loop limits itself to one trache of command output each time.
+ * For vfd_io_ps_blocking, the vfd stuff handles all files, pipes etc.
+ * non-blocking. The vf simulates blocking by local pselect. As far as
+ * the vfd level is concerned, once the file, pipe etc. is open, there is
+ * no difference between blocking and non-blocking except that blocking
+ * vfd (of either type) are not allowed to set read/write ready.
*
- * Resets the timer because something happened.
+ * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for
+ * disposing of its copy, if required.
*/
-static void
-uty_ready(vty_io vio)
+extern vio_vf
+uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
+ vfd_io_type_t io_type)
{
- enum vty_readiness ready ;
+ vio_vf vf ;
VTY_ASSERT_LOCKED() ;
- vio->cmd_out_done = 0 ; /* not done any command output yet */
+ vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ;
- 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) ;
+ /* Zeroising the structure has set:
+ *
+ * vio = X -- set below
+ * name = X -- set below
+ *
+ * vin_type = VIN_NONE -- see uty_vin_push()
+ * vin_state = vf_closed -- see uty_vin_push()
+ * vin_next = NULL -- see uty_vin_push()
+ *
+ * context = NULL -- see uty_vin_new_context()
+ *
+ * cli = NULL -- no CLI, yet
+ *
+ * ibuf = NULL -- none, yet -- see uty_vin_push()
+ * cl = NULL -- none, yet -- see uty_vin_push()
+ * line_complete = false -- see uty_vin_push()
+ * line_number = 0 -- nothing yet
+ * line_step = 0 -- see uty_vin_push()
+ *
+ * vout_type = VOUT_NONE -- see uty_vout_push()
+ * vout_state = vf_closed -- see uty_vout_push()
+ * vout_next = NULL -- see uty_vout_push()
+ *
+ * obuf = NULL -- none -- see uty_vout_push()
+ *
+ * depth_mark = 0 -- see uty_vout_push()
+ *
+ * blocking = X -- see below
+ *
+ * vfd = NULL -- no vfd, yet
+ *
+ * read_timeout = 0 -- none
+ * write_timeout = 0 -- none
+ *
+ * child = NULL -- none -- see uty_pipe_read/write_open()
+ *
+ * pr_state = vf_closed -- no pipe return vfd
+ * -- see uty_pipe_read/write_open()
+ * pr_vfd = NULL -- no vfd -- ditto
+ * pr_timeout = 0 -- none -- ditto
+ *
+ * ps_state = vf_closed -- no pipe stderr return vfd
+ * -- see uty_pipe_read/write_open()
+ * ps_vfd = NULL -- no vfd -- ditto
+ * ps_timeout = 0 -- none -- ditto
+ * ps_buf = NULL -- none, yet
+ */
+ confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ;
+ confirm(vf_closed == 0) ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
+
+ if (vio->blocking)
+ io_type |= vfd_io_ps_blocking ; /* inherit blocking state */
+
+ vf->vio = vio ;
+ vf->blocking = (io_type & (vfd_io_blocking | vfd_io_ps_blocking)) != 0 ;
+
+ if (name != NULL)
+ vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ;
- uty_sock_set_readiness(&vio->sock, ready) ;
- uty_sock_restart_timer(&vio->sock) ;
+ if (fd >= 0)
+ vf->vfd = vio_vfd_new(fd, type, io_type, vf) ;
+
+ return vf ;
} ;
-/*==============================================================================
- * Reading from VTY_TERM.
+/*------------------------------------------------------------------------------
+ * Close the read side of the given vio_vf -- if can.
*
- * The select/pselect call-back ends up in uty_read_ready().
+ * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully
+ * close it, and the vio_vfd will have been freed.
*
- * Note that uty_write_ready() also calls uty_read_ready, in order to kick the
- * current CLI.
- */
-
-/*------------------------------------------------------------------------------
- * Callback -- qnexus: ready to read -> kicking CLI
+ * For not-final close, if there is any other related I/O, then waits until
+ * that has completed. For example, with a VIN_PIPE:
+ *
+ * * waits for any return input to complete, and pushes it to the relevant
+ * vout.
+ *
+ * * waits to collect the child, and its termination state.
+ *
+ * This means that a not-final close may return errors. For not-blocking vf
+ * may return waiting state. For blocking vf, may block and may later return
+ * timeout error.
+ *
+ * For a final close, if there is any related I/O then will attempt to complete
+ * it -- but will give up if would block. I/O errors on final close are
+ * ignored. Final close always returns CMD_SUCCESS.
+ *
+ * Returns: CMD_SUCCESS -- closed
+ * CMD_WAITING -- waiting to complete close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*/
-static void
-vty_read_qnexus(qps_file qf, void* file_info)
+static cmd_return_code_t
+uty_vf_read_close(vio_vf vf, bool final)
{
- vty_io vio = file_info;
+ cmd_return_code_t ret ;
- VTY_LOCK() ;
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ;
+ ret = CMD_SUCCESS ;
- uty_ready(vio) ;
+ if (vf->vin_state == vf_closed)
+ return ret ; /* quit if already closed */
- VTY_UNLOCK() ;
-}
+ if (vf->vin_state == vf_open)
+ vf->vin_state = vf_end ; /* don't try to read any more ! */
+ else
+ qassert(vf->vin_state == vf_end) ;
-/*------------------------------------------------------------------------------
- * Callback -- threads: ready to read -> kicking CLI
- */
-static int
-vty_read_thread(struct thread *thread)
-{
- vty_io vio = THREAD_ARG (thread);
+ /* Do the vfd level read close and mark the vf no longer read_open */
+ if (vf->vin_type < VIN_SPECIALS)
+ vf->vfd = vio_vfd_read_close(vf->vfd) ;
- VTY_LOCK() ;
+ /* Now the vin_type specific clean up. */
+ switch(vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("invalid VIN_NONE") ;
+ break ;
- assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ;
+ case VIN_TERM:
+ ret = uty_term_read_close(vf, final) ;
+ break ;
- vio->sock.t_read = NULL ; /* implicitly */
- uty_ready(vio);
+ case VIN_VTYSH:
+ zabort("tba VIN_VTYSH") ;
+ break ;
- VTY_UNLOCK() ;
- return 0 ;
-}
+ case VIN_CONFIG:
+ ret = uty_config_read_close(vf, final) ;
+ break ;
-/*------------------------------------------------------------------------------
- * Read a lump of bytes and shovel into the keystroke stream
- *
- * Steal keystroke if required -- see keystroke_input()
- *
- * Returns: 0 => nothing available
- * > 0 => read at least one byte
- * -1 => EOF (or not open, or failed)
- */
-extern int
-uty_read (vty_io vio, keystroke steal)
-{
- unsigned char buf[500] ;
- int get ;
+ case VIN_FILE:
+ ret = uty_file_read_close(vf, final) ;
+ break ;
- if (!vio->sock.read_open)
- return -1 ; /* at EOF if not open */
+ case VIN_PIPE:
+ ret = uty_pipe_read_close(vf, final) ;
+ break ;
- get = read_nb(vio->sock.fd, buf, sizeof(buf)) ;
- if (get >= 0)
- keystroke_input(vio->key_stream, buf, get, steal) ;
- else if (get < 0)
- {
- if (get == -1)
- uty_sock_error(vio, "read") ;
+ case VIN_DEV_NULL:
+ ret = CMD_SUCCESS ;
+ break ;
- vio->sock.read_open = 0 ;
- keystroke_input(vio->key_stream, NULL, 0, steal) ;
+ default:
+ zabort("unknown VIN type") ;
+ } ;
+
+ if ((ret == CMD_SUCCESS) || final)
+ {
+ vf->vin_state = vf_closed ;
+ assert(vf->pr_state == vf_closed) ;
- get = -1 ;
+ ret = CMD_SUCCESS ;
} ;
- return get ;
+ return ret ;
} ;
-/*==============================================================================
- * The write sock action for VTY_TERM type VTY
+/*------------------------------------------------------------------------------
+ * Close the write side of the given vio_vf, if can.
*
- * There are two sets of buffering:
+ * Discards anything beyond the current end_mark, and clears the end_mark.
*
- * cli -- command line -- which reflects the status of the command line
+ * Pushes any outstanding output, and if is pipe will attempt to collect
+ * the child. On not-final close, if cannot complete everything, will return
+ * CMD_WAITING for non-blocking, or block (and may time out). On final close,
+ * will do as much as possible without blocking, and will then close even if
+ * there is outstanding output or child has not been collected.
*
- * cmd -- command output -- which is written to the file only while
- * cmd_out_enabled.
+ * For not-final close, for example, with a VOUT_PIPE:
*
- * The cli output takes precedence.
+ * * waits for any return input to complete, and pushes it to the relevant
+ * vout.
*
- * Output of command stuff is subject to line_control, and may go through the
- * "--more--" mechanism.
- */
-
-static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ;
-static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ;
-
-/*------------------------------------------------------------------------------
- * Callback -- qnexus: ready to write -> try to empty buffers
- */
-static void
-vty_write_qnexus(qps_file qf, void* file_info)
-{
- vty_io vio = file_info ;
-
- VTY_LOCK() ;
-
- assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ;
-
- uty_ready(vio) ;
-
- VTY_UNLOCK() ;
-}
-
-/*------------------------------------------------------------------------------
- * Callback -- thread: ready to write -> try to empty buffers
- */
-static int
-vty_write_thread(struct thread *thread)
-{
- vty_io vio = THREAD_ARG (thread);
-
- VTY_LOCK() ;
-
- assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ;
-
- vio->sock.t_write = NULL; /* implicitly */
- uty_ready(vio) ;
-
- VTY_UNLOCK() ;
- return 0 ;
-}
-
-/*------------------------------------------------------------------------------
- * Write as much as possible of what there is.
+ * * waits to collect the child, and its termination state.
*
- * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become,
- * empty.
+ * This means that a not-final close may return errors.
*
- * Note that if !write_open, or becomes !write_open, then the FIFOs are empty
- * and all output instantly successful.
+ * For a final close, if there is any related I/O then will attempt to complete
+ * it -- but will give up if would block. I/O errors on final close are
+ * ignored. Will close the vfd and return CMD_SUCCESS.
*
- * Sets write on if prevented from writing everything available for output
- * by write() threatening to block.
+ * If this is the vout_base, unless "final", does NOT actually close the vf or
+ * the vfd -- so the vout_base will still work and is left vf_open.
+ * The effect is, essentially, to try to empty out any buffers, but not to
+ * do anything that would prevent further output. This is used so that a
+ * command loop can close the vout_base in the usual way, waiting until all
+ * output is flushed, but when uty_close() is finally called, it can output
+ * any close reason there is to hand.
*
- * Returns: write_ready if should now set write on
- * now_ready if should loop back and try again
- * not_ready otherwise
+ * Returns: CMD_SUCCESS -- closed
+ * CMD_WAITING -- waiting to complete close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
+ *
+ * NB: must not have open vins at this or a higher level in the stack.
+ *
+ * NB: does not at this stage discard the obuf.
*/
-static enum vty_readiness
-uty_write(vty_io vio)
+static cmd_return_code_t
+uty_vf_write_close(vio_vf vf, bool final)
{
- int ret ;
-
- VTY_ASSERT_LOCKED() ;
-
- ret = -1 ;
- while (vio->sock.write_open)
- {
- /* Any outstanding line control output takes precedence */
- if (vio->cmd_lc != NULL)
- {
- ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ;
- if (ret != 0)
- break ;
- }
-
- /* Next: empty out the cli output */
- ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ;
- if (ret != 0)
- break ;
-
- /* Finished now if not allowed to progress the command stuff */
- if (!vio->cmd_out_enabled)
- return not_ready ; /* done all can do */
-
- /* Last: if there is something in the command buffer, do that */
- if (!vio_fifo_empty(&vio->cmd_obuf))
- {
- if (vio->cmd_out_done)
- break ; /* ...but not if done once */
-
- vio->cmd_out_done = 1 ; /* done this once */
-
- assert(!vio->cli_more_wait) ;
-
- if (vio->cmd_lc != NULL)
- ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ;
- else
- ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ;
-
- /* If moved into "--more--" state@
- *
- * * the "--more--" prompt is ready to be written, so do that now
- *
- * * if that completes, then want to run the CLI *now* to perform the
- * first stage of the "--more--" process.
- */
- if (vio->cli_more_wait)
- {
- ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ;
- if (ret == 0)
- return now_ready ;
- } ;
-
- if (ret != 0)
- break ;
- }
-
- /* Exciting stuff: there is nothing left to output...
- *
- * ... watch out for half closed state.
- */
- if (vio->half_closed)
- {
- if (vio->close_reason != NULL)
- {
- vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */
-
- struct vty* vty = vio->vty ;
- if (vio->cli_drawn || vio->cli_dirty)
- vty_out(vty, VTY_NEWLINE) ;
- vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ;
-
- vio->cmd_in_progress = 0 ;
+ cmd_return_code_t ret ;
+ bool base ;
- vio->close_reason = NULL ; /* MUST discard now... */
- continue ; /* ... and write away */
- } ;
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- if (!vio->closed) /* avoid recursion */
- uty_close(vio) ;
+ ret = CMD_SUCCESS ;
- return not_ready ; /* it's all over */
- } ;
+ if (vf->vout_state == vf_closed)
+ return ret ; /* quit if already closed */
- /* 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 ;
- } ;
+ base = (vf == vf->vio->vout_base) ;
- /* Arrives here if there is more to do, or failed (or was !write_open) */
+ /* Must always close vin before closing vout at the same level.
+ *
+ * Note that cannot currently be a pipe return slave, because if was
+ * slave to a VOUT_PIPE/VOUT_SH_CMD that vout must have been closed
+ * already.
+ */
+ qassert( (vf->vio->vin_depth < vf->vio->vout_depth)
+ || ((vf->vio->vin_depth == 0) && (vf->vio->vout_depth == 0)) ) ;
+ qassert(vf->vin_state == vf_closed) ;
+ qassert(vf->vio->obuf == vf->obuf) ;
- if (ret >= 0)
- return write_ready ;
+ /* If there is anything in the obuf beyond the end_mark, then it is
+ * assumed to be surplus to requirements, and we clear the end_mark.
+ */
+ vio_fifo_back_to_end_mark(vf->obuf, false) ;
- /* If is write_open, then report the error
+ /* The vout_type specific close functions will attempt to write
+ * everything away.
+ *
+ * If "final", will only keep going until would block -- at which point will
+ * bring everything to a shuddering halt.
*
- * If still read_open, let the reader pick up and report the error, when it
- * has finished anything it has buffered.
+ * NB: at this point vout_state is not vf_closed.
*/
- if (vio->sock.write_open)
- {
- if (!vio->sock.read_open)
- uty_sock_error(vio, "write") ;
+ switch(vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid VOUT_NONE") ;
+ break ;
- vio->sock.write_open = 0 ; /* crash close write */
- } ;
+ case VOUT_TERM:
+ ret = uty_term_write_close(vf, final) ;
+ break ;
- /* 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 */
+ case VOUT_VTYSH:
+ break ;
- vio->close_reason = NULL ; /* too late for this */
+ case VOUT_FILE:
+ case VOUT_CONFIG:
+ ret = uty_file_write_close(vf, final) ;
+ break ;
- return not_ready ; /* NB: NOT blocked by I/O */
-} ;
+ case VOUT_PIPE:
+ case VOUT_SH_CMD:
+ ret = uty_pipe_write_close(vf, final) ;
+ break ;
-/*------------------------------------------------------------------------------
- * 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() ;
+ case VOUT_DEV_NULL:
+ case VOUT_STDOUT:
+ case VOUT_STDERR:
+ ret = CMD_SUCCESS ;
+ break ;
- if (!vio->sock.write_open)
- return -1 ;
+ default:
+ zabort("unknown VOUT type") ;
+ } ;
- if (vio->cmd_lc != NULL)
+ if (((ret == CMD_SUCCESS) && !base) || final)
{
- int ret ;
- ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ;
+ if (vf->vout_type < VOUT_SPECIALS)
+ vf->vfd = vio_vfd_close(vf->vfd) ;
+ else
+ assert(vf->vfd == NULL) ;
+
+ vf->vout_state = vf_closed ;
- if (ret != 0)
- return ret ;
+ ret = CMD_SUCCESS ;
} ;
- return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Write the given FIFO to output -- subject to possible line control.
+ * Free the given vio_vf structure and all its contents.
*
- * 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.
+ * Expects the vfd to already have been closed, but will close it if not.
*
- * If the line control becomes "paused", it is time to enter "--more--" state
- * -- unless the FIFO is empty (or "--more--" is not enabled).
+ * Expects any cli to be closed, but will close it if not.
*
- * NB: expects that the sock is write_open
- *
- * Returns: > 0 => blocked or completed one tranche
- * 0 => all gone
- * < 0 => failed
+ * Assumes has been removed from any and all lists !
*/
-static int
-uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc)
+static vio_vf
+uty_vf_free(vio_vf vf)
{
- int ret ;
- char* src ;
- size_t have ;
+ assert((vf->vin_state == vf_closed) && (vf->vout_state == vf_closed)
+ && (vf->pr_state == vf_closed)
+ && (vf->ps_state == vf_closed)) ;
- /* 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 */
+ XFREE(MTYPE_VTY_NAME, vf->name) ;
- src = vio_fifo_get_rdr(vf, &have) ;
+ assert(vf->cli == NULL) ;
- while ((src != NULL) && (!lc->paused))
- {
- size_t take ;
- take = vio_lc_append(lc, src, have) ;
- src = vio_fifo_step_rdr(vf, &have, take) ;
- } ;
+ vf->ibuf = vio_fifo_free(vf->ibuf) ;
+ vf->cl = qs_reset(vf->cl, free_it) ;
+ vf->obuf = vio_fifo_free(vf->obuf) ;
- vio->cli_dirty = (lc->col != 0) ;
+ vf->context = cmd_context_free(vf->context, false) ; /* not a copy */
- /* Write the contents of the line control */
- ret = uty_write_lc(vio, vf, lc) ;
+ vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */
+ vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */
+ vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ; /* for completeness */
- if (ret < 0)
- return ret ; /* give up now if failed. */
+ vf->ps_buf = vio_fifo_free(vf->ps_buf) ;
- if ((ret == 0) && vio_fifo_empty(vf))
- return 0 ; /* FIFO and line control empty */
+ XFREE(MTYPE_VTY, vf) ;
- /* If should now do "--more--", now is the time to prepare for that.
- *
- * Entering more state issues a new prompt in the CLI buffer, which can
- * be written once line control write completes.
- *
- * The "--more--" cli will not do anything until the CLI buffer has
- * cleared.
- */
- if (lc->paused && vio->cli_more_enabled)
- uty_cli_enter_more_wait(vio) ;
-
- return 1 ; /* FIFO or line control, not empty */
+ return NULL ;
} ;
+/*==============================================================================
+ * vio_vf level read/write ready and timeout setting
+ */
+
/*------------------------------------------------------------------------------
- * Write contents of line control (if any).
+ * Set required read ready state. Applies the current read timeout.
*
- * NB: expects that the sock is write_open
+ * Forces off if: vf->vin_state != vf_open
*
- * NB: does nothing other than write() and buffer management.
+ * Does nothing if: vf->vin_state == vf_closed
+ * or: vf->vfd == NULL
*
- * Returns: > 0 => blocked
- * 0 => all gone
- * < 0 => failed
+ * NB: must NOT be a "blocking" vf
*/
-static int
-uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc)
+extern void
+uty_vf_set_read(vio_vf vf, on_off_b how)
{
- int ret ;
+ if (vf->vin_state != vf_open)
+ how = off ;
- ret = vio_lc_write_nb(vio->sock.fd, lc) ;
-
- if (ret <= 0)
- vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */
-
- return ret ;
+ if (vf->vin_state != vf_closed)
+ vio_vfd_set_read(vf->vfd, how, vf->read_timeout) ;
} ;
/*------------------------------------------------------------------------------
- * Start command output -- clears down the line control.
+ * Set required read ready timeout -- if already read on, restart it.
*
- * Requires that that current line is empty -- restarts the line control
- * on the basis that is at column 0.
+ * If this is a blocking vf, will set the timeout value, but since can never
+ * be "read on", will never attempt to restart any timer !
*/
extern void
-uty_cmd_output_start(vty_io vio)
+uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout)
{
- if (vio->cmd_lc != NULL)
- vio_lc_clear(vio->cmd_lc) ;
+ vf->read_timeout = read_timeout ;
+
+ if (!vf->blocking)
+ uty_vf_set_read(vf, on) ;
} ;
/*------------------------------------------------------------------------------
- * Set the effective height for line control (if any)
+ * Set required write ready state. Applies the current write timeout.
*
- * If using line_control, may enable the "--more--" output handling.
+ * Forces off if: vf->vout_state != vf_open
*
- * If not, want some limit on the amount of stuff output at a time.
+ * Does nothing if: vf->vout_state == vf_closed
+ * or: vf->vfd == NULL
*
- * Sets the line control window width and height.
- * Sets cli_more_enabled if "--more--" is enabled.
+ * NB: must NOT be a "blocking" vf
*/
extern void
-uty_set_height(vty_io vio)
+uty_vf_set_write(vio_vf vf, on_off_b how)
{
- bool on ;
-
- on = 0 ; /* default state */
-
- if ((vio->cmd_lc != NULL) && !vio->half_closed)
- {
- int height ;
-
- height = 0 ; /* default state */
-
- if ((vio->width) != 0)
- {
- /* If window size is known, use lines or given height */
- if (vio->lines >= 0)
- height = vio->lines ;
- else
- {
- /* Window height, leaving one line from previous "page"
- * and one line for the "--more--" -- if at all possible
- */
- height = vio->height - 2 ;
- if (height < 1)
- height = 1 ;
- } ;
- }
- else
- {
- /* If window size not known, use lines if that has been set
- * explicitly for this terminal.
- */
- if (vio->lines_set)
- height = vio->lines ;
- } ;
+ if (vf->vout_state != vf_open)
+ how = off ;
- if (height > 0)
- on = 1 ; /* have a defined height */
- else
- height = 200 ; /* but no "--more--" */
+ if (vf->vout_state != vf_closed)
+ vio_vfd_set_write(vf->vfd, how, vf->write_timeout) ;
+} ;
- vio_lc_set_window(vio->cmd_lc, vio->width, height) ;
- } ;
+/*------------------------------------------------------------------------------
+ * Set required write ready timeout -- if already write on, restart it.
+ *
+ * If this is a blocking vf, will set the timeout value, but since can never
+ * be "write on", will never attempt to restart any timer !
+ */
+extern void
+uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout)
+{
+ vf->write_timeout = write_timeout ;
- vio->cli_more_enabled = on ;
+ if (!vf->blocking)
+ uty_vf_set_write(vf, on) ;
} ;
/*==============================================================================
- * Timer for VTY_TERM (and VTY_SHELL_SERV).
+ * I/O error and timeout reporting.
+ *
+ * Posts error information and locus to the vio, which is signalled by a
+ * CMD_IO_ERROR return code.
*/
/*------------------------------------------------------------------------------
- * Timer has expired.
+ * Dealing with an I/O error or time-out on the given vio_vf:
+ *
+ * * sets the vf->vin_state, vf->vout_state, vf->pr_state or vf->ps_state to
+ * vf_end
+ *
+ * Note that this does not signal the error -- it means that any further
+ * I/O on this vin/vout is to be avoided.
+ *
+ * Errors and timeouts in either vin or vout also force any pipe return
+ * and/or pipe stderr return to vf_end.
+ *
+ * Errors and timeouts in either pipe return or pipe stderr return also
+ * force all of vin, vout, pipe return and pipe stderr return to vf_end.
+ *
+ * * if vio is a "monitor", turn that off, *before* issuing log message
+ * wrt to either the vin_base or the vout_base.
+ *
+ * * produce suitable log message.
*
- * If half_closed, then this is curtains -- have waited long enough !
+ * * insert suitable message in vio->ebuf
*
- * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
+ * * set the vio->err_depth to 0 if this is an error in vin_base or
+ * vout_base, or to no more than 1 for errors anywhere else.
+ *
+ * * signals CMD_IO_ERROR to the command loop -- which is how the pselect()
+ * process communicates with the command loop.
+ *
+ * Returns: CMD_IO_ERROR -- which may be returned to the command loop, as
+ * well as the vio->signal.
*/
-static void
-uty_timer_expired (vty_io vio)
+extern cmd_return_code_t
+uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err)
{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->half_closed)
- return uty_close(vio) ; /* curtains */
+ vty_io vio = vf->vio ;
- uty_half_close(vio, "Timed out") ; /* bring input side to a halt */
- } ;
+ VTY_ASSERT_LOCKED() ;
-/*------------------------------------------------------------------------------
- * Callback -- qnexus: deal with timer timeout.
- */
-static void
-vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when)
-{
- vty_io vio = timer_info ;
+ /* Set the error level and, if required, turn off any log monitoring *before*
+ * issuing any logging message.
+ */
+ if ((vf == vio->vin_base) || (vf == vio->vout_base))
+ {
+ vio->err_depth = 0 ;
+ uty_set_monitor(vio, off) ;
+ }
+ else
+ {
+ if (vio->err_depth > 1)
+ vio->err_depth = 1 ;
+ } ;
- VTY_LOCK() ;
+ /* Set vf->vin_state, vf->vout_state or vf->pr_state, as required.
+ */
+ switch (err_type)
+ {
+ case verr_none:
+ zabort("verr_io_none invalid") ;
+ break ;
- uty_timer_expired(vio);
+ case verr_io_vin:
+ case verr_to_vin:
+ qassert(vf->vin_state != vf_closed) ;
- VTY_UNLOCK() ;
-}
+ vf->vin_state = vf_end ;
+ break ;
-/*------------------------------------------------------------------------------
- * Callback -- thread: deal with timer timeout.
- */
-static int
-vty_timer_thread (struct thread *thread)
-{
- vty_io vio = THREAD_ARG (thread);
+ case verr_io_vout:
+ case verr_to_vout:
+ qassert(vf->vout_state != vf_closed) ;
- VTY_LOCK() ;
+ vf->vout_state = vf_end ;
+ break ;
- vio->sock.t_timer = NULL ; /* implicitly */
+ case verr_io_pr:
+ case verr_to_pr:
+ qassert(vf->pr_state != vf_closed) ;
+ uty_pipe_return_stop(vf) ;
+ break ;
- uty_timer_expired(vio) ;
+ case verr_io_ps:
+ case verr_to_ps:
+ qassert(vf->ps_state != vf_closed) ;
+ uty_pipe_return_stop(vf) ;
+ break ;
- VTY_UNLOCK() ;
- return 0;
-}
+ default:
+ zabort("unknown verr_xxxx") ;
+ } ;
-/*==============================================================================
- * VTY Listener(s)
- *
- * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY.
- */
+ /* If there is a pipe return (still active), then stop it now -- no point
+ * continuing after main vin/vout has failed.
+ *
+ * Ditto pipe stderr return.
+ */
+ if (vf->pr_state == vf_open)
+ vf->pr_state = vf_end ;
-typedef struct vty_listener* vty_listener ;
+ if (vf->ps_state == vf_open)
+ vf->ps_state = vf_end ;
-struct vty_listener
-{
- vty_listener next ; /* ssl type list */
+ /* Log the error and add an error message to the vio->ebuf.
+ */
+ zlog_warn("%s", uty_error_message(vf, err_type, err, true).str) ;
- enum vty_type type ;
+ vio_fifo_printf(uty_cmd_get_ebuf(vio), "\n%s\n",
+ uty_error_message(vf, err_type, err, false).str) ;
- struct vio_sock sock ;
-};
+ /* Signal to the command loop, if required, and return CMD_IO_ERROR.
+ *
+ * One or both will be collected in the command loop "hiatus" and dealt
+ * with -- it does not matter if both arrive.
+ */
+ uty_cmd_signal(vio, CMD_IO_ERROR) ;
-/* List of listeners so can tidy up. */
-static vty_listener vty_listeners_list = NULL ;
-
-/* Prototypes for listener stuff */
-static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ;
-static int uty_serv_sock(const char* addr, unsigned short port) ;
-static int uty_serv_sock_open(sa_family_t family, int type, int protocol,
- struct sockaddr* sa, unsigned short port) ;
-static int uty_serv_vtysh(const char *path) ;
-static int vty_accept_thread(struct thread *thread) ;
-static void vty_accept_qnexus(qps_file qf, void* listener) ;
-static int uty_accept(vty_listener listener, int listen_sock) ;
-static int uty_accept_term(vty_listener listener) ;
-static int uty_accept_shell_serv (vty_listener listener) ;
-
-static void uty_serv_start_listener(int fd, enum vty_type type) ;
+ return CMD_IO_ERROR ;
+} ;
/*------------------------------------------------------------------------------
- * If possible, will use getaddrinfo() to find all the things to listen on.
+ * Construct error message for given I/O or time-out error
*/
+extern verr_mess_t
+uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log)
+{
+ QFB_QFS(verr_mess, qfs) ;
-#if defined(HAVE_IPV6) && !defined(NRL)
-# define VTY_USE_ADDRINFO 1
-#else
-# define VTY_USE_ADDRINFO 0
-#endif
+ const char* name ;
+ const char* where ;
+ const char* what ;
+ bool vout ;
+ bool io ;
+ int fd ;
-/*------------------------------------------------------------------------------
- * Open VTY listener(s)
- *
- * addr -- address ) to listen for VTY_TERM connections
- * port -- port )
- * path -- path for VTYSH connections -- if VTYSH_ENABLED
- */
-extern void
-uty_open_listeners(const char *addr, unsigned short port, const char *path)
-{
VTY_ASSERT_LOCKED() ;
- /* If port is set to 0, do not listen on TCP/IP at all! */
- if (port)
+ vout = false ;
+ fd = -1 ;
+ what = NULL ;
+
+ switch (err_type & verr_mask)
{
- int n ;
+ case verr_vin:
+ vout = false ;
+ what = "read" ;
+ if (log && (vf->vfd != NULL))
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
- if (VTY_USE_ADDRINFO)
- n = uty_serv_sock_addrinfo(addr, port);
- else
- n = uty_serv_sock(addr, port);
+ case verr_vout:
+ vout = true ;
+ what = "write" ;
+ if (log && (vf->vfd != NULL))
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
- if (n == 0)
- uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ;
- }
+ case verr_pr:
+ vout = true ;
+ what = "pipe return" ;
+ if (log)
+ fd = vio_vfd_fd(vf->pr_vfd) ;
+ break ;
- /* If want to listen for vtysh, set up listener now */
- if (VTYSH_ENABLED && (path != NULL))
- uty_serv_vtysh(path) ;
-} ;
+ case verr_ps:
+ vout = (vf->vout_state != vf_closed) ;
+ what = "stderr return" ;
+ if (log)
+ fd = vio_vfd_fd(vf->ps_vfd) ;
+ break ;
+ } ;
-/*------------------------------------------------------------------------------
- * Close VTY listener
- *
- * addr -- address ) to listen for VTY_TERM connections
- * port -- port )
- * path -- path for VTYSH connections -- if VTYSH_ENABLED
- */
-extern void
-uty_close_listeners(void)
-{
- vty_listener listener ;
+ name = vf->name ;
+ where = NULL ;
- VTY_ASSERT_LOCKED() ;
+ io = (err_type & verr_to) == 0 ;
+ confirm((verr_to != 0) && (verr_io == 0)) ;
- while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL)
+ if (vout)
{
- uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */
- XFREE(MTYPE_VTY, listener) ;
- } ;
-} ;
+ switch (vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("VOUT_NONE invalid") ;
+ break ;
-/*------------------------------------------------------------------------------
- * Open listener(s) for VTY_TERM -- using getaddrinfo().
- *
- * Returns: number of listeners successfully opened.
- */
-static int
-uty_serv_sock_addrinfo (const char *hostname, unsigned short port)
-{
-#if VTY_USE_ADDRINFO
+ case VOUT_TERM:
+ where = "Terminal" ;
+ if (!log)
+ name = NULL ;
+ break ;
+
+ case VOUT_VTYSH:
+ where = "VTY Shell" ;
+ if (!log)
+ name = NULL ;
+ break ;
-# ifndef HAVE_IPV6
-# error Using getaddrinfo() but HAVE_IPV6 is not defined ??
-# endif
+ case VOUT_FILE:
+ where = "File" ;
+ break ;
- int ret;
- int n ;
- struct addrinfo req;
- struct addrinfo *ainfo;
- struct addrinfo *ainfo_save;
- char port_str[16];
+ case VOUT_PIPE:
+ where = "Pipe" ;
+ break ;
- VTY_ASSERT_LOCKED() ;
+ case VOUT_CONFIG:
+ where = "Configuration file" ;
+ break ;
- /* Want to listen, TCP-wise, on all available address families, on the
- * given port.
- */
- memset (&req, 0, sizeof (struct addrinfo));
- req.ai_flags = AI_PASSIVE;
- req.ai_family = AF_UNSPEC;
- req.ai_socktype = SOCK_STREAM;
- snprintf(port_str, sizeof(port_str), "%d", port);
+ case VOUT_DEV_NULL:
+ where = "/dev/null" ;
+ break ;
- ret = getaddrinfo (hostname, port_str, &req, &ainfo);
+ case VOUT_SH_CMD:
+ where = "Command" ;
+ break ;
- if (ret != 0)
- {
- fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str);
- exit (1);
- }
+ case VOUT_STDOUT:
+ where = "stdout" ;
+ break ;
- /* Open up sockets on all AF_INET and AF_INET6 addresses */
- ainfo_save = ainfo;
+ case VOUT_STDERR:
+ where = "stderr" ;
+ break ;
- n = 0 ;
- do
+ default:
+ zabort("unknown vout_type") ;
+ break ;
+ } ;
+ }
+ else
{
- if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6))
- continue;
+ switch (vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("VIN_NONE invalid") ;
+ break ;
- assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ;
+ case VIN_TERM:
+ where = "Terminal" ;
+ if (!log)
+ name = NULL ;
+ break ;
- ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype,
- ainfo->ai_protocol, ainfo->ai_addr, port) ;
- if (ret >= 0)
- ++n ;
- }
- while ((ainfo = ainfo->ai_next) != NULL);
+ case VIN_VTYSH:
+ where = "VTY Shell" ;
+ if (!log)
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
- freeaddrinfo (ainfo_save);
+ case VIN_FILE:
+ where = "File" ;
+ break ;
- return n ;
+ case VIN_PIPE:
+ where = "Pipe" ;
+ break ;
-#else
- zabort("uty_serv_sock_addrinfo not implemented") ;
-#endif /* VTY_USE_ADDRINFO */
-}
+ case VIN_CONFIG:
+ where = "Configuration file" ;
+ break ;
-/*------------------------------------------------------------------------------
- * Open listener(s) for VTY_TERM -- not using getaddrinfo() !
- *
- * Returns: number of listeners successfully opened.
- */
-static int
-uty_serv_sock(const char* addr, unsigned short port)
-{
- int ret;
- int n ;
- union sockunion su_addr ;
- struct sockaddr* sa ;
+ case VIN_DEV_NULL:
+ where = "/dev/null" ;
+ break ;
- VTY_ASSERT_LOCKED() ;
+ default:
+ zabort("unknown vin_type") ;
+ break ;
+ } ;
+ } ;
- n = 0 ; /* nothing opened yet */
+ qfs_printf(qfs, "%s %s %s", where, what, io ? "I/O error" : "time-out") ;
- /* If have an address, see what kind and whether valid */
- sa = NULL ;
+ if (name != NULL)
+ qfs_printf(qfs, " '%s'", name) ;
- if (addr != NULL)
- {
- ret = str2sockunion (addr, &su_addr) ;
- if (ret == 0)
- sa = &su_addr.sa ;
- else
- uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr);
- } ;
+ if (fd >= 0)
+ qfs_printf(qfs, " (fd=%d)", fd) ;
- /* Try for AF_INET */
- ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ;
- if (ret >= 0)
- ++n ; /* opened socket */
- if (ret == 1)
- sa = NULL ; /* used the address */
-
-#if HAVE_IPV6
- /* Try for AF_INET6 */
- ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ;
- if (ret >= 0)
- ++n ; /* opened socket */
- if (ret == 1)
- sa = NULL ; /* used the address */
-#endif
+ if (io && (err != 0))
+ qfs_printf(qfs, ": %s", errtoa(err, 0).str) ;
- /* If not used the address... something wrong */
- if (sa != NULL)
- zlog_err("could not use address %s, to listen for VTY", addr);
+ qfs_term(qfs) ;
+ return verr_mess ;
+} ;
- /* Done */
- return n ;
-}
+/*==============================================================================
+ * Child care.
+ *
+ * Management of vio_child objects and the vio_childer_list.
+ *
+ * When child is registered it is placed on the vio_childer_list. It remains
+ * there until it is "collected", that is until a waitpid() has returned a
+ * "report" for the child.
+ *
+ * When SIGCHLD events go off, the return code for the child is collected,
+ * and saved until the pipe I/O wants it.
+ *
+ * When pipe I/O is closing a pipe and requires the return code from the
+ * child, if the child has not already been collected (after a SIGCHLD),
+ * then:
+ *
+ * * for blocking vf (configuration reading), uty_child_collect() is used,
+ * in which a mini-pselect is used to wait and time-out. A SIGCHLD will
+ * wake up the CLI pthread (or the only thread if not multi-pthreaded)
+ * and, if required, that will wake the waiting pthread by sending a
+ * Quagga SIG_INTERRUPT to it.
+ *
+ * * for non-blocking vf, utf_child_awaited() is used, in which a time-out
+ * timer is set, in case the SIGCHLD does not arrive in good time. When
+ * the child is collected or the timer goes off, a uty_cmd_signal() is
+ * sent.
+ *
+ * When the parent sees that the child is "collected" or "overdue", it can
+ * examine any report and then dismiss the child.
+ *
+ * NB: time-out while waiting to collect a child is not treated as an error,
+ * here -- so no uty_vf_error() is signalled.
+ *
+ * When a parent "dismisses" a child, if it has not yet been collected it is
+ * "smacked" -- kill(SIGTERM) -- but kept on the register until it is
+ * collected. When a child which has been collected is dismissed it is freed.
+ *
+ * At "curtains" the register may contain children which have been dismissed,
+ * but not yet collected. Should NOT contain any children with living parents.
+ * All children remaining on the register are smacked, and all with no living
+ * parents are freed. (This could leave children on the register, but avoids
+ * the possibility of a dangling reference from a parent.)
-/*------------------------------------------------------------------------------
- * Open a VTY_TERM listener socket.
+ * The state of a vio_child object includes:
+ *
+ * parent -- pointer to the parent vf, if any -- set when the child is
+ * registered.
+ *
+ * A NULL parent pointer <=> the child is an orphan.
+ *
+ * At "curtains" all children are orphaned.
+ *
+ * collected -- this is set true when the child termination code is picked
+ * up (by uty_waitpid). It is forced true at "curtains".
+ *
+ * When a child is collected it is removed from the register.
+ *
+ * Once removed from the register, a child is the responsibility
+ * of its parent, if any.
*
- * The sockaddr 'sa' may be NULL or of a different address family, in which
- * case "any" address is used.
+ * When an orphan is removed from the register it can be freed.
*
- * If the sockaddr 'sa' is used, only the address portion is used.
+ * awaited -- this is true iff the parent is non-blocking and is now
+ * waiting to collect the child.
*
- * Returns: < 0 => failed
- * == 0 => OK -- did not use the sockaddr 'sa'.
- * > 1 => OK -- and did use the sockaddr 'sa'
+ * "awaited" <=> there is a timer running.
+ *
+ * When the timer goes off "awaited" is cleared, but the timer
+ * still exists (but is not running).
+ *
+ * overdue -- this is set true if times out waiting for child to be
+ * collected, or can wait no longer ("final" close).
*/
-static int
-uty_serv_sock_open(sa_family_t family, int type, int protocol,
- struct sockaddr* sa, unsigned short port)
-{
- union sockunion su[1] ;
- int sock_fd ;
- int ret ;
+static void uty_child_collected(vio_child child, int report) ;
+static vty_timer_time vty_child_overdue(vio_timer timer, void* action_info) ;
+static void uty_child_signal_parent(vio_child child) ;
+static void uty_child_free(vio_child child) ;
+static pid_t uty_waitpid(pid_t for_pid, int* p_report) ;
+/*------------------------------------------------------------------------------
+ * Set the vty_child_signal_nexus() -- if required.
+ *
+ * The SIGCHLD signal will wake up the cli thread. For blocking I/O (reading
+ * configuration file) may need to wake up another thread, for which the
+ * SIG_INTERRUPT signal is used.
+ *
+ * Note that this is set/cleared under VTY_LOCK() and its own mutex. This
+ * allows it to be read under its own mutex and/or VTY_LOCK().
+ */
+extern void
+uty_child_signal_nexus_set(vty_io vio)
+{
VTY_ASSERT_LOCKED() ;
- /* Is there an address and is it for this family ? */
- if ((sa != NULL) || (sa->sa_family == family))
- /* Set up sockunion containing required family and address */
- sockunion_new_sockaddr(su, sa) ;
- else
+ qassert(vio->blocking) ;
+
+ if (!vty_is_cli_thread())
{
- /* no address or wrong family -- set up empty sockunion of
- * required family */
- sockunion_init_new(su, family) ;
- sa = NULL ;
- } ;
+ qpt_mutex_lock(vty_child_signal_mutex) ;
- /* Open the socket and set its properties */
- sock_fd = sockunion_socket(su, type, protocol) ;
- if (sock_fd < 0)
- return -1 ;
+ vty_child_signal_nexus = qpn_find_self() ;
- ret = setsockopt_reuseaddr (sock_fd);
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+ } ;
+} ;
- if (ret >= 0)
- ret = setsockopt_reuseport (sock_fd);
+/*------------------------------------------------------------------------------
+ * If there is a nexus to signal, do that.
+ */
+extern void
+vty_child_signal_nexus_signal(void)
+{
+ qpt_mutex_lock(vty_child_signal_mutex) ;
- if (ret >= 0)
- ret = set_nonblocking(sock_fd);
+ if (vty_child_signal_nexus != NULL)
+ qpt_thread_signal(vty_child_signal_nexus->thread_id,
+ vty_child_signal_nexus->pselect_signal) ;
-#ifdef HAVE_IPV6
- /* Want only IPv6 on AF_INET6 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) && (family == AF_INET6))
- ret = setsockopt_ipv6_v6only(sock_fd) ;
-#endif
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+}
- if (ret >= 0)
- ret = sockunion_bind (sock_fd, su, port, (sa == NULL)) ;
+/*------------------------------------------------------------------------------
+ * Clear the vty_child_signal_nexus() -- if required.
+ *
+ * Note that this is set/cleared under VTY_LOCK() and its own mutex. This
+ * allows it to be read under its own mutex and/or VTY_LOCK().
+ */
+extern void
+uty_child_signal_nexus_clear(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
- if (ret >= 0)
- ret = sockunion_listen (sock_fd, 3);
+ assert(vio->blocking) ;
- if (ret < 0)
+ if (!vty_is_cli_thread())
{
- close (sock_fd);
- return -1 ;
- }
+ qpt_mutex_lock(vty_child_signal_mutex) ;
- /* Socket is open -- set VTY Term listener going */
- uty_serv_start_listener(sock_fd, VTY_TERM) ;
+ vty_child_signal_nexus = NULL ;
- /* Return OK and signal whether used address or not */
- return (sa != NULL) ? 1 : 0 ;
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Open a VTY_SHEL_SERV listener socket (UNIX domain).
+ * New child.
*
- * Returns: < 0 => failed
- * >= 0 => OK
+ * NB: must register child promptly (under VTY_LOCK, at same time as fork)
+ * in order to avoid missing the SIG_CHLD !
*/
-static int
-uty_serv_vtysh(const char *path)
+extern vio_child
+uty_child_register(pid_t pid, vio_vf parent)
{
- int ret;
- int sock, sa_len, path_len ;
- struct sockaddr_un sa_un ;
- mode_t old_mask;
- struct zprivs_ids_t ids;
+ vio_child child ;
VTY_ASSERT_LOCKED() ;
- /* worry about the path length */
- path_len = strlen(path) + 1 ;
- if (path_len >= (int)sizeof(sa_un.sun_path))
- {
- uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
- return -1 ;
- } ;
-
- /* First of all, unlink existing socket */
- unlink (path);
-
- /* Make UNIX domain socket. */
- sock = socket (AF_UNIX, SOCK_STREAM, 0);
- if (sock < 0)
- {
- uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
- errtoa(errno, 0).str) ;
- return -1 ;
- }
-
- /* Bind to the required path */
- memset (&sa_un, 0, sizeof(sa_un));
- sa_un.sun_family = AF_UNIX;
- strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1);
-
- sa_len = SUN_LEN(&sa_un) ;
-
-#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
- sa_un.sun_len = sa_len ;
-#endif
+ child = XCALLOC(MTYPE_VTY, sizeof(struct vio_child)) ;
- old_mask = umask (0007);
+ /* Zeroising has set:
+ *
+ * list -- NULLs -- added to vio_childer_list, below
+ *
+ * parent -- NULL -- parent vf set, below
+ *
+ * pid -- X -- child pid set below
+ * collected -- false -- not yet collected
+ * report -- X -- not relevant until collected
+ *
+ * awaited -- false -- not waiting for child
+ * overdue -- false -- child not overdue
+ * timer -- NULL -- no waiting timer set
+ */
+ child->parent = parent ;
+ child->pid = pid ;
- ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
- if (ret < 0)
- uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
+ sdl_push(vio_childer_list, child, list) ;
- if (ret >= 0)
- ret = set_nonblocking(sock);
+ return child ;
+} ;
- if (ret >= 0)
- {
- ret = listen (sock, 5);
- if (ret < 0)
- uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
- errtoa(errno, 0).str) ;
- } ;
+/*------------------------------------------------------------------------------
+ * Set waiting for child to be collected -- if not already set.
+ *
+ * This is for !vf->blocking.
+ *
+ * If not already waiting for child, set timer.
+ * If is already waiting, leave existing timer running.
+ */
+extern void
+uty_child_awaited(vio_child child, vty_timer_time timeout)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- zprivs_get_ids(&ids);
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->blocking) ;
- if (ids.gid_vty > 0)
+ if (child->awaited)
{
- /* set group of socket */
- if ( chown (path, -1, ids.gid_vty) )
- uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
- errtoa(errno, 0).str) ;
+ qassert(child->timer != NULL) ;
}
-
- umask (old_mask);
-
- /* Give up now if failed along the way */
- if (ret < 0)
+ else
{
- close (sock) ;
- return -1 ;
- } ;
+ child->awaited = true ;
- /* Socket is open -- set VTY Term listener going */
- uty_serv_start_listener(sock, VTY_SHELL_SERV) ;
+ if (child->timer == NULL)
+ child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ;
- return 0 ;
+ vio_timer_set(child->timer, timeout) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Socket is open -- set a VTY listener going
+ * Clear waiting for child to be collected, if that is set, and discard
+ * any timer.
+ *
+ * NB: child may already be orphaned.
*
- * Note that the vyt_listener structure is passed to the accept action function.
+ * NB: timer is not discarded when goes off, so will still exist, even
+ * though is no longer "awaited".
+ *
+ * If has a timer, then must be in the CLI thread -- which will be, because
+ * all !vf->blocking child handling is done in the CLI thread.
*/
static void
-uty_serv_start_listener(int fd, enum vty_type type)
+uty_child_not_awaited(vio_child child)
{
- vty_listener listener ;
-
- listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener));
+ VTY_ASSERT_LOCKED() ;
- ssl_push(vty_listeners_list, listener, next) ;
- uty_sock_init_new(&listener->sock, fd, listener) ;
+ if (child->awaited) /* "awaited" => not orphan & timer running */
+ {
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->blocking) ;
+ qassert(child->timer != NULL) ;
- listener->type = type ;
+ child->awaited = false ;
+ } ;
- if (vty_cli_nexus)
- listener->sock.action.read.qnexus = vty_accept_qnexus ;
- else
- listener->sock.action.read.thread = vty_accept_thread ;
+ if (child->timer != NULL) /* => in CLI thread */
+ {
+ VTY_ASSERT_CLI_THREAD() ;
- uty_sock_set_read(&listener->sock, on) ;
+ child->timer = vio_timer_reset(child->timer, free_it) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Accept action for the thread world -- create and dispatch VTY
+ * See if parent can collect child -- directly.
+ *
+ * This is for vf->blocking, or for "final" !vf->blocking.
+ *
+ * If can, will collect now -- marking collected and taking off the
+ * vio_childer_list.
+ *
+ * If cannot immediately collect, if final mark overdue and return.
+ *
+ * Otherwise wait for up to timeout seconds for a suitable SIGCHLD or related
+ * wake-up signal.
+ *
+ * Returns: true <=> collected
+ * false => timed out or final
*/
-static int
-vty_accept_thread(struct thread *thread)
+extern bool
+uty_child_collect(vio_child child, vty_timer_time timeout, bool final)
{
- vty_listener listener = THREAD_ARG(thread) ;
- int result ;
+ bool first ;
- VTY_LOCK() ;
+ VTY_ASSERT_LOCKED() ;
- result = uty_accept(listener, THREAD_FD(thread));
+ qassert(child->parent != NULL) ;
+ qassert(child->parent->blocking || final) ;
+ qassert(child->pid > 0) ;
- uty_sock_set_read(&listener->sock, on) ;
+ uty_child_not_awaited(child) ; /* clear flag & timer */
- VTY_UNLOCK() ;
- return result ;
-} ;
+ first = true ;
+ while (1)
+ {
+ pid_t pid ;
+ int report ;
-/*------------------------------------------------------------------------------
- * Accept action for the qnexus world -- create and dispatch VTY
- */
-static void
-vty_accept_qnexus(qps_file qf, void* listener)
-{
- VTY_LOCK() ;
+ qps_mini_t qm ;
+ sigset_t* sig_mask = NULL ;
- uty_accept(listener, qf->fd);
+ /* If already collected, or succeed in collecting, we are done. */
+ if (child->collected)
+ return true ; /* already collected */
- VTY_UNLOCK() ;
-}
+ pid = uty_waitpid(child->pid, &report) ;
-/*------------------------------------------------------------------------------
- * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV
- */
-static int
-uty_accept(vty_listener listener, int listen_sock)
-{
- VTY_ASSERT_LOCKED() ;
+ if (pid == child->pid)
+ {
+ uty_child_collected(child, report) ; /* not orphan */
+ return true ; /* collected */
+ } ;
- assert(listener->sock.fd == listen_sock) ;
+ /* If "final" or got an error, mark child overdue and give up */
+ if (final || (pid < 0))
+ {
+ child->overdue = true ;
+ return false ; /* overdue */
+ } ;
- switch (listener->type)
- {
- case VTY_TERM:
- return uty_accept_term(listener) ;
+ /* Need to wait -- if this is the first time through, prepare for
+ * that.
+ */
+ if (first)
+ {
+ qps_mini_set(qm, -1, 0, 6) ;
- case VTY_SHELL_SERV:
- return uty_accept_shell_serv(listener) ;
+ if (vty_is_cli_thread())
+ sig_mask = NULL ;
+ else
+ sig_mask = vty_child_signal_nexus->pselect_mask ;
- default:
- zabort("unknown vty type") ;
+ first = false ;
+ } ;
+
+ /* Wait on pselect. */
+ if (qps_mini_wait(qm, sig_mask, true) == 0)
+ final = true ; /* timed out => now final */
} ;
} ;
/*------------------------------------------------------------------------------
- * Accept action -- create and dispatch VTY_TERM
+ * Dismiss child
+ *
+ * If already collected, free the child.
+ *
+ * If not collected, smack but leave to be collected in due course (or free
+ * now at "curtains").
+ *
+ * This may be called in any thread -- but must be CLI thread if there is
+ * a timer, that is: must be CLI thread if this is/was non-blocking.
+ *
+ * When the register is closed, is done in CLI thread.
+ *
+ * NB: child will have been freed if was collected or this is "curtains".
*/
-static int
-uty_accept_term(vty_listener listener)
+extern void
+uty_child_dismiss(vio_child child, bool curtains)
{
- int sock_fd;
- union sockunion su;
- int ret;
- unsigned int on;
- struct prefix *p ;
-
VTY_ASSERT_LOCKED() ;
- /* We can handle IPv4 or IPv6 socket. */
- sockunion_init_new(&su, AF_UNSPEC) ;
-
- sock_fd = sockunion_accept (listener->sock.fd, &su);
+ uty_child_not_awaited(child) ;
- if (sock_fd < 0)
+ if (child->parent != NULL)
{
- if (sock_fd == -1)
- uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s",
- errtoa(errno, 0).str) ;
- return -1;
- }
-
- /* Really MUST have non-blocking */
- ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */
- if (ret < 0)
- {
- close(sock_fd) ;
- return -1 ;
+ child->parent->child = NULL ;
+ child->parent = NULL ; /* orphan from now on */
} ;
- /* New socket is open... worry about access lists */
- p = sockunion2hostprefix (&su);
- ret = 0 ; /* so far, so good */
-
- if ((p->family == AF_INET) && vty_accesslist_name)
- {
- /* VTY's accesslist apply. */
- struct access_list* acl ;
-
- if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) &&
- (access_list_apply (acl, p) == FILTER_DENY))
- ret = -1 ;
- }
-
-#ifdef HAVE_IPV6
- if ((p->family == AF_INET6) && vty_ipv6_accesslist_name)
+ if (child->collected)
+ uty_child_free(child) ;
+ else
{
- /* VTY's ipv6 accesslist apply. */
- struct access_list* acl ;
+ qassert(child->pid > 0) ;
- if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) &&
- (access_list_apply (acl, p) == FILTER_DENY))
- ret = -1 ;
- }
-#endif /* HAVE_IPV6 */
-
- prefix_free (p);
+ kill(child->pid, SIGKILL) ; /* hasten the end */
- if (ret != 0)
- {
- uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ;
- close (sock_fd);
- return 0;
+ if (curtains)
+ uty_child_collected(child, 0) ; /* orphan: so free it */
} ;
-
- /* Final options (optional) */
- on = 1 ;
- ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY,
- (void*)&on, sizeof (on));
- if (ret < 0)
- uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s",
- sock_fd, errtoa(errno, 0).str) ;
-
- /* All set -- create the VTY_TERM */
- uty_new_term(sock_fd, &su);
-
- /* Log new VTY */
- uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str,
- sock_fd) ;
-
- return 0;
-}
+} ;
/*------------------------------------------------------------------------------
- * Accept action -- create and dispatch VTY_SHELL_SERV
+ * At "curtains" -- dismiss any children left in the register.
*/
-static int
-uty_accept_shell_serv (vty_listener listener)
+extern void
+vty_child_close_register(void)
{
- int sock_fd ;
- int ret ;
- int client_len ;
- struct sockaddr_un client ;
-
- VTY_ASSERT_LOCKED() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- client_len = sizeof(client);
- memset (&client, 0, client_len);
+ while (vio_childer_list != NULL)
+ uty_child_dismiss(vio_childer_list, true) ; /* curtains */
+} ;
- sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client,
- (socklen_t *) &client_len) ;
+/*------------------------------------------------------------------------------
+ * See whether any children are ready for collection, and check each one
+ * against the register.
+ *
+ * Perform waitpid( , , WNOHANG) until no child is returned, and process
+ * each one against the register.
+ *
+ * The is done when a SIGCHLD is routed through the event mechanism.
+ *
+ * If another SIGCHLD occurs while this is being done, that will later cause
+ * another call of this function -- which may find that there are no children
+ * to be collected.
+ *
+ * This is also done when about to block waiting for a child.
+ *
+ * Set any children that can be collected, collected and signal to any parents
+ * that their children are now ready.
+ */
+extern void
+uty_sigchld(void)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- if (sock_fd < 0)
+ while (1)
{
- uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
- errtoa(errno, 0).str) ;
- return -1;
- }
+ vio_child child ;
+ pid_t pid ;
+ int report ;
- /* Really MUST have non-blocking */
- ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */
- if (ret < 0)
- {
- close(sock_fd) ;
- return -1 ;
- } ;
+ pid = uty_waitpid((pid_t)-1, &report) ;
- /* All set -- create the VTY_SHELL_SERV */
- if (VTYSH_DEBUG)
- printf ("VTY shell accept\n");
+ if (pid <= 0)
+ break ;
- uty_new_shell_serv(sock_fd) ;
+ child = vio_childer_list ;
+ while (1)
+ {
+ if (child == NULL)
+ {
+ zlog_err("waitpid(-1) returned pid %d, which is not registered",
+ pid) ;
+ break ;
+ } ;
- /* Log new VTY */
- uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
- return 0;
-}
+ if (child->pid == pid)
+ {
+ /* Remove child from register and set "collected". Turn off
+ * any timer and clear "awaited".
+ *
+ * If child is not orphaned: set report and signal parent if
+ * required.
+ *
+ * Otherwise: can free the child now.
+ */
+ uty_child_collected(child, report) ;
-/*==============================================================================
- * Reading from the VTY_SHELL_SERV type sock.
- *
- * The select/pselect call-back ends up in utysh_read_ready().
- */
+ break ;
+ } ;
+
+ child = sdl_next(child, list) ;
+ } ;
+ } ;
+} ;
/*------------------------------------------------------------------------------
- * Ready to read -> kicking the "SHELL_SERV CLI"
+ * Have collected a child.
+ *
+ * * if a parent is waiting, signal them
*
- * End up here when there is something ready to be read.
+ * * clear any awaited state and discard any timer.
*
- * 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.
+ * * remove from the register
*
- * Note that nothing is actually read here -- reading is done in the CLI itself,
- * if required.
+ * * if the child has a parent, set the child's report (the parent is now
+ * responsible for the child).
*
- * The CLI decides whether to re-enable read, or enable write, or both.
+ * otherwise, can free the child now.
*/
static void
-utysh_read_ready(vty_io vio)
+uty_child_collected(vio_child child, int report)
{
- uty_sock_set_read(&vio->sock, off) ;
+ qassert(!child->collected) ; /* can only collect once */
- /* TODO: need minimal "CLI" for VTY_SHELL_SERV
- * NB: when output from command is flushed out, must append the
- * following four bytes: '\0' '\0' '\0' <ret>
- * Where <ret> is the command return code.
- */
+ if (child->timer != NULL)
+ VTY_ASSERT_CLI_THREAD() ; /* must be to clear timer */
+
+ if (child->awaited)
+ uty_child_signal_parent(child) ;
+ uty_child_not_awaited(child) ; /* clear flag and timer */
+
+ child->collected = true ; /* remove from register */
+ sdl_del(vio_childer_list, child, list) ;
+
+ if (child->parent == NULL)
+ uty_child_free(child) ;
+ else
+ child->report = report ;
} ;
/*------------------------------------------------------------------------------
- * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI"
+ * Set child as overdue -- vio_timer action routine.
*/
-static void
-vtysh_read_qnexus(qps_file qf, void* file_info)
+static vty_timer_time
+vty_child_overdue(vio_timer timer, void* action_info)
{
- vty_io vio = file_info;
+ vio_child child ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ child = action_info ;
+ assert(timer == child->timer) ;
- assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ;
+ if (child->awaited) /* ignore if no longer awaited */
+ {
+ uty_child_signal_parent(child) ; /* clears "awaited" */
- utysh_read_ready(vio) ;
+ child->overdue = true ;
+ } ;
VTY_UNLOCK() ;
-}
+
+ return 0 ; /* stop timer */
+} ;
/*------------------------------------------------------------------------------
- * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI"
+ * Signal that child is ready -- collected or overdue -- clears "awaited".
+ *
+ * Must be "awaited" -- so not "blocking"
*/
-static int
-vtysh_read_thread(struct thread *thread)
+static void
+uty_child_signal_parent(vio_child child)
{
- vty_io vio = THREAD_ARG (thread);
+ qassert(child->awaited) ;
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->vio->blocking) ;
- VTY_LOCK() ;
+ uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ;
- assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ;
+ child->awaited = false ;
+} ;
- vio->sock.t_read = NULL ; /* implicitly */
- utysh_read_ready(vio);
+/*------------------------------------------------------------------------------
+ * Free the child -- caller must ensure that any parent has dismissed the child,
+ * and that it is collected (so not on the vio_childer_list) and that there
+ * is no timer running.
+ */
+static void
+uty_child_free(vio_child child)
+{
+ VTY_ASSERT_LOCKED() ;
- VTY_UNLOCK() ;
- return 0 ;
-}
+ if (child != NULL)
+ {
+ qassert(child->collected) ;
+ qassert(child->parent == NULL) ;
+ qassert(child->timer == NULL) ;
+
+ XFREE(MTYPE_VTY, child) ;
+ } ;
+} ;
/*------------------------------------------------------------------------------
- * Read a lump of bytes and shovel into the command line buffer
- *
- * Lines coming in are terminated by '\0'.
- *
- * Assumes that the incoming command line is empty or otherwise incomplete.
- *
- * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping
- * when get '\0' or empties the "buf".
- *
- * When empties "buf", reads a lump from the sock.
- *
- * Returns: 0 => command line is incomplete
- * 1 => have a complete command line
- * -1 => EOF (or not open, or failed)
+ * Wrapper for waitpid() -- deal with and log any errors.
*/
-extern int
-utysh_read (vty_io vio, qstring cl, qstring buf)
+static pid_t
+uty_waitpid(pid_t for_pid, int* p_report)
{
- int get ;
- char* cp ;
- char* ep ;
- size_t have ;
+ pid_t pid ;
while (1)
{
- /* process what there is in the buffer */
- if (buf->len > buf->cp)
- {
- cp = qs_cp_char(buf) ;
- ep = qs_ep_char(buf) ;
- have = ep - cp ;
-
- ep = memchr(cp, '\0', have) ;
- if (ep != NULL)
- have = ep - cp ; /* have upto, but excluding '\0' */
+ pid = waitpid(for_pid, p_report, WNOHANG) ;
- if (have > 0) /* take what have */
- {
- qs_insert(cl, cp, have) ;
- cl->cp += have ;
- buf->cp += have ;
- } ;
+ if (pid == 0)
+ return pid ; /* nothing to be had */
- if (ep != NULL) /* if found '\0' */
- {
- qs_term(cl) ; /* '\0' terminate */
- ++buf->cp ; /* step past it */
- return 1 ; /* have a complete line <<<<<<<<<<<<< */
- }
- } ;
+ if (pid > 0) /* got an answer */
+ {
+ if ((for_pid < 0) || (pid == for_pid))
+ return pid ;
- /* buffer is empty -- try and get some more stuff */
- assert(buf->len == buf->cp) ;
+ /* This is absolutely impossible. If for_pid is > 0, then
+ * the only valid response > 0 is for_pid !!
+ *
+ * Don't know what to do with this, but treating it as a
+ * "nothing to be had" return seems safe.
+ */
+ zlog_err("waitpid(%d) returned pid=%d", for_pid, pid) ;
- if (!vio->sock.read_open)
- return -1 ; /* at EOF if not open <<<<<<<<<<<<< */
+ return -1 ;
+ } ;
- qs_need(buf, 500) ; /* need a reasonable lump */
- qs_clear(buf) ; /* set cp = len = 0 */
+ if (errno == EINTR)
+ continue ; /* loop on "Interrupted" */
- get = read_nb(vio->sock.fd, buf->body, buf->size) ;
- if (get > 0)
- buf->len = get ;
- else if (get == 0)
- return 0 ; /* have an incomplete line <<<<<<<<<<<< */
- else
- {
- if (get == -1)
- uty_sock_error(vio, "read") ;
-
- vio->sock.read_open = 0 ;
+ /* Got an error other than EINTR, which is almost impossible...
+ *
+ * (1) ECHILD means that the given pid is not a child or there are
+ * no children.
+ *
+ * This is possible if this is called following a SIGCHLD, and
+ * all children have been collected between the signal and the
+ * uty_sigchld().
+ *
+ * Note that only call uty_waitpid() for a specific child if it
+ * is known to not yet have been collected.
+ *
+ * (2) only other known error is EINVAL -- invalid options...
+ * absolutely impossible.
+ */
+ if ((errno != ECHILD) || (for_pid > 0))
+ zlog_err("waitpid(%d) returned %s", for_pid, errtoa(errno, 0).str) ;
- return -1 ; /* at EOF or failed <<<<<<<<<<<<< */
- } ;
+ return -1 ;
} ;
} ;
/*==============================================================================
- * Output to vty which are set to "monitor".
- *
- * This is VERY TRICKY.
- *
- * If not running qpthreaded, then the objective is to get the message away
- * immediately -- do not wish it to be delayed in any way by the thread
- * system.
- *
- * So proceed as follows:
- *
- * a. wipe command line -- which adds output to the CLI buffer
- *
- * b. write the CLI buffer to the sock and any outstanding line control.
- *
- * c. write the monitor output.
- *
- * If that does not complete, put the tail end to the CLI buffer.
- *
- * d. restore any command line -- which adds output to the CLI buffer
- *
- * e. write the CLI buffer to the sock
- *
- * If that all succeeds, nothing has changed as far as the VTY stuff is
- * concerned -- except that possibly some CLI output was sent before it got
- * round to it.
- *
- * Note that step (b) will deal with any output hanging around from an
- * earlier step (e). If cannot complete that, then does not add fuel to the
- * fire -- but the message will be discarded.
- *
- * If that fails, or does not complete, then can set write on, to signal that
- * there is some output in the CLI buffer that needs to be sent, or some
- * error to be dealt with.
- *
- * The output should be tidy.
- *
- * To cut down the clutter, step (d) is performed only if the command line
- * is not empty (or if in cli_more_wait). Once a the user has started to enter
- * a command, the prompt and the command will remain visible.
- *
- * When logging an I/O error for a vty that happens to be a monitor, the
- * monitor-ness has already been turned off. The monitor output code does not
- * attempt to log any errors, sets write on so that the error will be picked
- * up that way.
- *
- * However, in the event of an assertion failure, it is possible that an
- * assertion will fail inside the monitor output. The monitor_busy flag
- * prevents disaster. It is also left set if I/O fails in monitor output, so
- * will not try to use the monitor again.
- *
- * Note that an assertion which is false for all vty monitors will recurse
- * through all the monitors, setting each one busy, in turn !
- *
-
-
- * TODO: sort out write on in the qpthreads world ??
+ * VTY Listener(s)
*
- * 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.
+ * Have listeners for VTY_TERMINAL and VTY_SHELL_SERVER types of VTY.
*/
+/* List of listeners so can tidy up. */
+static vio_listener vty_listeners_list = NULL ;
+
/*------------------------------------------------------------------------------
- * Output logging information to all vty which are set to "monitor".
+ * Open VTY listener(s) for VTY_TERMINAL and VTY_SHELL_SERVER.
+ *
+ * addr -- address ) to listen for VTY_TERMINAL connections
+ * port -- port )
+ * path -- path for VTY_SHELL_SERVER connections -- if VTYSH_ENABLED
*/
extern void
-uty_log(struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va)
+uty_open_listeners(const char *addr, unsigned short port, const char *path)
{
- vty_io vio ;
-
VTY_ASSERT_LOCKED() ;
- vio = sdl_head(vio_monitors_base) ;
-
- if (vio == NULL)
- return ; /* go no further if no "monitor" vtys */
-
- /* Prepare line for output. */
- uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */
-
- /* write to all known "monitor" vty
- *
- */
- while (vio != NULL)
- {
- if (!vio->monitor_busy)
- {
- int ret ;
-
- vio->monitor_busy = 1 ; /* close the door */
+ /* If port is set to 0, do not listen for VTY_TERMINAL at all! */
+ if (port)
+ uty_term_open_listeners(addr, port) ;
- uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */
+#if 0
+ /* If want to listen for vtysh, set up listener now */
+ if (VTYSH_ENABLED && (path != NULL))
+ uty_serv_vtysh(path) ;
+#endif
+} ;
- ret = uty_write_monitor(vio) ;
- if (ret == 0)
- {
- ret = write_nb(vio->sock.fd, ll->line, ll->len) ;
-
- if (ret >= 0)
- {
- ret = uty_cli_post_monitor(vio, ll->line + ret,
- ll->len - ret) ;
- if (ret > 0)
- ret = uty_write_monitor(vio) ;
- } ;
- } ;
+/*------------------------------------------------------------------------------
+ * Create listener and set it ready to accept.
+ *
+ * Adds to list of listeners for close down.
+ */
+extern void
+uty_add_listener(int fd, vio_vfd_accept* accept_action)
+{
+ vio_listener vl ;
- if (ret != 0)
- /* need to prod */ ;
+ VTY_ASSERT_LOCKED() ;
- if (ret >= 0)
- vio->monitor_busy = 0 ;
- } ;
+ vl = vio_listener_new(fd, accept_action) ;
- vio = sdl_next(vio, mon_list) ;
- } ;
+ ssl_push(vty_listeners_list, vl, next) ;
} ;
/*------------------------------------------------------------------------------
- * Async-signal-safe version of vty_log for fixed strings.
- *
- * This is last gasp operation.
+ * Close all VTY listeners
*/
-void
-vty_log_fixed (const char *buf, size_t len)
+extern void
+uty_close_listeners(void)
{
- vty_io vio ;
+ vio_listener listener ;
- /* Write to all known "monitor" vty
- *
- * Forget all the niceties -- about to die in any case.
- */
- vio = sdl_head(vio_monitors_base) ;
- while (vio != NULL)
- {
- write(vio->sock.fd, buf, len) ;
- write(vio->sock.fd, "\r\n", 2) ;
+ VTY_ASSERT_LOCKED() ;
- vio = sdl_next(vio, mon_list) ;
+ while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL)
+ {
+ vio_listener_close(listener) ; /* no ceremony, no flowers */
} ;
} ;
diff --git a/lib/vty_io.h b/lib/vty_io.h
index 19689853..5f550837 100644
--- a/lib/vty_io.h
+++ b/lib/vty_io.h
@@ -25,286 +25,545 @@
#ifndef _ZEBRA_VTY_IO_H
#define _ZEBRA_VTY_IO_H
-#include <stdbool.h>
-#include <errno.h>
+//#include "zebra.h"
+#include "misc.h"
+//#include <errno.h>
-#include "uty.h"
-#include "vty.h"
+#include "vty_local.h"
+#include "command_local.h"
+#include "vty_io_basic.h"
#include "vio_fifo.h"
-#include "vio_lines.h"
-#include "keystroke.h"
#include "thread.h"
-#include "command.h"
+#include "command_execute.h"
#include "qstring.h"
+#include "qfstring.h"
+#include "list_util.h"
/*==============================================================================
- * Here are structures and other definitions which are shared by:
+ * Structures and other definitions for the top level VTY I/O.
*
- * vty.c -- the main vty handler
- * vty_cli.c -- which handles the command line stuff
- * vty_io.c -- ....
- *
- * The "struct vty" is used extensively across the Quagga daemons, where it
- * has two functions relating to command handling as:
- *
- * 1) a "file handle" for output produced by commands
- *
- * 2) the holder of some context -- notably the current command "node" -- for
- * command execution to use
- *
- * The bulk of "struct vty" is, therefore, private to vty.c and is factored
- * out into the "struct vty_io".
- *
- * To reduce the size of vty.c, some groups of functions are separated into:
- *
- * vty_cli.c -- which looks after the keystroke by keystroke handling
- * of the command line.
+ * There is one struct vty_io per VTY, which contains, inter alia, the vin
+ * and vout stacks.
*
+ * The vin and vout stacks contain one or more struct vty_vf -- one per
+ * input and/or output associated with the VTY.
*/
+enum
+{
+ VTY_WATCH_DOG_INTERVAL = 5, /* interval between barks */
+
+ VTY_HALF_CLOSE_TIMEOUT = 120, /* timeout after half_close */
+
+ VTY_TIMEOUT_DEFAULT = 600, /* terminal timeout value */
+} ;
+
/*------------------------------------------------------------------------------
- * VTY sock structure
- *
- * Used for VTY_TERM and VTY_SHELL_SERV VTY types, which are attached to TCP
- * and UNIX sockets, respectively.
- *
- * Also used for the associated listeners.
+ * VTY VIN and OUT types
*/
+enum vio_in_type /* Command input */
+{
+ VIN_NONE = 0, /* not a valid input type */
-typedef int thread_action(struct thread *) ;
+ VIN_TERM, /* telnet terminal */
+ VIN_VTYSH, /* vty_shell input */
-union sock_action
-{
- qps_action* qnexus ;
- thread_action* thread ;
- void* anon ;
+ VIN_FILE, /* ordinary file input */
+ VIN_PIPE, /* pipe (from child process) */
+
+ VIN_CONFIG, /* config file */
+
+ /* The VIN types >= VIN_SPECIALS do not have an associated fd.
+ *
+ * These can coexist with a VOUT which does have an associated fd.
+ */
+ VIN_SPECIALS, /* all special from now on */
+
+ VIN_DEV_NULL = VIN_SPECIALS,
+ /* black hole input */
} ;
+typedef enum vio_in_type vio_in_type_t ;
-union timer_action
+enum vio_out_type /* Command output */
{
- qtimer_action* qnexus ;
- thread_action* thread ;
- void* anon ;
+ VOUT_NONE = 0, /* not a valid output type */
+
+ VOUT_TERM, /* a telnet terminal */
+ VOUT_VTYSH, /* a vty_shell output pipe */
+
+ VOUT_FILE, /* ordinary file */
+ VOUT_PIPE, /* pipe (to child process, and back again) */
+
+ VOUT_CONFIG, /* config file */
+
+ /* The VOUT types >= VOUT_SPECIALS do not have an associated fd.
+ *
+ * These can coexist with a VIN which does have an associated fd.
+ */
+ VOUT_SPECIALS, /* all special from now on */
+
+ VOUT_DEV_NULL = VOUT_SPECIALS,
+ /* black hole output */
+
+ VOUT_SH_CMD, /* pipe for shell command (no actual output) */
+
+ VOUT_STDOUT, /* stdout */
+ VOUT_STDERR, /* stderr */
+};
+typedef enum vio_out_type vio_out_type_t ;
+
+/*------------------------------------------------------------------------------
+ * State of a vf -- has separate state for vin/vout.
+ */
+enum vf_state
+{
+ vf_closed = 0, /* the vf has not been opened, or has been
+ * completely closed -- there will be no vfd. */
+
+ vf_open, /* the vf has been opened, and any required vfd
+ * is open and I/O is possible. */
+
+ vf_end, /* for a vin: EOF has been reached or input has
+ * been terminated, or an error or timeout has
+ * been met.
+ *
+ * for a vout: output has been terminated, or an
+ * error or tineout has been met.
+ *
+ * The vfd may have been closed -- but in any
+ * case no further vfd I/O should be attempted. */
} ;
+typedef enum vf_state vf_state_t ;
-struct vio_sock_actions
+/*------------------------------------------------------------------------------
+ * vio_child structure.
+ *
+ * Lives on the vio_childer_list until collected or "curtains".
+ *
+ */
+typedef enum vio_child_await vio_child_await_t ;
+
+struct vio_vf ; /* Forward reference */
+typedef struct vio_vf* vio_vf ;
+
+typedef struct vio_child* vio_child ;
+
+struct vio_child
{
- union sock_action read ;
- union sock_action write ;
- union timer_action timer ;
-};
+ struct dl_list_pair(vio_child) list ; /* in the list of children */
+
+ vio_vf parent ;
+
+ pid_t pid ;
+ bool collected ; /* waitpid() done */
+ int report ; /* from waitpid() */
+
+ bool overdue ; /* patience exhausted */
+
+ bool awaited ; /* if child is awaited -- !vf->blocking */
+ vio_timer timer ; /* limit the waiting time */
+} ;
-typedef struct vio_sock* vio_sock ;
-struct vio_sock
+/*------------------------------------------------------------------------------
+ * vty_vf -- "vty file" structure
+ *
+ * A vio_vf may be a read, write or read/write object.
+ *
+ * All I/O is via vio_vfd objects, except for VOUT_STDOUT and VOUT_STDERR.
+ * The vio_vfd layer hides the differences between the qpthreads an legacy
+ * thread environments.
+ *
+ * The VOUT_STDOUT and VOUT_STDERR are handled as direct output to the standard
+ * i/o file handles. In the case of a VTY_CONFIG_READ, the vin is VIN_CONFIG
+ * and the vout is VOUT_STDOUT, and these can share a single vty_vf.
+ *
+ * Also used for the associated listeners.
+ */
+struct vty_io ; /* Forward reference */
+typedef struct vty_io* vty_io ;
+
+struct vio_vf
{
- int fd ;
+ vty_io vio ; /* parent */
+
+ char* name ; /* MTYPE_VTY_NAME (if any) */
- void* info ; /* for action routines */
+ /* Input side. */
- struct vio_sock_actions action ;
+ vio_in_type_t vin_type ;
+ vf_state_t vin_state ;
+ vio_vf vin_next ; /* list of inputs */
- bool read_open ; /* read returns 0 if not open */
- bool write_open ; /* write completes instantly if not open */
- int error_seen ; /* non-zero => failed */
+ cmd_context context ; /* pushed exec->context. */
- qps_file qf ; /* when running qnexus */
+ struct vty_cli* cli ; /* NULL if not a VTY_TERMINAL ! */
- struct thread *t_read; /* when running threads */
- struct thread *t_write;
+ vio_fifo ibuf ; /* input fifo (if required) */
- unsigned long v_timeout; /* time-out in seconds -- 0 => none */
- bool timer_running ; /* true when timer is running */
+ qstring cl ; /* command line buffer */
+ bool line_complete ; /* false => line in construction */
+ uint line_number ; /* number of first line in cl */
+ uint line_step ; /* number of real lines in cl */
- qtimer qtr; /* when running qnexus */
- struct thread *t_timer; /* when running threads */
+ /* Output side. */
+ vio_out_type_t vout_type ;
+ vf_state_t vout_state ;
+ vio_vf vout_next ; /* list of outputs */
+
+ vio_fifo obuf ; /* output fifo (if required) */
+
+ uint depth_mark ; /* depth of this vout */
+
+ /* General I/O */
+
+ bool blocking ; /* using blocking I/O (eg config read) */
+
+ vio_vfd vfd ; /* vty_io_basic "file descriptor" */
+
+ vty_timer_time read_timeout ;
+ vty_timer_time write_timeout ;
+
+ /* Pipe extras -- child and pipe returns */
+
+ vio_child child ; /* state of child */
+
+ vf_state_t pr_state ; /* iff VOUT_PIPE/VOUT_SH_CMD */
+ vio_vfd pr_vfd ; /* if pr_state != vf_closed */
+ vty_timer_time pr_timeout ; /* set once closing pipe return */
+
+ vf_state_t ps_state ; /* stderr for all pipe types */
+ vio_vfd ps_vfd ; /* if ps_state != vf_closed */
+ vty_timer_time ps_timeout ; /* set once closing pipe return */
+
+ vio_fifo ps_buf ; /* to be moved to vio->ps_buf */
} ;
-enum
+enum vty_readiness /* bit significant */
{
- on = true,
- off = false
+ not_ready = 0,
+ read_ready = BIT(0),
+ write_ready = BIT(1), /* may take precedence */
+} ;
+typedef enum vty_readiness vty_readiness_t ;
+
+/*------------------------------------------------------------------------------
+ * State of a vty command loop.
+ */
+enum vc_state
+{
+ vc_stopped, /* the command loop has stopped, and will not run
+ * again.
+ *
+ * or, the command loop has never started. */
+
+ vc_waiting, /* the command loop is waiting for I/O.
+ * command queue command loop only */
+
+ vc_running, /* the command loop is running, and the vty is
+ * in its hands. */
} ;
+typedef enum vc_state vc_state_t ;
-enum vty_readiness /* bit significant */
+/*------------------------------------------------------------------------------
+ * I/O and time-out error types
+ */
+enum vio_err_type
{
- not_ready = 0,
- read_ready = 1,
- write_ready = 2, /* takes precedence */
- now_ready = 4
+ verr_none = 0,
+
+ verr_vin = 1,
+ verr_vout = 2,
+ verr_pr = 3, /* pipe return (read) */
+ verr_ps = 4, /* pipe stderr return (read) */
+
+ verr_mask = BIT(4) - 1,
+
+ verr_io = BIT(4) * 0,
+ verr_to = BIT(4) * 1,
+
+ verr_io_vin = verr_vin | verr_io,
+ verr_io_vout = verr_vout | verr_io,
+ verr_io_pr = verr_pr | verr_io,
+ verr_io_ps = verr_ps | verr_io,
+
+ verr_to_vin = verr_vin | verr_to,
+ verr_to_vout = verr_vout | verr_to,
+ verr_to_pr = verr_pr | verr_to,
+ verr_to_ps = verr_ps | verr_to,
} ;
+typedef enum vio_err_type vio_err_type_t ;
+
+QFB_T(verr_mess, 200) ;
/*------------------------------------------------------------------------------
* The vty_io structure
+ *
+ * The main elements of the vty_io (aka vio) object are the vin and vout stacks.
+ *
+ * The first entry in the vin/vout stacks is the "base" and is a bit special.
+ * This entry is at stack depth 1. Stack depth 0 is reserved for all closed,
+ * or about to be.
+ *
+ * The vin_depth counts the number of command inputs which have been opened
+ * and pushed on the stack.
+ *
+ * The vout_depth reflects the vin_depth at which the vout was opened, and
+ * will be:
+ *
+ * * vout_depth == vin_depth + 1
+ *
+ * this is true after a command line such as:
+ *
+ * ...some command... > some_file
+ *
+ * When the command completes, and all output is pushed out, then the
+ * vout will be closed and the vout_depth reduced.
+ *
+ * * vout_depth == vin_depth
+ *
+ * this is true when a vty is set up (and the depths will be 1), and also
+ * if vin and a vout are opened together, as in:
+ *
+ * < some_file > some_other_file
+ *
+ * When the vin reaches eof (or fails) and is closed, then the vout_depth
+ * will be vin_depth + 1, which triggers the closing of the vout.
+ *
+ * * vout_depth < vin_depth
+ *
+ * This is true when one or vins have been opened and are stacked on top
+ * of each other. As the vins are closed, the vin_depth reduces until
+ * it hits the vout_depth, as above.
+ *
+ * When a vout is opened, the then current vout_depth is stored in the
+ * vf->depth_mark, and restored from there when the vout is closed.
+ *
+ * The vin_depth drives the closing of vouts. The vin_true_depth drives the
+ * closing of vins.
*/
+struct vty_cli ; /* forward reference -- vty_cli.h is
+ *not* included, because that refers
+ back to the vty_io ! */
-struct vty_io {
- struct vty* vty ; /* the related vty */
- char *name ; /* for VTY_TERM is IP address) */
+struct vty_io /* typedef appears above */
+{
+ vty vty ; /* the related vty */
/* List of all vty_io objects */
struct dl_list_pair(vty_io) vio_list ;
- /* List of all vty_io that are in monitor state */
- struct dl_list_pair(vty_io) mon_list ;
-
- /* VTY type and sock stuff */
- enum vty_type type;
-
- struct vio_sock sock ; /* for VTY_TERM and VTY_SHELL_SERV */
+ /* The vin/vout stacks */
- bool half_closed ; /* => on death watch list */
- bool closed ; /* => all I/O terminated
- will also be half_closed */
+ vio_vf vin ; /* vin stack */
+ vio_vf vin_base ;
+ uint vin_depth ;
+ uint vin_true_depth ; /* less than vin_depth when closing */
- const char* close_reason ; /* message to be sent, once all other
- output has completed, giving reason
- for closing the VTY. */
+ vio_vf vout ; /* vout stack */
+ vio_vf vout_base ;
+ uint vout_depth ;
- /* When writing configuration file */
- enum vty_type real_type ;
+ bool cancel ;
- int file_fd ;
- int file_error ;
+ /* Error handling */
- /*--------------------------------------------------------------------*/
- /* Command line and related state */
+ vio_fifo ebuf ; /* buffer for error message */
- keystroke_stream key_stream ;
+ int err_depth ; /* on error, close stack to this depth */
- /* 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:
+ /* State
*
- * cli_prompt_len -- the length of the prompt part.
- * (will be the "--more--" prompt in cli_more_wait)
+ * "blocking" is set for configuration reading VTY, so that everything is
+ * done with blocking I/O.
*
- * cli_extra_len -- the length of any ^X at the cursor position
- * (for when blocked waiting for queued command)
- *
- * cli_echo_suppress -- the user part of the command line is suppressed
- *
- * NB: cli_echo_suppress is only used for password entry.
+ * "state" as described above.
*/
- bool cli_drawn ;
- bool cli_dirty ;
-
- int cli_prompt_len ;
- int cli_extra_len ;
+ bool blocking ; /* => all I/O is blocking. */
- bool cli_echo_suppress ;
+ vc_state_t state ; /* command loop state */
+ cmd_return_code_t signal ; /* signal sent to command loop */
- /* "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 ;
+ char* close_reason ; /* MTYPE_TMP (if any) */
- /* State of the CLI
- *
- * cli_blocked -- blocked from processing keystrokes
- * cmd_in_progress -- command dispatched (may be queued)
- * cmd_out_enabled -- contents of the command FIFO may be written away
- * cli_more_wait -- is in "--more--" wait state
+ /* Pipe stderr return buffer.
*/
- bool cli_blocked ;
- bool cmd_in_progress ;
- bool cmd_out_enabled ;
- bool cli_more_wait ;
+ vio_fifo ps_buf ;
- /* This is used to control command output, so that each write_ready event
- * generates at most one tranche of output.
+ /* For ease of output, pointer to current vout->obuf
+ *
+ * Even when the vty is almost closed, there will remain a valid obuf,
+ * though anything sent to it under those conditions will be discarded.
*/
- bool cmd_out_done ;
+ vio_fifo obuf ;
- /* This is set only if the "--more--" handling is enabled */
- bool cli_more_enabled ;
-
- /* Command Line(s)
- *
- * cli_do -- when current command being prepared is completed (by
- * CR/LF or otherwise) this says what there now is to be done.
- *
- * cl -- current command line being prepared.
- *
- * clx -- current command line being executed.
+ /* The following is for "vty monitor".
*
- * NB: during command execution vty->buf is set to point at the '\0'
- * terminated current command line being executed.
+ * With the exception of the "monitor" flag, need the LOG_MUTEX in order
+ * to change any of this.
*/
- enum cli_do cli_do ;
-
- qstring_t cl ;
- qstring_t clx ;
-
- /* CLI output buffering */
- vio_fifo_t cli_obuf ;
-
- /* Command output buffering */
- vio_fifo_t cmd_obuf ;
+ struct dl_list_pair(vty_io) mon_list ;
- vio_line_control cmd_lc ;
+ bool monitor ; /* is in monitor state */
- /* Failure count for login attempts */
- int fail;
+ bool mon_kick ; /* vty needs a kick */
+ int maxlvl ; /* message level wish to see */
- /* History of commands */
- vector_t hist ;
- int hp ; /* History lookup current point */
- int hindex; /* History insert end point */
+ vio_fifo mbuf ; /* monitor output pending */
+} ;
- /* Window width/height as reported by Telnet. 0 => unknown */
- int width;
- int height;
+/*==============================================================================
+ * Assertions for suitable state to close things !
+ */
+Inline void
+VTY_ASSERT_CAN_CLOSE(vty vty)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_LOCKED() ;
- /* Configure lines. */
- int lines;
- bool lines_set ; /* true <=> explicitly set */
+ if (!vty->vio->blocking && !vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
+ } ;
+} ;
- /* Terminal monitor. */
- bool monitor ;
- bool monitor_busy ;
+Inline void
+VTY_ASSERT_CAN_CLOSE_VF(vio_vf vf)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_LOCKED() ;
- /* In configure mode. */
- bool config;
+ if (!vf->blocking && !vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
+ } ;
} ;
/*==============================================================================
* Functions
*/
-extern struct vty* uty_new (enum vty_type type, int sock_fd) ;
+extern int uty_out (vty_io vio, const char* format, ...) PRINTF_ATTRIBUTE(2, 3);
+Inline int uty_vprintf(vty_io vio, const char *format, va_list args) ;
+
+Inline void uty_out_clear(vty_io vio) ;
+Inline void uty_out_accept(vty_io vio) ;
+Inline void uty_out_reject(vty_io vio) ;
+
+extern vty uty_new (vty_type_t type, node_type_t node) ;
+extern void uty_close(vty_io vio) ;
+
+extern void uty_set_timeout(vty_io vio, vty_timer_time timeout) ;
+
+extern void uty_vin_new_context(vty_io vio, cmd_context context,
+ qpath file_here) ;
+extern void uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
+ vio_vfd_action* read_action,
+ vio_timer_action* read_timer_action,
+ usize ibuf_size) ;
+extern void uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type,
+ vio_vfd_action* write_action,
+ vio_timer_action* write_timer_action,
+ usize obuf_size,
+ bool after) ;
+extern cmd_return_code_t uty_vin_pop(vty_io vio, cmd_context context,
+ bool final) ;
+extern cmd_return_code_t uty_vout_pop(vty_io vio, bool final) ;
+
+
+extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
+ vfd_io_type_t io_type) ;
+extern void uty_vf_set_read(vio_vf vf, on_off_b on) ;
+extern void uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) ;
+extern void uty_vf_set_write(vio_vf vf, on_off_b on) ;
+extern void uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) ;
+
+extern cmd_return_code_t uty_vf_error(vio_vf vf, vio_err_type_t err_type,
+ int err) ;
+extern verr_mess_t uty_error_message(vio_vf vf, vio_err_type_t err_type,
+ int err, bool log) ;
+extern vio_child uty_child_register(pid_t pid, vio_vf parent) ;
+extern void vty_child_close_register(void) ;
+extern void uty_child_awaited(vio_child child, vty_timer_time timeout) ;
+extern bool uty_child_collect(vio_child child, vty_timer_time timeout,
+ bool final) ;
+extern void uty_child_dismiss(vio_child child, bool final) ;
+extern void uty_sigchld(void) ;
+
+extern void uty_child_signal_nexus_set(vty_io vio) ;
+extern void vty_child_signal_nexus_signal(void) ;
+extern void uty_child_signal_nexus_clear(vty_io vio) ;
+
extern void uty_open_listeners(const char *addr, unsigned short port,
const char *path) ;
+extern void uty_add_listener(int fd, vio_vfd_accept* accept) ;
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 const char* uty_get_name(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_monitor(vty_io vio, bool on) ;
-extern void uty_set_height(vty_io vio) ;
-extern void uty_cmd_output_start(vty_io vio) ;
+/*==============================================================================
+ * Inline Functions
+ */
-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) ;
+Inline bool
+uty_is_terminal(struct vty *vty)
+{
+ return vty->type == VTY_TERMINAL ;
+}
-extern int uty_read (vty_io vio, keystroke steal) ;
-extern int utysh_read (vty_io vio, qstring cl, qstring buf) ;
+Inline bool
+uty_is_shell_server(struct vty *vty)
+{
+ return vty->type == VTY_SHELL_SERVER ;
+}
+Inline bool
+uty_is_shell_client(struct vty *vty)
+{
+ return vty->type == VTY_SHELL_CLIENT ;
+}
-extern const char* uty_get_name(vty_io vio) ;
+/*------------------------------------------------------------------------------
+ * Command output -- append to output buffer.
+ */
+Inline int
+uty_vprintf(vty_io vio, const char *format, va_list args)
+{
+ return vio_fifo_vprintf(vio->obuf, format, args) ;
+} ;
-extern void uty_set_monitor(vty_io vio, bool on) ;
+/*------------------------------------------------------------------------------
+ * Clear command output -- discard anything in the buffer, but keep markers.
+ */
+Inline void
+uty_out_clear(vty_io vio)
+{
+ vio_fifo_clear(vio->obuf, false) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept command output -- advance any end_mark to current put position.
+ */
+Inline void
+uty_out_accept(vty_io vio)
+{
+ vio_fifo_step_end_mark(vio->obuf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reject command output -- discard anything after the end_mark in the buffer,
+ * but keep markers.
+ */
+Inline void
+uty_out_reject(vty_io vio)
+{
+ vio_fifo_back_to_end_mark(vio->obuf, true) ;
+} ;
#endif /* _ZEBRA_VTY_IO_H */
diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c
new file mode 100644
index 00000000..bf86b17b
--- /dev/null
+++ b/lib/vty_io_basic.c
@@ -0,0 +1,1110 @@
+/* VTY IO Basic Functions -- bottom level of VTY IO hierarchy
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "misc.h"
+
+#include <sys/socket.h>
+#include <fcntl.h>
+
+#include "vty_io_basic.h"
+#include "vty_local.h"
+#include "log.h"
+#include "pthread_safe.h"
+
+#include "memory.h"
+
+/*==============================================================================
+ * Base level open operations -- files and pipes
+ *
+ */
+
+/*------------------------------------------------------------------------------
+ * Try to open the given file for the given type of I/O.
+ *
+ * vfd_io_write => create if does not exist (with given cmode)
+ *
+ * vfd_io_append => set O_APPEND
+ * otherwise => truncate file if it does exist
+ *
+ * vfd_io_read => fail if does not exist
+ *
+ * vfd_io_read_write => create if does not exist (with given cmode)
+ *
+ * vfd_io_append => set O_APPEND
+ * otherwise => leave file as is
+ *
+ * vfd_io_blocking => do not open O_NONBLOCK
+ *
+ * (if none of the above, treat as vfd_io_read)
+ *
+ * Returns: if >= 0 -- the fd of the open file
+ * < 0 -- failed to open file -- see errno
+ *
+ * NB: "vfd_io_blocking" really means that the I/O will be blocking.
+ *
+ * "vfd_io_ps_blocking", used elsewhere, means that the actual I/O is
+ * non-blocking, but blocking, with time-out, is simulated by pselect.
+ */
+extern int
+uty_fd_file_open(const char* name, vfd_io_type_t io_type, mode_t cmode)
+{
+ mode_t um ;
+ int oflag ;
+ int fd ;
+ int err ;
+
+ oflag = 0 ;
+ if ((io_type & vfd_io_read_write) == vfd_io_read_write)
+ oflag = O_RDWR | O_CREAT ;
+ else if ((io_type & vfd_io_write) != 0)
+ oflag = O_WRONLY | O_CREAT ;
+ else
+ oflag = O_RDONLY ;
+
+ if ((io_type & vfd_io_write) != 0)
+ {
+ if ((io_type & vfd_io_append) != 0)
+ oflag |= O_APPEND ;
+ else if ((io_type & vfd_io_read) == 0)
+ oflag |= O_TRUNC ;
+
+ if (cmode == 0)
+ cmode = S_IRUSR | S_IWUSR ;
+
+ cmode &= (S_IRWXU | S_IRWXG | S_IRWXO) ;
+ }
+ else
+ cmode = 0 ;
+
+ if ((io_type & vfd_io_blocking) == 0)
+ oflag |= O_NONBLOCK ;
+
+ if (cmode != 0)
+ um = umask((S_IRWXU | S_IRWXG | S_IRWXO) & ~cmode) ;
+ else
+ um = (mode_t)0 ;
+
+ while (1)
+ {
+ fd = open(name, oflag, cmode) ;
+ if (fd >= 0)
+ break ;
+
+ err = errno ;
+ if (err == EINTR)
+ continue ;
+
+ break ;
+ } ;
+
+ if (cmode != 0)
+ umask(um) ;
+
+ if (fd < 0)
+ errno = err ;
+
+ return fd ;
+} ;
+
+/*==============================================================================
+ * Base level I/O and Timer handling....
+ *
+ * This is separated out so that the differences between running in a qpnexus
+ * and an old thread environment are encapsulated here.
+ */
+
+static void vio_vfd_mqb_kick(vio_vfd vfd) ;
+static void vio_vfd_mqb_free(vio_vfd vfd) ;
+
+/*==============================================================================
+ * File Descriptor handling
+ *
+ * Provides read/write ready handling in consistent manner -- so don't care
+ * whether is qpnexus or old thread environment.
+ *
+ * NB: in all cases, when a read/write event goes off, the read/write readiness
+ * is cleared and any read/write timer is stopped.
+ *
+ * In the qpnexus world, there is a small complication... the qpselect stuff
+ * for all vty lives in the cli thread, so there is a mechanism here to allow
+ * for messages from other threads to implement the necessary qpselect things,
+ * see above.
+ */
+
+static void vio_vfd_qps_read_action(qps_file qf, void* file_info) ;
+static void vio_vfd_qps_write_action(qps_file qf, void* file_info) ;
+static int vio_vfd_thread_read_action(struct thread *thread) ;
+static int vio_vfd_thread_write_action(struct thread *thread) ;
+
+static void vio_timer_squelch(vio_timer timer) ;
+
+Inline void
+vio_vfd_do_read_action(vio_vfd vfd)
+{
+ if (vfd->read_action != NULL)
+ vfd->read_action(vfd, vfd->action_info) ;
+}
+
+Inline void
+vio_vfd_do_write_action(vio_vfd vfd)
+{
+ if (vfd->write_action != NULL)
+ vfd->write_action(vfd, vfd->action_info) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create a new vfd structure.
+ *
+ * Note that sets the same action info for read, write, read timeout and
+ * write timeout.
+ *
+ * Sets the blocking_vf state if the io_type is vfd_io_ps_blocking (where the
+ * I/O is, in fact, non-blocking, but the vf level simulates blocking) or if
+ * the io_type is vfd_io_blocking (where the I/O really is blocking).
+ *
+ * The blocking_vf state disallows any attempt to set read/write ready/timeout,
+ * but enables open/close when not in cli thread.
+ *
+ * Must be in CLI thread if is neither vfd_io_ps_blocking nor vfd_io_blocking.
+ */
+extern vio_vfd
+vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info)
+{
+ vio_vfd vfd ;
+
+ vfd = XCALLOC(MTYPE_VTY, sizeof(struct vio_vfd)) ;
+
+ /* Has set:
+ *
+ * fd -- X -- see below
+ * active -- X -- see below
+ *
+ * type -- X -- see below
+ * io_type -- X -- see below
+ *
+ * blocking_vf -- X -- see below
+ *
+ * action_info -- NULL -- set below if !blocking_vf
+ *
+ * read_action -- NULL
+ * write_action -- NULL
+ *
+ * read_timer -- NULL -- set below if !blocking_vf
+ * write_timer -- NULL -- set below if !blocking_vf
+ *
+ * f.qf -- NULL -- set below if !blocking_vf
+ *
+ * f.thread.read -- NULL
+ * f.thread.write -- NULL
+ *
+ * queued -- false
+ *
+ * read_req -- all zeros -- none set
+ * write_req -- all zeros -- none set
+ *
+ * mqb -- NULL -- none, yet
+ */
+ confirm(VIO_TIMER_INIT_ZERO) ;
+
+ vfd->fd = fd ;
+ vfd->type = type ;
+ vfd->io_type = io_type ;
+
+ vfd->blocking_vf = (io_type & (vfd_io_blocking | vfd_io_ps_blocking)) != 0 ;
+
+ if (!vfd->blocking_vf)
+ {
+ VTY_ASSERT_CLI_THREAD() ;
+
+ if (vty_nexus)
+ {
+ vfd->f.qf = qps_file_init_new(NULL, NULL) ;
+ qps_add_file(vty_cli_nexus->selection, vfd->f.qf, vfd->fd, vfd) ;
+ } ;
+
+ vfd->read_timer = vio_timer_init_new(NULL, NULL, NULL) ;
+ vfd->write_timer = vio_timer_init_new(NULL, NULL, NULL) ;
+
+ vio_vfd_set_action_info(vfd, action_info) ;
+ } ;
+
+ return vfd ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the read action field for the given vio_vfd.
+ */
+extern void
+vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action)
+{
+ vfd->read_action = action ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the write action field for the given vio_vfd.
+ */
+extern void
+vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action)
+{
+ vfd->write_action = action ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the read action field for the given vio_vfd.
+ */
+extern void
+vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action)
+{
+ vio_timer_set_action(vfd->read_timer, action) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the write action field for the given vio_vfd.
+ */
+extern void
+vio_vfd_set_write_timeout_action(vio_vfd vfd, vio_timer_action* action)
+{
+ vio_timer_set_action(vfd->write_timer, action) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the action_info field for the given vio_vfd read/write action.
+ */
+extern void
+vio_vfd_set_action_info(vio_vfd vfd, void* action_info)
+{
+ vfd->action_info = action_info ;
+ vio_timer_set_info(vfd->read_timer, action_info) ;
+ vio_timer_set_info(vfd->write_timer, action_info) ;
+} ;
+
+#if 0
+/*------------------------------------------------------------------------------
+ * If there is a read action set for the give vio_vfd (if any), then kick it.
+ */
+extern void
+vio_vfd_kick_read_action(vio_vfd vfd)
+{
+ if ((vfd != NULL) && (vfd->read_action != NULL))
+ vio_vfd_do_read_action(vfd) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If there is a write action set for the give vio_vfd (if any), then kick it.
+ */
+extern void
+vio_vfd_kick_write_action(vio_vfd vfd)
+{
+ if ((vfd != NULL) && (vfd->write_action != NULL))
+ vio_vfd_do_read_action(vfd) ;
+} ;
+#endif
+
+/*------------------------------------------------------------------------------
+ * Close the read side of the given vfd (if any).
+ *
+ * If the vfd is a socket, then does a shutdown of the read side.
+ *
+ * If the vfd is not a socket and is read (only) closes the vfd.
+ *
+ * In any case, turns off any read ready and read ready timeout.
+ *
+ * Returns original vfd, or NULL if it has been closed.
+ *
+ * NB: if this is not a "blocking_vf", then MUST be in the cli thread.
+ */
+extern vio_vfd
+vio_vfd_read_close(vio_vfd vfd)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vfd == NULL)
+ return NULL ;
+
+ if (!vfd->blocking_vf)
+ VTY_ASSERT_CLI_THREAD() ;
+
+ if ((vfd->io_type & vfd_io_read) != 0)
+ {
+ if ((vfd->io_type & vfd_io_write) != 0)
+ {
+ /* read & write, so really half-close if can */
+ if (vfd->fd >= 0)
+ {
+ if (vfd->type == vfd_socket)
+ {
+ int rc = shutdown(vfd->fd, SHUT_RD) ;
+ if (rc < 0)
+ zlog_err("%s: shutdown() failed, fd=%d: %s", __func__,
+ vfd->fd, errtoa(errno, 0).str) ;
+ } ;
+ vio_vfd_set_read(vfd, off, 0) ;
+ vfd->io_type ^= vfd_io_read ; /* now write only ! */
+ } ;
+ }
+ else
+ {
+ /* read only, so fully close */
+ vfd = vio_vfd_close(vfd) ;
+ } ;
+ } ;
+
+ return vfd ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close the given vfd (if any).
+ *
+ * If there is an fd, close it. Stops any read/write waiting and releases all
+ * memory.
+ *
+ * NB: if this is not a "blocking_vf", then MUST be in the cli thread.
+ * Inter alia, this guarantees that cannot be in the middle of a read/write
+ * ready/timeout operation -- so the file can be closed down, and
+ * any pending ready/timeout will be swept away.
+ */
+extern vio_vfd
+vio_vfd_close(vio_vfd vfd)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vfd == NULL)
+ return NULL ;
+
+ if (!vfd->blocking_vf)
+ VTY_ASSERT_CLI_THREAD() ;
+
+ /* Close the underlying fd, if any */
+
+ if (vfd->fd >= 0)
+ {
+ while(1)
+ {
+ int rc = close(vfd->fd) ;
+
+ if (rc == 0)
+ break ;
+
+ if (errno == EINTR)
+ continue ;
+
+ zlog_err("%s: close() failed, fd=%d: %s", __func__, vfd->fd,
+ errtoa(errno, 0).str) ;
+ } ;
+ } ;
+
+ /* Clear out the vfd then free it */
+
+ if (vty_nexus)
+ {
+ if (vfd->f.qf != NULL)
+ {
+ assert(vfd->fd == qps_file_fd(vfd->f.qf)) ;
+ vfd->f.qf = qps_file_free(vfd->f.qf) ;
+ } ;
+
+ vio_vfd_mqb_free(vfd) ;
+ }
+ else
+ {
+ if (vfd->f.thread.read != NULL)
+ {
+ assert(vfd->fd == THREAD_FD(vfd->f.thread.read)) ;
+ thread_cancel(vfd->f.thread.read) ;
+ vfd->f.thread.read = NULL ;
+ } ;
+
+ if (vfd->f.thread.write != NULL)
+ {
+ assert(vfd->fd == THREAD_FD(vfd->f.thread.write)) ;
+ thread_cancel(vfd->f.thread.write) ;
+ vfd->f.thread.write = NULL ;
+ } ;
+
+ assert(vfd->mqb == NULL) ;
+ } ;
+
+ vfd->read_timer = vio_timer_reset(vfd->read_timer, free_it) ;
+ vfd->write_timer = vio_timer_reset(vfd->write_timer, free_it) ;
+
+ XFREE(MTYPE_VTY, vfd) ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set or unset read ready state on given vio_vfd (if any) if it is active.
+ *
+ * Do nothing if vfd NULL, fd < 0 or not a read type of fd.
+ *
+ * If setting read_on, starts any read timeout timer (or stops it if 0).
+ * If setting read off, stops any read timeout timer.
+ *
+ * NB: this can done from any thread, but if not done from the CLI thread,
+ * a message must be sent to the CLI thread to actually implement.
+ *
+ * NB: must NOT be a "blocking_vf" !!
+ */
+extern on_off_b
+vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_read) == 0))
+ return off ;
+
+ assert(!vfd->blocking_vf) ;
+
+ if (vty_is_cli_thread())
+ {
+ /* In the cli thread (effectively) so do things directly. */
+
+ vfd->read_req.set = false ; /* doing or overriding request */
+
+ if (on)
+ {
+ assert(vfd->read_action != NULL) ;
+
+ if (vty_nexus)
+ qps_enable_mode(vfd->f.qf, qps_read_mnum,
+ vio_vfd_qps_read_action) ;
+ else
+ {
+ if (vfd->f.thread.read == NULL)
+ vfd->f.thread.read = thread_add_read(vty_master,
+ vio_vfd_thread_read_action, vfd, vfd->fd) ;
+ } ;
+
+ vio_timer_set(vfd->read_timer, timeout) ;
+ }
+ else
+ {
+ if (vty_nexus)
+ qps_disable_modes(vfd->f.qf, qps_read_mbit) ;
+ else
+ {
+ if (vfd->f.thread.read != NULL)
+ thread_cancel (vfd->f.thread.read) ;
+ } ;
+
+ vio_timer_unset(vfd->read_timer) ;
+ } ;
+ }
+ else
+ {
+ /* In other threads, must send message to cli thread */
+ vfd->read_req.set = true ;
+ vfd->read_req.on = on ;
+ vfd->read_req.timeout = timeout ;
+
+ vio_timer_squelch(vfd->read_timer) ;
+
+ vio_vfd_mqb_kick(vfd) ;
+ } ;
+
+ return on ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set or unset write ready state on given vio_vfd (if any) if it is active.
+ *
+ * Do nothing if vfd NULL, fd < 0 or not a write type of fd.
+ *
+ * If setting write_on, starts any write timeout timer.
+ * If setting write off, stops any write timeout timer.
+ *
+ * NB: this can done from any thread, but if not done from the CLI thread,
+ * a message must be sent to the CLI thread to actually implement.
+ *
+ * NB: must NOT be a "blocking_vf" !!
+ */
+extern on_off_b
+vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_write) == 0))
+ return off ;
+
+ assert(!vfd->blocking_vf) ;
+
+ if (vty_is_cli_thread())
+ {
+ /* In the cli thread (effectively) so do things directly. */
+
+ vfd->write_req.set = false ; /* doing or overriding request */
+
+ if (on)
+ {
+ assert(vfd->write_action != NULL) ;
+
+ if (vty_nexus)
+ qps_enable_mode(vfd->f.qf, qps_write_mnum,
+ vio_vfd_qps_write_action) ;
+ else
+ {
+ if (vfd->f.thread.write == NULL)
+ vfd->f.thread.write = thread_add_write(vty_master,
+ vio_vfd_thread_write_action, vfd, vfd->fd) ;
+ } ;
+
+ vio_timer_set(vfd->write_timer, timeout) ;
+ }
+ else
+ {
+ if (vty_nexus)
+ qps_disable_modes(vfd->f.qf, qps_write_mbit) ;
+ else
+ {
+ if (vfd->f.thread.write != NULL)
+ thread_cancel (vfd->f.thread.write) ;
+ } ;
+
+ vio_timer_unset(vfd->write_timer) ;
+ } ;
+ }
+ else
+ {
+ /* In other threads, must send message to cli thread */
+ vfd->write_req.set = true ;
+ vfd->write_req.on = on ;
+ vfd->write_req.timeout = timeout ;
+
+ vio_timer_squelch(vfd->write_timer) ;
+
+ vio_vfd_mqb_kick(vfd) ;
+ } ;
+
+ return on ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to read
+ *
+ * Clears read ready state and unsets any read timer.
+ */
+static void
+vio_vfd_qps_read_action(qps_file qf, void* file_info)
+{
+ vio_vfd vfd ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vfd = file_info ;
+
+ assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ;
+
+ qps_disable_modes(vfd->f.qf, qps_read_mbit) ;
+ vio_timer_unset(vfd->read_timer) ;
+
+ vio_vfd_do_read_action(vfd) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- thread: ready to read
+ *
+ * Clears read ready state and unsets any read timer.
+ *
+ * NB: if !vfd->active, then has been closed in another thread, but close
+ * message is yet to be procesed.
+ */
+static int
+vio_vfd_thread_read_action(struct thread *thread)
+{
+ vio_vfd vfd ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vfd = THREAD_ARG(thread);
+
+ assert(vfd->fd == THREAD_FD(thread)) ;
+
+ vfd->f.thread.read = NULL ; /* implicitly */
+ vio_timer_unset(vfd->read_timer) ;
+
+ vio_vfd_do_read_action(vfd) ;
+
+ VTY_UNLOCK() ;
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to write
+ *
+ * Clears write ready state and unsets any write timer.
+ *
+ * NB: if !vfd->active, then has been closed in another thread, but close
+ * message is yet to be procesed.
+ */
+static void
+vio_vfd_qps_write_action(qps_file qf, void* file_info)
+{
+ vio_vfd vfd = file_info ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ;
+
+ qps_disable_modes(vfd->f.qf, qps_write_mbit) ;
+ vio_timer_unset(vfd->write_timer) ;
+
+ vio_vfd_do_write_action(vfd) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- thread: ready to write
+ *
+ * Clears write ready state and unsets any write timer.
+ *
+ * NB: if !vfd->active, then has been closed in another thread, but close
+ * message is yet to be procesed.
+ */
+static int
+vio_vfd_thread_write_action(struct thread *thread)
+{
+ vio_vfd vfd = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ assert(vfd->fd == THREAD_FD(thread)) ;
+ vio_timer_unset(vfd->write_timer) ;
+
+ vfd->f.thread.write = NULL ; /* implicitly */
+
+ vio_vfd_do_write_action(vfd) ;
+
+ VTY_UNLOCK() ;
+ return 0 ;
+} ;
+
+/*==============================================================================
+ * Message handling, so that other threads can signal for output to be
+ * dispatched !
+ *
+ * There is one message block per vfd. It is only ever touched under the
+ * vty_mutex.
+ *
+ * Once it is dispatched it is marked 'active'. Can still be changed, but no
+ * further dispatch is required. When it has been dequeued and processed,
+ * it is marked inactive.
+ *
+ * If the vfd is closed while the message is active, the pointer from the mqb
+ * to the vfd is set NULL, and when the message is dequeued and actioned,
+ * the mqb is destroyed.
+ */
+
+static void vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag) ;
+
+/*------------------------------------------------------------------------------
+ * Dispatch mqb, if not already active
+ */
+static void
+vio_vfd_mqb_kick(vio_vfd vfd)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (!vfd->queued)
+ {
+ vfd->queued = true ;
+
+ if (vfd->mqb == NULL)
+ vfd->mqb = mqb_init_new(NULL, vio_vfd_mqb_action, vfd) ;
+
+ mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_priority) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free mqb, if exists.
+ */
+static void
+vio_vfd_mqb_free(vio_vfd vfd)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vfd->queued)
+ mqb_set_arg0(vfd->mqb, NULL) ; /* mqb will suicide */
+ else
+ mqb_free(vfd->mqb) ; /* kill now (if any) */
+
+ vfd->queued = false ;
+ vfd->mqb = NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Action routine for the read/write on/off setting message.
+ *
+ * If the mqb is marked to close, then it and any qps_file it points to have
+ * been cut loose, and now is the time to close the fd and release the
+ * qps_file, along with releasing the mqb.
+ *
+ * If the mqb is being revoked
+ */
+static void
+vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag)
+{
+ vio_vfd vfd ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vfd = mqb_get_arg0(mqb) ;
+
+ if (vfd != NULL)
+ {
+ assert(mqb == vfd->mqb) ;
+ vfd->queued = false ;
+ } ;
+
+ if ((flag != mqb_destroy) && (vfd != NULL))
+ {
+ if (vfd->read_req.set)
+ vio_vfd_set_read(vfd, vfd->read_req.on, vfd->read_req.timeout) ;
+ if (vfd->write_req.set)
+ vio_vfd_set_write(vfd, vfd->write_req.on, vfd->write_req.timeout) ;
+ }
+ else
+ {
+ mqb_free(mqb) ; /* Suicide */
+ if (vfd != NULL)
+ vfd->mqb = NULL ; /* make sure vfd knows */
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*==============================================================================
+ * Listener Handling
+ *
+ *
+ */
+
+static void vio_accept(vio_vfd vfd, void* info) ;
+
+/*------------------------------------------------------------------------------
+ * Create a new listener object for the newly opened listener socket.
+ *
+ * Sets the accept action that will be called, and passed the fd of the listener
+ * socket, when the listen socket goes 'read ready'.
+ *
+ * Returns address of newly created listener structure.
+ */
+extern vio_listener
+vio_listener_new(int fd, vio_vfd_accept* accept_action)
+{
+ vio_listener listener ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ listener = XCALLOC(MTYPE_VTY, sizeof(struct vio_listener)) ;
+ /* sets the next pointer to NULL */
+
+ listener->vfd = vio_vfd_new(fd, vfd_listener, vfd_io_read, listener) ;
+
+ listener->accept_action = accept_action ;
+
+ vio_vfd_set_read_action(listener->vfd, vio_accept) ;
+ vio_vfd_set_read(listener->vfd, on, 0) ;
+
+ return listener ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close listener and free listener structure.
+ * Stops any read waiting and releases all memory.
+ *
+ * NB: assumes that the structure has been removed from any list.
+ */
+extern void
+vio_listener_close(vio_listener listener)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ vio_vfd_close(listener->vfd) ;
+ XFREE(MTYPE_VTY, listener) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action -- this is the read_action from the listener vfd.
+ *
+ * info points at the listener object.
+ */
+static void
+vio_accept(vio_vfd vfd, void* info)
+{
+ vio_listener listener ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ listener = info ;
+ assert(vfd == listener->vfd) ;
+
+ vio_vfd_set_read(listener->vfd, on, 0) ;
+
+ listener->accept_action(vfd->fd) ;
+} ;
+
+/*==============================================================================
+ * Timer Handling
+ *
+ * Provides timer primitives that work either in qnexus environment or in
+ * a thread environment. Timer times are seconds.
+ *
+ * The main difference is that thread environment timers are 'one-shot', set up
+ * for one timing run and then destroyed.
+ */
+
+static void vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) ;
+static int vio_timer_thread_action(struct thread *thread) ;
+
+/*------------------------------------------------------------------------------
+ * Allocate and/or initialise vio_timer structure.
+ */
+extern vio_timer
+vio_timer_init_new(vio_timer timer, vio_timer_action* action, void* action_info)
+{
+ if (timer == NULL)
+ timer = XCALLOC(MTYPE_VTY, sizeof(vio_timer_t)) ;
+ else
+ memset(timer, 0, sizeof(vio_timer_t)) ;
+
+ confirm(VIO_TIMER_INIT_ZERO) ;
+
+ /* active -- 0, false
+ * cancel -- 0, false
+ * t -- NULL, no qtr and no thread
+ */
+
+ timer->action = action ;
+ timer->action_info = action_info ;
+
+ return timer ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the action field for the given timer.
+ */
+extern void
+vio_timer_set_action(vio_timer timer, vio_timer_action* action)
+{
+ if (timer != NULL)
+ timer->action = action ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the info field for the given timer.
+ */
+extern void
+vio_timer_set_info(vio_timer timer, void* action_info)
+{
+ if (timer != NULL)
+ timer->action_info = action_info ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Squelch the given timer -- if it goes off, do not call the action routine,
+ * and leave the timer inactive.
+ *
+ * Used when doing read/write ready from not-cli thread.
+ */
+static void
+vio_timer_squelch(vio_timer timer)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (timer != NULL)
+ timer->squelch = true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Reset vio_timer structure.
+ *
+ * Stops any timer and releases all memory, if required.
+ */
+extern vio_timer
+vio_timer_reset(vio_timer timer, free_keep_b free_structure)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (timer != NULL)
+ {
+ VTY_ASSERT_CLI_THREAD() ;
+
+ if (timer->t.anon != NULL)
+ {
+ if (vty_nexus)
+ qtimer_free(timer->t.qtr) ; /* stop and discard qtimer even
+ * if keeping vio_timer */
+ else
+ thread_cancel(timer->t.thread) ;
+
+ timer->t.anon = NULL ;
+ } ;
+
+ timer->active = false ;
+ timer->squelch = false ;
+
+ if (free_structure)
+ XFREE(MTYPE_VTY, timer) ; /* sets timer = NULL */
+ } ;
+
+ return timer ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set vio_timer going, with the given time (in seconds).
+ *
+ * If timer is running, set to new time.
+ *
+ * If the time == 0, stop any current timer, do not restart.
+ */
+extern void
+vio_timer_set(vio_timer timer, vty_timer_time time)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (time == 0)
+ {
+ vio_timer_unset(timer) ;
+ return ;
+ } ;
+
+ assert(timer->action != NULL) ;
+
+ if (vty_nexus)
+ {
+ if (timer->t.qtr == NULL) /* allocate qtr if required */
+ timer->t.qtr = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ vio_timer_qtr_action, timer) ;
+ qtimer_set(timer->t.qtr, qt_add_monotonic(QTIME(time)), NULL) ;
+ }
+ else
+ {
+ if (timer->t.thread != NULL)
+ thread_cancel(timer->t.thread) ;
+ timer->t.thread = thread_add_timer(vty_master,
+ vio_timer_thread_action, timer, time) ;
+ } ;
+
+ timer->active = true ;
+ timer->squelch = false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Stop vio_timer, if any.
+ */
+extern void
+vio_timer_unset(vio_timer timer)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (timer->active)
+ {
+ if (vty_nexus)
+ {
+ assert(timer->t.qtr != NULL) ;
+ qtimer_unset(timer->t.qtr) ;
+ }
+ else
+ {
+ assert(timer->t.thread != NULL) ;
+ thread_cancel(timer->t.thread) ;
+ timer->t.thread = NULL ;
+ } ;
+
+ timer->active = false ;
+ } ;
+
+ timer->squelch = false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: deal with timer timeout.
+ */
+static void
+vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when)
+{
+ vio_timer timer ;
+ vty_timer_time time ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ timer = timer_info ;
+
+ if ((timer->action != NULL) && (!timer->squelch))
+ {
+ time = timer->action(timer, timer->action_info) ;
+ if (time != 0)
+ vio_timer_set(timer, time) ;
+ else
+ timer->active = false ;
+ }
+ else
+ {
+ timer->squelch = false ;
+ timer->active = false ;
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- thread: deal with timer timeout.
+ */
+static int
+vio_timer_thread_action(struct thread *thread)
+{
+ vio_timer timer ;
+ vty_timer_time time ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ timer = THREAD_ARG(thread) ;
+ timer->t.thread = NULL ; /* implicitly */
+
+ if ((timer->action != NULL) && (!timer->squelch))
+ {
+ time = timer->action(timer, timer->action_info) ;
+ if (time != 0)
+ vio_timer_set(timer, time) ;
+ else
+ timer->active = false ;
+ }
+ else
+ {
+ timer->squelch = false ;
+ timer->active = false ;
+ } ;
+
+ VTY_UNLOCK() ;
+ return 0;
+} ;
diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h
new file mode 100644
index 00000000..cbeb9fed
--- /dev/null
+++ b/lib/vty_io_basic.h
@@ -0,0 +1,232 @@
+/* VTY IO Structure and Functions -- header
+ * Virtual terminal [aka TeletYpe] interface routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_IO_BASIC_H
+#define _ZEBRA_VTY_IO_BASIC_H
+
+#include "misc.h"
+
+//#include "vty_local.h"
+
+#include "qpselect.h"
+#include "thread.h"
+#include "mqueue.h"
+
+/*==============================================================================
+ * Here are structures and other definitions which are shared by all the
+ * VTY I/O hierarchy, providing the basic read/write ready functions and
+ * timer functions.
+ *
+ * This is separated out so that the differences between running in a qpnexus
+ * and an old thread environment are encapsulated here.
+ */
+
+enum vfd_type
+{
+ vfd_none = 0,
+ vfd_socket,
+ vfd_file,
+ vfd_pipe,
+ vfd_listener,
+} ;
+typedef enum vfd_type vfd_type_t ;
+
+enum vfd_io_type /* NB: *bit*significant* */
+{
+ vfd_io_none = 0,
+
+ vfd_io_read = BIT(0),
+ vfd_io_write = BIT(1),
+ vfd_io_read_write = vfd_io_read | vfd_io_write,
+
+ vfd_io_append = BIT(2),
+ vfd_io_blocking = BIT(3),
+
+ vfd_io_ps_blocking = BIT(4),
+} ;
+typedef enum vfd_io_type vfd_io_type_t ;
+
+/*------------------------------------------------------------------------------
+ * Timers -- implemented as qtimer or thread timer, depending on environment.
+ *
+ * Timer action function returns a new value for the timer; 0 => off.
+ */
+typedef unsigned long vty_timer_time ; /* Time out time in seconds */
+
+typedef struct vio_timer* vio_timer ;
+typedef vty_timer_time vio_timer_action(vio_timer timer, void* action_info) ;
+
+struct vio_timer
+{
+ vio_timer_action* action ; /* who do we call */
+ void* action_info ;
+
+ bool active ;
+ bool squelch ; /* used when message pending */
+
+ union {
+ qtimer qtr ; /* when running qnexus */
+ struct thread* thread ; /* when running threads */
+ void* anon ;
+ } t ;
+} ;
+typedef struct vio_timer vio_timer_t[1] ;
+
+enum { VIO_TIMER_INIT_ZERO = true } ;
+
+/*------------------------------------------------------------------------------
+ * File descriptors -- looks after ready to read and/or write.
+ *
+ * Implemented as qps_file or as read/write thread, depending on the
+ * environment.
+ */
+typedef struct vio_vfd* vio_vfd ;
+
+typedef void vio_vfd_action(vio_vfd vfd, void* action_info) ;
+
+typedef struct
+{
+ bool set ;
+ on_off_b on ;
+ vty_timer_time timeout ;
+} vfd_request_t ;
+
+struct vio_vfd
+{
+ int fd ;
+
+ vfd_type_t type ; /* used for half-close */
+ vfd_io_type_t io_type ; /* read, write, read/write */
+
+ /* The rest of the vfd is to do with managing read/write ready and
+ * read/write timeouts.
+ *
+ * At the vf level we manage "blocking" vfs, which do not and must not
+ * use read/write ready or read/write timeouts. When the vfd is created
+ * this is passed, so that mistakes can be spotted !
+ *
+ * Non-blocking vfs may only be created and closed in the cli thread (or
+ * when running in the legacy threads environment). This is because can
+ * only dismantle qps_file structure while in the owning thread.
+ */
+ bool blocking_vf ; /* Reject read/write ready etc. */
+
+ void* action_info ; /* for all action and time-out */
+
+ vio_vfd_action* read_action ;
+ vio_vfd_action* write_action ;
+
+ vio_timer read_timer ;
+ vio_timer write_timer ;
+
+ union
+ {
+ qps_file qf ; /* when running qnexus */
+
+ struct /* when running threads */
+ {
+ struct thread* read ;
+ struct thread* write ;
+ } thread ;
+ } f ;
+
+ /* To support remote setting clearing of read/write ready and read/write
+ * timeout -- by remote we mean anything such action not in the cli thread.
+ *
+ * This is required because the qpselect and qtimer stuff assumes that those
+ * structures are private to the thread whose qnexus they belong to !
+ *
+ * This stuff is only required when running multi-pthreaded.
+ */
+ bool queued ; /* message is on queue */
+
+ vfd_request_t read_req ;
+ vfd_request_t write_req ;
+
+ mqueue_block mqb ; /* message if any */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Listeners
+ */
+
+typedef struct vio_listener* vio_listener ;
+
+typedef void vio_vfd_accept(int fd) ;
+
+struct vio_listener
+{
+ vio_listener next ; /* ssl type list */
+ vio_vfd vfd ;
+ vio_vfd_accept* accept_action ;
+};
+
+/*==============================================================================
+ * Functions
+ */
+extern int uty_fd_file_open(const char* name, vfd_io_type_t io_type,
+ mode_t cmode) ;
+
+extern vio_vfd vio_vfd_new(int fd, vfd_type_t type,
+ vfd_io_type_t io_type, void* action_info) ;
+extern void vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action) ;
+extern void vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action) ;
+extern void vio_vfd_set_read_timeout_action(vio_vfd vfd,
+ vio_timer_action* action) ;
+extern void vio_vfd_set_write_timeout_action(vio_vfd vfd,
+ vio_timer_action* action) ;
+extern void vio_vfd_set_action_info(vio_vfd vfd, void* action_info) ;
+extern vio_vfd vio_vfd_read_close(vio_vfd vfd) ;
+extern vio_vfd vio_vfd_close(vio_vfd vfd) ;
+extern on_off_b vio_vfd_set_read(vio_vfd vfd, on_off_b on,
+ vty_timer_time timeout) ;
+extern on_off_b vio_vfd_set_write(vio_vfd vfd, on_off_b on,
+ vty_timer_time timeout) ;
+Inline int vio_vfd_fd(vio_vfd vfd) ;
+
+extern vio_listener vio_listener_new(int fd, vio_vfd_accept* accept) ;
+extern void vio_listener_close(vio_listener listener) ;
+
+extern vio_timer vio_timer_init_new(vio_timer timer, vio_timer_action* action,
+ void* action_info) ;
+extern vio_timer vio_timer_reset(vio_timer timer, free_keep_b free_structure) ;
+extern void vio_timer_set_action(vio_timer timer, vio_timer_action* action) ;
+extern void vio_timer_set_info(vio_timer timer, void* action_info) ;
+extern void vio_timer_set(vio_timer timer, vty_timer_time time) ;
+extern void vio_timer_unset(vio_timer timer) ;
+
+/*==============================================================================
+ * Inline Functions
+ */
+
+/*------------------------------------------------------------------------------
+ * Return the fd from a vio_vfd structure
+ */
+Inline int
+vio_vfd_fd(vio_vfd vfd)
+{
+ return vfd->fd ;
+} ;
+
+#endif /* _ZEBRA_VTY_IO_BASIC_H */
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c
new file mode 100644
index 00000000..d1d1a50a
--- /dev/null
+++ b/lib/vty_io_file.c
@@ -0,0 +1,2761 @@
+/* VTY I/O for Files
+ *
+ * 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 "misc.h"
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "command_local.h"
+
+#include "vty_io.h"
+#include "vty_io_file.h"
+#include "vty_io_basic.h"
+#include "vty_command.h"
+#include "network.h"
+#include "sigevent.h"
+#include "pthread_safe.h"
+#include "qlib_init.h"
+
+/*==============================================================================
+ * Here we handle:
+ *
+ * * VIN_CONFIG/VOUT_CONFIG -- configuration file read/write
+ *
+ * * VIN_FILE/VOUT_FILE -- file pipe read/write
+ *
+ * * VIN_PIPE/VOUT_PIPE -- shell command pipe read/write
+ */
+
+#define PIPEFILE_MODE CONFIGFILE_MASK
+
+enum
+{
+ file_timeout = 20, /* for file read/write */
+
+ pipe_timeout = 30, /* for pipe read/write */
+
+ child_timeout = 10, /* for collecting child process */
+
+ config_buffer_size = 64 * 1024, /* for config reading */
+ file_buffer_size = 16 * 1024, /* for other file read/write */
+
+ pipe_buffer_size = 4 * 1024, /* for pipe read/write */
+} ;
+
+/*==============================================================================
+ * VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG.
+ *
+ * The creation of configuration files is handled elsewhere, as is the actual
+ * opening of input configuration files.
+ *
+ * Once open, VIN_CONFIG and VOUT_CONFIG are read/written in the same way as
+ * VIN_FILE and VOUT_FILE.
+ *
+ * These files are handled as "blocking" because the parent vio is marked as
+ * "blocking".
+ */
+
+/*------------------------------------------------------------------------------
+ * Set up VTY on which to read configuration file -- using already open fd.
+ *
+ * Sets the vout_base to be VOUT_STDERR.
+ *
+ * NB: sets up a blocking vio -- so that the vin_base, vout_base and all files
+ * and pipes will block waiting for I/O to complete (or to time out).
+ *
+ * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for
+ * disposing of its copy, if required.
+ */
+extern vty
+vty_config_read_open(int fd, const char* name, bool full_lex)
+{
+ vty vty ;
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+
+ /* Create the vty and its vio.
+ *
+ * NB: VTY_CONFIG_READ type vty is set vio->blocking.
+ */
+ vty = uty_new(VTY_CONFIG_READ, CONFIG_NODE) ;
+
+ vio = vty->vio ;
+
+ /* Create the vin_base/vout_base vf
+ *
+ * NB: don't need to specify vfd_io_ps_blocking, because the vio is set
+ * blocking.
+ */
+ qassert(vio->blocking) ;
+
+ vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
+
+ uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, config_buffer_size) ;
+ vf->read_timeout = file_timeout ;
+ uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, pipe_buffer_size, true) ;
+ vf->write_timeout = file_timeout ;
+
+ /* Deal with the possibility that while reading the configuration file, may
+ * use a pipe, and therefore may block waiting to collect a child process.
+ *
+ * Before there is any chance of a SIGCHLD being raised for a child of the
+ * configuration file, invoke the magic required for SIGCHLD to wake up
+ * a pselect() while waiting to collect child.
+ */
+ uty_child_signal_nexus_set(vio) ;
+
+ VTY_UNLOCK() ;
+
+ return vty ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after config input file has been closed.
+ *
+ * There is now no further possibility that will block waiting for child to be
+ * collected.
+ *
+ * Nothing further required -- input comes to a halt.
+ */
+extern cmd_return_code_t
+uty_config_read_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_state == vf_end) ;
+
+ uty_child_signal_nexus_clear(vf->vio) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push the given fd as the VOUT_CONFIG.
+ *
+ * Note that the fd is assumed to be genuine I/O blocking, so this is a
+ * "blocking" vf, and so can be opened and closed in the cmd thread.
+ */
+extern void
+vty_config_write_open(vty vty, int fd)
+{
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ;
+
+ vf = uty_vf_new(vio, "config write", fd, vfd_file,
+ vfd_io_read | vfd_io_blocking) ;
+ uty_vout_push(vio, vf, VOUT_CONFIG, NULL, NULL, file_buffer_size, false) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write away any pending stuff, and pop the VOUT_CONFIG.
+ *
+ * This is a blocking vf -- so any outstanding output will complete here.
+ */
+extern cmd_return_code_t
+vty_config_write_close(struct vty* vty)
+{
+ cmd_return_code_t ret ;
+ VTY_LOCK() ;
+
+ qassert(vty->vio->vout->vout_type == VOUT_CONFIG) ;
+
+ ret = uty_vout_pop(vty->vio, false) ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ * VTY File I/O -- VIN_FILE and VOUT_FILE
+ * also read/write/close for VIN_CONFIG and VOUT_CONFIG.
+ *
+ * This is for input and output of configuration and piped files.
+ *
+ * When reading the configuration (and piped stuff in the configuration) I/O
+ * is blocking... nothing else can run while this is going on. Otherwise,
+ * all I/O is non-blocking. The actual I/O is non-blocking, the "blocking"
+ * I/O is manufactured using a mini-pselect, so can time-out file writing
+ * before too long.
+ */
+static void vty_file_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_file_read_timeout(vio_timer timer,
+ void* action_info) ;
+
+static void vty_file_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_file_write_timeout(vio_timer timer,
+ void* action_info) ;
+
+static cmd_return_code_t uty_fifo_command_line(vio_vf vf, cmd_action action) ;
+
+/*------------------------------------------------------------------------------
+ * Open file for input, to be read as commands -- VIN_FILE.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * If opens OK, save the current context in the current vin (before pushing
+ * the new vin).
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open
+ *
+ * If "blocking" vf, this can be called from any thread, otherwise must be the
+ * cli thread -- see uty_vfd_new().
+ */
+extern cmd_return_code_t
+uty_file_read_open(vty_io vio, qstring name, cmd_context context)
+{
+ cmd_return_code_t ret ;
+ qpath path ;
+ const char* pns ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->vin != NULL) ; /* Not expected to be vin_base */
+
+ /* Now is the time to complete the name, if required. */
+
+ path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
+ pns = qpath_string(path) ;
+
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
+ iot = vfd_io_read ;
+
+ fd = uty_fd_file_open(pns, iot, (mode_t)0) ; /* cmode not required */
+
+ /* If failed, report.
+ * If OK save context & update "here" then create and push the vin.
+ */
+ if (fd < 0)
+ {
+ uty_out(vio, "%% Could not open input file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
+ }
+ else
+ {
+ uty_vin_new_context(vio, context, path) ;
+
+ vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
+ uty_vin_push(vio, vf, VIN_FILE, vty_file_read_ready,
+ vty_file_read_timeout, file_buffer_size) ;
+ vf->read_timeout = file_timeout ;
+
+ ret = CMD_SUCCESS ;
+ } ;
+
+ qpath_free(path) ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open file for output -- VOUT_FILE.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * "after" <=> this vout is being opened after a vin being opened at the same
+ * time.
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open
+ *
+ * If "blocking" vf, this can be called from any thread, otherwise must be the
+ * cli thread -- see uty_vfd_new().
+ */
+extern cmd_return_code_t
+uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context,
+ bool after)
+{
+ cmd_return_code_t ret ;
+ qpath path ;
+ const char* pns ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Now is the time to complete the name, if required. */
+
+ path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
+ pns = qpath_string(path) ;
+
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
+ iot = vfd_io_write | (append ? vfd_io_append : 0) ;
+
+ fd = uty_fd_file_open(pns, iot, PIPEFILE_MODE) ;
+
+ /* If failed, report.
+ * If OK, create and push the vout.
+ */
+ if (fd < 0)
+ {
+ uty_out(vio, "%% Could not open output file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
+ }
+ else
+ {
+ vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
+ uty_vout_push(vio, vf, VOUT_FILE, vty_file_write_ready,
+ vty_file_write_timeout, file_buffer_size, after) ;
+ vf->write_timeout = file_timeout ;
+
+ ret = CMD_SUCCESS ;
+ } ;
+
+ qpath_free(path) ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command line fetch from a file -- VIN_FILE or VIN_CONFIG, in vf_open state.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- would not wait for input <=> non-blocking
+ * CMD_EOF -- ran into EOF (and nothing else)
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
+ *
+ * In "non-blocking" state, on CMD_WAITING sets read ready, which will
+ * vty_cmd_signal() the command loop to come round again.
+ *
+ * In "blocking" state, will not return until have something, or there is
+ * nothing to be had, or times out.
+ *
+ * This can be called in any thread.
+ *
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * NB: the vin_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR return
+ * code does the trick.
+ */
+extern cmd_return_code_t
+uty_file_fetch_command_line(vio_vf vf, cmd_action action)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ qassert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ;
+ qassert(vf->vin_state == vf_open) ;
+
+ while (1)
+ {
+ cmd_return_code_t ret ;
+
+ /* Try to complete line straight from the buffer.
+ *
+ * If buffer is empty and have seen eof on the input, signal CMD_EOF.
+ */
+ ret = uty_fifo_command_line(vf, action) ;
+
+ if (ret != CMD_WAITING)
+ return ret ;
+
+ /* Could not complete line and exhausted contents of fifo */
+ while (1)
+ {
+ qps_mini_t qm ;
+ int get ;
+
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 1) ;
+
+ if (get > 0)
+ break ; /* loop back to uty_fifo_command_line() */
+
+ if (get == -1)
+ return uty_vf_error(vf, verr_io_vin, errno) ;
+
+ if (get == -2)
+ {
+ /* Hit end of file, so set the vf into vf_end.
+ *
+ * NB: does not know has hit eof until tries to read and nothing
+ * is returned.
+ *
+ * Loop back so that uty_fifo_command_line() can reconsider, now
+ * that we know that there is no more data to come -- it may
+ * signal CMD_SUCCESS (non-empty final line) or CMD_EOF.
+ */
+ qassert(vio_fifo_empty(vf->ibuf)) ;
+
+ vf->vin_state = vf_end ;
+
+ break ; /* loop back to uty_fifo_command_line() */
+ } ;
+
+ /* Would block -- for non-blocking return CMD_WAITING, for
+ * blocking we block here with a timeout, and when there is more
+ * to read, loop back to vio_fifo_read_nb().
+ */
+ qassert(get == 0) ;
+
+ if (!vf->blocking)
+ {
+ uty_vf_set_read(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_read_mnum,
+ vf->read_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back to vio_fifo_read_nb() */
+
+ return uty_vf_error(vf, verr_to_vin, 0) ;
+ } ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Try to complete a command line for the given vf, from the current input
+ * fifo -- performs NO I/O.
+ *
+ * Expects that most of the time will be able to set the vf->cl to point
+ * directly at a command line in the fifo -- but at the edge of fifo buffers
+ * (and if get continuation lines) will copy line fragments to the vf->cl.
+ *
+ * Returns: CMD_SUCCESS -- have a command line.
+ * CMD_EOF -- there is no more
+ * CMD_WAITING -- waiting for more
+ *
+ * If vf->vin_state == vf_end, will return CMD_SUCCESS if have last part line
+ * in hand. Otherwise will return CMD_EOF, any number of times.
+ */
+static cmd_return_code_t
+uty_fifo_command_line(vio_vf vf, cmd_action action)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ if (vf->line_complete)
+ {
+ vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */
+
+ vf->line_complete = false ;
+ vf->line_number += vf->line_step ;
+
+ qs_set_len_nn(vf->cl, 0) ;
+ vf->line_step = 0 ;
+ } ;
+
+ while (1)
+ {
+ char* s, * p, * e ;
+ ulen have ;
+ ulen len ;
+ bool eol ;
+
+ /* Get what we can from the fifo */
+ have = vio_fifo_get(vf->ibuf) ;
+
+ /* If fifo is empty, may be last line before eof, eof or waiting */
+ if (have == 0)
+ {
+ if (vf->vin_state == vf_end)
+ {
+ if (qs_len_nn(vf->cl) > 0)
+ break ; /* have non-empty last line */
+ else
+ return CMD_EOF ;
+ } ;
+
+ return CMD_WAITING ;
+ } ;
+
+ qassert(vf->vin_state != vf_end) ; /* not empty => not eof */
+
+ /* Try to find a '\n' -- converting all other control chars to ' '
+ *
+ * When we find '\n' step back across any trailing ' ' (which includes
+ * any control chars before the '\n').
+ *
+ * This means that we cope with "\r\n" line terminators. But not
+ * anything more exotic.
+ */
+ p = s = vio_fifo_get_ptr(vf->ibuf) ;
+ e = s + have ; /* have != 0 */
+
+ eol = false ;
+ while (p < e)
+ {
+ if (*p++ < 0x20)
+ {
+ if (*(p-1) != '\n')
+ {
+ *(p-1) = ' ' ; /* everything other than '\n' */
+ continue ;
+ } ;
+
+ ++vf->line_step ; /* got a '\n' */
+
+ eol = true ;
+ break ;
+ } ;
+ } ;
+
+ /* Step past what have just consumed -- we have a hold_mark, so
+ * stuff is still in the fifo.
+ */
+ vio_fifo_step(vf->ibuf, p - s) ;
+
+ /* If not found '\n', then we have a line fragment that needs to be
+ * appended to any previous line fragments.
+ *
+ * Loops back to try to get some more form the fifo.
+ */
+ if (!eol)
+ {
+ qs_append_str_n(vf->cl, s, p - s) ;
+ continue ;
+ } ;
+
+ /* Have an eol. Step back across the '\n' and any trailing spaces
+ * we have in hand.
+ */
+ do --p ; while ((p > s) && (*(p-1) == ' ')) ;
+
+ /* If we have nothing so far, set alias to point at what we have in
+ * the fifo. Otherwise, append to what we have.
+ *
+ * End up with: s = start of entire line, so far.
+ * p = end of entire line so far.
+ */
+ len = p - s ; /* length to add */
+ if (qs_len_nn(vf->cl) == 0)
+ qs_set_alias_n(vf->cl, s, len) ;
+ else
+ {
+ if (len != 0)
+ qs_append_str_n(vf->cl, s, len) ;
+
+ s = qs_char_nn(vf->cl) ;
+ p = s + qs_len_nn(vf->cl) ;
+
+ if ((len == 0) && (p > s) && (*(p-1) == ' '))
+ {
+ /* Have an empty end of line section, and the last character
+ * of what we have so far is ' ', so need now to trim trailing
+ * spaces off the stored stuff.
+ */
+ do --p ; while ((p > s) && (*(p-1) == ' ')) ;
+
+ qs_set_len_nn(vf->cl, p - s) ;
+ } ;
+ } ;
+
+ /* Now worry about possible trailing '\'. */
+
+ if ((p == s) || (*(p-1) != '\\'))
+ break ; /* no \ => no continuation => success */
+
+ /* Have a trailing '\'.
+ *
+ * If there are an odd number of '\', strip the last one and loop
+ * round to collect the continuation.
+ *
+ * If there are an even number of '\', then this is not a continuation.
+ *
+ * Note that this rule deals with the case of the continuation line
+ * being empty... e.g. ....\\\ n n -- where n is '\n'
+ */
+ e = p ;
+ do --p ; while ((p > s) && (*(p-1) == '\\')) ;
+
+ if (((e - p) & 1) == 0)
+ break ; /* even => no continuation => success */
+
+ qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */
+
+ continue ; /* loop back to fetch more */
+ } ;
+
+ /* Success: have a line in hand */
+
+ vf->line_complete = true ;
+
+ action->to_do = cmd_do_command ;
+ action->line = vf->cl ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File is ready to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_file_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ /* If the vin is no longer open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File has timed out, waiting to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_file_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->read_timer == timer) ;
+
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command output push to a file -- VOUT_FILE or VOUT_CONFIG -- vf_open.
+ *
+ * Unless all || final this will not write the end_lump of the fifo, so output
+ * is in units of the fifo size -- which should be "chunky".
+ *
+ * Although it is unlikely to happen, if blocking, will block if cannot
+ * completely write away what is required, or enable write ready.
+ *
+ * If final, will write as much as possible, but not block and will ignore
+ * any errors (but will return an error return code).
+ *
+ * If an error occurred earlier, then returns immediately (CMD_SUCCESS).
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * If "non-blocking", on CMD_WAITING the pselect() background process
+ * will complete the output and signal the result via uty_cmd_signal().
+ *
+ * If "blocking", will not return until have written away everything there is,
+ * or cannot continue.
+ *
+ * This can be called in any thread.
+ */
+extern cmd_return_code_t
+uty_file_out_push(vio_vf vf, bool final, bool all)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+ qassert(vf->vout_state == vf_open) ;
+
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ {
+ while (1)
+ {
+ qps_mini_t qm ;
+ int n ;
+
+ n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ;
+
+ if (n < 0)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
+
+ if (n == 0)
+ break ; /* all gone */
+
+ /* Cannot write everything away without waiting */
+ if (!vf->blocking)
+ {
+ if (!final)
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ if (final)
+ return CMD_WAITING ;
+
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
+ vf->write_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* Loop back to vio_fifo_write_nb() */
+
+ return uty_vf_error(vf, verr_to_vout, 0) ;
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File is ready to write -- call-back for VOUT_FILE.
+ *
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
+ *
+ * Note that the write_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_file_write_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ /* If the vout is no longer open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ /* Push, not final and not all -- re-enables write ready if required */
+ ret = uty_file_out_push(vf, false, false) ;
+ }
+ else
+ ret = CMD_SUCCESS ;
+
+ if (ret != CMD_WAITING)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File has timed out, waiting to write -- call-back for VOUT_FILE.
+ *
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_file_write_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->write_timer == timer) ;
+
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after input file has been closed -- VIN_FILE.
+ *
+ * Nothing further required -- input comes to a halt.
+ *
+ * Returns: CMD_SUCCESS -- at all times
+ */
+extern cmd_return_code_t
+uty_file_read_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_FILE) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Flush output buffer ready for close -- VOUT_FILE and VOUT_CONFIG.
+ *
+ * See uty_file_out_push()
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ */
+extern cmd_return_code_t
+uty_file_write_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+
+ if (vf->vout_state == vf_open)
+ return uty_file_out_push(vf, final, true) ; /* write all */
+ else
+ return CMD_SUCCESS ;
+} ;
+
+/*==============================================================================
+ * VTY Pipe I/O -- VIN_PIPE, VOUT_PIPE & VOUT_SH_CMD
+ *
+ * This is for input/output from/to shell commands.
+ *
+ * This is complicated by (a) the existence of up to 3 streams of data, and
+ * (b) the extra step of collecting the child and its return code on close.
+ *
+ * The three streams of data, from the perspective of the shell command, are:
+ *
+ * stdin -- input from Quagga (if any)
+ *
+ * stdout -- output to Quagga -- may be treated as command lines
+ *
+ * stderr -- output to Quagga -- assumed to be diagnostic information
+ *
+ * There are three variants of pipe I/O:
+ *
+ * * VIN_PIPE -- command line: <| shell command
+ *
+ * stdin -- none
+ *
+ * stdout -- read by Quagga as command lines
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_PIPE -- command line: .... >| shell command
+ *
+ * stdin -- the Quagga command(s) output
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_SH_CMD -- command line: | shell_command
+ *
+ * stdin -- none
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * When Quagga is collecting stdout for output, that is known as the
+ * "pipe return", and is expected to be the result of the command.
+ *
+ * In all cases the stderr is assumed to be diagnostic information, which is
+ * known as the "pipe stderr return". This is collected in the vf->ps_buf.
+ * When the pipe is closed, any stderr return is then appended to the
+ * vio->ps_buf. When the output stack is closed down to the base vout, the
+ * contents of the vio->ps_buf are transferred to the vio->obuf. This means
+ * that:
+ *
+ * 1. all pipe stderr return for a given pipe is collected and output
+ * all together to the base vout (eg the VTY_TERMINAL). The output is
+ * bracketted so:
+ *
+ * %[--- 'name':
+ * ....
+ * ....
+ * %---]
+ *
+ * to give context.
+ *
+ * 2. the pipe stderr return is not mixed up with any other pipe or other
+ * command output, and in particular cannot be piped to another pipe.
+ *
+ * 3. but... if VOUT_PIPE are stacked on top of each other, it may be a
+ * while before the pipe stderr return stuff reaches the base vout.
+ *
+ * The problem here is that if one VOUT_PIPE is stacked above another,
+ * the lower pipe may or may not be in the middle of something when
+ * the upper pipe is closed -- so to avoid a mix-up, must wait until
+ * the all lower pipes close before the pipe stderr is finally output
+ * (along with all other pending stderr return).
+ *
+ * While the main vin/vout is open, the pipe I/O is handled as follows:
+ *
+ * * VIN_PIPE
+ *
+ * The shell command's stdout is connected to the vf's vin, and is read
+ * as command lines, much as VIN_FILE. A read timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * main vin. No timeout is set until the main vin is closed.
+ *
+ * * VOUT_PIPE
+ *
+ * The shell command's stdin is connected to the vf's vout, and is
+ * written to much like VOUT_FILE. A write timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stdout is connected to the vf's pipe return.
+ * For non-blocking vf, the pipe return is read autonomously under
+ * pselect() and pushed to the next vout. For blocking vf, the pipe
+ * return is polled whenever the (main) vout is written to, and any
+ * available input is pushed to the slave vout. No timeout is set
+ * until the main vout is closed.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * pipe return. No timeout is set until the main vout is closed.
+ *
+ * * VOUT_SH_CMD
+ *
+ * The shell command's stdin is set empty, and the vf's vout set to
+ * vf_closed.
+ *
+ * Otherwise, this is the same as VOUT_PIPE.
+ *
+ * The closing of a VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is a little involved:
+ *
+ * 0. close the main vin/vout.
+ *
+ * for a VIN_PIPE the vin_state will be:
+ *
+ * vf_end -- eof met or forced by an early close, or error or timeout
+ * occurred
+ *
+ * So phase 0 finishes immediately.
+ *
+ * for a VOUT_PIPE the vout_state will be:
+ *
+ * vf_open -- until all output completes or hits error or timeout
+ * vf_end -- nothing more to be output, or error or timeout occurred
+ *
+ * If vf_open, must wait until no longer vf_open, ie until all pending
+ * output completes, or hits and error or timeout.
+ *
+ * As soon as is no longer vf_open, must close the vfd to signal to the
+ * child that is now at eof.
+ *
+ * for a VOUT_SH_CMD the vout_state will be:
+ *
+ * vf_end -- nothing to be output.
+ *
+ * So phase 0 finishes immediately.
+ *
+ * 1. collect remaining input and close the pipe return.
+ *
+ * for a VIN_PIPE there is no pipe return.
+ *
+ * for a VOUT_PIPE or VOUT_SH_CMD, must empty the pipe return and then
+ * close it -- stopping immediately if gets error or time-out on either
+ * the pipe return or the slave vout.
+ *
+ * Note that up to this point no time-out has been set on the pipe return,
+ * since there is no need for there to be any input. But expect now to
+ * see at least eof in a reasonable time.
+ *
+ * For non-blocking vf, the pr_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 2. collect remaining input and close the pipe stderr return.
+ *
+ * For non-blocking vf, the ps_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process -- complete with time-out.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 3. once all input has been collected and pr_vfd and ps_vfd are closed,
+ * need to collect the child, so that we can check the return code.
+ *
+ * This may time out.
+ *
+ * If the return code is not 0, or anything else happens, a diagnostic
+ * message is appended to the vf->rbuf.
+ *
+ * For non-blocking vf the close may return CMD_WAITING, and must be called
+ * again later to progress the close.
+ *
+ * For "final" close will not block or return CMD_WAITING, but complete the
+ * process as quickly as possible while outputting as much as possible.
+ */
+typedef int pipe_pair_t[2] ;
+typedef int* pipe_pair ;
+
+typedef enum pipe_half
+{
+ in_fd = 0,
+ out_fd = 1,
+
+} pipe_half_t ;
+
+CONFIRM(STDIN_FILENO == 0) ;
+CONFIRM(STDOUT_FILENO == 1) ;
+CONFIRM(STDERR_FILENO == 2) ;
+
+typedef enum std_id
+{
+ stdin_fd = STDIN_FILENO,
+ stdout_fd = STDOUT_FILENO,
+ stderr_fd = STDERR_FILENO,
+
+ stds = 3
+
+} std_id_t ;
+
+typedef enum pipe_id
+{
+ in_pipe,
+ out_pipe,
+ err_pipe,
+
+ pipe_count
+
+} pipe_id_t ;
+
+typedef enum pipe_type
+{
+ vin_pipe,
+ vout_pipe,
+ vout_sh_cmd,
+
+} pipe_type_t ;
+
+typedef pipe_pair_t pipe_set[pipe_count] ;
+
+static pid_t uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes,
+ pipe_type_t type) ;
+static bool uty_pipe_pair(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_id_t id,
+ pipe_half_t half) ;
+static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_pair pair,
+ const char* action) ;
+static void uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) ;
+static void uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd) ;
+static bool uty_pipe_exec_prepare(vty_io vio, pipe_set pipes) ;
+
+static cmd_return_code_t uty_pipe_return_close(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_stderr_suck(vio_vf vf, bool final) ;
+static vio_fifo uty_pipe_ps_buf(vio_vf vf) ;
+
+static void vty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_read_timeout(vio_timer timer,
+ void* action_info) ;
+static void vty_pipe_return_ready(vio_vfd vfd, void* action_info) ;
+static void vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_return_timeout(vio_timer timer,
+ void* action_info) ;
+static vty_timer_time vty_pipe_stderr_return_timeout(vio_timer timer,
+ void* action_info) ;
+static void vty_pipe_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_pipe_write_timeout(vio_timer timer,
+ void* action_info) ;
+
+/*------------------------------------------------------------------------------
+ * Open VIN_PIPE: pipe whose child's stdout is read and executed as commands.
+ *
+ * The child's stderr is read, separately, as the pipe return, and sent to the
+ * current vout.
+ *
+ * The new vin has two vio_fd's, the standard one to read commands, and the
+ * other (the pr_vfd) to collect any stderr output (the "return") from the
+ * child, which is sent to the current vout.
+ *
+ * The current vout is made the "slave" of the new vin "master". For the
+ * return the pr_vd reads into the "slave" obuf.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open -- message sent to vio.
+ */
+extern cmd_return_code_t
+uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
+{
+ pipe_set pipes ;
+ const char* cmd_str ;
+ vio_vf vf ;
+ pid_t child ;
+ qpath dir ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->vin != NULL) ; /* Not expected to be vin_base */
+
+ cmd_str = qs_make_string(command) ;
+
+ /* Fork it */
+ child = uty_pipe_fork(vio, cmd_str, pipes, vin_pipe) ;
+
+ if (child < 0)
+ return CMD_WARNING ;
+
+ /* We have a pipe, so now save context */
+ dir = NULL ;
+
+ if (*cmd_str == '/')
+ {
+ const char* p ;
+ p = cmd_str ;
+ while (*p > ' ')
+ ++p ;
+ dir = qpath_set_n(NULL, cmd_str, p - cmd_str) ;
+ } ;
+
+ uty_vin_new_context(vio, context, dir) ;
+
+ if (dir != NULL)
+ qpath_free(dir) ;
+
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, cmd_str, pipes[in_pipe][in_fd], vfd_pipe, vfd_io_read) ;
+ uty_vin_push(vio, vf, VIN_PIPE, vty_pipe_read_ready,
+ vty_pipe_read_timeout, pipe_buffer_size) ;
+ vf->read_timeout = pipe_timeout ;
+
+ /* And the err_pair is set as the return from the child */
+ uty_pipe_open_complete(vf, child, -1, pipes[err_pipe][in_fd]) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open VOUT_PIPE or VOUT_SH_CMD: pipe which is going to be written to
+ * (or not, if shell_cmd), where what the pipe returns will be output to the
+ * current vout.
+ *
+ * The new vout becomes the "master", and has two vio_vfd's, one for output to
+ * the pipe (this is NULL if shell_only) and the other for the pipe return.
+ * The pipe return reads directly into the "slave" obuf.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open -- message sent to vio.
+ */
+extern cmd_return_code_t
+uty_pipe_write_open(vty_io vio, qstring command, bool shell_cmd, bool after)
+{
+ pipe_set pipes ;
+ const char* cmd_str ;
+ pid_t child ;
+ vio_vf vf ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ cmd_str = qs_make_string(command) ;
+
+ /* Do the basic file open. */
+ child = uty_pipe_fork(vio, cmd_str, pipes, shell_cmd ? vout_sh_cmd
+ : vout_pipe) ;
+ if (child < 0)
+ return CMD_WARNING ;
+
+ /* OK -- now push the new output onto the vout_stack.
+ *
+ * Note that for VOUT_SH_CMD no vfd is set up, and the vout_state is
+ * immediately set to vf_end.
+ */
+ if (shell_cmd)
+ {
+ vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ;
+ uty_vout_push(vio, vf, VOUT_SH_CMD, NULL, NULL, 0, after) ;
+ vf->vout_state = vf_end ;
+ }
+ else
+ {
+ vf = uty_vf_new(vio, cmd_str, pipes[out_pipe][out_fd],
+ vfd_pipe, vfd_io_write) ;
+ uty_vout_push(vio, vf, VOUT_PIPE, vty_pipe_write_ready,
+ vty_pipe_write_timeout, pipe_buffer_size, after) ;
+ vf->write_timeout = pipe_timeout ;
+ } ;
+
+ /* Record the child pid and set up the pr_vf and enslave vout_next */
+
+ uty_pipe_open_complete(vf, child, pipes[in_pipe][in_fd],
+ pipes[err_pipe][in_fd]) ;
+ /* Until eof (or error or timeout) on the vin, neither wait for or timeout
+ * the pipe return or the pipe stderr return.
+ */
+ qassert(vf->pr_timeout == 0) ;
+ qassert(vf->ps_timeout == 0) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Complete the opening of a pipe.
+ *
+ * All pipes have a return. For in_pipes this is the child's stderr, for
+ * out_pipes this is the child's stdout and its stderr.
+ *
+ * For non-blocking, sets up the pipe return and the pipe stderr return ready
+ * and timeout actions, but leaves pr_timeout == 0 and ps_timeout == 0
+ */
+static void
+uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd)
+{
+ vfd_io_type_t iot ;
+
+ vf->child = uty_child_register(pid, vf) ;
+
+ iot = vfd_io_read | (vf->blocking ? vfd_io_ps_blocking : 0) ;
+
+ /* If there is a pipe return, set up vfd and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, but do set the return
+ * read ready, to proceed asynchronously.
+ */
+ if (pr_fd >= 0)
+ {
+ vf->pr_vfd = vio_vfd_new(pr_fd, vfd_pipe, iot, vf) ;
+ vf->pr_state = vf_open ;
+
+ qassert(vf->pr_timeout == 0) ;
+
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->pr_vfd, vty_pipe_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->pr_vfd, vty_pipe_return_timeout) ;
+
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ } ;
+ } ;
+
+ /* Set up vfd for pipe stderr return and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, or set the return
+ * read ready.
+ */
+ vf->ps_vfd = vio_vfd_new(ps_fd, vfd_pipe, iot, vf) ;
+ vf->ps_state = vf_open ;
+
+ qassert(vf->ps_timeout == 0) ;
+
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->ps_vfd, vty_pipe_stderr_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->ps_vfd, vty_pipe_stderr_return_timeout) ;
+
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fork fork fork
+ *
+ * Set up pipes according to type of fork:
+ *
+ * in_pipe -- input from child's stdout as main fd
+ * input from child's stderr as stderr return fd
+ *
+ * out_pipe -- output to child's stdin as main fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
+ *
+ * err_pipe -- nothing for main fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
+ *
+ * vfork to create child process and then:
+ *
+ * -- in parent close the unused part(s) of the pair(s).
+ *
+ * -- in child, close all unused fds, and move relevant part(s) of pair(s)
+ * to stdin, stdout and stderr, sort out signal, set pwd then exec sh -c.
+ *
+ * Returns: > 0 -- OK, this is child pid
+ * < 0 -- Failed
+ *
+ * NB: only returns in the parent process -- exec's in the child.
+ */
+static pid_t
+uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_type_t type)
+{
+ pid_t child ;
+ int id ;
+
+ /* Set pipes empty */
+ for (id = 0 ; id < pipe_count ; ++id)
+ {
+ pipes[id][in_fd] = -1 ;
+ pipes[id][out_fd] = -1 ;
+ } ;
+
+ /* Open as many pipes as are required. */
+ if (type == vin_pipe)
+ if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd))
+ return -1 ;
+
+ if ((type == vout_pipe) || (type == vout_sh_cmd))
+ if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, in_pipe, in_fd))
+ return -1 ;
+
+ if (type == vout_pipe)
+ if (!uty_pipe_pair(vio, cmd_str, "output pipe", pipes, out_pipe, out_fd))
+ return -1 ;
+
+ if (!uty_pipe_pair(vio, cmd_str, "stderr pipe", pipes, err_pipe, in_fd))
+ return -1 ;
+
+ /* Off to the races */
+
+ child = vfork() ;
+
+ if (child == 0) /* In child */
+ {
+ /* Prepare all file descriptors and then execute */
+ if (uty_pipe_exec_prepare(vio, pipes))
+ execl("/bin/bash", "bash", "-c", cmd_str, NULL) ; /* does not return */
+ else
+ exit(0x80 | errno) ;
+ }
+ else if (child > 0) /* In parent -- success */
+ {
+ /* close the pipe fds we do not need */
+ uty_pipe_close_half_pipe(pipes[in_pipe], out_fd) ;
+ uty_pipe_close_half_pipe(pipes[out_pipe], in_fd) ;
+ uty_pipe_close_half_pipe(pipes[err_pipe], out_fd) ;
+ }
+ else if (child < 0) /* In parent -- failed */
+ uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ;
+
+ return child ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open a pipe pair -- generate suitable error message if failed and close
+ * any earlier pipes that have been opened.
+ *
+ * Returns: true <=> success
+ */
+static bool
+uty_pipe_pair(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_id_t id,
+ pipe_half_t half)
+{
+ pipe_pair pair ;
+
+ pair = pipes[id] ;
+
+ if (pipe(pair) < 0)
+ return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair, "open") ;
+
+ if (set_nonblocking(pair[half]) < 0)
+ return uty_pipe_fork_fail(vio, cmd_str, what, pipes, NULL,
+ "set non-blocking for") ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Failed to open pipe: generate suitable error message and close any earlier
+ * pipes that have been opened.
+ *
+ * Returns: false
+ */
+static bool
+uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_pair pair,
+ const char* action)
+{
+ int err = errno ;
+ int id ;
+
+ /* Close anything that has been opened */
+ for (id = 0 ; id < pipe_count ; ++id)
+ {
+ if (pipes[id] == pair)
+ continue ; /* ignore if just failed to open */
+
+ uty_pipe_close_half_pipe(pipes[id], in_fd) ;
+ uty_pipe_close_half_pipe(pipes[id], out_fd) ;
+ } ;
+
+ uty_out(vio, "%% Failed to %s %s for %s\n", action, what, cmd_str) ;
+
+ errno = err ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close half of a pipe pair, if it is open.
+ */
+static void
+uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half)
+{
+ if (pair[half] >= 0)
+ {
+ close(pair[half]) ;
+ pair[half] = -1 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * In the child process... prepare to exec the shell.
+ *
+ * Discard all fd's except for the those required for the shell command.
+ *
+ * Arrange for the pipe fd's to be stdin/stdout/stderr as required. The pairs
+ * are named wrt to the parent process:
+ *
+ * in_pipe -- if present, is: stdout for the child
+ * out_pipe -- if present, is: stdin for the child
+ * err_pipe -- if present, is: stderr for the child
+ * and: stdout for the child, if no in_pipe
+ *
+ * Set current directory.
+ *
+ * Reset all signals to default state and unmask everything.
+ *
+ * Returns: true <=> good to go
+ * false => some sort of error -- see errno
+ */
+static bool
+uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
+{
+ int std[stds] ;
+ int fd ;
+
+ /* Assign fds to child's std[] */
+
+ std[stdin_fd] = pipes[out_pipe][in_fd] ; /* stdin for child */
+ std[stdout_fd] = pipes[in_pipe][out_fd] ; /* stdout for child */
+ std[stderr_fd] = pipes[err_pipe][out_fd] ; /* stderr for child */
+
+ /* Mark everything to be closed on exec */
+ for (fd = 0 ; fd < qlib_open_max ; ++fd)
+ {
+ int fd_flags ;
+ fd_flags = fcntl(fd, F_GETFD, 0) ;
+ if (fd_flags >= 0)
+ fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) ;
+ } ;
+
+ /* Now dup anything of what we keep to ensure is above 2, making sure that
+ * the dup remains FD_CLOEXEC.
+ *
+ * This is highly unlikely, so no real extra work. It simplifies the next
+ * step which moves fds to the required position, and clears the FD_CLOEXEC
+ * flag on the duplicate.
+ */
+ for (fd = 0 ; fd < stds ; ++fd)
+ {
+ if ((std[fd] >= 0) && (std[fd] < stds))
+ if ((std[fd] = fcntl(std[fd], F_DUPFD_CLOEXEC, stds)) < 0)
+ return false ;
+ } ;
+
+ /* Now dup2 to the required location -- destination is NOT FD_CLOEXEC.
+ */
+ for (fd = 0 ; fd < stds ; ++fd)
+ {
+ if (std[fd] >= 0)
+ if (dup2(std[fd], fd) != fd)
+ return false ;
+ } ;
+
+ /* Finally (for fds) if we don't have a stdout for the child,
+ * dup stderr to stdout.
+ */
+ if ((std[stdout_fd] < 0) && (std[stderr_fd] >= 0))
+ if (dup2(stderr_fd, stdout_fd) != stdout_fd)
+ return false ;
+
+ /* Clear down all the signals magic. */
+ quagga_signal_reset() ;
+
+ /* Set the working directory */
+
+ if (qpath_setcwd(vio->vty->exec->context->dir_cd) != 0)
+ return false ;
+
+ return true ; /* coming, ready or not */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command line fetch from a pipe -- VIN_PIPE in vf_open state.
+ *
+ * Before attempting to fetch command line, will shovel any return into the
+ * slave and push. This means that any return is output between command lines.
+ *
+ * In "non-blocking" state, on CMD_WAITING may be waiting for read ready on
+ * this vf, or (indirectly) write ready on the slave.
+ *
+ * In "blocking" state, will not return until have something, or there is
+ * nothing to be had, or times out.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- waiting for input not output <=> non-blocking
+ * CMD_EOF -- ran into EOF -- on input
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
+ *
+ * This can be called in any thread.
+ *
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * Signals CMD_EOF on the main input. If this occurs before EOF on the
+ * return input, any remaining return input must be dealt with before
+ * the vf is finally closed -- see uty_pipe_read_close().
+ *
+ * NB: the vout_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR
+ * return code does the trick.
+ */
+extern cmd_return_code_t
+uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_open) ;
+
+ while (1) /* so blocking stuff can loop round */
+ {
+ cmd_return_code_t ret ;
+ int get ;
+ qps_mini_t qm ;
+
+ /* Try to complete line straight from the buffer.
+ *
+ * If buffer is empty and have seen eof on the input, signal CMD_EOF.
+ */
+ ret = uty_fifo_command_line(vf, action) ;
+
+ if (ret != CMD_WAITING)
+ return ret ;
+
+ /* If blocking, worry about the stderr return -- just to keep I/O moving.
+ *
+ * Expect only CMD_SUCCESS or CMD_IO_ERROR.
+ */
+ if (vf->blocking && (vf->ps_state == vf_open))
+ {
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
+
+ /* Need more from the main input.
+ */
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
+
+ if (get > 0)
+ continue ; /* loop back */
+
+ if (get == -1)
+ return uty_vf_error(vf, verr_io_vin, errno) ;
+
+ if (get == -2) /* EOF met immediately */
+ {
+ vf->vin_state = vf_end ;
+ continue ; /* loop back -- deals with possible
+ final line and the return. */
+ } ;
+
+ qassert(get == 0) ;
+
+ /* We get here if main input is not yet at eof, but has nothing
+ * to read at the moment.
+ */
+ qassert(vf->vin_state == vf_open) ;
+
+ if (!vf->blocking)
+ {
+ uty_vf_set_read(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vin and the pipe return.
+ */
+ qps_mini_set(qm, (vf->vin_state == vf_open) ? vio_vfd_fd(vf->vfd)
+ : -1,
+ qps_read_mnum, vf->read_timeout) ;
+ if (vf->pr_state == vf_open)
+ qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ;
+
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_vin, 0) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command output push to a pipe and (for blocking) shovel return into
+ * slave and push.
+ *
+ * For blocking, does not push to the pipe while there is return input to be
+ * read and pushed to the slave. For non-blocking the return input/output is
+ * handled autonomously.
+ *
+ * If final, will do final shovel from return to slave and also attempt to
+ * empty any output buffer -- will not wait or block, and ignores errors.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * In "non-blocking" state, on CMD_WAITING the slave output will put the
+ * master return pr_vfd into read ready state when it sees write ready, or will
+ * here set the pr_vfd into read ready state. This requires no further action
+ * from the caller, the background pselect process will complete the output and
+ * may signal the result via uty_cmd_signal().
+ *
+ * In "blocking" state, will not return until have written everything there is,
+ * away, or cannot continue.
+ *
+ * This can be called in any thread.
+ */
+extern cmd_return_code_t
+uty_pipe_out_push(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if (vf->vout_state != vf_open)
+ return CMD_SUCCESS ; /* Get out if going nowhere */
+
+ /* If blocking, keep the stderr return moving.
+ */
+ if (vf->blocking && (vf->ps_state == vf_open))
+ {
+ cmd_return_code_t ret ;
+
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
+
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ {
+ while (1)
+ {
+ qps_mini_t qm ;
+ int put ;
+
+ /* If blocking, see if there is anything in the return, and shovel.
+ *
+ * For non-blocking, the pselect process looks after this.
+ */
+ if (vf->blocking && (vf->pr_state == vf_open))
+ {
+ cmd_return_code_t ret ;
+
+ ret = uty_pipe_shovel(vf, final) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
+
+ if (vf->vout_state != vf_open)
+ break ; /* Quit if error has stopped the main vout */
+
+ /* Now write to the main vout, blocking if required.
+ */
+ put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) ;
+
+ if (put == 0) /* all gone */
+ break ;
+
+ if (put < 0)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
+
+ /* Cannot write everything away without waiting
+ */
+ if (!vf->blocking)
+ {
+ if (!final)
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vout and the pipe return.
+ */
+ if (final)
+ return CMD_WAITING ;
+
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
+ vf->write_timeout) ;
+ if (vf->pr_state == vf_open)
+ qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ;
+
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* Loop back */
+
+ return uty_vf_error(vf, verr_to_vout, 0) ;
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shovel from pipe return to slave, pushing the slave as we go.
+ *
+ * While the main vout is open, this function is used:
+ *
+ * * VOUT_PIPE: for blocking vf, this is called each time the output is
+ * pushed -- to keep any returned stuff moving. Does not
+ * block reading the return, but may block writing to the
+ * slave.
+ *
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously,
+ * or may be called "final".
+ *
+ * Note that pr_timeout == 0 while the main vout is open.
+ *
+ * * VOUT_SH_CMD: the main vin/vout is closed from the get go.
+ *
+ * All pipe return is handled by the close process.
+ *
+ * When the main vin/vout is closed:
+ *
+ * * VOUT_PIPE & VOUT_SH_CMD:
+ *
+ * for blocking vf, this is called by the close function,
+ * to suck up any remaining return, and push it to the slave.
+ * May block reading the return and/or the slave.
+ *
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously, or
+ * may be called "final".
+ *
+ * Note that pr_timeout != 0 once the main vout is closed.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if runs into I/O error or time-out on the slave output, then sets
+ * vf_end on the TODO
+ *
+ * This can be called in any thread.
+ */
+static cmd_return_code_t
+uty_pipe_shovel(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->pr_state == vf_open) ;
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if ((vf->vio->cancel) || (vf->vout_next->vout_state != vf_open))
+ return CMD_SUCCESS ;
+
+ /* Suck and blow -- may block in uty_cmd_out_push()
+ *
+ * Note that we do not push the slave if there is nothing new from the
+ * return -- there shouldn't be anything pending in the slave obuf.
+ */
+ while (1)
+ {
+ cmd_return_code_t ret ;
+ int get ;
+
+ get = vio_fifo_read_nb(vf->vout_next->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
+
+ if (get == 0) /* Nothing there, but not EOF */
+ {
+ qps_mini_t qm ;
+
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->pr_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ if (vf->ps_state == vf_open)
+ {
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
+
+ qps_mini_set(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum,
+ vf->pr_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_pr, 0) ; /* CMD_IO_ERROR */
+ }
+
+ else if (get > 0) /* Read something */
+ {
+ ret = uty_cmd_out_push(vf->vout_next, final) ; /* may block */
+
+ if (ret == CMD_SUCCESS)
+ continue ; /* Loop back if emptied buffer */
+
+ if (ret == CMD_IO_ERROR)
+ uty_pipe_return_stop(vf) ;
+ /* No point continuing */
+ else
+ {
+ /* Is CMD_WAITING on the slave output.
+ *
+ * If we are "final":
+ *
+ * for blocking vf that means would have blocked, but didn't.
+ *
+ * for non-blocking vf, that means could not empty the buffer.
+ *
+ * If not "final":
+ *
+ * cannot be a blocking vf !
+ *
+ * for non-blocking vf, the output buffer will be serviced,
+ * in due course and can leave it up to the pselect() process
+ * to read anything more -- no need to do so now.
+ *
+ * ...in all cases don't want to try to input or output any more,
+ * so give up and return CMD_WAITING.
+ */
+ qassert(ret == CMD_WAITING) ;
+ } ;
+
+ return ret ; /* CMD_WAITING or CMD_IO_ERROR */
+ }
+
+ else if (get == -1) /* Hit error */
+ {
+ return uty_vf_error(vf, verr_io_pr, errno) ; /* CMD_IO_ERROR */
+ }
+
+ else
+ {
+ assert (get == -2) ; /* eof on the return */
+
+ vf->pr_state = vf_end ;
+ break ; /* quit: OK */
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Suck the pipe stderr return to vf->ps_buf.
+ *
+ * While the main vin/vout is open or the pipe return is open, this function
+ * is used:
+ *
+ * for blocking vf, this is called each time the output is pushed, or the
+ * input is read, or the pipe return is read -- to keep any returned stuff
+ * moving. Does not block reading the stderr return.
+ *
+ * for non-blocking vf, this is called by the pselect which keeps the stderr
+ * return moving autonomously.
+ *
+ * Note that ps_timeout == 0 under these conditions.
+ *
+ * When the main vin/vout and the pipe return are closed:
+ *
+ * for blocking vf, this is called by the close function, to suck up any
+ * remaining stderr return, which may block.
+ *
+ * for non-blocking vf, this is called by the pselect process, which keeps
+ * the stderr return moving autonomously, or may be called "final".
+ *
+ * Note that ps_timeout != 0 under these conditions.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ */
+static cmd_return_code_t
+uty_pipe_stderr_suck(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->ps_state == vf_open) ;
+
+ /* Suck
+ */
+ while (1)
+ {
+ int get ;
+
+ if (vf->ps_buf == NULL)
+ {
+ char buf[100] ;
+ get = read_nb(vio_vfd_fd(vf->ps_vfd), buf, sizeof(buf)) ;
+
+ if (get > 0)
+ vio_fifo_put_bytes(uty_pipe_ps_buf(vf), buf, get) ;
+ }
+ else
+ get = vio_fifo_read_nb(vf->ps_buf, vio_vfd_fd(vf->ps_vfd), 10) ;
+
+ if (get == 0) /* Nothing there, but not EOF */
+ {
+ qps_mini_t qm ;
+
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->ps_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ qps_mini_set(qm, vio_vfd_fd(vf->ps_vfd), qps_read_mnum,
+ vf->ps_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_ps, 0) ; /* CMD_IO_ERROR */
+ }
+
+ else if (get > 0) /* Read something */
+ {
+ continue ;
+ }
+
+ else if (get == -1) /* Hit error on stderr return */
+ {
+ return uty_vf_error(vf, verr_io_ps, errno) ; /* CMD_IO_ERROR */
+ }
+
+ else
+ {
+ assert (get == -2) ; /* eof on the stderr return */
+
+ vf->ps_state = vf_end ;
+ break ; /* quit: OK */
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe is ready to read -- call-back for VIN_PIPE.
+ *
+ * This is used if the VIN_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_pipe_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ /* If the vin is no longer vf_open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe has timed out, waiting to read -- call-back for VIN_PIPE.
+ *
+ * This is used if the VIN_PIPE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->read_timer == timer) ;
+
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe return is ready to read -- call-back for VOUT_PIPE and VOUT_SH_CMD.
+ *
+ * This is used if the VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
+ *
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_pipe_return_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->pr_vfd == vfd) ;
+
+ /* If the pipe return is no longer open then read ready should have been
+ * turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->pr_state == vf_open)
+ ret = uty_pipe_shovel(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ else if (vf->ps_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe stderr return is ready to read -- call-back for VIN_PIPE, VOUT_PIPE and
+ * VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
+ *
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd == vfd) ;
+
+ /* If the pipe stderr return is no longer open then read ready should have
+ * been turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->ps_state == vf_open)
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
+
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ else if (vf->pr_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_return_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->pr_vfd->read_timer == timer) ;
+
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_ps, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_stderr_return_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd->read_timer == timer) ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_pr, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe is ready to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
+ *
+ * Note that the write_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+vty_pipe_write_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ /* If the vout is no longer vf_open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ ret = uty_pipe_out_push(vf, false) ; /* not final */
+ }
+ else
+ ret = CMD_SUCCESS ;
+
+ if (ret != CMD_WAITING)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe has timed out, waiting to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_write_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->write_timer == timer) ;
+
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Complete the close of a VIN_PIPE after the vfd has been read closed.
+ *
+ * Nothing needs to be done with the main input, but must close the return,
+ * collect the child and release the slave.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible, the return is done with
+ * (and the vfd closed) and the child has been
+ * collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for return I/O to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
+ */
+extern cmd_return_code_t
+uty_pipe_read_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_end) ;
+
+ return uty_pipe_return_close(vf, final) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close VOUT_PIPE or VOUT_SH_CMD.
+ *
+ * For not-final, wish to flush output buffer. If final will attempt to empty
+ * the output buffer -- but will not wait or block, and ignores errors.
+ *
+ * Must then close the return, collect the child and release the slave.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible, the vout and the
+ * return are done with (and the vfds closed) and
+ * the child has been collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for output or return I/O
+ * to complete <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
+ */
+extern cmd_return_code_t
+uty_pipe_write_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ /* If the main vfd is still there, keep pushing (which, for blocking vf,
+ * will keep shovelling from pipe return to slave).
+ */
+ if (vf->vout_state == vf_open)
+ {
+ cmd_return_code_t ret ;
+
+ ret = uty_pipe_out_push(vf, final) ;
+
+ if ((ret == CMD_WAITING) && !final)
+ return ret ;
+
+ vf->vout_state = vf_end ; /* no further output */
+ } ;
+
+ /* Now need to close the output vfd to signal to the child that we
+ * are done -- note that closing an already closed vfd does nothing.
+ */
+ vf->vfd = vio_vfd_close(vf->vfd) ;
+
+ /* If the return is still open, try to empty and close that. */
+ return uty_pipe_return_close(vf, final) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close the return on a VIN_PIPE, VOUT_PIPE or a VOUT_SH_CMD.
+ *
+ * This is the second stage of closing a pipe, after the vin or the main vout
+ * has reached vf_end, so all main I/O is complete (or failed).
+ *
+ * If returns CMD_WAITING from here, may be called again, any number of times,
+ * to attempt to complete the process.
+ *
+ * If "final" then will close after making efforts to complete I/O and collect
+ * child, short of blocking or waiting.
+ *
+ * Phase 1: if the return is vf_open:
+ *
+ * Set the read time out for the return (want any remaining return stuff, and
+ * the eof) which (for non-blocking) indicates is now prepared to block.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 2: if the stderr return is vf_open:
+ *
+ * Set the read time out for the stderr return (want any remaining stderr
+ * return stuff, and the eof) which (for non-blocking) indicates is now
+ * prepared to block.
+ *
+ * Suck stderr return. If "final", do not block or wait for either input or
+ * output. If not final, may block and may time-out or may return
+ * CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 3: collect the child return code and deal with it:
+ *
+ * If the child has yet to be collected:
+ *
+ * if "final", collect child immediately or set overdue.
+ *
+ * if not "final":
+ *
+ * if non-blocking, return CMD_WAITING and wait for the SIGCHLD to do
+ * the business or to time out.
+ *
+ * if blocking, collect child or time out and set overdue.
+ *
+ * If the child is not collected, or has not terminated cleanly, output
+ * diagnostic to the pipe stderr return.
+ *
+ * If the child is not collected, the SIGCHLD handling will tidy up in the
+ * background.
+ *
+ * In any case, dismiss child.
+ *
+ * Phase 4: transfer the pipe stderr return to the main vio pipe stderr return.
+ *
+ * This leaves it up to the vout_base to deal with the pipe stderr return,
+ * in its own time -- e.g when the output stack closes.
+ *
+ * When this (eventually) returns CMD_SUCCESS, all pipe return has been
+ * sucked up and has cleared the slave vout buffer, and the child has been
+ * collected -- unless something has gone wrong, and part of that has been
+ * short-circuited.
+ *
+ * Returns: CMD_SUCCESS -- closed and child collected
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ */
+static cmd_return_code_t
+uty_pipe_return_close(vio_vf vf, bool final)
+{
+ vty_io vio ;
+
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ vio = vf->vio ;
+
+ /* Phase 1: if return is still open, try to empty it -- subject to "final".
+ * Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and shovel one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->pr_state == vf_open)
+ {
+ cmd_return_code_t ret ;
+ bool set_timeout ;
+
+ set_timeout = (vf->pr_timeout == 0) ;
+ vf->pr_timeout = pipe_timeout ;
+
+ if (!vf->blocking)
+ {
+ if (!final)
+ {
+ if (set_timeout)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+
+ return CMD_WAITING ; /* in hands of pselect() */
+ } ;
+
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+ } ;
+
+ ret = uty_pipe_shovel(vf, final) ;
+
+ if ((ret == CMD_WAITING) && !final)
+ return ret ;
+ } ;
+
+ if (vf->pr_state != vf_closed)
+ {
+ vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ;
+ vf->pr_state = vf_closed ;
+ } ;
+
+ /* Phase 2: if stderr return is still open, try to empty it -- subject to
+ * "final". Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and suck one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->ps_state == vf_open)
+ {
+ cmd_return_code_t ret ;
+ bool set_timeout ;
+
+ set_timeout = (vf->ps_timeout == 0) ;
+ vf->ps_timeout = pipe_timeout ;
+
+ if (!vf->blocking)
+ {
+ if (!final)
+ {
+ if (set_timeout)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->pr_timeout) ;
+
+ return CMD_WAITING ; /* in hands of pselect() */
+ } ;
+
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+ } ;
+
+ ret = uty_pipe_stderr_suck(vf, final) ;
+
+ if ((ret == CMD_WAITING) && !final)
+ return ret ;
+ } ;
+
+ if (vf->ps_state != vf_closed)
+ {
+ vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ;
+ vf->ps_state = vf_closed ;
+ } ;
+
+ /* Phase 3: if not already collected, collect the child
+ *
+ * When does collect, or times out, will dismiss the child and set vf->child
+ * to NULL -- which indicates that the child is done with.
+ *
+ * Note that may write diagnostic message to slave, so in last phase we
+ * push the slave output to clear that out before releasing the slave.
+ */
+ if (vf->child != NULL)
+ {
+ /* Collect -- blocking or not blocking */
+ if (!vf->child->collected && !vf->child->overdue)
+ {
+ /* If we are blocking or "final" or vio->cancel, try to collect the
+ * child here and now -- if not final or vio->cancel may block here.
+ *
+ * For non-blocking and not "final", leave the child collection up to
+ * the SIGCHLD system.
+ */
+ if (vf->blocking || final || vf->vio->cancel)
+ {
+ uty_child_collect(vf->child, child_timeout,
+ final || vf->vio->cancel) ;
+ }
+ else
+ {
+ uty_child_awaited(vf->child, child_timeout) ;
+ return CMD_WAITING ;
+ } ;
+ } ;
+
+ /* If child is overdue, or did not terminate cleanly, write message to
+ * the pipe stderr return.
+ */
+ if (!vf->child->collected)
+ {
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process still running\n") ;
+ }
+ else if (WIFEXITED(vf->child->report))
+ {
+ int status = WEXITSTATUS(vf->child->report) ;
+ if (status != 0)
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process ended normally, but with status = %d\n",
+ status) ;
+ }
+ else if (WIFSIGNALED(vf->child->report))
+ {
+ int signal = WTERMSIG(vf->child->report) ;
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process terminated by signal = %d\n", signal) ;
+ }
+ else
+ {
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process ended in unknown state = %d\n",
+ vf->child->report) ;
+ } ;
+
+ /* Can now dismiss the child -- if not collected, is left on the register.
+ */
+ uty_child_dismiss(vf->child, false) ; /* not curtains */
+ } ;
+
+ /* Phase 4: if there is anything in the pipe stderr return buffer, finish
+ * it, and transfer to the vio->ps_buf (unless vio->cancel).
+ */
+ if (vf->ps_buf != NULL)
+ {
+ vio_fifo_trim(vf->ps_buf, true) ; /* trim trailing whitespace
+ * and '\n' terminate */
+ if (!vio_fifo_empty(vf->ps_buf) && !vio->cancel)
+ {
+ const char* what ;
+
+ if (vf->vin_type == VIN_PIPE)
+ what = "<|" ;
+ else if (vf->vout_type == VOUT_PIPE)
+ what = ">|" ;
+ else
+ what = "|" ;
+
+ if (vio->ps_buf == NULL)
+ vio->ps_buf = vio_fifo_new(1024) ;
+
+ vio_fifo_printf(vio->ps_buf, "%%--- in '%s %s':\n", what, vf->name) ;
+ vio_fifo_copy(vio->ps_buf, vf->ps_buf) ;
+ } ;
+
+ vf->ps_buf = vio_fifo_free(vf->ps_buf) ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Stop pipe return and/or pipe stderr return.
+ *
+ * Then if either vin and/or vout is open, set to vf_end to terminate I/O.
+ *
+ * There is no point continuing with anything once either return is broken.
+ */
+extern void
+uty_pipe_return_stop(vio_vf vf)
+{
+ if (vf->pr_state == vf_open)
+ vf->pr_state = vf_end ;
+
+ if (vf->ps_state == vf_open)
+ vf->ps_state = vf_end ;
+
+ if (vf->vin_state == vf_open)
+ vf->vin_state = vf_end ;
+
+ if (vf->vout_state == vf_open)
+ vf->vout_state = vf_end ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Cancel any further input from the pipe return.
+ *
+ * Note that does not touch anything buffered ready to be output in the
+ * slave -- that must be dealt with separately.
+ */
+extern void
+uty_pipe_return_cancel(vio_vf vf)
+{
+ qassert( (vf->vin_type == VIN_PIPE) || (vf->vout_type == VOUT_PIPE)
+ || (vf->vout_type == VOUT_SH_CMD) ) ;
+ qassert(vf->pr_state == vf_open) ;
+
+ vf->pr_state = vf_end ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get pipe stderr return buffer for the vf -- make one if none yet exists.
+ */
+static vio_fifo
+uty_pipe_ps_buf(vio_vf vf)
+{
+ if (vf->ps_buf == NULL)
+ vf->ps_buf = vio_fifo_new(1024) ;
+
+ return vf->ps_buf ;
+} ;
+
+/*==============================================================================
+ * stdout and stderr
+ *
+ *
+ */
+
diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h
new file mode 100644
index 00000000..701be801
--- /dev/null
+++ b/lib/vty_io_file.h
@@ -0,0 +1,70 @@
+/* VTY I/O for Files -- 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_VTY_IO_FILE_H
+#define _ZEBRA_VTY_IO_FILE_H
+
+#include "misc.h"
+
+#include "vty_io.h"
+#include "command_parse.h"
+
+/*==============================================================================
+ * Here are structures and other definitions which are shared by:
+ *
+ * vty_io.c -- the main VTY I/O stuff
+ *
+ * for I/O to files
+ */
+
+/*==============================================================================
+ * Functions
+ */
+extern vty vty_config_read_open(int fd, const char* name, bool full_lex) ;
+extern cmd_return_code_t uty_config_read_close(vio_vf vf, bool final) ;
+
+extern cmd_return_code_t uty_file_read_open(vty_io vio, qstring name,
+ cmd_context context) ;
+extern cmd_return_code_t uty_file_write_open(vty_io vio, qstring name,
+ bool append, cmd_context context, bool after) ;
+
+extern cmd_return_code_t uty_file_fetch_command_line(vio_vf vf,
+ cmd_action action) ;
+extern cmd_return_code_t uty_file_out_push(vio_vf vf, bool final, bool all) ;
+
+extern cmd_return_code_t uty_file_read_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_file_write_close(vio_vf vf, bool final) ;
+
+
+extern cmd_return_code_t uty_pipe_read_open(vty_io vio, qstring command,
+ cmd_context context) ;
+extern cmd_return_code_t uty_pipe_write_open(vty_io vio, qstring command,
+ bool shell_cmd, bool after) ;
+extern cmd_return_code_t uty_pipe_fetch_command_line(vio_vf vf,
+ cmd_action action) ;
+extern cmd_return_code_t uty_pipe_out_push(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_pipe_read_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_pipe_write_close(vio_vf vf, bool final) ;
+extern void uty_pipe_return_stop(vio_vf vf) ;
+extern void uty_pipe_return_cancel(vio_vf vf) ;
+
+#endif
diff --git a/lib/vty_io_shell.c b/lib/vty_io_shell.c
new file mode 100644
index 00000000..0b3cb5c4
--- /dev/null
+++ b/lib/vty_io_shell.c
@@ -0,0 +1,369 @@
+/* VTY IO SHELL -- VTY Shell I/O
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "zebra.h"
+
+#include "vty.h"
+#include "vty_io.h"
+#include "vty_cli.h"
+#include "qstring.h"
+#include "keystroke.h"
+
+#include "memory.h"
+
+#include "prefix.h"
+#include "filter.h"
+#include "privs.h"
+#include "sockunion.h"
+#include "network.h"
+
+#include <arpa/telnet.h>
+#include <sys/un.h> /* for VTYSH */
+#include <sys/socket.h>
+
+#define VTYSH_DEBUG 0
+
+#if 0
+
+/*------------------------------------------------------------------------------
+ * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session.
+ *
+ * Returns: new vty
+ */
+static struct vty *
+uty_new_shell_serv(int sock_fd)
+{
+ struct vty *vty ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Allocate new vty structure and set up default values. */
+ vty = uty_new (VTY_SHELL_SERV, sock_fd) ;
+ vio = vty->vio ;
+
+ /* Set the action functions */
+ if (vty_nexus)
+ {
+ vio->sock.action.read.qnexus = vtysh_read_qnexus ;
+ vio->sock.action.write.qnexus = vty_write_qnexus ;
+ vio->sock.action.timer.qnexus = NULL ;
+ }
+ else
+ {
+ vio->sock.action.read.thread = vtysh_read_thread ;
+ vio->sock.action.write.thread = vty_write_thread ;
+ vio->sock.action.timer.thread = NULL ;
+ } ;
+
+ vty->node = VIEW_NODE;
+
+ /* Kick start the CLI etc. */
+ uty_sock_set_readiness(&vio->sock, write_ready) ;
+
+ return vty;
+} ;
+
+
+/*------------------------------------------------------------------------------
+ * Open a VTY_SHEL_SERV listener socket (UNIX domain).
+ *
+ * Returns: < 0 => failed
+ * >= 0 => OK
+ */
+static int
+uty_serv_vtysh(const char *path)
+{
+ int ret;
+ int sock, sa_len, path_len ;
+ struct sockaddr_un sa_un ;
+ mode_t old_mask;
+ struct zprivs_ids_t ids;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* worry about the path length */
+ path_len = strlen(path) + 1 ;
+ if (path_len >= (int)sizeof(sa_un.sun_path))
+ {
+ zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
+ return -1 ;
+ } ;
+
+ /* First of all, unlink existing socket */
+ unlink (path);
+
+ /* Make UNIX domain socket. */
+ sock = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
+ errtoa(errno, 0).str) ;
+ return -1 ;
+ }
+
+ /* Bind to the required path */
+ memset (&sa_un, 0, sizeof(sa_un));
+ sa_un.sun_family = AF_UNIX;
+ strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1);
+
+ sa_len = SUN_LEN(&sa_un) ;
+
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ sa_un.sun_len = sa_len ;
+#endif
+
+ old_mask = umask (0007);
+
+ ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
+ if (ret < 0)
+ zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
+
+ if (ret >= 0)
+ ret = set_nonblocking(sock);
+
+ if (ret >= 0)
+ {
+ ret = listen (sock, 5);
+ if (ret < 0)
+ zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
+ errtoa(errno, 0).str) ;
+ } ;
+
+ zprivs_get_ids(&ids);
+
+ if (ids.gid_vty > 0)
+ {
+ /* set group of socket */
+ if ( chown (path, -1, ids.gid_vty) )
+ zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
+ errtoa(errno, 0).str) ;
+ }
+
+ umask (old_mask);
+
+ /* Give up now if failed along the way */
+ if (ret < 0)
+ {
+ close (sock) ;
+ return -1 ;
+ } ;
+
+ /* Socket is open -- set VTY Term listener going */
+ uty_serv_start_listener(sock, VTY_SHELL_SERV) ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_SHELL_SERV
+ */
+static int
+uty_accept_shell_serv (vty_listener listener)
+{
+ int sock_fd ;
+ int ret ;
+ int client_len ;
+ struct sockaddr_un client ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ client_len = sizeof(client);
+ memset (&client, 0, client_len);
+
+ sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client,
+ (socklen_t *) &client_len) ;
+
+ if (sock_fd < 0)
+ {
+ zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
+ errtoa(errno, 0).str) ;
+ return -1;
+ }
+
+ /* Really MUST have non-blocking */
+ ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */
+ if (ret < 0)
+ {
+ close(sock_fd) ;
+ return -1 ;
+ } ;
+
+ /* All set -- create the VTY_SHELL_SERV */
+ if (VTYSH_DEBUG)
+ printf ("VTY shell accept\n");
+
+ uty_new_shell_serv(sock_fd) ;
+
+ /* Log new VTY */
+ zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
+ return 0;
+}
+
+/*==============================================================================
+ * Reading from the VTY_SHELL_SERV type sock.
+ *
+ * The select/pselect call-back ends up in utysh_read_ready().
+ */
+
+/*------------------------------------------------------------------------------
+ * Ready to read -> kicking the "SHELL_SERV CLI"
+ *
+ * End up here when there is something ready to be read.
+ *
+ * Will also end up here if an error has occurred, the other end has closed,
+ * this end has half closed, etc. This fact is used to kick the CLI even when
+ * there is no data to be read.
+ *
+ * Note that nothing is actually read here -- reading is done in the CLI itself,
+ * if required.
+ *
+ * The CLI decides whether to re-enable read, or enable write, or both.
+ */
+static void
+utysh_read_ready(vty_io vio)
+{
+ uty_sock_set_read(&vio->sock, off) ;
+
+ /* TODO: need minimal "CLI" for VTY_SHELL_SERV
+ * NB: when output from command is flushed out, must append the
+ * following four bytes: '\0' '\0' '\0' <ret>
+ * Where <ret> is the command return code.
+ */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static void
+vtysh_read_qnexus(qps_file qf, void* file_info)
+{
+ vty_io vio = file_info;
+
+ VTY_LOCK() ;
+
+ assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ;
+
+ utysh_read_ready(vio) ;
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static int
+vtysh_read_thread(struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ;
+
+ vio->sock.t_read = NULL ; /* implicitly */
+ utysh_read_ready(vio);
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Read a lump of bytes and shovel into the command line buffer
+ *
+ * Lines coming in are terminated by '\0'.
+ *
+ * Assumes that the incoming command line is empty or otherwise incomplete.
+ *
+ * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping
+ * when get '\0' or empties the "buf".
+ *
+ * When empties "buf", reads a lump from the sock.
+ *
+ * Returns: 0 => command line is incomplete
+ * 1 => have a complete command line
+ * -1 => EOF (or not open, or failed)
+ */
+extern int
+utysh_read (vty_io vio, qstring cl, qstring buf)
+{
+ int get ;
+ char* cp ;
+ char* ep ;
+ size_t have ;
+
+ while (1)
+ {
+ /* process what there is in the buffer */
+ if (buf->len > buf->cp)
+ {
+ cp = qs_cp_char(buf) ;
+ ep = qs_ep_char(buf) ;
+ have = ep - cp ;
+
+ ep = memchr(cp, '\0', have) ;
+ if (ep != NULL)
+ have = ep - cp ; /* have upto, but excluding '\0' */
+
+ if (have > 0) /* take what have */
+ {
+ qs_insert(cl, cp, have) ;
+ cl->cp += have ;
+ buf->cp += have ;
+ } ;
+
+ if (ep != NULL) /* if found '\0' */
+ {
+ qs_term(cl) ; /* '\0' terminate */
+ ++buf->cp ; /* step past it */
+ return 1 ; /* have a complete line <<<<<<<<<<<<< */
+ }
+ } ;
+
+ /* buffer is empty -- try and get some more stuff */
+ assert(buf->len == buf->cp) ;
+
+ if (!vio->sock.read_open)
+ return -1 ; /* at EOF if not open <<<<<<<<<<<<< */
+
+ qs_need(buf, 500) ; /* need a reasonable lump */
+ qs_clear(buf) ; /* set cp = len = 0 */
+
+ get = read_nb(vio->sock.fd, buf->body, buf->size) ;
+ if (get > 0)
+ buf->len = get ;
+ else if (get == 0)
+ return 0 ; /* have an incomplete line <<<<<<<<<<<< */
+ else
+ {
+ if (get == -1)
+ uty_sock_error(vio, "read") ;
+
+ vio->sock.read_open = 0 ;
+
+ return -1 ; /* at EOF or failed <<<<<<<<<<<<< */
+ } ;
+ } ;
+} ;
+
+#endif
diff --git a/lib/vty_io_shell.h b/lib/vty_io_shell.h
new file mode 100644
index 00000000..87cd92d0
--- /dev/null
+++ b/lib/vty_io_shell.h
@@ -0,0 +1,55 @@
+/* VTY IO SHELL -- VTY Shell I/O -- header
+ * Virtual terminal [aka TeletYpe] interface routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_IO_SHELL_H
+#define _ZEBRA_VTY_IO_SHELL_H
+
+#include "misc.h"
+#include <errno.h>
+
+#include "uty.h"
+#include "vty.h"
+#include "vty_io.h"
+#include "vio_fifo.h"
+#include "vio_lines.h"
+#include "keystroke.h"
+#include "thread.h"
+#include "command.h"
+#include "qstring.h"
+
+/*==============================================================================
+ * Here are structures and other definitions which are shared by:
+ *
+ * vty_io.c -- the main VTY I/O stuff
+ *
+ * for I/O for VTY Shell Server.
+ */
+
+/*==============================================================================
+ * Functions
+ */
+
+extern int uty_vprintf_shell(vty_io vio, const char *format, va_list args) ;
+
+#endif /* _ZEBRA_VTY_IO_SHELL_H */
diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c
new file mode 100644
index 00000000..0660afb6
--- /dev/null
+++ b/lib/vty_io_term.c
@@ -0,0 +1,1607 @@
+/* VTY IO TERM -- Telnet Terminal I/O
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "misc.h"
+
+#include "vty_local.h"
+#include "vty_io.h"
+#include "vty_io_term.h"
+#include "vty_io_file.h"
+#include "vty_cli.h"
+#include "vty_command.h"
+#include "vio_fifo.h"
+
+#include "log_local.h"
+
+#include "qstring.h"
+#include "keystroke.h"
+
+#include "memory.h"
+
+#include "prefix.h"
+#include "filter.h"
+#include "privs.h"
+#include "sockunion.h"
+#include "sockopt.h"
+#include "network.h"
+
+#include <arpa/telnet.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+/*==============================================================================
+ * The I/O side of Telnet VTY_TERMINAL. The CLI side is vty_cli.c.
+ *
+ * A VTY_TERMINAL comes into being when a telnet connection is accepted, and
+ * is closed either on command, or on timeout, or when the daemon is reset
+ * or terminated.
+ *
+ * All VIN_TERM and VOUT_TERM I/O is non-blocking.
+ */
+
+/*==============================================================================
+ * If possible, will use getaddrinfo() to find all the things to listen on.
+ */
+#if defined(HAVE_IPV6) && !defined(NRL)
+# define VTY_USE_ADDRINFO 1
+#else
+# define VTY_USE_ADDRINFO 0
+#endif
+
+/*==============================================================================
+ * Opening and closing VTY_TERMINAL type
+ */
+
+static void uty_term_read_ready(vio_vfd vfd, void* action_info) ;
+static void uty_term_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_term_read_timeout(vio_timer timer,
+ void* action_info) ;
+static vty_timer_time uty_term_write_timeout(vio_timer timer,
+ void* action_info) ;
+
+typedef enum { /* see uty_term_write() */
+ utw_null = 0,
+
+ utw_error,
+
+ utw_done,
+
+ utw_blocked,
+ utw_paused,
+ utw_more_enter,
+
+} utw_ret_t ;
+
+static utw_ret_t uty_term_write(vio_vf vf) ;
+
+static void uty_term_will_echo(vty_cli cli) ;
+static void uty_term_will_suppress_go_ahead(vty_cli cli) ;
+static void uty_term_dont_linemode(vty_cli cli) ;
+static void uty_term_do_window_size(vty_cli cli) ;
+static void uty_term_dont_lflow_ahead(vty_cli cli) ;
+
+/*------------------------------------------------------------------------------
+ * Create new vty of type VTY_TERMINAL -- ie attached to a telnet session.
+ *
+ * This is called by the accept action for the VTY_TERMINAL listener.
+ */
+static void
+uty_term_open(int sock_fd, union sockunion *su)
+{
+ node_type_t node ;
+ vty vty ;
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ /* The initial vty->node will be authentication, unless the host does not
+ * require that, in which case it may be a number of other things.
+ *
+ * Note that setting NULL_NODE at this point will cause the terminal to be
+ * closed very quickly, after issuing suitable message.
+ */
+ node = (host.password != NULL) ? AUTH_NODE : NULL_NODE ;
+
+ if (host.no_password_check)
+ {
+ if (host.restricted_mode)
+ node = RESTRICTED_NODE;
+ else if (host.advanced && (host.enable == NULL))
+ node = ENABLE_NODE;
+ else
+ node = VIEW_NODE;
+ } ;
+
+ /* Allocate new vty structure and set up default values.
+ *
+ * This completes the initialisation of the vty object, except that the
+ * execution and vio objects are largely empty.
+ */
+ vty = uty_new(VTY_TERMINAL, node) ;
+ vio = vty->vio ;
+
+ /* Complete the initialisation of the vty_io object.
+ *
+ * Note that the defaults for:
+ *
+ * - read_timeout -- default = 0 => no timeout
+ * - write_timeout -- default = 0 => no timeout
+ *
+ * - parse_type -- default = cmd_parse_standard
+ * - reflect_enabled -- default = false
+ *
+ * Are OK, except that we want the read_timeout set to the current EXEC
+ * timeout value.
+ *
+ * The text form of the address identifies the VTY.
+ */
+ vf = uty_vf_new(vio, sutoa(su).str, sock_fd, vfd_socket, vfd_io_read_write) ;
+
+ uty_vin_push( vio, vf, VIN_TERM, uty_term_read_ready,
+ uty_term_read_timeout,
+ 0) ; /* no ibuf required */
+ uty_vout_push(vio, vf, VOUT_TERM, uty_term_write_ready,
+ uty_term_write_timeout,
+ 4096, /* obuf is required */
+ true) ; /* after buddy vin */
+
+ vf->read_timeout = host.vty_timeout_val ; /* current EXEC timeout */
+ vf->write_timeout = 30 ; /* something reasonable */
+
+ /* Set up the CLI object & initialise */
+ vf->cli = uty_cli_new(vf) ;
+
+ /* Issue Telnet commands/escapes to be a good telnet citizen -- not much
+ * real negotiating going on -- just a statement of intentions !
+ */
+ uty_term_will_echo (vf->cli);
+ uty_term_will_suppress_go_ahead (vf->cli);
+ uty_term_dont_linemode (vf->cli);
+ uty_term_do_window_size (vf->cli);
+ if (0)
+ uty_term_dont_lflow_ahead (vf->cli) ;
+
+ /* Say hello */
+ vty_hello(vty);
+
+ /* If about to authenticate, issue friendly message.
+ *
+ * If cannot authenticate, issue an error message.
+ */
+ if (vty->node == AUTH_NODE)
+ vty_out(vty, "User Access Verification\n") ;
+ else if (vty->node == NULL_NODE)
+ vty_out(vty, "%% Cannot continue because no password is set\n") ;
+
+ /* Enter the command loop. */
+ uty_cmd_queue_loop_enter(vio) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command line fetch from a VTY_TERMINAL -- in vf_open state.
+ *
+ * Fetching a command line <=> the previous command has completed.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * or: CMD_WAITING -- would not wait for input
+ *
+ * This can be called in any thread.
+ *
+ * Note that does not signal CMD_EOF because that is handled for the
+ * VTY_TERMINAL by the cmd_do_eof "special command".
+ *
+ * Note that this does no actual I/O, all that is done in the pselect() process,
+ * while a command line is collected in the CLI. So does not here return
+ * CMD_IO_ERROR -- any errors are dealt with by signalling the command loop.
+ */
+extern cmd_return_code_t
+uty_term_fetch_command_line(vio_vf vf, cmd_action action, cmd_context context)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_state == vf_open) ;
+
+ return uty_cli_want_command(vf->cli, action, context) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Showing error context for the VTY_TERMINAL command line.
+ *
+ * If the stack is at level == 1, then the last full line displayed will be
+ * the line in which the error occurred (unless have monitor output, which
+ * there is little we can do about). So there is no further output required.
+ * The command line is indented by the current prompt.
+ *
+ * If the stack is at level > 1, then may or may not have had output separating
+ * the command line from the current position, so we output the command line
+ * to provide context.
+ *
+ * Returns: indent position of command line
+ */
+extern uint
+uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth)
+{
+ if (depth == 1)
+ return uty_cli_prompt_len(vf->cli) ;
+
+ vio_fifo_printf(ebuf, "%% in command line:\n") ;
+ vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cli->clx)) ;
+
+ return 2 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push output to the terminal -- always not vf->blocking !
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * "final" => would have waited but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ *
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal().
+ */
+extern cmd_return_code_t
+uty_term_out_push(vio_vf vf, bool final)
+{
+ vty_cli cli = vf->cli ;
+ utw_ret_t done ;
+
+ qassert(vf->vout_state == vf_open) ;
+ qassert(!vf->blocking) ;
+
+ /* If squelching, dump anything we have in the obuf.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+
+ /* If have something in the obuf that needs to be written, then if not
+ * already out_active, make sure the command line is clear, and set
+ * out_active.
+ */
+ if (!cli->out_active && !vio_fifo_empty(vf->obuf))
+ {
+ uty_cli_wipe(cli, 0) ;
+ cli->out_active = true ;
+ vio_lc_counter_reset(cli->olc) ;
+ } ;
+
+ /* Give the terminal writing a shove.
+ *
+ * If final, keep pushing while succeeds in writing without blocking.
+ *
+ * Note that is only called "final" when closing the vout, by which time
+ * the "--more--" handling has been turned off and any output has been
+ * released.
+ */
+ if (!final)
+ done = uty_term_write(vf) ;
+ else
+ {
+ do
+ {
+ vio_lc_counter_reset(cli->olc) ;
+ done = uty_term_write(vf) ;
+ } while (done == utw_paused) ;
+ } ;
+
+ /* Deal with the result
+ *
+ * If required and if not final make sure that write ready is set, so
+ * that the pselect() process can pursue the issue.
+ *
+ * Return code depends on utw_xxx
+ */
+ if (done == utw_done)
+ return CMD_SUCCESS ; /* don't set write ready */
+
+ if (done == utw_error)
+ return CMD_IO_ERROR ; /* don't set write ready */
+
+ if (!final)
+ uty_term_set_readiness(vf, write_ready) ;
+
+ return CMD_WAITING ; /* waiting for write ready */
+} ;
+
+/*------------------------------------------------------------------------------
+ * The read side of the vfd has been closed. Close down CLI as far as
+ * possible, given that output may be continuing.
+ *
+ * Expects to be called once only for the VTY_TERMINAL.
+ *
+ * There is no difference between final and not-final close in this case.
+ *
+ * Note that this is only closed when the VTY_TERMINAL is forcibly closed, or
+ * when the user quits.
+ *
+ * Returns: CMD_SUCCESS -- all is quiet.
+ */
+extern cmd_return_code_t
+uty_term_read_close(vio_vf vf, bool final)
+{
+ vty_io vio ;
+
+ qassert(vf->vin_state == vf_end) ;
+
+ /* Get the vio and ensure that we are all straight */
+ vio = vf->vio ;
+ qassert((vio->vin == vio->vin_base) && (vio->vin == vf)) ;
+
+ /* Kill monitor state */
+ uty_set_monitor(vio, off) ;
+
+ /* Close the CLI as far as possible, leaving output side intact.
+ *
+ * Can generate some final output, which will be dealt with as the output
+ * side is closed.
+ */
+ uty_cli_close(vf->cli, false) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Try to output the close reason to the given VOUT_TERM.
+ *
+ * If there is anything pending to be output, discard it, first. The obuf has
+ * already been cleared.
+ *
+ * This will be pushed out when the VOUT_TERM is finally closed.
+ */
+extern void
+uty_term_close_reason(vio_vf vf, const char* reason)
+{
+ vio_lc_clear(vf->cli->olc) ;
+
+ vf->cli->mon_active = false ; /* stamp on any monitor output */
+
+ uty_cli_out(vf->cli, "%% %s%s", reason, uty_cli_newline) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close the writing side of VTY_TERMINAL.
+ *
+ * Assumes that the read side has been closed already, and so this is the last
+ * thing to be closed. Any monitor state was turned off earlier when the read
+ * side was closed.
+ *
+ * Kicks the output side:
+ *
+ * if final, will push as much as possible until all gone, would block or
+ * gets error. In any event, closes the cli, final.
+ *
+ * if not final, will push another tranche and let the uty_term_ready() keep
+ * pushing until buffers empty and can uty_cmd_signal().
+ *
+ * Returns: CMD_SUCCESS => all written,
+ * or cannot write anything (more),
+ * or final and wrote what could
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * "final" => would have waited but did not.
+ *
+ * NB: if an error occurs while writing, that will have been logged, but there
+ * is nothing more to be done about it here -- so does not return
+ * CMD_IO_ERROR.
+ */
+extern cmd_return_code_t
+uty_term_write_close(vio_vf vf, bool final)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Get the vio and ensure that we are all straight
+ *
+ * Can only be the vout_base and must also be the vin_base, and the vin_base
+ * must now be closed.
+ */
+ vio = vf->vio ;
+ qassert((vio->vout == vio->vout_base) && (vio->vout == vf)) ;
+ qassert((vio->vin == vio->vin_base) && (vio->vin->vin_state == vf_closed)) ;
+
+ ret = CMD_SUCCESS ;
+
+ if (vf->vout_state == vf_open)
+ {
+ ret = uty_term_out_push(vf, final) ;
+
+ if (ret != CMD_WAITING)
+ ret = CMD_SUCCESS ;
+ } ;
+
+ if (final)
+ {
+ vf->cli = uty_cli_close(vf->cli, final) ;
+
+ qassert(vio->vty->type == VTY_TERMINAL) ;
+ zlog (NULL, LOG_INFO, "Vty connection (fd %d) close",
+ vio_vfd_fd(vf->vfd)) ;
+ } ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ * Readiness and the VIN_TERM type vin.
+ *
+ * For TERM stuff the driving force is write ready. This is used to prompt the
+ * VOUT_TERM when there is outstanding output (obviously), but also if there
+ * is buffered input in the keystroke stream.
+ *
+ * The VIN_TERM is read ready permanently, until eof is met. Note that the
+ * read timeout is reset each time uty_term_set_readiness is called. When
+ * eof is met, the VIN_TERM is read closed, which prevents any further setting
+ * of read ready and its timeout.
+ */
+
+static void uty_term_ready(vio_vf vf) ;
+
+/*------------------------------------------------------------------------------
+ * Set read/write readiness -- for VIN_TERM/VOUT_TERM
+ *
+ * Is permanently read-ready (until eof or no longer vin_state == vf_open).
+ */
+extern void
+uty_term_set_readiness(vio_vf vf, vty_readiness_t ready)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if ((ready & write_ready) != 0)
+ uty_vf_set_write(vf, on) ;
+
+ uty_vf_set_read(vf, on) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Terminal read ready
+ */
+static void
+uty_term_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ qassert(vfd == vf->vfd) ;
+
+ vf->cli->paused = false ; /* read ready clears paused */
+
+ uty_term_read(vf) ;
+ uty_term_ready(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Terminal write ready
+ */
+static void
+uty_term_write_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ qassert(vfd == vf->vfd) ;
+
+ uty_term_ready(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Terminal, something is ready -- read, write or no longer paused.
+ *
+ * 1. attempts to clear any output it can.
+ *
+ * The state of the output affects the CLI, so must always do this before
+ * before invoking the CLI.
+ *
+ * If this write enters the "--more--" state, then will have tried to
+ * write away the prompt.
+ *
+ * 2. invokes the CLI
+ *
+ * Which will do either the standard CLI stuff or the special "--more--"
+ * stuff.
+ *
+ * 3. attempts to write any output there now is.
+ *
+ * If the CLI generated new output, as much as possible is written away
+ * now.
+ *
+ * If this write enters the "--more--" state, then it returns now_ready,
+ * if the prompt was written away, which loops back to the CLI.
+ *
+ * Note that this is arranging:
+ *
+ * a. to write away the "--more--" prompt as soon as the tranche of output to
+ * which it refers, completes
+ *
+ * b. to enter the cli_more_wait CLI for the first time immediately after the
+ * "--more--" prompt is written away.
+ *
+ * The loop limits itself to one trache of command output each time.
+ *
+ * Resets the timer because something happened.
+ */
+static void
+uty_term_ready(vio_vf vf)
+{
+ vty_readiness_t ready ;
+ utw_ret_t done, done_before ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Will attempt to write away any pending stuff, then for each call of
+ * uty_term_ready, put out another tranche of output (unless in '--more--'
+ * state).
+ */
+ if (!vf->cli->more_enabled)
+ vio_lc_counter_reset(vf->cli->olc) ; /* do one tranche */
+
+ /* Now loop kicking the CLI and the output, until stops changing.
+ *
+ * This is because the CLI may generate more to write, and writing stuff
+ * away may release the CLI.
+ */
+ done = utw_null ;
+ do
+ {
+ done_before = done ;
+
+ /* Kick the CLI, which may advance either because there is more input,
+ * or because some output has now completed, or for any other reason.
+ *
+ * This may return write_ready, which is a proxy for CLI ready, and
+ * MUST be honoured, even (especially) if the output buffers are empty.
+ */
+ ready = uty_cli(vf->cli) ;
+
+ /* Now try to write away any new output which may have been generated
+ * by the CLI.
+ *
+ * Note that when enters "--more--" will return utw_more_enter, once,
+ * which causes a loop back to uty_cli(), which will start the process.
+ * When comes through here a second time, will return utw_done, once
+ * any prompt etc has been output.
+ */
+ done = uty_term_write(vf) ;
+
+ if (done == utw_error)
+ return ; /* quit if in error */
+
+ } while (done != done_before) ;
+
+ if (done != utw_done) /* isn't utw_error, either */
+ ready |= write_ready ;
+
+ uty_term_set_readiness(vf, ready) ;
+
+ /* Signal the command loop if not waiting for write any more.
+ */
+ if ((ready & write_ready) == 0)
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read timer has expired.
+ *
+ * Discard anything in the keystroke stream and set it "eof, timed-out". This
+ * will be picked up by the CLI and a cmd_do_timed-out will float out.
+ */
+static vty_timer_time
+uty_term_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ qassert(timer == vf->vfd->read_timer) ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ keystroke_stream_set_eof(vf->cli->key_stream, true) ; /* timed out */
+
+ vf->cli->paused = false ;
+
+ uty_term_ready(vf) ;
+
+ return 0 ;
+ } ;
+
+/*------------------------------------------------------------------------------
+ * Write timer has expired.
+ *
+ * Signal write timeout error.
+ */
+static vty_timer_time
+uty_term_write_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ qassert(timer == vf->vfd->write_timer) ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ vf->cli->paused = false ;
+
+ uty_vf_error(vf, verr_to_vout, 0) ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Timeout on the cli->paused timer -- clear paused and treat as CLI ready.
+ */
+extern void
+vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when)
+{
+ vty_cli cli ;
+
+ VTY_LOCK() ;
+
+ cli = timer_info ;
+ assert(cli->pause_timer == qtr) ;
+
+ if (cli->paused)
+ {
+ cli->paused = false ;
+ uty_term_ready(cli->vf) ;
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*==============================================================================
+ * Reading from VTY_TERMINAL.
+ *
+ * The select/pselect call-back ends up in uty_term_ready().
+ */
+
+/*------------------------------------------------------------------------------
+ * Read a lump of bytes and shovel into the keystroke stream
+ *
+ * NB: need not be in the term_ready path. Indeed, when the VTY_TERMINAL is
+ * initialised, this is called to suck up any telnet preamble.
+ *
+ * NB: the terminal is permanently read-ready, so will keep calling this
+ * until all input is hoovered up. For real terminals it is assumed that
+ * reading a lump of bytes this small will immediately empty the input
+ * buffer.
+ *
+ * When reaches EOF on the input eof is set in the keystroke stream, and the
+ * vfd is read closed. Read closing the vfd turns off any timeout and prevents
+ * any further setting of read_ready (to avoid permanent read_ready !). Does
+ * NOT set vf->vin_state to vf_end, because that is not true until the
+ * keystroke stream is empty. Once eof is set in the keystroke stream, this
+ * code will not attempt any further input from the vfd.
+ *
+ * Returns: 0 => nothing available
+ * > 0 => read at least one byte
+ * == -1 => I/O error
+ * == -2 => hit EOF (without reading anything else)
+ */
+extern int
+uty_term_read(vio_vf vf)
+{
+ keystroke_stream stream ;
+ unsigned char buf[500] ;
+ int get ;
+
+ stream = vf->cli->key_stream ;
+
+ if (keystroke_stream_met_eof(stream))
+ return -2 ; /* already seen EOF */
+
+ if (vf->vin_state != vf_open)
+ {
+ /* If is not vf_open, but also not seen EOF on the keystroke stream,
+ * then now is a good moment to empty out the keystroke stream and
+ * force it to EOF.
+ */
+ keystroke_stream_set_eof(vf->cli->key_stream, false) ;
+ return -2 ;
+ } ;
+
+ /* OK: read from input and pass result to keystroke stream.
+ */
+ get = read_nb(vio_vfd_fd(vf->vfd), buf, sizeof(buf)) ;
+ /* -1 <=> error, -2 <=> EOF */
+ if (get != 0)
+ {
+ if (get == -1)
+ {
+ /* Error: signal to command loop and force key_stream empty */
+ uty_vf_error(vf, verr_io_vin, errno) ;
+ keystroke_stream_set_eof(vf->cli->key_stream, false) ;
+ /* not timed-out */
+ }
+ else
+ {
+ /* Not error. get < 0 => EOF and that is set in key_stream. */
+ keystroke_input(vf->cli->key_stream, buf, get) ;
+ } ;
+
+ if (get < 0)
+ vio_vfd_read_close(vf->vfd) ;
+ } ;
+
+ return get ;
+} ;
+
+/*==============================================================================
+ * Writing to VOUT_TERM -- driven by ready state.
+ *
+ * There are two sets of buffering:
+ *
+ * cli->cbuf -- command line -- reflects the status of the command line
+ *
+ * vf->obuf -- command output -- which is written to the file only while
+ * out_active, and not blocked in more_wait.
+ *
+ * The cli output takes precedence.
+ *
+ * Output of command stuff is subject to line_control, and may go through the
+ * "--more--" mechanism.
+ */
+
+static utw_ret_t uty_term_write_lc(vio_line_control lc, vio_vf vf,
+ vio_fifo vff) ;
+
+/*------------------------------------------------------------------------------
+ * Have some (more) monitor output to send to the vty.
+ *
+ * Make sure the command line is clear, then claim screen for monitor output
+ * and attempt to empty out buffers.
+ */
+extern void
+uty_term_mon_write(vio_vf vf)
+{
+ utw_ret_t done ;
+
+ uty_cli_pre_monitor(vf->cli) ; /* make sure in a fit state */
+
+ done = uty_term_write(vf) ; /* this calls uty_vf_error() if
+ * there are any errors */
+ if ((done != utw_done) && (done != utw_error))
+ uty_term_set_readiness(vf, write_ready) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write as much as possible of what there is.
+ *
+ * Move to more_wait if required.
+ *
+ * If is cli->flush, then when all buffers are emptied out, clears itself and
+ * the out_active flag.
+ *
+ * Returns:
+ *
+ * utw_error -- I/O error either now or sometime earlier.
+ *
+ * utw_done -- have written everything can find
+ *
+ * utw_blocked -- write blocked -- some write operation would block
+ *
+ * Need to set write ready & wait for it.
+ *
+ * utw_paused -- have written as much as line control allows in one go.
+ *
+ * Note that is *not* in "--more--" state, this is all to
+ * do with limiting work on each visit.
+ *
+ * Need to set write ready & wait for it.
+ *
+ * utw_more_enter -- has just entered "--more--" state
+ *
+ * Need either to set write ready, or call the CLI.
+ */
+static utw_ret_t
+uty_term_write(vio_vf vf)
+{
+ vty_cli cli = vf->cli ;
+ utw_ret_t ret ;
+ int did ;
+ ulen have, take ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* If the vout is neither vf_open nor vf_closing, discard all buffered
+ * output, and return all done.
+ */
+ if (vf->vout_state != vf_open)
+ {
+ qassert(vf->vout_state == vf_end) ;
+
+ vio_fifo_clear(vf->obuf, false) ;
+ vio_fifo_clear(cli->cbuf, false) ;
+ vio_lc_clear(cli->olc) ;
+
+ cli->out_active = false ;
+
+ cli->more_wait = false ;
+ cli->more_enter = false ;
+
+ return utw_error ;
+ } ;
+
+ /* Any outstanding line control output takes precedence */
+ if (vio_lc_pending(cli->olc))
+ {
+ ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
+ if (ret != utw_done)
+ return ret ; /* utw_blocked or utw_error */
+ } ;
+
+ /* Next: empty out the cli output */
+ did = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ;
+
+ if (did > 0)
+ return utw_blocked ;
+
+ if (did < 0)
+ {
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ return utw_error ;
+ } ;
+
+ /* Next: if there is monitor output to deal with, deal with it.
+ *
+ * Note that the mon_active flag is set under VTY_LOCK(), so do not
+ * need to LOG_LOCK() to discover whether there is anything to do.
+ *
+ * But the vio->mbuf is filled under LOG_LOCK(), so need to write it
+ * under the same.
+ */
+ if (cli->mon_active)
+ {
+ LOG_LOCK() ;
+
+ did = vio_fifo_write_nb(vf->vio->mbuf, vio_vfd_fd(vf->vfd), true) ;
+
+ LOG_UNLOCK() ;
+
+ if (did > 0)
+ return utw_blocked ;
+
+ if (did < 0)
+ {
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ return utw_error ;
+ } ;
+
+ uty_cli_post_monitor(vf->cli) ;
+ } ;
+
+ /* If not out_active, or in more_wait, then we are done, waiting for some
+ * external event to move things on.
+ */
+ if ((!cli->out_active) || (cli->more_wait))
+ return utw_done ; /* can do no more */
+
+ /* Push the output fifo and any complete line fragments that may be buffered
+ * in hand in the line control -- this will stop if the line counter becomes
+ * exhausted.
+ *
+ * Note that this arranges for vio_lc_append() to be called at least once,
+ * even if the fifo is empty -- this deals with any parts of a complete
+ * line that may be held in the line control due to counter exhaustion.
+ *
+ * If the fifo is or becomes empty, then if is cli->flush, flush out any
+ * incomplete line which may be held in the line control -- in effect,
+ * cli->flush is a phantom '\n' at the end of the output fifo !
+ */
+ vio_fifo_set_hold_mark(vf->obuf) ; /* released in uty_term_write_lc() */
+
+ have = vio_fifo_get(vf->obuf) ;
+ while (1)
+ {
+ take = vio_lc_append(cli->olc, vio_fifo_get_ptr(vf->obuf), have) ;
+
+ if (take == 0)
+ break ;
+
+ have = vio_fifo_step_get(vf->obuf, take) ;
+
+ if (have == 0)
+ break ;
+ } ;
+
+ if ((have == 0) && !cli->in_progress)
+ vio_lc_flush(cli->olc) ;
+
+ ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
+ if (ret != utw_done)
+ return ret ; /* utw_blocked or utw_error */
+
+ /* If arrive here, then:
+ *
+ * * no output is blocked and no errors have occurred.
+ *
+ * * the cli->cbuf is empty.
+ *
+ * * the line control iovec buffer is empty.
+ *
+ * If the fifo is not empty or there is a some part of a complete line in
+ * hand, then the line counter must be exhausted.
+ */
+ if ((have != 0) || vio_lc_have_complete_line_in_hand(cli->olc))
+ {
+ qassert(vio_lc_counter_is_exhausted(cli->olc)) ;
+
+ if (cli->more_enabled)
+ {
+ uty_cli_enter_more_wait(cli) ;
+ return utw_more_enter ;
+ }
+ else
+ return utw_paused ; /* artificial block */
+ } ;
+
+ /* Exciting stuff: there is nothing left to output...
+ *
+ * ...with the sole possible exception of an incomplete line buffered
+ * in the line control, which can do nothing about until there is more
+ * to be output, or the output is flushed...
+ *
+ * ...if not cli->flush, we are stopped, waiting for something else to
+ * happen.
+ */
+ qassert(!cli->more_wait && !cli->more_enter) ;
+
+ if (cli->in_progress)
+ return utw_done ; /* can do no more */
+
+ /* Even more exciting: is !cli->in_progress !
+ *
+ * This means that any incomplete line must have been flushed, above.
+ * So all buffers MUST be empty.
+ */
+ qassert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ;
+
+ cli->out_active = false ;
+
+ return utw_done ; /* absolutely all done */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write contents of line control iovec buffer (if any).
+ *
+ * NB: expects that the vf is write_open
+ *
+ * NB: does nothing other than write() and buffer management.
+ *
+ * Returns: utw_blocked => blocked
+ * utw_done => all gone (may still have stuff "in hand")
+ * utw_error => failed -- error posted to uty_vf_error()
+ */
+static utw_ret_t
+uty_term_write_lc(vio_line_control lc, vio_vf vf, vio_fifo vff)
+{
+ int did ;
+
+ did = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ;
+
+ if (did < 0)
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ else if (did > 0)
+ return utw_blocked ;
+
+ vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */
+
+ return (did == 0) ? utw_done : utw_error ;
+} ;
+
+/*==============================================================================
+ * VTY Listener(s) for VTY_TERMINAL
+ */
+
+/* Prototypes for listener stuff */
+
+static int uty_term_listen_addrinfo(const char *addr, unsigned short port) ;
+static int uty_term_listen_simple(const char *addr, unsigned short port) ;
+static int uty_term_listen_open(sa_family_t family, int type, int protocol,
+ struct sockaddr* sa, unsigned short port) ;
+static void uty_term_accept(int sock_listen) ;
+
+/*------------------------------------------------------------------------------
+ * Open listener(s) for VTY_TERMINAL -- using getaddrinfo() for preference.
+ */
+extern void
+uty_term_open_listeners(const char *addr, unsigned short port)
+{
+ int n ;
+
+ if (VTY_USE_ADDRINFO)
+ n = uty_term_listen_addrinfo(addr, port);
+ else
+ n = uty_term_listen_simple(addr, port);
+
+ if (n == 0)
+ zlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open listener(s) for VTY_TERMINAL -- using getaddrinfo()
+ *
+ * Returns: number of listeners successfully opened.
+ */
+static int
+uty_term_listen_addrinfo(const char *addr, unsigned short port)
+{
+#if VTY_USE_ADDRINFO /******************************************************/
+
+# ifndef HAVE_IPV6
+# error Using getaddrinfo() but HAVE_IPV6 is not defined ??
+# endif
+
+ int ret;
+ int n ;
+ struct addrinfo req;
+ struct addrinfo *ainfo;
+ struct addrinfo *ainfo_save;
+ char port_str[16];
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Want to listen, TCP-wise, on all available address families, on the
+ * given port.
+ */
+ memset (&req, 0, sizeof (struct addrinfo));
+ req.ai_flags = AI_PASSIVE;
+ req.ai_family = AF_UNSPEC;
+ req.ai_socktype = SOCK_STREAM;
+ snprintf(port_str, sizeof(port_str), "%d", port);
+
+ ret = getaddrinfo (addr, port_str, &req, &ainfo);
+
+ if (ret != 0)
+ {
+ fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str);
+ exit (1);
+ }
+
+ /* Open up sockets on all AF_INET and AF_INET6 addresses */
+ ainfo_save = ainfo;
+
+ n = 0 ;
+ do
+ {
+ if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6))
+ continue;
+
+ qassert(ainfo->ai_family == ainfo->ai_addr->sa_family) ;
+
+ ret = uty_term_listen_open(ainfo->ai_family, ainfo->ai_socktype,
+ ainfo->ai_protocol, ainfo->ai_addr, port) ;
+
+ if (ret >= 0)
+ ++n ;
+ }
+ while ((ainfo = ainfo->ai_next) != NULL);
+
+ freeaddrinfo (ainfo_save);
+
+ return n ;
+
+#else
+ zabort("uty_serv_sock_addrinfo not implemented") ;
+#endif /* VTY_USE_ADDRINFO ****************************************************/
+}
+
+/*------------------------------------------------------------------------------
+ * Open listener(s) for VTY_TERM -- not using getaddrinfo() !
+ *
+ * Returns: number of listeners successfully opened.
+ */
+static int
+uty_term_listen_simple(const char *addr, unsigned short port)
+{
+ int ret;
+ int n ;
+ union sockunion su_addr ;
+ struct sockaddr* sa ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ n = 0 ; /* nothing opened yet */
+
+ /* If have an address, see what kind and whether valid */
+ sa = NULL ;
+
+ if (addr != NULL)
+ {
+ ret = str2sockunion (addr, &su_addr) ;
+ if (ret == 0)
+ sa = &su_addr.sa ;
+ else
+ zlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr);
+ } ;
+
+ /* Try for AF_INET */
+ ret = uty_term_listen_open(AF_INET, SOCK_STREAM, 0, sa, port) ;
+ if (ret >= 0)
+ ++n ; /* opened socket */
+ if (ret == 1)
+ sa = NULL ; /* used the address */
+
+#if HAVE_IPV6
+ /* Try for AF_INET6 */
+ ret = uty_term_listen_open(AF_INET6, SOCK_STREAM, 0, sa, port) ;
+ if (ret >= 0)
+ ++n ; /* opened socket */
+ if (ret == 1)
+ sa = NULL ; /* used the address */
+#endif
+
+ /* If not used the address... something wrong */
+ if (sa != NULL)
+ zlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr);
+
+ /* Done */
+ return n ;
+}
+
+/*------------------------------------------------------------------------------
+ * Open a VTY_TERMINAL listener socket.
+ *
+ * The sockaddr 'sa' may be NULL or of a different address family, in which
+ * case "any" address is used.
+ *
+ * If the sockaddr 'sa' is used, only the address portion is used.
+ *
+ * Returns: < 0 => failed
+ * == 0 => OK -- did not use the sockaddr 'sa'.
+ * > 1 => OK -- and did use the sockaddr 'sa'
+ */
+static int
+uty_term_listen_open(sa_family_t family, int type, int protocol,
+ struct sockaddr* sa, unsigned short port)
+{
+ union sockunion su[1] ;
+ int sock_fd ;
+ int ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Is there an address and is it for this family ? */
+ if ((sa != NULL) || (sa->sa_family == family))
+ /* Set up sockunion containing required family and address */
+ sockunion_new_sockaddr(su, sa) ;
+ else
+ {
+ /* no address or wrong family -- set up empty sockunion of
+ * required family */
+ sockunion_init_new(su, family) ;
+ sa = NULL ;
+ } ;
+
+ /* Open the socket and set its properties */
+ sock_fd = sockunion_socket(su, type, protocol) ;
+ if (sock_fd < 0)
+ return -1 ;
+
+ ret = setsockopt_reuseaddr (sock_fd);
+
+ if (ret >= 0)
+ ret = setsockopt_reuseport (sock_fd);
+
+ if (ret >= 0)
+ ret = set_nonblocking(sock_fd);
+
+#ifdef HAVE_IPV6
+ /* Want only IPv6 on AF_INET6 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) && (family == AF_INET6))
+ ret = setsockopt_ipv6_v6only(sock_fd) ;
+#endif
+
+ if (ret >= 0)
+ ret = sockunion_bind (sock_fd, su, port, (sa == NULL)) ;
+
+ if (ret >= 0)
+ ret = sockunion_listen (sock_fd, 3);
+
+ if (ret < 0)
+ {
+ close (sock_fd);
+ return -1 ;
+ }
+
+ /* Socket is open -- set VTY_TERMINAL listener going */
+ uty_add_listener(sock_fd, uty_term_accept) ;
+
+ /* Return OK and signal whether used address or not */
+ return (sa != NULL) ? 1 : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_TERMINAL
+ */
+static void
+uty_term_accept(int sock_listen)
+{
+ int sock_fd;
+ union sockunion su;
+ int ret;
+ unsigned int on;
+ struct prefix *p ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* We can handle IPv4 or IPv6 socket. */
+ sockunion_init_new(&su, AF_UNSPEC) ;
+
+ sock_fd = sockunion_accept (sock_listen, &su);
+
+ if (sock_fd < 0)
+ {
+ if (sock_fd == -1)
+ zlog (NULL, LOG_WARNING, "can't accept vty socket : %s",
+ errtoa(errno, 0).str) ;
+ return ;
+ }
+
+ /* Really MUST have non-blocking */
+ ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */
+ if (ret < 0)
+ {
+ close(sock_fd) ;
+ return ;
+ } ;
+
+ /* New socket is open... worry about access lists */
+ p = sockunion2hostprefix (&su);
+ ret = 0 ; /* so far, so good */
+
+ if ((p->family == AF_INET) && host.vty_accesslist_name)
+ {
+ /* VTY's accesslist apply. */
+ struct access_list* acl ;
+
+ if ((acl = access_list_lookup (AFI_IP, host.vty_accesslist_name))
+ && (access_list_apply (acl, p) == FILTER_DENY))
+ ret = -1 ;
+ }
+
+#ifdef HAVE_IPV6
+ if ((p->family == AF_INET6) && host.vty_ipv6_accesslist_name)
+ {
+ /* VTY's ipv6 accesslist apply. */
+ struct access_list* acl ;
+
+ if ((acl = access_list_lookup (AFI_IP6, host.vty_ipv6_accesslist_name))
+ && (access_list_apply (acl, p) == FILTER_DENY))
+ ret = -1 ;
+ }
+#endif /* HAVE_IPV6 */
+
+ prefix_free (p);
+
+ if (ret != 0)
+ {
+ zlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ;
+ close (sock_fd);
+ return ;
+ } ;
+
+ /* Final options (optional) */
+ on = 1 ;
+ ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY,
+ (void*)&on, sizeof (on));
+ if (ret < 0)
+ zlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s",
+ sock_fd, errtoa(errno, 0).str) ;
+
+ /* All set -- create the VTY_TERMINAL and set it going */
+ uty_term_open(sock_fd, &su);
+
+ /* Log new VTY */
+ zlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str,
+ sock_fd) ;
+
+ return ;
+} ;
+
+/*==============================================================================
+ * VTY telnet stuff
+ *
+ * Note that all telnet commands (escapes) and any debug stuff is treated as
+ * CLI output.
+ */
+
+#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */
+
+static const char* telnet_commands[256] =
+{
+ [tn_IAC ] = "IAC",
+ [tn_DONT ] = "DONT",
+ [tn_DO ] = "DO",
+ [tn_WONT ] = "WONT",
+ [tn_WILL ] = "WILL",
+ [tn_SB ] = "SB",
+ [tn_GA ] = "GA",
+ [tn_EL ] = "EL",
+ [tn_EC ] = "EC",
+ [tn_AYT ] = "AYT",
+ [tn_AO ] = "AO",
+ [tn_IP ] = "IP",
+ [tn_BREAK] = "BREAK",
+ [tn_DM ] = "DM",
+ [tn_NOP ] = "NOP",
+ [tn_SE ] = "SE",
+ [tn_EOR ] = "EOR",
+ [tn_ABORT] = "ABORT",
+ [tn_SUSP ] = "SUSP",
+ [tn_EOF ] = "EOF",
+} ;
+
+static const char* telnet_options[256] =
+{
+ [to_BINARY] = "BINARY", /* 8-bit data path */
+ [to_ECHO] = "ECHO", /* echo */
+ [to_RCP] = "RCP", /* prepare to reconnect */
+ [to_SGA] = "SGA", /* suppress go ahead */
+ [to_NAMS] = "NAMS", /* approximate message size */
+ [to_STATUS] = "STATUS", /* give status */
+ [to_TM] = "TM", /* timing mark */
+ [to_RCTE] = "RCTE", /* remote controlled tx and echo */
+ [to_NAOL] = "NAOL", /* neg. about output line width */
+ [to_NAOP] = "NAOP", /* neg. about output page size */
+ [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */
+ [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */
+ [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */
+ [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */
+ [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */
+ [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */
+ [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */
+ [to_XASCII] = "XASCII", /* extended ascii character set */
+ [to_LOGOUT] = "LOGOUT", /* force logout */
+ [to_BM] = "BM", /* byte macro */
+ [to_DET] = "DET", /* data entry terminal */
+ [to_SUPDUP] = "SUPDUP", /* supdup protocol */
+ [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */
+ [to_SNDLOC] = "SNDLOC", /* send location */
+ [to_TTYPE] = "TTYPE", /* terminal type */
+ [to_EOR] = "EOR", /* end or record */
+ [to_TUID] = "TUID", /* TACACS user identification */
+ [to_OUTMRK] = "OUTMRK", /* output marking */
+ [to_TTYLOC] = "TTYLOC", /* terminal location number */
+ [to_3270REGIME] = "3270REGIME", /* 3270 regime */
+ [to_X3PAD] = "X3PAD", /* X.3 PAD */
+ [to_NAWS] = "NAWS", /* window size */
+ [to_TSPEED] = "TSPEED", /* terminal speed */
+ [to_LFLOW] = "LFLOW", /* remote flow control */
+ [to_LINEMODE] = "LINEMODE", /* Linemode option */
+ [to_XDISPLOC] = "XDISPLOC", /* X Display Location */
+ [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */
+ [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */
+ [to_ENCRYPT] = "ENCRYPT", /* Encryption option */
+ [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */
+ [to_EXOPL] = "EXOPL", /* extended-options-list */
+} ;
+
+/*------------------------------------------------------------------------------
+ * For debug. Put string or value as decimal.
+ */
+static void
+uty_cli_out_dec(vty_cli cli, const char* str, unsigned char u)
+{
+ if (str != NULL)
+ uty_cli_out(cli, "%s ", str) ;
+ else
+ uty_cli_out(cli, "%d ", (int)u) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * For debug. Put string or value as hex.
+ */
+static void
+uty_cli_out_hex(vty_cli cli, const char* str, unsigned char u)
+{
+ if (str != NULL)
+ uty_cli_out(cli, "%s ", str) ;
+ else
+ uty_cli_out(cli, "0x%02x ", (unsigned)u) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Send telnet: "WILL TELOPT_ECHO"
+ */
+static void
+uty_term_will_echo (vty_cli cli)
+{
+ unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO };
+ VTY_ASSERT_LOCKED() ;
+ uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd));
+}
+
+/*------------------------------------------------------------------------------
+ * Send telnet: "suppress Go-Ahead"
+ */
+static void
+uty_term_will_suppress_go_ahead (vty_cli cli)
+{
+ unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA };
+ VTY_ASSERT_LOCKED() ;
+ uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd));
+}
+
+/*------------------------------------------------------------------------------
+ * Send telnet: "don't use linemode"
+ */
+static void
+uty_term_dont_linemode (vty_cli cli)
+{
+ unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE };
+ VTY_ASSERT_LOCKED() ;
+ uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd));
+}
+
+/*------------------------------------------------------------------------------
+ * Send telnet: "Use window size"
+ */
+static void
+uty_term_do_window_size (vty_cli cli)
+{
+ unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS };
+ VTY_ASSERT_LOCKED() ;
+ uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd));
+}
+
+/*------------------------------------------------------------------------------
+ * Send telnet: "don't use lflow" -- not currently used
+ */
+static void
+uty_term_dont_lflow_ahead (vty_cli cli)
+{
+ unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW };
+ VTY_ASSERT_LOCKED() ;
+ uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd));
+}
+
+/*------------------------------------------------------------------------------
+ * 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.
+ */
+extern bool
+uty_telnet_command(vio_vf vf, keystroke stroke, bool callback)
+{
+ uint8_t* p ;
+ uint8_t o ;
+ int left ;
+ bool dealt_with ;
+
+ vty_cli cli = vf->cli ;
+
+ /* Echo to the other end if required */
+ if (TELNET_OPTION_DEBUG)
+ {
+ uty_cli_wipe(cli, 0) ;
+
+ p = stroke->buf ;
+ left = stroke->len ;
+
+ uty_cli_out_hex(cli, telnet_commands[tn_IAC], tn_IAC) ;
+
+ if (left-- > 0)
+ uty_cli_out_dec(cli, telnet_commands[*p], *p) ;
+ ++p ;
+
+ if (left-- > 0)
+ uty_cli_out_dec(cli, telnet_options[*p], *p) ;
+ ++p ;
+
+ if (left > 0)
+ {
+ while(left-- > 0)
+ uty_cli_out_hex(cli, NULL, *p++) ;
+
+ if (stroke->flags & kf_truncated)
+ uty_cli_out(cli, "... ") ;
+
+ if (!(stroke->flags & kf_broken))
+ {
+ uty_cli_out_hex(cli, telnet_commands[tn_IAC], tn_IAC) ;
+ uty_cli_out_hex(cli, telnet_commands[tn_SE], tn_SE) ;
+ }
+ } ;
+
+ if (stroke->flags & kf_broken)
+ uty_cli_out (cli, "BROKEN") ;
+
+ uty_cli_out_newline(cli) ;
+ } ;
+
+ /* Process the telnet command */
+ dealt_with = false ;
+
+ if (stroke->flags != 0)
+ return dealt_with ; /* go no further if broken */
+
+ p = stroke->buf ;
+ left = stroke->len ;
+
+ qassert(left >= 1) ; /* must be if not broken ! */
+ qassert(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:
+ qassert(left > 0) ; /* or parser failed */
+
+ o = *p++ ; /* the option byte */
+ --left ;
+ switch(o)
+ {
+ case to_NAWS:
+ if (left != 4)
+ {
+ zlog(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
+ {
+ int width, height ;
+
+ width = *p++ << 8 ; width += *p++ ;
+ height = *p++ << 8 ; height += *p ;
+
+ if (TELNET_OPTION_DEBUG)
+ {
+ uty_cli_out(cli, "TELNET NAWS window size received: "
+ "width %d, height %d", width, height) ;
+ uty_cli_out_newline(cli) ;
+ } ;
+
+ uty_cli_set_window(cli, width, height) ;
+
+ dealt_with = true ;
+ } ;
+ break ;
+
+ default: /* no other IAC SB <option> */
+ break ;
+ } ;
+ break ;
+
+ default: /* no other IAC X */
+ break ;
+ } ;
+
+ return dealt_with ;
+} ;
+
diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h
new file mode 100644
index 00000000..78d37b18
--- /dev/null
+++ b/lib/vty_io_term.h
@@ -0,0 +1,79 @@
+/* VTY IO TERM -- Telnet Terminal I/O -- header
+ * Virtual terminal [aka TeletYpe] interface routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_IO_TERM_H
+#define _ZEBRA_VTY_IO_TERM_H
+
+#include "misc.h"
+#include <errno.h>
+
+#include "vty.h"
+#include "vty_io_basic.h"
+#include "vty_io.h"
+#include "vty_cli.h"
+
+#include "vio_fifo.h"
+#include "vio_lines.h"
+#include "keystroke.h"
+#include "thread.h"
+#include "command_local.h"
+#include "qstring.h"
+#include "qtimers.h"
+
+/*==============================================================================
+ * Here are structures and other definitions which are shared by:
+ *
+ * vty_io.c -- the main VTY I/O stuff
+ *
+ * for I/O to Telnet Terminal.
+ */
+
+/*==============================================================================
+ * Functions
+ */
+
+extern void uty_term_new(vty_io vio, int sock_fd) ;
+
+extern cmd_return_code_t uty_term_fetch_command_line(vio_vf vf,
+ cmd_action action, cmd_context context) ;
+extern cmd_return_code_t uty_term_out_push(vio_vf vf, bool final) ;
+extern uint uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth) ;
+extern cmd_return_code_t uty_term_read_close(vio_vf vf, bool final) ;
+extern void uty_term_close_reason(vio_vf vf, const char* reason) ;
+extern cmd_return_code_t uty_term_write_close(vio_vf vf, bool final);
+
+extern int uty_term_read(vio_vf vf) ;
+extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ;
+
+extern qtimer_action vty_term_pause_timeout ;
+
+extern void uty_term_mon_write(vio_vf vf) ;
+
+extern bool uty_telnet_command(vio_vf, keystroke stroke, bool callback) ;
+
+
+extern void uty_term_open_listeners(const char *addr, unsigned short port) ;
+
+
+#endif /* _ZEBRA_VTY_IO_TERM_H */
diff --git a/lib/vty_io_vsh.c b/lib/vty_io_vsh.c
new file mode 100644
index 00000000..0b3cb5c4
--- /dev/null
+++ b/lib/vty_io_vsh.c
@@ -0,0 +1,369 @@
+/* VTY IO SHELL -- VTY Shell I/O
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "zebra.h"
+
+#include "vty.h"
+#include "vty_io.h"
+#include "vty_cli.h"
+#include "qstring.h"
+#include "keystroke.h"
+
+#include "memory.h"
+
+#include "prefix.h"
+#include "filter.h"
+#include "privs.h"
+#include "sockunion.h"
+#include "network.h"
+
+#include <arpa/telnet.h>
+#include <sys/un.h> /* for VTYSH */
+#include <sys/socket.h>
+
+#define VTYSH_DEBUG 0
+
+#if 0
+
+/*------------------------------------------------------------------------------
+ * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session.
+ *
+ * Returns: new vty
+ */
+static struct vty *
+uty_new_shell_serv(int sock_fd)
+{
+ struct vty *vty ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Allocate new vty structure and set up default values. */
+ vty = uty_new (VTY_SHELL_SERV, sock_fd) ;
+ vio = vty->vio ;
+
+ /* Set the action functions */
+ if (vty_nexus)
+ {
+ vio->sock.action.read.qnexus = vtysh_read_qnexus ;
+ vio->sock.action.write.qnexus = vty_write_qnexus ;
+ vio->sock.action.timer.qnexus = NULL ;
+ }
+ else
+ {
+ vio->sock.action.read.thread = vtysh_read_thread ;
+ vio->sock.action.write.thread = vty_write_thread ;
+ vio->sock.action.timer.thread = NULL ;
+ } ;
+
+ vty->node = VIEW_NODE;
+
+ /* Kick start the CLI etc. */
+ uty_sock_set_readiness(&vio->sock, write_ready) ;
+
+ return vty;
+} ;
+
+
+/*------------------------------------------------------------------------------
+ * Open a VTY_SHEL_SERV listener socket (UNIX domain).
+ *
+ * Returns: < 0 => failed
+ * >= 0 => OK
+ */
+static int
+uty_serv_vtysh(const char *path)
+{
+ int ret;
+ int sock, sa_len, path_len ;
+ struct sockaddr_un sa_un ;
+ mode_t old_mask;
+ struct zprivs_ids_t ids;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* worry about the path length */
+ path_len = strlen(path) + 1 ;
+ if (path_len >= (int)sizeof(sa_un.sun_path))
+ {
+ zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
+ return -1 ;
+ } ;
+
+ /* First of all, unlink existing socket */
+ unlink (path);
+
+ /* Make UNIX domain socket. */
+ sock = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
+ errtoa(errno, 0).str) ;
+ return -1 ;
+ }
+
+ /* Bind to the required path */
+ memset (&sa_un, 0, sizeof(sa_un));
+ sa_un.sun_family = AF_UNIX;
+ strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1);
+
+ sa_len = SUN_LEN(&sa_un) ;
+
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ sa_un.sun_len = sa_len ;
+#endif
+
+ old_mask = umask (0007);
+
+ ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
+ if (ret < 0)
+ zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
+
+ if (ret >= 0)
+ ret = set_nonblocking(sock);
+
+ if (ret >= 0)
+ {
+ ret = listen (sock, 5);
+ if (ret < 0)
+ zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
+ errtoa(errno, 0).str) ;
+ } ;
+
+ zprivs_get_ids(&ids);
+
+ if (ids.gid_vty > 0)
+ {
+ /* set group of socket */
+ if ( chown (path, -1, ids.gid_vty) )
+ zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
+ errtoa(errno, 0).str) ;
+ }
+
+ umask (old_mask);
+
+ /* Give up now if failed along the way */
+ if (ret < 0)
+ {
+ close (sock) ;
+ return -1 ;
+ } ;
+
+ /* Socket is open -- set VTY Term listener going */
+ uty_serv_start_listener(sock, VTY_SHELL_SERV) ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_SHELL_SERV
+ */
+static int
+uty_accept_shell_serv (vty_listener listener)
+{
+ int sock_fd ;
+ int ret ;
+ int client_len ;
+ struct sockaddr_un client ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ client_len = sizeof(client);
+ memset (&client, 0, client_len);
+
+ sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client,
+ (socklen_t *) &client_len) ;
+
+ if (sock_fd < 0)
+ {
+ zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
+ errtoa(errno, 0).str) ;
+ return -1;
+ }
+
+ /* Really MUST have non-blocking */
+ ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */
+ if (ret < 0)
+ {
+ close(sock_fd) ;
+ return -1 ;
+ } ;
+
+ /* All set -- create the VTY_SHELL_SERV */
+ if (VTYSH_DEBUG)
+ printf ("VTY shell accept\n");
+
+ uty_new_shell_serv(sock_fd) ;
+
+ /* Log new VTY */
+ zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
+ return 0;
+}
+
+/*==============================================================================
+ * Reading from the VTY_SHELL_SERV type sock.
+ *
+ * The select/pselect call-back ends up in utysh_read_ready().
+ */
+
+/*------------------------------------------------------------------------------
+ * Ready to read -> kicking the "SHELL_SERV CLI"
+ *
+ * End up here when there is something ready to be read.
+ *
+ * Will also end up here if an error has occurred, the other end has closed,
+ * this end has half closed, etc. This fact is used to kick the CLI even when
+ * there is no data to be read.
+ *
+ * Note that nothing is actually read here -- reading is done in the CLI itself,
+ * if required.
+ *
+ * The CLI decides whether to re-enable read, or enable write, or both.
+ */
+static void
+utysh_read_ready(vty_io vio)
+{
+ uty_sock_set_read(&vio->sock, off) ;
+
+ /* TODO: need minimal "CLI" for VTY_SHELL_SERV
+ * NB: when output from command is flushed out, must append the
+ * following four bytes: '\0' '\0' '\0' <ret>
+ * Where <ret> is the command return code.
+ */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static void
+vtysh_read_qnexus(qps_file qf, void* file_info)
+{
+ vty_io vio = file_info;
+
+ VTY_LOCK() ;
+
+ assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ;
+
+ utysh_read_ready(vio) ;
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static int
+vtysh_read_thread(struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ;
+
+ vio->sock.t_read = NULL ; /* implicitly */
+ utysh_read_ready(vio);
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Read a lump of bytes and shovel into the command line buffer
+ *
+ * Lines coming in are terminated by '\0'.
+ *
+ * Assumes that the incoming command line is empty or otherwise incomplete.
+ *
+ * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping
+ * when get '\0' or empties the "buf".
+ *
+ * When empties "buf", reads a lump from the sock.
+ *
+ * Returns: 0 => command line is incomplete
+ * 1 => have a complete command line
+ * -1 => EOF (or not open, or failed)
+ */
+extern int
+utysh_read (vty_io vio, qstring cl, qstring buf)
+{
+ int get ;
+ char* cp ;
+ char* ep ;
+ size_t have ;
+
+ while (1)
+ {
+ /* process what there is in the buffer */
+ if (buf->len > buf->cp)
+ {
+ cp = qs_cp_char(buf) ;
+ ep = qs_ep_char(buf) ;
+ have = ep - cp ;
+
+ ep = memchr(cp, '\0', have) ;
+ if (ep != NULL)
+ have = ep - cp ; /* have upto, but excluding '\0' */
+
+ if (have > 0) /* take what have */
+ {
+ qs_insert(cl, cp, have) ;
+ cl->cp += have ;
+ buf->cp += have ;
+ } ;
+
+ if (ep != NULL) /* if found '\0' */
+ {
+ qs_term(cl) ; /* '\0' terminate */
+ ++buf->cp ; /* step past it */
+ return 1 ; /* have a complete line <<<<<<<<<<<<< */
+ }
+ } ;
+
+ /* buffer is empty -- try and get some more stuff */
+ assert(buf->len == buf->cp) ;
+
+ if (!vio->sock.read_open)
+ return -1 ; /* at EOF if not open <<<<<<<<<<<<< */
+
+ qs_need(buf, 500) ; /* need a reasonable lump */
+ qs_clear(buf) ; /* set cp = len = 0 */
+
+ get = read_nb(vio->sock.fd, buf->body, buf->size) ;
+ if (get > 0)
+ buf->len = get ;
+ else if (get == 0)
+ return 0 ; /* have an incomplete line <<<<<<<<<<<< */
+ else
+ {
+ if (get == -1)
+ uty_sock_error(vio, "read") ;
+
+ vio->sock.read_open = 0 ;
+
+ return -1 ; /* at EOF or failed <<<<<<<<<<<<< */
+ } ;
+ } ;
+} ;
+
+#endif
diff --git a/lib/vty_io_vsh.h b/lib/vty_io_vsh.h
new file mode 100644
index 00000000..87cd92d0
--- /dev/null
+++ b/lib/vty_io_vsh.h
@@ -0,0 +1,55 @@
+/* VTY IO SHELL -- VTY Shell I/O -- header
+ * Virtual terminal [aka TeletYpe] interface routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_IO_SHELL_H
+#define _ZEBRA_VTY_IO_SHELL_H
+
+#include "misc.h"
+#include <errno.h>
+
+#include "uty.h"
+#include "vty.h"
+#include "vty_io.h"
+#include "vio_fifo.h"
+#include "vio_lines.h"
+#include "keystroke.h"
+#include "thread.h"
+#include "command.h"
+#include "qstring.h"
+
+/*==============================================================================
+ * Here are structures and other definitions which are shared by:
+ *
+ * vty_io.c -- the main VTY I/O stuff
+ *
+ * for I/O for VTY Shell Server.
+ */
+
+/*==============================================================================
+ * Functions
+ */
+
+extern int uty_vprintf_shell(vty_io vio, const char *format, va_list args) ;
+
+#endif /* _ZEBRA_VTY_IO_SHELL_H */
diff --git a/lib/vty_local.h b/lib/vty_local.h
new file mode 100644
index 00000000..ed2c5ce4
--- /dev/null
+++ b/lib/vty_local.h
@@ -0,0 +1,231 @@
+/* VTY top level
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_LOCAL_H
+#define _ZEBRA_VTY_LOCAL_H
+
+#include "misc.h"
+#include "vty_common.h" /* First and foremost */
+#include "command_common.h"
+
+#include "vargs.h"
+
+#include "qpthreads.h"
+#include "qpnexus.h"
+#include "thread.h"
+#include "qpath.h"
+
+/*==============================================================================
+ * This is for access to some things in vty.c which are not required
+ * by external users, who use vty.h.
+ *
+ * This is for use within the command/vty family.
+ *
+ * Should not be used with vty.h ! (Except in vty.c itself.)
+ *
+ * This may duplicate things published in vty.h, but also includes things
+ * which are not intended for "external" use.
+ */
+
+/*==============================================================================
+ * The VTYSH uses a unix socket to talk to the daemon.
+ *
+ * The ability to respond to a connection from VTYSH appears to be a *compile*
+ * time option. In the interests of keeping the code up to date, the VTYSH
+ * option is turned into a testable constant.
+ */
+#ifdef VTYSH
+ enum { VTYSH_ENABLED = true } ;
+#else
+ enum { VTYSH_ENABLED = false } ;
+#endif
+
+/*==============================================================================
+ * Variables in vty.c -- used in any of the family
+ */
+extern struct vty_io* vio_live_list ;
+extern struct vty_io* vio_monitor_list ;
+
+extern struct vio_child* vio_childer_list ;
+
+extern struct thread_master* vty_master ;
+
+extern bool vty_nexus ;
+extern bool vty_multi_nexus ;
+
+extern qpn_nexus vty_cli_nexus ;
+extern qpn_nexus vty_cmd_nexus ;
+
+extern qpt_mutex_t vty_child_signal_mutex ;
+extern qpn_nexus vty_child_signal_nexus ;
+
+/*==============================================================================
+ * To make vty qpthread safe we use a single mutex.
+ *
+ * A recursive mutex is used, which 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.
+ *
+ * vty also interacts with logging functions. Note that where it needs to
+ * LOG_LOCK() and VTY_LOCK() it will acquire the VTY_LOCK() *first*.
+ */
+
+extern qpt_mutex_t vty_mutex ;
+
+/*------------------------------------------------------------------------------
+ * Sort out VTY_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set VTY_DEBUG == 0 to turn off debug
+ * * or set VTY_DEBUG != 0 to turn on debug
+ * * or set VTY_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef VTY_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(VTY_DEBUG)
+# undef VTY_DEBUG
+# define VTY_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define VTY_DEBUG QDEBUG
+#endif
+
+#ifdef VTY_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG)
+# undef VTY_DEBUG
+# define VTY_DEBUG 0
+# endif
+#endif
+
+enum { vty_debug = VTY_DEBUG } ;
+
+/*------------------------------------------------------------------------------
+ * Locking and related functions.
+ */
+extern int vty_lock_count ;
+
+Inline void
+VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */
+{
+ qpt_mutex_lock(vty_mutex) ;
+ if (vty_debug)
+ ++vty_lock_count ;
+} ;
+
+Inline void
+VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */
+{
+ if (vty_debug)
+ --vty_lock_count ;
+ qpt_mutex_unlock(vty_mutex) ;
+} ;
+
+Inline bool /* true => is (effectively) cli thread */
+vty_is_cli_thread(void)
+{
+ return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ;
+} ;
+
+Inline bool /* true => running with more than one pthread */
+vty_is_multi_pthreaded(void)
+{
+ return !qpthreads_enabled && (vty_cli_nexus != vty_cmd_nexus) ;
+} ;
+
+/* 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.
+ *
+ * Note that both these checks will pass if !qpthreads_enabled. So can have
+ * code which is called before qpthreads are started up, or which will never
+ * run qpthreaded !
+ */
+
+extern int vty_assert_fail ;
+
+Inline void
+VTY_ASSERT_FAILED(void)
+{
+ if (vty_assert_fail == 0) ;
+ {
+ vty_assert_fail = 1 ;
+ assert(0) ;
+ } ;
+} ;
+
+Inline void
+VTY_ASSERT_LOCKED(void)
+{
+ if (vty_debug)
+ if ((vty_lock_count == 0) && (qpthreads_enabled))
+ VTY_ASSERT_FAILED() ;
+} ;
+
+Inline void
+VTY_ASSERT_CLI_THREAD(void)
+{
+ if (vty_debug)
+ if (!vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
+} ;
+
+Inline void
+VTY_ASSERT_CLI_THREAD_LOCKED(void)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_LOCKED() ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Functions in vty.c -- used in any of the vty and command family
+ */
+extern void vty_hello (vty vty);
+
+extern int vty_out (vty vty, const char* format, ...) PRINTF_ATTRIBUTE(2, 3);
+extern int vty_write(struct vty *vty, const void* buf, int n) ;
+extern int vty_out_indent(vty vty, int indent) ;
+extern void vty_out_clear(vty vty) ;
+extern cmd_return_code_t vty_cat_file(vty vty, qpath path, const char* desc) ;
+
+extern void vty_time_print (struct vty *, int) ;
+
+extern void vty_set_lines(vty vty, int lines) ;
+
+extern void vty_config_write_open(vty vty, int fd) ;
+extern cmd_return_code_t vty_config_write_close(struct vty* vty) ;
+
+#endif /* _ZEBRA_VTY_LOCAL_H */
diff --git a/lib/vty_log.c b/lib/vty_log.c
new file mode 100644
index 00000000..1f485e89
--- /dev/null
+++ b/lib/vty_log.c
@@ -0,0 +1,323 @@
+/* VTY interface to logging
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "misc.h"
+
+#include "vty_log.h"
+#include "vty_local.h"
+#include "vty_io_term.h"
+#include "log_local.h"
+#include "list_util.h"
+#include "vio_fifo.h"
+#include "mqueue.h"
+
+/*==============================================================================
+ * This supports the "vty monitor" facility -- which reflects logging
+ * information to one or more VTY_TERMINAL vty.
+ *
+ * NB: this *only* only to the base_vout of a VTY_TERMINAL.
+ *
+ * There are a number of issues:
+ *
+ * a) output of logging information should not be held up any longer than
+ * is absolutely necessary.
+ *
+ * b) console may be busy doing other things, so logging information needs
+ * to be buffered.
+ *
+ * c) zlog() et al, hold the LOG_LOCK(), which is at a lower level than the
+ * VTY_LOCK().
+ *
+ * MUST NOT require the VTY_LOCK() in order to complete a zlog() operation,
+ * hence the buffering and other mechanisms.
+ *
+ * d) may have one or more monitor vty, possibly at different levels of
+ * message.
+ *
+ * e) must avoid logging I/O error log messages for given vty on that vty !
+ *
+ * The I/O error handling turns off log monitoring for the vty if the
+ * vin_base or the vout_base is the locus of the error.
+ */
+static vio_fifo monitor_buffer = NULL ;
+static uint monitor_count = 0 ;
+
+static bool mon_kicked = false ;
+static mqueue_block mon_mqb = NULL ;
+
+static void vty_mon_action(mqueue_block mqb, mqb_flag_t flag) ;
+static void uty_monitor_update(void) ;
+
+/*------------------------------------------------------------------------------
+ * Initialise the vty monitor facility.
+ *
+ */
+extern void
+uty_init_monitor(void)
+{
+ LOG_LOCK() ;
+
+ vio_monitor_list = NULL ;
+ monitor_buffer = NULL ;
+
+ mon_kicked = false ;
+ mon_mqb = mqb_init_new(NULL, vty_mon_action, &mon_mqb) ;
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set/Clear "monitor" state:
+ *
+ * set: if VTY_TERM and not already "monitor" (and write_open !)
+ * clear: if is "monitor"
+ */
+extern void
+uty_set_monitor(vty_io vio, bool on)
+{
+ int level ;
+ int delta ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ level = 0 ;
+ delta = 0 ;
+
+ LOG_LOCK() ;
+
+ if (on && !vio->monitor)
+ {
+ if (vio->vty->type == VTY_TERMINAL)
+ {
+ vio->monitor = true ;
+
+ vio->maxlvl = uzlog_get_monitor_lvl(NULL) ;
+ vio->mon_kick = false ;
+
+ if (vio->mbuf == NULL)
+ vio->mbuf = vio_fifo_new(8 * 1024) ;
+
+ sdl_push(vio_monitor_list, vio, mon_list) ;
+
+ delta = +1 ;
+ } ;
+ }
+ else if (!on && vio->monitor)
+ {
+ vio->monitor = false ;
+
+ vio->maxlvl = ZLOG_DISABLED ;
+ vio->mon_kick = false ;
+
+ sdl_del(vio_monitor_list, vio, mon_list) ;
+ delta = -1 ;
+ }
+
+ monitor_count += delta ;
+
+ if (delta != 0)
+ uty_monitor_update() ; /* tell logging if number of monitors changes */
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the current VTY is a log monitor, set a new level
+ */
+extern void
+vty_set_monitor_level(vty vty, int level)
+{
+ VTY_LOCK() ;
+
+ if (vty->vio->monitor)
+ {
+ LOG_LOCK() ;
+
+ vty->vio->maxlvl = level ;
+ uty_monitor_update() ;
+
+ LOG_UNLOCK() ;
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Establish the maximum level of all monitors and tell the logging levels.
+ *
+ * This is used when a monitor is enabled or disabled, and when a VTY's monitor
+ * level is changed.
+ */
+static void
+uty_monitor_update(void)
+{
+ int level ;
+ uint count ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+ LOG_ASSERT_LOCKED() ;
+
+ vio = vio_monitor_list ;
+ count = 0 ;
+ level = ZLOG_DISABLED ;
+ while (vio != NULL)
+ {
+ ++count ;
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ if (level <= vio->maxlvl)
+ level = vio->maxlvl ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+
+ qassert(monitor_count == count) ;
+
+ uzlog_set_monitor(NULL, level) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Put logging message to all suitable monitors.
+ *
+ * All we can do here is to shovel stuff into buffers and then kick the VTY
+ * to do something. If running multi-nexus, then the kick takes the form of
+ * a message sent to the cli nexus, otherwise can call the message action
+ * function here and now.
+ *
+ * NB: expect incoming line to NOT include '\n' or any other line ending.
+ */
+extern void
+vty_log(int priority, const char* line, uint len)
+{
+ vty_io vio ;
+ bool kick ;
+
+ LOG_ASSERT_LOCKED() ;
+
+ vio = vio_monitor_list ;
+ kick = false ;
+ while (vio != NULL)
+ {
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ if (priority <= vio->maxlvl)
+ {
+ vio_fifo_put_bytes(vio->mbuf, line, len) ;
+ vio_fifo_put_bytes(vio->mbuf, "\r\n", 2) ;
+
+ vio->mon_kick = kick = true ;
+ }
+ else
+ vio->mon_kick = false ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+
+ if (kick)
+ {
+ if (vty_multi_nexus)
+ {
+ if (!mon_kicked)
+ {
+ mon_kicked = true ;
+ mqueue_enqueue(vty_cli_nexus->queue, mon_mqb, mqb_ordinary) ;
+ } ;
+ }
+ else
+ vty_mon_action(NULL, mqb_action) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Put logging message to all suitable monitors.
+ *
+ * All we can do here is to shovel stuff into buffers and then boot the VTY
+ * to do something.
+ *
+ * NB: expect incoming line to NOT include '\n' or any other line ending.
+ */
+static void
+vty_mon_action(mqueue_block mqb, mqb_flag_t flag)
+{
+ VTY_LOCK() ;
+ LOG_LOCK() ; /* IN THIS ORDER !!! */
+
+ mon_kicked = false ; /* If anything else happens, need to kick again */
+
+ if (flag == mqb_action)
+ {
+ vty_io vio ;
+
+ vio = vio_monitor_list ;
+ while (vio != NULL)
+ {
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ if (vio->mon_kick)
+ {
+ vio->mon_kick = false ;
+ uty_term_mon_write(vio->vout_base) ;
+ } ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+ }
+ else
+ mqb_free(mqb) ; /* Suicide */
+
+ LOG_UNLOCK() ;
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Async-signal-safe version of vty_log for fixed strings.
+ *
+ * This is last gasp operation.
+ */
+extern void
+vty_log_fixed (const char *buf, uint len)
+{
+ vty_io vio ;
+
+ /* Write to all known "monitor" vty
+ *
+ * Forget all the niceties -- about to die in any case.
+ */
+ vio = sdl_head(vio_monitor_list) ;
+ while (vio != NULL)
+ {
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ write(vio_vfd_fd(vio->vout_base->vfd), buf, len) ;
+ write(vio_vfd_fd(vio->vout_base->vfd), "\r\n", 2) ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+} ;
+
+
+
+
+
+
diff --git a/lib/vty_log.h b/lib/vty_log.h
new file mode 100644
index 00000000..55fff67f
--- /dev/null
+++ b/lib/vty_log.h
@@ -0,0 +1,42 @@
+/* VTY interface to logging -- header
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_LOG_H
+#define _ZEBRA_VTY_LOG_H
+
+#include "misc.h"
+#include "vty_io.h"
+
+/*==============================================================================
+ */
+
+extern void uty_init_monitor(void) ;
+extern void uty_terminate_monitor(void) ;
+
+extern void uty_set_monitor(vty_io vio, bool on) ;
+extern void vty_set_monitor_level(vty vty, int level) ;
+
+extern void vty_log(int priority, const char* line, uint len) ;
+extern void vty_log_fixed(const char* buf, uint len) ;
+
+#endif /* _ZEBRA_VTY_LOG_H */
diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c
new file mode 100644
index 00000000..b2c3fd64
--- /dev/null
+++ b/lib/vty_pipe.c
@@ -0,0 +1,496 @@
+/* VTY Command Line Pipe Handling
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#include "vty.h"
+#include "vty_cli.h"
+#include "vty_command.h"
+#include "vty_io.h"
+#include "vio_lines.h"
+
+#include "command_execute.h"
+#include "command_queue.h"
+
+#include "memory.h"
+
+/*==============================================================================
+ * VTY Command Line Input Pipe
+ *
+ * Here are the mechanics which support the:
+ *
+ * < file_name
+ *
+ * <| shell_command
+ *
+ * and the:
+ *
+ * > file_name
+ * >> file_name
+ * | shell_command
+ *
+ *==============================================================================
+ * The file_name handling
+ *
+ * Two directories are supported:
+ *
+ * "home" -- being the root for configuration files
+ *
+ * "cd" -- being the root for relative filenames, in the usual way
+ *
+ * "here" -- being the directory for the enclosing "< filename"
+ * see below for more detailed semantics
+ *
+ * There are the global values:
+ *
+ * config home -- defaults to directory for configuration file.
+ * may be set by command.
+ *
+ * config cd -- defaults to cwd at start up
+ *
+ * There are the local values, within a given CLI instance (ie VTY):
+ *
+ * cli home -- set to config home when CLI instance starts, or when
+ * config home is set within the CLI.
+ *
+ * cli cd -- similarly
+ *
+ * cli here -- outside < is same as cli home, unless explicitly set
+ * - set by cli here
+ * - pushed each time executes <, and set to directory for
+ * the < filename.
+ * - pushed each time executes <| and left unchanged
+ * - popped each time exits < or <|
+ * - set to parent by "no cli here" inside <, or to
+ * default state outside <
+ *
+ * And then the filename syntax:
+ *
+ * /path -- absolute path -- in the usual way
+ *
+ * path -- path wrt "cli cd" -- in the usual way
+ *
+ * ~/path -- path in "cli home" -- so "config" stuff
+ *
+ * ~./path -- path in "here" -- so wrt to enclosing < file
+ *
+ *==============================================================================
+ * The Input Pipe Commands
+ *
+ * These are "universal commands".
+ *
+ * They are:
+ *
+ * < filename -- filename as above
+ *
+ * treat contents of given file as command lines.
+ * (That may include further < commands.)
+ *
+ * See notes on cli here for pushing/popping the here
+ * directory.
+ *
+ * TODO: Filename and quotes ??
+ *
+ * <| shell_command -- the shell command is executed "as is" by system().
+ *
+ * the stdout and stderr are collected.
+ *
+ * treats stdout as command lines.
+ * (That may include further < commands.)
+ *
+ * anything from stderr is sent to the VTY output.
+ *
+ * As far as the top level CLI is concerned, these are discrete commands.
+ * That is to say:
+ *
+ * -- except where blocked while reading the "pipe", all commands are
+ * executed one after another, in one Routing Engine operation.
+ *
+ * -- in any event, all output is gathered in the VTY buffering, and will
+ * be sent to the console (or where ever) only when the outermost command
+ * completes.
+ *
+ * There are three options associated with the output from a < operation:
+ *
+ * -- suppress command line echo
+ *
+ * whether to suppress echo of commands to the VTY before they are
+ * dispatched.
+ *
+ * The default is not to suppress command line echo.
+ *
+ * -- suppress command results
+ *
+ * whether to suppress any output generated by each command.
+ *
+ * The default is not to suppress command results.
+ *
+ * -- suppress "more"
+ *
+ * whether to do "--more--", if currently applies, when finally
+ * outputting all the command results.
+ *
+ * The default is not to suppress "more".
+ *
+ * This option can only be set for the outermost < operation.
+ *
+ * Remembering that all output occurs in one go when the outermost < operation
+ * completes.
+ *
+ * The default is to show everything and implement "--more--", pretty much as
+ * if the commands had been typed in. Except that "--more--" applies to
+ * everything (including the command lines) together, rather than to the output
+ * of each command individually.
+ *
+ * These options may be changed by flags attached to the <, as follows:
+ *
+ * ! suppress command line echo
+ *
+ * ? suppress "--more--"
+ *
+ * * suppress command output
+ *
+ * TODO: cli xxx commands for echo and output suppression and notes ....
+ *
+ * TODO: Error handling....
+ *
+ * TODO: Closing the CLI....
+ *
+ * TODO: Time out and the CLI....
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+#if 0
+/*==============================================================================
+ *
+ */
+
+#include "misc.h"
+
+#include "command_local.h"
+
+#include "vty_io_pipe.h"
+#include "vty_io_file.h"
+#include "vty_io_basic.h"
+
+/*==============================================================================
+ * VTY File Output
+ *
+ * This is for input and output of configuration files and piped stuff.
+ *
+ * When reading the configuration (and piped stuff in the configuration) I/O
+ * is blocking... nothing else can run while this is going on. Otherwise,
+ * all I/O is non-blocking.
+ */
+
+
+
+/*==============================================================================
+ * Prototypes.
+ */
+
+/*------------------------------------------------------------------------------
+ *
+ *
+ */
+extern cmd_return_code_t
+uty_file_read_open(vty_io vio, qstring name, bool reflect)
+{
+ const char* name_str ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
+
+ name_str = qs_make_string(name) ;
+
+ iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */
+
+ /* Do the basic file open. */
+ fd = uty_fd_file_open(name_str, iot) ;
+
+ if (fd < 0)
+ {
+ uty_out(vio, "%% Could not open input file %s\n", name_str) ;
+
+ return CMD_WARNING ;
+ }
+
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
+ uty_vin_push(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ;
+
+ vf->parse_type = cmd_parse_strict ;
+ vf->reflect_enabled = reflect ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ *
+ *
+ */
+extern cmd_return_code_t
+uty_file_write_open(vty_io vio, qstring name, bool append)
+{
+ const char* name_str ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
+
+ iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */
+
+ if (append)
+ iot |= vfd_io_append ;
+
+ name_str = qs_make_string(name) ;
+
+ /* Do the basic file open. */
+ fd = uty_fd_file_open(name_str, iot) ;
+
+ if (fd < 0)
+ {
+ uty_out(vio, "%% Could not open output file %s\n", name_str) ;
+
+ return CMD_WARNING ;
+ }
+
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
+ uty_vout_push(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*==============================================================================
+ * Command line fetch from a file or pipe.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- do not have a command line at the moment
+ * CMD_EOF -- ran into EOF
+ * CMD_IO_ERROR -- ran into an I/O error
+ */
+extern cmd_return_code_t
+uty_file_fetch_command_line(vio_vf vf, qstring* line)
+{
+ assert(vf->vin_state == vf_open) ;
+
+ if (vf->line_complete)
+ {
+ vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */
+
+ vf->line_complete = false ;
+ vf->line_number += vf->line_step ;
+
+ qs_set_len_nn(vf->cl, 0) ;
+ vf->line_step = 0 ;
+ } ;
+
+ while (1)
+ {
+ char* s, * p, * q, * e ;
+ size_t have ;
+ ulen len ;
+
+ s = vio_fifo_get(vf->ibuf, &have) ;
+
+ /* If nothing in hand, try and get some more.
+ *
+ * Either exits or loops back to set s & have.
+ */
+ if (have == 0)
+ {
+ int get ;
+
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
+
+ if (get > 0)
+ continue ; /* loop back */
+
+ if (get == 0)
+ return CMD_WAITING ; /* need to set read ready ! */
+
+ if (get == -1)
+ ; /* register error */
+
+ if (get == -2)
+ return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ;
+ } ;
+
+ /* Try to find a '\n' -- converting all other control chars to ' '
+ *
+ * When we find '\n' step back across any trailing ' ' (which includes
+ * any control chars before the '\n').
+ *
+ * This means that we cope with "\r\n" line terminators. But not anything
+ * more exotic.
+ */
+ p = s ;
+ e = s + have ; /* have != 0 */
+ q = NULL ;
+
+ while (p < e)
+ {
+ if (*p++ < 0x20)
+ {
+ if (*(p-1) != '\n')
+ {
+ *(p-1) = ' ' ; /* everything other than '\n' */
+ continue ;
+ } ;
+
+ ++vf->line_step ; /* got a '\n' */
+
+ q = p ; /* point just past '\n' */
+ do --q ; while ((q > s) && (*(q-1) == ' ')) ;
+ /* discard trailing "spaces" */
+ break ;
+ } ;
+ } ;
+
+ /* Step past what have just consumed -- we have a hold_mark, so
+ * stuff is still in the fifo.
+ */
+ vio_fifo_step(vf->ibuf, p - s) ;
+
+ /* If not found '\n', then we have a line fragment that needs to be
+ * appended to any previous line fragments.
+ *
+ * Loops back to try to get some more form the fifo.
+ */
+ if (q == NULL)
+ {
+ qs_append_str_n(vf->cl, s, p - s) ;
+ continue ;
+ } ;
+
+ /* If we have nothing so far, set alias to point at what we have in
+ * the fifo. Otherwise, append to what we have.
+ *
+ * End up with: s = start of entire line, so far.
+ * p = end of entire line so far.
+ */
+ len = q - s ; /* length to add */
+ if (qs_len_nn(vf->cl) == 0)
+ {
+ qs_set_alias_n(vf->cl, s, len) ;
+ p = q ;
+ }
+ else
+ {
+ if (len != 0)
+ qs_append_str_n(vf->cl, s, len) ;
+
+ s = qs_char_nn(vf->cl) ;
+ p = s + qs_len_nn(vf->cl) ;
+
+ if ((len == 0) && (p > s) && (*(p-1) == ' '))
+ {
+ /* Have an empty end of line section, and the last character
+ * of what we have so far is ' ', so need now to trim trailing
+ * spaces off the stored stuff.
+ */
+ do --p ; while ((p > s) && (*(p-1) == ' ')) ;
+
+ qs_set_len_nn(vf->cl, p - s) ;
+ } ;
+ } ;
+
+ /* Now worry about we have a trailing '\'. */
+
+ if ((p == s) || (*(p-1) != '\\'))
+ break ; /* no \ => no continuation => success */
+
+ /* Have a trailing '\'.
+ *
+ * If there are an odd number of '\', strip the last one and loop
+ * round to collect the continuation.
+ *
+ * If there are an even number of '\', then this is not a continuation.
+ *
+ * Note that this rule deals with the case of the continuation line
+ * being empty... e.g. ....\\\ n n -- where n is '\n'
+ */
+ q = p ;
+ do --q ; while ((q > s) && (*(q-1) == '\\')) ;
+
+ if (((p - q) & 1) == 0)
+ break ; /* even => no continuation => success */
+
+ qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */
+
+ continue ; /* loop back to fetch more */
+ } ;
+
+ /* Success have a line in hand */
+
+ vf->line_complete = true ;
+ *line = vf->cl ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command output push to a file or pipe.
+ *
+ * Returns: CMD_SUCCESS -- done
+ * CMD_IO_ERROR -- ran into an I/O error
+ */
+extern cmd_return_code_t
+uty_file_out_push(vio_vf vf)
+{
+ assert(vf->vout_state == vf_open) ;
+
+ if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0)
+ return CMD_SUCCESS ;
+ else
+ return CMD_IO_ERROR ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after input file has been closed
+ */
+extern void
+uty_file_read_close(vio_vf vf)
+{
+ return ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Flush output buffer and close.
+ *
+ * Returns: true <=> buffer (now) empty
+ */
+extern bool
+uty_file_write_close(vio_vf vf, bool final)
+{
+ return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ;
+} ;
+#endif
diff --git a/lib/workqueue.h b/lib/workqueue.h
index 369cd871..46f27734 100644
--- a/lib/workqueue.h
+++ b/lib/workqueue.h
@@ -24,12 +24,10 @@
#ifndef _QUAGGA_WORK_QUEUE_H
#define _QUAGGA_WORK_QUEUE_H
-#ifndef Inline
-#define Inline static inline
-#endif
+#include "misc.h"
/* Hold time for the initial schedule of a queue run, in millisec */
-enum { WORK_QUEUE_DEFAULT_HOLD = 50 } ;
+enum { WORK_QUEUE_DEFAULT_HOLD = 50 } ;
/* action value, for use by item processor and item error handlers */
typedef enum
@@ -163,7 +161,7 @@ extern void work_queue_unplug (struct work_queue *wq);
/* Helpers, exported for thread.c and command.c */
extern int work_queue_run (struct thread *);
-extern struct cmd_element show_work_queues_cmd;
+extern struct cmd_command show_work_queues_cmd;
/*==============================================================================
* The Inline functions
diff --git a/lib/zassert.h b/lib/zassert.h
index 8ca2203f..ee36a98e 100644
--- a/lib/zassert.h
+++ b/lib/zassert.h
@@ -5,6 +5,7 @@
#ifndef _QUAGGA_ASSERT_H
#define _QUAGGA_ASSERT_H
+#include "misc.h"
#include "confirm.h"
extern void _zlog_assert_failed (const char *assertion, const char *file,
@@ -33,7 +34,7 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file,
#define zassert(EX) ((void)((EX) ? 0 : \
(_zlog_assert_failed(#EX, __FILE__, __LINE__, \
- __ASSERT_FUNCTION), 0)))
+ __ASSERT_FUNCTION), 0)))
/* Implicitly *permanent* assert() -- irrespective of NDEBUG */
#undef assert
@@ -44,11 +45,14 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file,
/* NDEBUG time assert() */
#ifndef NDEBUG
-#define dassert(EX) zassert(EX)
+# define dassert(EX) zassert(EX)
#else
-#define dassert(EX)
+# define dassert(EX)
#endif
+/* Assert iff QDEBUG */
+#define qassert(EX) zassert(qdebug && (EX))
+
/* Abort with message */
#define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION)
diff --git a/lib/zconfig.h b/lib/zconfig.h
new file mode 100644
index 00000000..f1bd6f78
--- /dev/null
+++ b/lib/zconfig.h
@@ -0,0 +1,42 @@
+/* Zebra common header.
+ Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro
+
+This file is part of GNU Zebra.
+
+GNU Zebra is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+GNU Zebra is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Zebra; see the file COPYING. If not, write to the Free
+Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+02111-1307, USA. */
+
+#ifndef _ZEBRA_CONFIG_H
+#define _ZEBRA_CONFIG_H
+
+#ifdef HAVE_CONFIG_H
+
+#include "config.h"
+
+#undef _THREAD_SAFE
+#define _THREAD_SAFE 1
+
+#undef _REENTRANT
+#define _REENTRANT 1
+
+#ifdef _FEATURES_H
+#error Features defined
+#endif
+
+#include <features.h>
+
+#endif /* HAVE_CONFIG_H */
+
+#endif /* _ZEBRA_CONFIG_H */
diff --git a/lib/zebra.h b/lib/zebra.h
index 799cfc3d..031fe85c 100644
--- a/lib/zebra.h
+++ b/lib/zebra.h
@@ -21,9 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _ZEBRA_H
#define _ZEBRA_H
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif /* HAVE_CONFIG_H */
+#include "zconfig.h"
#ifdef SUNOS_5
#define _XPG4_2
@@ -37,6 +35,7 @@ typedef unsigned char u_int8_t;
typedef int socklen_t;
#endif /* HAVE_SOCKLEN_T */
+#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -44,7 +43,6 @@ typedef int socklen_t;
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
-#include <string.h>
#include <pwd.h>
#include <grp.h>
#ifdef HAVE_STROPTS_H
diff --git a/ospf6d/ospf6_lsa.c b/ospf6d/ospf6_lsa.c
index 588b9462..9643e04d 100644
--- a/ospf6d/ospf6_lsa.c
+++ b/ospf6d/ospf6_lsa.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>
@@ -573,7 +573,7 @@ ospf6_lsa_unlock (struct ospf6_lsa *lsa)
ospf6_lsa_delete (lsa);
}
-
+
/* ospf6 lsa expiry */
int
ospf6_lsa_expire (struct thread *thread)
@@ -660,7 +660,7 @@ ospf6_lsa_refresh (struct thread *thread)
return 0;
}
-
+
/* enhanced Fletcher checksum algorithm, RFC1008 7.2 */
#define MODX 4102
@@ -717,12 +717,12 @@ ospf6_lsa_terminate (void)
{
vector_free (ospf6_lsa_handler_vector);
}
-
+
static char *
ospf6_lsa_handler_name (struct ospf6_lsa_handler *h)
{
static char buf[64];
- unsigned int i;
+ unsigned int i;
unsigned int size = strlen (h->name);
if (!strcmp(h->name, "Unknown") &&
diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c
index e41492a5..66f1df2c 100644
--- a/ospfd/ospf_spf.c
+++ b/ospfd/ospf_spf.c
@@ -52,7 +52,7 @@ static void ospf_vertex_free (void *);
* dynamically allocated at begin of ospf_spf_calculate
*/
static struct list vertex_list = { .del = ospf_vertex_free };
-
+
/* Heap related functions, for the managment of the candidates, to
* be used with pqueue. */
static int
@@ -90,7 +90,7 @@ update_stat (void *node , int position)
/* Set the status of the vertex, when its position changes. */
*(v->stat) = position;
}
-
+
static struct vertex_nexthop *
vertex_nexthop_new (void)
{
@@ -112,12 +112,12 @@ ospf_canonical_nexthops_free (struct vertex *root)
{
struct listnode *node, *nnode;
struct vertex *child;
-
+
for (ALL_LIST_ELEMENTS (root->children, node, nnode, child))
{
struct listnode *n2, *nn2;
struct vertex_parent *vp;
-
+
/* router vertices through an attached network each
* have a distinct (canonical / not inherited) nexthop
* which must be freed.
@@ -127,14 +127,14 @@ ospf_canonical_nexthops_free (struct vertex *root)
*/
if (child->type == OSPF_VERTEX_NETWORK)
ospf_canonical_nexthops_free (child);
-
+
/* Free child nexthops pointing back to this root vertex */
for (ALL_LIST_ELEMENTS (child->parents, n2, nn2, vp))
if (vp->parent == root && vp->nexthop)
vertex_nexthop_free (vp->nexthop);
}
-}
-
+}
+
/* TODO: Parent list should be excised, in favour of maintaining only
* vertex_nexthop, with refcounts.
*/
@@ -142,12 +142,12 @@ static struct vertex_parent *
vertex_parent_new (struct vertex *v, int backlink, struct vertex_nexthop *hop)
{
struct vertex_parent *new;
-
+
new = XMALLOC (MTYPE_OSPF_VERTEX_PARENT, sizeof (struct vertex_parent));
-
+
if (new == NULL)
return NULL;
-
+
new->parent = v;
new->backlink = backlink;
new->nexthop = hop;
@@ -159,7 +159,7 @@ vertex_parent_free (void *p)
{
XFREE (MTYPE_OSPF_VERTEX_PARENT, p);
}
-
+
static struct vertex *
ospf_vertex_new (struct ospf_lsa *lsa)
{
@@ -175,9 +175,9 @@ ospf_vertex_new (struct ospf_lsa *lsa)
new->children = list_new ();
new->parents = list_new ();
new->parents->del = vertex_parent_free;
-
+
listnode_add (&vertex_list, new);
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Created %s vertex %s", __func__,
new->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
@@ -189,28 +189,28 @@ static void
ospf_vertex_free (void *data)
{
struct vertex *v = data;
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Free %s vertex %s", __func__,
v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
inet_ntoa (v->lsa->id));
-
+
/* There should be no parents potentially holding references to this vertex
* Children however may still be there, but presumably referenced by other
* vertices
*/
//assert (listcount (v->parents) == 0);
-
+
if (v->children)
list_delete (v->children);
v->children = NULL;
-
+
if (v->parents)
list_delete (v->parents);
v->parents = NULL;
-
+
v->lsa = NULL;
-
+
XFREE (MTYPE_OSPF_VERTEX, v);
}
@@ -232,11 +232,11 @@ ospf_vertex_dump(const char *msg, struct vertex *v,
{
struct listnode *node;
struct vertex_parent *vp;
-
+
for (ALL_LIST_ELEMENTS_RO (v->parents, node, vp))
{
char buf1[BUFSIZ];
-
+
if (vp)
{
zlog_debug ("parent %s backlink %d nexthop %s interface %s",
@@ -251,7 +251,7 @@ ospf_vertex_dump(const char *msg, struct vertex *v,
{
struct listnode *cnode;
struct vertex *cv;
-
+
for (ALL_LIST_ELEMENTS_RO (v->children, cnode, cv))
ospf_vertex_dump(" child:", cv, 0, 0);
}
@@ -264,27 +264,27 @@ ospf_vertex_add_parent (struct vertex *v)
{
struct vertex_parent *vp;
struct listnode *node;
-
+
assert (v && v->parents);
-
+
for (ALL_LIST_ELEMENTS_RO (v->parents, node, vp))
{
assert (vp->parent && vp->parent->children);
-
+
/* No need to add two links from the same parent. */
if (listnode_lookup (vp->parent->children, v) == NULL)
listnode_add (vp->parent->children, v);
}
}
-
+
static void
ospf_spf_init (struct ospf_area *area)
{
struct vertex *v;
-
+
/* Create root node. */
v = ospf_vertex_new (area->router_lsa_self);
-
+
area->spf = v;
/* Reset ABR and ASBR router counts. */
@@ -407,7 +407,7 @@ ospf_spf_flush_parents (struct vertex *w)
{
struct vertex_parent *vp;
struct listnode *ln, *nn;
-
+
/* delete the existing nexthops */
for (ALL_LIST_ELEMENTS (w->parents, ln, nn, vp))
{
@@ -416,9 +416,9 @@ ospf_spf_flush_parents (struct vertex *w)
}
}
-/*
+/*
* Consider supplied next-hop for inclusion to the supplied list of
- * equal-cost next-hops, adjust list as neccessary.
+ * equal-cost next-hops, adjust list as neccessary.
*/
static void
ospf_spf_add_parent (struct vertex *v, struct vertex *w,
@@ -426,11 +426,11 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
unsigned int distance)
{
struct vertex_parent *vp;
-
+
/* we must have a newhop, and a distance */
assert (v && w && newhop);
assert (distance);
-
+
/* IFF w has already been assigned a distance, then we shouldn't get here
* unless callers have determined V(l)->W is shortest / equal-shortest
* path (0 is a special case distance (no distance yet assigned)).
@@ -439,7 +439,7 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
assert (distance <= w->distance);
else
w->distance = distance;
-
+
if (IS_DEBUG_OSPF_EVENT)
{
char buf[2][INET_ADDRSTRLEN];
@@ -447,7 +447,7 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
__func__,
inet_ntop(AF_INET, &v->lsa->id, buf[0], sizeof(buf[0])),
inet_ntop(AF_INET, &w->lsa->id, buf[1], sizeof(buf[1])));
- }
+ }
/* Adding parent for a new, better path: flush existing parents from W. */
if (distance < w->distance)
@@ -458,8 +458,8 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
ospf_spf_flush_parents (w);
w->distance = distance;
}
-
- /* new parent is <= existing parents, add it to parent list */
+
+ /* new parent is <= existing parents, add it to parent list */
vp = vertex_parent_new (v, ospf_lsa_has_link (w->lsa, v->lsa), newhop);
listnode_add (w->parents, vp);
@@ -497,11 +497,11 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
}
if (v == area->spf)
- {
+ {
/* 16.1.1 para 4. In the first case, the parent vertex (V) is the
- root (the calculating router itself). This means that the
+ root (the calculating router itself). This means that the
destination is either a directly connected network or directly
- connected router. The outgoing interface in this case is simply
+ connected router. The outgoing interface in this case is simply
the OSPF interface connecting to the destination network/router.
*/
@@ -511,15 +511,15 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
* l2 will be link from w to v
*/
struct router_lsa_link *l2 = NULL;
-
+
/* we *must* be supplied with the link data */
assert (l != NULL);
-
+
if (IS_DEBUG_OSPF_EVENT)
{
char buf1[BUFSIZ];
char buf2[BUFSIZ];
-
+
zlog_debug("ospf_nexthop_calculation(): considering link "
"type %d link_id %s link_data %s",
l->m[0].type,
@@ -539,13 +539,13 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
provides an IP address of the next hop router.
At this point l is a link from V to W, and V is the
- root ("us"). Find the local interface associated
+ root ("us"). Find the local interface associated
with l (its address is in l->link_data). If it
is a point-to-multipoint interface, then look through
the links in the opposite direction (W to V). If
any of them have an address that lands within the
subnet declared by the PtMP link, then that link
- is a constituent of the PtMP link, and its address is
+ is a constituent of the PtMP link, and its address is
a nexthop address for V.
*/
oi = ospf_if_is_configured (area->ospf, &l->link_data);
@@ -605,17 +605,17 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
else if (l->m[0].type == LSA_LINK_TYPE_VIRTUALLINK)
{
struct ospf_vl_data *vl_data;
-
- /* VLink implementation limitations:
+
+ /* VLink implementation limitations:
* a) vl_data can only reference one nexthop, so no ECMP
- * to backbone through VLinks. Though transit-area
+ * to backbone through VLinks. Though transit-area
* summaries may be considered, and those can be ECMP.
* b) We can only use /one/ VLink, even if multiple ones
* exist this router through multiple transit-areas.
*/
vl_data = ospf_vl_lookup (area->ospf, NULL, l->link_id);
-
- if (vl_data
+
+ if (vl_data
&& CHECK_FLAG (vl_data->flags, OSPF_VL_FLAG_APPROVED))
{
nh = vertex_nexthop_new ();
@@ -667,7 +667,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
/* ...For each link in the router-LSA that points back to the
* parent network, the link's Link Data field provides the IP
* address of a next hop router. The outgoing interface to
- * use can then be derived from the next hop IP address (or
+ * use can then be derived from the next hop IP address (or
* it can be inherited from the parent network).
*/
nh = vertex_nexthop_new ();
@@ -679,7 +679,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
}
}
/* NB: This code is non-trivial.
- *
+ *
* E.g. it is not enough to know that V connects to the root. It is
* also important that the while above, looping through all links from
* W->V found at least one link, so that we know there is
@@ -708,7 +708,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
added = 1;
ospf_spf_add_parent (v, w, vp->nexthop, distance);
}
-
+
return added;
}
@@ -735,13 +735,13 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
if (IS_ROUTER_LSA_VIRTUAL ((struct router_lsa *) v->lsa))
area->transit = OSPF_TRANSIT_TRUE;
}
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Next vertex of %s vertex %s",
- __func__,
+ __func__,
v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
inet_ntoa(v->lsa->id));
-
+
p = ((u_char *) v->lsa) + OSPF_LSA_HEADER_SIZE + 4;
lim = ((u_char *) v->lsa) + ntohs (v->lsa->length);
@@ -749,7 +749,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
{
struct vertex *w;
unsigned int distance;
-
+
/* In case of V is Router-LSA. */
if (v->lsa->type == OSPF_ROUTER_LSA)
{
@@ -764,7 +764,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
calculation. */
if ((type = l->m[0].type) == LSA_LINK_TYPE_STUB)
continue;
-
+
/* Infinite distance links shouldn't be followed, except
* for local links (a stub-routed router still wants to
* calculate tree, so must follow its own links).
@@ -886,7 +886,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
/* Get the vertex from candidates. */
w = candidate->array[w_lsa->stat];
- /* if D is greater than. */
+ /* if D is greater than. */
if (w->distance < distance)
{
continue;
@@ -894,7 +894,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
/* equal to. */
else if (w->distance == distance)
{
- /* Found an equal-cost path to W.
+ /* Found an equal-cost path to W.
* Calculate nexthop of to W from V. */
ospf_nexthop_calculation (area, v, w, l, distance);
}
@@ -909,7 +909,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
if (ospf_nexthop_calculation (area, v, w, l, distance))
/* Decrease the key of the node in the heap.
* trickle-sort it up towards root, just in case this
- * node should now be the new root due the cost change.
+ * node should now be the new root due the cost change.
* (next pqueu_{de,en}queue will fully re-heap the queue).
*/
trickle_up (w_lsa->stat, candidate);
@@ -941,7 +941,7 @@ ospf_spf_dump (struct vertex *v, int i)
if (IS_DEBUG_OSPF_EVENT)
for (ALL_LIST_ELEMENTS_RO (v->parents, nnode, parent))
{
- zlog_debug (" nexthop %p %s %s",
+ zlog_debug (" nexthop %p %s %s",
parent->nexthop,
inet_ntoa (parent->nexthop->router),
parent->nexthop->oi ? IF_NAME(parent->nexthop->oi)
@@ -1003,16 +1003,16 @@ ospf_spf_process_stubs (struct ospf_area *area, struct vertex *v,
{
if (CHECK_FLAG (child->flags, OSPF_VERTEX_PROCESSED))
continue;
-
+
/* the first level of routers connected to the root
- * should have 'parent_is_root' set, including those
+ * should have 'parent_is_root' set, including those
* connected via a network vertex.
*/
if (area->spf == v)
parent_is_root = 1;
else if (v->type == OSPF_VERTEX_ROUTER)
parent_is_root = 0;
-
+
ospf_spf_process_stubs (area, child, rt, parent_is_root);
SET_FLAG (child->flags, OSPF_VERTEX_PROCESSED);
@@ -1114,7 +1114,7 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
{
struct pqueue *candidate;
struct vertex *v;
-
+
if (IS_DEBUG_OSPF_EVENT)
{
zlog_debug ("ospf_spf_calculate: Start");
@@ -1135,11 +1135,11 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* RFC2328 16.1. (1). */
/* Initialize the algorithm's data structures. */
-
+
/* This function scans all the LSA database and set the stat field to
* LSA_SPF_NOT_EXPLORED. */
ospf_lsdb_clean_stat (area->lsdb);
- /* Create a new heap for the candidates. */
+ /* Create a new heap for the candidates. */
candidate = pqueue_create();
candidate->cmp = cmp;
candidate->update = update_stat;
@@ -1155,7 +1155,7 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* Set Area A's TransitCapability to FALSE. */
area->transit = OSPF_TRANSIT_FALSE;
area->shortcut_capability = 1;
-
+
for (;;)
{
/* RFC2328 16.1. (2). */
@@ -1201,29 +1201,29 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* Free candidate queue. */
pqueue_delete (candidate);
-
+
ospf_vertex_dump (__func__, area->spf, 0, 1);
/* Free nexthop information, canonical versions of which are attached
* the first level of router vertices attached to the root vertex, see
* ospf_nexthop_calculation.
*/
ospf_canonical_nexthops_free (area->spf);
-
+
/* Free SPF vertices, but not the list. List has ospf_vertex_free
* as deconstructor.
*/
list_delete_all_node (&vertex_list);
-
+
/* Increment SPF Calculation Counter. */
area->spf_calculation++;
quagga_gettime (QUAGGA_CLK_MONOTONIC, &area->ospf->ts_spf);
if (IS_DEBUG_OSPF_EVENT)
- zlog_debug ("ospf_spf_calculate: Stop. %ld vertices",
+ zlog_debug ("ospf_spf_calculate: Stop. %lu vertices",
mtype_stats_alloc(MTYPE_OSPF_VERTEX));
}
-
+
/* Timer for SPF calculation. */
static int
ospf_spf_calculate_timer (struct thread *thread)
@@ -1252,14 +1252,14 @@ ospf_spf_calculate_timer (struct thread *thread)
*/
if (ospf->backbone && ospf->backbone == area)
continue;
-
+
ospf_spf_calculate (area, new_table, new_rtrs);
}
-
+
/* SPF for backbone, if required */
if (ospf->backbone)
ospf_spf_calculate (ospf->backbone, new_table, new_rtrs);
-
+
ospf_vl_shut_unapproved (ospf);
ospf_ia_routing (ospf, new_table, new_rtrs);
@@ -1313,7 +1313,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
/* OSPF instance does not exist. */
if (ospf == NULL)
return;
-
+
/* SPF calculation timer is already scheduled. */
if (ospf->t_spf_calc)
{
@@ -1322,16 +1322,16 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
ospf->t_spf_calc);
return;
}
-
+
/* XXX Monotic timers: we only care about relative time here. */
result = tv_sub (recent_relative_time (), ospf->ts_spf);
-
+
elapsed = (result.tv_sec * 1000) + (result.tv_usec / 1000);
ht = ospf->spf_holdtime * ospf->spf_hold_multiplier;
-
+
if (ht > ospf->spf_max_holdtime)
ht = ospf->spf_max_holdtime;
-
+
/* Get SPF calculation delay time. */
if (elapsed < ht)
{
@@ -1341,7 +1341,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
*/
if (ht < ospf->spf_max_holdtime)
ospf->spf_hold_multiplier++;
-
+
/* always honour the SPF initial delay */
if ( (ht - elapsed) < ospf->spf_delay)
delay = ospf->spf_delay;
@@ -1354,7 +1354,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
delay = ospf->spf_delay;
ospf->spf_hold_multiplier = 1;
}
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("SPF: calculation timer delay = %ld", delay);
diff --git a/ripngd/ripngd.c b/ripngd/ripngd.c
index d534b2b6..6a7bbc54 100644
--- a/ripngd/ripngd.c
+++ b/ripngd/ripngd.c
@@ -233,10 +233,7 @@ ripng_recv_packet (int sock, u_char *buf, int bufsize,
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsgptr;
- struct in6_addr dst ;
-
- dst.s6_addr[0] = 0xFF ; /* calm down compiler: also do nothing
- if does not find a dst ! */
+ struct in6_addr dst = IN6ADDR_ANY_INIT ;
/* Ancillary data. This store cmsghdr and in6_pktinfo. But at this
point I can't determine size of cmsghdr */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d6e24db7..b4233fb8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,8 +6,8 @@ AM_LDFLAGS = $(PILDFLAGS)
noinst_PROGRAMS = testsig testbuffer testmemory heavy heavywq heavythread \
aspathtest testprivs teststream testbgpcap ecommtest \
- testbgpmpattr testchecksum testvector testsymtab \
- testlistutil
+ testbgpmpattr testchecksum testvector testsymtab \
+ testlistutil testqpath
testsig_SOURCES = test-sig.c
testbuffer_SOURCES = test-buffer.c
@@ -25,6 +25,7 @@ testchecksum_SOURCES = test-checksum.c
testvector_SOURCES = test-vector.c
testsymtab_SOURCES = test-symtab.c
testlistutil_SOURCES = test-list_util.c
+testqpath_SOURCES = test-qpath.c
testsig_LDADD = ../lib/libzebra.la @LIBCAP@
testbuffer_LDADD = ../lib/libzebra.la @LIBCAP@
@@ -42,3 +43,4 @@ testchecksum_LDADD = ../lib/libzebra.la @LIBCAP@
testvector_LDADD = ../lib/libzebra.la @LIBCAP@
testsymtab_LDADD = ../lib/libzebra.la @LIBCAP@
testlistutil_LDADD = ../lib/libzebra.la @LIBCAP@
+testqpath_LDADD = ../lib/libzebra.la @LIBCAP@
diff --git a/tests/test-list_util.c b/tests/test-list_util.c
index fc81a562..c4e88502 100644
--- a/tests/test-list_util.c
+++ b/tests/test-list_util.c
@@ -1,4 +1,5 @@
#include <zebra.h>
+#include "misc.h"
#include <list_util.h>
#include <string.h>
@@ -20,10 +21,10 @@ static void test_ddl(void);
} while (0)
static void
-test_assert_fail(const char* true, const char* message, const char* func,
+test_assert_fail(const char* truth, const char* message, const char* func,
int line)
{
- printf("*** %s %d: (%s) not true: %s\n", func, line, true, message) ;
+ printf("*** %s %d: (%s) not true: %s\n", func, line, truth, message) ;
} ;
@@ -253,12 +254,12 @@ test_ssl(void)
this = base ;
first = base->next ;
ret = ssl_del(base, this, next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == true, "ssl_del did not return true") ;
test_assert(first == base, "ssl_del of first item failed") ;
this = other->base ;
ret = ssl_del(other->base, this, other_next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == true, "ssl_del did not return true") ;
test_assert(first == other->base, "ssl_del of first item failed") ;
printf("\n") ;
@@ -271,9 +272,9 @@ test_ssl(void)
*/
printf(" Deleting arbitrary items") ;
ret = ssl_del(base, del, next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == true, "ssl_del did not return true") ;
ret = ssl_del(ssl_parent.base, other_del, other_next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == true, "ssl_del did not return true") ;
printf("\n") ;
/* Deletion of items from arbitrary place in list
@@ -282,9 +283,9 @@ test_ssl(void)
*/
printf(" Deleting non-existant items") ;
ret = ssl_del(base, &dummy, next) ;
- test_assert(ret == -1, "ssl_del did not return -1") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
ret = ssl_del(other->base, &dummy, other_next) ;
- test_assert(ret == -1, "ssl_del did not return -1") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
printf("\n") ;
/* Deletion of NULL items
@@ -296,10 +297,10 @@ test_ssl(void)
this = NULL ;
ret = ssl_del(base, this, next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
ret = ssl_del(ssl_parent.base, this, other_next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
printf("\n") ;
@@ -567,21 +568,21 @@ test_ssl(void)
test_assert(other->base == NULL, "ssl_del of first and only item failed") ;
ret = ssl_del(base, del, next) ;
- test_assert(ret == -1, "ssl_del did not return -1") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
test_assert(base == NULL, "ssl_del on empty list") ;
ret = ssl_del(other->base, other_del, other_next) ;
- test_assert(ret == -1, "ssl_del did not return -1") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
test_assert(other->base == NULL, "ssl_del on empty list") ;
this = NULL ;
ret = ssl_del(base, this, next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
test_assert(base == NULL, "ssl_del on empty list") ;
ret = ssl_del(other->base, this, other_next) ;
- test_assert(ret == 0, "ssl_del did not return 0") ;
+ test_assert(ret == false, "ssl_del did not return false") ;
test_assert(other->base == NULL, "ssl_del on empty list") ;
printf("\n") ;
diff --git a/tests/test-qpath.c b/tests/test-qpath.c
new file mode 100644
index 00000000..ecf1a0ae
--- /dev/null
+++ b/tests/test-qpath.c
@@ -0,0 +1,490 @@
+#include "misc.h"
+#include "qpath.h"
+#include "qlib_init.h"
+
+#include <stdio.h>
+
+struct thread_master *master ; /* required by lib ! */
+
+/*==============================================================================
+ * qpath torture tests
+ *
+ */
+static int qpath_reduce_testing(void) ;
+
+int
+main(int argc, char **argv)
+{
+ int errors = 0 ;
+
+ qlib_init_first_stage() ;
+
+ fprintf(stdout, "qpath torture tests\n") ;
+
+ errors += qpath_reduce_testing() ;
+
+
+
+ if (errors == 0)
+ fprintf(stdout, "No errors\n") ;
+ else
+ fprintf(stderr, "*** %d errors\n", errors) ;
+} ;
+
+/*==============================================================================
+ * Testing Path Reduction
+ */
+static int qpath_reduce_test(qpath qp, const char* from, const char* to) ;
+
+static int
+qpath_reduce_testing(void)
+{
+ int errors = 0 ;
+ qpath qp ;
+
+ fprintf(stdout, " qpath_reduce() testing") ;
+
+ qp = qpath_init_new(NULL) ;
+
+ /* Trivial Tests */
+
+ errors += qpath_reduce_test(qp, "",
+ "") ;
+ errors += qpath_reduce_test(qp, "a",
+ "a") ;
+ errors += qpath_reduce_test(qp, ".",
+ ".") ;
+ errors += qpath_reduce_test(qp, "/",
+ "/") ;
+ errors += qpath_reduce_test(qp, "/a",
+ "/a") ;
+ errors += qpath_reduce_test(qp, "/.",
+ "/-") ;
+ errors += qpath_reduce_test(qp, "//",
+ "//") ;
+ errors += qpath_reduce_test(qp, "//a",
+ "//a") ;
+ errors += qpath_reduce_test(qp, "//.",
+ "//-") ;
+ errors += qpath_reduce_test(qp, "///",
+ "/--") ;
+ errors += qpath_reduce_test(qp, "///a",
+ "/--a") ;
+ errors += qpath_reduce_test(qp, "///.",
+ "/---") ;
+
+ /* Slightly longer paths */
+
+ errors += qpath_reduce_test(qp, "abc",
+ "abc") ;
+ errors += qpath_reduce_test(qp, "abc.",
+ "abc.") ;
+ errors += qpath_reduce_test(qp, ".abc",
+ ".abc") ;
+ errors += qpath_reduce_test(qp, "/abc",
+ "/abc") ;
+ errors += qpath_reduce_test(qp, "/.abc",
+ "/.abc") ;
+ errors += qpath_reduce_test(qp, "/..abc",
+ "/..abc") ;
+ errors += qpath_reduce_test(qp, "//abc",
+ "//abc") ;
+ errors += qpath_reduce_test(qp, "//abc.",
+ "//abc.") ;
+ errors += qpath_reduce_test(qp, "//.abc",
+ "//.abc") ;
+ errors += qpath_reduce_test(qp, "///abc",
+ "/--abc") ;
+ errors += qpath_reduce_test(qp, "///abc.",
+ "/--abc.") ;
+ errors += qpath_reduce_test(qp, "///..abc",
+ "/--..abc") ;
+
+ errors += qpath_reduce_test(qp, "abc/pqr",
+ "abc/pqr") ;
+ errors += qpath_reduce_test(qp, "abc./pqr/",
+ "abc./pqr/") ;
+ errors += qpath_reduce_test(qp, ".abc/pqr//",
+ ".abc/pqr/-") ;
+ errors += qpath_reduce_test(qp, "/abc/pqr",
+ "/abc/pqr") ;
+ errors += qpath_reduce_test(qp, "/.abc/pqr/",
+ "/.abc/pqr/") ;
+ errors += qpath_reduce_test(qp, "/..abc/pqr///",
+ "/..abc/pqr/--") ;
+ errors += qpath_reduce_test(qp, "//abc/pqr",
+ "//abc/pqr") ;
+ errors += qpath_reduce_test(qp, "//abc./pqr/",
+ "//abc./pqr/") ;
+ errors += qpath_reduce_test(qp, "//.abc/pqr//",
+ "//.abc/pqr/-") ;
+ errors += qpath_reduce_test(qp, "///abc/pqr",
+ "/--abc/pqr") ;
+ errors += qpath_reduce_test(qp, "///abc./pqr/",
+ "/--abc./pqr/") ;
+ errors += qpath_reduce_test(qp, "///..abc/pqr//",
+ "/--..abc/pqr/-") ;
+
+ /* Lots of / and . to get rid of -- NB: does not discard trailing '/' */
+
+ errors += qpath_reduce_test(qp, "/.///./././//",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "/.a.///./././//",
+ "/.a./----------") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//",
+ "/----.b/------") ;
+ errors += qpath_reduce_test(qp, "/.a.///./././//.z.",
+ "/.a./----------.z.") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//z.",
+ "/----.b/------z.") ;
+ errors += qpath_reduce_test(qp, "/a///./././//.z./",
+ "/a/----------.z./") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//.z./",
+ "/----.b/------.z./") ;
+
+ errors += qpath_reduce_test(qp, "//.///./././//",
+ "//------------") ;
+ errors += qpath_reduce_test(qp, "//.a.///./././//",
+ "//.a./----------") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//",
+ "//----.b/------") ;
+ errors += qpath_reduce_test(qp, "//a///./././//z",
+ "//a/----------z") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//.z.",
+ "//----.b/------.z.") ;
+ errors += qpath_reduce_test(qp, "//a///./././//..z/",
+ "//a/----------..z/") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//z../",
+ "//----.b/------z../") ;
+
+ errors += qpath_reduce_test(qp, "///.///./././//",
+ "/--------------") ;
+ errors += qpath_reduce_test(qp, "///..a///./././//",
+ "/--..a/----------") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//",
+ "/------.b/------") ;
+ errors += qpath_reduce_test(qp, "///a..///./././//z",
+ "/--a../----------z") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//z",
+ "/------.b/------z") ;
+ errors += qpath_reduce_test(qp, "///..a..///./././//z/",
+ "/--..a../----------z/") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//z/",
+ "/------.b/------z/") ;
+
+ /* Assorted trailing '.' */
+
+ errors += qpath_reduce_test(qp, ".",
+ ".") ;
+ errors += qpath_reduce_test(qp, "./",
+ "./") ;
+ errors += qpath_reduce_test(qp, "a././",
+ "a./--") ;
+ errors += qpath_reduce_test(qp, "/.a/./",
+ "/.a/--") ;
+ errors += qpath_reduce_test(qp, "/a./.",
+ "/a./-") ;
+ errors += qpath_reduce_test(qp, "/.",
+ "/-") ;
+ errors += qpath_reduce_test(qp, "/./",
+ "/--") ;
+ errors += qpath_reduce_test(qp, "//.",
+ "//-") ;
+ errors += qpath_reduce_test(qp, "//./",
+ "//--") ;
+ errors += qpath_reduce_test(qp, "///.",
+ "/---") ;
+ errors += qpath_reduce_test(qp, "///.//",
+ "/-----") ;
+
+ /* Possible .. */
+
+ errors += qpath_reduce_test(qp, ".a.a.a./..",
+ "----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../../",
+ "-----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../..",
+ "----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../.././//././/.",
+ "---------------------") ;
+ errors += qpath_reduce_test(qp, "..aaa../../..z",
+ "-----------..z") ;
+ errors += qpath_reduce_test(qp, "..aaa../../..z../",
+ "-----------..z../") ;
+
+ errors += qpath_reduce_test(qp, "..b.b.b../..aaa../..",
+ "..b.b.b../----------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..a.a.a../../",
+ "..bbb../-------------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..a.a.a../.././///./",
+ "..bbb../--------------------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..aaa.././/../..z..",
+ "..bbb../--------------..z..") ;
+ errors += qpath_reduce_test(qp, "..bbb../..aaa.././/../..z../",
+ "..bbb../--------------..z../") ;
+
+ errors += qpath_reduce_test(qp, "/..a.a.a../..",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "/..a.a.a/../",
+ "/-----------") ;
+ errors += qpath_reduce_test(qp, "/a.a.a../.././///./",
+ "/------------------") ;
+ errors += qpath_reduce_test(qp, "/..aaa/.//../..z",
+ "/------------..z") ;
+ errors += qpath_reduce_test(qp, "/aaa.././/../z/",
+ "/------------z/") ;
+
+ errors += qpath_reduce_test(qp, "//aaa../..",
+ "//--------") ;
+ errors += qpath_reduce_test(qp, "//..aaa/../",
+ "//---------") ;
+ errors += qpath_reduce_test(qp, "//aaa../.././///./",
+ "//----------------") ;
+ errors += qpath_reduce_test(qp, "//aaa.././/../..z",
+ "//------------..z") ;
+ errors += qpath_reduce_test(qp, "//..aaa/.//../z../",
+ "//------------z../") ;
+
+ errors += qpath_reduce_test(qp, "./aaa../..",
+ "./--------") ;
+ errors += qpath_reduce_test(qp, "./..aaa/../",
+ "./---------") ;
+ errors += qpath_reduce_test(qp, "./aaa../.././///./",
+ "./----------------") ;
+ errors += qpath_reduce_test(qp, "./..aaa/.//../z",
+ "./------------z") ;
+ errors += qpath_reduce_test(qp, "./aaa.././/../z/",
+ "./------------z/") ;
+
+ errors += qpath_reduce_test(qp, ".///./aaa./..",
+ "./-----------") ;
+ errors += qpath_reduce_test(qp, "./././.aaa/../",
+ "./------------") ;
+ errors += qpath_reduce_test(qp, "./////.aaa/.././///./",
+ "./-------------------") ;
+ errors += qpath_reduce_test(qp, "././//./aaa././/../z",
+ "./-----------------z") ;
+ errors += qpath_reduce_test(qp, "./..aaa.././/../z/",
+ "./--------------z/") ;
+
+ errors += qpath_reduce_test(qp, "///..aaa../..",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "///.aaa./../",
+ "/-----------") ;
+ errors += qpath_reduce_test(qp, "///aaa/.././///./",
+ "/----------------") ;
+ errors += qpath_reduce_test(qp, "///..aaa/.//../z",
+ "/--------------z") ;
+ errors += qpath_reduce_test(qp, "///aaa.././/../z/",
+ "/--------------z/") ;
+
+ errors += qpath_reduce_test(qp, ".bbb/..aaa/../..",
+ "----------------") ;
+ errors += qpath_reduce_test(qp, "bbb./aaa../../../",
+ "-----------------") ;
+ errors += qpath_reduce_test(qp, "bbb../aaa../../.././///./",
+ "-------------------------") ;
+ errors += qpath_reduce_test(qp, "..bbb/..aaa/../../z",
+ "------------------z") ;
+ errors += qpath_reduce_test(qp, "bbb../..aaa/../../z/",
+ "------------------z/") ;
+
+ errors += qpath_reduce_test(qp, "bbb./aaa/../.zzz/../qqq/../..",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "bbb/.aaa/../zzz./../qqq/../../",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "/bbb/aaa/../zzz../../..qqq/../../",
+ "/--------------------------------") ;
+ errors += qpath_reduce_test(qp, "//bbb../aaa/../zzz/../qqq../../../",
+ "//--------------------------------") ;
+ errors += qpath_reduce_test(qp, "./..bbb/aaa/../zzz/../qqq/../../",
+ "./------------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa../../zzz/../qqq/../..",
+ "./-----------------------------") ;
+ errors += qpath_reduce_test(qp, "///bbb/aaa/../zzz../../qqq/../../",
+ "/--------------------------------") ;
+
+ errors += qpath_reduce_test(qp, "bbb/aaa/../zzz/../qqq/../../.",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "bbb/aaa/../zzz/../qqq/../.././",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "/bbb/aaa/../zzz/../qqq/../../",
+ "/----------------------------") ;
+ errors += qpath_reduce_test(qp, "//bbb/aaa/../zzz/../qqq/../../",
+ "//----------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa/../zzz/../qqq/../../",
+ "./----------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa/../zzz/../qqq/../..",
+ "./---------------------------") ;
+ errors += qpath_reduce_test(qp, "///bbb/aaa/../zzz/../qqq/../../",
+ "/------------------------------") ;
+
+ errors += qpath_reduce_test(qp, "o/aaa../../zzz/../qqq/../../.",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/aaa/../zzz../../qqq/../.././",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq../../../",
+ "o/------------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../../",
+ "o/----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../../",
+ "o/----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../..",
+ "o/---------------------------") ;
+ errors += qpath_reduce_test(qp, "o//bbb/aaa/../zzz/../qqq/../../",
+ "o/-----------------------------") ;
+
+ errors += qpath_reduce_test(qp, "zzz../././/.././o/..",
+ "--------------------") ;
+ errors += qpath_reduce_test(qp, "zzz.././o//.././o/../",
+ "zzz../---------------") ;
+
+ /* Impossible .. */
+
+ errors += qpath_reduce_test(qp, "..",
+ "..") ;
+ errors += qpath_reduce_test(qp, "../",
+ "../") ;
+ errors += qpath_reduce_test(qp, "/..",
+ "/..") ;
+ errors += qpath_reduce_test(qp, "/../",
+ "/../") ;
+ errors += qpath_reduce_test(qp, "//..",
+ "//..") ;
+ errors += qpath_reduce_test(qp, "//../",
+ "//../") ;
+ errors += qpath_reduce_test(qp, "///..",
+ "/--..") ;
+ errors += qpath_reduce_test(qp, "///../",
+ "/--../") ;
+ errors += qpath_reduce_test(qp, "./..",
+ "./..") ;
+ errors += qpath_reduce_test(qp, "./../",
+ "./../") ;
+ errors += qpath_reduce_test(qp, ".///..",
+ "./--..") ;
+ errors += qpath_reduce_test(qp, ".///../",
+ "./--../") ;
+ errors += qpath_reduce_test(qp, ".///..",
+ "./--..") ;
+ errors += qpath_reduce_test(qp, "././/../",
+ "./---../") ;
+
+ errors += qpath_reduce_test(qp, "..///./..",
+ "../----..") ;
+ errors += qpath_reduce_test(qp, "..///./../",
+ "../----../") ;
+ errors += qpath_reduce_test(qp, "/.././///..",
+ "/../-----..") ;
+ errors += qpath_reduce_test(qp, "/..///.//../",
+ "/../-----../") ;
+ errors += qpath_reduce_test(qp, "//..///.//../.",
+ "//../-----../-") ;
+ errors += qpath_reduce_test(qp, "//..//.///.././",
+ "//../-----../--") ;
+ errors += qpath_reduce_test(qp, "///..///.//..",
+ "/--../-----..") ;
+ errors += qpath_reduce_test(qp, "///.././/./../",
+ "/--../-----../") ;
+ errors += qpath_reduce_test(qp, "./../../..",
+ "./../../..") ;
+ errors += qpath_reduce_test(qp, "./../../../",
+ "./../../../") ;
+ errors += qpath_reduce_test(qp, ".///..///.///.///..",
+ "./--../----------..") ;
+ errors += qpath_reduce_test(qp, ".///.././././../",
+ "./--../------../") ;
+ errors += qpath_reduce_test(qp, ".///..///////..",
+ "./--../------..") ;
+ errors += qpath_reduce_test(qp, "././/..//////..////",
+ "./---../-----../---") ;
+
+ errors += qpath_reduce_test(qp, "../././/.",
+ "../------") ;
+ errors += qpath_reduce_test(qp, "../zzz/..",
+ "../------") ;
+ errors += qpath_reduce_test(qp, "/../zzz/.//./../",
+ "/../------------") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././..",
+ "/----------------..") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././../",
+ "/----------------../") ;
+ errors += qpath_reduce_test(qp, "zzz../././/.././..",
+ "----------------..") ;
+ errors += qpath_reduce_test(qp, "zzz../././/.././../",
+ "----------------../") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././..",
+ "/----------------..") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././../",
+ "/----------------../") ;
+ errors += qpath_reduce_test(qp, "//zzz../././/.././..",
+ "//----------------..") ;
+ errors += qpath_reduce_test(qp, "//zzz../././/.././../",
+ "//----------------../") ;
+ errors += qpath_reduce_test(qp, "///zzz../././/.././..",
+ "/------------------..") ;
+ errors += qpath_reduce_test(qp, "///zzz../././/.././../",
+ "/------------------../") ;
+
+ /* Finish up */
+
+ qpath_reset(qp, free_it) ;
+
+ if (errors == 0)
+ fprintf(stdout, " -- OK\n") ;
+ else
+ fprintf(stdout, "\n *** %d errors\n", errors) ;
+
+ return errors ;
+} ;
+
+
+static int
+qpath_reduce_test(qpath qp, const char* from, const char* to)
+{
+ const char* r ;
+ char e[100] ;
+ const char* p ;
+ char* q ;
+
+ assert(strlen(to) < sizeof(e)) ;
+ if (strlen(to) != strlen(from))
+ {
+ fprintf(stdout,
+ "\n"
+ " qpath_reduce(%s)\n"
+ " to: '%s' ???", from, to) ;
+ return 1 ;
+ } ;
+
+ p = to ;
+ q = e ;
+
+ while (1)
+ {
+ char ch = *p++ ;
+
+ if ((ch == ' ') || (ch == '-'))
+ continue ;
+
+ *q++ = ch ;
+
+ if (ch == '\0')
+ break ;
+ } ;
+
+ qpath_set(qp, from) ; /* Reduces automajically */
+
+ r = qpath_string(qp) ;
+
+ if (strcmp(r, e) == 0)
+ return 0 ;
+
+ fprintf(stdout,
+ "\n"
+ " qpath_reduce(%s)\n"
+ " returned: '%s'\n"
+ " expected: '%s'", from, r, e) ;
+
+ return 1 ;
+} ;
diff --git a/tests/test-symtab.c b/tests/test-symtab.c
index ed83e607..a4b0bcf2 100644
--- a/tests/test-symtab.c
+++ b/tests/test-symtab.c
@@ -56,24 +56,25 @@ test_symbol_table_init_new(void)
assert_true(table != NULL, "table == NULL");
/* expect to not find */
- sym = symbol_lookup(table, name, 0);
+ sym = symbol_lookup(table, name, no_add);
assert_true(sym == NULL, "sym != NULL");
/* add */
- sym = symbol_lookup(table, name, 1);
+ sym = symbol_lookup(table, name, add);
symbol_set_value(sym, value);
assert_true(sym != NULL, "sym == NULL");
- assert_true(strcmp(symbol_get_name(sym), name) == 0, "strcmp(symbol_get_name(sym), name) != 0");
+ assert_true(strcmp(symbol_get_name(sym), name) == 0,
+ "strcmp(symbol_get_name(sym), name) != 0");
/* find */
- sym2 = symbol_lookup(table, name, 0);
+ sym2 = symbol_lookup(table, name, no_add);
assert_true(sym == sym2, "sym != sym2");
assert_true(symbol_get_value(sym) == value, "symbol_get_value(sym) != value");
old_value = symbol_delete(sym);
assert_true(value == old_value, "value != old_value");
- while ((old_value = symbol_table_ream(table, 1)) != NULL)
+ while ((old_value = symbol_table_ream(table, keep_it)) != NULL)
{
}
@@ -98,7 +99,7 @@ test_symbol_table_lookup(void)
for (i = 0; i < len; ++i)
{
sprintf(buf, "%d-name", i);
- sym = symbol_lookup(table, buf, 1);
+ sym = symbol_lookup(table, buf, add);
assert_true(sym != NULL, "add: sym == NULL");
assert_true(strcmp(symbol_get_name(sym), buf) == 0,
"strcmp(symbol_get_name(sym), buf) != 0");
@@ -114,7 +115,7 @@ test_symbol_table_lookup(void)
for (i = 0; i < len; ++i)
{
sprintf(buf, "%d-name", i);
- sym = symbol_lookup(table, buf, 0);
+ sym = symbol_lookup(table, buf, no_add);
assert_true(sym != NULL, "find: sym == NULL");
assert_true(strcmp(symbol_get_name(sym), buf) == 0,
"strcmp(symbol_get_name(sym), buf) != 0");
@@ -162,12 +163,12 @@ test_call_back(void)
/* add */
symbol_table_set_value_call_back(table, call_back_function_set);
- sym = symbol_lookup(table, name, 1);
+ sym = symbol_lookup(table, name, add);
symbol_set_value(sym, value);
/* change */
symbol_table_set_value_call_back(table, call_back_function_change);
- sym = symbol_lookup(table, name, 1);
+ sym = symbol_lookup(table, name, add);
symbol_set_value(sym, new_value);
/* delete */
@@ -216,7 +217,7 @@ test_ref(void)
table = symbol_table_init_new(table, NULL, 0, 0, NULL, NULL);
/* add */
- sym = symbol_lookup(table, name, 1);
+ sym = symbol_lookup(table, name, add);
symbol_set_value(sym, value);
/* create references, in reverse order so that walk in order */
@@ -272,7 +273,7 @@ test_ref_heavy(void)
table = symbol_table_init_new(table, NULL, 0, 0, NULL, NULL);
/* add */
- sym = symbol_lookup(table, name, 1);
+ sym = symbol_lookup(table, name, add);
symbol_set_value(sym, value);
/* create references, in reverse order so that walk in order */
diff --git a/tests/test-vector.c b/tests/test-vector.c
index 808a7666..c528b9fd 100644
--- a/tests/test-vector.c
+++ b/tests/test-vector.c
@@ -25,12 +25,14 @@ void test_vector_insert_item(void);
void test_vector_insert_item_here(void);
void test_vector_delete_item(void);
void do_test_insert(const int rider);
-int sort_cmp(const void* const* a, const void* const* b);
+int sort_cmp(void const* const* a, void const* const* b);
void test_vector_sort(void);
void test_vector_bsearch(void);
void test_vector_move_item_here(void);
-void do_test_move_item_here(const int rider, vector_index ins, vector_index src,
- char strings[][10], const vector_index len) ;
+void do_test_move_item_here(const int rider, vector_index_t ins,
+ vector_index_t src,
+ char strings[][10],
+ const vector_length_t len) ;
void test_vector_part_reverse(void);
void test_vector_copy_here(void);
void test_vector_move_here(void);
@@ -290,9 +292,9 @@ test_vector_set(void)
"vector_active != 1000");
vector_set(v, s1000);
- assert_true(vector_count(v) == len + 1,
+ assert_true(vector_count(v) == (unsigned)len + 1,
"vector_count != 1001");
- assert_true(vector_active(v) == len + 1,
+ assert_true(vector_active(v) == (unsigned)len + 1,
"vector_active != 1001");
assert_true(vector_slot(v,1000) == s1000,
"vector_slot != 1000");
@@ -425,12 +427,12 @@ void
do_test_insert(const int rider)
{
vector v = NULL;
- const vector_index len = 100;
- const vector_index ins = 50;
- vector_index i;
+ const vector_length_t len = 100;
+ const vector_index_t ins = 50;
+ vector_index_t i;
char buf[10];
- vector_index check_end = len + 1;
- vector_index check_ins = ins;
+ vector_length_t check_end = len + 1;
+ vector_index_t check_ins = ins;
int check_shift = 1;
switch(rider)
@@ -551,7 +553,7 @@ test_vector_sort(void)
}
int
-sort_cmp(const void* const* a, const void* const* b)
+sort_cmp(void const* const* a, void const* const* b)
{
return strcmp(*a, *b);
}
@@ -564,9 +566,9 @@ test_vector_bsearch(void)
const int len = 2000;
char buf[20];
char target[20];
- vector_index target_index = 0;
+ vector_index_t target_index = 0;
int result;
- vector_index index;
+ vector_index_t index;
printf("test_vector_bsearch\n");
@@ -704,12 +706,14 @@ test_vector_move_item_here(void)
}
void
-do_test_move_item_here(const int rider, vector_index ins, vector_index src,
- char strings[][10], const vector_index len)
+do_test_move_item_here(const int rider, vector_index_t ins,
+ vector_index_t src,
+ char strings[][10],
+ const vector_length_t len)
{
vector v = NULL;
- vector_index i, e, check_end ;
- vector_index hi, lo ;
+ vector_index_t i, e, check_end ;
+ vector_index_t hi, lo ;
char* expect[len] ;
p_vector_item dest_item = NULL;
@@ -722,7 +726,6 @@ do_test_move_item_here(const int rider, vector_index ins, vector_index src,
case 0:
printf("test_vector_move_here at dst=%2d, src=%2d\n", src, ins);
- --check_end ;
break;
case 1:
@@ -876,17 +879,17 @@ do_test_move_item_here(const int rider, vector_index ins, vector_index src,
if (rider == 0)
free(dest_item);
- vector_free(v);
-}
+ vector_free(v);
+} ;
void
test_vector_part_reverse(void)
{
vector v = NULL;
- const vector_index len = 100;
- const vector_index rstart = 50;
- const vector_index rstop = 70;
- vector_index i;
+ const vector_length_t len = 100;
+ const vector_index_t rstart = 50;
+ const vector_index_t rstop = 70;
+ vector_index_t i;
char buf[10];
printf("test_vector_part_reverse\n");
@@ -942,8 +945,8 @@ test_vector_copy_here(void)
{
vector v1 = NULL;
vector v2 = NULL;
- vector_index i;
- const vector_index len = 100;
+ vector_index_t i;
+ const vector_length_t len = 100;
char buf[10];
printf("test_vector_copy_here\n");
@@ -981,8 +984,8 @@ test_vector_move_here(void)
{
vector v1 = NULL;
vector v2 = NULL;
- vector_index i;
- const vector_index len = 100;
+ vector_index_t i;
+ const vector_length_t len = 100;
char buf[10];
printf("test_vector_move_here\n");
@@ -1023,8 +1026,8 @@ test_vector_copy_append(void)
{
vector v1 = NULL;
vector v2 = NULL;
- vector_index i;
- const vector_index len = 100;
+ vector_index_t i;
+ const vector_length_t len = 100;
char buf[10];
printf("test_vector_copy_append\n");
@@ -1072,8 +1075,8 @@ test_vector_move_append(void)
{
vector v1 = NULL;
vector v2 = NULL;
- vector_index i;
- const vector_index len = 100;
+ vector_index_t i;
+ const vector_length_t len = 100;
char buf[10];
printf("test_vector_move_append\n");
@@ -1121,10 +1124,10 @@ void
test_vector_insert(void)
{
vector v = NULL;
- vector_index i;
- const vector_index len = 100;
- const vector_index istart = 50;
- const vector_index istop = 70;
+ vector_index_t i;
+ const vector_length_t len = 100;
+ const vector_index_t istart = 50;
+ const vector_index_t istop = 70;
char buf[10];
printf("test_vector_insert\n");
@@ -1176,10 +1179,10 @@ void
test_vector_delete(void)
{
vector v = NULL;
- vector_index i;
- const vector_index len = 100;
- const vector_index dstart = 50;
- const vector_index dstop = 70;
+ vector_index_t i;
+ const vector_length_t len = 100;
+ const vector_index_t dstart = 50;
+ const vector_index_t dstop = 70;
char buf[10];
printf("test_vector_delete\n");
@@ -1232,9 +1235,9 @@ void
test_vector_discard(void)
{
vector v = NULL;
- vector_index i;
- const vector_index len = 100;
- const vector_index dstart = 50;
+ vector_index_t i;
+ const vector_length_t len = 100;
+ const vector_index_t dstart = 50;
char buf[10];
printf("test_vector_discard\n");
@@ -1282,12 +1285,12 @@ test_vector_sak(void)
vector v1 = NULL;
vector v2 = NULL;
vector v3 = NULL;
- vector_index i;
- const vector_index len = 100;
- const vector_index sstart = 60;
- const vector_index sstop = 70;
- const vector_index dstart = 40;
- const vector_index dstop = 50;
+ vector_index_t i;
+ const vector_length_t len = 100;
+ const vector_index_t sstart = 60;
+ const vector_index_t sstop = 70;
+ const vector_index_t dstart = 40;
+ const vector_index_t dstop = 50;
char buf[10];
printf("test_vector_sak\n");
@@ -1304,7 +1307,7 @@ test_vector_sak(void)
}
v1 = vector_sak(1, v1, v2, dstart, dstop - dstart,
- v3, sstart, sstop - sstart, 0);
+ v3, sstart, sstop - sstart, 0);
assert_true(v1 != NULL, "v1 == NULL");
assert_true(vector_end(v1) == (dstop - dstart),
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 64c1b549..a1f65190 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -30,6 +30,7 @@
#include <readline/readline.h>
#include <readline/history.h>
+#include "command_execute.h"
#include "command.h"
#include "memory.h"
#include "vtysh/vtysh.h"
@@ -37,7 +38,7 @@
#include "bgpd/bgp_vty.h"
/* Struct VTY. */
-struct vty *vty;
+static struct vty* vtysh_vty;
/* VTY shell pager name. */
char *vtysh_pager_name = NULL;
@@ -51,13 +52,13 @@ struct vtysh_client
const char *path;
} vtysh_client[] =
{
- { .fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .path = ZEBRA_VTYSH_PATH},
- { .fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .path = RIP_VTYSH_PATH},
+ { .fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .path = ZEBRA_VTYSH_PATH},
+ { .fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .path = RIP_VTYSH_PATH},
{ .fd = -1, .name = "ripngd", .flag = VTYSH_RIPNGD, .path = RIPNG_VTYSH_PATH},
- { .fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .path = OSPF_VTYSH_PATH},
+ { .fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .path = OSPF_VTYSH_PATH},
{ .fd = -1, .name = "ospf6d", .flag = VTYSH_OSPF6D, .path = OSPF6_VTYSH_PATH},
- { .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
- { .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
+ { .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
+ { .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
};
#define VTYSH_INDEX_MAX (sizeof(vtysh_client)/sizeof(vtysh_client[0]))
@@ -250,7 +251,7 @@ vtysh_client_execute (struct vtysh_client *vclient, const char *line, FILE *fp)
}
}
-void
+static void
vtysh_exit_ripd_only (void)
{
if (ripd_client)
@@ -277,34 +278,34 @@ vtysh_execute_func (const char *line, int pager)
{
int ret, cmd_stat;
u_int i;
- struct cmd_element *cmd;
+ struct cmd_command *cmd;
FILE *fp = NULL;
int closepager = 0;
int tried = 0;
int saved_ret, saved_node;
/* TODO: how well does vtysh_execute_func work ?? -- esp. qpthreads_enabled */
- vty->buf = line ;
+ vtysh_vty->buf = line ;
- saved_ret = ret = cmd_execute_command (vty, cmd_parse_completion, &cmd);
+ saved_ret = ret = cmd_execute_command (vtysh_vty, cmd_parse_standard, &cmd);
if ((ret == CMD_SUCCESS) && (cmd == NULL))
return ret ; /* quit if nothing to do ??? */
- saved_node = vty->node;
+ saved_node = vtysh_vty->node;
/* If command doesn't succeeded in current node, try to walk up in node tree.
- * Changing vty->node is enough to try it just out without actual walkup in
- * the vtysh. */
+ * Changing vtysh_vty->node is enough to try it just out without actual
+ * walkup in the vtysh. */
while (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING
- && vty->node > CONFIG_NODE)
+ && vtysh_vty->node > CONFIG_NODE)
{
- vty->node = node_parent(vty->node);
- ret = cmd_execute_command (vty, cmd_parse_completion, &cmd);
+ vtysh_vty->node = cmd_node_parent(vtysh_vty->node);
+ ret = cmd_execute_command (vtysh_vty, cmd_parse_standard, &cmd);
tried++;
}
- vty->node = saved_node;
+ vtysh_vty->node = saved_node;
/* If command succeeded in any other node than current (tried > 0) we have
* to move into node in the vtysh where it succeeded. */
@@ -338,7 +339,7 @@ vtysh_execute_func (const char *line, int pager)
switch (ret)
{
case CMD_WARNING:
- if (vty->type == VTY_FILE)
+ if (vtysh_vty->type == VTY_FILE)
fprintf (stdout,"Warning...\n");
break;
case CMD_ERR_AMBIGUOUS:
@@ -382,16 +383,16 @@ vtysh_execute_func (const char *line, int pager)
{
line = "end";
- vty->buf = line ;
+ vtysh_vty->buf = line ;
- ret = cmd_execute_command (vty, cmd_parse_completion, &cmd);
+ ret = cmd_execute_command (vtysh_vty, cmd_parse_standard, &cmd);
if (ret != CMD_SUCCESS_DAEMON)
break;
}
else
if (cmd->func)
{
- (*cmd->func) (cmd, vty, 0, NULL);
+ (*cmd->func) (cmd, vtysh_vty, 0, NULL);
break;
}
}
@@ -410,7 +411,7 @@ vtysh_execute_func (const char *line, int pager)
break;
if (cmd->func)
- (*cmd->func) (cmd, vty, 0, NULL);
+ (*cmd->func) (cmd, vtysh_vty, 0, NULL);
}
}
if (pager && vtysh_pager_name && fp && closepager)
@@ -441,12 +442,12 @@ int
vtysh_config_from_file (struct vty *vty, FILE *fp)
{
int ret;
- struct cmd_element *cmd;
+ struct cmd_command *cmd;
/* TODO: (1) allocate buffer for vty->buf (2) what about CMD_QUEUED ?? */
- while (fgets (vty->buf, VTY_BUFSIZ, fp))
+ while (fgets (vty->buf, 2000, fp))
{
/* Execute configuration command : this is strict match. */
ret = cmd_execute_command(vty, cmd_parse_strict, &cmd);
@@ -524,7 +525,7 @@ vtysh_config_from_file (struct vty *vty, FILE *fp)
}
/* We don't care about the point of the cursor when '?' is typed. */
-int
+static int
vtysh_rl_describe (void)
{
int ret;
@@ -546,7 +547,7 @@ vtysh_rl_describe (void)
if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1]))
vector_set (vline, '\0');
- describe = cmd_describe_command (vline, vty->node, &ret);
+ describe = cmd_describe_command (vline, vtysh_vty->node, &ret);
fprintf (stdout,"\n");
@@ -591,14 +592,14 @@ vtysh_rl_describe (void)
if (desc->cmd[0] == '\0')
continue;
- if (! desc->str)
+ if (! desc->doc)
fprintf (stdout," %-s\n",
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd);
else
fprintf (stdout," %-*s %s\n",
width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
- desc->str);
+ desc->doc);
}
cmd_free_strvec (vline);
@@ -626,7 +627,7 @@ command_generator (const char *text, int state)
{
index = 0;
- if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+ if (vtysh_vty->node == AUTH_NODE || vtysh_vty->node == AUTH_ENABLE_NODE)
return NULL;
vline = cmd_make_strvec (rl_line_buffer);
@@ -636,7 +637,7 @@ command_generator (const char *text, int state)
if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1]))
vector_set (vline, '\0');
- matched = cmd_complete_command (vline, vty->node, &complete_status);
+ matched = cmd_complete_command (vline, vtysh_vty->node, &complete_status);
}
if (matched && matched[index])
@@ -671,7 +672,7 @@ vtysh_completion (char *text, int start, int end)
vector vline;
char **matched = NULL;
- if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+ if (vtysh_vty->node == AUTH_NODE || vtysh_vty->node == AUTH_ENABLE_NODE)
return NULL;
vline = cmd_make_strvec (rl_line_buffer);
@@ -682,7 +683,7 @@ vtysh_completion (char *text, int start, int end)
if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1]))
vector_set (vline, '\0');
- matched = cmd_complete_command (vline, vty, &ret);
+ matched = cmd_complete_command (vline, vtysh_vty, &ret);
cmd_free_strvec (vline);
@@ -791,17 +792,17 @@ static struct cmd_node keychain_key_node =
extern struct cmd_node vty_node;
/* When '^Z' is received from vty, move down to the enable mode. */
-int
+static int
vtysh_end (void)
{
- switch (vty->node)
+ switch (vtysh_vty->node)
{
case VIEW_NODE:
case ENABLE_NODE:
/* Nothing to do. */
break;
default:
- vty->node = ENABLE_NODE;
+ vtysh_vty->node = ENABLE_NODE;
break;
}
return CMD_SUCCESS;
@@ -2196,17 +2197,17 @@ vtysh_prompt (void)
hostname = names.nodename;
}
- snprintf (buf, sizeof buf, cmd_prompt (vty->node), hostname);
+ snprintf (buf, sizeof buf, cmd_prompt (vtysh_vty->node), hostname);
return buf;
}
-void
+struct vty*
vtysh_init_vty (void)
{
- /* Make vty structure. */
- vty = vty_open(VTY_SHELL);
- vty->node = VIEW_NODE;
+ /* Make vtysh_vty structure. */
+ vtysh_vty = vty_open(VTY_SHELL);
+ vtysh_vty->node = VIEW_NODE;
/* Initialize commands. */
cmd_init (0);
@@ -2437,4 +2438,5 @@ vtysh_init_vty (void)
install_element (CONFIG_NODE, &vtysh_enable_password_text_cmd);
install_element (CONFIG_NODE, &no_vtysh_enable_password_cmd);
+ return vtysh_vty ;
}
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index e711d593..820e5506 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -36,7 +36,7 @@
/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"
-void vtysh_init_vty (void);
+struct vty* vtysh_init_vty (void);
void vtysh_init_cmd (void);
extern int vtysh_connect_all (const char *optional_daemon_name);
void vtysh_readline_init (void);
@@ -64,6 +64,4 @@ void vtysh_pager_init (void);
/* Child process execution flag. */
extern int execute_flag;
-extern struct vty *vty;
-
#endif /* VTYSH_H */
diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c
index 15938f43..aed3a91c 100644
--- a/vtysh/vtysh_config.c
+++ b/vtysh/vtysh_config.c
@@ -47,19 +47,19 @@ struct config
struct list *config_top;
-int
-line_cmp (char *c1, char *c2)
+static int
+line_cmp (const char *c1, const char *c2)
{
return strcmp (c1, c2);
}
-void
+static void
line_del (char *line)
{
XFREE (MTYPE_VTYSH_CONFIG_LINE, line);
}
-struct config *
+static struct config *
config_new ()
{
struct config *config;
@@ -67,13 +67,13 @@ config_new ()
return config;
}
-int
+static int
config_cmp (struct config *c1, struct config *c2)
{
return strcmp (c1->name, c2->name);
}
-void
+static void
config_del (struct config* config)
{
list_delete (config->line);
@@ -82,7 +82,7 @@ config_del (struct config* config)
XFREE (MTYPE_VTYSH_CONFIG, config);
}
-struct config *
+static struct config *
config_get (int index, const char *line)
{
struct config *config;
@@ -121,13 +121,13 @@ config_get (int index, const char *line)
return config;
}
-void
+static void
config_add_line (struct list *config, const char *line)
{
listnode_add (config, XSTRDUP (MTYPE_VTYSH_CONFIG_LINE, line));
}
-void
+static void
config_add_line_uniq (struct list *config, const char *line)
{
struct listnode *node, *nnode;
@@ -141,7 +141,7 @@ config_add_line_uniq (struct list *config, const char *line)
listnode_add_sort (config, XSTRDUP (MTYPE_VTYSH_CONFIG_LINE, line));
}
-void
+static void
vtysh_config_parse_line (const char *line)
{
char c;
@@ -285,12 +285,19 @@ vtysh_config_parse (char *line)
/* Macro to check delimiter is needed between each configuration line
* or not. */
-#define NO_DELIMITER(I) \
- ((I) == ACCESS_NODE || (I) == PREFIX_NODE || (I) == IP_NODE \
- || (I) == AS_LIST_NODE || (I) == COMMUNITY_LIST_NODE || \
- (I) == ACCESS_IPV6_NODE || (I) == PREFIX_IPV6_NODE \
- || (I) == SERVICE_NODE || (I) == FORWARDING_NODE || (I) == DEBUG_NODE \
- || (I) == AAA_NODE)
+#define NO_DELIMITER(I) ( \
+ (I) == ACCESS_NODE || \
+ (I) == PREFIX_NODE || \
+ (I) == IP_NODE || \
+ (I) == AS_LIST_NODE || \
+ (I) == COMMUNITY_LIST_NODE || \
+ (I) == ACCESS_IPV6_NODE || \
+ (I) == PREFIX_IPV6_NODE || \
+ (I) == SERVICE_NODE || \
+ (I) == FORWARDING_NODE || \
+ (I) == DEBUG_NODE || \
+ (I) == AAA_NODE || \
+ 0 )
/* Display configuration to file pointer. */
void
@@ -365,7 +372,7 @@ vtysh_read_file (FILE *confp)
vtysh_execute_no_pager ("end");
vtysh_execute_no_pager ("disable");
- vty_close (vty);
+ vty_close_final(vty);
if (ret != CMD_SUCCESS)
{
@@ -396,7 +403,7 @@ vtysh_read_config (char *config_default_dir)
vtysh_read_file (confp);
fclose (confp);
- host_config_set (config_default_dir);
+ cmd_host_config_set (config_default_dir);
return (0);
}
diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c
index 4a315a5c..c576d3e0 100644
--- a/vtysh/vtysh_main.c
+++ b/vtysh/vtysh_main.c
@@ -63,7 +63,7 @@ struct thread_master *master;
FILE *logfile;
/* SIGTSTP handler. This function care user's ^Z input. */
-void
+static void
sigtstp (int sig)
{
/* Execute "end" command. */
@@ -84,7 +84,7 @@ sigtstp (int sig)
}
/* SIGINT handler. This function care user's ^Z input. */
-void
+static void
sigint (int sig)
{
/* Check this process is not child process. */
@@ -98,7 +98,7 @@ sigint (int sig)
/* Signale wrapper for vtysh. We don't use sigevent because
* vtysh doesn't use threads. TODO */
-RETSIGTYPE *
+static RETSIGTYPE *
vtysh_signal_set (int signo, void (*func)(int))
{
int ret;
@@ -121,7 +121,7 @@ vtysh_signal_set (int signo, void (*func)(int))
}
/* Initialization of signal handles. */
-void
+static void
vtysh_signal_init ()
{
vtysh_signal_set (SIGINT, sigint);
@@ -168,7 +168,7 @@ struct option longopts[] =
};
/* Read a string, and return a pointer to it. Returns NULL on EOF. */
-char *
+static char *
vtysh_rl_gets ()
{
HIST_ENTRY *last;
@@ -202,7 +202,7 @@ static void log_it(const char *line)
{
time_t t = time(NULL);
struct tm *tmp = localtime(&t);
- char *user = getenv("USER") ? : "boot";
+ const char *user = getenv("USER") ? : "boot";
char tod[64];
strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp);
@@ -214,6 +214,7 @@ static void log_it(const char *line)
int
main (int argc, char **argv, char **env)
{
+ struct vty* vty ;
char *p;
int opt;
int dryrun = 0;
@@ -292,7 +293,7 @@ main (int argc, char **argv, char **env)
vtysh_signal_init ();
/* Make vty structure and register commands. */
- vtysh_init_vty ();
+ vty = vtysh_init_vty ();
vtysh_init_cmd ();
vtysh_user_init ();
vtysh_config_init ();
diff --git a/vtysh/vtysh_user.c b/vtysh/vtysh_user.c
index 58676c10..5dd50bb7 100644
--- a/vtysh/vtysh_user.c
+++ b/vtysh/vtysh_user.c
@@ -21,6 +21,7 @@
#include <zebra.h>
#include <lib/version.h>
+#include <vtysh_user.h>
#include <pwd.h>
@@ -98,19 +99,21 @@ struct vtysh_user
struct list *userlist;
-struct vtysh_user *
+static struct vtysh_user *
user_new ()
{
return XCALLOC (0, sizeof (struct vtysh_user));
}
-void
+static void user_free (struct vtysh_user *user) __attribute__((unused)) ;
+
+static void
user_free (struct vtysh_user *user)
{
XFREE (0, user);
}
-struct vtysh_user *
+static struct vtysh_user *
user_lookup (const char *name)
{
struct listnode *node, *nnode;
@@ -124,8 +127,10 @@ user_lookup (const char *name)
return NULL;
}
-void
-user_config_write ()
+static void user_config_write (void) __attribute__((unused)) ;
+
+static void
+user_config_write (void)
{
struct listnode *node, *nnode;
struct vtysh_user *user;
@@ -137,7 +142,7 @@ user_config_write ()
}
}
-struct vtysh_user *
+static struct vtysh_user *
user_get (const char *name)
{
struct vtysh_user *user;
@@ -165,8 +170,8 @@ DEFUN (username_nopassword,
return CMD_SUCCESS;
}
-int
-vtysh_auth ()
+extern int
+vtysh_auth(void)
{
struct vtysh_user *user;
struct passwd *passwd;
@@ -186,8 +191,8 @@ vtysh_auth ()
return 0;
}
-void
-vtysh_user_init ()
+extern void
+vtysh_user_init(void)
{
userlist = list_new ();
install_element (CONFIG_NODE, &username_nopassword_cmd);
diff --git a/vtysh/vtysh_user.h b/vtysh/vtysh_user.h
index 8d0a4cf6..c02cfede 100644
--- a/vtysh/vtysh_user.h
+++ b/vtysh/vtysh_user.h
@@ -22,6 +22,7 @@
#ifndef _VTYSH_USER_H
#define _VTYSH_USER_H
-int vtysh_auth ();
+extern int vtysh_auth(void) ;
+extern void vtysh_user_init(void) ;
#endif /* _VTYSH_USER_H */