]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
lldpctl: add a CLI
authorVincent Bernat <bernat@luffy.cx>
Sat, 5 Jan 2013 13:44:46 +0000 (14:44 +0100)
committerVincent Bernat <bernat@luffy.cx>
Sat, 5 Jan 2013 13:44:46 +0000 (14:44 +0100)
CLI provides contextual help and completion. It uses libedit.

14 files changed:
NEWS
configure.ac
src/client/Makefile.am
src/client/actions.c
src/client/client.h
src/client/commands.c [new file with mode: 0644]
src/client/display.c
src/client/json_writer.c
src/client/lldpctl.8
src/client/lldpctl.c
src/client/lldpctl.supp [new file with mode: 0644]
src/client/misc.c [new file with mode: 0644]
src/client/show.c [new file with mode: 0644]
src/client/xml_writer.c

diff --git a/NEWS b/NEWS
index 555a4c69a72e0f6e11b3ca87b82bd85eedc4fefd..53917d4c8ac0478a7fa76d0e00a7fcfa9a4bf344 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -3,9 +3,9 @@ lldpd (0.7.0)
     + FreeBSD support.
     + OpenBSD support.
     + NetBSD support.
-    + Detect interface changes and handle them in less than one
-      second. Each port has now its own timer.
-  * Features:
+    + Detect interface changes.
+    + CLI for lldpctl.
+  * Other features:
     + Allow to disable LLDP protocol (with `-ll`). In this case, the
       first enabled protocol will be used when no neighbor is detected.
     + Allow to filter debug logs using tokens. Add more debug logs.
index 32b26ff244d4eb7fdcafbb115088aeb8198de6a2..528a8f8bc7472e45d838c53e2e7a1f158095469d 100644 (file)
@@ -99,6 +99,9 @@ PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [have_check=yes], [have_check=no])
 # Libevent
 lldp_CHECK_LIBEVENT
 
+# editline
+PKG_CHECK_MODULES([EDITLINE], [libedit >= 2.9])
+
 #######################
 ### Options
 
index 910e098cefa936b8ad2f6aee3825c1bd72b3e2cc..ee13c3aa0da73ba66f4b04c653a957bfd18edc00 100644 (file)
@@ -3,9 +3,15 @@ AM_CFLAGS = -I $(top_srcdir)/include
 sbin_PROGRAMS = lldpctl
 dist_man_MANS = lldpctl.8
 
-lldpctl_SOURCES  = client.h lldpctl.c display.c writer.h text_writer.c kv_writer.c actions.c
-lldpctl_LDADD    = $(top_builddir)/src/libcommon-daemon-client.la $(top_builddir)/src/lib/liblldpctl.la
-lldpctl_CFLAGS   = $(AM_CFLAGS)
+lldpctl_SOURCES  = client.h lldpctl.c display.c actions.c \
+       commands.c show.c \
+       misc.c \
+       writer.h text_writer.c kv_writer.c
+lldpctl_LDADD    = \
+       $(top_builddir)/src/libcommon-daemon-client.la \
+       $(top_builddir)/src/lib/liblldpctl.la \
+       @EDITLINE_LIBS@
+lldpctl_CFLAGS   = $(AM_CFLAGS) @EDITLINE_CFLAGS@
 
 if USE_XML
 lldpctl_SOURCES += xml_writer.c
index d8932f0152296b0c1f9057f9a789638cf82dc163..706b99358dec255dbdeb7e5a50e53aa42e1ae197 100644 (file)
 #include "client.h"
 #include "../log.h"
 
-/* Helpers to parse a ':'-separated string. */
-static char*
-get_next(lldpctl_atom_t *atom, char *string,
-    const char *what, int mandatory)
+static int
+_cmd_medlocation(struct lldpctl_conn_t *conn,
+    struct cmd_env *env, int format)
 {
-       static char *e2 = NULL;
-       static char *e1 = NULL;
-       static char *saved_string = NULL;
-       static int pos;
-       if (e2 != NULL) {
-               *e2 = ':';
-               e1 = e2 + 1;
-       } else if (e1 != NULL) e1 = "";
-       if (e1 == NULL || (saved_string != string)) {
-               e1 = saved_string = string;
-               pos = 1;
-               e2 = NULL;
+       lldpctl_atom_t *iface;
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               const char *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+               lldpctl_atom_t *port = lldpctl_get_port(iface);
+               lldpctl_atom_t *med_location = NULL, *med_locations = NULL;
+               const char *what = NULL;
+               int ok = 0;
+
+               med_locations = lldpctl_atom_get(port, lldpctl_k_port_med_locations);
+               if (med_locations == NULL) {
+                       log_warnx("lldpctl", "unable to set LLDP-MED location: support seems unavailable");
+                       goto end;
+               }
+
+               med_location = lldpctl_atom_iter_value(med_locations,
+                   lldpctl_atom_iter_next(med_locations,
+                       lldpctl_atom_iter(med_locations)));
+
+               switch (format) {
+               case LLDP_MED_LOCFORMAT_COORD:
+                       if ((what = "format", lldpctl_atom_set_int(med_location,
+                                   lldpctl_k_med_location_format,
+                                   format)) == NULL ||
+                           (what = "latitude", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_latitude,
+                               cmdenv_get(env, "latitude"))) == NULL ||
+                           (what = "longitude", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_longitude,
+                               cmdenv_get(env, "longitude"))) == NULL ||
+                           (what = "altitude", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_altitude,
+                               cmdenv_get(env, "altitude"))) == NULL ||
+                           (what = "altitude unit", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_altitude_unit,
+                               cmdenv_get(env, "altitude-unit"))) == NULL ||
+                           (what = "datum", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_geoid,
+                               cmdenv_get(env, "datum"))) == NULL)
+                               log_warnx("lldpctl",
+                                   "unable to set LLDP MED location value for %s on %s. %s.",
+                                   what, name, lldpctl_last_strerror(conn));
+                       else ok = 1;
+                       break;
+               case LLDP_MED_LOCFORMAT_CIVIC:
+                       if ((what = "format", lldpctl_atom_set_int(med_location,
+                                   lldpctl_k_med_location_format,
+                                   format)) == NULL ||
+                           (what = "country", lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_country,
+                               cmdenv_get(env, "country"))) == NULL) {
+                               log_warnx("lldpctl",
+                                   "unable to set LLDP MED location value for %s on %s. %s.",
+                                   what, name, lldpctl_last_strerror(conn));
+                               break;
+                       }
+                       ok = 1;
+                       for (lldpctl_map_t *addr_map =
+                                lldpctl_key_get_map(lldpctl_k_med_civicaddress_type);
+                            addr_map->string;
+                            addr_map++) {
+                               lldpctl_atom_t *cael, *caels;
+                               const char *value = cmdenv_get(env, addr_map->string);
+                               if (!value) continue;
+
+                               caels = lldpctl_atom_get(med_location, lldpctl_k_med_location_ca_elements);
+                               cael = lldpctl_atom_create(caels);
+
+                               if (lldpctl_atom_set_str(cael, lldpctl_k_med_civicaddress_type,
+                                       addr_map->string) == NULL ||
+                                   lldpctl_atom_set_str(cael, lldpctl_k_med_civicaddress_value,
+                                       value) == NULL ||
+                                   lldpctl_atom_set(med_location,
+                                       lldpctl_k_med_location_ca_elements,
+                                       cael) == NULL) {
+                                               log_warnx("lldpctl",
+                                                   "unable to add a civic address element `%s`. %s",
+                                                   addr_map->string,
+                                                   lldpctl_last_strerror(conn));
+                                               ok = 0;
+                               }
+
+                               lldpctl_atom_dec_ref(cael);
+                               lldpctl_atom_dec_ref(caels);
+                               if (!ok) break;
+                       }
+                       break;
+               case LLDP_MED_LOCFORMAT_ELIN:
+                       if (lldpctl_atom_set_int(med_location,
+                               lldpctl_k_med_location_format, format) == NULL ||
+                           lldpctl_atom_set_str(med_location,
+                               lldpctl_k_med_location_elin, cmdenv_get(env, "elin")) == NULL)
+                               log_warnx("lldpctl", "unable to set LLDP MED location on %s. %s",
+                                   name, lldpctl_last_strerror(conn));
+                       else ok = 1;
+                       break;
+               }
+               if (ok) {
+                       if (lldpctl_atom_set(port, lldpctl_k_port_med_locations,
+                               med_location) == NULL) {
+                               log_warnx("lldpctl", "unable to set LLDP MED location on %s. %s.",
+                                   name, lldpctl_last_strerror(conn));
+                       } else
+                               log_info("lldpctl", "LLDP-MED location has been set for port %s",
+                                   name);
+               }
+
+       end:
+               lldpctl_atom_dec_ref(med_location);
+               lldpctl_atom_dec_ref(med_locations);
+               lldpctl_atom_dec_ref(port);
        }
+       return 1;
+}
 
+static int
+cmd_medlocation_coordinate(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "set MED location coordinate");
+       return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_COORD);
+}
 
-       if (*e1 == '\0') {
-               if (mandatory)
-                       log_warnx(NULL, "unable to find %s in `%s' at pos %d",
-                           what, string, pos);
-               return NULL;
+static int
+cmd_medlocation_address(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "set MED location address");
+       return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_CIVIC);
+}
+
+static int
+cmd_medlocation_elin(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "set MED location ELIN");
+       return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_ELIN);
+}
+
+static int
+cmd_medpolicy(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "set MED policy");
+       lldpctl_atom_t *iface;
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               const char *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+               lldpctl_atom_t *port = lldpctl_get_port(iface);
+               lldpctl_atom_t *med_policy = NULL, *med_policies = NULL;
+               const char *what = NULL;
+
+               med_policies = lldpctl_atom_get(port, lldpctl_k_port_med_policies);
+               if (med_policies == NULL) {
+                       log_warnx("lldpctl", "unable to set LLDP-MED policies: support seems unavailable");
+                       goto end;
+               }
+
+               med_policy = lldpctl_atom_iter_value(med_policies,
+                   lldpctl_atom_iter_next(med_policies,
+                       lldpctl_atom_iter(med_policies)));
+
+               if ((what = "application", lldpctl_atom_set_str(med_policy,
+                           lldpctl_k_med_policy_type,
+                           cmdenv_get(env, "application"))) == NULL ||
+                   (what = "unknown flag", lldpctl_atom_set_int(med_policy,
+                       lldpctl_k_med_policy_unknown,
+                       cmdenv_get(env, "unknown")?1:0)) == NULL ||
+                   (what = "vlan",
+                       cmdenv_get(env, "vlan")?
+                       lldpctl_atom_set_str(med_policy,
+                           lldpctl_k_med_policy_vid,
+                           cmdenv_get(env, "vlan")):
+                       lldpctl_atom_set_int(med_policy,
+                           lldpctl_k_med_policy_vid, 0)) == NULL ||
+                   (what = "priority",
+                       cmdenv_get(env, "priority")?
+                       lldpctl_atom_set_str(med_policy,
+                           lldpctl_k_med_policy_priority,
+                           cmdenv_get(env, "priority")):
+                       lldpctl_atom_set_int(med_policy,
+                           lldpctl_k_med_policy_priority,
+                           0)) == NULL ||
+                   (what = "dscp",
+                       cmdenv_get(env, "dscp")?
+                       lldpctl_atom_set_str(med_policy,
+                           lldpctl_k_med_policy_dscp,
+                           cmdenv_get(env, "dscp")):
+                       lldpctl_atom_set_int(med_policy,
+                           lldpctl_k_med_policy_dscp,
+                           0)) == NULL)
+                       log_warnx("lldpctl",
+                           "unable to set LLDP MED policy value for %s on %s. %s.",
+                           what, name, lldpctl_last_strerror(conn));
+               else {
+                       if (lldpctl_atom_set(port, lldpctl_k_port_med_policies,
+                               med_policy) == NULL) {
+                               log_warnx("lldpctl", "unable to set LLDP MED policy on %s. %s.",
+                                   name, lldpctl_last_strerror(conn));
+                       } else
+                               log_info("lldpctl", "LLDP-MED policy has been set for port %s",
+                                   name);
+               }
+
+       end:
+               lldpctl_atom_dec_ref(med_policy);
+               lldpctl_atom_dec_ref(med_policies);
+               lldpctl_atom_dec_ref(port);
        }
-       e2 = strchr(e1, ':');
-       if (e2 != NULL) *e2 = '\0';
-       pos++;
-       return e1;
+       return 1;
 }
 
 static int
-get_next_and_set(lldpctl_atom_t *atom, char *string,
-    const char *what, lldpctl_key_t key, int mandatory)
+cmd_medpower(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
 {
-       char *e1 = get_next(atom, string, what, mandatory);
-       if (e1 == NULL) return -1;
-       if (lldpctl_atom_set_str(atom, key, e1) == NULL) {
-               log_warnx(NULL, "unable to set %s. %s.", what,
-                       lldpctl_last_strerror(lldpctl_atom_get_connection(atom)));
-               return 0;
+       log_debug("lldpctl", "set MED power");
+       lldpctl_atom_t *iface;
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               const char *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+               lldpctl_atom_t *port = lldpctl_get_port(iface);
+               lldpctl_atom_t *med_power;
+               const char *what = NULL;
+
+               med_power = lldpctl_atom_get(port, lldpctl_k_port_med_power);
+               if (med_power == NULL) {
+                       log_warnx("lldpctl", "unable to set LLDP-MED power: support seems unavailable");
+                       goto end;
+               }
+
+               if ((what = "device type", lldpctl_atom_set_str(med_power,
+                           lldpctl_k_med_power_type,
+                           cmdenv_get(env, "device-type"))) == NULL ||
+                   (what = "power source", lldpctl_atom_set_str(med_power,
+                       lldpctl_k_med_power_source,
+                       cmdenv_get(env, "source"))) == NULL ||
+                   (what = "power priority", lldpctl_atom_set_str(med_power,
+                       lldpctl_k_med_power_priority,
+                       cmdenv_get(env, "priority"))) == NULL ||
+                   (what = "power value", lldpctl_atom_set_str(med_power,
+                       lldpctl_k_med_power_val,
+                       cmdenv_get(env, "value"))) == NULL)
+                       log_warnx("lldpctl",
+                           "unable to set LLDP MED power value for %s on %s. %s.",
+                           what, name, lldpctl_last_strerror(conn));
+               else {
+                       if (lldpctl_atom_set(port, lldpctl_k_port_med_power,
+                               med_power) == NULL) {
+                               log_warnx("lldpctl", "unable to set LLDP MED power on %s. %s.",
+                                   name, lldpctl_last_strerror(conn));
+                       } else
+                               log_info("lldpctl", "LLDP-MED power has been set for port %s",
+                                   name);
+               }
+
+       end:
+               lldpctl_atom_dec_ref(med_power);
+               lldpctl_atom_dec_ref(port);
        }
        return 1;
 }
 
-/**
- * Parse dot3 power string.
- *
- * @param value String describing the new value.
- * @param power Atom to use to insert new values.
- * @return 1 on success, 0 otherwise.
- */
 static int
-parse_dot3_power(char *value, lldpctl_atom_t *power)
+cmd_dot3power(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
 {
-       int rc = 0;
-
-       if (get_next_and_set(power, value, "device type", lldpctl_k_dot3_power_devicetype, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power support", lldpctl_k_dot3_power_supported, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power enableness", lldpctl_k_dot3_power_enabled, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "pair control ability", lldpctl_k_dot3_power_paircontrol, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power pairs", lldpctl_k_dot3_power_pairs, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "class", lldpctl_k_dot3_power_class, 1) != 1)
-               return 0;
-       rc = get_next_and_set(power, value, "power type", lldpctl_k_dot3_power_type, 0);
-       if (rc == 0) return 0;
-       if (rc == -1) return 1;
-
-       if (get_next_and_set(power, value, "power source", lldpctl_k_dot3_power_source, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power priority", lldpctl_k_dot3_power_priority, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power requested", lldpctl_k_dot3_power_requested, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power allocated", lldpctl_k_dot3_power_allocated, 1) != 1)
-               return 0;
+       log_debug("lldpctl", "set dot3 power");
+       lldpctl_atom_t *iface;
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               const char *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+               lldpctl_atom_t *port = lldpctl_get_port(iface);
+               lldpctl_atom_t *dot3_power;
+               const char *what = NULL;
+               int ok = 1;
+
+               dot3_power = lldpctl_atom_get(port, lldpctl_k_port_dot3_power);
+               if (dot3_power == NULL) {
+                       log_warnx("lldpctl", "unable to set Dot3 power: support seems unavailable");
+                       goto end;
+               }
+
+               if ((what = "device type", lldpctl_atom_set_str(dot3_power,
+                           lldpctl_k_dot3_power_devicetype,
+                           cmdenv_get(env, "device-type"))) == NULL ||
+                   /* Flags */
+                   (what = "supported flag", lldpctl_atom_set_int(dot3_power,
+                       lldpctl_k_dot3_power_supported,
+                       cmdenv_get(env, "supported")?1:0)) == NULL ||
+                   (what = "enabled flag", lldpctl_atom_set_int(dot3_power,
+                       lldpctl_k_dot3_power_enabled,
+                       cmdenv_get(env, "enabled")?1:0)) == NULL ||
+                   (what = "paircontrol flag", lldpctl_atom_set_int(dot3_power,
+                       lldpctl_k_dot3_power_paircontrol,
+                       cmdenv_get(env, "paircontrol")?1:0)) == NULL ||
+                   /* Powerpairs */
+                   (what = "power pairs", lldpctl_atom_set_str(dot3_power,
+                       lldpctl_k_dot3_power_pairs,
+                       cmdenv_get(env, "powerpairs"))) == NULL ||
+                   /* Class */
+                   (what = "power class", cmdenv_get(env, "class")?
+                       lldpctl_atom_set_str(dot3_power,
+                           lldpctl_k_dot3_power_class,
+                           cmdenv_get(env, "class")):
+                       lldpctl_atom_set_int(dot3_power,
+                           lldpctl_k_dot3_power_class, 0)) == NULL ||
+                   (what = "802.3at type", lldpctl_atom_set_int(dot3_power,
+                       lldpctl_k_dot3_power_type, 0)) == NULL) {
+                       log_warnx("lldpctl",
+                           "unable to set LLDP Dot3 power value for %s on %s. %s.",
+                           what, name, lldpctl_last_strerror(conn));
+                       ok = 0;
+               } else if (cmdenv_get(env, "typeat")) {
+                       int typeat = cmdenv_get(env, "typeat")[0] - '0';
+                       const char *source = cmdenv_get(env, "source");
+                       if ((what = "802.3at type", lldpctl_atom_set_int(dot3_power,
+                                   lldpctl_k_dot3_power_type,
+                                   typeat)) == NULL ||
+                           (what = "source", lldpctl_atom_set_int(dot3_power,
+                               lldpctl_k_dot3_power_source,
+                               (!strcmp(source, "primary"))?LLDP_DOT3_POWER_SOURCE_PRIMARY:
+                               (!strcmp(source, "backup"))? LLDP_DOT3_POWER_SOURCE_BACKUP:
+                               (!strcmp(source, "pse"))?    LLDP_DOT3_POWER_SOURCE_PSE:
+                               (!strcmp(source, "local"))?  LLDP_DOT3_POWER_SOURCE_LOCAL:
+                               (!strcmp(source, "both"))?   LLDP_DOT3_POWER_SOURCE_BOTH:
+                               LLDP_DOT3_POWER_SOURCE_UNKNOWN)) == NULL ||
+                           (what = "priority", lldpctl_atom_set_str(dot3_power,
+                               lldpctl_k_dot3_power_priority,
+                               cmdenv_get(env, "priority"))) == NULL ||
+                           (what = "requested power", lldpctl_atom_set_str(dot3_power,
+                               lldpctl_k_dot3_power_requested,
+                               cmdenv_get(env, "requested"))) == NULL ||
+                           (what = "allocated power", lldpctl_atom_set_str(dot3_power,
+                               lldpctl_k_dot3_power_allocated,
+                               cmdenv_get(env, "allocated"))) == NULL) {
+                               log_warnx("lldpctl", "unable to set LLDP Dot3 power value for %s on %s. %s.",
+                                   what, name, lldpctl_last_strerror(conn));
+                               ok = 0;
+                       }
+               }
+               if (ok) {
+                       if (lldpctl_atom_set(port, lldpctl_k_port_dot3_power,
+                               dot3_power) == NULL) {
+                               log_warnx("lldpctl", "unable to set LLDP Dot3 power on %s. %s.",
+                                   name, lldpctl_last_strerror(conn));
+                       } else
+                               log_info("lldpctl", "LLDP Dot3 power has been set for port %s",
+                                   name);
+               }
 
+       end:
+               lldpctl_atom_dec_ref(dot3_power);
+               lldpctl_atom_dec_ref(port);
+       }
        return 1;
 }
 
+#define cmd_no_medlocation_coordinate cmd_not_implemented
+#define cmd_no_medlocation_address    cmd_not_implemented
+#define cmd_no_medlocation_elin       cmd_not_implemented
+#define cmd_no_medpolicy              cmd_not_implemented
+#define cmd_no_medpower               cmd_not_implemented
+#define cmd_no_dot3power              cmd_not_implemented
+
 /**
- * Parse LLDP-MED power string.
- *
- * @param value String describing the new value.
- * @param power Atom to use to insert new values.
- * @return 1 on success, 0 otherwise.
+ * Restrict the command to some ports.
  */
-static int
-parse_med_power(char *value, lldpctl_atom_t *power)
+static void
+restrict_ports(struct cmd_node *root)
 {
-       if (get_next_and_set(power, value, "device type", lldpctl_k_med_power_type, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power source", lldpctl_k_med_power_source, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power priority", lldpctl_k_med_power_priority, 1) != 1)
-               return 0;
-       if (get_next_and_set(power, value, "power value", lldpctl_k_med_power_val, 1) != 1)
-               return 0;
+       /* 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");
+}
 
-       return 1;
+/**
+ * Register `configure med location coordinate` commands.
+ */
+static void
+register_commands_medloc_coord(struct cmd_node *configure_medlocation)
+{
+       /* MED location coordinate (set) */
+       struct cmd_node *configure_medloc_coord = commands_new(
+               configure_medlocation,
+               "coordinate", "MED location coordinate configuration",
+               NULL, NULL, NULL);
+       commands_new(configure_medloc_coord,
+           NEWLINE, "Configure MED location coordinates",
+           cmd_check_env, cmd_medlocation_coordinate,
+           "latitude,longitude,altitude,altitude-unit,datum");
+       commands_new(
+               commands_new(
+                       configure_medloc_coord,
+                       "latitude", "Specify latitude",
+                       cmd_check_no_env, NULL, "latitude"),
+               NULL, "Latitude as xx.yyyyN or xx.yyyyS",
+               NULL, cmd_store_env_value_and_pop2, "latitude");
+       commands_new(
+               commands_new(
+                       configure_medloc_coord,
+                       "longitude", "Specify longitude",
+                       cmd_check_no_env, NULL, "longitude"),
+               NULL, "Longitude as xx.yyyyE or xx.yyyyW",
+               NULL, cmd_store_env_value_and_pop2, "longitude");
+       struct cmd_node *altitude = commands_new(
+               commands_new(
+                       configure_medloc_coord,
+                       "altitude", "Specify altitude",
+                       cmd_check_no_env, NULL, "altitude"),
+               NULL, "Altitude",
+               NULL, cmd_store_env_value, "altitude");
+       commands_new(altitude,
+           "m", "meters",
+           NULL, cmd_store_env_value_and_pop3, "altitude-unit");
+       commands_new(altitude,
+           "f", "floors",
+           NULL, cmd_store_env_value_and_pop3, "altitude-unit");
+
+       struct cmd_node *datum = commands_new(configure_medloc_coord,
+           "datum", "Specify datum",
+           cmd_check_no_env, NULL, "datum");
+       for (lldpctl_map_t *datum_map =
+                lldpctl_key_get_map(lldpctl_k_med_location_geoid);
+            datum_map->string;
+            datum_map++)
+               commands_new(datum, datum_map->string, NULL,
+                   NULL, cmd_store_env_value_and_pop2, "datum");
 }
 
 /**
- * Parse LLDP-MED policy string.
- *
- * @param value String describing the new value.
- * @param power Atom to use to insert new values.
- * @return 1 on success, 0 otherwise.
+ * Register `configure med location address` commands.
  */
-static int
-parse_med_policy(char *value, lldpctl_atom_t *policy)
+static void
+register_commands_medloc_addr(struct cmd_node *configure_medlocation)
 {
-       if (get_next_and_set(policy, value, "application type", lldpctl_k_med_policy_type, 1) != 1)
-               return 0;
-       if (get_next_and_set(policy, value, "unknown flag", lldpctl_k_med_policy_unknown, 1) != 1)
-               return 0;
-       if (get_next_and_set(policy, value, "tagged flag", lldpctl_k_med_policy_tagged, 1) != 1)
-               return 0;
-       if (get_next_and_set(policy, value, "VLAN ID", lldpctl_k_med_policy_vid, 1) != 1)
-               return 0;
-       if (get_next_and_set(policy, value, "Layer 2 priority", lldpctl_k_med_policy_priority, 1) != 1)
-               return 0;
-       if (get_next_and_set(policy, value, "DSCP", lldpctl_k_med_policy_dscp, 1) != 1)
-               return 0;
+       /* MED location address (set) */
+       struct cmd_node *configure_medloc_addr = commands_new(
+               configure_medlocation,
+               "address", "MED location address configuration",
+               NULL, NULL, NULL);
+       commands_new(configure_medloc_addr,
+           NEWLINE, "Configure MED location address",
+           cmd_check_env, cmd_medlocation_address,
+           "country");
 
-       return 1;
+       /* Country */
+       commands_new(
+               commands_new(
+                       configure_medloc_addr,
+                       "country", "Specify country (mandatory)",
+                       cmd_check_no_env, NULL, "country"),
+               NULL, "Country as a two-letter code",
+               NULL, cmd_store_env_value_and_pop2, "country");
+
+       /* Other fields */
+       for (lldpctl_map_t *addr_map =
+                lldpctl_key_get_map(lldpctl_k_med_civicaddress_type);
+            addr_map->string;
+            addr_map++)
+               commands_new(
+                       commands_new(
+                               configure_medloc_addr,
+                               strdup(totag(addr_map->string)), /* TODO: memory leak, happens once */
+                               addr_map->string,
+                               cmd_check_no_env, NULL, addr_map->string),
+                       NULL, addr_map->string,
+                       NULL, cmd_store_env_value_and_pop2, addr_map->string);
 }
 
 /**
- * Parse LLDP-MED location string.
- *
- * @param value String describing the new value.
- * @param power Atom to use to insert new values.
- * @return 1 on success, 0 otherwise.
+ * Register `configure med location elin` commands.
  */
+static void
+register_commands_medloc_elin(struct cmd_node *configure_medlocation)
+{
+       /* MED location elin (set) */
+       commands_new(
+               commands_new(
+                       commands_new(
+                               configure_medlocation,
+                               "elin", "MED location ELIN configuration",
+                               NULL, NULL, NULL),
+                       NULL, "ELIN number",
+                       NULL, cmd_store_env_value, "elin"),
+               NEWLINE, "Set MED location ELIN number",
+               NULL, cmd_medlocation_elin, NULL);
+}
+
+/**
+ * Register `configure med location` commands.
+ */
+static void
+register_commands_medloc(struct cmd_node *configure_med, struct cmd_node *unconfigure_med)
+{
+       struct cmd_node *configure_medlocation = commands_new(
+               configure_med,
+               "location", "MED location configuration",
+               NULL, NULL, NULL);
+
+       register_commands_medloc_coord(configure_medlocation);
+       register_commands_medloc_addr(configure_medlocation);
+       register_commands_medloc_elin(configure_medlocation);
+
+       /* MED location (unset) */
+       struct cmd_node *unconfigure_medlocation = commands_new(
+               unconfigure_med,
+               "location", "MED location configuration",
+               NULL, NULL, NULL);
+       commands_new(
+               commands_new(
+                       unconfigure_medlocation,
+                       "coordinate", "Unconfigure MED location coordinate",
+                       NULL, NULL, NULL),
+               NEWLINE, "Unconfigure MED location coordinate",
+               NULL, cmd_no_medlocation_coordinate, NULL);
+       commands_new(
+               commands_new(
+                       unconfigure_medlocation,
+                       "coordinate", "Unconfigure MED location address",
+                       NULL, NULL, NULL),
+               NEWLINE, "Unconfigure MED location address",
+               NULL, cmd_no_medlocation_address, NULL);
+       commands_new(
+               commands_new(
+                       unconfigure_medlocation,
+                       "coordinate", "Unconfigure MED location ELIN",
+                       NULL, NULL, NULL),
+               NEWLINE, "Unconfigure MED location ELIN",
+               NULL, cmd_no_medlocation_elin, NULL);
+}
+
 static int
-parse_med_location(char *value, lldpctl_atom_t *location)
+cmd_check_application_but_no(struct cmd_env *env, void *arg)
 {
-       int format, stop = 0;
-       lldpctl_atom_t *cael, *caels;
-       char *type;
-
-       if (get_next_and_set(location, value, "location format", lldpctl_k_med_location_format, 1) != 1)
-               return 0;
-       format = lldpctl_atom_get_int(location, lldpctl_k_med_location_format);
-       switch (format) {
-       case LLDP_MED_LOCFORMAT_COORD:
-               if (get_next_and_set(location, value, "latitude", lldpctl_k_med_location_latitude, 1) != 1)
-                       return 0;
-               if (get_next_and_set(location, value, "longitude", lldpctl_k_med_location_longitude, 1) != 1)
-                       return 0;
-               if (get_next_and_set(location, value, "altitude", lldpctl_k_med_location_altitude, 1) != 1)
-                       return 0;
-               if (get_next_and_set(location, value, "altitude unit", lldpctl_k_med_location_altitude_unit, 1) != 1)
-                       return 0;
-               if (get_next_and_set(location, value, "datum", lldpctl_k_med_location_geoid, 1) != 1)
-                       return 0;
-               return 1;
-       case LLDP_MED_LOCFORMAT_CIVIC:
-               if (get_next_and_set(location, value, "country", lldpctl_k_med_location_country, 1) != 1)
-                       return 0;
-               while ((type = get_next(location, value, "civic address type", 0)) != NULL &&
-                   !stop) {
-                       /* Next we have the element addresses */
-                       caels = lldpctl_atom_get(location, lldpctl_k_med_location_ca_elements);
-                       cael = lldpctl_atom_create(caels);
-
-                       if (lldpctl_atom_set_str(cael, lldpctl_k_med_civicaddress_type, type) != NULL) {
-                               if (get_next_and_set(cael, value, "civic address value",
-                                       lldpctl_k_med_civicaddress_value, 1) == 1) {
-                                       if (lldpctl_atom_set(location, lldpctl_k_med_location_ca_elements,
-                                               cael) == NULL) {
-                                               log_warnx(NULL, "unable to add a civic address element. %s",
-                                                   lldpctl_last_strerror(lldpctl_atom_get_connection(location)));
-                                               stop = 1;
-                                       }
-                               } else stop = 1;
-                       } else {
-                               log_warnx(NULL, "unable to set civic address type. %s.",
-                                   lldpctl_last_strerror(lldpctl_atom_get_connection(cael)));
-                               stop = 1;
-                       }
+       const char *what = arg;
+       if (!cmdenv_get(env, "application")) return 0;
+       if (cmdenv_get(env, what)) return 0;
+       return 1;
+}
+static int
+cmd_store_something_env_value_and_pop2(const char *what,
+    struct cmd_env *env, void *value)
+{
+       return (cmdenv_put(env, what, value) != -1 &&
+           cmdenv_pop(env, 2) != -1);
+}
+static int
+cmd_store_app_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *value)
+{
+       return cmd_store_something_env_value_and_pop2("application", env, value);
+}
+static int
+cmd_store_powerpairs_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *value)
+{
+       return cmd_store_something_env_value_and_pop2("powerpairs", env, value);
+}
+static int
+cmd_store_class_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *value)
+{
+       return cmd_store_something_env_value_and_pop2("class", env, value);
+}
+static int
+cmd_store_prio_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *value)
+{
+       return cmd_store_something_env_value_and_pop2("priority", env, value);
+}
+static int
+cmd_store_app_env_value(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *value)
+{
+       return (cmdenv_put(env, "application", value) != -1);
+}
 
-                       lldpctl_atom_dec_ref(cael);
-                       lldpctl_atom_dec_ref(caels);
-               }
-               if (stop) return 0;
-               return 1;
-       case LLDP_MED_LOCFORMAT_ELIN:
-               if (get_next_and_set(location, value, "ELIN number", lldpctl_k_med_location_elin, 1) != 1)
-                       return 0;
-               return 1;
-       default:
-               log_warnx(NULL, "unable to determine the requested location format");
-               return 0;
+/**
+ * Register `configure med policy` commands.
+ */
+static void
+register_commands_medpol(struct cmd_node *configure_med, struct cmd_node *unconfigure_med)
+{
+       struct cmd_node *configure_medpolicy = commands_new(
+               configure_med,
+               "policy", "MED policy configuration",
+               NULL, NULL, NULL);
+
+       /* MED policy (un set) */
+       struct cmd_node *unconfigure_application =
+           commands_new(
+                   commands_new(
+                           unconfigure_med,
+                           "policy", "MED policy configuration",
+                           NULL, NULL, NULL),
+                   "application", "MED policy application",
+                   NULL, NULL, NULL);
+
+       commands_new(
+               configure_medpolicy,
+               NEWLINE, "Apply new MED policy",
+               cmd_check_env, cmd_medpolicy, "application");
+
+       /* Application */
+       struct cmd_node *configure_application =
+           commands_new(
+                   configure_medpolicy,
+                   "application", "MED policy application",
+                   cmd_check_no_env, NULL, "application");
+
+       for (lldpctl_map_t *pol_map =
+                lldpctl_key_get_map(lldpctl_k_med_policy_type);
+            pol_map->string;
+            pol_map++) {
+               char *tag = strdup(totag(pol_map->string)); /* TODO: memory leak, happens once */
+               commands_new(
+                       commands_new(
+                               unconfigure_application,
+                               tag,
+                               pol_map->string,
+                               NULL, cmd_store_app_env_value, pol_map->string),
+                       NEWLINE, "Remove specified MED policy",
+                       NULL, cmd_no_medpolicy, NULL);
+               commands_new(
+                       configure_application,
+                       tag,
+                       pol_map->string,
+                       NULL, cmd_store_app_env_value_and_pop2, pol_map->string);
+       }
+
+       /* Remaining keywords */
+       commands_new(
+               configure_medpolicy,
+               "unknown", "Set unknown flag",
+               cmd_check_application_but_no, cmd_store_env_and_pop, "unknown");
+       commands_new(
+               commands_new(
+                       configure_medpolicy,
+                       "vlan", "VLAN advertising",
+                       cmd_check_application_but_no, NULL, "vlan"),
+               NULL, "VLAN ID to advertise",
+               NULL, cmd_store_env_value_and_pop2, "vlan");
+       commands_new(
+               commands_new(
+                       configure_medpolicy,
+                       "dscp", "DiffServ advertising",
+                       cmd_check_application_but_no, NULL, "dscp"),
+               NULL, "DSCP value to advertise (between 0 and 63)",
+               NULL, cmd_store_env_value_and_pop2, "dscp");
+       struct cmd_node *priority =
+           commands_new(
+                   configure_medpolicy,
+                   "priority", "MED policy priority",
+                   cmd_check_application_but_no, NULL, "priority");
+       for (lldpctl_map_t *prio_map =
+                lldpctl_key_get_map(lldpctl_k_med_policy_priority);
+            prio_map->string;
+            prio_map++) {
+               char *tag = strdup(totag(prio_map->string)); /* TODO: memory leak, happens once */
+               commands_new(
+                       priority,
+                       tag, prio_map->string,
+                       NULL, cmd_store_prio_env_value_and_pop2, prio_map->string);
        }
+}
 
+static int
+cmd_check_type_but_no(struct cmd_env *env, void *arg)
+{
+       const char *what = arg;
+       if (!cmdenv_get(env, "device-type")) return 0;
+       if (cmdenv_get(env, what)) return 0;
        return 1;
 }
+static int
+cmd_check_typeat_but_no(struct cmd_env *env, void *arg)
+{
+       const char *what = arg;
+       if (!cmdenv_get(env, "typeat")) return 0;
+       if (cmdenv_get(env, what)) return 0;
+       return 1;
+}
+static int
+cmd_check_type(struct cmd_env *env, const char *type)
+{
+       const char *etype = cmdenv_get(env, "device-type");
+       if (!etype) return 0;
+       return (!strcmp(type, etype));
+}
+static int
+cmd_check_pse(struct cmd_env *env, void *arg)
+{
+       return cmd_check_type(env, "pse");
+}
+static int
+cmd_check_pd(struct cmd_env *env, void *arg)
+{
+       return cmd_check_type(env, "pd");
+}
+
+static void
+register_commands_pow_source(struct cmd_node *source)
+{
+       commands_new(source,
+           "unknown", "Unknown power source",
+           NULL, cmd_store_env_and_pop, "source");
+       commands_new(source,
+           "primary", "Primary power source",
+           cmd_check_pse, cmd_store_env_value_and_pop2, "source");
+       commands_new(source,
+           "backup", "Backup power source",
+           cmd_check_pse, cmd_store_env_value_and_pop2, "source");
+       commands_new(source,
+           "pse", "Power source is PSE",
+           cmd_check_pd, cmd_store_env_value_and_pop2, "source");
+       commands_new(source,
+           "local", "Local power source",
+           cmd_check_pd, cmd_store_env_value_and_pop2, "source");
+       commands_new(source,
+           "both", "Both PSE and local source available",
+           cmd_check_pd, cmd_store_env_value_and_pop2, "source");
+}
+
+static void
+register_commands_pow_priority(struct cmd_node *priority, int key)
+{
+       for (lldpctl_map_t *prio_map =
+                lldpctl_key_get_map(key);
+            prio_map->string;
+            prio_map++) {
+               char *tag = strdup(totag(prio_map->string)); /* TODO: memory leak, happens once */
+               commands_new(
+                       priority,
+                       tag,
+                       prio_map->string,
+                       NULL, cmd_store_prio_env_value_and_pop2, prio_map->string);
+       }
+}
 
 /**
- * Modify the interfaces specified on the command line.
- *
- * @param conn    Connection to lldpd.
- * @param argc    Number of arguments.
- * @param argv    Array of arguments.
- * @param ifindex Index of the first non optional argument
+ * Register `configure med power` commands.
  */
-void
-modify_interfaces(lldpctl_conn_t *conn,
-    int argc, char **argv, int ifindex)
+static void
+register_commands_medpow(struct cmd_node *configure_med, struct cmd_node *unconfigure_med)
 {
-       int i, ch;
-       const char *iface_name;
-       lldpctl_atom_t *ifaces, *iface;
-       lldpctl_atom_t *port;
-
-       ifaces = lldpctl_get_interfaces(conn);
-       if (!ifaces) {
-               log_warnx(NULL, "not able to get the list of interfaces: %s", lldpctl_strerror(lldpctl_last_error(conn)));
-               return;
+       commands_new(
+               commands_new(unconfigure_med,
+                   "power", "MED power configuration",
+                   NULL, NULL, NULL),
+               NEWLINE, "Disable advertising of LLDP-MED POE-MDI TLV",
+               NULL, cmd_no_medpower, NULL);
+
+       struct cmd_node *configure_medpower = commands_new(
+               configure_med,
+               "power", "MED power configuration",
+               NULL, NULL, NULL);
+
+       commands_new(
+               configure_medpower,
+               NEWLINE, "Apply new MED power configuration",
+               cmd_check_env, cmd_medpower, "device-type,source,priority,value");
+
+       /* Type: PSE or PD */
+       commands_new(
+               configure_medpower,
+               "pd", "MED power consumer",
+               cmd_check_no_env, cmd_store_env_value_and_pop, "device-type");
+       commands_new(
+               configure_medpower,
+               "pse", "MED power provider",
+               cmd_check_no_env, cmd_store_env_value_and_pop, "device-type");
+
+       /* Source */
+       struct cmd_node *source = commands_new(
+               configure_medpower,
+               "source", "MED power source",
+               cmd_check_type_but_no, NULL, "source");
+       register_commands_pow_source(source);
+
+       /* Priority */
+       struct cmd_node *priority = commands_new(
+               configure_medpower,
+               "priority", "MED power priority",
+               cmd_check_type_but_no, NULL, "priority");
+       register_commands_pow_priority(priority, lldpctl_k_med_power_priority);
+
+       /* Value */
+       commands_new(
+               commands_new(configure_medpower,
+                   "value", "MED power value",
+                   cmd_check_type_but_no, NULL, "value"),
+               NULL, "MED power value in milliwatts",
+               NULL, cmd_store_env_value_and_pop2, "value");
+}
+
+static int
+cmd_check_env_power(struct cmd_env *env, void *nothing)
+{
+       /* We need type and powerpair but if we have typeat, we also request
+        * source, priority, requested and allocated. */
+       if (!cmdenv_get(env, "device-type")) return 0;
+       if (!cmdenv_get(env, "powerpairs")) return 0;
+       if (cmdenv_get(env, "typeat")) {
+               return (!!cmdenv_get(env, "source") &&
+                   !!cmdenv_get(env, "priority") &&
+                   !!cmdenv_get(env, "requested") &&
+                   !!cmdenv_get(env, "allocated"));
        }
+       return 1;
+}
 
-       lldpctl_atom_foreach(ifaces, iface) {
-               /* Only process specified interfaces or all interfaces if none
-                * is specified. */
-               iface_name = lldpctl_atom_get_str(iface,
-                   lldpctl_k_interface_name);
-               if (ifindex < argc) {
-                       for (i = ifindex; i < argc; i++)
-                               if (strcmp(argv[i],
-                                       iface_name) == 0)
-                                       break;
-                       if (i == argc)
-                               continue;
-               }
+/**
+ * Register `configure med dot3` commands.
+ */
+static void
+register_commands_dot3pow(struct cmd_node *configure_dot3, struct cmd_node *unconfigure_dot3)
+{
+       commands_new(
+               commands_new(unconfigure_dot3,
+                   "power", "Dot3 power configuration",
+                   NULL, NULL, NULL),
+               NEWLINE, "Disable advertising of Dot3 POE-MDI TLV",
+               NULL, cmd_no_dot3power, NULL);
 
-               port      = lldpctl_get_port(iface);
-
-               optind = 1;
-               while ((ch = getopt(argc, argv, LLDPCTL_ARGS)) != -1) {
-                       lldpctl_atom_t *dot3_power;
-                       lldpctl_atom_t *med_power;
-                       lldpctl_atom_t *med_policy, *med_policies;
-                       lldpctl_atom_t *med_location, *med_locations;
-
-                       switch (ch) {
-                       case 'o':
-                               /* Dot3 power */
-                               dot3_power = lldpctl_atom_get(port, lldpctl_k_port_dot3_power);
-                               if (dot3_power == NULL) {
-                                       log_warnx(NULL, "unable to set Dot3 power: support seems unavailable");
-                                       break;
-                               }
-                               if (parse_dot3_power(optarg, dot3_power)) {
-                                       if (lldpctl_atom_set(port, lldpctl_k_port_dot3_power,
-                                               dot3_power) == NULL)
-                                               log_warnx(NULL, "unable to set Dot3 power. %s",
-                                                   lldpctl_strerror(lldpctl_last_error(conn)));
-                                       else
-                                               log_info(NULL, "Dot3 power has been set for port %s",
-                                                   iface_name);
-                               }
-                               lldpctl_atom_dec_ref(dot3_power);
-                               break;
-                       case 'O':
-                               /* LLDP-MED power */
-                               med_power = lldpctl_atom_get(port, lldpctl_k_port_med_power);
-                               if (med_power == NULL) {
-                                       log_warnx(NULL, "unable to set LLDP-MED power: support seems unavailable");
-                                       break;
-                               }
-                               if (parse_med_power(optarg, med_power)) {
-                                       if (lldpctl_atom_set(port, lldpctl_k_port_med_power,
-                                               med_power) == NULL)
-                                               log_warnx(NULL, "unable to set LLDP-MED power. %s",
-                                                   lldpctl_strerror(lldpctl_last_error(conn)));
-                                       else
-                                               log_info(NULL, "LLDP-MED power has been set for port %s",
-                                                   iface_name);
-                               }
-                               lldpctl_atom_dec_ref(med_power);
-                               break;
-                       case 'P':
-                               /* LLDP-MED network policy */
-                               med_policies = lldpctl_atom_get(port, lldpctl_k_port_med_policies);
-                               if (med_policies == NULL) {
-                                       log_warnx(NULL, "unable to set LLDP-MED policy: support seems unavailable");
-                                       break;
-                               }
-                               /* We select the first policy. Since we will
-                                * modify the application type, it is not
-                                * necessary to select the one with the
-                                * appropriate index. */
-                               med_policy = lldpctl_atom_iter_value(med_policies,
-                                       lldpctl_atom_iter_next(med_policies,
-                                           lldpctl_atom_iter(med_policies)));
-                               if (parse_med_policy(optarg, med_policy)) {
-                                       if (lldpctl_atom_set(port, lldpctl_k_port_med_policies,
-                                               med_policy) == NULL)
-                                               log_warnx(NULL, "unable to set LLDP-MED policy. %s",
-                                                   lldpctl_strerror(lldpctl_last_error(conn)));
-                                       else
-                                               log_info(NULL, "LLDP-MED policy has been set for port %s",
-                                                   iface_name);
-                               }
-                               lldpctl_atom_dec_ref(med_policy);
-                               lldpctl_atom_dec_ref(med_policies);
-                               break;
-                       case 'L':
-                               /* LLDP-MED location */
-                               med_locations = lldpctl_atom_get(port, lldpctl_k_port_med_locations);
-                               if (med_locations == NULL) {
-                                       log_warnx(NULL, "unable to set LLDP-MED location: support seems unavailable");
-                                       break;
-                               }
-                               /* As for policy, we pick the first and it will
-                                * be reset when setting the format. No need to
-                                * pick the one with the appropriate index. */
-                               med_location = lldpctl_atom_iter_value(med_locations,
-                                   lldpctl_atom_iter_next(med_locations,
-                                       lldpctl_atom_iter(med_locations)));
-                               if (parse_med_location(optarg, med_location)) {
-                                       if (lldpctl_atom_set(port, lldpctl_k_port_med_locations,
-                                               med_location) == NULL)
-                                               log_warnx(NULL, "unable to set LLDP-MED location. %s",
-                                                       lldpctl_strerror(lldpctl_last_error(conn)));
-                                       else
-                                               log_info(NULL, "LLDP-MED location has been set for port %s",
-                                                   iface_name);
-                               }
-                               lldpctl_atom_dec_ref(med_location);
-                               lldpctl_atom_dec_ref(med_locations);
-                               break;
-                       default:
-                               /* We shouldn't be here... */
-                               break;
-                       }
-               }
+       struct cmd_node *configure_dot3power = commands_new(
+               configure_dot3,
+               "power", "Dot3 power configuration",
+               NULL, NULL, NULL);
 
-               lldpctl_atom_dec_ref(port);
+       commands_new(
+               configure_dot3power,
+               NEWLINE, "Apply new Dot3 power configuration",
+               cmd_check_env_power, cmd_dot3power, NULL);
+
+       /* Type: PSE or PD */
+       commands_new(
+               configure_dot3power,
+               "pd", "Dot3 power consumer",
+               cmd_check_no_env, cmd_store_env_value_and_pop, "device-type");
+       commands_new(
+               configure_dot3power,
+               "pse", "Dot3 power provider",
+               cmd_check_no_env, cmd_store_env_value_and_pop, "device-type");
+
+       /* Flags */
+       commands_new(
+               configure_dot3power,
+               "supported", "MDI power support present",
+               cmd_check_type_but_no, cmd_store_env_and_pop, "supported");
+       commands_new(
+               configure_dot3power,
+               "enabled", "MDI power support enabled",
+               cmd_check_type_but_no, cmd_store_env_and_pop, "enabled");
+       commands_new(
+               configure_dot3power,
+               "paircontrol", "MDI power pair can be selected",
+               cmd_check_type_but_no, cmd_store_env_and_pop, "paircontrol");
+
+       /* Power pairs */
+       struct cmd_node *powerpairs = commands_new(
+               configure_dot3power,
+               "powerpairs", "Which pairs are currently used for power (mandatory)",
+               cmd_check_type_but_no, NULL, "powerpairs");
+       for (lldpctl_map_t *pp_map =
+                lldpctl_key_get_map(lldpctl_k_dot3_power_pairs);
+            pp_map->string;
+            pp_map++) {
+               commands_new(
+                       powerpairs,
+                       pp_map->string,
+                       pp_map->string,
+                       NULL, cmd_store_powerpairs_env_value_and_pop2, pp_map->string);
+       }
+
+       /* Class */
+       struct cmd_node *class = commands_new(
+               configure_dot3power,
+               "class", "Power class",
+               cmd_check_type_but_no, NULL, "class");
+       for (lldpctl_map_t *class_map =
+                lldpctl_key_get_map(lldpctl_k_dot3_power_class);
+            class_map->string;
+            class_map++) {
+               const char *tag = strdup(totag(class_map->string));
+               commands_new(
+                       class,
+                       tag,
+                       class_map->string,
+                       NULL, cmd_store_class_env_value_and_pop2, class_map->string);
        }
 
-       lldpctl_atom_dec_ref(ifaces);
+       /* 802.3at type */
+       struct cmd_node *typeat = commands_new(
+               configure_dot3power,
+               "type", "802.3at device type",
+               cmd_check_type_but_no, NULL, "typeat");
+       commands_new(typeat,
+           "1", "802.3at type 1",
+           NULL, cmd_store_env_value_and_pop2, "typeat");
+       commands_new(typeat,
+           "2", "802.3at type 2",
+           NULL, cmd_store_env_value_and_pop2, "typeat");
+
+       /* Source */
+       struct cmd_node *source = commands_new(
+               configure_dot3power,
+               "source", "802.3at dot3 power source (mandatory)",
+               cmd_check_typeat_but_no, NULL, "source");
+       register_commands_pow_source(source);
+
+       /* Priority */
+       struct cmd_node *priority = commands_new(
+               configure_dot3power,
+               "priority", "802.3at dot3 power priority (mandatory)",
+               cmd_check_typeat_but_no, NULL, "priority");
+       register_commands_pow_priority(priority, lldpctl_k_dot3_power_priority);
+
+       /* Values */
+       commands_new(
+               commands_new(configure_dot3power,
+                   "requested", "802.3at dot3 power value requested (mandatory)",
+                   cmd_check_typeat_but_no, NULL, "requested"),
+               NULL, "802.3at power value requested in milliwatts",
+               NULL, cmd_store_env_value_and_pop2, "requested");
+       commands_new(
+               commands_new(configure_dot3power,
+                   "allocated", "802.3at dot3 power value allocated (mandatory)",
+                   cmd_check_typeat_but_no, NULL, "allocated"),
+               NULL, "802.3at power value allocated in milliwatts",
+               NULL, cmd_store_env_value_and_pop2, "allocated");
+}
+
+/**
+ * Register `configure` and `no configure` commands.
+ */
+void
+register_commands_configure(struct cmd_node *root)
+{
+       struct cmd_node *configure = commands_new(
+               root,
+               "configure",
+               "Change system settings",
+               NULL, NULL, NULL);
+       struct cmd_node *unconfigure = commands_new(
+               root,
+               "unconfigure",
+               "Unset configuration option",
+               NULL, NULL, NULL);
+       restrict_ports(configure);
+       restrict_ports(unconfigure);
+
+       struct cmd_node *configure_med = commands_new(
+               configure,
+               "med", "MED configuration",
+               NULL, NULL, NULL);
+       struct cmd_node *unconfigure_med = commands_new(
+               unconfigure,
+               "med", "MED configuration",
+               NULL, NULL, NULL);
+
+       register_commands_medloc(configure_med, unconfigure_med);
+       register_commands_medpol(configure_med, unconfigure_med);
+       register_commands_medpow(configure_med, unconfigure_med);
+
+       struct cmd_node *configure_dot3 = commands_new(
+               configure,
+               "dot3", "Dot3 configuration",
+               NULL, NULL, NULL);
+       struct cmd_node *unconfigure_dot3 = commands_new(
+               unconfigure,
+               "dot3", "Dot3 configuration",
+               NULL, NULL, NULL);
+
+       register_commands_dot3pow(configure_dot3, unconfigure_dot3);
 }
index 5ffa1b76d0526e34a68754fd8e1db5c474239d5d..232c688b49fbcaf5b27fffe47854b3d7bb5726ab 100644 (file)
 
 #include "../lib/lldpctl.h"
 #include "../lldp-const.h"
+#include "../log.h"
+#include "../ctl.h"
 #include "writer.h"
 
-#define LLDPCTL_ARGS "hdvaf:L:P:O:o:wCN"
+/* commands.c */
+#define NEWLINE "<CR>"
+struct cmd_node;
+struct cmd_env;
+struct cmd_node *commands_root(void);
+struct cmd_node *commands_new(
+       struct cmd_node *,
+       const char *,
+       const char *,
+       int(*validate)(struct cmd_env*, void *),
+       int(*execute)(struct lldpctl_conn_t*, struct writer*,
+           struct cmd_env*, void *),
+       void *);
+void commands_free(struct cmd_node *);
+const char *cmdenv_arg(struct cmd_env*);
+const char *cmdenv_get(struct cmd_env*, const char*);
+int cmdenv_put(struct cmd_env*, const char*, const char*);
+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);
+/* helpers */
+int cmd_not_implemented(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_check_no_env(struct cmd_env *, void *);
+int cmd_check_env(struct cmd_env *, void *);
+int cmd_store_env(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_store_env_and_pop(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_store_env_value(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_store_env_value_and_pop(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_store_env_value_and_pop2(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+int cmd_store_env_value_and_pop3(struct lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, void *);
+lldpctl_atom_t* cmd_iterate_on_interfaces(struct lldpctl_conn_t *,
+    struct cmd_env *);
+
+/* misc.c */
+int contains(const char *, const char *);
+char*  totag(const char *);
 
 /* display.c */
-void display_interfaces(lldpctl_conn_t *, struct writer *, int, int, char **);
+#define DISPLAY_BRIEF   1
+#define DISPLAY_NORMAL  2
+#define DISPLAY_DETAILS 3
+void display_interfaces(lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, int, int);
 void display_interface(lldpctl_conn_t *, struct writer *, int,
-    lldpctl_atom_t *, lldpctl_atom_t *);
+    lldpctl_atom_t *, lldpctl_atom_t *, int);
 void display_configuration(lldpctl_conn_t *, struct writer *);
 
+/* show.c */
+void register_commands_show(struct cmd_node *);
+void register_commands_watch(struct cmd_node *);
+
 /* actions.c */
-void modify_interfaces(lldpctl_conn_t *, int, char **, int);
+void register_commands_configure(struct cmd_node *);
 
 #endif
diff --git a/src/client/commands.c b/src/client/commands.c
new file mode 100644 (file)
index 0000000..2ac46ed
--- /dev/null
@@ -0,0 +1,645 @@
+/* -*- 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>
+
+/**
+ * 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 */
+
+       /**
+        * Function validating entry in this node. Can be @c NULL.
+        */
+       int(*validate)(struct cmd_env*, 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*, void *);
+       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)
+{
+       return commands_new(NULL, NULL, NULL, NULL, NULL, NULL);
+}
+
+/**
+ * Create a new node.
+ *
+ * @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*, void *),
+    int(*execute)(struct lldpctl_conn_t*, struct writer*,
+       struct cmd_env*, void *),
+    void *arg)
+{
+       struct cmd_node *new = calloc(1, sizeof(struct cmd_node));
+       if (new == NULL) {
+               log_warn("lldpctl", "unable to allocate memory for new command node");
+               return NULL;
+       }
+       new->token = token;
+       new->doc = doc;
+       new->validate = validate;
+       new->execute = execute;
+       new->arg = arg;
+       TAILQ_INIT(&new->subentries);
+       if (root != NULL)
+               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;
+};
+
+/**
+ * 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 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.
+ */
+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)
+{
+       int n, rc = 0, completion = (cursorc != -1 && cursoro != -1 && word != NULL);
+       struct cmd_env env = {
+               .elements = TAILQ_HEAD_INITIALIZER(env.elements),
+               .stack = TAILQ_HEAD_INITIALIZER(env.stack),
+               .argc = completion?cursorc:argc,
+               .argv = argv,
+               .argp = 0
+       };
+       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;
+
+       /* 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))) {
+               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;
+               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))) {
+                               if (!candidate->validate ||
+                                   candidate->validate(&env, candidate->arg) == 1) {
+                                       if (candidate->token &&
+                                           !strcmp(candidate->token, token)) {
+                                               /* Exact match */
+                                               best = candidate;
+                                               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 && (!candidate->validate ||
+                                       candidate->validate(&env, candidate->arg) == 1)) {
+                                       best = candidate;
+                                       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 && best->execute(conn, w, &env, best->arg) != 1) {
+                       rc = -1;
+                       goto end;
+               }
+               env.argp++;
+       }
+end:
+       if (!completion) {
+               if (rc == 0 && env.argp != env.argc + 1) {
+                       log_warnx("lldpctl", "incomplete command");
+                       rc = -1;
+               }
+       } else if (rc == 0 && env.argp == env.argc) {
+               /* 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 */
+               TAILQ_HEAD(, candidate_word) words; /* List of subnodes */
+               TAILQ_INIT(&words);
+               current = cmdenv_top(&env);
+               TAILQ_FOREACH(candidate, &current->subentries, next) {
+                       if ((!candidate->token ||
+                               !strncmp(env.argv[cursorc], candidate->token, cursoro)) &&
+                           (!candidate->validate ||
+                               candidate->validate(&env, candidate->arg) == 1)) {
+                               struct candidate_word *cword = malloc(sizeof(struct candidate_word));
+                               if (!cword) break;
+                               cword->word = candidate->token;
+                               cword->doc = candidate->doc;
+                               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. */
+                       int c = 0;
+                       char prefix[maxl + 2]; /* Extra space may be added at the end */
+                       struct candidate_word *cword, *cword_next;
+                       memset(prefix, 0, maxl+2);
+                       for (int n = 0; n < maxl; n++) {
+                               c = 1; /* Set to 0 to exit outer loop */
+                               TAILQ_FOREACH(cword, &words, next) {
+                                       c = 0;
+                                       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 && strcmp(prefix, NEWLINE) &&
+                           strlen(prefix) > 0 && cursoro < 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 */
+                               fprintf(stderr, "\n-- \033[1;34m%s\033[0m\n",
+                                   current->doc ? current->doc : "Help");
+                               TAILQ_FOREACH(cword, &words, next) {
+                                       char fmt[100];
+                                       snprintf(fmt, sizeof(fmt),
+                                           "%s%%%ds%s  %%s\n",
+                                           "\033[1;30m", maxl, "\033[0m");
+                                       fprintf(stderr, fmt,
+                                           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 cursorc, int cursoro, int all)
+{
+       char *word = NULL;
+       if (_commands_execute(NULL, NULL, root, argc, argv,
+               cursorc, cursoro, &word, all) == 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)
+{
+       return _commands_execute(conn, w, root, argc, argv, -1, -1, NULL, 0);
+}
+
+/**
+ * A generic "not implemented" command.
+ */
+int
+cmd_not_implemented(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_warnx("lldpctl", "not supported yet");
+       return 1;
+}
+
+/**
+ * Check if the environment does not contain the given key.
+ *
+ * @param env The environment.
+ * @param arg The key to search for.
+ */
+int
+cmd_check_no_env(struct cmd_env *env, void *key)
+{
+       return cmdenv_get(env, (const char*)key) == NULL;
+}
+
+/**
+ * Check if the environment does contain the given key.
+ *
+ * @param env The environment.
+ * @param arg The key to search for. Can be a comma separated list.
+ */
+int
+cmd_check_env(struct cmd_env *env, 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 env The environment.
+ * @param key The key to store.
+ */
+int
+cmd_store_env(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *key)
+{
+       return cmdenv_put(env, key, NULL) != -1;
+}
+
+/**
+ * Store the given key in the environment and pop one element from the stack.
+ *
+ * @param env The environment.
+ * @param key The key to store.
+ */
+int
+cmd_store_env_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, 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 env The environment.
+ * @param key The key to store.
+ */
+int
+cmd_store_env_value_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, 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, 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, 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, void *key)
+{
+       return (cmdenv_put(env, key, cmdenv_arg(env)) != -1 &&
+           cmdenv_pop(env, 3) != -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;
+}
index 74b0d35e38c7194c9ab4b8f754119d8ec0dfd552..3169e81ea7ab528e47137a1f8982b821cd33f787 100644 (file)
@@ -54,24 +54,6 @@ display_med_capability(struct writer *w, long int available, int cap,
        }
 }
 
-static char*
-totag(const char *value)
-{
-       int i;
-       static char *result = NULL;
-       free(result); result = NULL;
-       if (!value) return "none";
-       result = calloc(1, strlen(value));
-       if (!result) return "none";
-       for (i = 0; i < strlen(value); i++) {
-               switch (value[i]) {
-               case ' ': result[i] = '-'; break;
-               default: result[i] = tolower((int)value[i]); break;
-               }
-       }
-       return result;
-}
-
 static void
 display_med(struct writer *w, lldpctl_atom_t *port)
 {
@@ -239,7 +221,7 @@ display_med(struct writer *w, lldpctl_atom_t *port)
 }
 
 static void
-display_chassis(struct writer* w, lldpctl_atom_t* neighbor)
+display_chassis(struct writer* w, lldpctl_atom_t* neighbor, int details)
 {
        lldpctl_atom_t *mgmts, *mgmt;
 
@@ -253,6 +235,10 @@ display_chassis(struct writer* w, lldpctl_atom_t* neighbor)
        tag_end(w);
        tag_datatag(w, "name", "SysName",
            lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_name));
+       if (details == DISPLAY_BRIEF) {
+               tag_end(w);
+               return;
+       }
        tag_datatag(w, "descr", "SysDescr",
            lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_descr));
 
@@ -294,7 +280,7 @@ display_autoneg(struct writer * w, int advertised, int bithd, int bitfd, char *d
 }
 
 static void
-display_port(struct writer *w, lldpctl_atom_t *port)
+display_port(struct writer *w, lldpctl_atom_t *port, int details)
 {
        tag_start(w, "port", "Port");
        tag_start(w, "id", "PortID");
@@ -307,12 +293,12 @@ display_port(struct writer *w, lldpctl_atom_t *port)
            lldpctl_atom_get_str(port, lldpctl_k_port_descr));
 
        /* Dot3 */
-       tag_datatag(w, "mfs", "MFS",
-           lldpctl_atom_get_str(port, lldpctl_k_port_dot3_mfs));
-       tag_datatag(w, "aggregation", " Port is aggregated. PortAggregID",
-           lldpctl_atom_get_str(port, lldpctl_k_port_dot3_aggregid));
+       if (details == DISPLAY_DETAILS) {
+               tag_datatag(w, "mfs", "MFS",
+                   lldpctl_atom_get_str(port, lldpctl_k_port_dot3_mfs));
+               tag_datatag(w, "aggregation", " Port is aggregated. PortAggregID",
+                   lldpctl_atom_get_str(port, lldpctl_k_port_dot3_aggregid));
 
-       do {
                long int autoneg_support, autoneg_enabled, autoneg_advertised;
                autoneg_support = lldpctl_atom_get_int(port,
                    lldpctl_k_port_dot3_autoneg_support);
@@ -359,9 +345,7 @@ display_port(struct writer *w, lldpctl_atom_t *port)
                            lldpctl_atom_get_str(port, lldpctl_k_port_dot3_mautype));
                        tag_end(w);
                }
-       } while (0);
 
-       do {
                lldpctl_atom_t *dot3_power = lldpctl_atom_get(port,
                    lldpctl_k_port_dot3_power);
                int devicetype = lldpctl_atom_get_int(dot3_power,
@@ -422,7 +406,7 @@ display_port(struct writer *w, lldpctl_atom_t *port)
                        tag_end(w);
                }
                lldpctl_atom_dec_ref(dot3_power);
-       } while(0);
+       }
 
        tag_end(w);
 }
@@ -520,7 +504,7 @@ display_age(time_t lastchange)
 
 void
 display_interface(lldpctl_conn_t *conn, struct writer *w, int hidden,
-    lldpctl_atom_t *iface, lldpctl_atom_t *neighbor)
+    lldpctl_atom_t *iface, lldpctl_atom_t *neighbor, int details)
 {
        if (!hidden &&
            lldpctl_atom_get_int(neighbor, lldpctl_k_port_hidden))
@@ -531,63 +515,58 @@ display_interface(lldpctl_conn_t *conn, struct writer *w, int hidden,
            lldpctl_atom_get_str(iface, lldpctl_k_interface_name));
        tag_attr(w, "via" , "via",
            lldpctl_atom_get_str(neighbor, lldpctl_k_port_protocol));
-       tag_attr(w, "rid" , "RID",
-           lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_index));
-       tag_attr(w, "age" , "Time",
-           display_age(lldpctl_atom_get_int(neighbor, lldpctl_k_port_age)));
-
-       display_chassis(w, neighbor);
-       display_port(w, neighbor);
-       display_vlans(w, neighbor);
-       display_ppvids(w, neighbor);
-       display_pids(w, neighbor);
-       display_med(w, neighbor);
+       if (details > DISPLAY_BRIEF) {
+               tag_attr(w, "rid" , "RID",
+                   lldpctl_atom_get_str(neighbor, lldpctl_k_chassis_index));
+               tag_attr(w, "age" , "Time",
+                   display_age(lldpctl_atom_get_int(neighbor, lldpctl_k_port_age)));
+       }
+
+       display_chassis(w, neighbor, details);
+       display_port(w, neighbor, details);
+       if (details == DISPLAY_DETAILS) {
+               display_vlans(w, neighbor);
+               display_ppvids(w, neighbor);
+               display_pids(w, neighbor);
+               display_med(w, neighbor);
+       }
 
        tag_end(w);
 }
 
+/**
+ * Display information about interfaces.
+ *
+ * @param conn       Connection to lldpd.
+ * @param w          Writer.
+ * @param hidden     Whatever to show hidden ports.
+ * @param interfaces List of interfaces we should restrict to (comma separated).
+ * @param details    Level of details we need (DISPLAY_*).
+ */
 void
-display_interfaces(lldpctl_conn_t *conn, struct writer *w, int hidden,
-    int argc, char *argv[])
+display_interfaces(lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env,
+    int hidden, int details)
 {
-       int i;
-       lldpctl_atom_t *iface_list;
        lldpctl_atom_t *iface;
-       lldpctl_atom_t *port;
-       lldpctl_atom_t *neighbors;
-       lldpctl_atom_t *neighbor;
-
-       iface_list = lldpctl_get_interfaces(conn);
-       if (!iface_list) {
-               log_warnx(NULL, "not able to get the list of interfaces. %s",
-                   lldpctl_last_strerror(conn));
-               return;
-       }
 
        tag_start(w, "lldp", "LLDP neighbors");
-       lldpctl_atom_foreach(iface_list, iface) {
-               if (optind < argc) {
-                       for (i = optind; i < argc; i++)
-                               if (strcmp(argv[i],
-                                       lldpctl_atom_get_str(iface,
-                                           lldpctl_k_interface_name)) == 0)
-                                       break;
-                       if (i == argc)
-                               continue;
-               }
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               lldpctl_atom_t *port;
+               lldpctl_atom_t *neighbors;
+               lldpctl_atom_t *neighbor;
                port      = lldpctl_get_port(iface);
                neighbors = lldpctl_atom_get(port, lldpctl_k_port_neighbors);
                lldpctl_atom_foreach(neighbors, neighbor) {
-                       display_interface(conn, w, hidden, iface, neighbor);
+                       display_interface(conn, w, hidden, iface, neighbor, details);
                }
                lldpctl_atom_dec_ref(neighbors);
                lldpctl_atom_dec_ref(port);
        }
-       lldpctl_atom_dec_ref(iface_list);
        tag_end(w);
 }
 
-const char *
+static const char *
 N(const char *str) {
        if (str == NULL || strlen(str) == 0) return "(none)";
        return str;
@@ -600,7 +579,7 @@ display_configuration(lldpctl_conn_t *conn, struct writer *w)
 
        configuration = lldpctl_get_configuration(conn);
        if (!configuration) {
-               log_warnx(NULL, "not able to get configuration. %s",
+               log_warnx("lldpctl", "not able to get configuration. %s",
                    lldpctl_last_strerror(conn));
                return;
        }
index 609010de896845dab1873516fda4f9e32068f7b6..bf914a633c61c2e3d797b91202f5c09dc9a0ee32 100644 (file)
@@ -120,7 +120,7 @@ json_end(struct writer *w)
        struct json_writer_private *p = w->priv;
        struct json_element *current = TAILQ_LAST(p, json_writer_private);
        if (current == NULL) {
-               log_warnx(NULL, "unbalanced tags");
+               log_warnx("lldpctl", "unbalanced tags");
                return;
        }
        TAILQ_REMOVE(p, current, next);
@@ -191,9 +191,9 @@ json_finish(struct writer *w)
 {
        struct json_writer_private *p = w->priv;
        if (TAILQ_EMPTY(p)) {
-               log_warnx(NULL, "nothing to output");
+               log_warnx("lldpctl", "nothing to output");
        } else if (TAILQ_NEXT(TAILQ_FIRST(p), next) != NULL) {
-               log_warnx(NULL, "unbalanced tags");
+               log_warnx("lldpctl", "unbalanced tags");
                /* memory will leak... */
        } else {
                struct json_element *first = TAILQ_FIRST(p);
@@ -201,7 +201,7 @@ json_finish(struct writer *w)
                if (json_dumpf(export,
                        stdout,
                        JSON_INDENT(2) | JSON_PRESERVE_ORDER) == -1)
-                       log_warnx(NULL, "unable to output JSON");
+                       log_warnx("lldpctl", "unable to output JSON");
                json_decref(first->el);
                json_decref(export);
                TAILQ_REMOVE(p, first, next);
index 6072aed3f35cd035f8778b3819333ef8ddd76d00..253b16518e35b9657d0a17867561765a340b7ebb 100644 (file)
 .Nd control LLDP daemon
 .Sh SYNOPSIS
 .Nm
-.Op Fl advwCN
-.Op Fl L Ar location
-.Op Fl P Ar policy
-.Op Fl O Ar poe
-.Op Fl o Ar poe
-.Op Ar interface ...
+.Op Fl dv
+.Op Fl f Ar format
+.Op Ar command ...
 .Sh DESCRIPTION
 The
 .Nm
@@ -34,11 +31,11 @@ program controls
 .Xr lldpd 8
 daemon.
 .Pp
-When no specific option is given,
+When no command is specified,
 .Nm
-displays the list of discovered neighbors along with
-some of their advertised capabilities. If some interfaces are given,
-only those interfaces will be displayed.
+will start an interactive shell which can be used to input arbitrary
+commands as if they were specified on the command line. This
+interactive shell should provide completion and history support.
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
@@ -48,18 +45,6 @@ Enable more debugging information.
 Show
 .Nm
 version.
-.It Fl a
-Display all remote ports, including those hidden by the smart filter.
-.It Fl w
-Monitor neighbor changes. Each changed neighbor will be displayed.
-.It Fl C
-Display global configuration of
-.Em lldpd
-daemon.
-.It Fl N
-Make
-.Em lldpd
-update its information and send new LLDP PDU on all interfaces.
 .It Fl f Ar format
 Choose the output format. Currently
 .Em plain ,
@@ -68,212 +53,304 @@ Choose the output format. Currently
 and
 .Em keyvalue
 formats are available. The default is
-.Em plain.
-.It Fl L Ar location
-Enable the transmission of LLDP-MED location TLV for the given
-interfaces. This option can be repeated several times to enable the
-transmission of the location in several formats. Several formats are
-accepted:
-.Bl -tag -width "XX"
-.It Em Coordinate based location
-The format of
-.Ar location
-is
-.Ar 1:48.85667N:2.2014E:117.47:m:1
-The first digit is always
-.Ar 1 .
-It is followed by the latitude, a letter for the direction (
-.Ar E
+.Em plain .
+.El
+.Pp
+
+The following commands are supported. When there is no ambiguity, the
+keywords can be abbreviated. For example,
+.Cd show neighbors ports eth0 summary
+and
+.Cd sh neigh p eth0 sum
+are the same command.
+
+.Bd -ragged -offset XX
+.Cd exit
+.Bd -ragged -offset XXXXXX
+Quit
+.Nm .
+.Ed
+
+.Cd show neighbors
+.Op ports Ar ethX Op ...
+.Op Cd details | summary
+.Op Cd hidden
+.Bd -ragged -offset XXXXXX
+Display information about each neighbor known by
+.Xr lldpd 8
+daemon. With
+.Cd summary ,
+only a the name and the port description of each remote host will be
+displayed. On the other hand, with
+.Cd details ,
+all available information will be displayed, giving a verbose
+view. When using
+.Cd hidden ,
+also display remote ports hidden by the smart filter. When specifying
+one or several ports, the information displayed is limited to the
+given list of ports.
+.Ed
+
+.Cd watch
+.Op ports Ar ethX Op ...
+.Op Cd details | summary
+.Op Cd hidden
+.Bd -ragged -offset XXXXXX
+Watch for any neighbor changes and report them as soon as they
+happen. When specifying ports, the changes are only reported when
+happening on the given ports.
+.Cd hidden , summary
+and
+.Cd details
+have the same meaning than previously described.
+.Ed
+
+.Cd show configuration
+.Bd -ragged -offset XXXXXX
+Display global configuration of
+.Xr lldpd 8
+daemon.
+.Ed
+
+.Cd update
+.Bd -ragged -offset XXXXXX
+Make
+.Xr lldpd 8
+update its information and send new LLDP PDU on all interfaces.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd med location coordinate
+.Cd latitude Ar latitude
+.Cd longitude Ar longitude
+.Cd altitude Ar altitude Ar unit
+.Cd datum Ar datum
+.Bd -ragged -offset XXXXXX
+Advertise a coordinate based location on the given ports (or on all
+ports if no port is specified). The format of
+.Ar latitude
+is a decimal floating point number followed either by
+.Em N
 or
-.Ar W
-for East or West), the longitude and a letter for the direction (
-.Ar N
+.Em S .
+The format of
+.Ar longitude
+is a decimal floating point number followed either by 
+.Em E
 or
-.Ar S
-). The next figure is the altitude. It can be expressed in meters (the
-next letter is then
-.Ar m
-) or in floors (the letter should be
-.Ar f
-). The last digit is the datum. It can either be
-.Ar 1
-(WGS84),
-.Ar 2
-(NAD83) or
-.Ar 3
-(NAD83/MLLW).
-.It Em Civic address
-The location can be expressed as an address. The format of the
-location is then
-.Ar 2:FR:6:Commercial Rd:3:Roseville:19:4
-The first digit is always
-.Ar 2 .
-The next two letters are the country code. Then, arguments are paired
-to form the address. The first member of the pair is a digit
-indicating the type of the second member. Here is the list of
-valid types:
-.Bl -tag -width "XXXX." -compact
-.It Sy 0
-Language
-.It Sy 1
-National subdivisions
-.It Sy 2
-County, parish, district
-.It Sy 3
-City, township
-.It Sy 4
-City division, borough, ward
-.It Sy 5
-Neighborhood, block
-.It Sy 6
-Street
-.It Sy 16
-Leading street direction
-.It Sy 17
-Trailing street suffix
-.It Sy 18
-Street suffix
-.It Sy 19
-House number
-.It Sy 20
-House number suffix
-.It Sy 21
-Landmark or vanity address
-.It Sy 22
-Additional location info
-.It Sy 23
-Name
-.It Sy 24
-Postal/ZIP code
-.It Sy 25
-Building
-.It Sy 26
-Unit
-.It Sy 27
-Floor
-.It Sy 28
-Room number
-.It Sy 29
-Place type
-.It Sy 128
-Script
+.Em W .
+.Ar altitude
+is a decimal floating point number followed either by
+.Em m
+when expressed in meters or
+.Em f
+when expressed in floors.
+.Ar datum
+is one of those values:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+WGS84
+.It
+NAD83
+.It
+NAD83/MLLW
 .El
-.It ECS ELIN
-This is a numerical string using for setting up emergency call. The
-format of the location is then the following:
-.Ar 3:0000000911
-where the first digit should be
-.Ar 3
-and the second argument is the ELIN number.
+.Pp
+A valid use of this command is:
+.D1 configure ports eth0 med location coordinate latitude 48.85667N longitude 2.2014E altitude 117.47m datum WGS84
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd med location address
+.Cd country Ar country
+.Cd Op Ar type value Op ...
+.Bd -ragged -offset XXXXXX
+Advertise a civic address on the given ports (or on all ports if no
+port is specified).
+.Ar country
+is the two-letter code representing the country. The remaining
+arguments should be paired to form the address. The first member of
+each pair indicates the type of the second member which is a free-form
+text. Here is the list of valid types:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+language
+.It
+country-subdivision
+.It
+county
+.It
+city
+.It
+city-division
+.It
+block
+.It
+street
+.It
+direction
+.It
+trailing-street-suffix
+.It
+street-suffix
+.It
+number
+.It
+number-suffix
+.It
+landmark
+.It
+additional
+.It
+name
+.It
+zip
+.It
+building
+.It
+unit
+.It
+floor
+.It
+room
+.It
+place-type
+.It
+script
 .El
 .Pp
-When setting a location for a given port, all previous locations are
-erased. To erase all location, just use the empty string. There is
-currently no way to get the location from the command line.
+A valid use of this command is:
+.D1 configure ports eth1 med location address US street Qo Commercial Road Qc city Qo Roseville Qc
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd med location elin
+.Ar number
+.Bd -ragged -offset XXXXXX
+Advertise the availability of an ELIN number. This is used for setting
+up emergency call. If the provided number is too small, it will be
+padded with 0. Here is an example of use:
+.D1 configure ports eth2 med location elin 911
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ...
+.Cd med location
+.Op Cd coordinate | address | elin
+.Bd -ragged -offset XXXXXX
+Do not advertise the location on the given ports (or on all ports if
+no port was provided). Optionally, only remove the specified location
+(coordinate-based, address or ELIN number). Here is an example of use:
+.D1 unconfigure ports eth0 eth2 med location coordinate
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd med policy
+.Cd application Ar application
+.Op Cd unknown
+.Op Cd vlan Ar vlan
+.Op Cd priority Ar priority
+.Op Cd dscp Ar dscp
+.Bd -ragged -offset XXXXXX
+Advertise a specific network policy for the given ports (or for all
+ports if no port was provided). Only the application type is
+mandatory.
+.Ar application
+should be one of the following values:
+static const struct value_string port_med_policy_map[] = {
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+voice
+.It
+voice-signaling
+.It
+guest-voice
+.It
+guest-voice-signaling
+.It
+softphone-voice
+.It
+video-conferencing
+.It
+streaming-video
+.It
+video-signaling
+.El
 .Pp
-.It Fl P Ar policy
-Enable the transmission of LLDP-MED Network Policy TLVs for the given
-interfaces. This option can be repeated several times to specify
-different policies. Format (without spaces!):
+The
+.Cd unknown
+flag tells that the network policy for the specified application type
+is required by the device but is currently unknown. This is used by
+Endpoint Devices, not by Network Connectivity Devices. If not
+specified, the network policy for the given application type is
+defined.
 .Pp
-.Em App-Type
-:
-.Ar U
-:
-.Ar T
-:
-.Ar VLAN-ID
-:
-.Ar L2-Prio
-:
-.Ar DSCP
-.Bl -tag -width "XX"
-.It Ar App-Type
-Valid application types (see ANSI/TIA-1057 table 12):
-.Bl -tag -width "X." -compact
-.It Sy 1
-Voice
-.It Sy 2
-Voice Signaling
-.It Sy 3
-Guest Voice
-.It Sy 4
-Guest Voice Signaling
-.It Sy 5
-Softphone Voice
-.It Sy 6
-Video Conferencing
-.It Sy 7
-Streaming Video
-.It Sy 8
-Video Signaling
-.El
-.It Ar U
-Unknown Policy Flag.
-.Bl -tag -width "X." -compact
-.It Sy 0
-Network policy for the specified application type is defined.
-.It Sy 1
-Network policy for the specified application type is required by
-the device but is currently unknown. This is used by Endpoint
-Devices, not by Network Connectivity Devices.
-.El
-.It Ar T
-Tagged Flag.
-.Bl -tag -width "X." -compact
-.It Sy 0
-Untagged VLAN. In this case the VLAN ID and the Layer 2 Priority
-are ignored and only the DSCP value has relevance.
-.It Sy 1
-Tagged VLAN.
-.El
-.It Ar VLAN-ID
-IEEE 802.1q VLAN ID (VID). A value of 1 through 4094 defines a
-VLAN ID. A value of 0 means that only the priority level is
-significant.
-.It Ar L2-Prio
-IEEE 802.1d / IEEE 802.1p Layer 2 Priority, also known as Class of Service
-(CoS), to be used for the specified application type.
-.Bl -tag -width "X." -compact
-.It Sy 1
-Background
-.It Sy 2
-Spare
-.It Sy 0
-Best Effort (default)
-.It Sy 3
-Excellent Effort
-.It Sy 4
-Controlled Load
-.It Sy 5
-Video
-.It Sy 6
-Voice
-.It Sy 7
-Network Control
-.El
-.It Ar DSCP
-DiffServ/Differentiated Services Code Point (DSCP) value as defined
-in IETF RFC 2474 for the specified application type. Value: 0 (default
-per RFC 2475) through 63. Note: The class selector DSCP values are
-backwards compatible for devices that only support the old IP
-precedence Type of Service (ToS) format. (See the RFCs for what
-these values mean.)
-.It Examples:
-.Bl -tag -width "X." -compact
-.It Sy 1:0:1:500:6:46
-Voice (1): not unknown (0), tagged (1), VLAN-ID 500, l2 prio Voice (6), DSCP 46 (EF, Expedited Forwarding)
-.It Sy 2:0:1:500:3:24
-Voice Signaling (2): not unknown (0), tagged (1), VLAN-ID 500, l2 prio
-Excellent Effort (3), DSCP 24 (CS3, Class Selector 3)
-.El
+When a VLAN is specified with
+.Cd vlan
+tells which 802.1q VLAN ID has to be advertised for the network
+policy. A valid value is between 1 and 4094.
+.Cd priority
+allows one to specify IEEE 802.1d / IEEE 802.1p Layer 2 Priority, also
+known as Class of Service (CoS), to be used for the specified
+application type. It should be one of those values:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+background
+.It
+spare
+.It
+best-effort
+.It
+excellent-effort
+.It
+controlled-load
+.It
+video
+.It
+voice
+.It
+network-control
 .El
 .Pp
-.It Fl O Ar poe
-Enable the transmission of LLDP-MED POE-MDI TLV for the given
-interfaces. One can act as a PD (power consumer) or a PSE (power
-provider). No check is done on the validity of the parameters while
-LLDP-MED requires some restrictions:
+.Ar dscp
+represents the DSCP value to be advertised for the given network
+policy.  DiffServ/Differentiated Services Code Point (DSCP) value as
+defined in IETF RFC 2474 for the specified application type. Value: 0
+(default per RFC 2475) through 63. Note: The class selector DSCP
+values are backwards compatible for devices that only support the old
+IP precedence Type of Service (ToS) format. (See the RFCs for what
+these values mean)
+.Pp
+A valid use of this command is:
+.D1 configure med policy application voice vlan 500 priority voice dscp 46
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ...
+.Cd med policy
+.Cd application Ar application
+.Bd -ragged -offset XXXXXX
+Do not advertise any network policy on the given ports (or on all
+ports if no port was provided). Optionally, only remove the specified
+application type. Here is an example of use:
+.D1 unconfigure ports eth0 eth2 med policy application voice
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd med power pse | pd
+.Cd source Ar source
+.Cd priority Ar priority
+.Cd value Ar value
+.Bd -ragged -offset XXXXXX
+Advertise the LLDP-MED POE-MDI TLV for the given ports or for all
+interfaces if no port is provided.  One can act as a PD (power
+consumer) or a PSE (power provider). No check is done on the validity
+of the parameters while LLDP-MED requires some restrictions:
 .Bl -bullet
 .It
 PD shall never request more power than physical 802.3af class.
@@ -295,31 +372,16 @@ same applies for PD and power priority. LLDP-MED MIB does not allow
 this kind of representation.
 .El
 .Pp
-This option is distinct of
-.Fl o
-option. You may want to use both options at the same time.
-.Pp
-The format of this option is (without spaces):
-.Pp
-.Em type
-:
-.Ar source
-:
-.Ar priority
-:
-.Ar value
-.Bl -tag -width "XX"
-.It Ar type
 Valid types are:
-.Bl -tag -width "XXX." -compact
-.It Sy PSE
+.Bl -tag -width "XXX." -compact -offset XX
+.It Sy pse
 Power Sourcing Entity (power provider)
-.It Sy PD
+.It Sy pd
 Power Device (power consumer)
 .El
-.It Ar source
+.Pp
 Valid sources are:
-.Bl -tag -width "X." -compact
+.Bl -tag -width "XXXXXXX" -compact -offset XX
 .It Sy unknown
 Unknown
 .It Sy primary
@@ -335,9 +397,9 @@ For PD, the power source is a local source.
 .It Sy both
 For PD, the power source is both the PSE and a local source.
 .El
-.It Ar priority
-Four priorities are available:
-.Bl -tag -width "X." -compact
+.Pp
+Valid priorities are:
+.Bl -tag -width "XXXXXXXXX" -compact -offset XX
 .It Sy unknown
 Unknown priority
 .It Sy critical
@@ -347,121 +409,86 @@ High
 .It Sy low
 Low
 .El
-.It Ar value
-For PD, the power value is the total power in milliwatts required
-by a PD device from the PSE device.
-.El
-.It Fl o Ar poe
-Enable the transmission of Dot3 POE-MDI TLV for the given
-interfaces. One can act as a PD (power consumer) or a PSE (power
-provider). This option is distinct of the
-.Fl O
-option. You might want to use both. Contrary to LLDP-MED POE-MDI TLV,
-Dot3 POE-MDI TLV are strictly per-port values.
 .Pp
-The format of this option is (without spaces):
+.Ar value
+should be the total power in milliwatts required by the PD device or
+available by the PSE device.
+.Pp
+Here is an example of use:
+.D1 configure med power pd source pse priority high value 5000
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ...
+.Cd med power
+.Bd -ragged -offset XXXXXX
+Do not advertise the LLDP-MED POE-MDI TLV for the given portd (or on all
+ports if no port was provided).
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ...
+.Cd dot3 power pse | pd
+.Op Cd supported
+.Op Cd enabled
+.Op Cd paircontrol
+.Cd powerpairs Ar powerpairs
+.Op Cd class Ar class
+.Op Cd type Ar type Cd source Ar source Cd priority Ar priority Cd requested Ar requested Cd allocated Ar allocated
+.Bd -ragged -offset XXXXXX
+Advertise Dot3 POE-MDI TLV for the given port or for all ports if none
+was provided. One can act as a PD (power consumer) or a PSE (power
+provider). This configuration is distinct of the configuration of the
+transmission of the LLDP-MED POE-MDI TLV but the user should ensure
+the coherency of those two configurations if they are used together.
 .Pp
-.Em type
-:
 .Ar supported
-:
+means that MDI power is supported on the given port while
 .Ar enabled
-:
+means that MDI power is enabled.
 .Ar paircontrol
-:
+is used to indicate if pair selection can be controlled. Valid values
+forr
 .Ar powerpairs
-:
-.Ar class
-[ :
-.Ar powertype
-:
-.Ar source
-:
-.Ar priority
-:
-.Ar requested
-:
-.Ar allocated
-]
-.Bl -tag -width "XX"
-.It Ar type
-Valid types are:
-.Bl -tag -width "XXX." -compact
-.It Sy PSE
-Power Sourcing Entity (power provider)
-.It Sy PD
-Power Device (power consumer)
-.El
-.It Ar powerpairs
-Valid values are:
-.Bl -tag -width "X." -compact
+are:
+.Bl -tag -width "XXXXXX" -compact -offset XX
 .It Sy signal
 The signal pairs only are in use.
 .It Sy spare
 The spare pairs only are in use.
 .El
-.It Ar class
-Five classes are available:
-.Bl -tag -width "X." -compact
-.It Sy 1
-class 0
-.It Sy 2
-class 1
-.It Sy 3
-class 2
-.It Sy 4
-class 3
-.It Sy 5
-class 4
-.It Sy 0
-no class
-.El
-.El
 .Pp
-.Ar supported ,
-.Ar enabled
-and
-.Ar paircontrol
-can be set to to 0 or 1.
-.Ar supported
-means that MDI power is supported on the given port.
-.Ar enabled
-means that MDI power is enabled on the given port.
-.Ar paircontrol
-is used to indicate if the pair selection can be controlled on the
-given port.
+When specified,
+.Ar class
+is a number between 0 and 4.
 .Pp
-.Ar powertype ,
-.Ar source ,
-.Ar priority
-(and remaining values) are optional. They are only requested in
-conformance with 802.3at.
+The remaining parameters are in conformance with 802.3at and are optional.
 .Ar type
-should be either 1 or 2. For source, use one of the following values:
-Valid sources are:
-.Bl -tag -width "X." -compact
-.It Sy 0
-Unknown
-.It Sy 1
-For PD, the power source is the PSE. For PSE, the power source is the
-primary power source.
-.It Sy 2
-For PD, the power source is a local source. For PSE, the power source
-is the backup power source or a power conservation mode is asked (the
-PSE may be running on UPS for example).
-.It Sy 3
-For PD, the power source is both the PSE and a local source. For PSE,
-this value should not be used.
-.El
-For
-.Ar priority ,
-see what is done for LLDP-MED MDI/POE.
+should be either 1 or 2, indicating which if the device conforms to
+802.3at type 1 or 802.3at type 2. Values ofr
+.Ar source
+and
+.Ar priority
+are the same as for LLDP-MED POE-MDI TLV.
 .Ar requested
 and
 .Ar allocated
-are respectively the PD requested power value and the PSE allocated
-power value. This should be expressed in milliwatts.
-.El
+are expressed in milliwats.
+.Pp
+Here are two valid uses of this command:
+.D1 configure ports eth3 dot3 power pse supported enabled paircontrol powerpairs spare class 3
+.D1 configure dot3 power pd supported enabled powerpairs spare class 3 type 1 source pse priority low requested 10000 allocated 15000
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ...
+.Cd dot3 power
+.Bd -ragged -offset XXXXXX
+Do not advertise the Dot3 POE-MDI TLV for the given ports (or on all
+ports if no port was provided).
+.Ed
+
+.Ed
 .Sh FILES
 .Bl -tag -width "/var/run/lldpd.socketXX" -compact
 .It /var/run/lldpd.socket
index fc56a002f9c2c26af359d3f94ce10cd1070e6734..409fb3355a3a1f932f7114683563bb09ce5abb3f 100644 (file)
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <arpa/inet.h>
+#include <histedit.h>
 
-#include "../log.h"
-#include "../ctl.h"
 #include "client.h"
 
-static void             usage(void);
-
 #ifdef HAVE___PROGNAME
 extern const char      *__progname;
 #else
 # define __progname "lldpctl"
 #endif
 
+/* Global for completion */
+static struct cmd_node *root = NULL;
 
 static void
-usage(void)
+usage()
 {
-       fprintf(stderr, "Usage:   %s [OPTIONS ...] [INTERFACES ...]\n", __progname);
+       fprintf(stderr, "Usage:   %s [OPTIONS ...] [COMMAND ...]\n", __progname);
        fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
 
        fprintf(stderr, "\n");
 
        fprintf(stderr, "-d          Enable more debugging information.\n");
-       fprintf(stderr, "-a          Display all remote ports, including hidden ones.\n");
-       fprintf(stderr, "-w          Watch for changes.\n");
-       fprintf(stderr, "-C          Display global configuration of lldpd.\n");
-       fprintf(stderr, "-N          Make lldpd transmit LLDP PDU now.\n");
        fprintf(stderr, "-f format   Choose output format (plain, keyvalue or xml).\n");
-       fprintf(stderr, "-L location Enable the transmission of LLDP-MED location TLV for the\n");
-       fprintf(stderr, "            given interfaces. Can be repeated to enable the transmission\n");
-       fprintf(stderr, "            of the location in several formats.\n");
-       fprintf(stderr, "-P policy   Enable the transmission of LLDP-MED Network Policy TLVs\n");
-       fprintf(stderr, "            for the given interfaces. Can be repeated to specify\n");
-       fprintf(stderr, "            different policies.\n");
-       fprintf(stderr, "-O poe      Enable the transmission of LLDP-MED POE-MDI TLV\n");
-       fprintf(stderr, "            for the given interfaces.\n");
-       fprintf(stderr, "-o poe      Enable the transmission of Dot3 POE-MDI TLV\n");
-       fprintf(stderr, "            for the given interfaces.\n");
 
        fprintf(stderr, "\n");
 
@@ -70,59 +55,142 @@ usage(void)
        exit(1);
 }
 
-struct cbargs {
-       int argc;
-       char **argv;
-       struct writer *w;
-};
-
-void
-watchcb(lldpctl_conn_t *conn,
-    lldpctl_change_t type,
-    lldpctl_atom_t *interface,
-    lldpctl_atom_t *neighbor,
-    void *data)
+static int
+is_privileged()
+{
+       return (!(getuid() != geteuid() || getgid() != getegid()));
+}
+
+static char*
+prompt(EditLine *el)
+{
+       int privileged = is_privileged();
+       if (privileged)
+               return "[lldpctl] # ";
+       return "[lldpctl] $ ";
+}
+
+static int must_exit = 0;
+/**
+ * Exit the interpreter.
+ */
+static int
+cmd_exit(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
 {
-       int i;
-       struct cbargs *args = data;
-       optind = 0;
-       while (getopt(args->argc, args->argv, LLDPCTL_ARGS) != -1);
-       if (optind < args->argc) {
-               for (i = optind; i < args->argc; i++)
-                       if (strcmp(args->argv[i],
-                               lldpctl_atom_get_str(interface,
-                                   lldpctl_k_interface_name)) == 0)
-                               break;
-               if (i == args->argc)
-                       return;
+       log_info("lldpctl", "quit lldpctl");
+       must_exit = 1;
+       return 1;
+}
+
+/**
+ * Send an "update" request.
+ */
+static int
+cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_info("lldpctl", "ask for global update");
+
+       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;
        }
-       switch (type) {
-       case lldpctl_c_deleted:
-               tag_start(args->w, "lldp-deleted", "LLDP neighbor deleted");
-               break;
-       case lldpctl_c_updated:
-               tag_start(args->w, "lldp-updated", "LLDP neighbor updated");
-               break;
-       case lldpctl_c_added:
-               tag_start(args->w, "lldp-added", "LLDP neighbor added");
-               break;
-       default: return;
+       if (lldpctl_atom_set_int(config,
+               lldpctl_k_config_tx_interval, -1) == NULL) {
+               log_warnx("lldpctl", "unable to ask lldpd for immediate retransmission. %s",
+                   lldpctl_last_strerror(conn));
+               lldpctl_atom_dec_ref(config);
+               return 0;
        }
-       display_interface(conn, args->w, 1, interface, neighbor);
-       tag_end(args->w);
+       log_info("lldpctl", "immediate retransmission requested successfuly");
+       lldpctl_atom_dec_ref(config);
+       return 1;
+}
+
+static unsigned char
+_cmd_complete(EditLine *el, int ch, int all)
+{
+       int rc = CC_ERROR;
+       Tokenizer *eltok;
+       if ((eltok = tok_init(NULL)) == NULL)
+               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);
+       if (compl) {
+               el_deletestr(el, cursoro);
+               if (el_insertstr(el, compl) == -1) {
+                       free(compl);
+                       goto end;
+               }
+               free(compl);
+               rc = CC_REDISPLAY;
+               goto end;
+       }
+       /* No completion or several completion available. We beep. */
+       el_beep(el);
+       rc = CC_REDISPLAY;
+end:
+       if (eltok) tok_end(eltok);
+       return rc;
+}
+
+static unsigned char
+cmd_complete(EditLine *el, int ch)
+{
+       return _cmd_complete(el, ch, 0);
+}
+
+static unsigned char
+cmd_help(EditLine *el, int ch)
+{
+       return _cmd_complete(el, ch, 1);
+}
+
+static struct cmd_node*
+register_commands()
+{
+       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_new(
+               commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
+               NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
+       return root;
 }
 
 int
 main(int argc, char *argv[])
 {
-       int ch, debug = 1;
+       int ch, debug = 1, rc = EXIT_FAILURE;
        char *fmt = "plain";
-       int action = 0, hidden = 0, watch = 0, configuration = 0, now = 0;
        lldpctl_conn_t *conn;
-       struct cbargs args;
+       struct writer *w;
+
+       EditLine  *el;
+       History   *elhistory;
+       HistEvent  elhistev;
+       Tokenizer *eltok;
 
        /* Get and parse command line options */
-       while ((ch = getopt(argc, argv, LLDPCTL_ARGS)) != -1) {
+       while ((ch = getopt(argc, argv, "hdvf:")) != -1) {
                switch (ch) {
                case 'h':
                        usage();
@@ -134,27 +202,9 @@ main(int argc, char *argv[])
                        fprintf(stdout, "%s\n", PACKAGE_VERSION);
                        exit(0);
                        break;
-               case 'a':
-                       hidden = 1;
-                       break;
                case 'f':
                        fmt = optarg;
                        break;
-               case 'L':
-               case 'P':
-               case 'O':
-               case 'o':
-                       action = 1;
-                       break;
-               case 'w':
-                       watch = 1;
-                       break;
-               case 'C':
-                       configuration = 1;
-                       break;
-               case 'N':
-                       now = 1;
-                       break;
                default:
                        usage();
                }
@@ -162,75 +212,106 @@ main(int argc, char *argv[])
 
        log_init(debug, __progname);
 
-       if ((action != 0) &&
-           (getuid() != geteuid() || getgid() != getegid())) {
-               fatalx("mere mortals may not do that, admin privileges are required.");
+       /* Register commands */
+       root = register_commands();
+
+       /* 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, 0);
+       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);
        }
 
-       conn = lldpctl_new(NULL, NULL, NULL);
-       if (conn == NULL) exit(EXIT_FAILURE);
-
-       args.argc = argc;
-       args.argv = argv;
-       if (watch) {
-               if (lldpctl_watch_callback(conn, watchcb, &args) < 0) {
-                       log_warnx(NULL, "unable to watch for neighbors. %s",
-                           lldpctl_last_strerror(conn));
-                       exit(EXIT_FAILURE);
-               }
+       /* 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);
        }
 
-       do {
-               if (strcmp(fmt, "plain") == 0) {
-                       args.w = txt_init(stdout);
-               } else if (strcmp(fmt, "keyvalue") == 0) {
-                       args.w = kv_init(stdout);
+       /* Init tokenizer */
+       eltok = tok_init(NULL);
+       if (eltok == NULL) {
+               log_warnx("lldpctl", "unable to initialize tokenizer");
+               goto end;
+       }
+
+       /* Make a connection */
+       log_debug("lldpctl", "connect to lldpd");
+       conn = lldpctl_new(NULL, NULL, NULL);
+       if (conn == NULL)
+               exit(EXIT_FAILURE);
+
+       while (!must_exit) {
+               const char *line;
+               const char **argv;
+               int count, n, 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, &argc, &argv);
+               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;
                }
-#ifdef USE_XML
-               else if (strcmp(fmt,"xml") == 0 ) {
-                       args.w = xml_init(stdout);
+               if (argc == 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);
 #endif
 #ifdef USE_JSON
-               else if (strcmp(fmt, "json") == 0) {
-                       args.w = json_init(stdout);
-               }
+               else if (strcmp(fmt, "json")     == 0) w = json_init(stdout);
 #endif
-               else {
-                       args.w = txt_init(stdout);
-               }
+               else w = txt_init(stdout);
 
-               if (action) {
-                       modify_interfaces(conn, argc, argv, optind);
-               } else if (watch) {
-                       if (lldpctl_watch(conn) < 0) {
-                               log_warnx(NULL, "unable to watch for neighbors. %s",
-                                   lldpctl_last_strerror(conn));
-                               watch = 0;
-                       }
-               } else if (configuration) {
-                       display_configuration(conn, args.w);
-               } else if (now) {
-                       lldpctl_atom_t *config = lldpctl_get_configuration(conn);
-                       if (config == NULL) {
-                               log_warnx(NULL, "unable to get configuration from lldpd. %s",
-                                       lldpctl_last_strerror(conn));
-                       } else {
-                               if (lldpctl_atom_set_int(config,
-                                       lldpctl_k_config_tx_interval, -1) == NULL) {
-                                       log_warnx(NULL, "unable to ask lldpd for immediate retransmission. %s",
-                                               lldpctl_last_strerror(conn));
-                               } else
-                                       log_info(NULL, "immediate retransmission requested successfuly");
-                               lldpctl_atom_dec_ref(config);
-                       }
-               } else {
-                       display_interfaces(conn, args.w,
-                           hidden, argc, argv);
-               }
-               args.w->finish(args.w);
-       } while (watch);
+               /* Execute command */
+               if (commands_execute(conn, w,
+                       root, argc, argv) != 0)
+                       log_info("lldpctl", "an error occurred while executing last command");
+               w->finish(w);
+               tok_reset(eltok);
+       }
 
-       lldpctl_release(conn);
-       return EXIT_SUCCESS;
+       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);
+       return rc;
 }
diff --git a/src/client/lldpctl.supp b/src/client/lldpctl.supp
new file mode 100644 (file)
index 0000000..2016899
--- /dev/null
@@ -0,0 +1,35 @@
+# We have those one-time leaks that we don't bother to correct. Those leaks
+# are due to the conversion of the some map strings to tags. This
+# happens only when registering new commands, so once.
+{
+   one-time-memory-leak-with-med-civicaddress-map-conversion
+   Memcheck:Leak
+   ...
+   fun:strdup
+   fun:register_commands_medloc_addr
+   ...
+}
+{
+   one-time-memory-leak-with-med-policy-map-conversion
+   Memcheck:Leak
+   ...
+   fun:strdup
+   fun:register_commands_medpol
+   ...
+}
+{
+   one-time-memory-leak-with-power-map-conversion
+   Memcheck:Leak
+   ...
+   fun:strdup
+   fun:register_commands_pow_priority
+   ...
+}
+{
+   one-time-memory-leak-with-dot3-power-map-conversion
+   Memcheck:Leak
+   ...
+   fun:strdup
+   fun:register_commands_dot3pow
+   ...
+}
diff --git a/src/client/misc.c b/src/client/misc.c
new file mode 100644 (file)
index 0000000..c6ce9ca
--- /dev/null
@@ -0,0 +1,68 @@
+/* -*- 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 <ctype.h>
+
+/**
+ * Check if an element is present in a comma-separated list.
+ *
+ * @param list    Comma-separated list of elements.
+ * @param element Element we want to check for.
+ * @return 0 if the element was not found, 1 otherwise.
+ */
+int
+contains(const char *list, const char *element)
+{
+       int len;
+       if (element == NULL || list == NULL) return 0;
+       while (list) {
+               len = strlen(element);
+               if (!strncmp(list, element, len) &&
+                   (list[len] == '\0' || list[len] == ','))
+                       return 1;
+               list = strchr(list, ',');
+               if (list) list++;
+       }
+       return 0;
+}
+
+/**
+ * Transform a string to a tag. This puts the string into lower space and
+ * replace spaces with '-'. The result is statically allocated.
+ *
+ * @param value String to transform to a tag.
+ * @return The tagged value or the string "none" if @c value is @c NULL
+ */
+char*
+totag(const char *value)
+{
+       int i;
+       static char *result = NULL;
+       free(result); result = NULL;
+       if (!value) return "none";
+       result = calloc(1, strlen(value) + 1);
+       if (!result) return "none";
+       for (i = 0; i < strlen(value); i++) {
+               switch (value[i]) {
+               case ' ': result[i] = '-'; break;
+               default: result[i] = tolower((int)value[i]); break;
+               }
+       }
+       return result;
+}
diff --git a/src/client/show.c b/src/client/show.c
new file mode 100644 (file)
index 0000000..8f64636
--- /dev/null
@@ -0,0 +1,233 @@
+/* -*- 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"
+
+/**
+ * Show neighbors.
+ *
+ * The environment will contain the following keys:
+ *  - C{ports} list of ports we want to restrict showing.
+ *  - C{hidden} if we should show hidden ports.
+ *  - C{summary} if we want to show only a summary
+ *  - C{detailed} for a detailed overview
+ */
+static int
+cmd_show_neighbors(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "show neighbors data (%s) %s hidden neighbors",
+           cmdenv_get(env, "summary")?"summary":
+           cmdenv_get(env, "detailed")?"detailed":
+           "normal", cmdenv_get(env, "hidden")?"with":"without");
+       if (cmdenv_get(env, "ports"))
+               log_debug("lldpctl", "restrict to the following ports: %s",
+                   cmdenv_get(env, "ports"));
+
+       display_interfaces(conn, w, env, !!cmdenv_get(env, "hidden"),
+           cmdenv_get(env, "summary")?DISPLAY_BRIEF:
+           cmdenv_get(env, "detailed")?DISPLAY_DETAILS:
+           DISPLAY_NORMAL);
+
+       return 1;
+}
+
+static int
+cmd_check_no_detailed_nor_summary(struct cmd_env *env, void *arg)
+{
+       if (cmdenv_get(env, "detailed")) return 0;
+       if (cmdenv_get(env, "summary")) return 0;
+       return 1;
+}
+
+/**
+ * Show running configuration.
+ */
+static int
+cmd_show_configuration(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "show running configuration");
+       display_configuration(conn, w);
+       return 1;
+}
+
+/**
+ * Callback for the next function to display a new neighbor.
+ */
+static void
+watchcb(lldpctl_conn_t *conn,
+    lldpctl_change_t type,
+    lldpctl_atom_t *interface,
+    lldpctl_atom_t *neighbor,
+    void *data)
+{
+       struct cmd_env *env = data;
+       struct writer *w = (struct writer *)cmdenv_get(env, "writer");
+       const char *interfaces = cmdenv_get(env, "ports");
+       if (interfaces && !contains(interfaces, lldpctl_atom_get_str(interface,
+                   lldpctl_k_interface_name)))
+               return;
+
+       switch (type) {
+       case lldpctl_c_deleted:
+               tag_start(w, "lldp-deleted", "LLDP neighbor deleted");
+               break;
+       case lldpctl_c_updated:
+               tag_start(w, "lldp-updated", "LLDP neighbor updated");
+               break;
+       case lldpctl_c_added:
+               tag_start(w, "lldp-added", "LLDP neighbor added");
+               break;
+       default: return;
+       }
+       display_interface(conn, w, 1, interface, neighbor,
+           cmdenv_get(env, "summary")?DISPLAY_BRIEF:
+           cmdenv_get(env, "detailed")?DISPLAY_DETAILS:
+           DISPLAY_NORMAL);
+       tag_end(w);
+}
+
+/**
+ * Watch for neighbor changes.
+ */
+static int
+cmd_watch_neighbors(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       int watch = 1;
+       log_debug("lldpctl", "watch for neighbor changes");
+       if (lldpctl_watch_callback(conn, watchcb, env) < 0) {
+               log_warnx("lldpctl", "unable to watch for neighbors. %s",
+                   lldpctl_last_strerror(conn));
+               return 0;
+       }
+       cmdenv_put(env, "writer", (const char*)w); /* Hackish, but we really
+                                                   * don't care. */
+       while (watch) {
+               if (lldpctl_watch(conn) < 0) {
+                       log_warnx("lldpctl", "unable to watch for neighbors. %s",
+                           lldpctl_last_strerror(conn));
+                       watch = 0;
+               }
+       }
+       return 0;
+}
+
+/**
+ * Register common subcommands for `watch` and `show neighbors`.
+ */
+void
+register_common_commands(struct cmd_node *root)
+{
+       /* With hidden neighbors */
+       commands_new(root,
+           "hidden",
+           "Include hidden neighbors",
+           cmd_check_no_env, cmd_store_env_and_pop, "hidden");
+
+       /* With more details */
+       commands_new(root,
+           "details",
+           "With more details",
+           cmd_check_no_detailed_nor_summary, cmd_store_env_and_pop, "detailed");
+
+       /* With less details */
+       commands_new(root,
+           "summary",
+           "With less details",
+           cmd_check_no_detailed_nor_summary, cmd_store_env_and_pop, "summary");
+
+       /* Some specific port */
+       commands_new(
+               commands_new(root,
+                   "ports",
+                   "Restrict to neighbors seen on some ports",
+                   cmd_check_no_env, NULL, "ports"),
+               NULL,
+               "Restrict to neighbors on those ports (comma-separated list)",
+               NULL, cmd_store_env_value_and_pop2, "ports");
+}
+
+/**
+ * Register subcommands to `show`
+ *
+ * @param root Root node
+ */
+void
+register_commands_show(struct cmd_node *root)
+{
+       struct cmd_node *show = commands_new(
+               root,
+               "show",
+               "Show running system information",
+               NULL, NULL, NULL);
+       struct cmd_node *neighbors = commands_new(
+               show,
+               "neighbors",
+               "Show neighbors data",
+               NULL, NULL, NULL);
+
+       /* Neighbors data */
+       commands_new(neighbors,
+           NEWLINE,
+           "Show neighbors data",
+           NULL, cmd_show_neighbors, NULL);
+
+       register_common_commands(neighbors);
+
+       /* Register "show configuration" and "show running-configuration" */
+       commands_new(
+               commands_new(show,
+                   "configuration",
+                   "Show running configuration",
+                   NULL, NULL, NULL),
+               NEWLINE,
+               "Show running configuration",
+               NULL, cmd_show_configuration, NULL);
+       commands_new(
+               commands_new(show,
+                   "running-configuration",
+                   "Show running configuration",
+                   NULL, NULL, NULL),
+               NEWLINE,
+               "Show running configuration",
+               NULL, cmd_show_configuration, NULL);
+}
+
+/**
+ * Register subcommands to `watch`
+ *
+ * @param root Root node
+ */
+void
+register_commands_watch(struct cmd_node *root)
+{
+       struct cmd_node *watch = commands_new(
+               root,
+               "watch",
+               "Monitor neighbor changes",
+               NULL, NULL, NULL);
+
+       /* Neighbors data */
+       commands_new(watch,
+           NEWLINE,
+           "Monitor neighbors change",
+           NULL, cmd_watch_neighbors, NULL);
+
+       register_common_commands(watch);
+}
index d8ed0ac5146bee8c32deaa6f6539f13b135c9aeb..32ec285a3b2e78662e1f1d0524f77a86365b1cd6 100644 (file)
@@ -34,11 +34,11 @@ void xml_start(struct writer * w , const char * tag, const char * descr ) {
        struct xml_writer_private * p = w->priv;
 
        if (xmlTextWriterStartElement(p->xw, BAD_CAST tag) < 0)
-               log_warnx(NULL, "cannot start '%s' element", tag);
+               log_warnx("lldpctl", "cannot start '%s' element", tag);
 
        if ( descr && (strlen(descr) > 0) ) {
                if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST "label", "%s", descr) < 0)
-                       log_warnx(NULL, "cannot add attribute 'label' to element %s", tag);
+                       log_warnx("lldpctl", "cannot add attribute 'label' to element %s", tag);
        }
 }
 
@@ -46,20 +46,20 @@ void xml_attr(struct writer * w, const char * tag, const char * descr, const cha
        struct xml_writer_private * p = w->priv;
 
        if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST tag, "%s", value) < 0)
-               log_warnx(NULL, "cannot add attribute %s with value %s", tag, value);
+               log_warnx("lldpctl", "cannot add attribute %s with value %s", tag, value);
 }
 
 void xml_data(struct writer * w, const char * data) {
        struct xml_writer_private * p = w->priv;
        if (xmlTextWriterWriteString(p->xw, BAD_CAST data) < 0 )
-               log_warnx(NULL, "cannot add '%s' as data to element", data);
+               log_warnx("lldpctl", "cannot add '%s' as data to element", data);
 }
 
 void xml_end(struct writer * w) {
        struct xml_writer_private * p = w->priv;
 
        if (xmlTextWriterEndElement(p->xw) < 0 )
-               log_warnx(NULL, "cannot end element");
+               log_warnx("lldpctl", "cannot end element");
 }
 
 #define MY_ENCODING "UTF-8"
@@ -69,7 +69,7 @@ void xml_finish(struct writer * w) {
        int failed = 0;
 
        if (xmlTextWriterEndDocument(p->xw) < 0 ) {
-               log_warnx(NULL, "cannot finish document");
+               log_warnx("lldpctl", "cannot finish document");
                failed = 1;
        }