* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#define _GNU_SOURCE
+
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
-#include <histedit.h>
#include <libgen.h>
+#include <dirent.h>
+#include <signal.h>
+#include <sys/queue.h>
#include "client.h"
/* Global for completion */
static struct cmd_node *root = NULL;
+const char *ctlname = NULL;
+
+static int
+is_lldpctl(const char *name)
+{
+ static int last_result = -1;
+ if (last_result == -1 && name) {
+ char *basec = strdup(name);
+ if (!basec) return 0;
+ char *bname = basename(basec);
+ last_result = (!strcmp(bname, "lldpctl"));
+ free(basec);
+ }
+ return (last_result == -1)?0:last_result;
+}
static void
usage()
fprintf(stderr, "\n");
fprintf(stderr, "-d Enable more debugging information.\n");
- fprintf(stderr, "-f format Choose output format (plain, keyvalue or xml).\n");
+ fprintf(stderr, "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
+ fprintf(stderr, "-f format Choose output format (plain, keyvalue, json, json0"
+#if defined USE_XML
+ ", xml"
+#endif
+ ").\n");
+ if (!is_lldpctl(NULL))
+ fprintf(stderr, "-c conf Read the provided configuration file.\n");
fprintf(stderr, "\n");
static int
is_privileged()
{
- return (!(getuid() != geteuid() || getgid() != getegid()));
+ /* Check we can access the control socket with read/write
+ * privileges. The `access()` function uses the real UID and real GID,
+ * therefore we don't have to mangle with our identity. */
+ return (ctlname && access(ctlname, R_OK|W_OK) == 0);
}
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;
lldpctl_atom_dec_ref(config);
return 0;
}
- log_info("lldpctl", "immediate retransmission requested successfuly");
+ log_info("lldpctl", "immediate retransmission requested successfully");
lldpctl_atom_dec_ref(config);
return 1;
}
-static unsigned char
-_cmd_complete(EditLine *el, int ch, int all)
+/**
+ * Pause or resume execution of lldpd.
+ *
+ * @param conn The connection to lldpd.
+ * @param pause 1 if we want to pause lldpd, 0 otherwise
+ * @return 1 on success, 0 on error
+ */
+static int
+cmd_pause_resume(lldpctl_conn_t *conn, int pause)
{
- int rc = CC_ERROR;
- Tokenizer *eltok;
- if ((eltok = tok_init(NULL)) == NULL)
- goto end;
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
+ log_debug("lldpctl", "lldpd is already %s",
+ pause?"paused":"resumed");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+ }
+ if (lldpctl_atom_set_int(config,
+ lldpctl_k_config_paused, pause) == NULL) {
+ log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
+ pause?"pause":"resume",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "lldpd should %s operations",
+ pause?"pause":"resume");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+static int
+cmd_pause(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, void *arg) {
+ (void)w; (void)env;
+ return cmd_pause_resume(conn, 1);
+}
+static int
+cmd_resume(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, void *arg) {
+ (void)w; (void)env;
+ return cmd_pause_resume(conn, 0);
+}
- 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)
+#ifdef HAVE_LIBREADLINE
+static int
+_cmd_complete(int all)
+{
+ char **argv = NULL;
+ int argc = 0;
+ int rc = 1;
+ size_t len = strlen(rl_line_buffer);
+ char *line = malloc(len + 2);
+ if (!line) return -1;
+ strlcpy(line, rl_line_buffer, len + 2);
+ line[rl_point] = 2; /* empty character, will force a word */
+ line[rl_point+1] = 0;
+
+ if (tokenize_line(line, &argc, &argv) != 0)
goto end;
- compl = commands_complete(root, argc, argv, cursorc, cursoro, all);
- if (compl) {
- el_deletestr(el, cursoro);
- if (el_insertstr(el, compl) == -1) {
+
+ char *compl = commands_complete(root, argc, (const char **)argv, all, is_privileged());
+ if (compl && strlen(argv[argc-1]) < strlen(compl)) {
+ if (rl_insert_text(compl + strlen(argv[argc-1])) < 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. */
+ free(compl);
+ fprintf(stderr, "\n");
+ rl_forced_update_display();
+ 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);
+}
+#else
+static char*
+readline(const char *p)
+{
+ static char line[2048];
+ fprintf(stderr, "%s", p);
+ fflush(stderr);
+ if (fgets(line, sizeof(line) - 2, stdin) == NULL)
+ return NULL;
+ return strdup(line);
+}
+#endif
+
+/**
+ * Execute a tokenized command and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param argc Number of arguments.
+ * @param argv Array of arguments.
+ * @return 0 if an error occurred, 1 otherwise
+ */
+static int
+cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
+{
+ /* Init output formatter */
+ struct writer *w;
+
+ if (strcmp(fmt, "plain") == 0) w = txt_init(stdout);
+ else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
+ else if (strcmp(fmt, "json") == 0) w = json_init(stdout, 1);
+ else if (strcmp(fmt, "json0") == 0) w = json_init(stdout, 0);
+#ifdef USE_XML
+ else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
+#endif
+ else {
+ log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
+ w = txt_init(stdout);
+ }
+
+ /* Execute command */
+ int rc = commands_execute(conn, w,
+ root, argc, argv, is_privileged());
+ if (rc != 0) {
+ log_info("lldpctl", "an error occurred while executing last command");
+ w->finish(w);
+ return 0;
+ }
+ w->finish(w);
+ return 1;
+}
+
+/**
+ * Execute a command line and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param line Line to execute.
+ * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
+ */
+static int
+parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
+{
+ int cargc = 0; char **cargv = NULL;
+ int n;
+ log_debug("lldpctl", "tokenize command line");
+ n = tokenize_line(line, &cargc, &cargv);
+ switch (n) {
+ case -1:
+ log_warnx("lldpctl", "internal error while tokenizing");
+ return -1;
+ case 1:
+ log_warnx("lldpctl", "unmatched quotes");
+ return -1;
+ }
+ if (cargc != 0)
+ n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
+ tokenize_free(cargc, cargv);
+ return (cargc == 0)?0:
+ (n == 0)?-1:
+ 1;
}
static struct cmd_node*
root = commands_root();
register_commands_show(root);
register_commands_watch(root);
- if (is_privileged()) {
- commands_new(
- commands_new(root, "update", "Update information and send LLDPU on all ports",
- NULL, NULL, NULL),
- NEWLINE, "Update information and send LLDPU on all ports",
- NULL, cmd_update, NULL);
- register_commands_configure(root);
- }
+ commands_privileged(commands_new(
+ commands_new(root, "update", "Update information and send LLDPU on all ports",
+ NULL, NULL, NULL),
+ NEWLINE, "Update information and send LLDPU on all ports",
+ NULL, cmd_update, NULL));
+ register_commands_configure(root);
+ commands_hidden(commands_new(root, "complete", "Get possible completions from a given command",
+ NULL, cmd_store_env_and_pop, "complete"));
+ commands_new(root, "help", "Get help on a possible command",
+ NULL, cmd_store_env_and_pop, "help");
+ commands_new(
+ commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL),
+ NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
+ commands_new(
+ commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL),
+ NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
commands_new(
commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
return root;
}
+struct input {
+ TAILQ_ENTRY(input) next;
+ char *name;
+};
+TAILQ_HEAD(inputs, input);
static int
-is_lldpctl(const char *name)
+filter(const struct dirent *dir)
{
- static int last_result = -1;
- if (last_result == -1 && name) {
- char *basec = strdup(name);
- if (!basec) return 0;
- char *bname = basename(basec);
- last_result = (!strcmp(bname, "lldpctl"));
- free(basec);
+ if (strlen(dir->d_name) < 5) return 0;
+ if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
+ return 1;
+}
+
+/**
+ * Append a new input file/directory to the list of inputs.
+ *
+ * @param arg Directory or file name to add.
+ * @param inputs List of inputs
+ * @param acceptdir 1 if we accept a directory, 0 otherwise
+ */
+static void
+input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
+{
+ struct stat statbuf;
+ if (stat(arg, &statbuf) == -1) {
+ if (warn) {
+ log_warn("lldpctl", "cannot find configuration file/directory %s",
+ arg);
+ } else {
+ log_debug("lldpctl", "cannot find configuration file/directory %s",
+ arg);
+ }
+ return;
}
- return (last_result == -1)?0:last_result;
+
+ if (!S_ISDIR(statbuf.st_mode)) {
+ struct input *input = malloc(sizeof(struct input));
+ if (!input) {
+ log_warn("lldpctl", "not enough memory to process %s",
+ arg);
+ return;
+ }
+ log_debug("lldpctl", "input: %s", arg);
+ input->name = strdup(arg);
+ TAILQ_INSERT_TAIL(inputs, input, next);
+ return;
+ }
+ if (!acceptdir) {
+ log_debug("lldpctl", "skip directory %s",
+ arg);
+ return;
+ }
+
+ struct dirent **namelist = NULL;
+ int n = scandir(arg, &namelist, filter, alphasort);
+ if (n < 0) {
+ log_warnx("lldpctl", "unable to read directory %s",
+ arg);
+ return;
+ }
+ for (int i=0; i < n; i++) {
+ char *fullname;
+ if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
+ input_append(fullname, inputs, 0, 1);
+ free(fullname);
+ }
+ free(namelist[i]);
+ }
+ free(namelist);
}
int
main(int argc, char *argv[])
{
- int ch, debug = 1, rc = EXIT_FAILURE;
- char *fmt = "plain";
- lldpctl_conn_t *conn;
- struct writer *w;
+ int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
+ const char *fmt = "plain";
+ lldpctl_conn_t *conn = NULL;
+ const char *options = is_lldpctl(argv[0])?"hdvf:u:":"hdsvf:c:C:u:";
- EditLine *el = NULL;
- History *elhistory = NULL;
- HistEvent elhistev;
- Tokenizer *eltok = NULL;
+ int gotinputs = 0, version = 0;
+ struct inputs inputs;
+ TAILQ_INIT(&inputs);
- char *interfaces = NULL;
+ ctlname = lldpctl_get_default_transport();
+
+ signal(SIGHUP, SIG_IGN);
/* Get and parse command line options */
- while ((ch = getopt(argc, argv, "hdvf:")) != -1) {
+ optind = 1;
+ while ((ch = getopt(argc, argv, options)) != -1) {
switch (ch) {
+ case 'd':
+ if (use_syslog)
+ use_syslog = 0;
+ else
+ debug++;
+ break;
+ case 's':
+ if (debug == 0)
+ use_syslog = 1;
+ else
+ debug--;
+ break;
case 'h':
usage();
break;
- case 'd':
- debug++;
+ case 'u':
+ ctlname = optarg;
break;
case 'v':
- fprintf(stdout, "%s\n", PACKAGE_VERSION);
- exit(0);
+ version++;
break;
case 'f':
fmt = optarg;
break;
+ case 'C':
+ case 'c':
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ gotinputs = 1;
+ }
+ input_append(optarg, &inputs, 1, ch == 'c');
+ break;
default:
usage();
}
}
- log_init(debug, __progname);
+ if (version) {
+ version_display(stdout, "lldpcli", version > 1);
+ exit(0);
+ }
+
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ }
+
+ /* Disable SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
/* Register commands */
root = register_commands();
- if (is_lldpctl(argv[0])) {
+ /* Make a connection */
+ log_debug("lldpctl", "connect to lldpd");
+ conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
+ if (conn == NULL) goto end;
+
+ /* Process file inputs */
+ while (gotinputs && !TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ log_debug("lldpctl", "process: %s", first->name);
+ FILE *file = fopen(first->name, "r");
+ if (file) {
+ size_t n;
+ ssize_t len;
+ char *line;
+ while (line = NULL, len = 0, (len = getline(&line, &n, file)) > 0) {
+ if (line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ parse_and_exec(conn, fmt, line);
+ }
+ free(line);
+ }
+ free(line);
+ fclose(file);
+ } else {
+ log_warn("lldpctl", "unable to open %s",
+ first->name);
+ }
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
+
+ /* Process additional arguments. First if we are lldpctl (interfaces) */
+ if (is_lldpctl(NULL)) {
+ char *line = NULL;
for (int i = optind; i < argc; i++) {
- char *prev = interfaces;
- if (asprintf(&interfaces, "%s%s%s",
- prev?prev:"", prev?",":"", argv[i]) == -1) {
+ char *prev = line;
+ if (asprintf(&line, "%s%s%s",
+ prev?prev:"show neigh ports ", argv[i],
+ (i == argc - 1)?" details":",") == -1) {
log_warnx("lldpctl", "not enough memory to build list of interfaces");
+ free(prev);
goto end;
}
free(prev);
}
- must_exit = 1;
- goto skipeditline;
- } else {
- if (optind < argc) {
- /* More arguments! */
- must_exit = 1;
+ if (line == NULL && (line = strdup("show neigh details")) == NULL) {
+ log_warnx("lldpctl", "not enough memory to build command line");
+ goto end;
}
- }
-
- /* Init editline */
- log_debug("lldpctl", "init editline");
- el = el_init("lldpctl", stdin, stdout, stderr);
- if (el == NULL) {
- log_warnx("lldpctl", "unable to setup editline");
+ log_debug("lldpctl", "execute %s", line);
+ if (parse_and_exec(conn, fmt, line) != -1)
+ rc = EXIT_SUCCESS;
+ free(line);
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 {
- history(elhistory, &elhistev, H_SETSIZE, 800);
- el_set(el, EL_HIST, history, elhistory);
+ /* Then, if we are regular lldpcli (command line) */
+ if (optind < argc) {
+ const char **cargv;
+ int cargc;
+ cargv = &((const char **)argv)[optind];
+ cargc = argc - optind;
+ if (cmd_exec(conn, fmt, cargc, cargv) == 1)
+ rc = EXIT_SUCCESS;
+ goto end;
}
- /* Init tokenizer */
- eltok = tok_init(NULL);
- if (eltok == NULL) {
- log_warnx("lldpctl", "unable to initialize tokenizer");
+ if (gotinputs) {
+ rc = EXIT_SUCCESS;
goto end;
}
-skipeditline:
- /* Make a connection */
- log_debug("lldpctl", "connect to lldpd");
- conn = lldpctl_new(NULL, NULL, NULL);
- if (conn == NULL)
- exit(EXIT_FAILURE);
-
- do {
- const char *line;
- const char **cargv;
- int count, n, cargc;
-
- if (!is_lldpctl(NULL) && (optind >= argc)) {
- /* Read a new line. */
- line = el_gets(el, &count);
- if (line == NULL) break;
-
- /* Tokenize it */
- log_debug("lldpctl", "tokenize command line");
- n = tok_str(eltok, 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);
- }
-
- /* Init output formatter */
- if (strcmp(fmt, "plain") == 0) w = txt_init(stdout);
- else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
-#ifdef USE_XML
- else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
+ /* Interactive session */
+#ifdef HAVE_LIBREADLINE
+ rl_bind_key('?', cmd_help);
+ rl_bind_key('\t', cmd_complete);
#endif
-#ifdef USE_JSON
- else if (strcmp(fmt, "json") == 0) w = json_init(stdout);
+ char *line = NULL;
+ do {
+ if ((line = readline(prompt()))) {
+ int n = parse_and_exec(conn, fmt, line);
+ if (n != 0) {
+#ifdef HAVE_READLINE_HISTORY
+ add_history(line);
#endif
- else w = txt_init(stdout);
-
- if (is_lldpctl(NULL)) {
- if (!interfaces) {
- cargv = (const char*[]){ "show", "neighbors", "details" };
- cargc = 3;
- } else {
- cargv = (const char*[]){ "show", "neighbors", "ports", interfaces, "details" };
- cargc = 5;
}
- } else if (optind < argc) {
- cargv = (const char **)argv;
- cargv = &cargv[optind];
- cargc = argc - optind;
+ free(line);
}
-
- /* Execute command */
- if (commands_execute(conn, w,
- root, cargc, cargv) != 0)
- log_info("lldpctl", "an error occurred while executing last command");
- w->finish(w);
-
- if (eltok) tok_reset(eltok);
- } while (!must_exit);
-
+ } while (!must_exit && line != NULL);
rc = EXIT_SUCCESS;
+
end:
+ while (!TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
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;
}