diff --git a/client/birdc.c b/client/birdc.c index f1aea2fe..f05cf8e0 100644 --- a/client/birdc.c +++ b/client/birdc.c @@ -81,7 +81,7 @@ static int input_complete(int arg UNUSED, int key UNUSED) { static int complete_flag; - char buf[256]; + char buf[256] = {}; if (rl_last_func != input_complete) complete_flag = 0; @@ -157,19 +157,22 @@ history_init(void) void input_init(void) { + prompt_active = 0; + if (interactive) + { + prompt_active = 1; history_init(); - rl_readline_name = "birdc"; - rl_add_defun("bird-complete", input_complete, '\t'); - rl_add_defun("bird-help", input_help, '?'); - rl_callback_handler_install("bird> ", input_got_line); + rl_readline_name = "birdc"; + rl_add_defun("bird-complete", input_complete, '\t'); + rl_add_defun("bird-help", input_help, '?'); + rl_callback_handler_install("bird> ", input_got_line); + } // rl_get_screen_size(); term_lns = LINES; term_cls = COLS; - prompt_active = 1; - // readline library does strange things when stdin is nonblocking. // if (fcntl(0, F_SETFL, O_NONBLOCK) < 0) // DIE("fcntl"); @@ -216,7 +219,10 @@ input_notify(int prompt) void input_read(void) { - rl_callback_read_char(); + if (interactive) + rl_callback_read_char(); + else + simple_input_read(); } void diff --git a/client/birdcl.c b/client/birdcl.c index 4508185c..e1a439ea 100644 --- a/client/birdcl.c +++ b/client/birdcl.c @@ -22,8 +22,6 @@ #include "client/client.h" #include "sysdep/unix/unix.h" -#define INPUT_BUF_LEN 2048 - struct termios tty_save; void @@ -42,59 +40,17 @@ void input_notify(int prompt) { /* No ncurses -> no status to reveal/hide, print prompt manually. */ - if (!prompt) + if (!prompt || !interactive) return; - printf("bird> "); + printf("\rbird> "); fflush(stdout); } - -static int -lastnb(char *str, int i) -{ - while (i--) - if ((str[i] != ' ') && (str[i] != '\t')) - return str[i]; - - return 0; -} - void input_read(void) { - char buf[INPUT_BUF_LEN]; - - if ((fgets(buf, INPUT_BUF_LEN, stdin) == NULL) || (buf[0] == 0)) - { - putchar('\n'); - cleanup(); - exit(0); - } - - int l = strlen(buf); - if ((l+1) == INPUT_BUF_LEN) - { - printf("Input too long.\n"); - return; - } - - if (buf[l-1] == '\n') - buf[--l] = '\0'; - - if (!interactive) - printf("%s\n", buf); - - if (l == 0) - return; - - if (lastnb(buf, l) == '?') - { - cmd_help(buf, strlen(buf)); - return; - } - - submit_command(buf); + simple_input_read(); } static struct termios stored_tty; diff --git a/client/client.c b/client/client.c index 0d4bdf3e..cab5601e 100644 --- a/client/client.c +++ b/client/client.c @@ -34,8 +34,11 @@ #include "lib/string.h" #include "client/client.h" #include "sysdep/unix/unix.h" +#include "client/reply_codes.h" -#define SERVER_READ_BUF_LEN 4096 +#define SERVER_READ_BUF_LEN 4096 +#define INPUT_BUF_LEN 2048 +#define REFRESH_SYMBOLS_CMD "refresh symbols" /* Name of cli command for retrieve new symbols from daemon */ static char *opt_list = "s:vrl"; static int verbose, restricted, once; @@ -47,12 +50,16 @@ static byte server_read_buf[SERVER_READ_BUF_LEN]; static byte *server_read_pos = server_read_buf; int init = 1; /* During intial sequence */ -int busy = 1; /* Executing BIRD command */ +int busy = 0; /* Executing BIRD command */ int interactive; /* Whether stdin is terminal */ +int welcomed = 0; /* Was welcome message with BIRD version printed out? */ static int num_lines, skip_input; int term_lns, term_cls; +static list symbols; +static uint longest_symbol_len; + /*** Parsing of arguments ***/ @@ -107,7 +114,7 @@ parse_args(int argc, char **argv) tmp += strlen(tmp); *tmp++ = ' '; } - tmp[-1] = 0; + tmp[-1] = 0; /* Put ending null-terminator */ once = 1; interactive = 0; @@ -132,6 +139,12 @@ handle_internal_command(char *cmd) puts("Press `?' for context sensitive help."); return 1; } + if (!strncmp(cmd, REFRESH_SYMBOLS_CMD, sizeof(REFRESH_SYMBOLS_CMD)-1)) + { + retrieve_symbols(); + return 1; + } + return 0; } @@ -172,6 +185,41 @@ submit_command(char *cmd_raw) free(cmd); } +static void +add_to_symbols(int flag, const char *name) +{ + struct cli_symbol *sym = malloc(sizeof(struct cli_symbol)); + sym->flags = flag; + + sym->len = strlen(name); + char *name_ = malloc(sym->len + 1); + memcpy(name_, name, sym->len + 1); + sym->name = name_; + add_tail(&symbols, &sym->n); + + if (longest_symbol_len < sym->len) + longest_symbol_len = sym->len; +} + +void +retrieve_symbols(void) +{ + /* Purge old symbols */ + list *syms = cli_get_symbol_list(); + struct cli_symbol *sym, *next; + WALK_LIST_DELSAFE(sym, next, *syms) + { + rem_node(&sym->n); + free((char *) sym->name); + free(sym); + } + + add_to_symbols(CLI_SF_KW_ALL, "all"); + add_to_symbols(CLI_SF_KW_OFF, "off"); + + submit_server_command(REFRESH_SYMBOLS_CMD); +} + static void init_commands(void) { @@ -198,6 +246,13 @@ init_commands(void) exit(0); } + init_list(&symbols); + longest_symbol_len = 1; /* Be careful, it's used as denominator! */ + + /* In symbol list is a BIRD version for welcome message too */ + if (interactive) + retrieve_symbols(); + input_init(); term_lns = (term_lns > 0) ? term_lns : 25; @@ -262,31 +317,89 @@ server_connect(void) DIE("fcntl"); } +list * +cli_get_symbol_list(void) +{ + return &symbols; +} + +uint +cli_get_symbol_maxlen(void) +{ + return longest_symbol_len; +} + +static void +process_internal_message(int reply_code, const char *name) +{ + u32 flag = 0; + + switch (reply_code) + { + case RC_BIRD_VERSION_NUM: + if (interactive && !welcomed) + { + welcomed = 1; + printf("BIRD %s ready.\n", name); + } + return; + + case RC_NOTIFY: + if (interactive) + retrieve_symbols(); + return; + + /* Symbols */ + case RC_CONSTANT_NAME: flag = CLI_SF_CONSTANT; break; + case RC_VARIABLE_NAME: flag = CLI_SF_VARIABLE; break; + case RC_FILTER_NAME: flag = CLI_SF_FILTER; break; + case RC_FUNCTION_NAME: flag = CLI_SF_FUNCTION; break; + case RC_PROTOCOL_NAME: flag = CLI_SF_PROTOCOL; break; + case RC_TABLE_NAME: flag = CLI_SF_TABLE; break; + case RC_TEMPLATE_NAME: flag = CLI_SF_TEMPLATE; break; + case RC_INTERFACE_NAME: flag = CLI_SF_INTERFACE; break; + default: + printf("Undefined %d: %s", reply_code, name); + return; + } + + if (flag && name && *name) + add_to_symbols(flag, name); +} #define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0) static void server_got_reply(char *x) { - int code; + int code = 0; int len = 0; - if (*x == '+') /* Async reply */ + if (*x == '+') { /* Async reply */ + busy = 1; + input_notify(0); PRINTF(len, ">>> %s\n", x+1); + } else if (x[0] == ' ') /* Continuation */ PRINTF(len, "%s%s\n", verbose ? " " : "", x+1); else if (strlen(x) > 4 && sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 && (x[4] == ' ' || x[4] == '-')) { - if (code) - PRINTF(len, "%s\n", verbose ? x : x+5); + if (code >= 3000 && code < 4000) + { + process_internal_message(code, x+5); + } + else if (code) + { + PRINTF(len, "%s\n", verbose ? x : x+5); + } if (x[4] == ' ') { - busy = 0; - skip_input = 0; - return; + busy = 0; + skip_input = 0; + return; } } else @@ -341,6 +454,51 @@ server_read(void) } } +static int +lastnb(char *str, int i) +{ + while (i--) + if ((str[i] != ' ') && (str[i] != '\t')) + return str[i]; + + return 0; +} + +void +simple_input_read(void) +{ + char buf[INPUT_BUF_LEN]; + + if ((fgets(buf, INPUT_BUF_LEN, stdin) == NULL) || (buf[0] == 0)) + { + if (interactive) + putchar('\n'); + cleanup(); + exit(0); + } + + int l = strlen(buf); + if ((l+1) == INPUT_BUF_LEN) + { + printf("Input too long.\n"); + return; + } + + if (buf[l-1] == '\n') + buf[--l] = '\0'; + + if (l == 0) + return; + + if (lastnb(buf, l) == '?') + { + cmd_help(buf, strlen(buf)); + return; + } + + submit_command(buf); +} + static void select_loop(void) { diff --git a/client/client.h b/client/client.h index f9693def..b4a45cc5 100644 --- a/client/client.h +++ b/client/client.h @@ -6,6 +6,8 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ +#ifndef _BIRD_CLIENT_H_ +#define _BIRD_CLIENT_H_ extern int init, busy, interactive; extern int term_lns, term_cls; @@ -33,7 +35,41 @@ char *cmd_expand(char *cmd); /* client.c */ +/* Client Symbol Flags: Types */ +#define CLI_SF_CONSTANT (1 << 0) +#define CLI_SF_VARIABLE (1 << 1) +#define CLI_SF_FILTER (1 << 2) +#define CLI_SF_FUNCTION (1 << 3) +#define CLI_SF_PROTOCOL (1 << 4) +#define CLI_SF_TABLE (1 << 5) +#define CLI_SF_TEMPLATE (1 << 6) +#define CLI_SF_INTERFACE (1 << 7) + +#define CLI_SF_OPTIONAL (1 << 8) /* This node is optional not mandatory */ +#define CLI_SF_PARAMETER (1 << 9) /* A parameter/word will follow after this node */ + +/* Client Symbol Flags: Keywords */ +#define CLI_SF_KW_ALL (1 << 10) +#define CLI_SF_KW_OFF (1 << 11) + + +struct cli_symbol +{ + node n; + const char *name; + uint len; + u32 flags; /* CLI_SF_* */ +}; + void submit_command(char *cmd_raw); /* die() with system error messages */ #define DIE(x, y...) die(x ": %s", ##y, strerror(errno)) + +void retrieve_symbols(void); +void add_keywords_to_symbols(void); +list *cli_get_symbol_list(void); +uint cli_get_symbol_maxlen(void); +void simple_input_read(void); + +#endif diff --git a/client/commands.c b/client/commands.c index f2134c1b..ae313d7f 100644 --- a/client/commands.c +++ b/client/commands.c @@ -16,10 +16,14 @@ #include "client/client.h" struct cmd_info { + /* use for build cmd tree and cli commands */ char *command; char *args; char *help; + + /* only for build tree */ int is_real_cmd; + u32 flags; /* Mask of (CLI_SF_*) */ }; static struct cmd_info command_table[] = { @@ -27,11 +31,15 @@ static struct cmd_info command_table[] = { }; struct cmd_node { - struct cmd_node *sibling, *son, **plastson; - struct cmd_info *cmd, *help; - int len; - signed char prio; - char token[1]; + struct cmd_node *sibling; + struct cmd_node *son; + struct cmd_node **plastson; /* Helping pointer to son */ + struct cmd_info *cmd; /* Short info */ + struct cmd_info *help; /* Detailed info */ + signed char prio; /* Priority */ + u32 flags; /* Mask of (CLI_SF_*) */ + uint len; /* Length of string in token */ + char token[1]; /* Name of command */ }; static struct cmd_node cmd_root; @@ -78,6 +86,7 @@ cmd_build_tree(void) old->cmd = cmd; else old->help = cmd; + old->flags |= cmd->flags; } } @@ -100,7 +109,7 @@ cmd_display_help(struct cmd_info *c1, struct cmd_info *c2) } static struct cmd_node * -cmd_find_abbrev(struct cmd_node *root, char *cmd, int len, int *pambiguous) +cmd_find_abbrev(struct cmd_node *root, char *cmd, uint len, int *pambiguous) { struct cmd_node *m, *best = NULL, *best2 = NULL; @@ -127,13 +136,21 @@ cmd_find_abbrev(struct cmd_node *root, char *cmd, int len, int *pambiguous) } static void -cmd_list_ambiguous(struct cmd_node *root, char *cmd, int len) +cmd_list_ambiguous(struct cmd_node *root, char *cmd, uint len) { struct cmd_node *m; for(m=root->son; m; m=m->sibling) - if (m->len > len && !memcmp(m->token, cmd, len)) + if (m->len > len && !memcmp(m->token, cmd, len)) cmd_display_help(m->help, m->cmd); + + struct cli_symbol *sym; + list *syms = cli_get_symbol_list(); + WALK_LIST(sym, *syms) + { + if ((sym->flags & root->flags) && sym->len > len && memcmp(sym->name, cmd, len) == 0) + printf("%s\n", sym->name); + } } void @@ -170,14 +187,43 @@ cmd_help(char *cmd, int len) cmd_display_help(m->help, m->cmd); } +/* + * Return length of common prefix of all matches, + * Write common prefix string into buf + */ static int -cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, char *buf) +cmd_merge_match_with_others(int max_common_len, const char *token_name, int token_len, char *buf, int from) +{ + if (max_common_len < 0) + { + /* For a case that we'll have exactly one match */ + strcpy(buf, token_name + from); + max_common_len = token_len - from; + } + else + { + int i = 0; + while (i < max_common_len && i < token_len - from && buf[i] == token_name[from+i]) + i++; + max_common_len = i; + } + return max_common_len; +} + +/* + * Return length of common prefix of all matches, + * Write count of all matches into pcount, + * Write common prefix string into buf + */ +static int +cmd_find_common_match(struct cmd_node *root, char *cmd, uint len, int *pcount, char *buf) { struct cmd_node *m; - int best, best_prio, i; + int max_common_len; + int best_prio; *pcount = 0; - best = -1; + max_common_len = -1; best_prio = -1; for(m=root->son; m; m=m->sibling) { @@ -190,25 +236,31 @@ cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, ch if (best_prio < m->prio) { *pcount = 0; - best = -1; + max_common_len = -1; } + if (max_common_len < 0) + best_prio = m->prio; + (*pcount)++; - if (best < 0) - { - strcpy(buf, m->token + len); - best = m->len - len; - best_prio = m->prio; - } - else - { - i = 0; - while (i < best && i < m->len - len && buf[i] == m->token[len+i]) - i++; - best = i; - } + max_common_len = cmd_merge_match_with_others(max_common_len, m->token, m->len, buf, len); } - return best; + + list *syms = cli_get_symbol_list(); + struct cli_symbol *sym; + WALK_LIST(sym, *syms) + { + if (!(sym->flags & root->flags)) + continue; + + if (sym->len < len || memcmp(sym->name, cmd, len)) + continue; + + (*pcount)++; + max_common_len = cmd_merge_match_with_others(max_common_len, sym->name, sym->len, buf, len); + } + + return max_common_len; } int @@ -217,7 +269,7 @@ cmd_complete(char *cmd, int len, char *buf, int again) char *start = cmd; char *end = cmd + len; char *fin; - struct cmd_node *n, *m; + struct cmd_node *n, *m = NULL; char *z; int ambig, cnt = 0, common; @@ -227,7 +279,7 @@ cmd_complete(char *cmd, int len, char *buf, int again) /* Find the context */ n = &cmd_root; - while (cmd < fin && n->son) + while (cmd < fin) { if (isspace(*cmd)) { @@ -249,12 +301,39 @@ cmd_complete(char *cmd, int len, char *buf, int again) } if (!m) return -1; - n = m; + + /* Try skip a parameter/word */ + if (m->flags & CLI_SF_PARAMETER) + { + z = cmd; + + /* Skip spaces before parameter */ + while (cmd < fin && isspace(*cmd)) + cmd++; + + /* Skip one parameter/word */ + while (cmd < fin && !isspace(*cmd)) + cmd++; + + /* Check ending of parameter */ + if (isspace(*cmd)) + { + if (m->flags & CLI_SF_OPTIONAL) + m = n; + continue; + } + else + cmd = z; + } + + /* Do not enter to optional command nodes */ + if (!(m->flags & CLI_SF_OPTIONAL)) + n = m; } - /* Completion of parameters is not yet supported */ - if (!n->son) - return -1; + /* Enter to the last command node */ + if (m && (m->flags & CLI_SF_PARAMETER)) + n = m; /* We know the context, let's try to complete */ common = cmd_find_common_match(n, fin, end-fin, &cnt, buf); @@ -282,8 +361,8 @@ cmd_complete(char *cmd, int len, char *buf, int again) char * cmd_expand(char *cmd) { - struct cmd_node *n, *m; - char *c, *b, *args; + struct cmd_node *n, *m, *last_real_cmd = NULL; + char *c, *b, *args, *lrc_args = NULL; int ambig; args = c = cmd; @@ -307,14 +386,28 @@ cmd_expand(char *cmd) cmd_list_ambiguous(n, b, c-b); return NULL; } + args = c; n = m; + + if (m->cmd) + { + last_real_cmd = m; + lrc_args = c; + } } - if (!n->cmd) + + if (!n->cmd && !last_real_cmd) { puts("No such command. Press `?' for help."); return NULL; } + + if (last_real_cmd && last_real_cmd != n) + { + n = last_real_cmd; + args = lrc_args; + } b = malloc(strlen(n->cmd->command) + strlen(args) + 1); sprintf(b, "%s%s", n->cmd->command, args); return b; diff --git a/client/reply_codes.h b/client/reply_codes.h new file mode 100644 index 00000000..20b108b5 --- /dev/null +++ b/client/reply_codes.h @@ -0,0 +1,103 @@ +/* + * BIRD Client -- Reply codes for communication between client and daemon + * + * (c) 2016 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_REPLY_CODES_H_ +#define _BIRD_REPLY_CODES_H_ + +/* +Reply codes of BIRD command-line interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +0xxx Action suceessfully completed +1xxx Table entry +2xxx Table heading +3xxx Internal messages +8xxx Run-time error +9xxx Parse-time error + Continuation ++ Spontaneous printout +*/ + +enum reply_code { + RC_OK = 0, + RC_WELCOME = 1, + RC_READING_CONFIGURATION = 2, + RC_RECONFIGURED = 3, + RC_RECONFIGURATION_IN_PROGRESS = 4, + RC_RECONFIGURATION_ALREADY_IN_PROGRESS, QUEUEING = 5, + RC_RECONFIGURATION_IGNORED_SHUTTING_DOWN = 6, + RC_SHUTDOWN_ORDERED = 7, + RC_ALREADY_DISABLED = 8, + RC_DISABLED = 9, + RC_ALREADY_ENABLED = 10, + RC_ENABLED = 11, + RC_RESTARTED = 12, + RC_STATUS_REPORT = 13, + RC_ROUTE_COUNT = 14, + RC_RELOADING = 15, + RC_ACCESS_RESTRICTED = 16, + RC_RECONFIGURATION_ALREADY_IN_PROGRESS_REMOVING_QUEUED_CONFIG = 17, + RC_RECONFIGURATION_CONFIRMED = 18, + RC_NOTHING_TO_DO_CONFIGURE_UNDO_CONFIRM = 19, + RC_CONFIGURATION_OK = 20, + RC_UNDO_REQUESTED = 21, + RC_UNDO_SCHEDULED = 22, + RC_EVALUATION_OF_EXPRESSION = 23, + RC_GRACEFUL_RESTART_STATUS_REPORT = 24, + + RC_BIRD_VERSION = 1000, + RC_INTERFACE_LIST = 1001, + RC_PROTOCOL_LIST = 1002, + RC_INTERFACE_ADDRESS = 1003, + RC_INTERFACE_FLAGS = 1004, + RC_INTERFACE_SUMMARY = 1005, + RC_PROTOCOL_DETAILS = 1006, + RC_ROUTE_LIST = 1007, + RC_ROUTE_DETAILS = 1008, + RC_STATIC_ROUTE_LIST = 1009, + RC_SYMBOL_LIST = 1010, + RC_UPTIME = 1011, + RC_ROUTE_EXTENDED_ATTRIBUTE_LIST = 1012, + RC_SHOW_OSPF_NEIGHBORS = 1013, + RC_SHOW_OSPF = 1014, + RC_SHOW_OSPF_INTERFACE = 1015, + RC_SHOW_OSPF_STATE_TOPOLOGY = 1016, + RC_SHOW_OSPF_LSADB = 1017, + RC_SHOW_MEMORY = 1018, + RC_SHOW_ROA_LIST = 1019, + RC_SHOW_BFD_SESSIONS = 1020, + RC_SHOW_RIP_INTERFACE = 1021, + RC_SHOW_RIP_NEIGHBORS = 1022, + + RC_BIRD_VERSION_NUM = 3000, + RC_TABLE_NAME = 3001, + RC_PROTOCOL_NAME = 3002, + RC_FILTER_NAME = 3003, + RC_FUNCTION_NAME = 3004, + RC_CONSTANT_NAME = 3005, + RC_VARIABLE_NAME = 3006, + RC_TEMPLATE_NAME = 3007, + RC_INTERFACE_NAME = 3008, + RC_NOTIFY = 3009, /* Daemon notifies the client about new symbol set */ + + RC_REPLY_TOO_LONG = 8000, + RC_ROUTE_NOT_FOUND = 8001, + RC_CONFIGURATION_FILE_ERROR = 8002, + RC_NO_PROTOCOLS_MATCH = 8003, + RC_STOPPED_DUE_TO_RECONFIGURATION = 8004, + RC_PROTOCOL_IS_DOWN_CANNOT_DUMP = 8005, + RC_RELOAD_FAILED = 8006, + RC_ACCESS_DENIED = 8007, + RC_EVALUATION_RUNTIME_ERROR = 8008, + + RC_COMMAND_TOO_LONG = 9000, + RC_PARSE_ERROR = 9001, + RC_INVALID_SYMBOL_TYPE = 9002, +}; + +#endif + diff --git a/conf/conf.c b/conf/conf.c index 9978ef23..93409634 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -450,6 +450,7 @@ config_undo(void) } extern void cmd_reconfig_undo_notify(void); +extern void cmd_reconfig_msg(int r); static void config_timeout(struct timer *t UNUSED) @@ -460,6 +461,8 @@ config_timeout(struct timer *t UNUSED) int r = config_undo(); if (r < 0) log(L_ERR "Undo request failed"); + + cmd_reconfig_msg(r); } void diff --git a/conf/conf.h b/conf/conf.h index 4b245e00..428bfbe2 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -161,6 +161,9 @@ char *cf_symbol_class_name(struct symbol *sym); static inline int cf_symbol_is_constant(struct symbol *sym) { return (sym->class & 0xff00) == SYM_CONSTANT; } +static inline int cf_symbol_is_variable(struct symbol *sym) +{ return (sym->class & 0xff00) == SYM_VARIABLE; } + /* Parser */ diff --git a/conf/gen_commands.m4 b/conf/gen_commands.m4 index 3ed21f13..d5779ffe 100644 --- a/conf/gen_commands.m4 +++ b/conf/gen_commands.m4 @@ -7,13 +7,13 @@ m4_divert(-1)m4_dnl # Can be freely distributed and used under the terms of the GNU GPL. # -m4_define(CF_CLI, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$3", "$4", 1 }, +m4_define(CF_CLI, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$3", "$4", 1, $5 }, m4_divert(-1)') -m4_define(CF_CLI_CMD, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 1 }, +m4_define(CF_CLI_CMD, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 1, 0 }, m4_divert(-1)') -m4_define(CF_CLI_HELP, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 0 }, +m4_define(CF_CLI_HELP, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 0, $4 }, m4_divert(-1)') # As we are processing C source, we must access all M4 primitives via diff --git a/nest/cli.c b/nest/cli.c index aceb5770..af37c597 100644 --- a/nest/cli.c +++ b/nest/cli.c @@ -27,7 +27,8 @@ * white space character. * * Reply codes starting with 0 stand for `action successfully completed' messages, - * 1 means `table entry', 8 `runtime error' and 9 `syntax error'. + * 1 means `table entry', 3 means `internal message`, 8 `runtime error' and 9 + * `syntax error'. * * Each CLI session is internally represented by a &cli structure and a * resource pool containing all resources associated with the connection, @@ -67,8 +68,10 @@ #include "nest/cli.h" #include "conf/conf.h" #include "lib/string.h" +#include "client/reply_codes.h" pool *cli_pool; +static list cli_client_list; static byte * cli_alloc_out(cli *c, int size) @@ -228,7 +231,7 @@ cli_written(cli *c) ev_schedule(c->event); } - +/* cli read hooks variables */ static byte *cli_rh_pos; static uint cli_rh_len; static int cli_rh_trick_flag; @@ -304,9 +307,8 @@ cli * cli_new(void *priv) { pool *p = rp_new(cli_pool, "CLI"); - cli *c = mb_alloc(p, sizeof(cli)); + cli *c = mb_allocz(p, sizeof(cli)); - bzero(c, sizeof(cli)); c->pool = p; c->priv = priv; c->event = ev_new(p); @@ -317,6 +319,7 @@ cli_new(void *priv) c->show_pool = lp_new_default(c->pool); c->rx_buf = mb_alloc(c->pool, CLI_RX_BUF_SIZE); ev_schedule(c->event); + add_tail(&cli_client_list, &c->cli_client_node); return c; } @@ -408,6 +411,7 @@ cli_free(cli *c) c->cleanup(c); if (c == cmd_reconfig_stored_cli) cmd_reconfig_stored_cli = NULL; + rem_node(&c->cli_client_node); rfree(c->pool); } @@ -423,4 +427,23 @@ cli_init(void) cli_pool = rp_new(&root_pool, "CLI"); init_list(&cli_log_hooks); cli_log_inited = 1; + init_list(&cli_client_list); +} + +/** + * cli_notify_all_clients - send push notification to all cli clients + * + * Send a notification to all command line clients about some news. + * Client could then send a request for pulling symbols. + */ +void +cli_notify_all_clients(void) +{ + struct cli *cli; + node *n; + WALK_LIST2(cli, n, cli_client_list, cli_client_node) + { + cli_printf(cli, RC_NOTIFY, ""); + cli_write_trigger(cli); + } } diff --git a/nest/cli.h b/nest/cli.h index 6040be91..d882a4bf 100644 --- a/nest/cli.h +++ b/nest/cli.h @@ -27,6 +27,7 @@ struct cli_out { typedef struct cli { node n; /* Node in list of all log hooks */ + node cli_client_node; /* Node in list of all cli clients */ pool *pool; void *priv; /* Private to sysdep layer */ byte *rx_buf, *rx_pos, *rx_aux; /* sysdep */ @@ -66,6 +67,7 @@ void cli_free(cli *); void cli_kick(cli *); void cli_written(cli *); void cli_echo(uint class, byte *msg); +void cli_notify_all_clients(void); static inline int cli_access_restricted(void) { diff --git a/nest/cmds.c b/nest/cmds.c index f0a14425..19b10cae 100644 --- a/nest/cmds.c +++ b/nest/cmds.c @@ -15,6 +15,8 @@ #include "lib/string.h" #include "lib/resource.h" #include "filter/filter.h" +#include "client/reply_codes.h" +#include "nest/iface.h" extern int shutting_down; extern int configuring; @@ -64,6 +66,56 @@ cmd_show_symbols(struct sym_show_data *sd) } } +static int +get_cli_code_for_sym(struct symbol *sym) +{ + if (cf_symbol_is_constant(sym)) + return RC_CONSTANT_NAME; + + if (cf_symbol_is_variable(sym)) + return RC_VARIABLE_NAME; + + switch (sym->class & 0xff) + { + case SYM_PROTO: return RC_PROTOCOL_NAME; + case SYM_TEMPLATE: return RC_TEMPLATE_NAME; + case SYM_FUNCTION: return RC_FUNCTION_NAME; + case SYM_FILTER: return RC_FILTER_NAME; + case SYM_TABLE: return RC_TABLE_NAME; + default: + log(L_ERR "Undefined class %d of %s", sym->class, sym->name); + } + return 0; +} + +/** + * cmd_send_symbols - send all symbols for auto-completion interactive CLI + * + * This function sends all known symbols for auto-completion interactive BIRD's + * CLI. The first symbol is version of BIRD. + */ +void +cmd_send_symbols(void) +{ + int code, pos = 0; + struct symbol *sym = NULL; + + cli_msg(RC_BIRD_VERSION_NUM, "%s", BIRD_VERSION); + + while (sym = cf_walk_symbols(config, sym, &pos)) + { + code = get_cli_code_for_sym(sym); + cli_msg(code, "%s", sym->name); + } + + struct iface *i; + WALK_LIST(i, iface_list) + if (!(i->flags & IF_SHUTDOWN)) + cli_msg(RC_INTERFACE_NAME, "\"%s\"", i->name); + + cli_msg(0, ""); +} + static void print_size(char *dsc, size_t val) { diff --git a/nest/cmds.h b/nest/cmds.h index 4cf8fb1b..7191166c 100644 --- a/nest/cmds.h +++ b/nest/cmds.h @@ -15,5 +15,6 @@ struct f_inst; void cmd_show_status(void); void cmd_show_symbols(struct sym_show_data *sym); +void cmd_send_symbols(void); void cmd_show_memory(void); void cmd_eval(struct f_inst *expr); diff --git a/nest/config.Y b/nest/config.Y index e672d730..406e346f 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -74,6 +74,7 @@ CF_KEYWORDS(PRIMARY, STATS, COUNT, BY, FOR, COMMANDS, PREEXPORT, NOEXPORT, GENER CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION, SORTED) CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC, CLASS, DSCP) CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, FLUSH, AS) +CF_KEYWORDS(REFRESH) /* For r_args_channel */ CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC) @@ -494,10 +495,10 @@ CF_CLI(SHOW STATUS,,, [[Show router status]]) CF_CLI(SHOW MEMORY,,, [[Show memory usage]]) { cmd_show_memory(); } ; -CF_CLI(SHOW PROTOCOLS, proto_patt2, [ | \"\"], [[Show routing protocols]]) +CF_CLI(SHOW PROTOCOLS, proto_patt2, [ | \"\"], [[Show routing protocols]], CLI_SF_PROTOCOL) { proto_apply_cmd($3, proto_cmd_show, 0, 0); } ; -CF_CLI(SHOW PROTOCOLS ALL, proto_patt2, [ | \"\"], [[Show routing protocol details]]) +CF_CLI(SHOW PROTOCOLS ALL, proto_patt2, [ | \"\"], [[Show routing protocol details]], CLI_SF_PROTOCOL) { proto_apply_cmd($4, proto_cmd_show, 0, 1); } ; optsym: @@ -515,6 +516,20 @@ CF_CLI_HELP(SHOW ROUTE, ..., [[Show routing table]]) CF_CLI(SHOW ROUTE, r_args, [[[|for |for ] [table ] [filter |where ] [all] [primary] [filtered] [(export|preexport|noexport)

] [protocol

] [stats|count]]], [[Show routing table]]) { rt_show($3); } ; +CF_CLI_HELP(SHOW ROUTE FOR, | ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) +CF_CLI_HELP(SHOW ROUTE TABLE, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_TABLE) +CF_CLI_HELP(SHOW ROUTE FILTER, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_FILTER) +CF_CLI_HELP(SHOW ROUTE WHERE, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) +CF_CLI_HELP(SHOW ROUTE ALL, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW ROUTE PRIMARY, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW ROUTE FILTRED, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW ROUTE EXPORT, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_PROTOCOL) +CF_CLI_HELP(SHOW ROUTE PREEXPORT, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_PROTOCOL) +CF_CLI_HELP(SHOW ROUTE NOEXPORT, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_PROTOCOL) +CF_CLI_HELP(SHOW ROUTE PROTOCOL, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER | CLI_SF_PROTOCOL) +CF_CLI_HELP(SHOW ROUTE STATS, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW ROUTE COUNT, ...,, CLI_SF_OPTIONAL) + r_args: /* empty */ { $$ = cfg_allocz(sizeof(struct rt_show_data)); @@ -671,9 +686,13 @@ r_args_channel: ; CF_CLI_HELP(SHOW SYMBOLS, ..., [[Show all known symbolic names]]) -CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|], [[Show all known symbolic names]]) +CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|], [[Show all known symbolic names]], ~CLI_SF_OPTIONAL) { cmd_show_symbols($3); } ; +CF_CLI_HELP(REFRESH, symbols, [[Check out new symbols from daemon for autocomplete in BIRD Client]]) +CF_CLI(REFRESH SYMBOLS,,, [[Check out new symbols from daemon for autocomplete in BIRD Client]]) +{ cmd_send_symbols(); } ; + sym_args: /* empty */ { $$ = cfg_allocz(sizeof(struct sym_show_data)); @@ -705,11 +724,11 @@ CF_CLI(DUMP ROUTES,,, [[Dump routing table]]) CF_CLI(DUMP PROTOCOLS,,, [[Dump protocol information]]) { protos_dump_all(); cli_msg(0, ""); } ; -CF_CLI(EVAL, term, , [[Evaluate an expression]]) +CF_CLI(EVAL, term, , [[Evaluate an expression]], CLI_SF_CONSTANT | CLI_SF_VARIABLE) { cmd_eval($2); } ; CF_CLI_HELP(ECHO, ..., [[Control echoing of log messages]]) -CF_CLI(ECHO, echo_mask echo_size, (all | off | { debug|trace|info|remote|warning|error|auth [, ...] }) [], [[Control echoing of log messages]]) { +CF_CLI(ECHO, echo_mask echo_size, (all | off | { debug | trace | info | remote | warning | error | auth }) [], [[Control echoing of log messages]], CLI_SF_KW_ALL | CLI_SF_KW_OFF) { cli_set_log_echo(this_cli, $2, $3); cli_msg(0, ""); } ; @@ -728,25 +747,25 @@ echo_size: } ; -CF_CLI(DISABLE, proto_patt, | \"\" | all, [[Disable protocol]]) +CF_CLI(DISABLE, proto_patt, | \"\" | all, [[Disable protocol]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($2, proto_cmd_disable, 1, 0); } ; -CF_CLI(ENABLE, proto_patt, | \"\" | all, [[Enable protocol]]) +CF_CLI(ENABLE, proto_patt, | \"\" | all, [[Enable protocol]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($2, proto_cmd_enable, 1, 0); } ; -CF_CLI(RESTART, proto_patt, | \"\" | all, [[Restart protocol]]) +CF_CLI(RESTART, proto_patt, | \"\" | all, [[Restart protocol]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($2, proto_cmd_restart, 1, 0); } ; -CF_CLI(RELOAD, proto_patt, | \"\" | all, [[Reload protocol]]) +CF_CLI(RELOAD, proto_patt, | \"\" | all, [[Reload protocol]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($2, proto_cmd_reload, 1, CMD_RELOAD); } ; -CF_CLI(RELOAD IN, proto_patt, | \"\" | all, [[Reload protocol (just imported routes)]]) +CF_CLI(RELOAD IN, proto_patt, | \"\" | all, [[Reload protocol (just imported routes)]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($3, proto_cmd_reload, 1, CMD_RELOAD_IN); } ; -CF_CLI(RELOAD OUT, proto_patt, | \"\" | all, [[Reload protocol (just exported routes)]]) +CF_CLI(RELOAD OUT, proto_patt, | \"\" | all, [[Reload protocol (just exported routes)]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL) { proto_apply_cmd($3, proto_cmd_reload, 1, CMD_RELOAD_OUT); } ; CF_CLI_HELP(DEBUG, ..., [[Control protocol debugging via BIRD logs]]) -CF_CLI(DEBUG, proto_patt debug_mask, ( | \"\" | all) (all | off | { states|routes|filters|interfaces|events|packets [, ...] }), [[Control protocol debugging via BIRD logs]]) +CF_CLI(DEBUG, proto_patt debug_mask, ( | \"\" | all) (all | off | { states | routes | filters | interfaces | events | packets }), [[Control protocol debugging via BIRD logs]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL | CLI_SF_KW_OFF | CLI_SF_PARAMETER) { proto_apply_cmd($2, proto_cmd_debug, 1, $3); } ; CF_CLI_HELP(MRTDUMP, ..., [[Control protocol debugging via MRTdump files]]) -CF_CLI(MRTDUMP, proto_patt mrtdump_mask, ( | \"\" | all) (all | off | { states|messages [, ...] }), [[Control protocol debugging via MRTdump format]]) +CF_CLI(MRTDUMP, proto_patt mrtdump_mask, ( | \"\" | all) (all | off | { states | messages }), [[Control protocol debugging via MRTdump format]], CLI_SF_PROTOCOL | CLI_SF_KW_ALL | CLI_SF_KW_OFF | CLI_SF_PARAMETER) { proto_apply_cmd($2, proto_cmd_mrtdump, 1, $3); } ; CF_CLI(RESTRICT,,,[[Restrict current CLI session to safe commands]]) diff --git a/nest/iface.c b/nest/iface.c index 00af5052..2c89e31e 100644 --- a/nest/iface.c +++ b/nest/iface.c @@ -206,27 +206,32 @@ if_notify_change(unsigned c, struct iface *i) #endif if (c & IF_CHANGE_DOWN) + { neigh_if_down(i); - if (c & IF_CHANGE_DOWN) WALK_LIST(a, i->addrs) { a->flags = (i->flags & ~IA_FLAGS) | (a->flags & IA_FLAGS); ifa_notify_change_(IF_CHANGE_DOWN, a); } + cli_notify_all_clients(); + } + WALK_LIST(p, proto_list) if_send_notify(p, c, i); if (c & IF_CHANGE_UP) + { WALK_LIST(a, i->addrs) { a->flags = (i->flags & ~IA_FLAGS) | (a->flags & IA_FLAGS); ifa_notify_change_(IF_CHANGE_UP, a); } - if (c & IF_CHANGE_UP) neigh_if_up(i); + cli_notify_all_clients(); + } if ((c & (IF_CHANGE_UP | IF_CHANGE_DOWN | IF_CHANGE_LINK)) == IF_CHANGE_LINK) neigh_if_link(i); diff --git a/proto/ospf/config.Y b/proto/ospf/config.Y index 9cfc70a9..d55d9607 100644 --- a/proto/ospf/config.Y +++ b/proto/ospf/config.Y @@ -431,13 +431,13 @@ CF_ADDTO(dynamic_attr, OSPF_TAG { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEM CF_ADDTO(dynamic_attr, OSPF_ROUTER_ID { $$ = f_new_dynamic_attr(EAF_TYPE_ROUTER_ID | EAF_TEMP, T_QUAD, EA_OSPF_ROUTER_ID); }) CF_CLI_HELP(SHOW OSPF, ..., [[Show information about OSPF protocol]]); -CF_CLI(SHOW OSPF, optsym, [], [[Show information about OSPF protocol XXX]]) +CF_CLI(SHOW OSPF, optsym, [], [[Show information about OSPF protocol XXX]], CLI_SF_PROTOCOL) { ospf_sh(proto_get_named($3, &proto_ospf)); }; -CF_CLI(SHOW OSPF NEIGHBORS, optsym opttext, [] [\"\"], [[Show information about OSPF neighbors]]) +CF_CLI(SHOW OSPF NEIGHBORS, optsym opttext, [] [\"\"], [[Show information about OSPF neighbors]], CLI_SF_PROTOCOL | CLI_SF_INTERFACE | CLI_SF_PARAMETER) { ospf_sh_neigh(proto_get_named($4, &proto_ospf), $5); }; -CF_CLI(SHOW OSPF INTERFACE, optsym opttext, [] [\"\"], [[Show information about interface]]) +CF_CLI(SHOW OSPF INTERFACE, optsym opttext, [] [\"\"], [[Show information about interface]], CLI_SF_PROTOCOL | CLI_SF_INTERFACE | CLI_SF_PARAMETER) { ospf_sh_iface(proto_get_named($4, &proto_ospf), $5); }; CF_CLI_HELP(SHOW OSPF TOPOLOGY, [all] [], [[Show information about OSPF network topology]]) @@ -445,20 +445,27 @@ CF_CLI_HELP(SHOW OSPF TOPOLOGY, [all] [], [[Show information about OSPF ne CF_CLI(SHOW OSPF TOPOLOGY, optsym opttext, [], [[Show information about reachable OSPF network topology]]) { ospf_sh_state(proto_get_named($4, &proto_ospf), 0, 1); }; -CF_CLI(SHOW OSPF TOPOLOGY ALL, optsym opttext, [], [[Show information about all OSPF network topology]]) +CF_CLI(SHOW OSPF TOPOLOGY ALL, optsym opttext, [], [[Show information about all OSPF network topology]], CLI_SF_PROTOCOL) { ospf_sh_state(proto_get_named($5, &proto_ospf), 0, 0); }; CF_CLI_HELP(SHOW OSPF STATE, [all] [], [[Show information about OSPF network state]]) -CF_CLI(SHOW OSPF STATE, optsym opttext, [], [[Show information about reachable OSPF network state]]) +CF_CLI(SHOW OSPF STATE, optsym opttext, [], [[Show information about reachable OSPF network state]], CLI_SF_PROTOCOL) { ospf_sh_state(proto_get_named($4, &proto_ospf), 1, 1); }; -CF_CLI(SHOW OSPF STATE ALL, optsym opttext, [], [[Show information about all OSPF network state]]) +CF_CLI(SHOW OSPF STATE ALL, optsym opttext, [], [[Show information about all OSPF network state]], CLI_SF_PROTOCOL) { ospf_sh_state(proto_get_named($5, &proto_ospf), 1, 0); }; -CF_CLI_HELP(SHOW OSPF LSADB, ..., [[Show content of OSPF LSA database]]); -CF_CLI(SHOW OSPF LSADB, lsadb_args, [global | area | link] [type ] [lsid ] [self | router ] [], [[Show content of OSPF LSA database]]) +CF_CLI_HELP(SHOW OSPF LSADB, ..., [[Show content of OSPF LSA database]]) +CF_CLI(SHOW OSPF LSADB, lsadb_args, [global | area | link] [type ] [lsid ] [self | router ] [], [[Show content of OSPF LSA database]], CLI_SF_PROTOCOL) { ospf_sh_lsadb($4); }; +CF_CLI_HELP(SHOW OSPF LSADB GLOBAL, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW OSPF LSADB AREA, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) +CF_CLI_HELP(SHOW OSPF LSADB LINK, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW OSPF LSADB TYPE, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) +CF_CLI_HELP(SHOW OSPF LSADB LSID, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) +CF_CLI_HELP(SHOW OSPF LSADB SELF, ...,, CLI_SF_OPTIONAL) +CF_CLI_HELP(SHOW OSPF LSADB ROUTER, ...,, CLI_SF_OPTIONAL | CLI_SF_PARAMETER) lsadb_args: /* empty */ { diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index c1b92b7e..e17e6615 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -35,6 +35,7 @@ #include "nest/locks.h" #include "conf/conf.h" #include "filter/filter.h" +#include "client/reply_codes.h" #include "unix.h" #include "krt.h" @@ -271,12 +272,12 @@ cmd_check_config(char *name) config_free(conf); } -static void +void cmd_reconfig_msg(int r) { switch (r) { - case CONF_DONE: cli_msg( 3, "Reconfigured"); break; + case CONF_DONE: cli_msg( 3, "Reconfigured"); cli_msg(RC_NOTIFY, ""); cli_msg(19, "Nothing to do"); break; case CONF_PROGRESS: cli_msg( 4, "Reconfiguration in progress"); break; case CONF_QUEUED: cli_msg( 5, "Reconfiguration already in progress, queueing new config"); break; case CONF_UNQUEUED: cli_msg(17, "Reconfiguration already in progress, removing queued config"); break;