From a5e9f3d26f887deb451a3ea086e52266c117aa0a Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Tue, 23 Apr 2013 02:42:35 +0200 Subject: [PATCH] Restructures birdc and birdcl to merge duplicated code. The BIRD client code is restructured that most of the code (including main function) is shared in client.c, while birdc.c and birdcl.c contain just I/O-specific callbacks. This removes all duplicated code from variant-specific files. --- client/Makefile | 2 +- client/birdc.c | 223 +++++++++++++++++++++++++ client/birdcl.c | 165 ++++++++++++++++++ client/client.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++++ client/client.h | 31 ++-- doc/bird.sgml | 8 +- 6 files changed, 848 insertions(+), 17 deletions(-) create mode 100644 client/birdc.c create mode 100644 client/birdcl.c create mode 100644 client/client.c diff --git a/client/Makefile b/client/Makefile index 8c2f91e0..a1578766 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,4 +1,4 @@ -source=commands.c util.c common.c +source=commands.c util.c client.c root-rel=../ dir-name=client diff --git a/client/birdc.c b/client/birdc.c new file mode 100644 index 00000000..9dd6d9b9 --- /dev/null +++ b/client/birdc.c @@ -0,0 +1,223 @@ +/* + * BIRD Client - Readline variant I/O + * + * (c) 1999--2004 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "nest/bird.h" +#include "lib/resource.h" +#include "lib/string.h" +#include "client/client.h" +#include "sysdep/unix/unix.h" + +static int input_hidden_end; +static int prompt_active; + +/*** Input ***/ + +/* HACK: libreadline internals we need to access */ +extern int _rl_vis_botlin; +extern void _rl_move_vert(int); +extern Function *rl_last_func; + +static void +add_history_dedup(char *cmd) +{ + /* Add history line if it differs from the last one */ + HIST_ENTRY *he = history_get(history_length); + if (!he || strcmp(he->line, cmd)) + add_history(cmd); +} + +static void +input_got_line(char *cmd_buffer) +{ + if (!cmd_buffer) + { + cleanup(); + exit(0); + } + + if (cmd_buffer[0]) + { + add_history_dedup(cmd_buffer); + submit_command(cmd_buffer); + } + + free(cmd_buffer); +} + +void +input_start_list(void) +{ + /* Leave the currently edited line and make space for listing */ + _rl_move_vert(_rl_vis_botlin); +#ifdef HAVE_RL_CRLF + rl_crlf(); +#endif +} + +void +input_stop_list(void) +{ + /* Reprint the currently edited line after listing */ + rl_on_new_line(); + rl_redisplay(); +} + +static int +input_complete(int arg UNUSED, int key UNUSED) +{ + static int complete_flag; + char buf[256]; + + if (rl_last_func != input_complete) + complete_flag = 0; + switch (cmd_complete(rl_line_buffer, rl_point, buf, complete_flag)) + { + case 0: + complete_flag = 1; + break; + case 1: + rl_insert_text(buf); + break; + default: + complete_flag = 1; +#ifdef HAVE_RL_DING + rl_ding(); +#endif + } + return 0; +} + +static int +input_help(int arg, int key UNUSED) +{ + int i, in_string, in_bracket; + + if (arg != 1) + return rl_insert(arg, '?'); + + in_string = in_bracket = 0; + for (i = 0; i < rl_point; i++) + { + + if (rl_line_buffer[i] == '"') + in_string = ! in_string; + else if (! in_string) + { + if (rl_line_buffer[i] == '[') + in_bracket++; + else if (rl_line_buffer[i] == ']') + in_bracket--; + } + } + + /* `?' inside string or path -> insert */ + if (in_string || in_bracket) + return rl_insert(1, '?'); + + rl_begin_undo_group(); /* HACK: We want to display `?' at point position */ + rl_insert_text("?"); + rl_redisplay(); + rl_end_undo_group(); + input_start_list(); + cmd_help(rl_line_buffer, rl_point); + rl_undo_command(1, 0); + input_stop_list(); + return 0; +} + +void +input_init(void) +{ + 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 ? LINES : 25; + term_cls = COLS ? COLS : 80; + + prompt_active = 1; + + // readline library does strange things when stdin is nonblocking. + // if (fcntl(0, F_SETFL, O_NONBLOCK) < 0) + // die("fcntl: %m"); +} + +static void +input_reveal(void) +{ + /* need this, otherwise some lib seems to eat pending output when + the prompt is displayed */ + fflush(stdout); + tcdrain(STDOUT_FILENO); + + rl_end = input_hidden_end; + rl_expand_prompt("bird> "); + rl_forced_update_display(); + + prompt_active = 1; +} + +static void +input_hide(void) +{ + input_hidden_end = rl_end; + rl_end = 0; + rl_expand_prompt(""); + rl_redisplay(); + + prompt_active = 0; +} + +void +input_notify(int prompt) +{ + if (prompt == prompt_active) + return; + + if (prompt) + input_reveal(); + else + input_hide(); +} + +void +input_read(void) +{ + rl_callback_read_char(); +} + +void +more_begin(void) +{ +} + +void +more_end(void) +{ +} + +void +cleanup(void) +{ + if (init) + return; + + input_hide(); + rl_callback_handler_remove(); +} diff --git a/client/birdcl.c b/client/birdcl.c new file mode 100644 index 00000000..c41b046c --- /dev/null +++ b/client/birdcl.c @@ -0,0 +1,165 @@ +/* + * BIRD Client - Light variant I/O + * + * (c) 1999--2004 Martin Mares + * (c) 2013 Tomas Hlavacek + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include +#include +#include + +#include +#include + +#include "nest/bird.h" +#include "lib/resource.h" +#include "lib/string.h" +#include "client/client.h" +#include "sysdep/unix/unix.h" + +#define INPUT_BUF_LEN 2048 + +struct termios tty_save; + +void +input_start_list(void) +{ + /* Empty in non-ncurses version. */ +} + +void +input_stop_list(void) +{ + /* Empty in non-ncurses version. */ +} + +void +input_notify(int prompt) +{ + /* No ncurses -> no status to reveal/hide, print prompt manually. */ + if (!prompt) + return; + + printf("bird> "); + 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); +} + +static struct termios stored_tty; +static int more_active = 0; + +void +more_begin(void) +{ + static struct termios tty; + + tty = stored_tty; + tty.c_lflag &= (~ECHO); + tty.c_lflag &= (~ICANON); + + if (tcsetattr (0, TCSANOW, &tty) < 0) + die("tcsetattr: %m"); + + more_active = 1; +} + +void +more_end(void) +{ + more_active = 0; + + if (tcsetattr (0, TCSANOW, &stored_tty) < 0) + die("tcsetattr: %m"); +} + +static void +sig_handler(int signal) +{ + cleanup(); + exit(0); +} + +void +input_init(void) +{ + if (!interactive) + return; + + if (tcgetattr(0, &stored_tty) < 0) + die("tcgetattr: %m"); + + if (signal(SIGINT, sig_handler) == SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGTERM, sig_handler) == SIG_IGN) + signal(SIGTERM, SIG_IGN); + + struct winsize tws; + if (ioctl(0, TIOCGWINSZ, &tws) == 0) + { + term_lns = tws.ws_row; + term_cls = tws.ws_col; + } + else + { + term_lns = 25; + term_cls = 80; + } +} + +void +cleanup(void) +{ + if (more_active) + more_end(); +} diff --git a/client/client.c b/client/client.c new file mode 100644 index 00000000..61caf38b --- /dev/null +++ b/client/client.c @@ -0,0 +1,436 @@ +/* + * BIRD Client + * + * (c) 1999--2004 Martin Mares + * (c) 2013 Tomas Hlavacek + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: BIRD client + * + * There are two variants of BIRD client: regular and light. regular + * variant depends on readline and ncurses libraries, while light + * variant uses just libc. Most of the code and the main() is common + * for both variants (in client.c file) and just a few functions are + * different (in birdc.c for regular and birdcl.c for light). Two + * binaries are generated by linking common object files like client.o + * (which is compiled from client.c just once) with either birdc.o or + * birdcl.o for each variant. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nest/bird.h" +#include "lib/resource.h" +#include "lib/string.h" +#include "client/client.h" +#include "sysdep/unix/unix.h" + +#define SERVER_READ_BUF_LEN 4096 + +static char *opt_list = "s:vr"; +static int verbose, restricted, once; +static char *init_cmd; + +static char *server_path = PATH_CONTROL_SOCKET; +static int server_fd; +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 interactive; /* Whether stdin is terminal */ + +static int num_lines, skip_input; +int term_lns, term_cls; + + +/*** Parsing of arguments ***/ + +static void +usage(char *name) +{ + fprintf(stderr, "Usage: %s [-s ] [-v] [-r]\n", name); + exit(1); +} + +static void +parse_args(int argc, char **argv) +{ + int c; + + while ((c = getopt(argc, argv, opt_list)) >= 0) + switch (c) + { + case 's': + server_path = optarg; + break; + case 'v': + verbose++; + break; + case 'r': + restricted = 1; + break; + default: + usage(argv[0]); + } + + /* If some arguments are not options, we take it as commands */ + if (optind < argc) + { + char *tmp; + int i; + int len = 0; + + for (i = optind; i < argc; i++) + len += strlen(argv[i]) + 1; + + tmp = init_cmd = malloc(len); + for (i = optind; i < argc; i++) + { + strcpy(tmp, argv[i]); + tmp += strlen(tmp); + *tmp++ = ' '; + } + tmp[-1] = 0; + + once = 1; + interactive = 0; + } +} + + +/*** Input ***/ + +static void server_send(char *cmd); + +static int +handle_internal_command(char *cmd) +{ + if (!strncmp(cmd, "exit", 4) || !strncmp(cmd, "quit", 4)) + { + cleanup(); + exit(0); + } + if (!strncmp(cmd, "help", 4)) + { + puts("Press `?' for context sensitive help."); + return 1; + } + return 0; +} + +static void +submit_server_command(char *cmd) +{ + busy = 1; + num_lines = 2; + server_send(cmd); +} + +void +submit_command(char *cmd_raw) +{ + char *cmd = cmd_expand(cmd_raw); + + if (!cmd) + return; + + if (!handle_internal_command(cmd)) + submit_server_command(cmd); + + free(cmd); +} + +static void +init_commands(void) +{ + if (restricted) + { + submit_server_command("restrict"); + restricted = 0; + return; + } + + if (init_cmd) + { + /* First transition - client received hello from BIRD + and there is waiting initial command */ + submit_server_command(init_cmd); + init_cmd = NULL; + return; + } + + if (once) + { + /* Initial command is finished and we want to exit */ + cleanup(); + exit(0); + } + + input_init(); + init = 0; +} + + +/*** Output ***/ + +void +more(void) +{ + more_begin(); + printf("--More--\015"); + fflush(stdout); + + redo: + switch (getchar()) + { + case ' ': + num_lines = 2; + break; + case '\n': + case '\r': + num_lines--; + break; + case 'q': + skip_input = 1; + break; + default: + goto redo; + } + + printf(" \015"); + fflush(stdout); + more_end(); +} + + +/*** Communication with server ***/ + +static void +server_connect(void) +{ + struct sockaddr_un sa; + + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) + die("Cannot create socket: %m"); + + if (strlen(server_path) >= sizeof(sa.sun_path)) + die("server_connect: path too long"); + + bzero(&sa, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, server_path); + if (connect(server_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) < 0) + die("Unable to connect to server control socket (%s): %m", server_path); + if (fcntl(server_fd, F_SETFL, O_NONBLOCK) < 0) + die("fcntl: %m"); +} + + +#define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0) + +static void +server_got_reply(char *x) +{ + int code; + int len = 0; + + if (*x == '+') /* Async reply */ + 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 (x[4] == ' ') + { + busy = 0; + skip_input = 0; + return; + } + } + else + PRINTF(len, "??? <%s>\n", x); + + if (interactive && busy && !skip_input && !init && (len > 0)) + { + num_lines += (len + term_cls - 1) / term_cls; /* Divide and round up */ + if (num_lines >= term_lns) + more(); + } +} + +static void +server_read(void) +{ + int c; + byte *start, *p; + + redo: + c = read(server_fd, server_read_pos, server_read_buf + sizeof(server_read_buf) - server_read_pos); + if (!c) + die("Connection closed by server."); + if (c < 0) + { + if (errno == EINTR) + goto redo; + else + die("Server read error: %m"); + } + + start = server_read_buf; + p = server_read_pos; + server_read_pos += c; + while (p < server_read_pos) + if (*p++ == '\n') + { + p[-1] = 0; + server_got_reply(start); + start = p; + } + if (start != server_read_buf) + { + int l = server_read_pos - start; + memmove(server_read_buf, start, l); + server_read_pos = server_read_buf + l; + } + else if (server_read_pos == server_read_buf + sizeof(server_read_buf)) + { + strcpy(server_read_buf, "?"); + server_read_pos = server_read_buf + 11; + } +} + +static void +select_loop(void) +{ + int rv; + while (1) + { + if (init && !busy) + init_commands(); + + if (!init) + input_notify(!busy); + + fd_set select_fds; + FD_ZERO(&select_fds); + + FD_SET(server_fd, &select_fds); + if (!busy) + FD_SET(0, &select_fds); + + rv = select(server_fd+1, &select_fds, NULL, NULL, NULL); + if (rv < 0) + { + if (errno == EINTR) + continue; + else + die("select: %m"); + } + + if (FD_ISSET(0, &select_fds)) + { + input_read(); + continue; + } + + if (FD_ISSET(server_fd, &select_fds)) + { + server_read(); + continue; + } + } +} + +static void +wait_for_write(int fd) +{ + while (1) + { + int rv; + fd_set set; + FD_ZERO(&set); + FD_SET(fd, &set); + + rv = select(fd+1, NULL, &set, NULL, NULL); + if (rv < 0) + { + if (errno == EINTR) + continue; + else + die("select: %m"); + } + + if (FD_ISSET(server_fd, &set)) + return; + } +} + +static void +server_send(char *cmd) +{ + int l = strlen(cmd); + byte *z = alloca(l + 1); + + memcpy(z, cmd, l); + z[l++] = '\n'; + while (l) + { + int cnt = write(server_fd, z, l); + + if (cnt < 0) + { + if (errno == EAGAIN) + wait_for_write(server_fd); + else if (errno == EINTR) + continue; + else + die("Server write error: %m"); + } + else + { + l -= cnt; + z += cnt; + } + } +} + + +/* XXXX + + get_term_size(); + + if (tcgetattr(0, &tty_save) != 0) + { + perror("tcgetattr error"); + return(EXIT_FAILURE); + } + } + + */ +int +main(int argc, char **argv) +{ + interactive = isatty(0); + parse_args(argc, argv); + cmd_build_tree(); + server_connect(); + select_loop(); + return 0; +} diff --git a/client/client.h b/client/client.h index 2e4e2ea3..b194a772 100644 --- a/client/client.h +++ b/client/client.h @@ -6,12 +6,23 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ -/* client.c callbacks */ -void cleanup(void); +extern int init, busy, interactive; +extern int term_lns, term_cls; + +/* birdc.c / birdcl.c */ + void input_start_list(void); void input_stop_list(void); -void server_got_reply(char *x); + +void input_init(void); +void input_notify(int prompt); +void input_read(void); + +void more_begin(void); +void more_end(void); + +void cleanup(void); /* commands.c */ @@ -20,16 +31,6 @@ void cmd_help(char *cmd, int len); int cmd_complete(char *cmd, int len, char *buf, int again); char *cmd_expand(char *cmd); -/* common.c */ +/* client.c */ -#define STATE_PROMPT 0 -#define STATE_CMD_SERVER 1 -#define STATE_CMD_USER 2 - -#define SERVER_READ_BUF_LEN 4096 - -int handle_internal_command(char *cmd); -void submit_server_command(char *cmd); -void server_connect(void); -void server_read(void); -void server_send(char *cmd); +void submit_command(char *cmd_raw); diff --git a/doc/bird.sgml b/doc/bird.sgml index e83cf0e1..88d35e49 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -623,7 +623,13 @@ codes along with the messages. You do not necessarily need to use -- the format of communication between BIRD and There is also lightweight variant of BIRD client called +Many commands have the Here is a brief list of supported functions: