blob: c53345d2424d61bb3f3ca4eb8e365e2df4039d6d [file] [log] [blame]
/*
* teamnl.c - Team device Netlink tool
* Copyright (C) 2012-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 <limits.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <team.h>
#include <private/misc.h>
#define CMD_PARAM_MAX_CNT 8
struct cmd_ctx {
int argc;
char **argv;
char *port_devname_arg;
char *array_index_arg;
bool port_ifindex_present;
uint32_t port_ifindex;
bool array_index_present;
uint32_t array_index;
};
typedef int (*run_cmd_t)(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx);
struct cmd_type {
char *name;
char *params[CMD_PARAM_MAX_CNT];
run_cmd_t run_cmd;
};
static int run_cmd_ports(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
struct team_port *port;
char buf[120];
bool trunc;
team_for_each_port(port, th) {
trunc = team_port_str(port, buf, sizeof(buf));
printf("%s %s\n", buf, trunc ? "<trunc>" : "");
}
return 0;
}
static int run_cmd_options(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
struct team_option *option;
char buf[120];
bool trunc;
team_for_each_option(option, th) {
trunc = team_option_str(th, option, buf, sizeof(buf));
printf("%s %s\n", buf, trunc ? "<trunc>" : "");
}
return 0;
}
static struct team_option *__find_option(struct team_handle *th, char *opt_name,
struct cmd_ctx *cmd_ctx)
{
if (cmd_ctx->array_index_present && cmd_ctx->port_ifindex_present)
return team_get_option(th, "npa", opt_name,
cmd_ctx->port_ifindex,
cmd_ctx->array_index);
else if (cmd_ctx->array_index_present)
return team_get_option(th, "na", opt_name,
cmd_ctx->array_index);
else if (cmd_ctx->port_ifindex_present)
return team_get_option(th, "np", opt_name,
cmd_ctx->port_ifindex);
else
return team_get_option(th, "n", opt_name);
}
#define BUFSIZSTEP 1024
static int run_cmd_getoption(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
struct team_option *option;
char *buf = NULL;
char *tmpbuf;
size_t bufsiz = 0;
bool trunc;
if (cmd_ctx->argc < 1) {
fprintf(stderr, "%s: Option name as a command line parameter expected.\n",
cmd_name);
return -EINVAL;
}
option = __find_option(th, cmd_ctx->argv[0], cmd_ctx);
if (!option)
return -ENOENT;
do {
bufsiz += BUFSIZSTEP;
tmpbuf = realloc(buf, bufsiz);
if (!tmpbuf) {
free(buf);
return -ENOMEM;
}
buf = tmpbuf;
trunc = team_option_value_str(option, buf, bufsiz);
} while(trunc);
printf("%s\n", buf);
free(buf);
return 0;
}
static int run_cmd_setoption(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
struct team_option *option;
if (cmd_ctx->argc < 1) {
fprintf(stderr, "%s: Option name as a command line parameter expected.\n",
cmd_name);
return -EINVAL;
}
if (cmd_ctx->argc < 2) {
fprintf(stderr, "%s: Option value as a command line parameter expected.\n",
cmd_name);
return -EINVAL;
}
option = __find_option(th, cmd_ctx->argv[0], cmd_ctx);
if (!option)
return -ENOENT;
return team_set_option_value_from_string(th, option, cmd_ctx->argv[1]);
}
static int run_main_loop(struct team_handle *th)
{
fd_set rfds;
fd_set rfds_tmp;
int fdmax;
int ret;
sigset_t mask;
int sfd;
int tfd;
int err = 0;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
ret = sigprocmask(SIG_BLOCK, &mask, NULL);
if (ret == -1) {
fprintf(stderr, "Failed to set blocked signals\n");
return -errno;
}
sfd = signalfd(-1, &mask, 0);
if (sfd == -1) {
fprintf(stderr, "Failed to open signalfd\n");
return -errno;
}
FD_ZERO(&rfds);
FD_SET(sfd, &rfds);
fdmax = sfd;
tfd = team_get_event_fd(th);
FD_SET(tfd, &rfds);
if (tfd > fdmax)
fdmax = tfd;
fdmax++;
for (;;) {
rfds_tmp = rfds;
ret = select(fdmax, &rfds_tmp, NULL, NULL, NULL);
if (ret == -1) {
fprintf(stderr, "Select failed\n");
err = -errno;
goto out;
}
if (FD_ISSET(sfd, &rfds_tmp)) {
struct signalfd_siginfo fdsi;
ssize_t len;
len = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
if (len != sizeof(struct signalfd_siginfo)) {
fprintf(stderr, "Unexpected data length came from signalfd\n");
err = -EINVAL;
goto out;
}
switch (fdsi.ssi_signo) {
case SIGINT:
case SIGQUIT:
case SIGTERM:
goto out;
default:
fprintf(stderr, "Read unexpected signal\n");
err = -EINVAL;
goto out;
}
}
if (FD_ISSET(tfd, &rfds_tmp)) {
err = team_handle_events(th);
if (err) {
fprintf(stderr, "Team handle events failed\n");
return err;
}
}
}
out:
close(sfd);
return err;
}
enum {
MONITOR_STYLE_CHANGED,
MONITOR_STYLE_ALL,
};
struct monitor_priv {
unsigned int style;
};
static bool __should_show(struct monitor_priv *mpriv, bool changed)
{
if (mpriv->style == MONITOR_STYLE_ALL)
return true;
if (mpriv->style == MONITOR_STYLE_CHANGED && changed)
return true;
return false;
}
static void monitor_port_list(struct team_handle *th,
struct monitor_priv *mpriv)
{
struct team_port *port;
char buf[120];
bool trunc;
bool skip = true;
team_for_each_port(port, th) {
if (__should_show(mpriv, team_is_port_changed(port))) {
skip = false;
break;
}
}
if (skip)
return;
printf("ports:\n");
team_for_each_port(port, th) {
if (!__should_show(mpriv, team_is_port_changed(port)))
continue;
trunc = team_port_str(port, buf, sizeof(buf));
printf(" %s %s\n", buf, trunc ? "..." : "");
}
}
static void monitor_option_list(struct team_handle *th,
struct monitor_priv *mpriv)
{
struct team_option *option;
char buf[120];
bool trunc;
bool skip = true;
team_for_each_option(option, th) {
if (__should_show(mpriv, team_is_option_changed(option))) {
skip = false;
break;
}
}
if (skip)
return;
printf("options:\n");
team_for_each_option(option, th) {
if (!__should_show(mpriv, team_is_option_changed(option)))
continue;
trunc = team_option_str(th, option, buf, sizeof(buf));
printf(" %s%s%s\n", buf, trunc ? "..." : "",
team_is_option_changed(option) ? " changed" : "");
}
}
static void monitor_ifinfo_list(struct team_handle *th,
struct monitor_priv *mpriv)
{
struct team_ifinfo *ifinfo;
char buf[120];
bool trunc;
bool skip = true;
team_for_each_ifinfo(ifinfo, th) {
if (__should_show(mpriv, team_is_ifinfo_changed(ifinfo))) {
skip = false;
break;
}
}
if (skip)
return;
printf("ifinfos:\n");
team_for_each_ifinfo(ifinfo, th) {
if (!__should_show(mpriv, team_is_ifinfo_changed(ifinfo)))
continue;
trunc = team_ifinfo_str(ifinfo, buf, sizeof(buf));
printf(" %s %s\n", buf, trunc ? "..." : "");
}
}
static int debug_change_handler_func(struct team_handle *th, void *priv,
team_change_type_mask_t type_mask)
{
struct monitor_priv *mpriv = priv;
if (type_mask & TEAM_PORT_CHANGE)
monitor_port_list(th, mpriv);
if (type_mask & TEAM_OPTION_CHANGE)
monitor_option_list(th, mpriv);
if (type_mask & TEAM_IFINFO_CHANGE)
monitor_ifinfo_list(th, mpriv);
return 0;
}
static const struct team_change_handler debug_change_handler = {
.func = debug_change_handler_func,
.type_mask = TEAM_PORT_CHANGE | TEAM_OPTION_CHANGE | TEAM_IFINFO_CHANGE,
};
static int run_cmd_monitor(char *cmd_name, struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
struct monitor_priv mpriv;
int err;
mpriv.style = MONITOR_STYLE_CHANGED;
if (cmd_ctx->argc > 0) {
char *monitor_style_str = cmd_ctx->argv[0];
if (!strncmp(monitor_style_str, "all",
strlen(monitor_style_str))) {
mpriv.style = MONITOR_STYLE_ALL;
} else if (!strncmp(monitor_style_str, "changed",
strlen(monitor_style_str))) {
mpriv.style = MONITOR_STYLE_CHANGED;
} else {
fprintf(stderr, "Unknown monitor style \"%s\"\n",
monitor_style_str);
return -EINVAL;
}
}
err = team_change_handler_register(th, &debug_change_handler, &mpriv);
if (err) {
fprintf(stderr, "Failed to register change handler\n");
return err;
}
err = run_main_loop(th);
team_change_handler_unregister(th, &debug_change_handler, &mpriv);
return err;
}
static struct cmd_type cmd_types[] = {
{
.name = "ports",
.params = { NULL },
.run_cmd = run_cmd_ports,
},
{
.name = "options",
.params = { NULL },
.run_cmd = run_cmd_options,
},
{
.name = "getoption",
.params = { "OPT_NAME", NULL },
.run_cmd = run_cmd_getoption,
},
{
.name = "setoption",
.params = { "OPT_NAME", "OPT_VALUE", NULL },
.run_cmd = run_cmd_setoption,
},
{
.name = "monitor",
.params = { "OPT_STYLE", NULL },
.run_cmd = run_cmd_monitor,
},
};
#define CMD_TYPE_COUNT ARRAY_SIZE(cmd_types)
static int process_port_devname_arg(struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
uint32_t port_ifindex;
struct team_port *port;
if (!cmd_ctx->port_devname_arg)
return 0;
port_ifindex = team_ifname2ifindex(th, cmd_ctx->port_devname_arg);
if (!port_ifindex) {
fprintf(stderr, "Netdevice \"%s\" not found.\n",
cmd_ctx->port_devname_arg);
return -ENODEV;
}
team_for_each_port(port, th) {
if (port_ifindex == team_get_port_ifindex(port)) {
cmd_ctx->port_ifindex_present = true;
cmd_ctx->port_ifindex = port_ifindex;
return 0;
}
}
fprintf(stderr, "Netdevice \"%s\" is not port of this team.\n",
cmd_ctx->port_devname_arg);
return -ENODEV;
}
static int process_array_index_arg(struct team_handle *th,
struct cmd_ctx *cmd_ctx)
{
uint32_t array_index;
unsigned long int tmp;
char *endptr;
if (!cmd_ctx->array_index_arg)
return 0;
tmp = strtoul(cmd_ctx->array_index_arg, &endptr, 10);
if (tmp == ULONG_MAX) {
fprintf(stderr, "Failed to parse array index.\n");
return -errno;
}
if (strlen(endptr) != 0) {
fprintf(stderr, "Failed to parse array index.\n");
return -EINVAL;
}
array_index = tmp;
if (tmp != array_index) {
fprintf(stderr, "Array index too big.\n");
return -ERANGE;
}
cmd_ctx->array_index_present = true;
cmd_ctx->array_index = array_index;
return 0;
}
static int process_args(struct team_handle *th, struct cmd_ctx *cmd_ctx)
{
int err;
err = process_port_devname_arg(th, cmd_ctx);
if (err)
return err;
return process_array_index_arg(th, cmd_ctx);
}
static int call_cmd(char *team_devname, char *cmd_name,
struct cmd_ctx *cmd_ctx, run_cmd_t run_cmd)
{
struct team_handle *th;
uint32_t ifindex;
int err;
th = team_alloc();
if (!th) {
fprintf(stderr, "Team alloc failed.\n");
return -ENOMEM;
}
ifindex = team_ifname2ifindex(th, team_devname);
if (!ifindex) {
fprintf(stderr, "Netdevice \"%s\" not found.\n", team_devname);
err = -ENODEV;
goto team_free;
}
err = team_init(th, ifindex);
if (err) {
fprintf(stderr, "Team init failed.\n");
goto team_free;
}
err = process_args(th, cmd_ctx);
if (err)
goto team_free;
err = run_cmd(cmd_name, th, cmd_ctx);
team_free:
team_free(th);
return err;
}
static void print_help(const char *argv0) {
int i, j;
printf(
"%s [options] teamdevname command [command args]\n"
"\t-h --help Show this help\n"
"\t-p --port_name team slave port name\n"
"\t-a --array_index team option array index\n",
argv0);
printf("Commands:\n");
for (i = 0; i < CMD_TYPE_COUNT; i++) {
printf("\t%s", cmd_types[i].name);
for (j = 0; cmd_types[i].params[j]; j++)
printf(" %s", cmd_types[i].params[j]);
printf("\n");
}
}
int main(int argc, char **argv)
{
char *argv0 = argv[0];
char *team_devname;
char *cmd_name;
struct cmd_ctx cmd_ctx;
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "port_name", required_argument, NULL, 'p' },
{ "array_index", required_argument, NULL, 'a' },
{ NULL, 0, NULL, 0 }
};
int opt;
int err;
int i;
int res = EXIT_FAILURE;
memset(&cmd_ctx, 0, sizeof(cmd_ctx));
while ((opt = getopt_long(argc, argv, "hp:a:",
long_options, NULL)) >= 0) {
switch(opt) {
case 'h':
print_help(argv0);
return EXIT_SUCCESS;
case 'p':
free(cmd_ctx.port_devname_arg);
cmd_ctx.port_devname_arg = strdup(optarg);
break;
case 'a':
free(cmd_ctx.array_index_arg);
cmd_ctx.array_index_arg = strdup(optarg);
break;
case '?':
fprintf(stderr, "unknown option.\n");
print_help(argv0);
return EXIT_FAILURE;
default:
fprintf(stderr, "unknown option \"%c\".\n", opt);
print_help(argv0);
return EXIT_FAILURE;
}
}
if (optind >= argc) {
fprintf(stderr, "No team device specified.\n");
printf("\n");
print_help(argv0);
goto errout;
}
if (optind + 1 >= argc) {
fprintf(stderr, "No command specified.\n");
printf("\n");
print_help(argv0);
goto errout;
}
argv += optind;
team_devname = *argv++;
cmd_name = *argv++;
argc -= optind + 2;
cmd_ctx.argc = argc;
cmd_ctx.argv = argv;
for (i = 0; i < CMD_TYPE_COUNT; i++) {
if (strncmp(cmd_types[i].name, cmd_name, strlen(cmd_name)))
continue;
err = call_cmd(team_devname, cmd_name, &cmd_ctx,
cmd_types[i].run_cmd);
if (err) {
fprintf(stderr, "Command failed: %s\n", strerror(-err));
goto errout;
}
break;
}
if (i == CMD_TYPE_COUNT) {
fprintf(stderr, "Unknown command \"%s\".\n", cmd_name);
printf("\n");
print_help(argv0);
goto errout;
}
res = EXIT_SUCCESS;
errout:
free(cmd_ctx.port_devname_arg);
free(cmd_ctx.array_index_arg);
return res;
}