| /* |
| * teamdctl.c - Network team device daemon control tool |
| * Copyright (C) 2013-2015 Jiri Pirko <jiri@resnulli.us> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <getopt.h> |
| #include <errno.h> |
| #include <jansson.h> |
| #include <private/misc.h> |
| #include <teamdctl.h> |
| |
| #include "config.h" |
| |
| enum verbosity_level { |
| VERB1, |
| VERB2, |
| VERB3, |
| VERB4, |
| }; |
| |
| static bool g_oneline = false; |
| #define DEFAULT_VERB VERB1 |
| static int g_verbosity = DEFAULT_VERB; |
| static int g_indent_level = 0; |
| #define INDENT_STR_STEP 2 |
| #define INDENT_STR_MAXLEN 32 |
| static char g_indent_str[INDENT_STR_MAXLEN + 1] = ""; |
| |
| static void pr_out_indent_inc(void) |
| { |
| if (g_indent_level + INDENT_STR_STEP > INDENT_STR_MAXLEN) |
| return; |
| g_indent_level += INDENT_STR_STEP; |
| memset(g_indent_str, ' ', sizeof(g_indent_str)); |
| g_indent_str[g_indent_level] = '\0'; |
| } |
| |
| static void pr_out_indent_dec(void) |
| { |
| if (g_indent_level - INDENT_STR_STEP < 0) |
| return; |
| g_indent_level -= INDENT_STR_STEP; |
| g_indent_str[g_indent_level] = '\0'; |
| } |
| |
| #define pr_err(args...) fprintf(stderr, ##args) |
| #define pr_outx(verb_level, args...) \ |
| if (verb_level <= g_verbosity) { \ |
| fprintf(stdout, "%s", g_indent_str); \ |
| fprintf(stdout, ##args); \ |
| } |
| #define pr_out(args...) pr_outx(DEFAULT_VERB, ##args) |
| #define pr_out2(args...) pr_outx(VERB2, ##args) |
| #define pr_out3(args...) pr_outx(VERB3, ##args) |
| #define pr_out4(args...) pr_outx(VERB4, ##args) |
| |
| static int __jsonload(json_t **pjson, char *inputstrjson) |
| { |
| json_t *json; |
| json_error_t jerror; |
| |
| json = json_loads(inputstrjson, JSON_REJECT_DUPLICATES, &jerror); |
| if (!json) { |
| pr_err("Failed to parse JSON dump.\n"); |
| return -EINVAL; |
| } |
| *pjson = json; |
| return 0; |
| } |
| |
| static int __jsondump(json_t *json) |
| { |
| char *dump; |
| int indent = g_oneline ? 0 : 4; |
| |
| dump = json_dumps(json, JSON_INDENT(indent) | JSON_ENSURE_ASCII | |
| JSON_SORT_KEYS); |
| if (!dump) { |
| pr_err("Failed to get JSON dump.\n"); |
| return -ENOMEM; |
| } |
| pr_out("%s\n", dump); |
| free(dump); |
| return 0; |
| } |
| |
| static int __jsonloaddump(char *inputstrjson) |
| { |
| int err; |
| json_t *json; |
| |
| err = __jsonload(&json, inputstrjson); |
| if (err) |
| return err; |
| err = __jsondump(json); |
| json_decref(json); |
| return err; |
| } |
| |
| static int jsonsimpledump_process_reply(char *reply) |
| { |
| return __jsonloaddump(reply); |
| } |
| |
| static int noportsdump_json_process(char *dump) |
| { |
| int err; |
| json_t *json; |
| |
| err = __jsonload(&json, dump); |
| if (err) |
| return err; |
| json_object_del(json, "ports"); |
| err = __jsondump(json); |
| json_decref(json); |
| return err; |
| } |
| |
| static int jsonnoportsdump_process_reply(char *reply) |
| { |
| return noportsdump_json_process(reply); |
| } |
| |
| #define boolyesno(val) (val ? "yes" : "no") |
| #define boolupdown(val) (val ? "up" : "down") |
| |
| static int stateview_json_setup_process(char **prunner_name, json_t *dump_json) |
| { |
| int err; |
| char *runner_name; |
| char *kernel_team_mode_name; |
| int dbus_enabled; |
| int zmq_enabled; |
| int debug_level; |
| int daemonized; |
| int pid; |
| char *pid_file; |
| |
| pr_out("setup:\n"); |
| err = json_unpack(dump_json, "{s:{s:s, s:s, s:b, s:b, s:i, s:b, s:i, s:s}}", |
| "setup", |
| "runner_name", &runner_name, |
| "kernel_team_mode_name", &kernel_team_mode_name, |
| "dbus_enabled", &dbus_enabled, |
| "zmq_enabled", &zmq_enabled, |
| "debug_level", &debug_level, |
| "daemonized", &daemonized, |
| "pid", &pid, |
| "pid_file", &pid_file); |
| if (err) { |
| pr_err("Failed to parse JSON setup dump.\n"); |
| return -EINVAL; |
| } |
| pr_out_indent_inc(); |
| pr_out("runner: %s\n", runner_name); |
| pr_out2("kernel team mode: %s\n", kernel_team_mode_name); |
| pr_out2("D-BUS enabled: %s\n", boolyesno(dbus_enabled)); |
| pr_out2("ZeroMQ enabled: %s\n", boolyesno(zmq_enabled)); |
| pr_out2("debug level: %d\n", debug_level); |
| pr_out2("daemonized: %s\n", boolyesno(daemonized)); |
| pr_out2("PID: %d\n", pid); |
| pr_out2("PID file: %s\n", pid_file); |
| pr_out_indent_dec(); |
| |
| *prunner_name = runner_name; |
| return 0; |
| } |
| |
| static int stateview_json_link_watch_info_process(char *lw_name, |
| json_t *lw_json) |
| { |
| int err; |
| |
| if (!strcmp(lw_name, "ethtool")) { |
| int delay_up; |
| int delay_down; |
| |
| err = json_unpack(lw_json, "{s:i, s:i}", |
| "delay_up", &delay_up, |
| "delay_down", &delay_down); |
| if (err) { |
| pr_err("Failed to parse JSON ethtool link watch dump.\n"); |
| return -EINVAL; |
| } |
| pr_out2("link up delay: %d\n", delay_up); |
| pr_out2("link down delay: %d\n", delay_down); |
| } else if (!strcmp(lw_name, "arp_ping")) { |
| char *source_host; |
| char *target_host; |
| int interval; |
| int init_wait; |
| int validate_active; |
| int validate_inactive; |
| int send_always; |
| int missed_max; |
| int missed; |
| |
| err = json_unpack(lw_json, "{s:s, s:s, s:i, s:i, s:b, s:b, s:b, s:i, s:i}", |
| "source_host", &source_host, |
| "target_host", &target_host, |
| "interval", &interval, |
| "init_wait", &init_wait, |
| "validate_active", &validate_active, |
| "validate_inactive", &validate_inactive, |
| "send_always", &send_always, |
| "missed_max", &missed_max, |
| "missed", &missed); |
| if (err) { |
| pr_err("Failed to parse JSON arp_ping link watch dump.\n"); |
| return -EINVAL; |
| } |
| pr_out2("source host: %s\n", source_host); |
| pr_out2("target host: %s\n", target_host); |
| pr_out2("interval: %d\n", interval); |
| pr_out2("missed packets: %d/%d\n", missed, missed_max); |
| pr_out2("validate_active: %s\n", boolyesno(validate_active)); |
| pr_out2("validate_inactive: %s\n", boolyesno(validate_inactive)); |
| pr_out2("send_always: %s\n", boolyesno(send_always)); |
| pr_out2("initial wait: %d\n", init_wait); |
| } else if (!strcmp(lw_name, "nsna_ping")) { |
| char *target_host; |
| int interval; |
| int init_wait; |
| int missed_max; |
| int missed; |
| |
| err = json_unpack(lw_json, "{s:s, s:i, s:i, s:i, s:i}", |
| "target_host", &target_host, |
| "interval", &interval, |
| "init_wait", &init_wait, |
| "missed_max", &missed_max, |
| "missed", &missed); |
| if (err) { |
| pr_err("Failed to parse JSON nsna_ping link watch dump.\n"); |
| return -EINVAL; |
| } |
| pr_out2("target host: %s\n", target_host); |
| pr_out2("interval: %d\n", interval); |
| pr_out2("missed packets: %d/%d\n", missed, missed_max); |
| pr_out2("initial wait: %d\n", init_wait); |
| } else if (!strcmp(lw_name, "tipc")) { |
| char *tipc_bearer; |
| |
| err = json_unpack(lw_json, "{s:s}", "tipc_bearer", &tipc_bearer); |
| if (err) { |
| pr_err("Failed to parse JSON tipc_bearer link watch dump\n"); |
| return -EINVAL; |
| } |
| pr_out2("tipc bearer: %s\n", tipc_bearer); |
| } else { |
| pr_err("Failed to parse JSON unknown link watch info dump.\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int stateview_json_port_link_watches_list_process(json_t *port_link_watches_json) |
| { |
| int err; |
| int up; |
| int down_count; |
| json_t *lw_list_json; |
| json_t *lw_json; |
| char *lw_name; |
| const char *key; |
| |
| err = json_unpack(port_link_watches_json, "{s:o}", "list", &lw_list_json); |
| if (err) |
| return 0; |
| json_object_foreach(lw_list_json, key, lw_json) { |
| err = json_unpack(lw_json, "{s:b, s:s, s:i}", |
| "up", &up, "name", &lw_name, |
| "down_count", &down_count); |
| if (err) { |
| pr_err("Failed to parse JSON port link watch dump.\n"); |
| return -EINVAL; |
| } |
| pr_out("instance[%s]:\n", key); |
| pr_out_indent_inc(); |
| pr_out("name: %s\n", lw_name); |
| pr_out("link: %s\n", boolupdown(up)); |
| pr_out("down count: %d\n", down_count); |
| err = stateview_json_link_watch_info_process(lw_name, |
| lw_json); |
| if (err) |
| return err; |
| pr_out_indent_dec(); |
| } |
| return 0; |
| } |
| |
| static int stateview_json_port_link_watches_process(json_t *port_link_watches_json) |
| { |
| int err; |
| int up; |
| |
| err = json_unpack(port_link_watches_json, "{s:b}", "up", &up); |
| if (err) { |
| pr_err("Failed to parse JSON port link watches dump.\n"); |
| return -EINVAL; |
| } |
| pr_out("link watches:\n"); |
| pr_out_indent_inc(); |
| pr_out("link summary: %s\n", boolupdown(up)); |
| err = stateview_json_port_link_watches_list_process(port_link_watches_json); |
| if (err) |
| return err; |
| pr_out_indent_dec(); |
| return 0; |
| } |
| |
| static int stateview_json_lacpdu_process(json_t *lacpdu_json) |
| { |
| int err; |
| int system_priority; |
| char *system; |
| int key; |
| int port_priority; |
| int port; |
| int state; |
| |
| err = json_unpack(lacpdu_json, "{s:i, s:s, s:i, s:i, s:i, s:i}", |
| "system_priority", &system_priority, |
| "system", &system, |
| "key", &key, |
| "port_priority", &port_priority, |
| "port", &port, |
| "state", &state); |
| if (err) { |
| pr_err("Failed to parse JSON port runner lacpdu dump.\n"); |
| return -EINVAL; |
| } |
| pr_out2("system priority: %d\n", system_priority); |
| pr_out2("system: %s\n", system); |
| pr_out2("key: %d\n", key); |
| pr_out2("port_priority: %d\n", port_priority); |
| pr_out2("port: %d\n", port); |
| pr_out2("state: 0x%x\n", state); |
| return 0; |
| } |
| |
| static int stateview_json_port_runner_process(char *runner_name, |
| json_t *port_json) |
| { |
| int err; |
| |
| if (!strcmp(runner_name, "lacp")) { |
| int selected; |
| int aggregator_id; |
| int aggregator_selected; |
| char *state; |
| int key; |
| int prio; |
| json_t *actor_json; |
| json_t *partner_json; |
| |
| pr_out("runner:\n"); |
| err = json_unpack(port_json, |
| "{s:{s:b, s:{s:i, s:b}, s:s, s:i, s:i, s:o, s:o}}", |
| "runner", |
| "selected", &selected, |
| "aggregator", "id", &aggregator_id, |
| "selected", &aggregator_selected, |
| "state", &state, |
| "key", &key, |
| "prio", &prio, |
| "actor_lacpdu_info", &actor_json, |
| "partner_lacpdu_info", &partner_json); |
| if (err) { |
| pr_err("Failed to parse JSON port runner dump.\n"); |
| return -EINVAL; |
| } |
| pr_out_indent_inc(); |
| pr_out("aggregator ID: %d%s\n", aggregator_id, |
| aggregator_selected ? ", Selected" : ""); |
| pr_out("selected: %s\n", boolyesno(selected)); |
| pr_out("state: %s\n", state); |
| pr_out2("key: %d\n", key); |
| pr_out2("priority: %d\n", prio); |
| pr_out2("actor LACPDU info:\n"); |
| pr_out_indent_inc(); |
| err = stateview_json_lacpdu_process(actor_json); |
| if (err) |
| return err; |
| pr_out_indent_dec(); |
| pr_out2("partner LACPDU info:\n"); |
| pr_out_indent_inc(); |
| err = stateview_json_lacpdu_process(partner_json); |
| if (err) |
| return err; |
| pr_out_indent_dec(); |
| pr_out_indent_dec(); |
| } |
| return 0; |
| } |
| |
| static int stateview_json_port_process(char *runner_name, const char *port_name, |
| json_t *port_json) |
| { |
| int err; |
| char *dev_addr; |
| int dev_addr_len; |
| int ifindex; |
| char *ifname; |
| char *duplex; |
| int speed; |
| int up; |
| json_t *port_link_watches_json; |
| |
| err = json_unpack(port_json, |
| "{s:{s:s, s:i, s:i, s:s}, s:{s:s, s:i, s:b}, s:o}", |
| "ifinfo", |
| "dev_addr", &dev_addr, |
| "dev_addr_len", &dev_addr_len, |
| "ifindex", &ifindex, |
| "ifname", &ifname, |
| "link", |
| "duplex", &duplex, |
| "speed", &speed, |
| "up", &up, |
| "link_watches", &port_link_watches_json); |
| if (err) { |
| pr_err("Failed to parse JSON port dump.\n"); |
| return -EINVAL; |
| } |
| pr_out("%s\n", port_name); |
| pr_out_indent_inc(); |
| pr_out2("ifindex: %d\n", ifindex); |
| pr_out2("addr: %s\n", dev_addr); |
| pr_out2("ethtool link: %dmbit/%sduplex/%s\n", speed, duplex, |
| boolupdown(up)); |
| err = stateview_json_port_link_watches_process(port_link_watches_json); |
| if (err) |
| goto err_out; |
| err = stateview_json_port_runner_process(runner_name, port_json); |
| pr_out_indent_dec(); |
| err_out: |
| return err; |
| } |
| |
| static int stateview_json_ports_process(char *runner_name, json_t *dump_json) |
| { |
| int err; |
| json_t *ports_json; |
| json_t *iter; |
| |
| err = json_unpack(dump_json, "{s:o}", "ports", &ports_json); |
| if (err) |
| return 0; |
| pr_out("ports:\n"); |
| for (iter = json_object_iter(ports_json); iter; |
| iter = json_object_iter_next(ports_json, iter)) { |
| const char *port_name = json_object_iter_key(iter); |
| json_t *port_json = json_object_iter_value(iter); |
| |
| pr_out_indent_inc(); |
| err = stateview_json_port_process(runner_name, port_name, |
| port_json); |
| if (err) |
| return err; |
| pr_out_indent_dec(); |
| } |
| return 0; |
| } |
| |
| static int stateview_json_runner_process(char *runner_name, json_t *json) |
| { |
| int err; |
| |
| if (!strcmp(runner_name, "activebackup")) { |
| char *active_port; |
| |
| pr_out("runner:\n"); |
| err = json_unpack(json, "{s:{s:s}}", "runner", |
| "active_port", &active_port); |
| if (err) { |
| pr_err("Failed to parse JSON runner dump.\n"); |
| return -EINVAL; |
| } |
| pr_out_indent_inc(); |
| pr_out("active port: %s\n", active_port); |
| pr_out_indent_dec(); |
| } else if (!strcmp(runner_name, "lacp")) { |
| int active; |
| int sys_prio; |
| int fast_rate; |
| |
| pr_out("runner:\n"); |
| err = json_unpack(json, "{s:{s:b, s:i, s:b}}", "runner", |
| "active", &active, |
| "sys_prio", &sys_prio, |
| "fast_rate", &fast_rate); |
| if (err) { |
| pr_err("Failed to parse JSON runner dump.\n"); |
| return -EINVAL; |
| } |
| pr_out_indent_inc(); |
| pr_out("active: %s\n", boolyesno(active)); |
| pr_out("fast rate: %s\n", boolyesno(fast_rate)); |
| pr_out2("system priority: %d\n", sys_prio); |
| pr_out_indent_dec(); |
| } |
| return 0; |
| } |
| |
| static int stateview_json_process(char *dump) |
| { |
| int err; |
| char *runner_name; |
| json_t *dump_json; |
| |
| err = __jsonload(&dump_json, dump); |
| if (err) |
| return err; |
| err = stateview_json_setup_process(&runner_name, dump_json); |
| if (err) |
| goto free_json; |
| err = stateview_json_ports_process(runner_name, dump_json); |
| if (err) |
| goto free_json; |
| err = stateview_json_runner_process(runner_name, dump_json); |
| free_json: |
| json_decref(dump_json); |
| return err; |
| } |
| |
| static int stateview_process_reply(char *reply) |
| { |
| return stateview_json_process(reply); |
| } |
| |
| static int state_json_port_present(char *dump, const char *port_devname) |
| { |
| json_t *dump_json; |
| json_t *port_json; |
| int err; |
| |
| err = __jsonload(&dump_json, dump); |
| if (err) |
| return err; |
| err = json_unpack(dump_json, "{s:{s:o}}", "ports", port_devname, &port_json); |
| if (err) |
| err = -ENODEV; |
| json_decref(dump_json); |
| return err; |
| } |
| |
| static int call_method_config_jsonsimpledump(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return jsonsimpledump_process_reply(teamdctl_config_get_raw(tdc)); |
| } |
| |
| static int call_method_config_jsonnoportsdump(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return jsonnoportsdump_process_reply(teamdctl_config_get_raw(tdc)); |
| } |
| |
| static int call_method_config_actual_jsonsimpledump(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return jsonsimpledump_process_reply(teamdctl_config_actual_get_raw(tdc)); |
| } |
| |
| static int call_method_state_jsonsimpledump(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return jsonsimpledump_process_reply(teamdctl_state_get_raw(tdc)); |
| } |
| |
| static int call_method_state_stateview(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return stateview_process_reply(teamdctl_state_get_raw(tdc)); |
| } |
| |
| static int call_method_port_add(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return teamdctl_port_add(tdc, argv[0]); |
| } |
| |
| static int call_method_port_remove(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return teamdctl_port_remove(tdc, argv[0]); |
| } |
| |
| static int call_method_port_present(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return state_json_port_present(teamdctl_state_get_raw(tdc), argv[0]); |
| } |
| |
| static int call_method_port_config_update(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return teamdctl_port_config_update_raw(tdc, argv[0], argv[1]); |
| } |
| |
| static int call_method_port_config_dump(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| int err; |
| char *cfg; |
| |
| err = teamdctl_port_config_get_raw_direct(tdc, argv[0], &cfg); |
| if (err) |
| return err; |
| return jsonsimpledump_process_reply(cfg); |
| } |
| |
| static int call_method_state_item_get(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| char *reply; |
| int err; |
| |
| err = teamdctl_state_item_value_get(tdc, argv[0], &reply); |
| if (err) |
| return err; |
| pr_out("%s\n", reply); |
| free(reply); |
| return 0; |
| } |
| |
| static int call_method_state_item_set(struct teamdctl *tdc, |
| int argc, char **argv) |
| { |
| return teamdctl_state_item_value_set(tdc, argv[0], argv[1]); |
| } |
| |
| |
| enum id_command_type { |
| ID_CMDTYPE_NONE = 0, |
| ID_CMDTYPE_C, |
| ID_CMDTYPE_C_D, |
| ID_CMDTYPE_C_D_N, |
| ID_CMDTYPE_C_D_A, |
| ID_CMDTYPE_S, |
| ID_CMDTYPE_S_D, |
| ID_CMDTYPE_S_V, |
| ID_CMDTYPE_S_I, |
| ID_CMDTYPE_S_I_G, |
| ID_CMDTYPE_S_I_S, |
| ID_CMDTYPE_P, |
| ID_CMDTYPE_P_A, |
| ID_CMDTYPE_P_R, |
| ID_CMDTYPE_P_P, |
| ID_CMDTYPE_P_C, |
| ID_CMDTYPE_P_C_U, |
| ID_CMDTYPE_P_C_D, |
| }; |
| |
| typedef int (*process_reply_t)(int argc, char **argv, char *reply); |
| typedef int (*call_method_t)(struct teamdctl *tdc, int argc, char **argv); |
| |
| #define COMMAND_PARAM_MAX_CNT 8 |
| |
| struct command_type { |
| enum id_command_type id; |
| enum id_command_type parent_id; |
| char *name; |
| char *params[COMMAND_PARAM_MAX_CNT]; |
| call_method_t call_method; |
| process_reply_t process_reply; |
| size_t priv_size; |
| }; |
| |
| static struct command_type command_types[] = { |
| { |
| .id = ID_CMDTYPE_C, |
| .name = "config", |
| }, |
| { |
| .id = ID_CMDTYPE_C_D, |
| .parent_id = ID_CMDTYPE_C, |
| .name = "dump", |
| .call_method = call_method_config_jsonsimpledump, |
| }, |
| { |
| .id = ID_CMDTYPE_C_D_N, |
| .parent_id = ID_CMDTYPE_C_D, |
| .name = "noports", |
| .call_method = call_method_config_jsonnoportsdump, |
| }, |
| { |
| .id = ID_CMDTYPE_C_D_A, |
| .parent_id = ID_CMDTYPE_C_D, |
| .name = "actual", |
| .call_method = call_method_config_actual_jsonsimpledump, |
| }, |
| { |
| .id = ID_CMDTYPE_S, |
| .name = "state", |
| .call_method = call_method_state_stateview, |
| }, |
| { |
| .id = ID_CMDTYPE_S_D, |
| .parent_id = ID_CMDTYPE_S, |
| .name = "dump", |
| .call_method = call_method_state_jsonsimpledump, |
| }, |
| { |
| .id = ID_CMDTYPE_S_V, |
| .parent_id = ID_CMDTYPE_S, |
| .name = "view", |
| .call_method = call_method_state_stateview, |
| }, |
| { |
| .id = ID_CMDTYPE_S_I, |
| .parent_id = ID_CMDTYPE_S, |
| .name = "item", |
| }, |
| { |
| .id = ID_CMDTYPE_S_I_G, |
| .parent_id = ID_CMDTYPE_S_I, |
| .name = "get", |
| .call_method = call_method_state_item_get, |
| .params = {"ITEMPATH"}, |
| }, |
| { |
| .id = ID_CMDTYPE_S_I_S, |
| .parent_id = ID_CMDTYPE_S_I, |
| .name = "set", |
| .call_method = call_method_state_item_set, |
| .params = {"ITEMPATH", "VALUE"}, |
| }, |
| { |
| .id = ID_CMDTYPE_P, |
| .name = "port", |
| }, |
| { |
| .id = ID_CMDTYPE_P_A, |
| .parent_id = ID_CMDTYPE_P, |
| .name = "add", |
| .call_method = call_method_port_add, |
| .params = {"PORTDEV"}, |
| }, |
| { |
| .id = ID_CMDTYPE_P_R, |
| .parent_id = ID_CMDTYPE_P, |
| .name = "remove", |
| .call_method = call_method_port_remove, |
| .params = {"PORTDEV"}, |
| }, |
| { |
| .id = ID_CMDTYPE_P_P, |
| .parent_id = ID_CMDTYPE_P, |
| .name = "present", |
| .call_method = call_method_port_present, |
| .params = {"PORTDEV"}, |
| }, |
| { |
| .id = ID_CMDTYPE_P_C, |
| .parent_id = ID_CMDTYPE_P, |
| .name = "config", |
| }, |
| { |
| .id = ID_CMDTYPE_P_C_U, |
| .parent_id = ID_CMDTYPE_P_C, |
| .name = "update", |
| .call_method = call_method_port_config_update, |
| .params = {"PORTDEV", "PORTCONFIG"}, |
| }, |
| { |
| .id = ID_CMDTYPE_P_C_D, |
| .parent_id = ID_CMDTYPE_P_C, |
| .name = "dump", |
| .call_method = call_method_port_config_dump, |
| .params = {"PORTDEV"}, |
| }, |
| }; |
| |
| #define COMMAND_TYPE_COUNT ARRAY_SIZE(command_types) |
| |
| static bool __cmd_executable(struct command_type *command_type) |
| { |
| return command_type->call_method; |
| } |
| |
| static int __cmd_param_cnt(struct command_type *command_type) |
| { |
| int i = 0; |
| |
| while (command_type->params[i]) |
| i++; |
| return i; |
| } |
| |
| static struct command_type *__get_cmd_by_parent(char *cmd_name, |
| enum id_command_type parent_id) |
| { |
| int i; |
| |
| for (i = 0; i < COMMAND_TYPE_COUNT; i++) { |
| if (!strncmp(command_types[i].name, cmd_name, |
| strlen(cmd_name)) && |
| command_types[i].parent_id == parent_id) |
| return &command_types[i]; |
| } |
| return NULL; |
| } |
| |
| static struct command_type *__get_cmd_by_id(enum id_command_type id) |
| { |
| int i; |
| |
| for (i = 0; i < COMMAND_TYPE_COUNT; i++) { |
| if (command_types[i].id == id) |
| return &command_types[i]; |
| } |
| return NULL; |
| } |
| |
| static int find_command(struct command_type **pcommand_type, |
| int *argc, char ***argv) |
| { |
| char *cmd_name; |
| enum id_command_type parent_id = ID_CMDTYPE_NONE; |
| struct command_type *command_type; |
| |
| while (1) { |
| if (!*argc) { |
| pr_err("None or incomplete command\n"); |
| return -EINVAL; |
| } |
| cmd_name = *argv[0]; |
| (*argc)--; |
| (*argv)++; |
| command_type = __get_cmd_by_parent(cmd_name, parent_id); |
| if (!command_type) { |
| pr_err("Unknown command \"%s\".\n", cmd_name); |
| return -EINVAL; |
| } |
| if (__cmd_executable(command_type) && |
| __cmd_param_cnt(command_type) >= *argc) { |
| *pcommand_type = command_type; |
| return 0; |
| } |
| parent_id = command_type->id; |
| } |
| } |
| |
| static int check_command_params(struct command_type *command_type, |
| int argc, char **argv) |
| { |
| int i = 0; |
| |
| while (command_type->params[i]) { |
| if (i == argc) { |
| pr_err("Command line parameter \"%s\" expected.\n", |
| command_type->params[i]); |
| return -EINVAL; |
| } |
| i++; |
| } |
| return 0; |
| } |
| |
| static int check_team_devname(char *team_devname) |
| { |
| int err; |
| uint32_t ifindex = 0; /* gcc needs this initialized */ |
| |
| err = ifname2ifindex(&ifindex, team_devname); |
| if (err) { |
| pr_err("Device \"%s\" - failed to get interface index (%s)\n", |
| team_devname, strerror(-err)); |
| return err; |
| } |
| if (!ifindex) { |
| pr_err("Device \"%s\" does not exist\n", team_devname); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| static int check_teamd_team_devname(struct teamdctl *tdc, |
| const char *team_devname) |
| { |
| json_t *dump_json; |
| char *devname; |
| int err; |
| |
| err = __jsonload(&dump_json, teamdctl_config_get_raw(tdc)); |
| if (err) |
| return err; |
| err = json_unpack(dump_json, "{s:s}", "device", &devname); |
| if (err) { |
| pr_err("Failed to parse device name from config.\n"); |
| err = -EINVAL; |
| goto free_json; |
| } |
| |
| if (strcmp(team_devname, devname)) { |
| pr_err("Unable to access to %s through connected teamd daemon because it controls %s.\n", |
| team_devname, devname); |
| err = -EINVAL; |
| goto free_json; |
| } |
| |
| free_json: |
| json_decref(dump_json); |
| return err; |
| } |
| |
| static int call_command(struct teamdctl *tdc, int argc, char **argv, |
| struct command_type *command_type) |
| { |
| return command_type->call_method(tdc, argc, argv); |
| } |
| |
| static void print_cmd(struct command_type *command_type) |
| { |
| if (command_type->parent_id != ID_CMDTYPE_NONE) { |
| print_cmd(__get_cmd_by_id(command_type->parent_id)); |
| pr_out(" "); |
| } |
| pr_out("%s", command_type->name); |
| } |
| |
| static void print_help(const char *argv0) { |
| int i, j; |
| struct command_type *command_type; |
| |
| pr_out("%s [options] teamdevname command [command args]\n" |
| " -h --help Show this help\n" |
| " -v --verbose Increase output verbosity\n" |
| " -o --oneline Force output to one line if possible\n" |
| " -D --force-dbus Force to use D-Bus interface\n" |
| " -Z --force-zmq=ADDRESS Force to use ZeroMQ interface [-Z[Address]]\n" |
| " -U --force-usock Force to use UNIX domain socket interface\n", |
| argv0); |
| pr_out("Commands:\n"); |
| for (i = 0; i < COMMAND_TYPE_COUNT; i++) { |
| command_type = &command_types[i]; |
| if (!__cmd_executable(command_type)) |
| continue; |
| pr_out(" "); |
| print_cmd(command_type); |
| for (j = 0; command_type->params[j]; j++) |
| pr_out(" %s", command_type->params[j]); |
| pr_out("\n"); |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| char *argv0 = argv[0]; |
| char *team_devname; |
| static const struct option long_options[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "verbose", no_argument, NULL, 'v' }, |
| { "oneline", no_argument, NULL, 'o' }, |
| { "force-dbus", no_argument, NULL, 'D' }, |
| { "force-zmq", required_argument, NULL, 'Z' }, |
| { "force-usock", no_argument, NULL, 'U' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| int opt; |
| int err; |
| struct command_type *command_type; |
| struct teamdctl *tdc; |
| int ret; |
| char *addr = NULL; |
| bool force_dbus = false; |
| bool force_zmq = false; |
| bool force_usock = false; |
| |
| while ((opt = getopt_long(argc, argv, "hvoDZ:U", |
| long_options, NULL)) >= 0) { |
| |
| switch(opt) { |
| case 'h': |
| print_help(argv0); |
| return EXIT_SUCCESS; |
| case 'v': |
| g_verbosity++; |
| break; |
| case 'o': |
| g_oneline = true; |
| break; |
| case 'D': |
| #ifndef ENABLE_DBUS |
| fprintf(stderr, "D-Bus support is not compiled-in\n"); |
| return EXIT_FAILURE; |
| #else |
| force_dbus = true; |
| #endif |
| break; |
| case 'Z': |
| #ifndef ENABLE_ZMQ |
| fprintf(stderr, "ZeroMQ support is not compiled-in\n"); |
| return EXIT_FAILURE; |
| #else |
| force_zmq = true; |
| addr = optarg; |
| #endif |
| break; |
| case 'U': |
| force_usock = true; |
| break; |
| case '?': |
| pr_err("unknown option.\n"); |
| print_help(argv0); |
| return EXIT_FAILURE; |
| default: |
| pr_err("unknown option \"%c\".\n", opt); |
| print_help(argv0); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if ((force_usock && force_dbus) || |
| (force_usock && force_zmq) || |
| (force_dbus && force_zmq)) { |
| pr_err("Only one interface could be forced at a time (UNIX domain socket, D-Bus, ZMQ).\n"); |
| print_help(argv0); |
| return EXIT_FAILURE; |
| } |
| |
| if (optind >= argc) { |
| pr_err("No team device specified.\n"); |
| print_help(argv0); |
| return EXIT_FAILURE; |
| } |
| argv += optind; |
| team_devname = *argv++; |
| argc -= optind + 1; |
| |
| err = find_command(&command_type, &argc, &argv); |
| if (err) { |
| print_help(argv0); |
| return EXIT_FAILURE; |
| } |
| err = check_command_params(command_type, argc, argv); |
| if (err) { |
| print_help(argv0); |
| return EXIT_FAILURE; |
| } |
| |
| err = check_team_devname(team_devname); |
| if (err) |
| return EXIT_FAILURE; |
| |
| tdc = teamdctl_alloc(); |
| if (!tdc) { |
| pr_err("teamdctl_alloc failed\n"); |
| return EXIT_FAILURE; |
| } |
| |
| err = teamdctl_connect(tdc, team_devname, addr, |
| (force_usock ? "usock" : (force_dbus ? |
| "dbus": (force_zmq ? "zmq" :NULL)))); |
| if (err) { |
| pr_err("teamdctl_connect failed (%s)\n", strerror(-err)); |
| ret = EXIT_FAILURE; |
| goto teamdctl_free; |
| } |
| |
| err = check_teamd_team_devname(tdc, team_devname); |
| if (err) { |
| ret = EXIT_FAILURE; |
| goto teamdctl_disconnect; |
| } |
| |
| err = call_command(tdc, argc, argv, command_type); |
| if (err) { |
| pr_err("command call failed (%s)\n", strerror(-err)); |
| ret = EXIT_FAILURE; |
| goto teamdctl_disconnect; |
| } |
| |
| ret = EXIT_SUCCESS; |
| |
| teamdctl_disconnect: |
| teamdctl_disconnect(tdc); |
| teamdctl_free: |
| teamdctl_free(tdc); |
| return ret; |
| } |