]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Restructures birdc and birdcl to merge duplicated code.
authorOndrej Zajicek <santiago@crfreenet.org>
Tue, 23 Apr 2013 00:42:35 +0000 (02:42 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Tue, 23 Apr 2013 00:42:35 +0000 (02:42 +0200)
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
client/birdc.c [new file with mode: 0644]
client/birdcl.c [new file with mode: 0644]
client/client.c [new file with mode: 0644]
client/client.h
doc/bird.sgml

index 8c2f91e099f8caf39932010529c36f9dcde6abc7..a157876678c08907c9eb5a0a1d5f6bb9571e985d 100644 (file)
@@ -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 (file)
index 0000000..9dd6d9b
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ *     BIRD Client - Readline variant I/O
+ *
+ *     (c) 1999--2004 Martin Mares <mj@ucw.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <curses.h>
+
+#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 (file)
index 0000000..c41b046
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ *     BIRD Client - Light variant I/O
+ *
+ *     (c) 1999--2004 Martin Mares <mj@ucw.cz>
+ *      (c) 2013 Tomas Hlavacek <tomas.hlavacek@nic.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include <sys/ioctl.h>
+#include <signal.h>
+
+#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 (file)
index 0000000..61caf38
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ *     BIRD Client
+ *
+ *     (c) 1999--2004 Martin Mares <mj@ucw.cz>
+ *     (c) 2013 Tomas Hlavacek <tmshlvck@gmail.com>
+ *
+ *     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 <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+
+#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 <control-socket>] [-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, "?<too-long>");
+      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;
+}
index 2e4e2ea303ba5092a69cf43bd867009af8abe8d5..b194a772634cc99cd8f9fbc9cb38dffd136a9fcc 100644 (file)
@@ -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 */
-
-#define STATE_PROMPT           0
-#define STATE_CMD_SERVER       1
-#define STATE_CMD_USER         2
-
-#define SERVER_READ_BUF_LEN 4096
+/* client.c */
 
-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);
index e83cf0e1f2f379bd2277fb2e54bbcdc7a168d2e8..88d35e49f9e27d8e0d7da7ee1f2cd33f92653738 100644 (file)
@@ -623,7 +623,13 @@ codes along with the messages. You do not necessarily need to use
 -- the format of communication between BIRD and <file/birdc/ is stable
 (see the programmer's documentation).
 
-Many commands have the <m/name/ of the protocol instance as an argument.
+<p>There is also lightweight variant of BIRD client called
+<file/birdcl/, which does not support command line editing and history
+and has minimal dependencies. This is useful for running BIRD in
+resource constrained environments, where Readline library (required
+for regular BIRD client) is not available.
+
+<p>Many commands have the <m/name/ of the protocol instance as an argument.
 This argument can be omitted if there exists only a single instance.
 
 <p>Here is a brief list of supported functions: