summaryrefslogtreecommitdiffstats
path: root/src/client/commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/commands.c')
-rw-r--r--src/client/commands.c855
1 files changed, 855 insertions, 0 deletions
diff --git a/src/client/commands.c b/src/client/commands.c
new file mode 100644
index 0000000..804789e
--- /dev/null
+++ b/src/client/commands.c
@@ -0,0 +1,855 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 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>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <libgen.h>
+
+/**
+ * An element of the environment (a key and a value).
+ */
+struct cmd_env_el {
+ TAILQ_ENTRY(cmd_env_el) next; /**< Next environment element */
+ const char *key; /**< Key for this element */
+ const char *value; /**< Value for this element */
+};
+
+/**
+ * A stack element.
+ */
+struct cmd_env_stack {
+ TAILQ_ENTRY(cmd_env_stack) next; /**< Next element, down the stack */
+ struct cmd_node *el; /**< Stored element */
+};
+
+/**
+ * Structure representing an environment for the current command.
+ *
+ * An environment is a list of values stored for use for the function executing
+ * as well as the current command, the current position in the command and a
+ * stack for cmd_node
+ */
+struct cmd_env {
+ TAILQ_HEAD(, cmd_env_el) elements; /**< List of environment variables */
+ TAILQ_HEAD(, cmd_env_stack) stack; /**< Stack */
+ int argc; /**< Number of argument in the command */
+ int argp; /**< Current argument */
+ const char **argv; /**< Arguments */
+};
+
+/**
+ * Structure representing a command node.
+ *
+ * Such a node contains a token accepted to enter the node (or @c NULL if there
+ * is no token needed), a documentation string to present the user, a function
+ * to validate the user input (or @c NULL if no function is needed) and a
+ * function to execute when entering the node. Because we can enter a node just
+ * by completing, the execution part should have no other effect than modifying
+ * the environment, with the exception of execution on @c NEWLINE (which cannot
+ * happen when completing).
+ */
+struct cmd_node {
+ TAILQ_ENTRY(cmd_node) next; /**< Next sibling */
+
+ const char *token; /**< Token to enter this cnode */
+ const char *doc; /**< Documentation string */
+ int privileged; /**< Privileged command? */
+ int lock; /**< Lock required for execution? */
+ int hidden; /**< Hidden command? */
+
+ /**
+ * Function validating entry in this node. Can be @c NULL.
+ */
+ int (*validate)(struct cmd_env *, const void *);
+ /**
+ * Function to execute when entering this node. May be @c NULL.
+ *
+ * This function can alter the environment
+ */
+ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *);
+ const void *arg; /**< Magic argument for the previous two functions */
+
+ /* List of possible subentries */
+ TAILQ_HEAD(, cmd_node) subentries; /* List of subnodes */
+};
+
+/**
+ * Create a root node.
+ *
+ * @return the root node.
+ */
+struct cmd_node *
+commands_root(void)
+{
+ struct cmd_node *new = calloc(1, sizeof(struct cmd_node));
+ if (new == NULL) fatalx("lldpctl", "out of memory");
+ TAILQ_INIT(&new->subentries);
+ return new;
+}
+
+/**
+ * Make a node accessible only to privileged users.
+ *
+ * @param node node to change privileges
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_privileged(struct cmd_node *node)
+{
+ if (node) node->privileged = 1;
+ return node;
+}
+
+/**
+ * Make a node accessible only with a lock.
+ *
+ * @param node node to use lock to execute
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_lock(struct cmd_node *node)
+{
+ if (node) node->lock = 1;
+ return node;
+}
+
+/**
+ * Hide a node from help or completion.
+ *
+ * @param node node to hide
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_hidden(struct cmd_node *node)
+{
+ if (node) node->hidden = 1;
+ return node;
+}
+
+/**
+ * Create a new node acessible by any user.
+ *
+ * @param root The node we want to attach this node.
+ * @param token Token to enter this node. Or @c NULL if no token is needed.
+ * @param doc Documentation for this node.
+ * @param validate Function that should return 1 if we can enter the node.
+ * @param execute Function that should return 1 on successful execution of this node.
+ * @param arg Magic argument for precedent functions.
+ * @return the newly created node
+ */
+struct cmd_node *
+commands_new(struct cmd_node *root, const char *token, const char *doc,
+ int (*validate)(struct cmd_env *, const void *),
+ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *),
+ const void *arg)
+{
+ struct cmd_node *new = calloc(1, sizeof(struct cmd_node));
+ if (new == NULL) fatalx("lldpctl", "out of memory");
+ new->token = token;
+ new->doc = doc;
+ new->validate = validate;
+ new->execute = execute;
+ new->arg = arg;
+ TAILQ_INIT(&new->subentries);
+ TAILQ_INSERT_TAIL(&root->subentries, new, next);
+ return new;
+}
+
+/**
+ * Free a command tree.
+ *
+ * @param root The node we want to free.
+ */
+void
+commands_free(struct cmd_node *root)
+{
+ struct cmd_node *subcmd, *subcmd_next;
+ for (subcmd = TAILQ_FIRST(&root->subentries); subcmd != NULL;
+ subcmd = subcmd_next) {
+ subcmd_next = TAILQ_NEXT(subcmd, next);
+ TAILQ_REMOVE(&root->subentries, subcmd, next);
+ commands_free(subcmd);
+ }
+ free(root);
+}
+
+/**
+ * Return the current argument in the environment. This can be @c NEWLINE or
+ * @c NULL.
+ *
+ * @param env The environment.
+ * @return current argument.
+ */
+const char *
+cmdenv_arg(struct cmd_env *env)
+{
+ if (env->argp < env->argc) return env->argv[env->argp];
+ if (env->argp == env->argc) return NEWLINE;
+ return NULL;
+}
+
+/**
+ * Get a value from the environment.
+ *
+ * @param env The environment.
+ * @param key The key for the requested value.
+ * @return @c NULL if not found or the requested value otherwise. If no value is
+ * associated, return the key.
+ */
+const char *
+cmdenv_get(struct cmd_env *env, const char *key)
+{
+ struct cmd_env_el *el;
+ TAILQ_FOREACH (el, &env->elements, next)
+ if (!strcmp(el->key, key)) return el->value ? el->value : el->key;
+ return NULL;
+}
+
+/**
+ * Put a value in the environment.
+ *
+ * @param env The environment.
+ * @param key The key for the value.
+ * @param value The value.
+ * @return 0 on success, -1 otherwise.
+ */
+int
+cmdenv_put(struct cmd_env *env, const char *key, const char *value)
+{
+ struct cmd_env_el *el = malloc(sizeof(struct cmd_env_el));
+ if (el == NULL) {
+ log_warn("lldpctl",
+ "unable to allocate memory for new environment variable");
+ return -1;
+ }
+ el->key = key;
+ el->value = value;
+ TAILQ_INSERT_TAIL(&env->elements, el, next);
+ return 0;
+}
+
+/**
+ * Pop some node from the execution stack.
+ *
+ * This allows to resume parsing on a previous state. Useful to call after
+ * parsing optional arguments.
+ *
+ * @param env The environment.
+ * @param n How many element we want to pop.
+ * @return 0 on success, -1 otherwise.
+ */
+int
+cmdenv_pop(struct cmd_env *env, int n)
+{
+ while (n-- > 0) {
+ if (TAILQ_EMPTY(&env->stack)) {
+ log_warnx("lldpctl", "environment stack is empty");
+ return -1;
+ }
+ struct cmd_env_stack *first = TAILQ_FIRST(&env->stack);
+ TAILQ_REMOVE(&env->stack, first, next);
+ free(first);
+ }
+ return 0;
+}
+
+/**
+ * Push some node on the execution stack.
+ *
+ * @param env The environment.
+ * @param node The node to push.
+ * @return 0 on success, -1 on error.
+ */
+static int
+cmdenv_push(struct cmd_env *env, struct cmd_node *node)
+{
+ struct cmd_env_stack *el = malloc(sizeof(struct cmd_env_stack));
+ if (el == NULL) {
+ log_warn("lldpctl", "not enough memory to allocate a stack element");
+ return -1;
+ }
+ el->el = node;
+ TAILQ_INSERT_HEAD(&env->stack, el, next);
+ return 0;
+}
+
+/**
+ * Return the top of the stack, without poping it.
+ *
+ * @param env The environment.
+ * @return the top element or @c NULL is the stack is empty.
+ */
+static struct cmd_node *
+cmdenv_top(struct cmd_env *env)
+{
+ if (TAILQ_EMPTY(&env->stack)) return NULL;
+ return TAILQ_FIRST(&env->stack)->el;
+}
+
+/**
+ * Free execution environment.
+ *
+ * @param env The environment.
+ */
+static void
+cmdenv_free(struct cmd_env *env)
+{
+ while (!TAILQ_EMPTY(&env->stack))
+ cmdenv_pop(env, 1);
+
+ struct cmd_env_el *first;
+ while (!TAILQ_EMPTY(&env->elements)) {
+ first = TAILQ_FIRST(&env->elements);
+ TAILQ_REMOVE(&env->elements, first, next);
+ free(first);
+ }
+}
+
+struct candidate_word {
+ TAILQ_ENTRY(candidate_word) next;
+ const char *word;
+ const char *doc;
+ int hidden;
+};
+
+/**
+ * Execute or complete a command from the given node.
+ *
+ * @param conn Connection to lldpd.
+ * @param w Writer for output.
+ * @param root Root node we want to start from.
+ * @param argc Number of arguments.
+ * @param argv Array of arguments.
+ * @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.
+ * @param priv Is the current user privileged?
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+_commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root,
+ int argc, const char **argv, char **word, int all, int priv)
+{
+ int n, rc = 0, completion = (word != NULL);
+ int help = 0; /* Are we asking for help? */
+ int complete = 0; /* Are we asking for possible completions? */
+ int needlock = 0; /* Do we need a lock? */
+ struct cmd_env env = { .elements = TAILQ_HEAD_INITIALIZER(env.elements),
+ .stack = TAILQ_HEAD_INITIALIZER(env.stack),
+ .argc = argc,
+ .argv = argv,
+ .argp = 0 };
+ static int lockfd = -1;
+ static char *lockname = NULL; /* Name of lockfile */
+ cmdenv_push(&env, root);
+ if (!completion)
+ for (n = 0; n < argc; n++)
+ log_debug("lldpctl", "argument %02d: `%s`", n, argv[n]);
+ if (completion) *word = NULL;
+
+#define CAN_EXECUTE(candidate) \
+ ((!candidate->privileged || priv || complete) && \
+ (!candidate->validate || candidate->validate(&env, candidate->arg) == 1))
+
+ /* When completion is in progress, we use the same algorithm than for
+ * execution until we reach the cursor position. */
+ struct cmd_node *current = NULL;
+ while ((current = cmdenv_top(&env))) {
+ if (!completion) {
+ help = !!cmdenv_get(&env, "help"); /* Are we asking for help? */
+ complete = !!cmdenv_get(&env, "complete"); /* Or completion? */
+ }
+
+ struct cmd_node *candidate, *best = NULL;
+ const char *token = (env.argp < env.argc) ? env.argv[env.argp] :
+ (env.argp == env.argc && !help && !complete) ? 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);
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if (candidate->token &&
+ !strncmp(candidate->token, token, strlen(token)) &&
+ CAN_EXECUTE(candidate)) {
+ if (candidate->token &&
+ !strcmp(candidate->token, token)) {
+ /* Exact match */
+ best = candidate;
+ needlock = needlock || candidate->lock;
+ break;
+ }
+ if (!best)
+ best = candidate;
+ else {
+ if (!completion)
+ log_warnx("lldpctl",
+ "ambiguous token: %s (%s or %s)",
+ token, candidate->token,
+ best->token);
+ rc = -1;
+ goto end;
+ }
+ }
+ }
+ if (!best) {
+ /* Take first that validate */
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if (!candidate->token && CAN_EXECUTE(candidate)) {
+ best = candidate;
+ needlock = needlock || candidate->lock;
+ break;
+ }
+ }
+ }
+ if (!best && env.argp == env.argc) goto end;
+ if (!best) {
+ if (!completion)
+ log_warnx("lldpctl",
+ "unknown command from argument %d: `%s`",
+ env.argp + 1, token);
+ rc = -1;
+ goto end;
+ }
+
+ /* Push and execute */
+ cmdenv_push(&env, best);
+ if (best->execute) {
+ if (needlock) {
+ if (lockfd == -1) {
+ if (lockname == NULL &&
+ asprintf(&lockname, "%s.lock", ctlname) ==
+ -1) {
+ log_warnx("lldpctl",
+ "not enough memory to build lock filename");
+ rc = -1;
+ goto end;
+ }
+ log_debug("lldpctl", "open %s for locking",
+ lockname);
+ if ((lockfd = open(lockname, O_RDWR)) == -1) {
+ log_warn("lldpctl",
+ "cannot open lock %s", lockname);
+ rc = -1;
+ goto end;
+ }
+ }
+ if (lockf(lockfd, F_LOCK, 0) == -1) {
+ log_warn("lldpctl", "cannot get lock on %s",
+ lockname);
+ rc = -1;
+ close(lockfd);
+ lockfd = -1;
+ goto end;
+ }
+ }
+ rc = best->execute(conn, w, &env, best->arg) != 1 ? -1 : rc;
+ if (needlock && lockf(lockfd, F_ULOCK, 0) == -1) {
+ log_warn("lldpctl", "cannot unlock %s", lockname);
+ close(lockfd);
+ lockfd = -1;
+ }
+ if (rc == -1) goto end;
+ }
+ env.argp++;
+ }
+end:
+ if (!completion && !help && !complete) {
+ if (rc == 0 && env.argp != env.argc + 1) {
+ log_warnx("lldpctl", "incomplete command");
+ rc = -1;
+ }
+ } else if (rc == 0 && (env.argp == env.argc - 1 || help || complete)) {
+ /* We need to complete. Let's build the list of candidate words. */
+ struct cmd_node *candidate = NULL;
+ size_t maxl = 10; /* Max length of a word */
+ TAILQ_HEAD(, candidate_word) words; /* List of subnodes */
+ TAILQ_INIT(&words);
+ current = cmdenv_top(&env);
+ if (!TAILQ_EMPTY(&current->subentries)) {
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if ((!candidate->token || help || complete ||
+ !strncmp(env.argv[env.argc - 1],
+ candidate->token,
+ strlen(env.argv[env.argc - 1]))) &&
+ CAN_EXECUTE(candidate)) {
+ struct candidate_word *cword =
+ malloc(sizeof(struct candidate_word));
+ if (!cword) break;
+ cword->word = candidate->token;
+ cword->doc = candidate->doc;
+ cword->hidden = candidate->hidden;
+ if (cword->word && strlen(cword->word) > maxl)
+ maxl = strlen(cword->word);
+ TAILQ_INSERT_TAIL(&words, cword, next);
+ }
+ }
+ }
+ if (!TAILQ_EMPTY(&words)) {
+ /* Search if there is a common prefix, then return it. */
+ char prefix[maxl + 2]; /* Extra space may be added at the end */
+ struct candidate_word *cword, *cword_next;
+ memset(prefix, 0, maxl + 2);
+ for (size_t n = 0; n < maxl; n++) {
+ int c = 1; /* Set to 0 to exit outer loop */
+ TAILQ_FOREACH (cword, &words, next) {
+ c = 0;
+ if (cword->hidden) continue;
+ if (cword->word == NULL) break;
+ if (!strcmp(cword->word, NEWLINE)) break;
+ if (strlen(cword->word) == n) break;
+ if (prefix[n] == '\0')
+ prefix[n] = cword->word[n];
+ else if (prefix[n] != cword->word[n])
+ break;
+ c = 1;
+ }
+ if (c == 0) {
+ prefix[n] = '\0';
+ break;
+ }
+ }
+ /* If the prefix is complete, add a space, otherwise,
+ * just return it as is. */
+ if (!all && !help && !complete && strcmp(prefix, NEWLINE) &&
+ 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)] = ' ';
+ break;
+ }
+ }
+ *word = strdup(prefix);
+ } else {
+ /* No common prefix, print possible completions */
+ if (!complete)
+ fprintf(stderr, "\n-- \033[1;34m%s\033[0m\n",
+ current->doc ? current->doc : "Help");
+ TAILQ_FOREACH (cword, &words, next) {
+ if (cword->hidden) continue;
+
+ char fmt[100];
+ if (!complete) {
+ snprintf(fmt, sizeof(fmt),
+ "%s%%%ds%s %%s\n", "\033[1m",
+ (int)maxl, "\033[0m");
+ fprintf(stderr, fmt,
+ cword->word ? cword->word : "WORD",
+ cword->doc ? cword->doc : "...");
+ } else {
+ if (!cword->word ||
+ !strcmp(cword->word, NEWLINE))
+ continue;
+ fprintf(stdout, "%s %s\n",
+ cword->word ? cword->word : "WORD",
+ cword->doc ? cword->doc : "...");
+ }
+ }
+ }
+ for (cword = TAILQ_FIRST(&words); cword != NULL;
+ cword = cword_next) {
+ cword_next = TAILQ_NEXT(cword, next);
+ TAILQ_REMOVE(&words, cword, next);
+ free(cword);
+ }
+ }
+ }
+ cmdenv_free(&env);
+ return rc;
+}
+
+/**
+ * Complete the given command.
+ */
+char *
+commands_complete(struct cmd_node *root, int argc, const char **argv, int all,
+ int privileged)
+{
+ char *word = NULL;
+ if (_commands_execute(NULL, NULL, root, argc, argv, &word, all, privileged) ==
+ 0)
+ return word;
+ return NULL;
+}
+
+/**
+ * Execute the given commands.
+ */
+int
+commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root,
+ int argc, const char **argv, int privileged)
+{
+ return _commands_execute(conn, w, root, argc, argv, NULL, 0, privileged);
+}
+
+/**
+ * Check if the environment does not contain the given key.
+ *
+ * @param env The environment.
+ * @param key The key to search for.
+ * @return 1 if the environment does not contain the key. 0 otherwise.
+ */
+int
+cmd_check_no_env(struct cmd_env *env, const void *key)
+{
+ return cmdenv_get(env, (const char *)key) == NULL;
+}
+
+/**
+ * Check if the environment does contain the given key.
+ *
+ * @param env The environment.
+ * @param key The key to search for. Can be a comma separated list.
+ * @return 1 if the environment does contain the key. 0 otherwise.
+ */
+int
+cmd_check_env(struct cmd_env *env, const void *key)
+{
+ struct cmd_env_el *el;
+ const char *list = key;
+ int count = 0;
+ TAILQ_FOREACH (el, &env->elements, next) {
+ if (contains(list, el->key)) count++;
+ }
+ while ((list = strchr(list, ','))) {
+ list++;
+ count--;
+ }
+ return (count == 1);
+}
+
+/**
+ * Store the given key in the environment.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *key)
+{
+ return cmdenv_put(env, key, NULL) != -1;
+}
+
+/**
+ * Store the given key in the environment and pop one element from the stack.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (cmd_store_env(conn, w, env, key) != -1 && cmdenv_pop(env, 1) != -1);
+}
+
+/**
+ * Store the given key with a value being the current keyword in the environment
+ * and pop X elements from the stack.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env_value_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 1) != -1);
+}
+int
+cmd_store_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 2) != -1);
+}
+int
+cmd_store_env_value(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *key)
+{
+ return (cmdenv_put(env, key, cmdenv_arg(env)) != -1);
+}
+int
+cmd_store_env_value_and_pop3(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 3) != -1);
+}
+int
+cmd_store_something_env_value_and_pop2(const char *what, struct cmd_env *env,
+ const void *value)
+{
+ return (cmdenv_put(env, what, value) != -1 && cmdenv_pop(env, 2) != -1);
+}
+int
+cmd_store_something_env_value(const char *what, struct cmd_env *env, const void *value)
+{
+ return (cmdenv_put(env, what, value) != -1);
+}
+
+/**
+ * Provide an iterator on all interfaces contained in "ports".
+ *
+ * @warning This function is not reentrant. It uses static variables to keep
+ * track of ports that have already been provided. Moreover, to release all
+ * resources, the iterator should be used until its end.
+ *
+ * @param conn The connection.
+ * @param env The environment.
+ * @return The next interface in the set of ports (or in all ports if no `ports`
+ * variable is present in the environment)
+ */
+lldpctl_atom_t *
+cmd_iterate_on_interfaces(struct lldpctl_conn_t *conn, struct cmd_env *env)
+{
+ static lldpctl_atom_iter_t *iter = NULL;
+ static lldpctl_atom_t *iface_list = NULL;
+ static lldpctl_atom_t *iface = NULL;
+ const char *interfaces = cmdenv_get(env, "ports");
+
+ do {
+ if (iter == NULL) {
+ iface_list = lldpctl_get_interfaces(conn);
+ if (!iface_list) {
+ log_warnx("lldpctl",
+ "not able to get the list of interfaces. %s",
+ lldpctl_last_strerror(conn));
+ return NULL;
+ }
+ iter = lldpctl_atom_iter(iface_list);
+ if (!iter) return NULL;
+ } else {
+ iter = lldpctl_atom_iter_next(iface_list, iter);
+ if (iface) {
+ lldpctl_atom_dec_ref(iface);
+ iface = NULL;
+ }
+ if (!iter) {
+ lldpctl_atom_dec_ref(iface_list);
+ return NULL;
+ }
+ }
+
+ iface = lldpctl_atom_iter_value(iface_list, iter);
+ } while (interfaces &&
+ !contains(interfaces,
+ lldpctl_atom_get_str(iface, lldpctl_k_interface_name)));
+
+ return iface;
+}
+
+/**
+ * Provide an iterator on all ports contained in "ports", as well as the
+ * default port.
+ *
+ * @warning This function is not reentrant. It uses static variables to keep
+ * track of ports that have already been provided. Moreover, to release all
+ * resources, the iterator should be used until its end.
+ *
+ * @param conn The connection.
+ * @param env The environment.
+ * @param name Name of the interface (for logging purpose)
+ * @return The next interface in the set of ports (or in all ports if no `ports`
+ * variable is present in the environment), including the default port
+ * if no `ports` variable is present in the environment.
+ */
+lldpctl_atom_t *
+cmd_iterate_on_ports(struct lldpctl_conn_t *conn, struct cmd_env *env,
+ const char **name)
+{
+ static int put_default = 0;
+ static lldpctl_atom_t *last_port = NULL;
+ const char *interfaces = cmdenv_get(env, "ports");
+
+ if (last_port) {
+ lldpctl_atom_dec_ref(last_port);
+ last_port = NULL;
+ }
+ if (!put_default) {
+ lldpctl_atom_t *iface = cmd_iterate_on_interfaces(conn, env);
+ if (iface) {
+ *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+ last_port = lldpctl_get_port(iface);
+ return last_port;
+ }
+ if (!interfaces) {
+ put_default = 1;
+ *name = "(default)";
+ last_port = lldpctl_get_default_port(conn);
+ return last_port;
+ }
+ return NULL;
+ } else {
+ put_default = 0;
+ return NULL;
+ }
+}
+
+/**
+ * Restrict the command to some ports.
+ */
+void
+cmd_restrict_ports(struct cmd_node *root)
+{
+ /* Restrict to some ports. */
+ commands_new(commands_new(root, "ports", "Restrict configuration to some ports",
+ cmd_check_no_env, NULL, "ports"),
+ NULL,
+ "Restrict configuration to the specified ports (comma-separated list)",
+ NULL, cmd_store_env_value_and_pop2, "ports");
+}
+
+/**
+ * Restrict the command to specific protocol
+ */
+void
+cmd_restrict_protocol(struct cmd_node *root)
+{
+ /* Restrict to some ports. */
+ commands_new(commands_new(root, "protocol", "Restrict to specific protocol",
+ cmd_check_no_env, NULL, "protocol"),
+ NULL, "Restrict display to the specified protocol", NULL,
+ cmd_store_env_value_and_pop2, "protocol");
+}