blob: 3cb4a47a7d323bc7036c0c49446487c769bce85b [file] [log] [blame]
/*
* libteamdctl.c - Teamd daemon control library
* 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
*/
/**
* @defgroup libteamdctl Libteamdctl
* Teamd daemon control library
*
* @{
*
* Header
* ------
* ~~~~{.c}
* #include <teamdctl.h>
* ~~~~
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <private/misc.h>
#include <private/list.h>
#include <teamdctl.h>
#include "config.h"
#include "teamdctl_private.h"
/**
* SECTION: logging
*/
void teamdctl_log(struct teamdctl *tdc, int priority,
const char *file, int line, const char *fn,
const char *format, ...)
{
va_list args;
va_start(args, format);
tdc->log_fn(tdc, priority, file, line, fn, format, args);
va_end(args);
}
static void log_stderr(struct teamdctl *tdc, int priority,
const char *file, int line, const char *fn,
const char *format, va_list args)
{
fprintf(stderr, "libteamdctl: %s: ", fn);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
}
static int log_priority(const char *priority)
{
char *endptr;
int prio;
prio = strtol(priority, &endptr, 10);
if (endptr[0] == '\0' || isspace(endptr[0]))
return prio;
if (strncmp(priority, "err", 3) == 0)
return LOG_ERR;
if (strncmp(priority, "info", 4) == 0)
return LOG_INFO;
if (strncmp(priority, "debug", 5) == 0)
return LOG_DEBUG;
return 0;
}
/**
* SECTION: reply cache
*/
static void reply_cache_clean(struct teamdctl *tdc)
{
struct teamdctl_reply_cache_item *rcitem;
struct teamdctl_reply_cache_item *tmp;
list_for_each_node_entry_safe(rcitem, tmp,
&tdc->reply_cache_list, list) {
list_del(&rcitem->list);
free(rcitem->reply);
free(rcitem);
}
}
static void replace_str(char **p_str, char *new)
{
if (*p_str)
free(*p_str);
*p_str = new;
}
static struct teamdctl_reply_cache_item *find_rcitem(struct teamdctl *tdc,
const char *id)
{
struct teamdctl_reply_cache_item *rcitem;
list_for_each_node_entry(rcitem, &tdc->reply_cache_list, list) {
if (!strcmp(rcitem->id, id))
return rcitem;
}
return NULL;
}
static char *reply_cache_query(struct teamdctl *tdc, const char *id)
{
struct teamdctl_reply_cache_item *rcitem;
rcitem = find_rcitem(tdc, id);
if (rcitem)
return rcitem->reply;
return NULL;
}
static char *reply_cache_update(struct teamdctl *tdc, const char *id,
char *reply)
{
struct teamdctl_reply_cache_item *rcitem;
rcitem = find_rcitem(tdc, id);
if (rcitem)
goto skip_create;
rcitem = myzalloc(sizeof(*rcitem) + strlen(id) + 1);
if (!rcitem) {
free(reply);
return NULL;
}
strcpy(rcitem->id, id);
list_add_tail(&tdc->reply_cache_list, &rcitem->list);
skip_create:
replace_str(&rcitem->reply, reply);
return reply;
}
/**
* @details Allocates library context and does initial setup.
*
* @return New libteam library context.
**/
TEAMDCTL_EXPORT
struct teamdctl *teamdctl_alloc(void)
{
struct teamdctl *tdc;
const char *env;
tdc = myzalloc(sizeof(*tdc));
if (!tdc)
return NULL;
list_init(&tdc->reply_cache_list);
tdc->log_fn = log_stderr;
tdc->log_priority = LOG_ERR;
/* environment overwrites config */
env = getenv("TEAMDCTL_LOG");
if (env != NULL)
teamdctl_set_log_priority(tdc, log_priority(env));
dbg(tdc, "teamdctl %p created.", tdc);
dbg(tdc, "log_priority=%d", tdc->log_priority);
return tdc;
}
/**
* @param tdc libteam library context
*
* @details Do library context cleanup.
**/
TEAMDCTL_EXPORT
void teamdctl_free(struct teamdctl *tdc)
{
reply_cache_clean(tdc);
free(tdc);
}
/**
* @param tdc libteamdctl library context
* @param log_fn function to be called for logging messages
*
* @details The built-in logging writes to stderr. It can be overridden
* by a custom function, to plug log messages into the user's
* logging functionality.
**/
TEAMDCTL_EXPORT
void teamdctl_set_log_fn(struct teamdctl *tdc,
void (*log_fn)(struct teamdctl *tdc, int priority,
const char *file, int line,
const char *fn, const char *format,
va_list args))
{
tdc->log_fn = log_fn;
dbg(tdc, "Custom logging function %p registered.", log_fn);
}
/**
* @param tdc libteamdctl library context
*
* @return The current logging priority.
**/
TEAMDCTL_EXPORT
int teamdctl_get_log_priority(struct teamdctl *tdc)
{
return tdc->log_priority;
}
/**
* @param tdc libteamdctl library context
* @param priority the new logging priority
*
* @details Set the current logging priority. The value controls which messages
* are logged.
**/
TEAMDCTL_EXPORT
void teamdctl_set_log_priority(struct teamdctl *tdc, int priority)
{
tdc->log_priority = priority;
}
static int cli_method_call(struct teamdctl *tdc, const char *method_name,
char **p_reply, const char *fmt, ...)
{
va_list ap;
int err;
va_start(ap, fmt);
err = tdc->cli->method_call(tdc, method_name, p_reply,
tdc->cli_priv, fmt, ap);
va_end(ap);
return err;
}
static int cli_init(struct teamdctl *tdc, const char *team_name)
{
int err;
if (tdc->cli->priv_size) {
tdc->cli_priv = myzalloc(tdc->cli->priv_size);
if (!tdc->cli_priv)
return -ENOMEM;
}
err = tdc->cli->init(tdc, team_name, tdc->cli_priv);
if (err)
goto free_priv;
if (tdc->cli->test_method_call_required) {
err = cli_method_call(tdc, "ConfigDump", NULL, "");
if (err)
goto free_priv;
}
return 0;
free_priv:
if (tdc->cli->priv_size)
free(tdc->cli_priv);
return err;
}
static void cli_fini(struct teamdctl *tdc)
{
tdc->cli->fini(tdc, tdc->cli_priv);
free(tdc->cli_priv);
}
/**
* @param tdc libteamdctl library context
* @param team_name team device name
* @param addr address (for zeromq only)
* @param cli_type client type
*
* @details Connect to teamd instance controlling team driver instance
* with interface name team_name. Use client type cli_type to connect.
* That can be either "dbus" for connection over D-Bus, "usock" which
* will use unix domain socket or NULL to select the type
* automatically.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_connect(struct teamdctl *tdc, const char *team_name,
const char *addr, const char *cli_type)
{
int err;
int i;
const struct teamdctl_cli *teamdctl_cli_list[] = {
teamdctl_cli_usock_get(),
#ifdef ENABLE_DBUS
teamdctl_cli_dbus_get(),
#endif
#ifdef ENABLE_ZMQ
teamdctl_cli_zmq_get(),
#endif
};
#define TEAMDCTL_CLI_LIST_SIZE ARRAY_SIZE(teamdctl_cli_list)
if (tdc->cli)
return -EBUSY;
for (i = 0; i < TEAMDCTL_CLI_LIST_SIZE; i++) {
const struct teamdctl_cli *cli = teamdctl_cli_list[i];
int orig_log_prio = teamdctl_get_log_priority(tdc);
if (cli_type && strcmp(cli_type, cli->name))
continue;
tdc->cli = cli;
/* In case cli_type is not specified, we will try to connect
* using all avaivable clis. Once some of the cli connects,
* it will be selected. In that case, silence error messages
* that can appear during cli init by setting log priority to
* LOG_EMERG.
*/
if (!cli_type && orig_log_prio < LOG_DEBUG)
teamdctl_set_log_priority(tdc, LOG_EMERG);
tdc->addr = (char *) addr;
err = cli_init(tdc, team_name);
/* restore original log priority */
teamdctl_set_log_priority(tdc, orig_log_prio);
if (!err)
break; /* usable cli found */
if (cli_type) {
err(tdc, "Failed to connect using CLI \"%s\".",
cli->name);
goto err_out;
}
}
if (i == TEAMDCTL_CLI_LIST_SIZE) {
if (!cli_type)
err(tdc, "Failed to connect using all CLIs.");
else
err(tdc, "Failed to connect using unknown CLI \"%s\".",
cli_type);
err = -EINVAL;
goto err_out;
}
dbg(tdc, "Connected using CLI \"%s\".", tdc->cli->name);
err = teamdctl_refresh(tdc);
if (err)
goto err_out;
return 0;
err_out:
tdc->cli = NULL;
return err;
}
/**
* @param tdc libteamdctl library context
*
* @details Disconnect from teamd instance.
**/
TEAMDCTL_EXPORT
void teamdctl_disconnect(struct teamdctl *tdc)
{
cli_fini(tdc);
tdc->cli = NULL;
}
static int cache_config(struct teamdctl *tdc, const char *id, char **p_reply)
{
int err;
char *reply;
err = cli_method_call(tdc, id, &reply, "");
if (err)
return err;
reply = reply_cache_update(tdc, id, reply);
if (!reply)
return -ENOMEM;
if (p_reply)
*p_reply = reply;
return 0;
}
/**
* @param tdc libteamdctl library context
*
* @details Refresh cache.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_refresh(struct teamdctl *tdc)
{
int err;
err = cache_config(tdc, "ConfigDump", NULL);
if (err)
return err;
err = cache_config(tdc, "ConfigDumpActual", NULL);
if (err)
return err;
err = cache_config(tdc, "StateDump", NULL);
if (err)
return err;
return 0;
}
/**
* @param tdc libteamdctl library context
* @param port_devname port device name
*
* @details Adds specified port to team.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_port_add(struct teamdctl *tdc, const char *port_devname)
{
return cli_method_call(tdc, "PortAdd", NULL, "s", port_devname);
}
/**
* @param tdc libteamdctl library context
* @param port_devname port device name
*
* @details Removes specified port from team.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_port_remove(struct teamdctl *tdc, const char *port_devname)
{
return cli_method_call(tdc, "PortRemove", NULL, "s", port_devname);
}
/**
* @param tdc libteamdctl library context
* @param port_devname port device name
* @param port_config_raw port config
*
* @details Update config for specified port.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_port_config_update_raw(struct teamdctl *tdc,
const char *port_devname,
const char *port_config_raw)
{
return cli_method_call(tdc, "PortConfigUpdate", NULL,
"ss", port_devname, port_config_raw);
}
/**
* @param tdc libteamdctl library context
* @param port_devname port device name
* @param p_cfg pointer to string which will be set
*
* @details Gets raw port config string.
* Does direct method call avoiding possible stale data in the cache.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_port_config_get_raw_direct(struct teamdctl *tdc,
const char *port_devname,
char **p_cfg)
{
int err;
char *reply;
#define PC_ID_PREFIX "_portcnf_"
char id[sizeof(PC_ID_PREFIX) + IFNAMSIZ + 1];
if (strlen(port_devname) > IFNAMSIZ)
return -EINVAL;
err = cli_method_call(tdc, "PortConfigDump", &reply, "s", port_devname);
if (err)
return err;
sprintf(id, "%s%s", PC_ID_PREFIX, port_devname);
reply = reply_cache_update(tdc, id, reply);
if (!reply)
return -ENOMEM;
if (p_cfg)
*p_cfg = reply;
return 0;
}
/**
* @param tdc libteamdctl library context
*
* @details Gets raw config string.
* Using reply cache. Return value is never NULL.
* To refresh the cache, use teamdctl_refresh function.
* Note: the obtained string should not be modified or freed by caller.
*
* Return Pointer to cached config string.
**/
TEAMDCTL_EXPORT
char *teamdctl_config_get_raw(struct teamdctl *tdc)
{
return reply_cache_query(tdc, "ConfigDump");
}
/**
* @param tdc libteamdctl library context
* @param p_cfg pointer to string which will be set
*
* @details Gets raw config string.
* Does direct method call avoiding possible stale data in the cache.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_config_get_raw_direct(struct teamdctl *tdc, char **p_cfg)
{
return cache_config(tdc, "ConfigDump", p_cfg);
}
/**
* @param tdc libteamdctl library context
*
* @details Gets raw actual config string.
* Using reply cache. Return value is never NULL.
* To refresh the cache, use teamdctl_refresh function.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Pointer to cached actual config string.
**/
TEAMDCTL_EXPORT
char *teamdctl_config_actual_get_raw(struct teamdctl *tdc)
{
return reply_cache_query(tdc, "ConfigDumpActual");
}
/**
* @param tdc libteamdctl library context
* @param p_cfg pointer to string which will be set
*
* @details Gets raw actual config string.
* Does direct method call avoiding possible stale data in the cache.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_config_actual_get_raw_direct(struct teamdctl *tdc, char **p_cfg)
{
return cache_config(tdc, "ConfigDumpActual", p_cfg);
}
/**
* @param tdc libteamdctl library context
*
* @details Gets raw state string.
* Using reply cache. Return value is never NULL.
* To refresh the cache, use teamdctl_refresh function.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Pointer to cached state string.
**/
TEAMDCTL_EXPORT
char *teamdctl_state_get_raw(struct teamdctl *tdc)
{
return reply_cache_query(tdc, "StateDump");
}
/**
* @param tdc libteamdctl library context
* @param p_cfg pointer to string which will be set
*
* @details Gets raw state string.
* Does direct method call avoiding possible stale data in the cache.
* Note: the obtained string should not be modified or freed by caller.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_state_get_raw_direct(struct teamdctl *tdc, char **p_cfg)
{
return cache_config(tdc, "StateDump", p_cfg);
}
/**
* @param tdc libteamdctl library context
* @param item_path path to item
* @param p_value pointer where reply string will be stored
*
* @details Get state item value. Note that caller is responsible to
* free *p_value.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_state_item_value_get(struct teamdctl *tdc, const char *item_path,
char **p_value)
{
return cli_method_call(tdc, "StateItemValueGet", p_value,
"s", item_path);
}
/**
* @param tdc libteamdctl library context
* @param item_path path to item
* @param value new value to be set
*
* @details Set state item value.
*
* @return Zero on success or negative number in case of an error.
**/
TEAMDCTL_EXPORT
int teamdctl_state_item_value_set(struct teamdctl *tdc, const char *item_path,
const char *value)
{
return cli_method_call(tdc, "StateItemValueSet", NULL,
"ss", item_path, value);
}
/**
* @}
*/