From 9a775667baec4ef97d1d35729a374247bf4faf13 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Sat, 5 Jan 2013 14:44:46 +0100 Subject: [PATCH] lldpctl: add a CLI CLI provides contextual help and completion. It uses libedit. --- NEWS | 6 +- configure.ac | 3 + src/client/Makefile.am | 12 +- src/client/actions.c | 1231 ++++++++++++++++++++++++++++---------- src/client/client.h | 62 +- src/client/commands.c | 645 ++++++++++++++++++++ src/client/display.c | 115 ++-- src/client/json_writer.c | 8 +- src/client/lldpctl.8 | 707 +++++++++++----------- src/client/lldpctl.c | 359 ++++++----- src/client/lldpctl.supp | 35 ++ src/client/misc.c | 68 +++ src/client/show.c | 233 ++++++++ src/client/xml_writer.c | 12 +- 14 files changed, 2629 insertions(+), 867 deletions(-) create mode 100644 src/client/commands.c create mode 100644 src/client/lldpctl.supp create mode 100644 src/client/misc.c create mode 100644 src/client/show.c diff --git a/NEWS b/NEWS index 555a4c69..53917d4c 100644 --- 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. diff --git a/configure.ac b/configure.ac index 32b26ff2..528a8f8b 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/client/Makefile.am b/src/client/Makefile.am index 910e098c..ee13c3aa 100644 --- a/src/client/Makefile.am +++ b/src/client/Makefile.am @@ -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 diff --git a/src/client/actions.c b/src/client/actions.c index d8932f01..706b9935 100644 --- a/src/client/actions.c +++ b/src/client/actions.c @@ -21,354 +21,985 @@ #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); } diff --git a/src/client/client.h b/src/client/client.h index 5ffa1b76..232c688b 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -24,17 +24,71 @@ #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 "" +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 index 00000000..2ac46ed1 --- /dev/null +++ b/src/client/commands.c @@ -0,0 +1,645 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +/** + * 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, ¤t->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, ¤t->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, ¤t->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; +} diff --git a/src/client/display.c b/src/client/display.c index 74b0d35e..3169e81e 100644 --- a/src/client/display.c +++ b/src/client/display.c @@ -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; } diff --git a/src/client/json_writer.c b/src/client/json_writer.c index 609010de..bf914a63 100644 --- a/src/client/json_writer.c +++ b/src/client/json_writer.c @@ -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); diff --git a/src/client/lldpctl.8 b/src/client/lldpctl.8 index 6072aed3..253b1651 100644 --- a/src/client/lldpctl.8 +++ b/src/client/lldpctl.8 @@ -21,12 +21,9 @@ .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 diff --git a/src/client/lldpctl.c b/src/client/lldpctl.c index fc56a002..409fb335 100644 --- a/src/client/lldpctl.c +++ b/src/client/lldpctl.c @@ -25,44 +25,29 @@ #include #include #include +#include -#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 index 00000000..20168992 --- /dev/null +++ b/src/client/lldpctl.supp @@ -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 index 00000000..c6ce9caa --- /dev/null +++ b/src/client/misc.c @@ -0,0 +1,68 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +/** + * 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 index 00000000..8f646360 --- /dev/null +++ b/src/client/show.c @@ -0,0 +1,233 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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); +} diff --git a/src/client/xml_writer.c b/src/client/xml_writer.c index d8ed0ac5..32ec285a 100644 --- a/src/client/xml_writer.c +++ b/src/client/xml_writer.c @@ -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; } -- 2.39.5