]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
lldpcli: switch to GNU Readline.
authorVincent Bernat <bernat@luffy.cx>
Sun, 6 Jan 2013 09:47:21 +0000 (10:47 +0100)
committerVincent Bernat <bernat@luffy.cx>
Sun, 6 Jan 2013 10:41:18 +0000 (11:41 +0100)
libedit is not available on old versions of Linux distro. We try to
use Readline API instead which may be compatible with the libedit
version available in BSD.

This means that we have to write our own tokenizer. This also means
that we can make Readline optional.

12 files changed:
.travis.yml
configure.ac
debian/control
m4/ax_lib_readline.m4 [new file with mode: 0644]
m4/libedit.m4 [deleted file]
redhat/lldpd.spec
src/client/Makefile.am
src/client/client.h
src/client/commands.c
src/client/lldpcli.c
src/client/lldpctl.supp
src/client/tokenizer.c [new file with mode: 0644]

index 4026df2dbdd69da7c900bf5e292bfbcf4f419e5a..6b0ecc64cbe0237b3f0e3ceaa672e08e8ccb71ce 100644 (file)
@@ -2,7 +2,7 @@ language: "python"
 install:
   - "sudo apt-get -qq update"
   - "sudo apt-get -y install automake autoconf libtool pkg-config"
-  - "sudo apt-get -y install libsnmp-dev libxml2-dev libjansson-dev libevent-dev libedit-dev check"
+  - "sudo apt-get -y install libsnmp-dev libxml2-dev libjansson-dev libevent-dev libreadline-dev check"
 script: "./autogen.sh && ./configure $LLDPD_CONFIG_ARGS && make && make check && make distcheck && sudo make install"
 env:
   - LLDPD_CONFIG_ARGS=""
index cc0327eff8c72a4f670eab68c406e2ae2504aa2d..50ca5b4c2a4b4af6f61882e6aee65ec7b4d8a161 100644 (file)
@@ -100,8 +100,8 @@ PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [have_check=yes], [have_check=no])
 # Libevent
 lldp_CHECK_LIBEVENT
 
-# editline
-lldp_CHECK_EDITLINE
+# readline (or something similar)
+AX_LIB_READLINE
 
 #######################
 ### Options
index 6db271c372cac63446c6a3daa936b5247fc59534..6676c6dcc5c4f766b77abcff873858eef9146088 100644 (file)
@@ -4,7 +4,7 @@ Priority: optional
 Maintainer: Vincent Bernat <bernat@debian.org>
 Build-Depends: debhelper (>= 5), cdbs, autotools-dev,
                libsnmp15-dev | libsnmp9-dev | libsnmp-dev,
-              libxml2-dev, libevent-dev, libedit-dev, pkg-config
+              libxml2-dev, libevent-dev, libreadline-dev, pkg-config
 Standards-Version: 3.8.4
 Homepage: https://github.com/vincentbernat/lldpd/wiki
 
diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4
new file mode 100644 (file)
index 0000000..439db7f
--- /dev/null
@@ -0,0 +1,113 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_LIB_READLINE
+#
+# DESCRIPTION
+#
+#   Searches for a readline compatible library. If found, defines
+#   `HAVE_LIBREADLINE'. If the found library has the `add_history' function,
+#   sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the
+#   necessary include files and sets `HAVE_READLINE_H' or
+#   `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or
+#   'HAVE_HISTORY_H' if the corresponding include files exists.
+#
+#   The libraries that may be readline compatible are `libedit',
+#   `libeditline' and `libreadline'. Sometimes we need to link a termcap
+#   library for readline to work, this macro tests these cases too by trying
+#   to link with `libtermcap', `libcurses' or `libncurses' before giving up.
+#
+#   Here is an example of how to use the information provided by this macro
+#   to perform the necessary includes or declarations in a C file:
+#
+#     #ifdef HAVE_LIBREADLINE
+#     #  if defined(HAVE_READLINE_READLINE_H)
+#     #    include <readline/readline.h>
+#     #  elif defined(HAVE_READLINE_H)
+#     #    include <readline.h>
+#     #  else /* !defined(HAVE_READLINE_H) */
+#     extern char *readline ();
+#     #  endif /* !defined(HAVE_READLINE_H) */
+#     char *cmdline = NULL;
+#     #else /* !defined(HAVE_READLINE_READLINE_H) */
+#       /* no readline */
+#     #endif /* HAVE_LIBREADLINE */
+#
+#     #ifdef HAVE_READLINE_HISTORY
+#     #  if defined(HAVE_READLINE_HISTORY_H)
+#     #    include <readline/history.h>
+#     #  elif defined(HAVE_HISTORY_H)
+#     #    include <history.h>
+#     #  else /* !defined(HAVE_HISTORY_H) */
+#     extern void add_history ();
+#     extern int write_history ();
+#     extern int read_history ();
+#     #  endif /* defined(HAVE_READLINE_HISTORY_H) */
+#       /* no history */
+#     #endif /* HAVE_READLINE_HISTORY */
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Ville Laurikari <vl@iki.fi>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 6
+
+AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE])
+AC_DEFUN([AX_LIB_READLINE], [
+  AC_CACHE_CHECK([for a readline compatible library],
+                 ax_cv_lib_readline, [
+    _save_LIBS="$LIBS"
+    for readline_lib in readline edit editline; do
+      for termcap_lib in "" termcap curses ncurses; do
+        if test -z "$termcap_lib"; then
+          TRY_LIB="-l$readline_lib"
+        else
+          TRY_LIB="-l$readline_lib -l$termcap_lib"
+        fi
+        LIBS="$ORIG_LIBS $TRY_LIB"
+        AC_TRY_LINK_FUNC(readline, ax_cv_lib_readline="$TRY_LIB")
+        if test -n "$ax_cv_lib_readline"; then
+          break
+        fi
+      done
+      if test -n "$ax_cv_lib_readline"; then
+        break
+      fi
+    done
+    if test -z "$ax_cv_lib_readline"; then
+      ax_cv_lib_readline="no"
+    fi
+    LIBS="$_save_LIBS"
+  ])
+
+  if test "$ax_cv_lib_readline" != "no"; then
+    READLINE_LIBS="$ax_cv_lib_readline"
+    AC_SUBST(READLINE_LIBS)
+
+    _save_LIBS="$LIBS"
+    LIBS="$LIBS $READLINE_LIBS"
+    AC_DEFINE(HAVE_LIBREADLINE, 1,
+              [Define if you have a readline compatible library])
+    AC_CHECK_HEADERS(readline.h readline/readline.h)
+    AC_CACHE_CHECK([whether readline supports history],
+                   ax_cv_lib_readline_history, [
+      ax_cv_lib_readline_history="no"
+      AC_TRY_LINK_FUNC(add_history, ax_cv_lib_readline_history="yes")
+    ])
+    if test "$ax_cv_lib_readline_history" = "yes"; then
+      AC_DEFINE(HAVE_READLINE_HISTORY, 1,
+                [Define if your readline library has \`add_history'])
+      AC_CHECK_HEADERS(history.h readline/history.h)
+    fi
+
+    LIBS="$_save_LIBS"
+  fi
+])dnl
diff --git a/m4/libedit.m4 b/m4/libedit.m4
deleted file mode 100644 (file)
index 10d67fb..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# lldp_CHECK_EDITLINE
-#
-
-AC_DEFUN([lldp_CHECK_EDITLINE], [
-  _save_LIBS="$LIBS"
-  _save_CFLAGS="$CFLAGS"
-
-  # First, try with pkg-config
-  PKG_CHECK_MODULES([EDITLINE], [libedit >= 2.9], [], [
-    # Nothing appropriate. Maybe it is installed anyway.
-    AC_CHECK_HEADER([histedit.h], [],
-      [AC_MSG_ERROR([*** unable to find editline/libedit])])
-    EDITLINE_CFLAGS=""
-    EDITLINE_LIBS="-ledit -lcurses"
-  ])
-
-  # Check if everything works as expected
-  LIBS="$LIBS $EDITLINE_LIBS"
-  CFLAGS="$CFLAGS $EDITLINE_CFLAGS"
-  AC_MSG_CHECKING([if libedit version is compatible])
-  AC_COMPILE_IFELSE(
-    [AC_LANG_PROGRAM([[
-@%:@include <stdlib.h>
-@%:@include <histedit.h>
-    ]], [[
-int i = H_SETSIZE; (void)i;
-el_init("", NULL, NULL, NULL);
-exit(0);
-    ]])],
-    [ AC_MSG_RESULT([yes]) ],
-    [ AC_MSG_RESULT([no])
-      AC_MSG_ERROR([*** libedit does not work as expected])])])
-
-  LIBS="$_save_LIBS"
-  CFLAGS="$_save_CFLAGS"
-
-  AC_SUBST([EDITLINE_CFLAGS])
-  AC_SUBST([EDITLINE_LIBS])
-])
index d4d836e99be045b2affa80fe26f13344e96a7e7d..737777ac07fff39b14296882bfdd567c6daf4662 100644 (file)
@@ -38,7 +38,7 @@ Source1: lldpd.init%{?suse_version:.suse}
 Source2: lldpd.sysconfig
 
 BuildRequires: pkgconfig
-BuildRequires: libedit-devel
+BuildRequires: readline-devel
 %if %{with snmp}
 BuildRequires: net-snmp-devel
 BuildRequires: openssl-devel
index 590c522007680232c7021cdc28209dc0e850ef21..f7eb1bc9d36fcd595f11a6c8b0ced20580b55bb0 100644 (file)
@@ -10,13 +10,13 @@ uninstall-local:
 
 lldpcli_SOURCES  = client.h lldpcli.c display.c actions.c \
        commands.c show.c \
-       misc.c \
+       misc.c tokenizer.c \
        writer.h text_writer.c kv_writer.c
 lldpcli_LDADD    = \
        $(top_builddir)/src/libcommon-daemon-client.la \
        $(top_builddir)/src/lib/liblldpctl.la \
-       @EDITLINE_LIBS@
-lldpcli_CFLAGS   = $(AM_CFLAGS) @EDITLINE_CFLAGS@
+       @READLINE_LIBS@
+lldpcli_CFLAGS   = $(AM_CFLAGS)
 
 if USE_XML
 lldpcli_SOURCES += xml_writer.c
index 1cf91c14522b9110b927c8e4fdc20820707484a8..524612ffb17f849f7aafd632a5bea55f3d5e6e17 100644 (file)
 #include "../ctl.h"
 #include "writer.h"
 
+/* Readline stuff */
+#ifdef HAVE_LIBREADLINE
+#  if defined(HAVE_READLINE_READLINE_H)
+#    include <readline/readline.h>
+#  elif defined(HAVE_READLINE_H)
+#    include <readline.h>
+#  else
+extern char *readline();
+extern char *rl_line_buffer
+extern int rl_point;
+extern int rl_insert_text(const char*);
+extern int rl_delete_text(int, int);
+extern int rl_ding(void);
+extern int rl_crlf(void);
+extern int rl_on_new_line(void);
+extern int rl_bind_key(int, int(*f)(int, int));
+#  endif
+#endif
+#ifdef HAVE_READLINE_HISTORY
+#  if defined(HAVE_READLINE_HISTORY_H)
+#    include <readline/history.h>
+#  elif defined(HAVE_HISTORY_H)
+#    include <history.h>
+#  else
+extern void add_history ();
+#  endif
+#endif
+#undef NEWLINE
+
 /* commands.c */
 #define NEWLINE "<CR>"
 struct cmd_node;
@@ -49,7 +78,7 @@ int cmdenv_pop(struct cmd_env*, int);
 int commands_execute(struct lldpctl_conn_t *, struct writer *,
     struct cmd_node *, int argc, const char **argv);
 char *commands_complete(struct cmd_node *, int argc, const char **argv,
-    int cursorc, int cursoro, int all);
+    int all);
 /* helpers */
 int cmd_check_no_env(struct cmd_env *, void *);
 int cmd_check_env(struct cmd_env *, void *);
@@ -89,4 +118,8 @@ void register_commands_watch(struct cmd_node *);
 /* actions.c */
 void register_commands_configure(struct cmd_node *);
 
+/* tokenizer.c */
+int tokenize_line(const char*, int*, char***);
+void tokenize_free(int, char**);
+
 #endif
index 7eee64a6a2b67cf212aa2e8b4855ece86c6920d3..366e2cf6b32939e4840d39c704308c309d445e7a 100644 (file)
@@ -298,8 +298,6 @@ struct candidate_word {
  * @param root    Root node we want to start from.
  * @param argc    Number of arguments.
  * @param argv    Array of arguments.
- * @param cursorc On which argument the cursor is. -1 if no completion is required.
- * @param cursoro On which offset the cursor is. -1 if no completion is required.
  * @param word    Completed word. Or NULL when no completion is required.
  * @param all     When completing, display possible completions even if only one choice is possible.
  * @return 0 on success, -1 otherwise.
@@ -307,13 +305,13 @@ struct candidate_word {
 static int
 _commands_execute(struct lldpctl_conn_t *conn, struct writer *w,
     struct cmd_node *root, int argc, const char **argv,
-    int cursorc, int cursoro, char **word, int all)
+    char **word, int all)
 {
-       int n, rc = 0, completion = (cursorc != -1 && cursoro != -1 && word != NULL);
+       int n, rc = 0, completion = (word != NULL);
        struct cmd_env env = {
                .elements = TAILQ_HEAD_INITIALIZER(env.elements),
                .stack = TAILQ_HEAD_INITIALIZER(env.stack),
-               .argc = completion?cursorc:argc,
+               .argc = argc,
                .argv = argv,
                .argp = 0
        };
@@ -329,8 +327,10 @@ _commands_execute(struct lldpctl_conn_t *conn, struct writer *w,
        while ((current = cmdenv_top(&env))) {
                struct cmd_node *candidate, *best = NULL;
                const char *token = (env.argp < env.argc) ? env.argv[env.argp] :
-                   (env.argp == env.argc && !completion) ? NEWLINE : NULL;
-               if (token == NULL) goto end;
+                   (env.argp == env.argc) ? NEWLINE : NULL;
+               if (token == NULL ||
+                   (completion && env.argp == env.argc - 1))
+                       goto end;
                if (!completion)
                        log_debug("lldpctl", "process argument %02d: `%s`",
                            env.argp, token);
@@ -389,7 +389,7 @@ end:
                        log_warnx("lldpctl", "incomplete command");
                        rc = -1;
                }
-       } else if (rc == 0 && env.argp == env.argc) {
+       } else if (rc == 0 && env.argp == env.argc - 1) {
                /* We need to complete. Let's build the list of candidate words. */
                struct cmd_node *candidate = NULL;
                int maxl = 10;                      /* Max length of a word */
@@ -398,7 +398,8 @@ end:
                current = cmdenv_top(&env);
                TAILQ_FOREACH(candidate, &current->subentries, next) {
                        if ((!candidate->token ||
-                               !strncmp(env.argv[cursorc], candidate->token, cursoro)) &&
+                               !strncmp(env.argv[env.argc - 1], candidate->token,
+                                   strlen(env.argv[env.argc -1 ]))) &&
                            (!candidate->validate ||
                                candidate->validate(&env, candidate->arg) == 1)) {
                                struct candidate_word *cword = malloc(sizeof(struct candidate_word));
@@ -435,7 +436,8 @@ end:
                        /* If the prefix is complete, add a space, otherwise,
                         * just return it as is. */
                        if (!all && strcmp(prefix, NEWLINE) &&
-                           strlen(prefix) > 0 && cursoro < strlen(prefix)) {
+                           strlen(prefix) > 0 &&
+                           strlen(env.argv[env.argc-1]) < strlen(prefix)) {
                                TAILQ_FOREACH(cword, &words, next) {
                                        if (cword->word && !strcmp(prefix, cword->word)) {
                                                prefix[strlen(prefix)] = ' ';
@@ -474,11 +476,11 @@ end:
  */
 char *
 commands_complete(struct cmd_node *root, int argc, const char **argv,
-    int cursorc, int cursoro, int all)
+    int all)
 {
        char *word = NULL;
        if (_commands_execute(NULL, NULL, root, argc, argv,
-               cursorc, cursoro, &word, all) == 0)
+               &word, all) == 0)
                return word;
        return NULL;
 }
@@ -490,7 +492,7 @@ int
 commands_execute(struct lldpctl_conn_t *conn, struct writer *w,
     struct cmd_node *root, int argc, const char **argv)
 {
-       return _commands_execute(conn, w, root, argc, argv, -1, -1, NULL, 0);
+       return _commands_execute(conn, w, root, argc, argv, NULL, 0);
 }
 
 /**
index 92324e88eab28c5aa1468b78e6ba615c60239647..641fe9f8ab9e7c39b704b17a01800003ad548bc8 100644 (file)
@@ -15,6 +15,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+
 #define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,7 +27,6 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <arpa/inet.h>
-#include <histedit.h>
 #include <libgen.h>
 
 #include "client.h"
@@ -64,12 +64,16 @@ is_privileged()
 }
 
 static char*
-prompt(EditLine *el)
+prompt()
 {
+#define CESC "\033"
        int privileged = is_privileged();
-       if (privileged)
-               return "[lldpcli] # ";
-       return "[lldpcli] $ ";
+       if (isatty(STDIN_FILENO)) {
+               if (privileged)
+                       return "[lldpcli] # ";
+               return "[lldpcli] $ ";
+       }
+       return "";
 }
 
 static int must_exit = 0;
@@ -112,50 +116,55 @@ cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
        return 1;
 }
 
-static unsigned char
-_cmd_complete(EditLine *el, int ch, int all)
+static int
+_cmd_complete(int all)
 {
-       int rc = CC_ERROR;
-       Tokenizer *eltok;
-       if ((eltok = tok_init(NULL)) == NULL)
+       char **argv = NULL;
+       int argc = 0;
+       int rc = 1;
+       char *line = malloc(strlen(rl_line_buffer) + 2);
+       if (!line) return -1;
+       strcpy(line, rl_line_buffer);
+       line[rl_point]   = 2;   /* empty character, will force a word */
+       line[rl_point+1] = 0;
+
+       if (tokenize_line(line, &argc, &argv) != 0)
                goto end;
 
-       const LineInfo *li = el_line(el);
-
-       const char **argv;
-       char *compl;
-       int argc, cursorc, cursoro;
-       if (tok_line(eltok, li, &argc, &argv, &cursorc, &cursoro) != 0)
-               goto end;
-       compl = commands_complete(root, argc, argv, cursorc, cursoro, all);
+       char *compl = commands_complete(root, argc, (const char **)argv, all);
        if (compl) {
-               el_deletestr(el, cursoro);
-               if (el_insertstr(el, compl) == -1) {
+               int from = rl_point - strlen(argv[argc-1]);
+               rl_delete_text(from, rl_point);
+               rl_point = from;
+               if (rl_insert_text(compl) < 0) {
                        free(compl);
                        goto end;
                }
                free(compl);
-               rc = CC_REDISPLAY;
+               rc = 0;
                goto end;
        }
-       /* No completion or several completion available. We beep. */
-       el_beep(el);
-       rc = CC_REDISPLAY;
+       /* No completion or several completion available. */
+       if (!all) rl_ding();
+       rl_crlf();
+       rl_on_new_line();
+       rc = 0;
 end:
-       if (eltok) tok_end(eltok);
+       free(line);
+       tokenize_free(argc, argv);
        return rc;
 }
 
-static unsigned char
-cmd_complete(EditLine *el, int ch)
+static int
+cmd_complete(int count, int ch)
 {
-       return _cmd_complete(el, ch, 0);
+       return _cmd_complete(0);
 }
 
-static unsigned char
-cmd_help(EditLine *el, int ch)
+static int
+cmd_help(int count, int ch)
 {
-       return _cmd_complete(el, ch, 1);
+       return _cmd_complete(1);
 }
 
 static struct cmd_node*
@@ -200,11 +209,6 @@ main(int argc, char *argv[])
        lldpctl_conn_t *conn = NULL;
        struct writer *w;
 
-       EditLine  *el = NULL;
-       History   *elhistory = NULL;
-       HistEvent  elhistev;
-       Tokenizer *eltok = NULL;
-
        char *interfaces = NULL;
 
        /* Get and parse command line options */
@@ -244,51 +248,15 @@ main(int argc, char *argv[])
                        free(prev);
                }
                must_exit = 1;
-               goto skipeditline;
-       } else {
-               if (optind < argc) {
-                       /* More arguments! */
-                       must_exit = 1;
-               }
-       }
-
-       /* Init editline */
-       log_debug("lldpctl", "init editline");
-       el = el_init("lldpctl", stdin, stdout, stderr);
-       if (el == NULL) {
-               log_warnx("lldpctl", "unable to setup editline");
-               goto end;
-       }
-       el_set(el, EL_PROMPT, prompt);
-       el_set(el, EL_SIGNAL, 1);
-       el_set(el, EL_EDITOR, "emacs");
-       /* If on a TTY, setup completion */
-       if (isatty(STDERR_FILENO)) {
-               el_set(el, EL_ADDFN, "command_complete",
-                   "Execute completion", cmd_complete);
-               el_set(el, EL_ADDFN, "command_help",
-                   "Show completion", cmd_help);
-               el_set(el, EL_BIND, "^I", "command_complete", NULL);
-               el_set(el, EL_BIND, "?", "command_help", NULL);
-       }
-
-       /* Init history */
-       elhistory = history_init();
-       if (elhistory == NULL) {
-               log_warnx("lldpctl", "unable to enable history");
+       } else if (optind < argc) {
+               /* More arguments! */
+               must_exit = 1;
        } else {
-               history(elhistory, &elhistev, H_SETSIZE, 800);
-               el_set(el, EL_HIST, history, elhistory);
+               /* Shell session */
+               rl_bind_key('?', cmd_help);
+               rl_bind_key('\t', cmd_complete);
        }
 
-       /* Init tokenizer */
-       eltok = tok_init(NULL);
-       if (eltok == NULL) {
-               log_warnx("lldpctl", "unable to initialize tokenizer");
-               goto end;
-       }
-
-skipeditline:
        /* Make a connection */
        log_debug("lldpctl", "connect to lldpd");
        conn = lldpctl_new(NULL, NULL, NULL);
@@ -297,34 +265,25 @@ skipeditline:
 
        do {
                const char *line;
-               const char **cargv;
-               int count, n, cargc;
-
+               char **cargv;
+               int n, cargc;
                if (!is_lldpctl(NULL) && (optind >= argc)) {
-                       /* Read a new line. */
-                       line = el_gets(el, &count);
-                       if (line == NULL) break;
+                       line = readline(prompt());
+                       if (line == NULL) break; /* EOF */
 
                        /* Tokenize it */
                        log_debug("lldpctl", "tokenize command line");
-                       n = tok_str(eltok, line, &cargc, &cargv);
+                       n = tokenize_line(line, &cargc, &cargv);
                        switch (n) {
                        case -1:
                                log_warnx("lldpctl", "internal error while tokenizing");
                                goto end;
                        case 1:
-                       case 2:
-                       case 3:
-                               /* TODO: handle multiline statements */
                                log_warnx("lldpctl", "unmatched quotes");
-                               tok_reset(eltok);
-                               continue;
-                       }
-                       if (cargc == 0) {
-                               tok_reset(eltok);
                                continue;
                        }
-                       if (elhistory) history(elhistory, &elhistev, H_ENTER, line);
+                       if (cargc == 0) continue;
+                       add_history(line);
                }
 
                /* Init output formatter */
@@ -340,33 +299,31 @@ skipeditline:
 
                if (is_lldpctl(NULL)) {
                        if (!interfaces) {
-                               cargv = (const char*[]){ "show", "neighbors", "details" };
+                               cargv = (char*[]){ "show", "neighbors", "details" };
                                cargc = 3;
                        } else {
-                               cargv = (const char*[]){ "show", "neighbors", "ports", interfaces, "details" };
+                               cargv = (char*[]){ "show", "neighbors", "ports", interfaces, "details" };
                                cargc = 5;
                        }
                } else if (optind < argc) {
-                       cargv = (const char **)argv;
+                       cargv = argv;
                        cargv = &cargv[optind];
                        cargc = argc - optind;
                }
 
                /* Execute command */
                if (commands_execute(conn, w,
-                       root, cargc, cargv) != 0)
+                       root, cargc, (const char **)cargv) != 0)
                        log_info("lldpctl", "an error occurred while executing last command");
                w->finish(w);
 
-               if (eltok) tok_reset(eltok);
+               if (!is_lldpctl(NULL) && optind >= argc)
+                       tokenize_free(cargc, cargv);
        } while (!must_exit);
 
        rc = EXIT_SUCCESS;
 end:
        if (conn) lldpctl_release(conn);
-       if (eltok) tok_end(eltok);
-       if (elhistory) history_end(elhistory);
-       if (el) el_end(el);
        if (root) commands_free(root);
        free(interfaces);
        return rc;
index 201689929cda83acc5d95120ba33b032dbaae077..97804534e4766d03da26cafd6a845d84e1856d01 100644 (file)
    fun:register_commands_dot3pow
    ...
 }
+
+# libreadline has no function to free memory
+{
+   readline-internal-memory
+   Memcheck:Leak
+   fun:malloc
+   ...
+   fun:readline
+   fun:main
+}
diff --git a/src/client/tokenizer.c b/src/client/tokenizer.c
new file mode 100644 (file)
index 0000000..784114c
--- /dev/null
@@ -0,0 +1,114 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "client.h"
+
+#include <string.h>
+/**
+ * Tokenize the given line. We support quoted strings and escaped characters
+ * with backslash.
+ *
+ * @param line Line to tokenize.
+ * @param argv Will get an array of arguments tokenized.
+ * @param argc Will get the number of tokenized arguments.
+ * @return 0 on success, -1 on internal error, 1 on unmatched quotes
+ */
+int
+tokenize_line(const char *line, int *argc, char ***argv)
+{
+       int iargc = 0; char **iargv = NULL;
+       char *ifs     = " \n\t";
+       char *quotes  = "'\"";
+       char *escapes = "\\";
+       char empty = 2;         /* Empty character, will be removed from output
+                                * but will mark a word. */
+
+       /* Escape handle. Also escape quoted characters. */
+       int escaped = 0;
+       int ipos = 0;
+       char quote = 0;
+       char input[2*strlen(line) + 2];
+       memset(input, 0, 2*strlen(line) + 4);
+       for (int pos = 0; line[pos]; pos++) {
+               if (!escaped && strchr(escapes, line[pos]))
+                       escaped = 1;
+               else if (!escaped && strchr(quotes, line[pos]) && !quote) {
+                       input[ipos++] = empty;
+                       input[ipos++] = '!';
+                       quote = line[pos];
+               } else if (!escaped && quote == line[pos])
+                       quote = 0;
+               else {
+                       input[ipos++] = line[pos];
+                       input[ipos++] = (escaped || quote)?'!':' ';
+                       escaped = 0;
+               }
+       }
+       if (escaped || quote) return 1;
+       /* Trick to not have to handle \0 in a special way */
+       input[ipos++] = ifs[0];
+       input[ipos++] = ' ';
+
+       /* Tokenize, we don't have to handle quotes anymore */
+       int wbegin  = -1;      /* Offset of the beginning of the current word */
+
+#define CURRENT (input[2*pos])
+#define ESCAPED (input[2*pos+1] != ' ')
+       for (int pos = 0; CURRENT; pos++) {
+               if (wbegin == -1) {
+                       if (!ESCAPED && strchr(ifs, CURRENT))
+                               /* IFS while not in a word, continue. */
+                               continue;
+                       /* Start a word. */
+                       wbegin = pos;
+                       continue;
+               }
+               if (ESCAPED || !strchr(ifs, CURRENT))
+                       /* Regular character in a word. */
+                       continue;
+
+               /* End of word. */
+               char *word = calloc(1, pos - wbegin + 1);
+               if (!word) goto error;
+               int i,j;
+               for (i = wbegin, j = 0;
+                    i != pos;
+                    i++)
+                       if (input[2*i] != empty) word[j++] = input[2*i];
+               char **nargv = realloc(iargv, sizeof(char*) * (iargc + 1));
+               if (!nargv) goto error;
+               nargv[iargc++] = word;
+               iargv  = nargv;
+               wbegin = -1;
+       }
+
+       *argc = iargc;
+       *argv = iargv;
+       return 0;
+
+error:
+       tokenize_free(iargc, iargv);
+       return -1;
+}
+
+void
+tokenize_free(int argc, char **argv)
+{
+       while (argc) free(argv[--argc]);
+       free(argv);
+}
+