Add lightweight client - birdcl

Restructure client/ subdir. Add two different flavors of client.
The full featured birdc client code is in client/birdc/.
The new light client birtcl is in client/birdcl/.
Common sources of both clients are directly in client/.

Rework on-line auto-completion in client/command.c to conditionally turn off
ncurses-specific code.

Add lightweight client without libreadline and ncurses dependencies - birdcl.
The birdcl lacks support of history, on-line auto-completion and there
are different implementations of "more" functionality and help on '?' press.
New client operates in canonical terminal mode (apart from "more" display)
and therefore all commands have to be executed by a return key including help
commands (called by '?' character in the end of the line).
Apart from these limitations the interaction style should be the same as
for the full client - birdc.
Build of birdcl is always on (independent on --enable-client parameter).
This commit is contained in:
Tomas Hlavacek 2013-02-24 23:47:22 +01:00
parent e454916149
commit 8322ecde12
9 changed files with 454 additions and 14 deletions

View file

@ -1,4 +1,4 @@
source=client_full.c commands.c util.c client_common.c source=commands.c util.c client_common.c
root-rel=../ root-rel=../
dir-name=client dir-name=client

5
client/birdc/Makefile Normal file
View file

@ -0,0 +1,5 @@
source=client.c
root-rel=../../
dir-name=client/birdc
include ../../Rules

5
client/birdcl/Makefile Normal file
View file

@ -0,0 +1,5 @@
source=client.c
root-rel=../../
dir-name=client/birdcl
include ../../Rules

416
client/birdcl/client.c Normal file
View file

@ -0,0 +1,416 @@
/*
* BIRD Client
*
* (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 <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.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
static char *opt_list = "s:vr";
static int verbose;
static char *init_cmd;
static int once;
extern char *server_path;
extern int server_fd;
extern int nstate;
extern int num_lines, skip_input, interactive;
static int term_lns=25;
static int term_cls=80;
struct termios tty_save;
void
input_start_list(void)
{
/* Empty in non-ncurses version. */
}
void
input_stop_list(void)
{
/* Empty in non-ncurses version. */
}
/*** Parsing of arguments ***/
static void
usage(void)
{
fprintf(stderr, "Usage: birdc [-s <control-socket>] [-v] [-r]\n");
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':
init_cmd = "restrict";
break;
default:
usage();
}
/* If some arguments are not options, we take it as commands */
if (optind < argc)
{
char *tmp;
int i;
int len = 0;
if (init_cmd)
usage();
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;
}
}
static void
run_init_cmd(void)
{
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 (!init_cmd && once && (nstate == STATE_CMD_USER))
{
/* Initial command is finished and we want to exit */
cleanup();
exit(0);
}
}
/*** Input ***/
static void
got_line(char *cmd_buffer)
{
char *cmd;
if (!cmd_buffer)
{
cleanup();
exit(0);
}
if (cmd_buffer[0])
{
cmd = cmd_expand(cmd_buffer);
if (cmd)
{
if (!handle_internal_command(cmd))
submit_server_command(cmd);
free(cmd);
}
}
free(cmd_buffer);
}
void
cleanup(void)
{
/* No ncurses -> restore terminal state. */
if (interactive)
if (tcsetattr (0, TCSANOW, &tty_save) != 0)
{
perror("tcsetattr error");
exit(EXIT_FAILURE);
}
}
static void
print_prompt(void)
{
/* No ncurses -> no status to reveal/hide, print prompt manually. */
printf("bird> ");
fflush(stdout);
}
static void
term_read(void)
{
char *buf = malloc(INPUT_BUF_LEN);
if (fgets(buf, INPUT_BUF_LEN, stdin) == NULL) {
free(buf);
exit(0);
}
if (buf[strlen(buf)-1] != '\n')
{
printf("Input too long.\n");
free(buf);
return;
}
if (strlen(buf) <= 0)
{
free(buf);
return;
}
buf[strlen(buf)-1] = '\0';
if (!interactive)
{
print_prompt();
printf("%s\n",buf);
}
if (strchr(buf, '?'))
{
printf("\n");
cmd_help(buf, strlen(buf));
free(buf);
return;
}
if (strlen(buf) > 0)
{
got_line(buf); /* buf is free()-ed inside */
}
else
{
free(buf); /* no command, only newline -> no-op */
return;
}
}
/*** Communication with server ***/
void
more(void)
{
struct termios tty;
printf("--More--\015");
fflush(stdout);
if (tcgetattr(0, &tty) != 0)
{
perror("tcgetattr error");
exit(EXIT_FAILURE);
}
tty.c_lflag &= (~ECHO);
tty.c_lflag &= (~ICANON);
if (tcsetattr (0, TCSANOW, &tty) != 0)
{
perror("tcsetattr error");
exit(EXIT_FAILURE);
}
redo:
switch (getchar())
{
case 32:
num_lines = 2;
break;
case 13:
num_lines--;
break;
case '\n':
num_lines--;
break;
case 'q':
skip_input = 1;
break;
default:
goto redo;
}
tty.c_lflag |= ECHO;
tty.c_lflag |= ICANON;
if (tcsetattr (0, TCSANOW, &tty) != 0)
{
perror("tcsetattr error");
exit(EXIT_FAILURE);
}
printf(" \015");
fflush(stdout);
}
static void
get_term_size(void)
{
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;
}
}
#define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0)
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] == ' ')
{
nstate = STATE_CMD_USER;
skip_input = 0;
return;
}
}
else
PRINTF(len, "??? <%s>\n", x);
if (skip_input)
return;
if (interactive && (len > 0))
{
num_lines += (len + term_cls - 1) / term_cls; /* Divide and round up */
if (num_lines >= term_lns)
more();
}
}
static fd_set select_fds;
static void
select_loop(void)
{
int rv;
while (1)
{
FD_ZERO(&select_fds);
if (nstate != STATE_CMD_USER)
FD_SET(server_fd, &select_fds);
if (nstate != STATE_CMD_SERVER)
{
FD_SET(0, &select_fds);
if (interactive)
print_prompt();
}
rv = select(server_fd+1, &select_fds, NULL, NULL, NULL);
if (rv < 0)
{
if (errno == EINTR)
continue;
else
die("select: %m");
}
if (FD_ISSET(server_fd, &select_fds))
{
server_read();
run_init_cmd();
}
if (FD_ISSET(0, &select_fds))
term_read();
}
}
static void
sig_handler(int signal)
{
cleanup();
exit(0);
}
int
main(int argc, char **argv)
{
interactive = isatty(fileno(stdin));
if (interactive)
{
if (signal(SIGINT, sig_handler) == SIG_IGN)
signal(SIGINT, SIG_IGN);
if (signal(SIGHUP, sig_handler) == SIG_IGN)
signal(SIGHUP, SIG_IGN);
if (signal(SIGTERM, sig_handler) == SIG_IGN)
signal(SIGTERM, SIG_IGN);
get_term_size();
if (tcgetattr(0, &tty_save) != 0)
{
perror("tcgetattr error");
return(EXIT_FAILURE);
}
}
parse_args(argc, argv);
cmd_build_tree();
server_connect();
select_loop();
return 0;
}

View file

@ -6,11 +6,12 @@
* Can be freely distributed and used under the terms of the GNU GPL. * Can be freely distributed and used under the terms of the GNU GPL.
*/ */
/* client.c */ /* client.c callbacks */
void cleanup(void); void cleanup(void);
void input_start_list(void); void input_start_list(void);
void input_stop_list(void); void input_stop_list(void);
void server_got_reply(char *x);
/* commands.c */ /* commands.c */

View file

@ -234,7 +234,7 @@ fi
CLIENT= CLIENT=
CLIENT_LIBS= CLIENT_LIBS=
if test "$enable_client" = yes ; then if test "$enable_client" = yes ; then
CLIENT=client CLIENT=birdc
AC_CHECK_LIB(history, add_history, CLIENT_LIBS="-lhistory") AC_CHECK_LIB(history, add_history, CLIENT_LIBS="-lhistory")
AC_CHECK_LIB(ncurses, tgetent, USE_TERMCAP_LIB=-lncurses, AC_CHECK_LIB(ncurses, tgetent, USE_TERMCAP_LIB=-lncurses,
AC_CHECK_LIB(curses, tgetent, USE_TERMCAP_LIB=-lcurses, AC_CHECK_LIB(curses, tgetent, USE_TERMCAP_LIB=-lcurses,

View file

@ -3,29 +3,36 @@
include Rules include Rules
.PHONY: all daemon client subdir depend clean distclean tags docs userdocs progdocs .PHONY: all daemon birdci birdcl subdir depend clean distclean tags docs userdocs progdocs
all: sysdep/paths.h .dep-stamp subdir daemon @CLIENT@ all: sysdep/paths.h .dep-stamp subdir daemon birdcl @CLIENT@
daemon: $(exedir)/bird daemon: $(exedir)/bird
client: $(exedir)/birdc birdc: $(exedir)/birdc
birdcl: $(exedir)/birdcl
bird-dep := $(addsuffix /all.o, $(static-dirs)) conf/all.o lib/birdlib.a bird-dep := $(addsuffix /all.o, $(static-dirs)) conf/all.o lib/birdlib.a
$(bird-dep): sysdep/paths.h .dep-stamp subdir $(bird-dep): sysdep/paths.h .dep-stamp subdir
birdc-dep := client/all.o lib/birdlib.a birdc-dep := client/birdc/all.o client/all.o lib/birdlib.a
$(birdc-dep): sysdep/paths.h .dep-stamp subdir $(birdc-dep): sysdep/paths.h .dep-stamp subdir
birdcl-dep := client/birdcl/all.o client/all.o lib/birdlib.a
$(birdcl-dep): sysdep/paths.h .dep-stamp subdir
depend: sysdep/paths.h .dir-stamp depend: sysdep/paths.h .dir-stamp
set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done
set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done set -e ; for a in $(static-dirs) $(birdcl-dirs) $(birdc-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done
subdir: sysdep/paths.h .dir-stamp .dep-stamp subdir: sysdep/paths.h .dir-stamp .dep-stamp
set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done
set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done set -e ; for a in $(static-dirs) $(birdcl-dirs) $(birdc-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done
$(exedir)/bird: $(bird-dep) $(exedir)/bird: $(bird-dep)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
@ -33,8 +40,11 @@ $(exedir)/bird: $(bird-dep)
$(exedir)/birdc: $(birdc-dep) $(exedir)/birdc: $(birdc-dep)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS)
$(exedir)/birdcl: $(birdcl-dep)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
.dir-stamp: sysdep/paths.h .dir-stamp: sysdep/paths.h
mkdir -p $(static-dirs) $(client-dirs) $(doc-dirs) mkdir -p $(static-dirs) $(birdcl-dirs) $(birdc-dirs) $(doc-dirs)
touch .dir-stamp touch .dir-stamp
.dep-stamp: .dep-stamp:
@ -58,6 +68,7 @@ tags:
install: all install: all
$(INSTALL) -d $(DESTDIR)/$(sbindir) $(DESTDIR)/$(sysconfdir) $(DESTDIR)/@runtimedir@ $(INSTALL) -d $(DESTDIR)/$(sbindir) $(DESTDIR)/$(sysconfdir) $(DESTDIR)/@runtimedir@
$(INSTALL_PROGRAM) -s $(exedir)/bird $(DESTDIR)/$(sbindir)/bird@SUFFIX@ $(INSTALL_PROGRAM) -s $(exedir)/bird $(DESTDIR)/$(sbindir)/bird@SUFFIX@
$(INSTALL_PROGRAM) -s $(exedir)/birdcl $(DESTDIR)/$(sbindir)/birdcl@SUFFIX@
if test -n "@CLIENT@" ; then \ if test -n "@CLIENT@" ; then \
$(INSTALL_PROGRAM) -s $(exedir)/birdc $(DESTDIR)/$(sbindir)/birdc@SUFFIX@ ; \ $(INSTALL_PROGRAM) -s $(exedir)/birdc $(DESTDIR)/$(sbindir)/birdc@SUFFIX@ ; \
fi fi
@ -74,7 +85,7 @@ install-docs:
clean: clean:
find . -name "*.[oa]" -o -name core -o -name depend -o -name "*.html" | xargs rm -f find . -name "*.[oa]" -o -name core -o -name depend -o -name "*.html" | xargs rm -f
rm -f conf/cf-lex.c conf/cf-parse.* conf/commands.h conf/keywords.h rm -f conf/cf-lex.c conf/cf-parse.* conf/commands.h conf/keywords.h
rm -f $(exedir)/bird $(exedir)/birdc $(exedir)/bird.ctl $(exedir)/bird6.ctl .dep-stamp rm -f $(exedir)/bird $(exedir)/birdcl $(exedir)/birdc $(exedir)/bird.ctl $(exedir)/bird6.ctl .dep-stamp
distclean: clean distclean: clean
rm -f config.* configure sysdep/autoconf.h sysdep/paths.h Makefile Rules rm -f config.* configure sysdep/autoconf.h sysdep/paths.h Makefile Rules

View file

@ -11,12 +11,14 @@ static-dirs := nest filter $(addprefix proto/,$(protocols))
static-dir-paths := $(addprefix $(srcdir)/,$(static-dirs)) static-dir-paths := $(addprefix $(srcdir)/,$(static-dirs))
dynamic-dirs := lib conf dynamic-dirs := lib conf
dynamic-dir-paths := $(dynamic-dirs) dynamic-dir-paths := $(dynamic-dirs)
client-dirs := @CLIENT@ birdc-dirs := client client/@CLIENT@
client-dir-paths := $(client-dirs) birdc-dir-paths := $(birdc-dirs)
birdcl-dirs := client client/birdcl
birdcl-dir-paths := $(birdcl-dirs)
doc-dirs := doc doc-dirs := doc
doc-dir-paths := $(doc-dirs) doc-dir-paths := $(doc-dirs)
all-dirs:=$(static-dirs) $(dynamic-dirs) $(client-dirs) $(doc-dirs) all-dirs:=$(static-dirs) $(dynamic-dirs) $(birdc-dirs) $(doc-dirs)
clean-dirs:=$(all-dirs) proto sysdep clean-dirs:=$(all-dirs) proto sysdep
CPPFLAGS=-I$(root-rel) -I$(srcdir) @CPPFLAGS@ CPPFLAGS=-I$(root-rel) -I$(srcdir) @CPPFLAGS@